diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.mobile.impl/src/main/java/org/wso2/carbon/device/mgt/mobile/impl/android/AndroidFeatureManager.java b/components/device-mgt/org.wso2.carbon.device.mgt.mobile.impl/src/main/java/org/wso2/carbon/device/mgt/mobile/impl/android/AndroidFeatureManager.java index 3cbabce0e1..6913d53168 100644 --- a/components/device-mgt/org.wso2.carbon.device.mgt.mobile.impl/src/main/java/org/wso2/carbon/device/mgt/mobile/impl/android/AndroidFeatureManager.java +++ b/components/device-mgt/org.wso2.carbon.device.mgt.mobile.impl/src/main/java/org/wso2/carbon/device/mgt/mobile/impl/android/AndroidFeatureManager.java @@ -266,6 +266,19 @@ public class AndroidFeatureManager implements FeatureManager { feature.setDescription("Send message"); supportedFeatures.add(feature); + feature = new Feature(); + feature.setCode("DEVICE_REBOOT"); + feature.setName("Reboot"); + feature.setDescription("Reboot the device"); + supportedFeatures.add(feature); + + feature = new Feature(); + feature.setCode("UPGRADE_FIRMWARE"); + feature.setName("Upgrade Firmware"); + feature.setDescription("Upgrade Firmware"); + supportedFeatures.add(feature); + return supportedFeatures; } -} \ No newline at end of file + +} diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/build.xml b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/build.xml new file mode 100644 index 0000000000..b0b52fc6ba --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/build.xml @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/pom.xml b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/pom.xml new file mode 100644 index 0000000000..a3a622fc41 --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/pom.xml @@ -0,0 +1,90 @@ + + + + + + androidsense-plugin + org.wso2.carbon.devicemgt-plugins + 2.1.0-SNAPSHOT + ../pom.xml + + + 4.0.0 + org.wso2.carbon.device.mgt.iot.androidsense.analytics + WSO2 Carbon - IoT Server Android Sense Analytics capp + pom + + + + + maven-clean-plugin + 2.4.1 + + + auto-clean + initialize + + clean + + + + + + maven-antrun-plugin + 1.7 + + + process-resources + + + + + + + run + + + + + + maven-assembly-plugin + 2.5.5 + + ${project.artifactId}-${carbon.device.mgt.version} + false + + src/assembly/src.xml + + + + + create-archive + package + + single + + + + + + + + \ No newline at end of file diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/assembly/src.xml b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/assembly/src.xml new file mode 100644 index 0000000000..a5a375010e --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/assembly/src.xml @@ -0,0 +1,36 @@ + + + + src + + zip + + false + ${basedir}/src + + + ${basedir}/target/carbonapps + / + true + + + \ No newline at end of file diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Accelerometer/Eventreceiver_accelerometer_1.0.0/EventReceiver_accelerometer.xml b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Accelerometer/Eventreceiver_accelerometer_1.0.0/EventReceiver_accelerometer.xml new file mode 100644 index 0000000000..cf95025b6c --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Accelerometer/Eventreceiver_accelerometer_1.0.0/EventReceiver_accelerometer.xml @@ -0,0 +1,26 @@ + + + + + + false + + + + diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Accelerometer/Eventreceiver_accelerometer_1.0.0/artifact.xml b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Accelerometer/Eventreceiver_accelerometer_1.0.0/artifact.xml new file mode 100644 index 0000000000..aeead5417a --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Accelerometer/Eventreceiver_accelerometer_1.0.0/artifact.xml @@ -0,0 +1,22 @@ + + + + + EventReceiver_accelerometer.xml + diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Accelerometer/Eventstore_accelerometer_1.0.0/artifact.xml b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Accelerometer/Eventstore_accelerometer_1.0.0/artifact.xml new file mode 100644 index 0000000000..cc7c499075 --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Accelerometer/Eventstore_accelerometer_1.0.0/artifact.xml @@ -0,0 +1,22 @@ + + + + + org_wso2_iot_devices_accelerometer.xml + diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Accelerometer/Eventstore_accelerometer_1.0.0/org_wso2_iot_devices_accelerometer.xml b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Accelerometer/Eventstore_accelerometer_1.0.0/org_wso2_iot_devices_accelerometer.xml new file mode 100644 index 0000000000..5e2d427c4f --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Accelerometer/Eventstore_accelerometer_1.0.0/org_wso2_iot_devices_accelerometer.xml @@ -0,0 +1,62 @@ + + + + + + org.wso2.iot.devices.accelerometer: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 + + + accelerometer + false + false + false + FLOAT + + + \ No newline at end of file diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Accelerometer/Eventstream_accelerometer_1.0.0/artifact.xml b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Accelerometer/Eventstream_accelerometer_1.0.0/artifact.xml new file mode 100644 index 0000000000..4a9d840f94 --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Accelerometer/Eventstream_accelerometer_1.0.0/artifact.xml @@ -0,0 +1,23 @@ + + + + + org.wso2.iot.devices.accelerometer_1.0.0.json + + diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Accelerometer/Eventstream_accelerometer_1.0.0/org.wso2.iot.devices.accelerometer_1.0.0.json b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Accelerometer/Eventstream_accelerometer_1.0.0/org.wso2.iot.devices.accelerometer_1.0.0.json new file mode 100644 index 0000000000..db6af8eecb --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Accelerometer/Eventstream_accelerometer_1.0.0/org.wso2.iot.devices.accelerometer_1.0.0.json @@ -0,0 +1,20 @@ +{ + "name": "org.wso2.iot.devices.accelerometer", + "version": "1.0.0", + "nickName": "accelerometer Data", + "description": "accelerometer data received from the Device", + "metaData": [ + {"name":"owner","type":"STRING"}, + {"name":"deviceType","type":"STRING"}, + {"name":"deviceId","type":"STRING"}, + {"name":"time","type":"LONG"} + ], + "payloadData": [ + { + "name": "accelerometer","type": "FLOAT" + } + ] +} + + + diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Accelerometer/Sparkscripts_1.0.0/Accelerometer_Sensor_Script.xml b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Accelerometer/Sparkscripts_1.0.0/Accelerometer_Sensor_Script.xml new file mode 100644 index 0000000000..e1533c75c3 --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Accelerometer/Sparkscripts_1.0.0/Accelerometer_Sensor_Script.xml @@ -0,0 +1,31 @@ + + + + + Accelerometer_Sensor_Script + + 0 * * * * ? + diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Accelerometer/Sparkscripts_1.0.0/artifact.xml b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Accelerometer/Sparkscripts_1.0.0/artifact.xml new file mode 100644 index 0000000000..3cdac3edda --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Accelerometer/Sparkscripts_1.0.0/artifact.xml @@ -0,0 +1,23 @@ + + + + + Accelerometer_Sensor_Script.xml + + diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Accelerometer/artifacts.xml b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Accelerometer/artifacts.xml new file mode 100644 index 0000000000..bc58f6a369 --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Accelerometer/artifacts.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Battery/Eventreceiver_battery_1.0.0/EventReceiver_battery.xml b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Battery/Eventreceiver_battery_1.0.0/EventReceiver_battery.xml new file mode 100644 index 0000000000..af55acea81 --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Battery/Eventreceiver_battery_1.0.0/EventReceiver_battery.xml @@ -0,0 +1,26 @@ + + + + + + false + + + + diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Battery/Eventreceiver_battery_1.0.0/artifact.xml b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Battery/Eventreceiver_battery_1.0.0/artifact.xml new file mode 100644 index 0000000000..3b963dafc7 --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Battery/Eventreceiver_battery_1.0.0/artifact.xml @@ -0,0 +1,22 @@ + + + + + EventReceiver_battery.xml + diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Battery/Eventstore_battery_1.0.0/artifact.xml b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Battery/Eventstore_battery_1.0.0/artifact.xml new file mode 100644 index 0000000000..6bc9aadd88 --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Battery/Eventstore_battery_1.0.0/artifact.xml @@ -0,0 +1,22 @@ + + + + + org_wso2_iot_devices_battery.xml + diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Battery/Eventstore_battery_1.0.0/org_wso2_iot_devices_battery.xml b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Battery/Eventstore_battery_1.0.0/org_wso2_iot_devices_battery.xml new file mode 100644 index 0000000000..ce70882da6 --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Battery/Eventstore_battery_1.0.0/org_wso2_iot_devices_battery.xml @@ -0,0 +1,62 @@ + + + + + + org.wso2.iot.devices.battery: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 + + + battery + false + false + false + FLOAT + + + \ No newline at end of file diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Battery/Eventstream_battery_1.0.0/artifact.xml b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Battery/Eventstream_battery_1.0.0/artifact.xml new file mode 100644 index 0000000000..a03e58d404 --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Battery/Eventstream_battery_1.0.0/artifact.xml @@ -0,0 +1,23 @@ + + + + + org.wso2.iot.devices.battery_1.0.0.json + + diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Battery/Eventstream_battery_1.0.0/org.wso2.iot.devices.battery_1.0.0.json b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Battery/Eventstream_battery_1.0.0/org.wso2.iot.devices.battery_1.0.0.json new file mode 100644 index 0000000000..c0a2106f03 --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Battery/Eventstream_battery_1.0.0/org.wso2.iot.devices.battery_1.0.0.json @@ -0,0 +1,20 @@ +{ + "name": "org.wso2.iot.devices.battery", + "version": "1.0.0", + "nickName": "battery Data", + "description": "battery data received from the Device", + "metaData": [ + {"name":"owner","type":"STRING"}, + {"name":"deviceType","type":"STRING"}, + {"name":"deviceId","type":"STRING"}, + {"name":"time","type":"LONG"} + ], + "payloadData": [ + { + "name": "battery","type": "FLOAT" + } + ] +} + + + diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Battery/Sparkscripts_1.0.0/Battery_Sensor_Script.xml b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Battery/Sparkscripts_1.0.0/Battery_Sensor_Script.xml new file mode 100644 index 0000000000..07559e3429 --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Battery/Sparkscripts_1.0.0/Battery_Sensor_Script.xml @@ -0,0 +1,31 @@ + + + + + Battery_Sensor_Script + + 0 * * * * ? + diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Battery/Sparkscripts_1.0.0/artifact.xml b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Battery/Sparkscripts_1.0.0/artifact.xml new file mode 100644 index 0000000000..b573355bbf --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Battery/Sparkscripts_1.0.0/artifact.xml @@ -0,0 +1,22 @@ + + + + + Battery_Sensor_Script.xml + diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Battery/artifacts.xml b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Battery/artifacts.xml new file mode 100644 index 0000000000..2b72bb5b9c --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Battery/artifacts.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/GPS/Eventreceiver_gps_1.0.0/EventReceiver_gps.xml b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/GPS/Eventreceiver_gps_1.0.0/EventReceiver_gps.xml new file mode 100644 index 0000000000..dd51b28179 --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/GPS/Eventreceiver_gps_1.0.0/EventReceiver_gps.xml @@ -0,0 +1,26 @@ + + + + + + false + + + + diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/GPS/Eventreceiver_gps_1.0.0/artifact.xml b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/GPS/Eventreceiver_gps_1.0.0/artifact.xml new file mode 100644 index 0000000000..4ae981667a --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/GPS/Eventreceiver_gps_1.0.0/artifact.xml @@ -0,0 +1,22 @@ + + + + + EventReceiver_gps.xml + diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/GPS/Eventstore_gps_1.0.0/artifact.xml b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/GPS/Eventstore_gps_1.0.0/artifact.xml new file mode 100644 index 0000000000..1ad7609e65 --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/GPS/Eventstore_gps_1.0.0/artifact.xml @@ -0,0 +1,22 @@ + + + + + org_wso2_iot_devices_gps.xml + diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/GPS/Eventstore_gps_1.0.0/org_wso2_iot_devices_gps.xml b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/GPS/Eventstore_gps_1.0.0/org_wso2_iot_devices_gps.xml new file mode 100644 index 0000000000..781c37ab08 --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/GPS/Eventstore_gps_1.0.0/org_wso2_iot_devices_gps.xml @@ -0,0 +1,69 @@ + + + + + + org.wso2.iot.devices.gps: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 + + + latitude + false + false + false + FLOAT + + + longitude + false + false + false + FLOAT + + + \ No newline at end of file diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/GPS/Eventstream_gps_1.0.0/artifact.xml b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/GPS/Eventstream_gps_1.0.0/artifact.xml new file mode 100644 index 0000000000..2028042e0e --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/GPS/Eventstream_gps_1.0.0/artifact.xml @@ -0,0 +1,23 @@ + + + + + org.wso2.iot.devices.gps_1.0.0.json + + diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/GPS/Eventstream_gps_1.0.0/org.wso2.iot.devices.gps_1.0.0.json b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/GPS/Eventstream_gps_1.0.0/org.wso2.iot.devices.gps_1.0.0.json new file mode 100644 index 0000000000..04d2b10699 --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/GPS/Eventstream_gps_1.0.0/org.wso2.iot.devices.gps_1.0.0.json @@ -0,0 +1,23 @@ +{ + "name": "org.wso2.iot.devices.gps", + "version": "1.0.0", + "nickName": "GPS Data", + "description": "GPS data received from the Device", + "metaData": [ + {"name":"owner","type":"STRING"}, + {"name":"deviceType","type":"STRING"}, + {"name":"deviceId","type":"STRING"}, + {"name":"time","type":"LONG"} + ], + "payloadData": [ + { + "name": "latitude","type": "FLOAT" + }, + { + "name": "longitude","type": "FLOAT" + } + ] +} + + + diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/GPS/Sparkscripts_1.0.0/GPS_Sensor_Script.xml b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/GPS/Sparkscripts_1.0.0/GPS_Sensor_Script.xml new file mode 100644 index 0000000000..e9f0b83a71 --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/GPS/Sparkscripts_1.0.0/GPS_Sensor_Script.xml @@ -0,0 +1,31 @@ + + + + + IoTServer_Sensor_Script + + 0 * * * * ? + diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/GPS/Sparkscripts_1.0.0/artifact.xml b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/GPS/Sparkscripts_1.0.0/artifact.xml new file mode 100644 index 0000000000..87dcbba09d --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/GPS/Sparkscripts_1.0.0/artifact.xml @@ -0,0 +1,22 @@ + + + + + GPS_Sensor_Script.xml + diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/GPS/artifacts.xml b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/GPS/artifacts.xml new file mode 100644 index 0000000000..750796e829 --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/GPS/artifacts.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Gravity/Eventreceiver_gravaity_1.0.0/EventReceiver_gravity.xml b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Gravity/Eventreceiver_gravaity_1.0.0/EventReceiver_gravity.xml new file mode 100644 index 0000000000..a1d23503a9 --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Gravity/Eventreceiver_gravaity_1.0.0/EventReceiver_gravity.xml @@ -0,0 +1,26 @@ + + + + + + false + + + + diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Gravity/Eventreceiver_gravaity_1.0.0/artifact.xml b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Gravity/Eventreceiver_gravaity_1.0.0/artifact.xml new file mode 100644 index 0000000000..00108bb4e5 --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Gravity/Eventreceiver_gravaity_1.0.0/artifact.xml @@ -0,0 +1,22 @@ + + + + + EventReceiver_gravity.xml + diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Gravity/Eventstore_gravity_1.0.0/artifact.xml b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Gravity/Eventstore_gravity_1.0.0/artifact.xml new file mode 100644 index 0000000000..0abc61558d --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Gravity/Eventstore_gravity_1.0.0/artifact.xml @@ -0,0 +1,22 @@ + + + + + org_wso2_iot_devices_gravity.xml + diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Gravity/Eventstore_gravity_1.0.0/org_wso2_iot_devices_gravity.xml b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Gravity/Eventstore_gravity_1.0.0/org_wso2_iot_devices_gravity.xml new file mode 100644 index 0000000000..2c68c0d43d --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Gravity/Eventstore_gravity_1.0.0/org_wso2_iot_devices_gravity.xml @@ -0,0 +1,62 @@ + + + + + + org.wso2.iot.devices.gravity: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 + + + gravity + false + false + false + FLOAT + + + \ No newline at end of file diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Gravity/Eventstream_gravity_1.0.0/artifact.xml b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Gravity/Eventstream_gravity_1.0.0/artifact.xml new file mode 100644 index 0000000000..7d6e0d5f61 --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Gravity/Eventstream_gravity_1.0.0/artifact.xml @@ -0,0 +1,23 @@ + + + + + org.wso2.iot.devices.gravity_1.0.0.json + + diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Gravity/Eventstream_gravity_1.0.0/org.wso2.iot.devices.gravity_1.0.0.json b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Gravity/Eventstream_gravity_1.0.0/org.wso2.iot.devices.gravity_1.0.0.json new file mode 100644 index 0000000000..f18bd87be1 --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Gravity/Eventstream_gravity_1.0.0/org.wso2.iot.devices.gravity_1.0.0.json @@ -0,0 +1,20 @@ +{ + "name": "org.wso2.iot.devices.gravity", + "version": "1.0.0", + "nickName": "Gravity Data", + "description": "Gravity data received from the Device", + "metaData": [ + {"name":"owner","type":"STRING"}, + {"name":"deviceType","type":"STRING"}, + {"name":"deviceId","type":"STRING"}, + {"name":"time","type":"LONG"} + ], + "payloadData": [ + { + "name": "gravity","type": "FLOAT" + } + ] +} + + + diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Gravity/Sparkscripts_1.0.0/Gravity_Sensor_Script.xml b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Gravity/Sparkscripts_1.0.0/Gravity_Sensor_Script.xml new file mode 100644 index 0000000000..13fd7222df --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Gravity/Sparkscripts_1.0.0/Gravity_Sensor_Script.xml @@ -0,0 +1,31 @@ + + + + + IoTServer_Sensor_Script + + 0 * * * * ? + diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Gravity/Sparkscripts_1.0.0/artifact.xml b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Gravity/Sparkscripts_1.0.0/artifact.xml new file mode 100644 index 0000000000..4459a44fe8 --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Gravity/Sparkscripts_1.0.0/artifact.xml @@ -0,0 +1,22 @@ + + + + + Gravity_Sensor_Script.xml + diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Gravity/artifacts.xml b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Gravity/artifacts.xml new file mode 100644 index 0000000000..7fc9acb07a --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Gravity/artifacts.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Gyroscope/Eventreceiver_gyroscope_1.0.0/EventReceiver_gyroscope.xml b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Gyroscope/Eventreceiver_gyroscope_1.0.0/EventReceiver_gyroscope.xml new file mode 100644 index 0000000000..00c7092ba9 --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Gyroscope/Eventreceiver_gyroscope_1.0.0/EventReceiver_gyroscope.xml @@ -0,0 +1,26 @@ + + + + + + false + + + + diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Gyroscope/Eventreceiver_gyroscope_1.0.0/artifact.xml b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Gyroscope/Eventreceiver_gyroscope_1.0.0/artifact.xml new file mode 100644 index 0000000000..1ce90879c5 --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Gyroscope/Eventreceiver_gyroscope_1.0.0/artifact.xml @@ -0,0 +1,22 @@ + + + + + EventReceiver_gyroscope.xml + diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Gyroscope/Eventstore_gyroscope_1.0.0/artifact.xml b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Gyroscope/Eventstore_gyroscope_1.0.0/artifact.xml new file mode 100644 index 0000000000..6a2662c04e --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Gyroscope/Eventstore_gyroscope_1.0.0/artifact.xml @@ -0,0 +1,22 @@ + + + + + org_wso2_iot_devices_gyroscope.xml + diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Gyroscope/Eventstore_gyroscope_1.0.0/org_wso2_iot_devices_gyroscope.xml b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Gyroscope/Eventstore_gyroscope_1.0.0/org_wso2_iot_devices_gyroscope.xml new file mode 100644 index 0000000000..899e32c85e --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Gyroscope/Eventstore_gyroscope_1.0.0/org_wso2_iot_devices_gyroscope.xml @@ -0,0 +1,62 @@ + + + + + + org.wso2.iot.devices.gyroscope: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 + + + gyroscope + false + false + false + FLOAT + + + \ No newline at end of file diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Gyroscope/Eventstream_gyroscope_1.0.0/artifact.xml b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Gyroscope/Eventstream_gyroscope_1.0.0/artifact.xml new file mode 100644 index 0000000000..f221d409cf --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Gyroscope/Eventstream_gyroscope_1.0.0/artifact.xml @@ -0,0 +1,23 @@ + + + + + org.wso2.iot.devices.gyroscope_1.0.0.json + + diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Gyroscope/Eventstream_gyroscope_1.0.0/org.wso2.iot.devices.gyroscope_1.0.0.json b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Gyroscope/Eventstream_gyroscope_1.0.0/org.wso2.iot.devices.gyroscope_1.0.0.json new file mode 100644 index 0000000000..0583da295a --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Gyroscope/Eventstream_gyroscope_1.0.0/org.wso2.iot.devices.gyroscope_1.0.0.json @@ -0,0 +1,20 @@ +{ + "name": "org.wso2.iot.devices.gyroscope", + "version": "1.0.0", + "nickName": "Gyroscope Data", + "description": "Gyroscope data received from the Device", + "metaData": [ + {"name":"owner","type":"STRING"}, + {"name":"deviceType","type":"STRING"}, + {"name":"deviceId","type":"STRING"}, + {"name":"time","type":"LONG"} + ], + "payloadData": [ + { + "name": "gyroscope","type": "FLOAT" + } + ] +} + + + diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Gyroscope/Sparkscripts_1.0.0/Gyroscope_Sensor_Script.xml b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Gyroscope/Sparkscripts_1.0.0/Gyroscope_Sensor_Script.xml new file mode 100644 index 0000000000..88e20226b0 --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Gyroscope/Sparkscripts_1.0.0/Gyroscope_Sensor_Script.xml @@ -0,0 +1,31 @@ + + + + + IoTServer_Sensor_Script + + 0 * * * * ? + diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Gyroscope/Sparkscripts_1.0.0/artifact.xml b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Gyroscope/Sparkscripts_1.0.0/artifact.xml new file mode 100644 index 0000000000..de967666d9 --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Gyroscope/Sparkscripts_1.0.0/artifact.xml @@ -0,0 +1,22 @@ + + + + + Gyroscope_Sensor_Script.xml + diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Gyroscope/artifacts.xml b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Gyroscope/artifacts.xml new file mode 100644 index 0000000000..928f9398eb --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Gyroscope/artifacts.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Light/Eventreceiver_light_1.0.0/EventReceiver_light.xml b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Light/Eventreceiver_light_1.0.0/EventReceiver_light.xml new file mode 100644 index 0000000000..09ced9de4c --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Light/Eventreceiver_light_1.0.0/EventReceiver_light.xml @@ -0,0 +1,26 @@ + + + + + + false + + + + diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Light/Eventreceiver_light_1.0.0/artifact.xml b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Light/Eventreceiver_light_1.0.0/artifact.xml new file mode 100644 index 0000000000..604e9384e4 --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Light/Eventreceiver_light_1.0.0/artifact.xml @@ -0,0 +1,22 @@ + + + + + EventReceiver_light.xml + diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Light/Eventstore_light_1.0.0/artifact.xml b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Light/Eventstore_light_1.0.0/artifact.xml new file mode 100644 index 0000000000..1e06aaa295 --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Light/Eventstore_light_1.0.0/artifact.xml @@ -0,0 +1,22 @@ + + + + + org_wso2_iot_devices_light.xml + diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Light/Eventstore_light_1.0.0/org_wso2_iot_devices_light.xml b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Light/Eventstore_light_1.0.0/org_wso2_iot_devices_light.xml new file mode 100644 index 0000000000..d7051c715f --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Light/Eventstore_light_1.0.0/org_wso2_iot_devices_light.xml @@ -0,0 +1,62 @@ + + + + + + org.wso2.iot.devices.light: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 + + + light + false + false + false + FLOAT + + + \ No newline at end of file diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Light/Eventstream_light_1.0.0/artifact.xml b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Light/Eventstream_light_1.0.0/artifact.xml new file mode 100644 index 0000000000..6a6db53ad8 --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Light/Eventstream_light_1.0.0/artifact.xml @@ -0,0 +1,23 @@ + + + + + org.wso2.iot.devices.light_1.0.0.json + + diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Light/Eventstream_light_1.0.0/org.wso2.iot.devices.light_1.0.0.json b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Light/Eventstream_light_1.0.0/org.wso2.iot.devices.light_1.0.0.json new file mode 100644 index 0000000000..253f0b0c0d --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Light/Eventstream_light_1.0.0/org.wso2.iot.devices.light_1.0.0.json @@ -0,0 +1,20 @@ +{ + "name": "org.wso2.iot.devices.light", + "version": "1.0.0", + "nickName": "light Data", + "description": "light data received from the Device", + "metaData": [ + {"name":"owner","type":"STRING"}, + {"name":"deviceType","type":"STRING"}, + {"name":"deviceId","type":"STRING"}, + {"name":"time","type":"LONG"} + ], + "payloadData": [ + { + "name": "light","type": "FLOAT" + } + ] +} + + + diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Light/Sparkscripts_1.0.0/Light_Sensor_Script.xml b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Light/Sparkscripts_1.0.0/Light_Sensor_Script.xml new file mode 100644 index 0000000000..53b53d5446 --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Light/Sparkscripts_1.0.0/Light_Sensor_Script.xml @@ -0,0 +1,31 @@ + + + + + Light_Sensor_Script + + 0 * * * * ? + diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Light/Sparkscripts_1.0.0/artifact.xml b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Light/Sparkscripts_1.0.0/artifact.xml new file mode 100644 index 0000000000..817faf53cd --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Light/Sparkscripts_1.0.0/artifact.xml @@ -0,0 +1,22 @@ + + + + + Light_Sensor_Script.xml + diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Light/artifacts.xml b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Light/artifacts.xml new file mode 100644 index 0000000000..1138a4c609 --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Light/artifacts.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Magnetic/Eventreceiver_magnetic_1.0.0/EventReceiver_magnetic.xml b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Magnetic/Eventreceiver_magnetic_1.0.0/EventReceiver_magnetic.xml new file mode 100644 index 0000000000..33b0ee1acc --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Magnetic/Eventreceiver_magnetic_1.0.0/EventReceiver_magnetic.xml @@ -0,0 +1,26 @@ + + + + + + false + + + + diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Magnetic/Eventreceiver_magnetic_1.0.0/artifact.xml b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Magnetic/Eventreceiver_magnetic_1.0.0/artifact.xml new file mode 100644 index 0000000000..4a9272db8d --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Magnetic/Eventreceiver_magnetic_1.0.0/artifact.xml @@ -0,0 +1,22 @@ + + + + + EventReceiver_magnetic.xml + diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Magnetic/Eventstore_magnetic_1.0.0/artifact.xml b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Magnetic/Eventstore_magnetic_1.0.0/artifact.xml new file mode 100644 index 0000000000..67a726ea67 --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Magnetic/Eventstore_magnetic_1.0.0/artifact.xml @@ -0,0 +1,22 @@ + + + + + org_wso2_iot_devices_magnetic.xml + diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Magnetic/Eventstore_magnetic_1.0.0/org_wso2_iot_devices_magnetic.xml b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Magnetic/Eventstore_magnetic_1.0.0/org_wso2_iot_devices_magnetic.xml new file mode 100644 index 0000000000..725e2103f7 --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Magnetic/Eventstore_magnetic_1.0.0/org_wso2_iot_devices_magnetic.xml @@ -0,0 +1,62 @@ + + + + + + org.wso2.iot.devices.magnetic: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 + + + magnetic + false + false + false + FLOAT + + + \ No newline at end of file diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Magnetic/Eventstream_magnetic_1.0.0/artifact.xml b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Magnetic/Eventstream_magnetic_1.0.0/artifact.xml new file mode 100644 index 0000000000..24b52cb62e --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Magnetic/Eventstream_magnetic_1.0.0/artifact.xml @@ -0,0 +1,23 @@ + + + + + org.wso2.iot.devices.magnetic_1.0.0.json + + diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Magnetic/Eventstream_magnetic_1.0.0/org.wso2.iot.devices.magnetic_1.0.0.json b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Magnetic/Eventstream_magnetic_1.0.0/org.wso2.iot.devices.magnetic_1.0.0.json new file mode 100644 index 0000000000..5cbe22ba12 --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Magnetic/Eventstream_magnetic_1.0.0/org.wso2.iot.devices.magnetic_1.0.0.json @@ -0,0 +1,20 @@ +{ + "name": "org.wso2.iot.devices.magnetic", + "version": "1.0.0", + "nickName": "magnetic Data", + "description": "magnetic data received from the Device", + "metaData": [ + {"name":"owner","type":"STRING"}, + {"name":"deviceType","type":"STRING"}, + {"name":"deviceId","type":"STRING"}, + {"name":"time","type":"LONG"} + ], + "payloadData": [ + { + "name": "magnetic","type": "FLOAT" + } + ] +} + + + diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Magnetic/Sparkscripts_1.0.0/Magnetic_Sensor_Script.xml b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Magnetic/Sparkscripts_1.0.0/Magnetic_Sensor_Script.xml new file mode 100644 index 0000000000..3ee9b5797d --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Magnetic/Sparkscripts_1.0.0/Magnetic_Sensor_Script.xml @@ -0,0 +1,30 @@ + + + + + IoTServer_Sensor_Script + + 0 * * * * ? + diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Magnetic/Sparkscripts_1.0.0/artifact.xml b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Magnetic/Sparkscripts_1.0.0/artifact.xml new file mode 100644 index 0000000000..0c88076bf1 --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Magnetic/Sparkscripts_1.0.0/artifact.xml @@ -0,0 +1,22 @@ + + + + + Magnetic_Sensor_Script.xml + diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Magnetic/artifacts.xml b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Magnetic/artifacts.xml new file mode 100644 index 0000000000..b98adc8cf8 --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Magnetic/artifacts.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Pressure/Eventreceiver_pressure_1.0.0/EventReceiver_pressure.xml b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Pressure/Eventreceiver_pressure_1.0.0/EventReceiver_pressure.xml new file mode 100644 index 0000000000..e23884d78a --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Pressure/Eventreceiver_pressure_1.0.0/EventReceiver_pressure.xml @@ -0,0 +1,26 @@ + + + + + + false + + + + diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Pressure/Eventreceiver_pressure_1.0.0/artifact.xml b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Pressure/Eventreceiver_pressure_1.0.0/artifact.xml new file mode 100644 index 0000000000..3f6e571d34 --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Pressure/Eventreceiver_pressure_1.0.0/artifact.xml @@ -0,0 +1,22 @@ + + + + + EventReceiver_pressure.xml + diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Pressure/Eventstore_pressure_1.0.0/artifact.xml b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Pressure/Eventstore_pressure_1.0.0/artifact.xml new file mode 100644 index 0000000000..139505cfc1 --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Pressure/Eventstore_pressure_1.0.0/artifact.xml @@ -0,0 +1,22 @@ + + + + + org_wso2_iot_devices_pressure.xml + diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Pressure/Eventstore_pressure_1.0.0/org_wso2_iot_devices_pressure.xml b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Pressure/Eventstore_pressure_1.0.0/org_wso2_iot_devices_pressure.xml new file mode 100644 index 0000000000..5e2f91f34d --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Pressure/Eventstore_pressure_1.0.0/org_wso2_iot_devices_pressure.xml @@ -0,0 +1,62 @@ + + + + + + org.wso2.iot.devices.pressure: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 + + + pressure + false + false + false + FLOAT + + + \ No newline at end of file diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Pressure/Eventstream_pressure_1.0.0/artifact.xml b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Pressure/Eventstream_pressure_1.0.0/artifact.xml new file mode 100644 index 0000000000..9c5d908c2a --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Pressure/Eventstream_pressure_1.0.0/artifact.xml @@ -0,0 +1,23 @@ + + + + + org.wso2.iot.devices.pressure_1.0.0.json + + diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Pressure/Eventstream_pressure_1.0.0/org.wso2.iot.devices.pressure_1.0.0.json b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Pressure/Eventstream_pressure_1.0.0/org.wso2.iot.devices.pressure_1.0.0.json new file mode 100644 index 0000000000..f58059d284 --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Pressure/Eventstream_pressure_1.0.0/org.wso2.iot.devices.pressure_1.0.0.json @@ -0,0 +1,20 @@ +{ + "name": "org.wso2.iot.devices.pressure", + "version": "1.0.0", + "nickName": "Pressure Data", + "description": "Pressure data received from the Device", + "metaData": [ + {"name":"owner","type":"STRING"}, + {"name":"deviceType","type":"STRING"}, + {"name":"deviceId","type":"STRING"}, + {"name":"time","type":"LONG"} + ], + "payloadData": [ + { + "name": "pressure","type": "FLOAT" + } + ] +} + + + diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Pressure/Sparkscripts_1.0.0/Pressure_Sensor_Script.xml b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Pressure/Sparkscripts_1.0.0/Pressure_Sensor_Script.xml new file mode 100644 index 0000000000..8d731ca315 --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Pressure/Sparkscripts_1.0.0/Pressure_Sensor_Script.xml @@ -0,0 +1,31 @@ + + + + + IoTServer_Sensor_Script + + 0 * * * * ? + diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Pressure/Sparkscripts_1.0.0/artifact.xml b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Pressure/Sparkscripts_1.0.0/artifact.xml new file mode 100644 index 0000000000..4ff8b53317 --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Pressure/Sparkscripts_1.0.0/artifact.xml @@ -0,0 +1,22 @@ + + + + + Pressure_Sensor_Script.xml + diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Pressure/artifacts.xml b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Pressure/artifacts.xml new file mode 100644 index 0000000000..7403d3a69e --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Pressure/artifacts.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Proximity/Eventreceiver_proximity_1.0.0/EventReceiver_proximity.xml b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Proximity/Eventreceiver_proximity_1.0.0/EventReceiver_proximity.xml new file mode 100644 index 0000000000..a5024f7d7c --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Proximity/Eventreceiver_proximity_1.0.0/EventReceiver_proximity.xml @@ -0,0 +1,26 @@ + + + + + + false + + + + diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Proximity/Eventreceiver_proximity_1.0.0/artifact.xml b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Proximity/Eventreceiver_proximity_1.0.0/artifact.xml new file mode 100644 index 0000000000..a6c67ff6cc --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Proximity/Eventreceiver_proximity_1.0.0/artifact.xml @@ -0,0 +1,22 @@ + + + + + EventReceiver_proximity.xml + diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Proximity/Eventstore_proximity_1.0.0/artifact.xml b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Proximity/Eventstore_proximity_1.0.0/artifact.xml new file mode 100644 index 0000000000..0d21663c74 --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Proximity/Eventstore_proximity_1.0.0/artifact.xml @@ -0,0 +1,22 @@ + + + + + org_wso2_iot_devices_proximity.xml + diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Proximity/Eventstore_proximity_1.0.0/org_wso2_iot_devices_proximity.xml b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Proximity/Eventstore_proximity_1.0.0/org_wso2_iot_devices_proximity.xml new file mode 100644 index 0000000000..ef48193220 --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Proximity/Eventstore_proximity_1.0.0/org_wso2_iot_devices_proximity.xml @@ -0,0 +1,62 @@ + + + + + + org.wso2.iot.devices.proximity: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 + + + proximity + false + false + false + FLOAT + + + \ No newline at end of file diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Proximity/Eventstream_proximity_1.0.0/artifact.xml b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Proximity/Eventstream_proximity_1.0.0/artifact.xml new file mode 100644 index 0000000000..4458b50a9b --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Proximity/Eventstream_proximity_1.0.0/artifact.xml @@ -0,0 +1,23 @@ + + + + + org.wso2.iot.devices.proximity_1.0.0.json + + diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Proximity/Eventstream_proximity_1.0.0/org.wso2.iot.devices.proximity_1.0.0.json b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Proximity/Eventstream_proximity_1.0.0/org.wso2.iot.devices.proximity_1.0.0.json new file mode 100644 index 0000000000..88fb90089d --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Proximity/Eventstream_proximity_1.0.0/org.wso2.iot.devices.proximity_1.0.0.json @@ -0,0 +1,20 @@ +{ + "name": "org.wso2.iot.devices.proximity", + "version": "1.0.0", + "nickName": "Proximity Data", + "description": "Proximity data received from the Device", + "metaData": [ + {"name":"owner","type":"STRING"}, + {"name":"deviceType","type":"STRING"}, + {"name":"deviceId","type":"STRING"}, + {"name":"time","type":"LONG"} + ], + "payloadData": [ + { + "name": "proximity","type": "FLOAT" + } + ] +} + + + diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Proximity/Sparkscripts_1.0.0/Proximity_Sensor_Script.xml b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Proximity/Sparkscripts_1.0.0/Proximity_Sensor_Script.xml new file mode 100644 index 0000000000..4bdbcc2124 --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Proximity/Sparkscripts_1.0.0/Proximity_Sensor_Script.xml @@ -0,0 +1,31 @@ + + + + + IoTServer_Sensor_Script + + 0 * * * * ? + diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Proximity/Sparkscripts_1.0.0/artifact.xml b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Proximity/Sparkscripts_1.0.0/artifact.xml new file mode 100644 index 0000000000..8486e84ee2 --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Proximity/Sparkscripts_1.0.0/artifact.xml @@ -0,0 +1,22 @@ + + + + + Proximity_Sensor_Script.xml + diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Proximity/artifacts.xml b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Proximity/artifacts.xml new file mode 100644 index 0000000000..8b24e448e3 --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Proximity/artifacts.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Rotation/Eventreceiver_rotation_1.0.0/EventReceiver_rotation.xml b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Rotation/Eventreceiver_rotation_1.0.0/EventReceiver_rotation.xml new file mode 100644 index 0000000000..a2bb72eddf --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Rotation/Eventreceiver_rotation_1.0.0/EventReceiver_rotation.xml @@ -0,0 +1,26 @@ + + + + + + false + + + + diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Rotation/Eventreceiver_rotation_1.0.0/artifact.xml b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Rotation/Eventreceiver_rotation_1.0.0/artifact.xml new file mode 100644 index 0000000000..d49ec80d53 --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Rotation/Eventreceiver_rotation_1.0.0/artifact.xml @@ -0,0 +1,22 @@ + + + + + EventReceiver_rotation.xml + diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Rotation/Eventstore_rotation_1.0.0/artifact.xml b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Rotation/Eventstore_rotation_1.0.0/artifact.xml new file mode 100644 index 0000000000..816c8f58cb --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Rotation/Eventstore_rotation_1.0.0/artifact.xml @@ -0,0 +1,22 @@ + + + + + org_wso2_iot_devices_rotation.xml + diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Rotation/Eventstore_rotation_1.0.0/org_wso2_iot_devices_rotation.xml b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Rotation/Eventstore_rotation_1.0.0/org_wso2_iot_devices_rotation.xml new file mode 100644 index 0000000000..941f62198a --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Rotation/Eventstore_rotation_1.0.0/org_wso2_iot_devices_rotation.xml @@ -0,0 +1,62 @@ + + + + + + org.wso2.iot.devices.rotation: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 + + + rotation + false + false + false + FLOAT + + + \ No newline at end of file diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Rotation/Eventstream_rotation_1.0.0/artifact.xml b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Rotation/Eventstream_rotation_1.0.0/artifact.xml new file mode 100644 index 0000000000..585d53afb2 --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Rotation/Eventstream_rotation_1.0.0/artifact.xml @@ -0,0 +1,23 @@ + + + + + org.wso2.iot.devices.rotation_1.0.0.json + + diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Rotation/Eventstream_rotation_1.0.0/org.wso2.iot.devices.rotation_1.0.0.json b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Rotation/Eventstream_rotation_1.0.0/org.wso2.iot.devices.rotation_1.0.0.json new file mode 100644 index 0000000000..03881bcf3b --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Rotation/Eventstream_rotation_1.0.0/org.wso2.iot.devices.rotation_1.0.0.json @@ -0,0 +1,20 @@ +{ + "name": "org.wso2.iot.devices.rotation", + "version": "1.0.0", + "nickName": "Rotation Data", + "description": "Rotation data received from the Device", + "metaData": [ + {"name":"owner","type":"STRING"}, + {"name":"deviceType","type":"STRING"}, + {"name":"deviceId","type":"STRING"}, + {"name":"time","type":"LONG"} + ], + "payloadData": [ + { + "name": "rotation","type": "FLOAT" + } + ] +} + + + diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Rotation/Sparkscripts_1.0.0/Rotation_Sensor_Script.xml b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Rotation/Sparkscripts_1.0.0/Rotation_Sensor_Script.xml new file mode 100644 index 0000000000..e03e7fab5c --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Rotation/Sparkscripts_1.0.0/Rotation_Sensor_Script.xml @@ -0,0 +1,31 @@ + + + + + IoTServer_Sensor_Script + + 0 * * * * ? + diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Rotation/Sparkscripts_1.0.0/artifact.xml b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Rotation/Sparkscripts_1.0.0/artifact.xml new file mode 100644 index 0000000000..9a959892d1 --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Rotation/Sparkscripts_1.0.0/artifact.xml @@ -0,0 +1,22 @@ + + + + + Rotation_Sensor_Script.xml + diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Rotation/artifacts.xml b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Rotation/artifacts.xml new file mode 100644 index 0000000000..7bbe4ac500 --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/Rotation/artifacts.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/WordCount/Eventreceiver_wordcount_1.0.0/EventReceiver_wordcount.xml b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/WordCount/Eventreceiver_wordcount_1.0.0/EventReceiver_wordcount.xml new file mode 100644 index 0000000000..dac705e766 --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/WordCount/Eventreceiver_wordcount_1.0.0/EventReceiver_wordcount.xml @@ -0,0 +1,27 @@ + + + + + + false + + + + diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/WordCount/Eventreceiver_wordcount_1.0.0/artifact.xml b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/WordCount/Eventreceiver_wordcount_1.0.0/artifact.xml new file mode 100644 index 0000000000..b9ee00adb1 --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/WordCount/Eventreceiver_wordcount_1.0.0/artifact.xml @@ -0,0 +1,22 @@ + + + + + EventReceiver_wordcount.xml + diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/WordCount/Eventstore_wordcount_1.0.0/artifact.xml b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/WordCount/Eventstore_wordcount_1.0.0/artifact.xml new file mode 100644 index 0000000000..f1ff8a5b84 --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/WordCount/Eventstore_wordcount_1.0.0/artifact.xml @@ -0,0 +1,22 @@ + + + + + org_wso2_iot_devices_wordcount.xml + diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/WordCount/Eventstore_wordcount_1.0.0/org_wso2_iot_devices_wordcount.xml b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/WordCount/Eventstore_wordcount_1.0.0/org_wso2_iot_devices_wordcount.xml new file mode 100644 index 0000000000..8e9649dd1e --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/WordCount/Eventstore_wordcount_1.0.0/org_wso2_iot_devices_wordcount.xml @@ -0,0 +1,76 @@ + + + + + + org.wso2.iot.devices.wordcount: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 + + + sessionId + false + false + false + STRING + + + word + false + false + false + STRING + + + status + false + false + false + STRING + + + \ No newline at end of file diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/WordCount/Eventstream_wordcount_1.0.0/artifact.xml b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/WordCount/Eventstream_wordcount_1.0.0/artifact.xml new file mode 100644 index 0000000000..c3cc85f26d --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/WordCount/Eventstream_wordcount_1.0.0/artifact.xml @@ -0,0 +1,23 @@ + + + + + org.wso2.iot.devices.wordcount_1.0.0.json + + diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/WordCount/Eventstream_wordcount_1.0.0/org.wso2.iot.devices.wordcount_1.0.0.json b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/WordCount/Eventstream_wordcount_1.0.0/org.wso2.iot.devices.wordcount_1.0.0.json new file mode 100644 index 0000000000..c5e6e7748c --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/WordCount/Eventstream_wordcount_1.0.0/org.wso2.iot.devices.wordcount_1.0.0.json @@ -0,0 +1,17 @@ +{ + "name": "org.wso2.iot.devices.wordcount", + "version": "1.0.0", + "nickName": "Word Count Data", + "description": "Word Count received from the Device", + "metaData": [ + {"name":"owner","type":"STRING"}, + {"name":"deviceType","type":"STRING"}, + {"name":"deviceId","type":"STRING"}, + {"name":"time","type":"LONG"} + ], + "payloadData": [ + {"name" : "sessionId", "type" : "STRING"}, + {"name" : "word", "type" : "STRING"}, + {"name" : "status", "type" : "STRING"} + ] +} \ No newline at end of file diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/WordCount/Sparkscripts_1.0.0/Wordcount_Script.xml b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/WordCount/Sparkscripts_1.0.0/Wordcount_Script.xml new file mode 100644 index 0000000000..9050af8ba2 --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/WordCount/Sparkscripts_1.0.0/Wordcount_Script.xml @@ -0,0 +1,34 @@ + + + + + IoTServer_Sensor_Script + + 0 * * * * ? + diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/WordCount/Sparkscripts_1.0.0/artifact.xml b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/WordCount/Sparkscripts_1.0.0/artifact.xml new file mode 100644 index 0000000000..4f9088a72c --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/WordCount/Sparkscripts_1.0.0/artifact.xml @@ -0,0 +1,22 @@ + + + + + Wordcount_Script.xml + diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/WordCount/artifacts.xml b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/WordCount/artifacts.xml new file mode 100644 index 0000000000..737b58626e --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.analytics/src/main/resources/carbonapps/WordCount/artifacts.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.controller.api/pom.xml b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.controller.api/pom.xml new file mode 100644 index 0000000000..4b2b5e6824 --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.controller.api/pom.xml @@ -0,0 +1,205 @@ + + + + androidsense-plugin + org.wso2.carbon.devicemgt-plugins + 2.1.0-SNAPSHOT + ../pom.xml + + + 4.0.0 + org.wso2.carbon.device.mgt.iot.androidsense.controller.api + war + WSO2 Carbon - Android Sense Controller API + WSO2 Carbon - Android Sense Service Controller API Implementation + http://wso2.org + + + + + + org.wso2.carbon.devicemgt + org.wso2.carbon.device.mgt.common + provided + + + org.wso2.carbon.devicemgt + org.wso2.carbon.device.mgt.core + provided + + + org.apache.axis2.wso2 + axis2-client + + + + + + + org.apache.cxf + cxf-rt-frontend-jaxws + provided + + + org.apache.cxf + cxf-rt-frontend-jaxrs + provided + + + org.apache.cxf + cxf-rt-transports-http + provided + + + + org.eclipse.paho + org.eclipse.paho.client.mqttv3 + provided + + + + + org.apache.httpcomponents + httpasyncclient + 4.1 + provided + + + org.wso2.carbon.devicemgt-plugins + org.wso2.carbon.device.mgt.iot + provided + + + org.wso2.carbon.devicemgt-plugins + org.wso2.carbon.device.mgt.iot.androidsense.plugin + provided + + + + org.codehaus.jackson + jackson-core-asl + + + org.codehaus.jackson + jackson-jaxrs + + + javax + javaee-web-api + provided + + + javax.ws.rs + jsr311-api + provided + + + + org.wso2.carbon.devicemgt + org.wso2.carbon.device.mgt.analytics + provided + + + + commons-httpclient.wso2 + commons-httpclient + provided + + + + org.wso2.carbon + org.wso2.carbon.utils + provided + + + 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.wso2.carbon.devicemgt + org.wso2.carbon.apimgt.annotations + provided + + + org.wso2.carbon.devicemgt + org.wso2.carbon.device.mgt.extensions + provided + + + + + + + maven-compiler-plugin + + UTF-8 + ${wso2.maven.compiler.source} + ${wso2.maven.compiler.target} + + + + maven-war-plugin + + android_sense + + + + + + diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.controller.api/src/main/java/org/wso2/carbon/device/mgt/iot/androidsense/controller/service/impl/AndroidSenseControllerService.java b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.controller.api/src/main/java/org/wso2/carbon/device/mgt/iot/androidsense/controller/service/impl/AndroidSenseControllerService.java new file mode 100644 index 0000000000..5d740dc335 --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.controller.api/src/main/java/org/wso2/carbon/device/mgt/iot/androidsense/controller/service/impl/AndroidSenseControllerService.java @@ -0,0 +1,667 @@ +/* + * Copyright (c) 2016, 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.wso2.carbon.device.mgt.iot.androidsense.controller.service.impl; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.apimgt.annotations.api.API; +import org.wso2.carbon.context.PrivilegedCarbonContext; +import org.wso2.carbon.device.mgt.analytics.common.AnalyticsDataRecord; +import org.wso2.carbon.device.mgt.analytics.exception.DataPublisherConfigurationException; +import org.wso2.carbon.device.mgt.analytics.exception.DeviceManagementAnalyticsException; +import org.wso2.carbon.device.mgt.analytics.service.DeviceAnalyticsService; +import org.wso2.carbon.device.mgt.extensions.feature.mgt.annotations.DeviceType; +import org.wso2.carbon.device.mgt.extensions.feature.mgt.annotations.Feature; +import org.wso2.carbon.device.mgt.iot.androidsense.controller.service.impl.transport.AndroidSenseMQTTConnector; +import org.wso2.carbon.device.mgt.iot.androidsense.controller.service.impl.util.DeviceData; +import org.wso2.carbon.device.mgt.iot.androidsense.controller.service.impl.util.SensorData; +import org.wso2.carbon.device.mgt.iot.androidsense.plugin.constants.AndroidSenseConstants; +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.service.IoTServerStartupListener; +import org.wso2.carbon.device.mgt.iot.transport.TransportHandlerException; + +import javax.servlet.http.HttpServletResponse; +import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; +import javax.ws.rs.FormParam; +import javax.ws.rs.GET; +import javax.ws.rs.HeaderParam; +import javax.ws.rs.POST; +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.MediaType; +import javax.ws.rs.core.Response; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +/** + * The api for + */ +@DeviceType(value = "android_sense") +@API(name = "android_sense", version = "1.0.0", context = "/android_sense", tags = {"android_sense"}) +public class AndroidSenseControllerService { + @Context //injected response proxy supporting multiple thread + private HttpServletResponse response; + private static Log log = LogFactory.getLog(AndroidSenseControllerService.class); + private static AndroidSenseMQTTConnector androidSenseMQTTConnector; + + /** + * Fetches the `AndroidSenseMQTTConnector` specific to this Android Sense controller service. + * + * @return the 'AndroidSenseMQTTConnector' instance bound to the 'AndroidSenseMQTTConnector' variable of + * this service. + */ + @SuppressWarnings("Unused") + public AndroidSenseMQTTConnector getAndroidSenseMQTTConnector() { + return androidSenseMQTTConnector; + } + + /** + * Sets the `AndroidSenseMQTTConnector` variable of this Android Sense controller service. + * + * @param androidSenseMQTTConnector a 'AndroidSenseMQTTConnector' object that handles all MQTT related + * communications of any connected Android Sense device-type + */ + @SuppressWarnings("Unused") + public void setAndroidSenseMQTTConnector(final AndroidSenseMQTTConnector androidSenseMQTTConnector) { + Runnable connector = new Runnable() { + public void run() { + if (waitForServerStartup()) { + return; + } + AndroidSenseControllerService.androidSenseMQTTConnector = androidSenseMQTTConnector; + if (MqttConfig.getInstance().isEnabled()) { + androidSenseMQTTConnector.connect(); + } else { + log.warn("MQTT disabled in 'devicemgt-config.xml'. Hence, VirtualFireAlarmMQTTConnector not started."); + } + } + }; + Thread connectorThread = new Thread(connector); + connectorThread.setDaemon(true); + connectorThread.start(); + } + + private boolean waitForServerStartup() { + while (!IoTServerStartupListener.isServerReady()) { + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + return true; + } + } + return false; + } + + /** + * Service to push all the sensor data collected by the Android. Called by the Android device + * + * @param dataMsg The json string containing sensor readings + * @param response the HTTP servlet response object received by default as part of the HTTP + * call to this API + */ + @Path("controller/sensors") + @POST + @Consumes(MediaType.APPLICATION_JSON) + public void addSensorData(DeviceData dataMsg, @Context HttpServletResponse response) { + PrivilegedCarbonContext ctx = PrivilegedCarbonContext.getThreadLocalCarbonContext(); + DeviceAnalyticsService deviceAnalyticsService = (DeviceAnalyticsService) ctx + .getOSGiService(DeviceAnalyticsService.class, null); + SensorData[] sensorData = dataMsg.values; + String streamDef = null; + Object payloadData[] = null; + String sensorName = null; + for (SensorData sensor : sensorData) { + switch (sensor.key) { + case "battery" : + streamDef = AndroidSenseConstants.BATTERY_STREAM_DEFINITION; + payloadData = new Float[]{Float.parseFloat(sensor.value)}; + sensorName = AndroidSenseConstants.SENSOR_BATTERY; + break; + case "GPS" : + streamDef = AndroidSenseConstants.GPS_STREAM_DEFINITION; + String gpsValue = sensor.value; + String gpsValuesString[] = gpsValue.split(","); + Float gpsValues[] = new Float[2]; + gpsValues[0] = Float.parseFloat(gpsValuesString[0]); + gpsValues[1] = Float.parseFloat(gpsValuesString[0]); + payloadData = gpsValues; + sensorName = AndroidSenseConstants.SENSOR_GPS; + break; + default : + try { + int androidSensorId = Integer.parseInt(sensor.key); + String value = sensor.value; + String sensorValueString[] = value.split(","); + Float sensorValues[] = new Float[1]; + switch (androidSensorId) { + case 1: + streamDef = AndroidSenseConstants.ACCELEROMETER_STREAM_DEFINITION; + sensorValues[0] = Float.parseFloat(sensorValueString[0]) * + Float.parseFloat(sensorValueString[0]) * Float.parseFloat(sensorValueString[0]); + payloadData = sensorValues; + sensorName = AndroidSenseConstants.SENSOR_ACCELEROMETER; + break; + case 2: + streamDef = AndroidSenseConstants.MAGNETIC_STREAM_DEFINITION; + sensorValues[0] = Float.parseFloat(sensorValueString[0]) * + Float.parseFloat(sensorValueString[0]) * Float.parseFloat(sensorValueString[0]); + payloadData = sensorValues; + sensorName = AndroidSenseConstants.SENSOR_MAGNETIC; + break; + case 4: + streamDef = AndroidSenseConstants.GYROSCOPE_STREAM_DEFINITION; + sensorValues[0] = Float.parseFloat(sensorValueString[0]) * + Float.parseFloat(sensorValueString[0]) * Float.parseFloat(sensorValueString[0]); + payloadData = sensorValues; + sensorName = AndroidSenseConstants.SENSOR_GYROSCOPE; + break; + case 5: + streamDef = AndroidSenseConstants.LIGHT_STREAM_DEFINITION; + sensorName = AndroidSenseConstants.SENSOR_LIGHT; + payloadData = new Float[]{Float.parseFloat(sensorValueString[0])}; + break; + case 6: + streamDef = AndroidSenseConstants.PRESSURE_STREAM_DEFINITION; + sensorName = AndroidSenseConstants.SENSOR_PRESSURE; + payloadData = new Float[]{Float.parseFloat(sensorValueString[0])}; + break; + case 8: + streamDef = AndroidSenseConstants.PROXIMITY_STREAM_DEFINITION; + sensorName = AndroidSenseConstants.SENSOR_PROXIMITY; + payloadData = new Float[]{Float.parseFloat(sensorValueString[0])}; + break; + case 9: + streamDef = AndroidSenseConstants.GRAVITY_STREAM_DEFINITION; + sensorValues[0] = Float.parseFloat(sensorValueString[0]) * + Float.parseFloat(sensorValueString[0]) * Float.parseFloat(sensorValueString[0]); + payloadData = sensorValues; + sensorName = AndroidSenseConstants.SENSOR_GRAVITY; + break; + case 11: + streamDef = AndroidSenseConstants.ROTATION_STREAM_DEFINITION; + sensorValues[0] = Float.parseFloat(sensorValueString[0]) * + Float.parseFloat(sensorValueString[0]) * Float.parseFloat(sensorValueString[0]); + payloadData = sensorValues; + sensorName = AndroidSenseConstants.SENSOR_ROTATION; + break; + } + } catch (NumberFormatException e) { + log.error("Invalid sensor value is sent from the device"); + continue; + } + } + Object metaData[] = {dataMsg.owner, AndroidSenseConstants.DEVICE_TYPE, dataMsg.deviceId, sensor.time}; + if (streamDef != null && payloadData != null && payloadData.length > 0) { + try { + SensorDataManager.getInstance() + .setSensorRecord(dataMsg.deviceId, sensorName, sensor.value, sensor.time); + deviceAnalyticsService.publishEvent(streamDef, "1.0.0", metaData, new Object[0], payloadData); + } catch (DataPublisherConfigurationException e) { + response.setStatus(Response.Status.UNSUPPORTED_MEDIA_TYPE.getStatusCode()); + } + } + } + } + + + /** + * End point which is called by Front-end js to get Light sensor readings from the server. + * + * @param deviceId The registered device id + * @param response the HTTP servlet response object received by default as part of the HTTP + * call to this API. + * @return This method returns a SensorRecord object. + */ + @Path("controller/device/{deviceId}/sensors/light") + @GET + @Consumes("application/json") + @Produces("application/json") + @Feature(code = "light", name = "Light", description = "Read Light data from the device", type = "monitor") + public SensorRecord getLightData(@PathParam("deviceId") String deviceId, @Context HttpServletResponse response) { + SensorRecord sensorRecord = null; + try { + sensorRecord = SensorDataManager.getInstance().getSensorRecord(deviceId, AndroidSenseConstants.SENSOR_LIGHT); + response.setStatus(Response.Status.OK.getStatusCode()); + } catch (DeviceControllerException e) { + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + } + return sensorRecord; + } + + /** + * End point which is called by Front-end js to get Battery data from the server. + * @param deviceId The registered device id + * @param response the HTTP servlet response object received by default as part of the HTTP + * call to this API. + * @return This method returns a SensorRecord object. + */ + @Path("controller/device/{deviceId}/sensors/battery") + @GET + @Consumes("application/json") + @Produces("application/json") + @Feature(code = "battery", name = "Battery", description = "Read Battery data from the device", type = "monitor") + public SensorRecord getBattery(@PathParam("deviceId") String deviceId, @Context HttpServletResponse response) { + SensorRecord sensorRecord = null; + try { + sensorRecord = SensorDataManager.getInstance().getSensorRecord(deviceId, AndroidSenseConstants.SENSOR_BATTERY); + response.setStatus(Response.Status.OK.getStatusCode()); + } catch (DeviceControllerException e) { + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + } + return sensorRecord; + } + + /** + * End point which is called by Front-end js to get GPS data from the server. + * @param deviceId The registered device id + * @param response the HTTP servlet response object received by default as part of the HTTP + * call to this API. + * @return This method returns a SensorRecord object. + */ + @Path("controller/device/{deviceId}/sensors/gps") + @GET + @Consumes("application/json") + @Produces("application/json") + @Feature(code = "gps", name = "gps", description = "Read GPS data from the device", type = "monitor") + public SensorRecord getGPS(@PathParam("deviceId") String deviceId, @Context HttpServletResponse response) { + SensorRecord sensorRecord = null; + try { + sensorRecord = SensorDataManager.getInstance().getSensorRecord(deviceId, AndroidSenseConstants.SENSOR_GPS); + response.setStatus(Response.Status.OK.getStatusCode()); + } catch (DeviceControllerException e) { + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + } + return sensorRecord; + } + + /** + * End point which is called by Front-end js to get Magnetic data readings from the server. + * @param deviceId The registered device id + * @param response the HTTP servlet response object received by default as part of the HTTP + * call to this API. + * @return This method returns a SensorRecord object. + */ + @Path("controller/device/{deviceId}/sensors/magnetic") + @GET + @Consumes("application/json") + @Produces("application/json") + @Feature(code = "magnetic", name = "Magnetic", description = "Read Magnetic data from the device", type = "monitor") + public SensorRecord readMagnetic(@PathParam("deviceId") String deviceId, @Context HttpServletResponse response) { + SensorRecord sensorRecord = null; + try { + sensorRecord = SensorDataManager.getInstance().getSensorRecord(deviceId, AndroidSenseConstants.SENSOR_MAGNETIC); + response.setStatus(Response.Status.OK.getStatusCode()); + } catch (DeviceControllerException e) { + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + } + return sensorRecord; + } + + /** + * End point which is called by Front-end js to get Accelerometer data from the server. + * @param deviceId The registered device id + * @param response the HTTP servlet response object received by default as part of the HTTP call to this API. + * @return This method returns a SensorRecord object. + */ + @Path("controller/device/{deviceId}/sensors/accelerometer") + @GET + @Consumes("application/json") + @Produces("application/json") + @Feature(code = "accelerometer", name = "Accelerometer", description = "Read Accelerometer data from the device", + type = "monitor") + public SensorRecord readAccelerometer(@PathParam("deviceId") String deviceId, @Context HttpServletResponse response) { + SensorRecord sensorRecord = null; + try { + sensorRecord = SensorDataManager.getInstance().getSensorRecord(deviceId, + AndroidSenseConstants.SENSOR_ACCELEROMETER); + response.setStatus(Response.Status.OK.getStatusCode()); + } catch (DeviceControllerException e) { + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + } + return sensorRecord; + } + + /** + * End point which is called by Front-end js to get Rotation data from the server. + * @param deviceId The registered device id + * @param response the HTTP servlet response object received by default as part of the HTTP + * call to this API. + * @return This method returns a SensorRecord object. + */ + @Path("controller/device/{deviceId}/sensors/rotation") + @GET + @Consumes("application/json") + @Produces("application/json") + @Feature(code = "rotation", name = "Rotation", description = "Read Rotational Vector data from the device", + type = "monitor") + public SensorRecord readRotation(@PathParam("deviceId") String deviceId, @Context HttpServletResponse response) { + SensorRecord sensorRecord = null; + try { + sensorRecord = SensorDataManager.getInstance().getSensorRecord(deviceId, + AndroidSenseConstants.SENSOR_ROTATION); + response.setStatus(Response.Status.OK.getStatusCode()); + } catch (DeviceControllerException e) { + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + } + return sensorRecord; + } + + /** + * End point which is called by Front-end js to get Proximity data from the server. + * @param deviceId The registered device id + * @param response the HTTP servlet response object received by default as part of the HTTP + * call to this API. + * @return This method returns a SensorRecord object. + */ + @Path("controller/device/{deviceId}/sensors/proximity") + @GET + @Consumes("application/json") + @Produces("application/json") + @Feature(code = "proximity", name = "Proximity", description = "Read Proximity data from the device", + type = "monitor") + public SensorRecord readProximity(@PathParam("deviceId") String deviceId, @Context HttpServletResponse response) { + SensorRecord sensorRecord = null; + try { + sensorRecord = SensorDataManager.getInstance().getSensorRecord(deviceId, + AndroidSenseConstants.SENSOR_PROXIMITY); + response.setStatus(Response.Status.OK.getStatusCode()); + } catch (DeviceControllerException e) { + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + } + return sensorRecord; + } + + /** + * End point which is called by Front-end js to get Gyroscope data from the server. + * @param deviceId The registered device id + * @param response the HTTP servlet response object received by default as part of the HTTP + * call to this API. + * @return This method returns a SensorRecord object. + */ + @Path("controller/device/{deviceId}/sensors/gyroscope") + @GET + @Consumes("application/json") + @Produces("application/json") + @Feature(code = "gyroscope", name = "Gyroscope", description = "Read Gyroscope data from the device", + type = "monitor") + public SensorRecord readGyroscope(@PathParam("deviceId") String deviceId, @Context HttpServletResponse response) { + SensorRecord sensorRecord = null; + try { + sensorRecord = SensorDataManager.getInstance().getSensorRecord(deviceId, + AndroidSenseConstants.SENSOR_GYROSCOPE); + response.setStatus(Response.Status.OK.getStatusCode()); + } catch (DeviceControllerException e) { + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + } + return sensorRecord; + } + + /** + * End point which is called by Front-end js to get Pressure data from the server. + * @param deviceId The registered device id + * @param response the HTTP servlet response object received by default as part of the HTTP + * call to this API. + * @return This method returns a SensorRecord object. + */ + @Path("controller/device/{deviceId}/sensors/pressure") + @GET + @Consumes("application/json") + @Produces("application/json") + @Feature(code = "pressure", name = "Pressure", description = "Read Pressure data from the device", type = "monitor") + public SensorRecord readPressure(@PathParam("deviceId") String deviceId, @Context HttpServletResponse response) { + SensorRecord sensorRecord = null; + try { + sensorRecord = SensorDataManager.getInstance().getSensorRecord(deviceId, + AndroidSenseConstants.SENSOR_PRESSURE); + response.setStatus(Response.Status.OK.getStatusCode()); + } catch (DeviceControllerException e) { + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + } + return sensorRecord; + } + + /** + * End point which is called by Front-end js to get Gravity data from the server. + * + * @param deviceId The registered device id + * @param response the HTTP servlet response object received by default as part of the HTTP + * call to this API. + * @return This method returns a SensorRecord object. + */ + @Path("controller/device/{deviceId}/sensors/gravity") + @GET + @Consumes("application/json") + @Produces("application/json") + @Feature(code = "gravity", name = "Gravity", + description = "Read Gravity data from the device", type = "monitor") + public SensorRecord readGravity(@PathParam("deviceId") String deviceId, @Context HttpServletResponse response) { + SensorRecord sensorRecord = null; + try { + sensorRecord = SensorDataManager.getInstance().getSensorRecord(deviceId, + AndroidSenseConstants.SENSOR_GRAVITY); + response.setStatus(Response.Status.OK.getStatusCode()); + } catch (DeviceControllerException e) { + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + } + return sensorRecord; + } + + @Path("controller/device/{deviceId}/sensors/words") + @GET + @Consumes("application/json") + @Produces("application/json") + @Feature(code = "words", name = "Words", description = "Get the key words and occurrences", + type = "monitor") + public SensorRecord getWords(@PathParam("deviceId") String deviceId, @QueryParam("sessionId") String sessionId, + @Context HttpServletResponse response) { + SensorRecord sensorRecord = null; + try { + sensorRecord = SensorDataManager.getInstance().getSensorRecord(deviceId, + AndroidSenseConstants.SENSOR_WORDCOUNT); + response.setStatus(Response.Status.OK.getStatusCode()); + } catch (DeviceControllerException e) { + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + } + return sensorRecord; + } + + /** + * End point to send the key words to the device + + * @param deviceId The registered device Id. + * @param keywords The key words to be sent. (Comma separated values) + * @param response the HTTP servlet response object received by default as part of the HTTP + * call to this API + */ + @Path("controller/device/{deviceId}/sensors/words") + @POST + @Feature(code = "keywords", name = "Add Keywords", description = "Send keywords to the device", + type = "operation") + public void sendKeyWords(@PathParam("deviceId") String deviceId, @FormParam("keywords") String keywords, + @Context HttpServletResponse response) { + try { + String username = PrivilegedCarbonContext.getThreadLocalCarbonContext().getUsername(); + androidSenseMQTTConnector.publishDeviceData(username, deviceId, "add", keywords); + response.setStatus(Response.Status.OK.getStatusCode()); + } catch (TransportHandlerException e) { + log.error(e); + response.setStatus(Response.Status.UNAUTHORIZED.getStatusCode()); + } + } + + /** + * End point to send the key words to the device + * + * @param deviceId The registered device Id. + * @param threshold The key words to be sent. (Comma separated values) + * @param response the HTTP servlet response object received by default as part of the HTTP + * call to this API + */ + @Path("controller/device/{deviceId}/sensors/words/threshold") + @POST + @Feature(code = "threshold", name = "Add a Threshold", description = "Set a threshold for word in the device", + type = "operation") + public void sendThreshold(@PathParam("deviceId") String deviceId, @FormParam("threshold") String threshold, + @Context HttpServletResponse response) { + try { + String username = PrivilegedCarbonContext.getThreadLocalCarbonContext().getUsername(); + androidSenseMQTTConnector.publishDeviceData(username, deviceId, "threshold", threshold); + response.setStatus(Response.Status.OK.getStatusCode()); + } catch (TransportHandlerException e) { + log.error(e); + response.setStatus(Response.Status.UNAUTHORIZED.getStatusCode()); + } + } + + @Path("controller/device/{deviceId}/sensors/words") + @DELETE + @Feature(code = "remove", name = "Remove Keywords", description = "Remove the keywords", + type = "operation") + public void removeKeyWords(@PathParam("deviceId") String deviceId, @QueryParam("words") String words, + @Context HttpServletResponse response) { + try { + String username = PrivilegedCarbonContext.getThreadLocalCarbonContext().getUsername(); + androidSenseMQTTConnector.publishDeviceData(username, deviceId, "remove", words); + response.setStatus(Response.Status.OK.getStatusCode()); + } catch (TransportHandlerException e) { + log.error(e); + response.setStatus(Response.Status.UNAUTHORIZED.getStatusCode()); + } + } + + /** + * Retrieve Sensor data for the device type + * + */ + @Path("controller/stats/device/{deviceId}/sensors/{sensorName}") + @GET + @Consumes("application/json") + @Produces("application/json") + public SensorData[] getAndroidSenseDeviceStats(@PathParam("deviceId") String deviceId, + @PathParam("sensorName") String sensor, + @QueryParam("from") long from, + @QueryParam("to") long to) { + String fromDate = String.valueOf(from); + String toDate = String.valueOf(to); + String user = PrivilegedCarbonContext.getThreadLocalCarbonContext().getUsername(); + List sensorDatas = new ArrayList<>(); + PrivilegedCarbonContext ctx = PrivilegedCarbonContext.getThreadLocalCarbonContext(); + DeviceAnalyticsService deviceAnalyticsService = (DeviceAnalyticsService) ctx + .getOSGiService(DeviceAnalyticsService.class, null); + String query = "owner:" + user + " AND deviceId:" + deviceId + " AND deviceType:" + + AndroidSenseConstants.DEVICE_TYPE + " AND time : [" + fromDate + " TO " + toDate + "]"; + if (sensor.equals(AndroidSenseConstants.SENSOR_WORDCOUNT)) { + query = "owner:" + user + " AND deviceId:" + deviceId; + } + String sensorTableName = getSensorEventTableName(sensor); + try { + List records = deviceAnalyticsService.getAllEventsForDevice(sensorTableName, query); + if (sensor.equals(AndroidSenseConstants.SENSOR_WORDCOUNT)) { + for (AnalyticsDataRecord record : records) { + SensorData sensorData = new SensorData(); + sensorData.setKey((String)record.getValue("word")); + sensorData.setTime((long) record.getValue("occurence")); + sensorData.setValue((String) record.getValue("sessionId")); + sensorDatas.add(sensorData); + } + } else { + Collections.sort(records, new Comparator() { + @Override + public int compare(AnalyticsDataRecord o1, AnalyticsDataRecord o2) { + long t1 = (Long) o1.getValue("time"); + long t2 = (Long) o2.getValue("time"); + if (t1 < t2) { + return -1; + } else if (t1 > t2) { + return 1; + } else { + return 0; + } + } + }); + for (AnalyticsDataRecord record : records) { + SensorData sensorData = new SensorData(); + sensorData.setTime((long) record.getValue("time")); + sensorData.setValue("" + (float) record.getValue(sensor)); + sensorDatas.add(sensorData); + } + } + return sensorDatas.toArray(new SensorData[sensorDatas.size()]); + } catch (DeviceManagementAnalyticsException e) { + String errorMsg = "Error on retrieving stats on table " + sensorTableName + " with query " + query; + log.error(errorMsg); + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + return sensorDatas.toArray(new SensorData[sensorDatas.size()]); + } + } + + /** + * get the event table from the sensor name. + */ + private String getSensorEventTableName(String sensorName) { + String sensorEventTableName; + switch (sensorName) { + case AndroidSenseConstants.SENSOR_ACCELEROMETER: + sensorEventTableName = "DEVICE_ACCELEROMETER_SUMMARY"; + break; + case AndroidSenseConstants.SENSOR_BATTERY: + sensorEventTableName = "DEVICE_BATTERY_SUMMARY"; + break; + case AndroidSenseConstants.SENSOR_GPS: + sensorEventTableName = "DEVICE_GPS_SUMMARY"; + break; + case AndroidSenseConstants.SENSOR_GRAVITY: + sensorEventTableName = "DEVICE_GRAVITY_SUMMARY"; + break; + case AndroidSenseConstants.SENSOR_GYROSCOPE: + sensorEventTableName = "DEVICE_GYROSCOPE_SUMMARY"; + break; + case AndroidSenseConstants.SENSOR_LIGHT: + sensorEventTableName = "DEVICE_LIGHT_SUMMARY"; + break; + case AndroidSenseConstants.SENSOR_MAGNETIC: + sensorEventTableName = "DEVICE_MAGNETIC_SUMMARY"; + break; + case AndroidSenseConstants.SENSOR_PRESSURE: + sensorEventTableName = "DEVICE_PRESSURE_SUMMARY"; + break; + case AndroidSenseConstants.SENSOR_PROXIMITY: + sensorEventTableName = "DevicePROXIMITYSummaryData"; + break; + case AndroidSenseConstants.SENSOR_ROTATION: + sensorEventTableName = "DEVICE_ROTATION_SUMMARY"; + break; + case AndroidSenseConstants.SENSOR_WORDCOUNT: + sensorEventTableName = "WORD_COUNT_SUMMARY"; + break; + default: + sensorEventTableName = ""; + } + return sensorEventTableName; + } +} diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.controller.api/src/main/java/org/wso2/carbon/device/mgt/iot/androidsense/controller/service/impl/transport/AndroidSenseMQTTConnector.java b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.controller.api/src/main/java/org/wso2/carbon/device/mgt/iot/androidsense/controller/service/impl/transport/AndroidSenseMQTTConnector.java new file mode 100644 index 0000000000..52ace2bf57 --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.controller.api/src/main/java/org/wso2/carbon/device/mgt/iot/androidsense/controller/service/impl/transport/AndroidSenseMQTTConnector.java @@ -0,0 +1,339 @@ +/* + * 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.wso2.carbon.device.mgt.iot.androidsense.controller.service.impl.transport; + +import com.google.gson.Gson; +import com.google.gson.JsonSyntaxException; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.eclipse.paho.client.mqttv3.MqttException; +import org.eclipse.paho.client.mqttv3.MqttMessage; +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.Device; +import org.wso2.carbon.device.mgt.common.DeviceIdentifier; +import org.wso2.carbon.device.mgt.common.DeviceManagementException; +import org.wso2.carbon.device.mgt.core.service.DeviceManagementProviderService; +import org.wso2.carbon.device.mgt.iot.androidsense.controller.service.impl.util.DeviceData; +import org.wso2.carbon.device.mgt.iot.androidsense.controller.service.impl.util.SensorData; +import org.wso2.carbon.device.mgt.iot.androidsense.plugin.constants.AndroidSenseConstants; +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 org.wso2.carbon.utils.multitenancy.MultitenantUtils; + +import java.nio.charset.StandardCharsets; +import java.util.UUID; + +@SuppressWarnings("no JAX-WS annotation") +public class AndroidSenseMQTTConnector extends MQTTTransportHandler { + private static Log log = LogFactory.getLog(AndroidSenseMQTTConnector.class); + private static String subscribeTopic = AndroidSenseConstants.MQTT_SUBSCRIBE_WORDS_TOPIC; + private static String iotServerSubscriber = UUID.randomUUID().toString().substring(0, 5); + + private AndroidSenseMQTTConnector() { + super(iotServerSubscriber, AndroidSenseConstants.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(); + } + + /** + * @throws TransportHandlerException in the event of any exceptions that occur whilst processing the message. + */ + @Override + public void processIncomingMessage() throws TransportHandlerException { + } + + /** + * @param message the message (of the type specific to the protocol) received from the device. + * @throws TransportHandlerException + */ + @Override + public void processIncomingMessage(MqttMessage message) throws TransportHandlerException { + } + + + @Override + public void publishDeviceData(String... publishData) throws TransportHandlerException { + if (publishData.length != 3) { + String errorMsg = "Incorrect number of arguments received to SEND-MQTT Message. " + + "Need to be [owner, deviceId, content]"; + log.error(errorMsg); + throw new TransportHandlerException(errorMsg); + } + String deviceId = publishData[0]; + String operation = publishData[1]; + String resource = publishData[2]; + MqttMessage pushMessage = new MqttMessage(); + String publishTopic = "wso2/" + AndroidSenseConstants.DEVICE_TYPE + "/" + deviceId + "/command"; + if (operation.equals("add")) { + publishTopic = publishTopic + "/words"; + } else if (operation.equals("remove")) { + publishTopic = publishTopic + "/remove"; + } else if (operation.equals("threshold")) { + publishTopic = publishTopic + "/threshold"; + } else { + return; + } + String actualMessage = resource; + pushMessage.setPayload(actualMessage.getBytes(StandardCharsets.UTF_8)); + pushMessage.setQos(DEFAULT_MQTT_QUALITY_OF_SERVICE); + pushMessage.setRetained(false); + publishToQueue(publishTopic, pushMessage); + } + + @Override + public void processIncomingMessage(MqttMessage mqttMessage, String... strings) throws TransportHandlerException { + String[] topic = strings[0].split("/"); + String deviceId = topic[3]; + if (log.isDebugEnabled()) { + log.debug("Received MQTT message for: & [DEVICE.ID-" + deviceId + "]"); + } + try { + Gson gson = new Gson(); + String actualMessage = mqttMessage.toString(); + DeviceData deviceData = gson.fromJson(actualMessage, DeviceData.class); + + SensorData[] sensorData = deviceData.values; + String streamDef = null; + Object payloadData[] = null; + String sensorName = null; + PrivilegedCarbonContext.startTenantFlow(); + PrivilegedCarbonContext ctx = PrivilegedCarbonContext.getThreadLocalCarbonContext(); + ctx.setTenantDomain("carbon.super", true); + for (SensorData sensor : sensorData) { + if (sensor.key.equals("battery")) { + streamDef = AndroidSenseConstants.BATTERY_STREAM_DEFINITION; + payloadData = new Float[]{Float.parseFloat(sensor.value)}; + sensorName = AndroidSenseConstants.SENSOR_BATTERY; + } else if (sensor.key.equals("GPS")) { + streamDef = AndroidSenseConstants.GPS_STREAM_DEFINITION; + String gpsValue = sensor.value; + String gpsValuesString[] = gpsValue.split(","); + Float gpsValues[] = new Float[2]; + gpsValues[0] = Float.parseFloat(gpsValuesString[0]); + gpsValues[1] = Float.parseFloat(gpsValuesString[0]); + payloadData = gpsValues; + sensorName = AndroidSenseConstants.SENSOR_GPS; + } else if (sensor.key.equals("word")) { + try { + streamDef = AndroidSenseConstants.WORD_COUNT_STREAM_DEFINITION; + String[] values = sensor.value.split(","); + String sessionId = values[0]; + String keyword = values[1]; + int occurrence = Integer.parseInt(values[2]); + String status = values[3]; + sensorName = AndroidSenseConstants.SENSOR_WORDCOUNT; + + if (occurrence > 0) { + payloadData = new Object[]{sessionId, keyword, status}; + for (int i = 0; i < occurrence; i++) { + Long timestamp = Long.parseLong(values[3 + occurrence]); + publishDataToDAS(deviceId, timestamp, sensorName, streamDef, + sensor.value, payloadData); + + } + continue; + } + } catch (ArrayIndexOutOfBoundsException e) { + log.error( + "Timestamp does not match the occurence sensor values are sent from the device."); + } + continue; + } else { + try { + int androidSensorId = Integer.parseInt(sensor.key); + String sensorValue = sensor.value; + String sensorValuesString[] = sensorValue.split(","); + Float sensorValues[] = new Float[1]; + + switch (androidSensorId) { + case 1: + streamDef = AndroidSenseConstants.ACCELEROMETER_STREAM_DEFINITION; + sensorValues[0] = Float.parseFloat(sensorValuesString[0]) * Float.parseFloat( + sensorValuesString[0]) * Float. + parseFloat(sensorValuesString[0]); + payloadData = sensorValues; + sensorName = AndroidSenseConstants.SENSOR_ACCELEROMETER; + break; + case 2: + streamDef = AndroidSenseConstants.MAGNETIC_STREAM_DEFINITION; + sensorValues[0] = Float.parseFloat(sensorValuesString[0]) * Float.parseFloat( + sensorValuesString[0]) * Float. + parseFloat(sensorValuesString[0]); + payloadData = sensorValues; + sensorName = AndroidSenseConstants.SENSOR_MAGNETIC; + break; + case 4: + streamDef = AndroidSenseConstants.GYROSCOPE_STREAM_DEFINITION; + sensorValues[0] = Float.parseFloat(sensorValuesString[0]) * Float.parseFloat( + sensorValuesString[0]) * Float. + parseFloat(sensorValuesString[0]); + payloadData = sensorValues; + sensorName = AndroidSenseConstants.SENSOR_GYROSCOPE; + break; + case 5: + streamDef = AndroidSenseConstants.LIGHT_STREAM_DEFINITION; + sensorName = AndroidSenseConstants.SENSOR_LIGHT; + payloadData = new Float[]{Float.parseFloat(sensorValuesString[0])}; + break; + case 6: + streamDef = AndroidSenseConstants.PRESSURE_STREAM_DEFINITION; + sensorName = AndroidSenseConstants.SENSOR_PRESSURE; + payloadData = new Float[]{Float.parseFloat(sensorValuesString[0])}; + break; + case 8: + streamDef = AndroidSenseConstants.PROXIMITY_STREAM_DEFINITION; + sensorName = AndroidSenseConstants.SENSOR_PROXIMITY; + payloadData = new Float[]{Float.parseFloat(sensorValuesString[0])}; + break; + case 9: + streamDef = AndroidSenseConstants.GRAVITY_STREAM_DEFINITION; + sensorValues[0] = Float.parseFloat(sensorValuesString[0]) * Float.parseFloat( + sensorValuesString[0]) * Float. + parseFloat(sensorValuesString[0]); + payloadData = sensorValues; + sensorName = AndroidSenseConstants.SENSOR_GRAVITY; + break; + case 11: + streamDef = AndroidSenseConstants.ROTATION_STREAM_DEFINITION; + sensorValues[0] = Float.parseFloat(sensorValuesString[0]) * Float.parseFloat( + sensorValuesString[0]) * Float. + parseFloat(sensorValuesString[0]); + payloadData = sensorValues; + sensorName = AndroidSenseConstants.SENSOR_ROTATION; + break; + } + } catch (NumberFormatException e) { + log.error("Invalid sensor values are sent from the device."); + continue; + } + } + publishDataToDAS(deviceId, sensor.time, sensorName, streamDef, sensor.value, payloadData); + } + } catch (JsonSyntaxException e) { + throw new TransportHandlerException("Invalid message format " + mqttMessage.toString()); + } finally { + PrivilegedCarbonContext.endTenantFlow(); + } + } + + private void publishDataToDAS(String deviceId, Long time, String sensorName, + String streamDefinition, String sensorValue, Object payloadData[]) { + try { + PrivilegedCarbonContext ctx = PrivilegedCarbonContext.getThreadLocalCarbonContext(); + DeviceManagementProviderService deviceManagementProviderService = (DeviceManagementProviderService) ctx + .getOSGiService(DeviceManagementProviderService.class, null); + if (deviceManagementProviderService != null) { + DeviceIdentifier identifier = new DeviceIdentifier(deviceId, AndroidSenseConstants.DEVICE_TYPE); + Device device = deviceManagementProviderService.getDevice(identifier); + if (device != null) { + String owner = device.getEnrolmentInfo().getOwner(); + ctx.setTenantDomain(MultitenantUtils.getTenantDomain(owner), true); + DeviceAnalyticsService deviceAnalyticsService = (DeviceAnalyticsService) ctx + .getOSGiService(DeviceAnalyticsService.class, null); + if (deviceAnalyticsService != null) { + Object metaData[] = {owner, AndroidSenseConstants.DEVICE_TYPE, deviceId, time}; + if (streamDefinition != null && payloadData != null && payloadData.length > 0) { + try { + SensorDataManager.getInstance().setSensorRecord(deviceId, sensorName, sensorValue, time); + deviceAnalyticsService.publishEvent(streamDefinition, "1.0.0", metaData, + new Object[0], payloadData); + } catch (DataPublisherConfigurationException e) { + log.error("Data publisher configuration failed - " + e); + } + } + } + } + } + } catch (DeviceManagementException e) { + log.error("Failed to load device management service.", e); + } + } + + /** + * @throws TransportHandlerException in the event of any exceptions that occur whilst sending the message. + */ + @Override + public void publishDeviceData() throws TransportHandlerException { + + } + + /** + * @param publishData the message (of the type specific to the protocol) to be sent to the device. + * @throws TransportHandlerException in the event of any exceptions that occur whilst sending the message. + */ + @Override + public void publishDeviceData(MqttMessage publishData) throws TransportHandlerException { + + } + + @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 - " + AndroidSenseConstants.DEVICE_TYPE, e); + } + try { + Thread.sleep(timeoutInterval); + } catch (InterruptedException e1) { + log.error("MQTT-Terminator: Thread Sleep Interrupt Exception at device-type - " + + AndroidSenseConstants.DEVICE_TYPE, e1); + } + } + } + } + }; + Thread terminatorThread = new Thread(stopConnection); + terminatorThread.setDaemon(true); + terminatorThread.start(); + } +} diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.controller.api/src/main/java/org/wso2/carbon/device/mgt/iot/androidsense/controller/service/impl/util/DeviceData.java b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.controller.api/src/main/java/org/wso2/carbon/device/mgt/iot/androidsense/controller/service/impl/util/DeviceData.java new file mode 100644 index 0000000000..2577a21f81 --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.controller.api/src/main/java/org/wso2/carbon/device/mgt/iot/androidsense/controller/service/impl/util/DeviceData.java @@ -0,0 +1,15 @@ +package org.wso2.carbon.device.mgt.iot.androidsense.controller.service.impl.util; + +import org.codehaus.jackson.annotate.JsonIgnoreProperties; + +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; + +@XmlRootElement + +@JsonIgnoreProperties(ignoreUnknown = true) +public class DeviceData { + @XmlElement(required = true) public String owner; + @XmlElement(required = true) public String deviceId; + @XmlElement public SensorData[] values; +} diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.controller.api/src/main/java/org/wso2/carbon/device/mgt/iot/androidsense/controller/service/impl/util/SensorData.java b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.controller.api/src/main/java/org/wso2/carbon/device/mgt/iot/androidsense/controller/service/impl/util/SensorData.java new file mode 100644 index 0000000000..a2f9cebf98 --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.controller.api/src/main/java/org/wso2/carbon/device/mgt/iot/androidsense/controller/service/impl/util/SensorData.java @@ -0,0 +1,44 @@ +package org.wso2.carbon.device.mgt.iot.androidsense.controller.service.impl.util; + +import org.codehaus.jackson.annotate.JsonIgnoreProperties; + +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; + +@XmlRootElement +/** + * This stores sensor event data for android sense. + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class SensorData { + + @XmlElement public Long time; + @XmlElement public String key; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } + + public Long getTime() { + return time; + } + + public void setTime(Long time) { + this.time = time; + } + + @XmlElement public String value; + +} diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.controller.api/src/main/webapp/META-INF/permissions.xml b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.controller.api/src/main/webapp/META-INF/permissions.xml new file mode 100644 index 0000000000..d30a5096fb --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.controller.api/src/main/webapp/META-INF/permissions.xml @@ -0,0 +1,143 @@ + + + + + + + + + + add sensor information + /login + /controller/sensors + POST + + + + Get light information + /login + /controller/device/{deviceId}/sensors/light + GET + + + + Get battery information + /login + /controller/device/{deviceId}/sensors/battery + GET + + + + Get gps information + /login + /controller/device/{deviceId}/sensors/gps + GET + + + + Get magnetic information + /login + /controller/device/{deviceId}/sensors/magnetic + GET + + + + Get accelerometer information + /login + /controller/device/{deviceId}/sensors/accelerometer + GET + + + + Get rotation information + /login + /controller/device/{deviceId}/sensors/rotation + GET + + + + Get proximity information + /login + /controller/device/{deviceId}/sensors/proximity + GET + + + + Get gyroscope information + /login + /controller/device/{deviceId}/sensors/gyroscope + GET + + + + Get pressure information + /login + /controller/device/{deviceId}/sensors/pressure + GET + + + + Get gravity information + /login + /controller/device/{deviceId}/sensors/gravity + GET + + + + Get words information + /login + /controller/device/{deviceId}/sensors/words + GET + + + + Get words information + /login + /controller/device/{deviceId}/sensors/words + POST + + + + set word threshold information + /login + /controller/device/{deviceId}/sensors/words/threshold + POST + + + + delete words + /login + /controller/device/{deviceId}/sensors/words + DELETE + + + + get device stats + /login + /controller/stats/device/{deviceId}/sensors/{sensorName} + GET + + + \ No newline at end of file diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.controller.api/src/main/webapp/META-INF/webapp-classloading.xml b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.controller.api/src/main/webapp/META-INF/webapp-classloading.xml new file mode 100644 index 0000000000..fa44619195 --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.controller.api/src/main/webapp/META-INF/webapp-classloading.xml @@ -0,0 +1,33 @@ + + + + + + + + + false + + + CXF,Carbon + diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.controller.api/src/main/webapp/WEB-INF/cxf-servlet.xml b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.controller.api/src/main/webapp/WEB-INF/cxf-servlet.xml new file mode 100644 index 0000000000..476cbcc610 --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.controller.api/src/main/webapp/WEB-INF/cxf-servlet.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.controller.api/src/main/webapp/WEB-INF/web.xml b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.controller.api/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 0000000000..138f002d0d --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.controller.api/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,57 @@ + + + Android Sense + Android Sense + + + CXFServlet + org.apache.cxf.transport.servlet.CXFServlet + 1 + + + CXFServlet + /* + + + isAdminService + false + + + doAuthentication + false + + + isSharedWithAllTenants + true + + + providerTenantDomain + carbon.super + + + + + managed-api-enabled + true + + + managed-api-owner + admin + + + managed-api-context-template + /android_sense/{version} + + + managed-api-application + android_sense + + + managed-api-isSecured + true + + diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.manager.api/pom.xml b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.manager.api/pom.xml new file mode 100644 index 0000000000..91f33ca0c7 --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.manager.api/pom.xml @@ -0,0 +1,205 @@ + + + + androidsense-plugin + org.wso2.carbon.devicemgt-plugins + 2.1.0-SNAPSHOT + ../pom.xml + + + 4.0.0 + org.wso2.carbon.device.mgt.iot.androidsense.manager.api + war + WSO2 Carbon - Android Sense Management API + WSO2 Carbon - Android Sense Management Service-API Implementation + http://wso2.org + + + + + + org.wso2.carbon.devicemgt + org.wso2.carbon.device.mgt.common + provided + + + org.wso2.carbon.devicemgt + org.wso2.carbon.device.mgt.core + provided + + + org.apache.axis2.wso2 + axis2-client + + + + + + + org.apache.cxf + cxf-rt-frontend-jaxws + provided + + + org.apache.cxf + cxf-rt-frontend-jaxrs + provided + + + org.apache.cxf + cxf-rt-transports-http + provided + + + + org.eclipse.paho + org.eclipse.paho.client.mqttv3 + + + + + org.apache.httpcomponents + httpasyncclient + 4.1 + provided + + + org.wso2.carbon.devicemgt-plugins + org.wso2.carbon.device.mgt.iot + provided + + + org.wso2.carbon.devicemgt-plugins + org.wso2.carbon.device.mgt.iot.androidsense.plugin + provided + + + + org.codehaus.jackson + jackson-core-asl + + + org.codehaus.jackson + jackson-jaxrs + + + javax + javaee-web-api + provided + + + javax.ws.rs + jsr311-api + provided + + + + org.wso2.carbon.devicemgt + org.wso2.carbon.device.mgt.analytics + provided + + + + commons-httpclient.wso2 + commons-httpclient + provided + + + + org.wso2.carbon + org.wso2.carbon.utils + provided + + + 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.wso2.carbon.devicemgt + org.wso2.carbon.apimgt.annotations + provided + + + org.wso2.carbon.devicemgt + org.wso2.carbon.apimgt.webapp.publisher + provided + + + + + + + + maven-compiler-plugin + + UTF-8 + ${wso2.maven.compiler.source} + ${wso2.maven.compiler.target} + + + + maven-war-plugin + + android_sense_mgt + + + + + + diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.manager.api/src/main/java/org/wso2/carbon/device/mgt/iot/androidsense/manager/service/impl/AndroidSenseManagerService.java b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.manager.api/src/main/java/org/wso2/carbon/device/mgt/iot/androidsense/manager/service/impl/AndroidSenseManagerService.java new file mode 100644 index 0000000000..a9f942fde9 --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.manager.api/src/main/java/org/wso2/carbon/device/mgt/iot/androidsense/manager/service/impl/AndroidSenseManagerService.java @@ -0,0 +1,166 @@ +/* + * Copyright (c) 2016, 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.wso2.carbon.device.mgt.iot.androidsense.manager.service.impl; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.context.PrivilegedCarbonContext; +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.androidsense.manager.service.impl.util.APIUtil; +import org.wso2.carbon.device.mgt.iot.androidsense.plugin.constants.AndroidSenseConstants; +import org.wso2.carbon.utils.CarbonUtils; +import javax.servlet.http.HttpServletResponse; +import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; +import javax.ws.rs.FormParam; +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.io.File; +import java.util.Date; + +public class AndroidSenseManagerService { + + private static Log log = LogFactory.getLog(AndroidSenseManagerService.class); + + @Context //injected response proxy supporting multiple thread + private HttpServletResponse response; + + @Path("manager/device") + @POST + public boolean register(@QueryParam("deviceId") String deviceId, @QueryParam("deviceName") String deviceName) { + DeviceIdentifier deviceIdentifier = new DeviceIdentifier(); + deviceIdentifier.setId(deviceId); + deviceIdentifier.setType(AndroidSenseConstants.DEVICE_TYPE); + try { + if (APIUtil.getDeviceManagementService().isEnrolled(deviceIdentifier)) { + response.setStatus(Response.Status.CONFLICT.getStatusCode()); + return true; + } + + 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); + device.setName(deviceName); + device.setType(AndroidSenseConstants.DEVICE_TYPE); + enrolmentInfo.setOwner(APIUtil.getAuthenticatedUser()); + device.setEnrolmentInfo(enrolmentInfo); + boolean added = APIUtil.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; + } + } + + @Path("manager/device/{device_id}") + @DELETE + public void removeDevice(@PathParam("device_id") String deviceId, @Context HttpServletResponse response) { + DeviceIdentifier deviceIdentifier = new DeviceIdentifier(); + deviceIdentifier.setId(deviceId); + deviceIdentifier.setType(AndroidSenseConstants.DEVICE_TYPE); + try { + boolean removed = APIUtil.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()); + } + } + + @Path("manager/device/{device_id}") + @PUT + public boolean updateDevice(@PathParam("device_id") String deviceId, @QueryParam("name") String name, + @Context HttpServletResponse response) { + DeviceIdentifier deviceIdentifier = new DeviceIdentifier(); + deviceIdentifier.setId(deviceId); + deviceIdentifier.setType(AndroidSenseConstants.DEVICE_TYPE); + try { + Device device = APIUtil.getDeviceManagementService().getDevice(deviceIdentifier); + device.setDeviceIdentifier(deviceId); + device.getEnrolmentInfo().setDateOfLastUpdate(new Date().getTime()); + device.setName(name); + device.setType(AndroidSenseConstants.DEVICE_TYPE); + boolean updated = APIUtil.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; + } + } + + @Path("manager/device/{device_id}") + @GET + @Consumes("application/json") + @Produces("application/json") + public Device getDevice(@PathParam("device_id") String deviceId) { + DeviceIdentifier deviceIdentifier = new DeviceIdentifier(); + deviceIdentifier.setId(deviceId); + deviceIdentifier.setType(AndroidSenseConstants.DEVICE_TYPE); + try { + return APIUtil.getDeviceManagementService().getDevice(deviceIdentifier); + } catch (DeviceManagementException e) { + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + return null; + } + } + + @Path("manager/device/{sketch_type}/download") + @GET + @Produces("application/octet-stream") + public Response downloadSketch(@PathParam("sketch_type") String sketchType) { + try { + String sep = File.separator; + String sketchFolder = "repository" + sep + "resources" + sep + "sketches" + sep + "android_sense" + sep; + String archivesPath = CarbonUtils.getCarbonHome() + sep + sketchFolder; + Response.ResponseBuilder rb = Response.ok(new File(archivesPath+sep+"androidsense.apk")); + rb.header("Content-Disposition", "attachment; filename=\"" + "androidsense.apk" + "\""); + return rb.build(); + } catch (IllegalArgumentException ex) { + return Response.status(400).entity(ex.getMessage()).build();//bad request + } + } +} diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.manager.api/src/main/java/org/wso2/carbon/device/mgt/iot/androidsense/manager/service/impl/util/APIUtil.java b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.manager.api/src/main/java/org/wso2/carbon/device/mgt/iot/androidsense/manager/service/impl/util/APIUtil.java new file mode 100644 index 0000000000..ec55710dd5 --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.manager.api/src/main/java/org/wso2/carbon/device/mgt/iot/androidsense/manager/service/impl/util/APIUtil.java @@ -0,0 +1,36 @@ +package org.wso2.carbon.device.mgt.iot.androidsense.manager.service.impl.util; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.context.PrivilegedCarbonContext; +import org.wso2.carbon.device.mgt.core.service.DeviceManagementProviderService; + +/** + * This class provides utility functions used by REST-API. + */ +public class APIUtil { + + private static Log log = LogFactory.getLog(APIUtil.class); + + public static String getAuthenticatedUser() { + PrivilegedCarbonContext threadLocalCarbonContext = PrivilegedCarbonContext.getThreadLocalCarbonContext(); + String username = threadLocalCarbonContext.getUsername(); + String tenantDomain = threadLocalCarbonContext.getTenantDomain(); + if (username.endsWith(tenantDomain)) { + return username.substring(0, username.lastIndexOf("@")); + } + return username; + } + + public static DeviceManagementProviderService getDeviceManagementService() { + PrivilegedCarbonContext ctx = PrivilegedCarbonContext.getThreadLocalCarbonContext(); + DeviceManagementProviderService deviceManagementProviderService = + (DeviceManagementProviderService) ctx.getOSGiService(DeviceManagementProviderService.class, null); + if (deviceManagementProviderService == null) { + String msg = "Device Management service has not initialized."; + log.error(msg); + throw new IllegalStateException(msg); + } + return deviceManagementProviderService; + } +} diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.manager.api/src/main/webapp/META-INF/permissions.xml b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.manager.api/src/main/webapp/META-INF/permissions.xml new file mode 100644 index 0000000000..44c1677f0c --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.manager.api/src/main/webapp/META-INF/permissions.xml @@ -0,0 +1,66 @@ + + + + + + + + + + Get device + /device-mgt/user/devices/list + /manager/device/* + GET + + + + Add device + /device-mgt/user/devices/add + /manager/device + POST + + + + Remove device + /device-mgt/user/devices/remove + /manager/device/* + DELETE + + + + Download device + /device-mgt/user/devices/add + /manager/device/*/download + GET + + + + Update device + /device-mgt/user/devices/update + /manager/device/* + POST + + + \ No newline at end of file diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.manager.api/src/main/webapp/META-INF/webapp-classloading.xml b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.manager.api/src/main/webapp/META-INF/webapp-classloading.xml new file mode 100644 index 0000000000..fa44619195 --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.manager.api/src/main/webapp/META-INF/webapp-classloading.xml @@ -0,0 +1,33 @@ + + + + + + + + + false + + + CXF,Carbon + diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.manager.api/src/main/webapp/WEB-INF/cxf-servlet.xml b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.manager.api/src/main/webapp/WEB-INF/cxf-servlet.xml new file mode 100644 index 0000000000..e732f74271 --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.manager.api/src/main/webapp/WEB-INF/cxf-servlet.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.manager.api/src/main/webapp/WEB-INF/web.xml b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.manager.api/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 0000000000..0c0ec3ef10 --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.manager.api/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,49 @@ + + + Android Sense + Android Sense + + + CXFServlet + org.apache.cxf.transport.servlet.CXFServlet + 1 + + + CXFServlet + /* + + + + isAdminService + false + + + doAuthentication + true + + + + managed-api-enabled + false + + + managed-api-owner + admin + + + managed-api-context-template + /android_sense_mgt/{version} + + + managed-api-application + android_sense + + + managed-api-isSecured + false + + diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.plugin/pom.xml b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.plugin/pom.xml new file mode 100644 index 0000000000..fc0d2f4c90 --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.plugin/pom.xml @@ -0,0 +1,115 @@ + + + + + + + androidsense-plugin + org.wso2.carbon.devicemgt-plugins + 2.1.0-SNAPSHOT + ../pom.xml + + + 4.0.0 + org.wso2.carbon.device.mgt.iot.androidsense.plugin + bundle + WSO2 Carbon - AndroidSense DeviceType Plugin + WSO2 Carbon - AndroidSense DeviceType Plugin Implementation + http://wso2.org + + + + + org.apache.felix + maven-scr-plugin + + + maven-compiler-plugin + + 1.7 + 1.7 + + 2.3.2 + + + org.apache.felix + maven-bundle-plugin + 1.4.0 + true + + + ${project.artifactId} + ${project.artifactId} + ${carbon.devicemgt.plugins.version} + IoT Server Impl Bundle + org.wso2.carbon.device.mgt.iot.androidsense.plugin.internal + + org.osgi.framework, + org.osgi.service.component, + org.apache.commons.logging, + javax.xml.bind.*;resolution:=optional, + javax.naming;resolution:=optional, + javax.sql;resolution:=optional, + javax.xml.bind.annotation.*;resolution:=optional, + javax.xml.parsers;resolution:=optional, + javax.net;resolution:=optional, + javax.net.ssl;resolution:=optional, + org.w3c.dom;resolution:=optional, + org.wso2.carbon.device.mgt.common.*, + org.wso2.carbon.device.mgt.common, + org.wso2.carbon.context.*, + org.wso2.carbon.ndatasource.core, + org.wso2.carbon.device.mgt.iot.*, + org.wso2.carbon.device.mgt.extensions.feature.mgt.*, + org.wso2.carbon.utils.* + + + !org.wso2.carbon.device.mgt.iot.androidsense.plugin.internal, + org.wso2.carbon.device.mgt.iot.androidsense.plugin.* + + + + + + + + + + org.eclipse.osgi + org.eclipse.osgi + + + org.eclipse.osgi + org.eclipse.osgi.services + + + org.wso2.carbon + org.wso2.carbon.logging + + + org.wso2.carbon.devicemgt + org.wso2.carbon.device.mgt.common + + + org.wso2.carbon.devicemgt + org.wso2.carbon.device.mgt.extensions + + + org.wso2.carbon + org.wso2.carbon.ndatasource.core + + + org.wso2.carbon + org.wso2.carbon.utils + + + \ No newline at end of file diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.plugin/src/main/java/org/wso2/carbon/device/mgt/iot/androidsense/plugin/constants/AndroidSenseConstants.java b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.plugin/src/main/java/org/wso2/carbon/device/mgt/iot/androidsense/plugin/constants/AndroidSenseConstants.java new file mode 100644 index 0000000000..9666dcc826 --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.plugin/src/main/java/org/wso2/carbon/device/mgt/iot/androidsense/plugin/constants/AndroidSenseConstants.java @@ -0,0 +1,53 @@ +/* + * 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. + */ + +package org.wso2.carbon.device.mgt.iot.androidsense.plugin.constants; + +public class AndroidSenseConstants { + + public final static String DEVICE_TYPE = "android_sense"; + public final static String DEVICE_PLUGIN_DEVICE_NAME = "DEVICE_NAME"; + public final static String DEVICE_PLUGIN_DEVICE_ID = "ANDROID_DEVICE_ID"; + public final static String DEVICE_TYPE_PROVIDER_DOMAIN = "carbon.super"; + //Android Sense Stream definitions. + public static final String ACCELEROMETER_STREAM_DEFINITION = "org.wso2.iot.devices.accelerometer"; + public static final String BATTERY_STREAM_DEFINITION = "org.wso2.iot.devices.battery"; + public static final String GPS_STREAM_DEFINITION = "org.wso2.iot.devices.gps"; + public static final String GRAVITY_STREAM_DEFINITION = "org.wso2.iot.devices.gravity"; + public static final String GYROSCOPE_STREAM_DEFINITION = "org.wso2.iot.devices.gyroscope"; + public static final String LIGHT_STREAM_DEFINITION = "org.wso2.iot.devices.light"; + public static final String MAGNETIC_STREAM_DEFINITION = "org.wso2.iot.devices.magnetic"; + public static final String PRESSURE_STREAM_DEFINITION = "org.wso2.iot.devices.pressure"; + public static final String PROXIMITY_STREAM_DEFINITION = "org.wso2.iot.devices.proximity"; + public static final String ROTATION_STREAM_DEFINITION = "org.wso2.iot.devices.rotation"; + public static final String WORD_COUNT_STREAM_DEFINITION = "org.wso2.iot.devices.wordcount"; + + //Android Sensor names + public static final String SENSOR_ACCELEROMETER = "accelerometer"; + public static final String SENSOR_BATTERY = "battery"; + public static final String SENSOR_GPS = "gps"; + public static final String SENSOR_GRAVITY = "gravity"; + public static final String SENSOR_GYROSCOPE = "gyroscope"; + public static final String SENSOR_LIGHT = "light"; + public static final String SENSOR_MAGNETIC = "magnetic"; + public static final String SENSOR_PRESSURE = "pressure"; + public static final String SENSOR_PROXIMITY = "proximity"; + public static final String SENSOR_ROTATION = "rotation"; + public static final String SENSOR_WORDCOUNT = "wordcounter"; + //MQTT Subscribe topic + public static final String MQTT_SUBSCRIBE_WORDS_TOPIC = "wso2/android_sense/+/data"; + public static final String DATA_SOURCE_NAME = "jdbc/AndroidSenseDM_DB"; +} diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.plugin/src/main/java/org/wso2/carbon/device/mgt/iot/androidsense/plugin/exception/AndroidSenseDeviceMgtPluginException.java b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.plugin/src/main/java/org/wso2/carbon/device/mgt/iot/androidsense/plugin/exception/AndroidSenseDeviceMgtPluginException.java new file mode 100644 index 0000000000..88557cd9f5 --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.plugin/src/main/java/org/wso2/carbon/device/mgt/iot/androidsense/plugin/exception/AndroidSenseDeviceMgtPluginException.java @@ -0,0 +1,56 @@ +/* + * 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.wso2.carbon.device.mgt.iot.androidsense.plugin.exception; + + +public class AndroidSenseDeviceMgtPluginException extends Exception{ + + private String errorMessage; + + public String getErrorMessage() { + return errorMessage; + } + + public void setErrorMessage(String errorMessage) { + this.errorMessage = errorMessage; + } + + public AndroidSenseDeviceMgtPluginException(String msg, Exception nestedEx) { + super(msg, nestedEx); + setErrorMessage(msg); + } + + public AndroidSenseDeviceMgtPluginException(String message, Throwable cause) { + super(message, cause); + setErrorMessage(message); + } + + public AndroidSenseDeviceMgtPluginException(String msg) { + super(msg); + setErrorMessage(msg); + } + + public AndroidSenseDeviceMgtPluginException() { + super(); + } + + public AndroidSenseDeviceMgtPluginException(Throwable cause) { + super(cause); + } + +} diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.plugin/src/main/java/org/wso2/carbon/device/mgt/iot/androidsense/plugin/impl/AndroidSenseManager.java b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.plugin/src/main/java/org/wso2/carbon/device/mgt/iot/androidsense/plugin/impl/AndroidSenseManager.java new file mode 100644 index 0000000000..7cab751f65 --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.plugin/src/main/java/org/wso2/carbon/device/mgt/iot/androidsense/plugin/impl/AndroidSenseManager.java @@ -0,0 +1,256 @@ +/* + * 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. + */ + +package org.wso2.carbon.device.mgt.iot.androidsense.plugin.impl; + + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +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.DeviceManager; +import org.wso2.carbon.device.mgt.common.EnrolmentInfo; +import org.wso2.carbon.device.mgt.common.FeatureManager; +import org.wso2.carbon.device.mgt.common.configuration.mgt.TenantConfiguration; +import org.wso2.carbon.device.mgt.common.license.mgt.License; +import org.wso2.carbon.device.mgt.common.license.mgt.LicenseManagementException; +import org.wso2.carbon.device.mgt.iot.androidsense.plugin.exception.AndroidSenseDeviceMgtPluginException; +import org.wso2.carbon.device.mgt.iot.androidsense.plugin.impl.feature.AndroidSenseFeatureManager; +import org.wso2.carbon.device.mgt.iot.androidsense.plugin.impl.dao.AndroidSenseDAO; +import java.util.List; + + +/** + * This represents the Android implementation of DeviceManagerService. + */ +public class AndroidSenseManager implements DeviceManager { + + private static final AndroidSenseDAO androidSenseDAO = new AndroidSenseDAO(); + private static final Log log = LogFactory.getLog(AndroidSenseManager.class); + private FeatureManager androidSenseFeatureManager = new AndroidSenseFeatureManager(); + + @Override + public FeatureManager getFeatureManager() { + return androidSenseFeatureManager; + } + + @Override + public boolean saveConfiguration(TenantConfiguration tenantConfiguration) + throws DeviceManagementException { + //TODO implement this + return false; + } + + @Override + public TenantConfiguration getConfiguration() throws DeviceManagementException { + //TODO implement this + return null; + } + + @Override + public boolean enrollDevice(Device device) throws DeviceManagementException { + boolean status; + try { + if (log.isDebugEnabled()) { + log.debug("Enrolling a new Android device : " + device.getDeviceIdentifier()); + } + AndroidSenseDAO.beginTransaction(); + status = androidSenseDAO.getDeviceDAO().addDevice(device); + AndroidSenseDAO.commitTransaction(); + } catch (AndroidSenseDeviceMgtPluginException e) { + try { + AndroidSenseDAO.rollbackTransaction(); + } catch (AndroidSenseDeviceMgtPluginException iotDAOEx) { + String msg = "Error occurred while roll back the device enrol transaction :" + device.toString(); + log.warn(msg, iotDAOEx); + } + String msg = "Error while enrolling the Android device : " + device.getDeviceIdentifier(); + log.error(msg, e); + throw new DeviceManagementException(msg, e); + } + return status; + } + + @Override + public boolean modifyEnrollment(Device device) throws DeviceManagementException { + boolean status; + try { + if (log.isDebugEnabled()) { + log.debug("Modifying the Android device enrollment data"); + } + AndroidSenseDAO.beginTransaction(); + status = androidSenseDAO.getDeviceDAO().updateDevice(device); + AndroidSenseDAO.commitTransaction(); + } catch (AndroidSenseDeviceMgtPluginException e) { + try { + AndroidSenseDAO.rollbackTransaction(); + } catch (AndroidSenseDeviceMgtPluginException iotDAOEx) { + String msg = "Error occurred while roll back the update device transaction :" + device.toString(); + log.warn(msg, iotDAOEx); + } + String msg = "Error while updating the enrollment of the Android device : " + + device.getDeviceIdentifier(); + log.error(msg, e); + throw new DeviceManagementException(msg, e); + } + return status; + } + + @Override + public boolean disenrollDevice(DeviceIdentifier deviceId) throws DeviceManagementException { + boolean status; + try { + if (log.isDebugEnabled()) { + log.debug("Dis-enrolling Android device : " + deviceId); + } + AndroidSenseDAO.beginTransaction(); + status = androidSenseDAO.getDeviceDAO().deleteDevice(deviceId.getId()); + AndroidSenseDAO.commitTransaction(); + } catch (AndroidSenseDeviceMgtPluginException e) { + try { + AndroidSenseDAO.rollbackTransaction(); + } catch (AndroidSenseDeviceMgtPluginException iotDAOEx) { + String msg = "Error occurred while roll back the device dis enrol transaction :" + deviceId.toString(); + log.warn(msg, iotDAOEx); + } + String msg = "Error while removing the Android device : " + deviceId.getId(); + log.error(msg, e); + throw new DeviceManagementException(msg, e); + } + return status; + } + + @Override + public boolean isEnrolled(DeviceIdentifier deviceId) throws DeviceManagementException { + boolean isEnrolled = false; + try { + if (log.isDebugEnabled()) { + log.debug("Checking the enrollment of Android device : " + deviceId.getId()); + } + Device iotDevice = androidSenseDAO.getDeviceDAO().getDevice(deviceId.getId()); + if (iotDevice != null) { + isEnrolled = true; + } + } catch (AndroidSenseDeviceMgtPluginException e) { + String msg = "Error while checking the enrollment status of Android device : " + + deviceId.getId(); + log.error(msg, e); + throw new DeviceManagementException(msg, e); + } + return isEnrolled; + } + + @Override + public boolean isActive(DeviceIdentifier deviceId) throws DeviceManagementException { + return true; + } + + @Override + public boolean setActive(DeviceIdentifier deviceId, boolean status) + throws DeviceManagementException { + return true; + } + + @Override + public Device getDevice(DeviceIdentifier deviceId) throws DeviceManagementException { + Device device; + try { + if (log.isDebugEnabled()) { + log.debug("Getting the details of Android device : " + deviceId.getId()); + } + device = androidSenseDAO.getDeviceDAO().getDevice(deviceId.getId()); + } catch (AndroidSenseDeviceMgtPluginException e) { + String msg = "Error while fetching the Android device : " + deviceId.getId(); + log.error(msg, e); + throw new DeviceManagementException(msg, e); + } + return device; + } + + @Override + public boolean setOwnership(DeviceIdentifier deviceId, String ownershipType) + throws DeviceManagementException { + return true; + } + + public boolean isClaimable(DeviceIdentifier deviceIdentifier) throws DeviceManagementException { + return false; + } + + @Override + public boolean setStatus(DeviceIdentifier deviceId, String currentOwner, + EnrolmentInfo.Status status) throws DeviceManagementException { + return false; + } + + @Override + public License getLicense(String s) throws LicenseManagementException { + return null; + } + + @Override + public void addLicense(License license) throws LicenseManagementException { + + } + + @Override public boolean requireDeviceAuthorization() { + return false; + } + + @Override + public boolean updateDeviceInfo(DeviceIdentifier deviceIdentifier, Device device) throws DeviceManagementException { + boolean status; + try { + if (log.isDebugEnabled()) { + log.debug( + "updating the details of Android device : " + deviceIdentifier); + } + AndroidSenseDAO.beginTransaction(); + status = androidSenseDAO.getDeviceDAO().updateDevice(device); + AndroidSenseDAO.commitTransaction(); + } catch (AndroidSenseDeviceMgtPluginException e) { + try { + AndroidSenseDAO.rollbackTransaction(); + } catch (AndroidSenseDeviceMgtPluginException iotDAOEx) { + String msg = "Error occurred while roll back the update device info transaction :" + device.toString(); + log.warn(msg, iotDAOEx); + } + String msg = + "Error while updating the Android device : " + deviceIdentifier; + log.error(msg, e); + throw new DeviceManagementException(msg, e); + } + return status; + } + + @Override + public List getAllDevices() throws DeviceManagementException { + List devices = null; + try { + if (log.isDebugEnabled()) { + log.debug("Fetching the details of all Android devices"); + } + devices = androidSenseDAO.getDeviceDAO().getAllDevices(); + } catch (AndroidSenseDeviceMgtPluginException e) { + String msg = "Error while fetching all Android devices."; + log.error(msg, e); + throw new DeviceManagementException(msg, e); + } + return devices; + } + +} \ No newline at end of file diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.plugin/src/main/java/org/wso2/carbon/device/mgt/iot/androidsense/plugin/impl/AndroidSenseManagerService.java b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.plugin/src/main/java/org/wso2/carbon/device/mgt/iot/androidsense/plugin/impl/AndroidSenseManagerService.java new file mode 100644 index 0000000000..b31b86531a --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.plugin/src/main/java/org/wso2/carbon/device/mgt/iot/androidsense/plugin/impl/AndroidSenseManagerService.java @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2016, 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. + */ + +package org.wso2.carbon.device.mgt.iot.androidsense.plugin.impl; + +import org.wso2.carbon.device.mgt.common.DeviceIdentifier; +import org.wso2.carbon.device.mgt.common.DeviceManagementException; +import org.wso2.carbon.device.mgt.common.DeviceManager; +import org.wso2.carbon.device.mgt.common.app.mgt.Application; +import org.wso2.carbon.device.mgt.common.app.mgt.ApplicationManagementException; +import org.wso2.carbon.device.mgt.common.app.mgt.ApplicationManager; +import org.wso2.carbon.device.mgt.common.operation.mgt.Operation; +import org.wso2.carbon.device.mgt.common.spi.DeviceManagementService; +import org.wso2.carbon.device.mgt.iot.androidsense.plugin.constants.AndroidSenseConstants; + +import java.util.List; + +public class AndroidSenseManagerService implements DeviceManagementService { + private DeviceManager deviceManager; + @Override + public String getType() { + return AndroidSenseConstants.DEVICE_TYPE; + } + + @Override + public String getProviderTenantDomain() { + return "carbon.super"; + } + + @Override + public boolean isSharedWithAllTenants() { + return true; + } + + @Override + public void init() throws DeviceManagementException { + this.deviceManager=new AndroidSenseManager(); + } + + @Override + public DeviceManager getDeviceManager() { + return deviceManager; + } + + @Override + public ApplicationManager getApplicationManager() { + return null; + } + + @Override public void notifyOperationToDevices(Operation operation, List list) + throws DeviceManagementException { + + } + + @Override + public Application[] getApplications(String domain, int pageNumber, int size) + throws ApplicationManagementException { + return new Application[0]; + } + + @Override + public void updateApplicationStatus(DeviceIdentifier deviceId, Application application, + String status) throws ApplicationManagementException { + + } + + @Override + public String getApplicationStatus(DeviceIdentifier deviceId, Application application) + throws ApplicationManagementException { + return null; + } + + @Override public void installApplicationForDevices(Operation operation, List list) + throws ApplicationManagementException { + + } + + @Override public void installApplicationForUsers(Operation operation, List list) + throws ApplicationManagementException { + + } + + @Override public void installApplicationForUserRoles(Operation operation, List list) + throws ApplicationManagementException { + + } + +} diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.plugin/src/main/java/org/wso2/carbon/device/mgt/iot/androidsense/plugin/impl/dao/AndroidSenseDAO.java b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.plugin/src/main/java/org/wso2/carbon/device/mgt/iot/androidsense/plugin/impl/dao/AndroidSenseDAO.java new file mode 100644 index 0000000000..ab12ac61d8 --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.plugin/src/main/java/org/wso2/carbon/device/mgt/iot/androidsense/plugin/impl/dao/AndroidSenseDAO.java @@ -0,0 +1,123 @@ +/* + * 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. + */ + +package org.wso2.carbon.device.mgt.iot.androidsense.plugin.impl.dao; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.device.mgt.iot.androidsense.plugin.exception.AndroidSenseDeviceMgtPluginException; +import org.wso2.carbon.device.mgt.iot.androidsense.plugin.constants.AndroidSenseConstants; +import org.wso2.carbon.device.mgt.iot.androidsense.plugin.impl.dao.impl.AndroidSenseDAOImpl; +import javax.naming.Context; +import javax.naming.InitialContext; +import javax.naming.NamingException; +import javax.sql.DataSource; +import java.sql.Connection; +import java.sql.SQLException; + +public class AndroidSenseDAO { + + private static final Log log = LogFactory.getLog(AndroidSenseDAO.class); + static DataSource dataSource; + private static ThreadLocal currentConnection = new ThreadLocal(); + + public AndroidSenseDAO() { + initAndroidDAO(); + } + + public static void initAndroidDAO() { + try { + Context ctx = new InitialContext(); + dataSource = (DataSource) ctx.lookup(AndroidSenseConstants.DATA_SOURCE_NAME); + } catch (NamingException e) { + log.error("Error while looking up the data source: " + AndroidSenseConstants.DATA_SOURCE_NAME); + } + } + + public AndroidSenseDAOImpl getDeviceDAO() { + return new AndroidSenseDAOImpl(); + } + + public static void beginTransaction() throws AndroidSenseDeviceMgtPluginException { + try { + Connection conn = dataSource.getConnection(); + conn.setAutoCommit(false); + currentConnection.set(conn); + } catch (SQLException e) { + throw new AndroidSenseDeviceMgtPluginException("Error occurred while retrieving datasource connection", e); + } + } + + public static Connection getConnection() throws AndroidSenseDeviceMgtPluginException { + if (currentConnection.get() == null) { + try { + currentConnection.set(dataSource.getConnection()); + } catch (SQLException e) { + throw new AndroidSenseDeviceMgtPluginException("Error occurred while retrieving data source connection", e); + } + } + return currentConnection.get(); + } + + public static void commitTransaction() throws AndroidSenseDeviceMgtPluginException { + 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 AndroidSenseDeviceMgtPluginException("Error occurred while committing the transaction", e); + } finally { + closeConnection(); + } + } + + public static void closeConnection() throws AndroidSenseDeviceMgtPluginException { + + 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 AndroidSenseDeviceMgtPluginException { + 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 AndroidSenseDeviceMgtPluginException("Error occurred while rollback the transaction", e); + } finally { + closeConnection(); + } + } +} \ No newline at end of file diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.plugin/src/main/java/org/wso2/carbon/device/mgt/iot/androidsense/plugin/impl/dao/impl/AndroidSenseDAOImpl.java b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.plugin/src/main/java/org/wso2/carbon/device/mgt/iot/androidsense/plugin/impl/dao/impl/AndroidSenseDAOImpl.java new file mode 100644 index 0000000000..30afa96f11 --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.plugin/src/main/java/org/wso2/carbon/device/mgt/iot/androidsense/plugin/impl/dao/impl/AndroidSenseDAOImpl.java @@ -0,0 +1,198 @@ +/* + * Copyright (c) 2016, 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. + */ + +package org.wso2.carbon.device.mgt.iot.androidsense.plugin.impl.dao.impl; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.device.mgt.common.Device; +import org.wso2.carbon.device.mgt.iot.androidsense.plugin.constants.AndroidSenseConstants; +import org.wso2.carbon.device.mgt.iot.androidsense.plugin.exception.AndroidSenseDeviceMgtPluginException; +import org.wso2.carbon.device.mgt.iot.androidsense.plugin.impl.dao.AndroidSenseDAO; +import org.wso2.carbon.device.mgt.iot.androidsense.plugin.impl.dao.util.AndroidSenseUtils; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; + +/** + * Implements dao impl for android Devices. + */ +public class AndroidSenseDAOImpl { + + + private static final Log log = LogFactory.getLog(AndroidSenseDAOImpl.class); + + public Device getDevice(String deviceId) throws AndroidSenseDeviceMgtPluginException { + Connection conn = null; + PreparedStatement stmt = null; + Device device = null; + ResultSet resultSet = null; + try { + conn = AndroidSenseDAO.getConnection(); + String selectDBQuery = + "SELECT ANDROID_DEVICE_ID, DEVICE_NAME" + + " FROM ANDROID_SENSE_DEVICE WHERE ANDROID_DEVICE_ID = ?"; + stmt = conn.prepareStatement(selectDBQuery); + stmt.setString(1, deviceId); + resultSet = stmt.executeQuery(); + + if (resultSet.next()) { + device = new Device(); + device.setName(resultSet.getString(AndroidSenseConstants.DEVICE_PLUGIN_DEVICE_NAME)); + if (log.isDebugEnabled()) { + log.debug("Android device " + deviceId + " data has been fetched from " + + "Android database."); + } + } + } catch (SQLException e) { + String msg = "Error occurred while fetching Android device : '" + deviceId + "'"; + log.error(msg, e); + throw new AndroidSenseDeviceMgtPluginException(msg, e); + } finally { + AndroidSenseUtils.cleanupResources(stmt, resultSet); + AndroidSenseDAO.closeConnection(); + } + return device; + } + + public boolean addDevice(Device device)throws AndroidSenseDeviceMgtPluginException { + boolean status = false; + Connection conn = null; + PreparedStatement stmt = null; + try { + conn = AndroidSenseDAO.getConnection(); + String createDBQuery = + "INSERT INTO ANDROID_SENSE_DEVICE(ANDROID_DEVICE_ID, DEVICE_NAME) VALUES (?, ?)"; + + stmt = conn.prepareStatement(createDBQuery); + stmt.setString(1, device.getDeviceIdentifier()); + stmt.setString(2, device.getName()); + int rows = stmt.executeUpdate(); + if (rows > 0) { + status = true; + if (log.isDebugEnabled()) { + log.debug("Android device " + device.getDeviceIdentifier() + " data has been" + + " added to the Android database."); + } + } + } catch (SQLException e) { + String msg = "Error occurred while adding the Android device '" + device.getDeviceIdentifier() + + "' to the Android db."; + log.error(msg, e); + throw new AndroidSenseDeviceMgtPluginException(msg, e); + } finally { + AndroidSenseUtils.cleanupResources(stmt, null); + } + return status; + } + + public boolean updateDevice(Device device) throws AndroidSenseDeviceMgtPluginException { + boolean status = false; + Connection conn = null; + PreparedStatement stmt = null; + try { + conn = AndroidSenseDAO.getConnection(); + String updateDBQuery = + "UPDATE ANDROID_SENSE_DEVICE SET DEVICE_NAME = ? WHERE ANDROID_DEVICE_ID = ?"; + + stmt = conn.prepareStatement(updateDBQuery); + stmt.setString(1, device.getName()); + stmt.setString(2, device.getDeviceIdentifier()); + int rows = stmt.executeUpdate(); + if (rows > 0) { + status = true; + if (log.isDebugEnabled()) { + log.debug("Android device " + device.getDeviceIdentifier() + " data has been modified."); + } + } + } catch (SQLException e) { + String msg = "Error occurred while modifying the Android device '" + + device.getDeviceIdentifier() + "' data."; + log.error(msg, e); + throw new AndroidSenseDeviceMgtPluginException(msg, e); + } finally { + AndroidSenseUtils.cleanupResources(stmt, null); + } + return status; + } + + public boolean deleteDevice(String deviceId) throws AndroidSenseDeviceMgtPluginException { + boolean status = false; + Connection conn = null; + PreparedStatement stmt = null; + try { + conn = AndroidSenseDAO.getConnection(); + String deleteDBQuery = + "DELETE FROM ANDROID_SENSE_DEVICE WHERE ANDROID_DEVICE_ID = ?"; + stmt = conn.prepareStatement(deleteDBQuery); + stmt.setString(1, deviceId); + int rows = stmt.executeUpdate(); + if (rows > 0) { + status = true; + if (log.isDebugEnabled()) { + log.debug("Android device " + deviceId + " data has deleted from the Android database."); + } + } + } catch (SQLException e) { + String msg = "Error occurred while deleting Android device " + deviceId; + log.error(msg, e); + throw new AndroidSenseDeviceMgtPluginException(msg, e); + } finally { + AndroidSenseUtils.cleanupResources(stmt, null); + } + return status; + } + + public List getAllDevices() throws AndroidSenseDeviceMgtPluginException { + + Connection conn = null; + PreparedStatement stmt = null; + ResultSet resultSet = null; + Device device; + List iotDevices = new ArrayList<>(); + + try { + conn = AndroidSenseDAO.getConnection(); + String selectDBQuery = + "SELECT ANDROID_DEVICE_ID, DEVICE_NAME FROM ANDROID_SENSE_DEVICE"; + stmt = conn.prepareStatement(selectDBQuery); + resultSet = stmt.executeQuery(); + while (resultSet.next()) { + device = new Device(); + device.setDeviceIdentifier(resultSet.getString(AndroidSenseConstants.DEVICE_PLUGIN_DEVICE_ID)); + device.setName(resultSet.getString(AndroidSenseConstants.DEVICE_PLUGIN_DEVICE_NAME)); + iotDevices.add(device); + } + if (log.isDebugEnabled()) { + log.debug("All Android device details have fetched from Android database."); + } + return iotDevices; + } catch (SQLException e) { + String msg = "Error occurred while fetching all Android device data'"; + log.error(msg, e); + throw new AndroidSenseDeviceMgtPluginException(msg, e); + } finally { + AndroidSenseUtils.cleanupResources(stmt, resultSet); + AndroidSenseDAO.closeConnection(); + } + + } + + } \ No newline at end of file diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.plugin/src/main/java/org/wso2/carbon/device/mgt/iot/androidsense/plugin/impl/dao/util/AndroidSenseUtils.java b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.plugin/src/main/java/org/wso2/carbon/device/mgt/iot/androidsense/plugin/impl/dao/util/AndroidSenseUtils.java new file mode 100644 index 0000000000..187649aa60 --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.plugin/src/main/java/org/wso2/carbon/device/mgt/iot/androidsense/plugin/impl/dao/util/AndroidSenseUtils.java @@ -0,0 +1,109 @@ +/* + * 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. + */ + +package org.wso2.carbon.device.mgt.iot.androidsense.plugin.impl.dao.util; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.device.mgt.common.Device; +import org.wso2.carbon.device.mgt.iot.androidsense.plugin.constants.AndroidSenseConstants; +import org.wso2.carbon.device.mgt.iot.androidsense.plugin.exception.AndroidSenseDeviceMgtPluginException; + +import javax.naming.Context; +import javax.naming.InitialContext; +import javax.naming.NamingException; +import javax.sql.DataSource; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.List; + +/** + * Contains utility methods used by plugin. + */ +public class AndroidSenseUtils { + + private static Log log = LogFactory.getLog(AndroidSenseUtils.class); + + public static String getDeviceProperty(List deviceProperties, String propertyKey) { + String deviceProperty = ""; + for(Device.Property property :deviceProperties){ + if(propertyKey.equals(property.getName())){ + deviceProperty = property.getValue(); + } + } + return deviceProperty; + } + + public static Device.Property getProperty(String property, String value) { + if (property != null) { + Device.Property prop = new Device.Property(); + prop.setName(property); + prop.setValue(value); + return prop; + } + return null; + } + + public static void cleanupResources(Connection conn, PreparedStatement stmt, ResultSet rs) { + if (rs != null) { + try { + rs.close(); + } catch (SQLException e) { + log.warn("Error occurred while closing result set", e); + } + } + if (stmt != null) { + try { + stmt.close(); + } catch (SQLException e) { + log.warn("Error occurred while closing prepared statement", e); + } + } + if (conn != null) { + try { + conn.close(); + } catch (SQLException e) { + log.warn("Error occurred while closing database connection", e); + } + } + } + + public static void cleanupResources(PreparedStatement stmt, ResultSet rs) { + cleanupResources(null, stmt, rs); + } + + /** + * Creates the device management schema. + */ + public static void setupDeviceManagementSchema() throws AndroidSenseDeviceMgtPluginException { + try { + Context ctx = new InitialContext(); + DataSource dataSource = (DataSource) ctx.lookup(AndroidSenseConstants.DATA_SOURCE_NAME); + DeviceSchemaInitializer initializer = + new DeviceSchemaInitializer(dataSource); + log.info("Initializing device management repository database schema"); + initializer.createRegistryDatabase(); + + } catch (NamingException e) { + log.error("Error while looking up the data source: " + AndroidSenseConstants.DATA_SOURCE_NAME); + } catch (Exception e) { + throw new AndroidSenseDeviceMgtPluginException("Error occurred while initializing Iot Device " + + "Management database schema", e); + } + } +} diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.plugin/src/main/java/org/wso2/carbon/device/mgt/iot/androidsense/plugin/impl/dao/util/DeviceSchemaInitializer.java b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.plugin/src/main/java/org/wso2/carbon/device/mgt/iot/androidsense/plugin/impl/dao/util/DeviceSchemaInitializer.java new file mode 100644 index 0000000000..1b49350100 --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.plugin/src/main/java/org/wso2/carbon/device/mgt/iot/androidsense/plugin/impl/dao/util/DeviceSchemaInitializer.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2016, 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.wso2.carbon.device.mgt.iot.androidsense.plugin.impl.dao.util; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.utils.CarbonUtils; +import org.wso2.carbon.utils.dbcreator.DatabaseCreator; + +import javax.sql.DataSource; +import java.io.File; + +/** + * Provides methods for initializing the database script. + */ +public class DeviceSchemaInitializer extends DatabaseCreator{ + + private static final Log log = LogFactory.getLog(DeviceSchemaInitializer.class); + private static final String setupSQLScriptBaseLocation = CarbonUtils.getCarbonHome() + File.separator + "dbscripts" + + File.separator + "cdm" + File.separator + "plugins" + File.separator; + + public DeviceSchemaInitializer(DataSource dataSource) { + super(dataSource); + } + + @Override + protected String getDbScriptLocation(String databaseType) { + String scriptName = databaseType + ".sql"; + if (log.isDebugEnabled()) { + log.debug("Loading database script from :" + scriptName); + } + return setupSQLScriptBaseLocation.replaceFirst("DBTYPE", databaseType) + scriptName; + } +} diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.plugin/src/main/java/org/wso2/carbon/device/mgt/iot/androidsense/plugin/impl/feature/AndroidSenseFeatureManager.java b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.plugin/src/main/java/org/wso2/carbon/device/mgt/iot/androidsense/plugin/impl/feature/AndroidSenseFeatureManager.java new file mode 100644 index 0000000000..1fb947ae65 --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.plugin/src/main/java/org/wso2/carbon/device/mgt/iot/androidsense/plugin/impl/feature/AndroidSenseFeatureManager.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2016, 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. + */ + +package org.wso2.carbon.device.mgt.iot.androidsense.plugin.impl.feature; + +import org.wso2.carbon.device.mgt.common.DeviceManagementException; +import org.wso2.carbon.device.mgt.common.Feature; +import org.wso2.carbon.device.mgt.common.FeatureManager; +import org.wso2.carbon.device.mgt.extensions.feature.mgt.GenericFeatureManager; +import org.wso2.carbon.device.mgt.iot.androidsense.plugin.constants.AndroidSenseConstants; + +import java.util.List; + +public class AndroidSenseFeatureManager implements FeatureManager { + @Override + public boolean addFeature(Feature feature) throws DeviceManagementException { + return false; + } + + @Override + public boolean addFeatures(List features) throws DeviceManagementException { + return false; + } + + @Override + public Feature getFeature(String name) throws DeviceManagementException { + GenericFeatureManager genericFeatureManager = GenericFeatureManager.getInstance(); + return genericFeatureManager.getFeature(AndroidSenseConstants.DEVICE_TYPE, name); + } + + @Override + public List getFeatures() throws DeviceManagementException { + GenericFeatureManager genericFeatureManager = GenericFeatureManager.getInstance(); + return genericFeatureManager.getFeatures(AndroidSenseConstants.DEVICE_TYPE); + } + + @Override + public boolean removeFeature(String name) throws DeviceManagementException { + return false; + } + + @Override + public boolean addSupportedFeaturesToDB() throws DeviceManagementException { + return false; + } +} diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.plugin/src/main/java/org/wso2/carbon/device/mgt/iot/androidsense/plugin/internal/AndroidSenseManagementServiceComponent.java b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.plugin/src/main/java/org/wso2/carbon/device/mgt/iot/androidsense/plugin/internal/AndroidSenseManagementServiceComponent.java new file mode 100644 index 0000000000..4896302ea5 --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.plugin/src/main/java/org/wso2/carbon/device/mgt/iot/androidsense/plugin/internal/AndroidSenseManagementServiceComponent.java @@ -0,0 +1,84 @@ +/* + * 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. + */ + +package org.wso2.carbon.device.mgt.iot.androidsense.plugin.internal; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.osgi.framework.BundleContext; +import org.osgi.framework.ServiceRegistration; +import org.osgi.service.component.ComponentContext; +import org.wso2.carbon.device.mgt.common.spi.DeviceManagementService; +import org.wso2.carbon.device.mgt.iot.androidsense.plugin.exception.AndroidSenseDeviceMgtPluginException; +import org.wso2.carbon.device.mgt.iot.androidsense.plugin.impl.AndroidSenseManagerService; +import org.wso2.carbon.device.mgt.iot.androidsense.plugin.impl.dao.util.AndroidSenseUtils; + +/** + * @scr.component name="org.wso2.carbon.device.mgt.iot.android.internal.AndroidSenseManagementServiceComponent" + * immediate="true" + */ +public class AndroidSenseManagementServiceComponent { + + + private ServiceRegistration androidServiceRegRef; + private static final Log log = LogFactory.getLog(AndroidSenseManagementServiceComponent.class); + protected void activate(ComponentContext ctx) { + if (log.isDebugEnabled()) { + log.debug("Activating Android Device Management Service Component"); + } + try { + BundleContext bundleContext = ctx.getBundleContext(); + androidServiceRegRef = + bundleContext.registerService(DeviceManagementService.class.getName(), new AndroidSenseManagerService(), null); + String setupOption = System.getProperty("setup"); + if (setupOption != null) { + if (log.isDebugEnabled()) { + log.debug( + "-Dsetup is enabled. Iot Device management repository schema initialization is about " + + "to begin"); + } + try { + AndroidSenseUtils.setupDeviceManagementSchema(); + } catch (AndroidSenseDeviceMgtPluginException e) { + log.error("Exception occurred while initializing device management database schema", e); + } + } + if (log.isDebugEnabled()) { + log.debug("Android Device Management Service Component has been successfully activated"); + } + } catch (Throwable e) { + log.error("Error occurred while activating Android Device Management Service Component", e); + } + } + + protected void deactivate(ComponentContext ctx) { + if (log.isDebugEnabled()) { + log.debug("De-activating Android Device Management Service Component"); + } + try { + if (androidServiceRegRef != null) { + androidServiceRegRef.unregister(); + } + + if (log.isDebugEnabled()) { + log.debug( + "Android Device Management Service Component has been successfully de-activated"); + } + } catch (Throwable e) { + log.error("Error occurred while de-activating Android Device Management bundle", e); + } + } +} diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.ui/pom.xml b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.ui/pom.xml new file mode 100644 index 0000000000..032743a6c2 --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.ui/pom.xml @@ -0,0 +1,61 @@ + + + + + + + androidsense-plugin + org.wso2.carbon.devicemgt-plugins + 2.1.0-SNAPSHOT + ../pom.xml + + + 4.0.0 + org.wso2.carbon.device.mgt.iot.androidsense.ui + WSO2 Carbon - IoT Server Android Sense UI + pom + + + + + maven-assembly-plugin + 2.5.5 + + ${project.artifactId}-${carbon.device.mgt.version} + false + + src/assembly/src.xml + + + + + create-archive + package + + single + + + + + + + + \ No newline at end of file diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.ui/src/assembly/src.xml b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.ui/src/assembly/src.xml new file mode 100644 index 0000000000..2797034e07 --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.ui/src/assembly/src.xml @@ -0,0 +1,36 @@ + + + + src + + zip + + false + ${basedir}/src + + + ${basedir}/src/main/resources/jaggeryapps/devicemgt + / + true + + + \ No newline at end of file diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.android_sense.device-view/device-view.hbs b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.android_sense.device-view/device-view.hbs new file mode 100644 index 0000000000..f5fb9d840d --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.android_sense.device-view/device-view.hbs @@ -0,0 +1,84 @@ +{{#zone "topCss"}} + +{{/zone}} +{{#zone "device-thumbnail"}} + +{{/zone}} + +{{#zone "device-opetations"}} +
+ Operations +
+
+ {{unit "iot.unit.device.operation-bar" device=device}} +
+{{/zone}} + +{{#zone "device-detail-properties"}} +
+ +
+
+ +
+
Device Statistics
+ {{unit "iot.unit.device.stats" device=device}} +
+
+
Device Location
+
+
+
+
+
+
+
+
Operations Log
+
+ +
+
+ Not available yet +
+
+
+
+
+
+
+
+{{/zone}} + +{{#zone "bottomJs"}} + +{{/zone}} \ No newline at end of file diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.android_sense.device-view/device-view.js b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.android_sense.device-view/device-view.js new file mode 100644 index 0000000000..21035b3d8d --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.android_sense.device-view/device-view.js @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2016, 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("device-view.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") { + return {"device": device}; + } else { + response.sendError(404, "Device Id " + deviceId + "of type " + deviceType + " cannot be found!"); + exit(); + } + } +} \ No newline at end of file diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.android_sense.device-view/device-view.json b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.android_sense.device-view/device-view.json new file mode 100644 index 0000000000..9eecd8f5bf --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.android_sense.device-view/device-view.json @@ -0,0 +1,3 @@ +{ + "version": "1.0.0" +} \ No newline at end of file diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.android_sense.device-view/public/images/android-sense-icon.png b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.android_sense.device-view/public/images/android-sense-icon.png new file mode 100644 index 0000000000..4baf188bb1 Binary files /dev/null and b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.android_sense.device-view/public/images/android-sense-icon.png differ diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.android_sense.device-view/public/images/thumb.png b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.android_sense.device-view/public/images/thumb.png new file mode 100644 index 0000000000..1524c56fe7 Binary files /dev/null and b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.android_sense.device-view/public/images/thumb.png differ diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.android_sense.type-view/public/images/android-sense-icon.png b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.android_sense.type-view/public/images/android-sense-icon.png new file mode 100644 index 0000000000..4baf188bb1 Binary files /dev/null and b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.android_sense.type-view/public/images/android-sense-icon.png differ diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.android_sense.type-view/public/images/myDevices_analytics.png b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.android_sense.type-view/public/images/myDevices_analytics.png new file mode 100644 index 0000000000..292097a451 Binary files /dev/null and b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.android_sense.type-view/public/images/myDevices_analytics.png differ diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.android_sense.type-view/public/images/publishDataView.png b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.android_sense.type-view/public/images/publishDataView.png new file mode 100644 index 0000000000..993610c4db Binary files /dev/null and b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.android_sense.type-view/public/images/publishDataView.png differ diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.android_sense.type-view/public/images/registerView.png b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.android_sense.type-view/public/images/registerView.png new file mode 100644 index 0000000000..385713e13d Binary files /dev/null and b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.android_sense.type-view/public/images/registerView.png differ diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.android_sense.type-view/public/images/selectSensorView.png b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.android_sense.type-view/public/images/selectSensorView.png new file mode 100644 index 0000000000..94af4a48a0 Binary files /dev/null and b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.android_sense.type-view/public/images/selectSensorView.png differ diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.android_sense.type-view/public/images/thumb.png b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.android_sense.type-view/public/images/thumb.png new file mode 100644 index 0000000000..1524c56fe7 Binary files /dev/null and b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.android_sense.type-view/public/images/thumb.png differ diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.android_sense.type-view/public/js/download.js b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.android_sense.type-view/public/js/download.js new file mode 100644 index 0000000000..f963c0e0c5 --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.android_sense.type-view/public/js/download.js @@ -0,0 +1,180 @@ +/* + * Copyright (c) 2016, 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. + */ + +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 == 'virtual_firealarm') { + $('.sketchType').remove(); + $('input[name="sketchType"][value="virtual_firealarm"]').prop('checked', true); + $("label[for='virtual_firealarm']").text("Simple Agent"); + $("label[for='virtual_firealarm_advanced']").text("Advanced 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(); + }); + + }); +} + +//Device owner removed. +function downloadAgent() { + $('#downloadForm').submit(); + hidePopup(); + $(modalPopupContent).html($('#device-agent-downloading-content').html()); + showPopup(); + 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/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.android_sense.type-view/public/js/jquery.validate.js b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.android_sense.type-view/public/js/jquery.validate.js new file mode 100644 index 0000000000..fe7ecf07a6 --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.android_sense.type-view/public/js/jquery.validate.js @@ -0,0 +1,1227 @@ +/* + * Copyright (c) 2016, 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($) { + +$.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/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.android_sense.type-view/type-view.hbs b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.android_sense.type-view/type-view.hbs new file mode 100644 index 0000000000..6548564faa --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.android_sense.type-view/type-view.hbs @@ -0,0 +1,337 @@ +
+

Android Sense

+
+
+ +
+ +
+ +
+ +
+ +
+

What it Does

+
+

Connect an Android device to WSO2 IoT Server and visualize sensor + data.

+
+

What You Need

+
+

You should have an Android Device to get started.

+
    +
  • + STEP 01 +    Go ahead and click [Enroll Device]. +
  • +
  • + STEP 02 +    You can either scan the QR code or directly download Android agent. +
  • +
  • + STEP 03 +    Install Android agent into your Android Device. +
  • +
  • STEP 04 +    Proceed to [Prepare] section. +
+
+ + Enroll Device +

+ + + +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+

+
+ +
+

Prepare

+
+
    +
  • + 01 +    Connect Android device to network. +
  • +
  • + 02 +    Start Android Sense app in your device. +
  • +
  • + 03 +    Fill login form with the credentials. + (Use server URL as [ https://<WSO2_IoT_SERVER_HOST>:< + HTTPS_SERVER_PORT> ] and click on + Register Device button.) +
  • +
  • + + + +
  • + +
  • + 04 +    Once the device is enrolled, click on [+] button to + select the sensors. + + + +
  • +
  • + 05 +    Click on [Publish data] button to publish sensor readings to + IoT server. + + + +
  • +
+
+
+ +
+

Try Out

+
+
    +
  • + + + + +    You can view all your connected devices + [Device Management] page. +
  • +
  • + + + + +    You can select any one of your connected devices for available + operations and Real-Time data monitoring. +
  • +
  • + + + +    For historical analytics of sensor data navigate to device + analytics page. +
  • +
+
+

Click on the image to zoom

+
+ + + +
+
+ + + + + +{{#zone "bottomJs"}} + {{js "/js/download.js"}} + + {{js "/js/jquery.validate.js"}} +{{/zone}} diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.android_sense.type-view/type-view.js b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.android_sense.type-view/type-view.js new file mode 100644 index 0000000000..6f277602a8 --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.android_sense.type-view/type-view.js @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2016, 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 viewModel = {}; + var process = require("process"); + var serverIP = process.getProperty("carbon.local.ip"); + var serverPort = process.getProperty("carbon.http.port"); + viewModel.enrollmentURL = "http://"+serverIP+":"+serverPort+"/android_sense_mgt/manager/device/android_sense/download/"; + return viewModel; +} \ No newline at end of file diff --git a/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.android_sense.type-view/type-view.json b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.android_sense.type-view/type-view.json new file mode 100644 index 0000000000..9eecd8f5bf --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/org.wso2.carbon.device.mgt.iot.androidsense.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.android_sense.type-view/type-view.json @@ -0,0 +1,3 @@ +{ + "version": "1.0.0" +} \ No newline at end of file diff --git a/components/iot-plugins/androidsense-plugin/pom.xml b/components/iot-plugins/androidsense-plugin/pom.xml new file mode 100644 index 0000000000..59a343d4c6 --- /dev/null +++ b/components/iot-plugins/androidsense-plugin/pom.xml @@ -0,0 +1,62 @@ + + + + + + + org.wso2.carbon.devicemgt-plugins + iot-plugins + 2.1.0-SNAPSHOT + ../pom.xml + + + 4.0.0 + androidsense-plugin + pom + WSO2 Carbon - Arduino Plugin + http://wso2.org + + + org.wso2.carbon.device.mgt.iot.androidsense.analytics + org.wso2.carbon.device.mgt.iot.androidsense.controller.api + org.wso2.carbon.device.mgt.iot.androidsense.manager.api + org.wso2.carbon.device.mgt.iot.androidsense.plugin + org.wso2.carbon.device.mgt.iot.androidsense.ui + + + + + + + org.apache.felix + maven-scr-plugin + 1.7.2 + + + generate-scr-scrdescriptor + + scr + + + + + + + + diff --git a/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.analytics/build.xml b/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.analytics/build.xml new file mode 100644 index 0000000000..a877b11413 --- /dev/null +++ b/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.analytics/build.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.analytics/pom.xml b/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.analytics/pom.xml new file mode 100644 index 0000000000..5591c22e55 --- /dev/null +++ b/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.analytics/pom.xml @@ -0,0 +1,91 @@ + + + + + + + arduino-plugin + org.wso2.carbon.devicemgt-plugins + 2.1.0-SNAPSHOT + ../pom.xml + + + 4.0.0 + org.wso2.carbon.device.mgt.iot.arduino.analytics + WSO2 Carbon - IoT Server Arduino Analytics capp + pom + + + + + maven-clean-plugin + 2.4.1 + + + auto-clean + initialize + + clean + + + + + + maven-antrun-plugin + 1.7 + + + process-resources + + + + + + + run + + + + + + maven-assembly-plugin + 2.5.5 + + ${project.artifactId}-${carbon.device.mgt.version} + false + + src/assembly/src.xml + + + + + create-archive + package + + single + + + + + + + + \ No newline at end of file diff --git a/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.analytics/src/assembly/src.xml b/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.analytics/src/assembly/src.xml new file mode 100644 index 0000000000..a5a375010e --- /dev/null +++ b/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.analytics/src/assembly/src.xml @@ -0,0 +1,36 @@ + + + + src + + zip + + false + ${basedir}/src + + + ${basedir}/target/carbonapps + / + true + + + \ No newline at end of file diff --git a/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.analytics/src/main/resources/carbonapps/Temperature_Sensor/Eventreceiver_temperature_1.0.0/EventReceiver_temperature.xml b/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.analytics/src/main/resources/carbonapps/Temperature_Sensor/Eventreceiver_temperature_1.0.0/EventReceiver_temperature.xml new file mode 100644 index 0000000000..28b710c27f --- /dev/null +++ b/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.analytics/src/main/resources/carbonapps/Temperature_Sensor/Eventreceiver_temperature_1.0.0/EventReceiver_temperature.xml @@ -0,0 +1,26 @@ + + + + + + false + + + + diff --git a/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.analytics/src/main/resources/carbonapps/Temperature_Sensor/Eventreceiver_temperature_1.0.0/artifact.xml b/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.analytics/src/main/resources/carbonapps/Temperature_Sensor/Eventreceiver_temperature_1.0.0/artifact.xml new file mode 100644 index 0000000000..25df56734b --- /dev/null +++ b/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.analytics/src/main/resources/carbonapps/Temperature_Sensor/Eventreceiver_temperature_1.0.0/artifact.xml @@ -0,0 +1,22 @@ + + + + + EventReceiver_temperature.xml + diff --git a/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.analytics/src/main/resources/carbonapps/Temperature_Sensor/Eventstore_temperature_1.0.0/artifact.xml b/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.analytics/src/main/resources/carbonapps/Temperature_Sensor/Eventstore_temperature_1.0.0/artifact.xml new file mode 100644 index 0000000000..ccfb3b3140 --- /dev/null +++ b/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.analytics/src/main/resources/carbonapps/Temperature_Sensor/Eventstore_temperature_1.0.0/artifact.xml @@ -0,0 +1,22 @@ + + + + + org_wso2_iot_devices_temperature.xml + diff --git a/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.analytics/src/main/resources/carbonapps/Temperature_Sensor/Eventstore_temperature_1.0.0/org_wso2_iot_devices_temperature.xml b/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.analytics/src/main/resources/carbonapps/Temperature_Sensor/Eventstore_temperature_1.0.0/org_wso2_iot_devices_temperature.xml new file mode 100644 index 0000000000..d06f73b14e --- /dev/null +++ b/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.analytics/src/main/resources/carbonapps/Temperature_Sensor/Eventstore_temperature_1.0.0/org_wso2_iot_devices_temperature.xml @@ -0,0 +1,62 @@ + + + + + + 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/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.analytics/src/main/resources/carbonapps/Temperature_Sensor/Eventstream_temperature_1.0.0/artifact.xml b/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.analytics/src/main/resources/carbonapps/Temperature_Sensor/Eventstream_temperature_1.0.0/artifact.xml new file mode 100644 index 0000000000..27ec69702e --- /dev/null +++ b/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.analytics/src/main/resources/carbonapps/Temperature_Sensor/Eventstream_temperature_1.0.0/artifact.xml @@ -0,0 +1,23 @@ + + + + + org.wso2.iot.devices.temperature_1.0.0.json + + diff --git a/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.analytics/src/main/resources/carbonapps/Temperature_Sensor/Eventstream_temperature_1.0.0/org.wso2.iot.devices.temperature_1.0.0.json b/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.analytics/src/main/resources/carbonapps/Temperature_Sensor/Eventstream_temperature_1.0.0/org.wso2.iot.devices.temperature_1.0.0.json new file mode 100644 index 0000000000..5d94b9821b --- /dev/null +++ b/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.analytics/src/main/resources/carbonapps/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/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.analytics/src/main/resources/carbonapps/Temperature_Sensor/Sparkscripts_1.0.0/Temperature_Sensor_Script.xml b/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.analytics/src/main/resources/carbonapps/Temperature_Sensor/Sparkscripts_1.0.0/Temperature_Sensor_Script.xml new file mode 100644 index 0000000000..41938dd4ff --- /dev/null +++ b/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.analytics/src/main/resources/carbonapps/Temperature_Sensor/Sparkscripts_1.0.0/Temperature_Sensor_Script.xml @@ -0,0 +1,31 @@ + + + + + IoTServer_Sensor_Script + + 0 * * * * ? + diff --git a/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.analytics/src/main/resources/carbonapps/Temperature_Sensor/Sparkscripts_1.0.0/artifact.xml b/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.analytics/src/main/resources/carbonapps/Temperature_Sensor/Sparkscripts_1.0.0/artifact.xml new file mode 100644 index 0000000000..9b4228e30c --- /dev/null +++ b/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.analytics/src/main/resources/carbonapps/Temperature_Sensor/Sparkscripts_1.0.0/artifact.xml @@ -0,0 +1,22 @@ + + + + + Temperature_Sensor_Script.xml + diff --git a/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.analytics/src/main/resources/carbonapps/Temperature_Sensor/artifacts.xml b/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.analytics/src/main/resources/carbonapps/Temperature_Sensor/artifacts.xml new file mode 100644 index 0000000000..c4580f909d --- /dev/null +++ b/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.analytics/src/main/resources/carbonapps/Temperature_Sensor/artifacts.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + diff --git a/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.controller.service.impl/pom.xml b/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.controller.service.impl/pom.xml new file mode 100644 index 0000000000..9ef464d1a2 --- /dev/null +++ b/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.controller.service.impl/pom.xml @@ -0,0 +1,170 @@ + + + + + + arduino-plugin + org.wso2.carbon.devicemgt-plugins + 2.1.0-SNAPSHOT + ../pom.xml + + + + 4.0.0 + org.wso2.carbon.device.mgt.iot.arduino.controller.service.impl + war + WSO2 Carbon - IoT Server Arduino Controller API + WSO2 Carbon - Arduino Service Controller API Implementation + http://wso2.org + + + + + org.wso2.carbon + org.wso2.carbon.utils + provided + + + + + org.wso2.carbon.devicemgt + org.wso2.carbon.device.mgt.common + provided + + + org.wso2.carbon.devicemgt + org.wso2.carbon.device.mgt.core + provided + + + org.apache.axis2.wso2 + axis2-client + + + + + + org.wso2.carbon.devicemgt + org.wso2.carbon.device.mgt.analytics + provided + + + org.apache.axis2.wso2 + axis2-client + + + + + + + + org.apache.cxf + cxf-rt-frontend-jaxws + provided + + + org.apache.cxf + cxf-rt-frontend-jaxrs + provided + + + org.apache.cxf + cxf-rt-transports-http + provided + + + + + org.eclipse.paho + org.eclipse.paho.client.mqttv3 + provided + + + + + org.apache.httpcomponents + httpasyncclient + 4.1 + provided + + + org.wso2.carbon.devicemgt-plugins + org.wso2.carbon.device.mgt.iot + provided + + + org.wso2.carbon.devicemgt-plugins + org.wso2.carbon.device.mgt.iot.arduino.plugin.impl + provided + + + + + org.codehaus.jackson + jackson-core-asl + + + org.codehaus.jackson + jackson-jaxrs + + + javax + javaee-web-api + provided + + + javax.ws.rs + jsr311-api + provided + + + org.wso2.carbon.devicemgt + org.wso2.carbon.apimgt.annotations + provided + + + org.wso2.carbon.devicemgt + org.wso2.carbon.device.mgt.extensions + provided + + + + + + + + maven-compiler-plugin + + UTF-8 + ${wso2.maven.compiler.source} + ${wso2.maven.compiler.target} + + + + maven-war-plugin + + arduino + + + + + + \ No newline at end of file diff --git a/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.controller.service.impl/src/main/java/org/wso2/carbon/device/mgt/iot/arduino/controller/service/impl/ArduinoControllerService.java b/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.controller.service.impl/src/main/java/org/wso2/carbon/device/mgt/iot/arduino/controller/service/impl/ArduinoControllerService.java new file mode 100644 index 0000000000..7c5be93c05 --- /dev/null +++ b/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.controller.service.impl/src/main/java/org/wso2/carbon/device/mgt/iot/arduino/controller/service/impl/ArduinoControllerService.java @@ -0,0 +1,300 @@ +/* + * Copyright (c) 2016, 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.wso2.carbon.device.mgt.iot.arduino.controller.service.impl; + +import org.apache.commons.httpclient.HttpStatus; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.apimgt.annotations.api.API; +import org.wso2.carbon.context.PrivilegedCarbonContext; +import org.wso2.carbon.device.mgt.analytics.common.AnalyticsDataRecord; +import org.wso2.carbon.device.mgt.analytics.exception.DeviceManagementAnalyticsException; +import org.wso2.carbon.device.mgt.analytics.service.DeviceAnalyticsService; +import org.wso2.carbon.device.mgt.extensions.feature.mgt.annotations.DeviceType; +import org.wso2.carbon.device.mgt.extensions.feature.mgt.annotations.Feature; +import org.wso2.carbon.device.mgt.iot.arduino.controller.service.impl.dto.DeviceData; +import org.wso2.carbon.device.mgt.iot.arduino.controller.service.impl.dto.SensorData; +import org.wso2.carbon.device.mgt.iot.arduino.controller.service.impl.transport.ArduinoMQTTConnector; +import org.wso2.carbon.device.mgt.iot.arduino.controller.service.impl.util.ArduinoServiceUtils; +import org.wso2.carbon.device.mgt.iot.arduino.plugin.constants.ArduinoConstants; +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.service.IoTServerStartupListener; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.ws.rs.Consumes; +import javax.ws.rs.FormParam; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +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.MediaType; +import javax.ws.rs.core.Response; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.concurrent.ConcurrentHashMap; + + +@API( name="arduino", version="1.0.0", context="/arduino", tags = {"arduino"}) +@DeviceType( value = "arduino") +public class ArduinoControllerService { + + private static Log log = LogFactory.getLog(ArduinoControllerService.class); + private static Map> replyMsgQueue = new HashMap<>(); + private static Map> internalControlsQueue = new HashMap<>(); + @Context //injected response proxy supporting multiple thread + private HttpServletResponse response; + private ArduinoMQTTConnector arduinoMQTTConnector; + private ConcurrentHashMap deviceToIpMap = new ConcurrentHashMap<>(); + + /** + * @return the queue containing all the MQTT reply messages from all Arduinos communicating to this service + */ + public static Map> getReplyMsgQueue() { + return replyMsgQueue; + } + + /** + * @return the queue containing all the MQTT controls received to be sent to any Arduinos connected to this server + */ + public static Map> getInternalControlsQueue() { + return internalControlsQueue; + } + + private boolean waitForServerStartup() { + while (!IoTServerStartupListener.isServerReady()) { + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + return true; + } + } + return false; + } + + /** + * @return the "ArduinoMQTTConnector" object of this ArduinoControllerService instance + */ + @SuppressWarnings("unused") + public ArduinoMQTTConnector getArduinoMQTTConnector() { + return arduinoMQTTConnector; + } + + /** + * @param arduinoMQTTConnector an object of type "ArduinoMQTTConnector" specific for this ArduinoControllerService + */ + @SuppressWarnings("unused") + public void setArduinoMQTTConnector(final ArduinoMQTTConnector arduinoMQTTConnector) { + Runnable connector = new Runnable() { + public void run() { + if (waitForServerStartup()) { + return; + } + ArduinoControllerService.this.arduinoMQTTConnector = arduinoMQTTConnector; + if (MqttConfig.getInstance().isEnabled()) { + arduinoMQTTConnector.connect(); + } else { + log.warn("MQTT disabled in 'devicemgt-config.xml'. Hence, ArduinoMQTTConnector not started."); + } + } + }; + Thread connectorThread = new Thread(connector); + connectorThread.setDaemon(true); + connectorThread.start(); + } + + @Path("controller/register/device/{deviceId}/{ip}/{port}") + @POST + public String registerDeviceIP(@PathParam("deviceId") String deviceId, @PathParam("ip") String deviceIP, + @PathParam("port") String devicePort, @Context HttpServletResponse response, + @Context HttpServletRequest request) { + String result; + if (log.isDebugEnabled()) { + log.debug("Got register call from IP: " + deviceIP + " for Device ID: " + deviceId + " of owner: "); + } + String deviceHttpEndpoint = deviceIP + ":" + devicePort; + deviceToIpMap.put(deviceId, deviceHttpEndpoint); + result = "Device-IP Registered"; + response.setStatus(Response.Status.OK.getStatusCode()); + if (log.isDebugEnabled()) { + log.debug(result); + } + return result; + } + + @Path("controller/device/{deviceId}/bulb") + @POST + @Feature(code = "bulb", name = "Control Bulb", type = "operation", description = "Control Bulb on Arduino Uno") + public void switchBulb(@PathParam("deviceId") String deviceId, @QueryParam("protocol") String protocol, + @FormParam("state") String state, @Context HttpServletResponse response) { + + LinkedList deviceControlList = internalControlsQueue.get(deviceId); + String operation = "BULB:" + state.toUpperCase(); + log.info(operation); + if (deviceControlList == null) { + deviceControlList = new LinkedList<>(); + deviceControlList.add(operation); + internalControlsQueue.put(deviceId, deviceControlList); + } else { + deviceControlList.add(operation); + } + response.setStatus(Response.Status.OK.getStatusCode()); + } + + @Path("controller/device/{deviceId}/temperature") + @GET + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + @Feature( code="temperature", name="Temperature", type="monitor", description="Request temperature reading from Arduino agent") + public SensorRecord requestTemperature(@PathParam("deviceId") String deviceId, @QueryParam("protocol") String protocol, + @Context HttpServletResponse response) { + SensorRecord sensorRecord = null; + try { + sensorRecord = SensorDataManager.getInstance().getSensorRecord(deviceId, + ArduinoConstants.SENSOR_TEMPERATURE); + } catch (DeviceControllerException e) { + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + } + response.setStatus(Response.Status.OK.getStatusCode()); + return sensorRecord; + } + + @Path("controller/sensor") + @POST + @Consumes(MediaType.APPLICATION_JSON) + public void pushData(DeviceData dataMsg, @Context HttpServletResponse response) { + String owner = PrivilegedCarbonContext.getThreadLocalCarbonContext().getUsername(); + String deviceId = dataMsg.deviceId; + float pinData = dataMsg.value; + SensorDataManager.getInstance().setSensorRecord(deviceId, ArduinoConstants.SENSOR_TEMPERATURE, + String.valueOf(pinData), + Calendar.getInstance().getTimeInMillis()); + if (!ArduinoServiceUtils.publishToDAS(dataMsg.deviceId, dataMsg.value)) { + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + log.warn("An error occured whilst trying to publish pin data of Arduino with ID [" + + deviceId + "] of owner [" + owner + "]"); + } + } + + @Path("controller/device/{deviceId}/controls") + @GET + public String readControls(@PathParam("deviceId") String deviceId, @QueryParam("protocol") String protocol, + @Context HttpServletResponse response) { + String result; + LinkedList deviceControlList = internalControlsQueue.get(deviceId); + String owner = PrivilegedCarbonContext.getThreadLocalCarbonContext().getUsername(); + if (deviceControlList == null) { + result = "No controls have been set for device " + deviceId + " of owner " + owner; + response.setStatus(HttpStatus.SC_NO_CONTENT); + } else { + try { + result = deviceControlList.remove(); + response.setStatus(HttpStatus.SC_ACCEPTED); + } catch (NoSuchElementException ex) { + result = "There are no more controls for device " + deviceId + " of owner " + owner; + response.setStatus(HttpStatus.SC_NO_CONTENT); + } + } + if (log.isDebugEnabled()) { + log.debug(result); + } + return result; + } + + @Path("controller/temperature") + @POST + @Consumes(MediaType.APPLICATION_JSON) + public void pushTemperatureData(final DeviceData dataMsg, @Context HttpServletResponse response, + @Context HttpServletRequest request) { + String owner = PrivilegedCarbonContext.getThreadLocalCarbonContext().getUsername(); + String deviceId = dataMsg.deviceId; + float temperature = dataMsg.value; + SensorDataManager.getInstance().setSensorRecord(deviceId, ArduinoConstants.SENSOR_TEMPERATURE, + String.valueOf(temperature), + Calendar.getInstance().getTimeInMillis()); + if (!ArduinoServiceUtils.publishToDAS(dataMsg.deviceId, dataMsg.value)) { + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + log.warn("An error occured whilst trying to publish temperature data of Arduino with ID [" + deviceId + + "] of owner [" + owner + "]"); + } + } + + /** + * Retreive Sensor data for the device type + */ + @Path("controller/stats/device/{deviceId}/sensors/temperature") + @GET + @Consumes("application/json") + @Produces("application/json") + public SensorData[] getArduinoTemperatureStats(@PathParam("deviceId") String deviceId, + @QueryParam("from") long from, + @QueryParam("to") long to) { + String fromDate = String.valueOf(from); + String toDate = String.valueOf(to); + List sensorDatas = new ArrayList<>(); + PrivilegedCarbonContext ctx = PrivilegedCarbonContext.getThreadLocalCarbonContext(); + DeviceAnalyticsService deviceAnalyticsService = (DeviceAnalyticsService) ctx + .getOSGiService(DeviceAnalyticsService.class, null); + String query = "deviceId:" + deviceId + " AND deviceType:" + + ArduinoConstants.DEVICE_TYPE + " AND time : [" + fromDate + " TO " + toDate + "]"; + String sensorTableName = ArduinoConstants.TEMPERATURE_EVENT_TABLE; + try { + List records = deviceAnalyticsService.getAllEventsForDevice(sensorTableName, query); + Collections.sort(records, new Comparator() { + @Override + public int compare(AnalyticsDataRecord o1, AnalyticsDataRecord o2) { + long t1 = (Long) o1.getValue("time"); + long t2 = (Long) o2.getValue("time"); + if (t1 < t2) { + return -1; + } else if (t1 > t2) { + return 1; + } else { + return 0; + } + } + }); + for (AnalyticsDataRecord record : records) { + SensorData sensorData = new SensorData(); + sensorData.setTime((long) record.getValue("time")); + sensorData.setValue("" + (float) record.getValue(ArduinoConstants.SENSOR_TEMPERATURE)); + sensorDatas.add(sensorData); + } + return sensorDatas.toArray(new SensorData[sensorDatas.size()]); + } catch (DeviceManagementAnalyticsException e) { + String errorMsg = "Error on retrieving stats on table " + sensorTableName + " with query " + query; + log.error(errorMsg); + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + return sensorDatas.toArray(new SensorData[sensorDatas.size()]); + } + } + +} diff --git a/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.controller.service.impl/src/main/java/org/wso2/carbon/device/mgt/iot/arduino/controller/service/impl/dto/DeviceData.java b/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.controller.service.impl/src/main/java/org/wso2/carbon/device/mgt/iot/arduino/controller/service/impl/dto/DeviceData.java new file mode 100644 index 0000000000..00de218bd3 --- /dev/null +++ b/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.controller.service.impl/src/main/java/org/wso2/carbon/device/mgt/iot/arduino/controller/service/impl/dto/DeviceData.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.wso2.carbon.device.mgt.iot.arduino.controller.service.impl.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 DeviceData { + @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/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.controller.service.impl/src/main/java/org/wso2/carbon/device/mgt/iot/arduino/controller/service/impl/dto/SensorData.java b/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.controller.service.impl/src/main/java/org/wso2/carbon/device/mgt/iot/arduino/controller/service/impl/dto/SensorData.java new file mode 100644 index 0000000000..22a58f0f86 --- /dev/null +++ b/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.controller.service.impl/src/main/java/org/wso2/carbon/device/mgt/iot/arduino/controller/service/impl/dto/SensorData.java @@ -0,0 +1,44 @@ +package org.wso2.carbon.device.mgt.iot.arduino.controller.service.impl.dto; + +import org.codehaus.jackson.annotate.JsonIgnoreProperties; + +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; + +@XmlRootElement +/** + * This stores sensor event data for the device type. + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class SensorData { + + @XmlElement public Long time; + @XmlElement public String key; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } + + public Long getTime() { + return time; + } + + public void setTime(Long time) { + this.time = time; + } + + @XmlElement public String value; + +} diff --git a/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.controller.service.impl/src/main/java/org/wso2/carbon/device/mgt/iot/arduino/controller/service/impl/exception/ArduinoException.java b/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.controller.service.impl/src/main/java/org/wso2/carbon/device/mgt/iot/arduino/controller/service/impl/exception/ArduinoException.java new file mode 100644 index 0000000000..82766a0dbc --- /dev/null +++ b/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.controller.service.impl/src/main/java/org/wso2/carbon/device/mgt/iot/arduino/controller/service/impl/exception/ArduinoException.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.wso2.carbon.device.mgt.iot.arduino.controller.service.impl.exception; + +public class ArduinoException extends Exception { + private static final long serialVersionUID = 118512086957330189L; + + public ArduinoException(String errorMessage) { + super(errorMessage); + } + + public ArduinoException(String errorMessage, Throwable throwable) { + super(errorMessage, throwable); + } +} diff --git a/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.controller.service.impl/src/main/java/org/wso2/carbon/device/mgt/iot/arduino/controller/service/impl/transport/ArduinoMQTTConnector.java b/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.controller.service.impl/src/main/java/org/wso2/carbon/device/mgt/iot/arduino/controller/service/impl/transport/ArduinoMQTTConnector.java new file mode 100644 index 0000000000..246fb79035 --- /dev/null +++ b/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.controller.service.impl/src/main/java/org/wso2/carbon/device/mgt/iot/arduino/controller/service/impl/transport/ArduinoMQTTConnector.java @@ -0,0 +1,193 @@ +/* + * 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.wso2.carbon.device.mgt.iot.arduino.controller.service.impl.transport; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.eclipse.paho.client.mqttv3.MqttException; +import org.eclipse.paho.client.mqttv3.MqttMessage; +import org.wso2.carbon.device.mgt.iot.arduino.plugin.constants.ArduinoConstants; +import org.wso2.carbon.device.mgt.iot.arduino.controller.service.impl.ArduinoControllerService; +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.transport.TransportHandlerException; +import org.wso2.carbon.device.mgt.iot.transport.mqtt.MQTTTransportHandler; + +import java.io.File; +import java.util.LinkedList; +import java.util.UUID; + +public class ArduinoMQTTConnector extends MQTTTransportHandler { + private static Log log = LogFactory.getLog(ArduinoMQTTConnector.class); + private static final String subscribeTopic = "wso2/" + ArduinoConstants.DEVICE_TYPE + "/#"; + private static final String iotServerSubscriber = UUID.randomUUID().toString().substring(0, 5); + + private static final String MESSAGE_TO_SEND = "IN"; + private static final String MESSAGE_RECEIVED = "OUT"; + + private ArduinoMQTTConnector() { + super(iotServerSubscriber, ArduinoConstants.DEVICE_TYPE, + MqttConfig.getInstance().getMqttQueueEndpoint(), subscribeTopic); + } + + @Override + public void connect() { + Runnable connector = new Runnable() { + public void run() { + while (!isConnected()) { + try { + String brokerUsername = MqttConfig.getInstance().getMqttQueueUsername(); + String brokerPassword = MqttConfig.getInstance().getMqttQueuePassword(); + setUsernameAndPassword(brokerUsername, brokerPassword); + connectToQueue(); + } catch (TransportHandlerException e) { + log.error("Connection to MQTT Broker at: " + mqttBrokerEndPoint + " failed", e); + try { + Thread.sleep(timeoutInterval); + } catch (InterruptedException ex) { + log.error("MQTT-Connector: Thread Sleep Interrupt Exception.", ex); + } + } + + try { + subscribeToQueue(); + } catch (TransportHandlerException e) { + log.warn("Subscription to MQTT Broker at: " + mqttBrokerEndPoint + " failed", e); + } + } + } + }; + + + Thread connectorThread = new Thread(connector); + connectorThread.setDaemon(true); + connectorThread.start(); + } + + @Override + public void processIncomingMessage(MqttMessage message, String... messageParams) throws TransportHandlerException { + if(messageParams.length != 0) { + // owner and the deviceId are extracted from the MQTT topic to which the message was received. + // = [ServerName/Owner/DeviceType/DeviceId] + String topic = messageParams[0]; + String[] topicParams = topic.split("/"); + String deviceId = topicParams[3]; + + if (log.isDebugEnabled()) { + log.debug("Received MQTT message for: [DEVICE.ID-" + deviceId + "]"); + } + + int lastIndex = message.toString().lastIndexOf(":"); + String msgContext = message.toString().substring(lastIndex + 1); + + LinkedList deviceControlList; + LinkedList replyMessageList; + + if (msgContext.equals(MESSAGE_TO_SEND) || msgContext.equals(ArduinoConstants.STATE_ON) || msgContext.equals( + ArduinoConstants.STATE_OFF)) { + + if (log.isDebugEnabled()) { + log.debug("Received a control message: "); + log.debug("Control message topic: " + topic); + log.debug("Control message: " + message.toString()); + } + + synchronized (ArduinoControllerService.getInternalControlsQueue()) { + deviceControlList = ArduinoControllerService.getInternalControlsQueue().get(deviceId); + if (deviceControlList == null) { + ArduinoControllerService.getInternalControlsQueue() + .put(deviceId, deviceControlList = new LinkedList()); + } + } + deviceControlList.add(message.toString()); + + } else if (msgContext.equals(MESSAGE_RECEIVED)) { + + if (log.isDebugEnabled()) { + log.debug("Received reply from a device: "); + log.debug("Reply message topic: " + topic); + log.debug("Reply message: " + message.toString().substring(0, lastIndex)); + } + + synchronized (ArduinoControllerService.getReplyMsgQueue()) { + replyMessageList = ArduinoControllerService.getReplyMsgQueue().get(deviceId); + if (replyMessageList == null) { + ArduinoControllerService.getReplyMsgQueue() + .put(deviceId, replyMessageList = new LinkedList<>()); + } + } + replyMessageList.add(message.toString()); + } + } + } + + @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 - " + ArduinoConstants.DEVICE_TYPE, e); + } + + try { + Thread.sleep(timeoutInterval); + } catch (InterruptedException e1) { + log.error("MQTT-Terminator: Thread Sleep Interrupt Exception at device-type - " + + ArduinoConstants.DEVICE_TYPE, e1); + } + } + } + } + }; + + Thread terminatorThread = new Thread(stopConnection); + terminatorThread.setDaemon(true); + terminatorThread.start(); + } + + @Override + public void processIncomingMessage() throws TransportHandlerException { + + } + + @Override + public void processIncomingMessage(MqttMessage message) throws TransportHandlerException { + + } + + @Override + public void publishDeviceData() throws TransportHandlerException { + + } + + @Override + public void publishDeviceData(MqttMessage publishData) throws TransportHandlerException { + + } + + @Override + public void publishDeviceData(String... publishData) throws TransportHandlerException { + + } +} diff --git a/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.controller.service.impl/src/main/java/org/wso2/carbon/device/mgt/iot/arduino/controller/service/impl/util/ArduinoServiceUtils.java b/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.controller.service.impl/src/main/java/org/wso2/carbon/device/mgt/iot/arduino/controller/service/impl/util/ArduinoServiceUtils.java new file mode 100644 index 0000000000..bdba9fef1c --- /dev/null +++ b/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.controller.service.impl/src/main/java/org/wso2/carbon/device/mgt/iot/arduino/controller/service/impl/util/ArduinoServiceUtils.java @@ -0,0 +1,217 @@ +/* + * 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.wso2.carbon.device.mgt.iot.arduino.controller.service.impl.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.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.Device; +import org.wso2.carbon.device.mgt.common.DeviceIdentifier; +import org.wso2.carbon.device.mgt.common.DeviceManagementException; +import org.wso2.carbon.device.mgt.core.service.DeviceManagementProviderService; +import org.wso2.carbon.device.mgt.iot.arduino.plugin.constants.ArduinoConstants; +import org.wso2.carbon.device.mgt.iot.sensormgt.SensorDataManager; +import org.wso2.carbon.utils.multitenancy.MultitenantUtils; + +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 ArduinoServiceUtils { + private static final Log log = LogFactory.getLog(ArduinoServiceUtils.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 = ArduinoConstants.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 deviceId, float temperature) { + PrivilegedCarbonContext ctx = PrivilegedCarbonContext.getThreadLocalCarbonContext(); + String owner = ctx.getUsername(); + Object metdaData[] = {owner, ArduinoConstants.DEVICE_TYPE, deviceId, System.currentTimeMillis()}; + Object payloadData[] = {temperature}; + DeviceAnalyticsService deviceAnalyticsService = (DeviceAnalyticsService) ctx + .getOSGiService(DeviceAnalyticsService.class, null); + if (deviceAnalyticsService != null) { + try { + deviceAnalyticsService.publishEvent(TEMPERATURE_STREAM_DEFINITION, "1.0.0", metdaData, + new Object[0], payloadData); + } catch (DataPublisherConfigurationException e) { + return false; + } + return true; + } + return false; + } +} diff --git a/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.controller.service.impl/src/main/webapp/META-INF/permissions.xml b/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.controller.service.impl/src/main/webapp/META-INF/permissions.xml new file mode 100644 index 0000000000..f2595154a0 --- /dev/null +++ b/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.controller.service.impl/src/main/webapp/META-INF/permissions.xml @@ -0,0 +1,87 @@ + + + + + + + + + + Register Device + /login + /controller/register/device/{deviceId}/{ip}/{port} + POST + + + + get device bulb statjs + /login + /controller/device/{deviceId}/bulb + GET + + + + get device temperature + /login + /controller/device/{deviceId}/temperature + GET + + + + get device temperature + /login + /controller/device/{deviceId}/temperature + GET + + + + get device temperature + /login + /controller/sensor + POST + + + + get controls + /login + /controller/device/{deviceId}/controls + POST + + + + push temperature + /login + /controller/temperature + POST + + + + get temperature + /login + /controller/stats/device/{deviceId}/sensors/temperature + GET + + + \ No newline at end of file diff --git a/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.controller.service.impl/src/main/webapp/META-INF/webapp-classloading.xml b/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.controller.service.impl/src/main/webapp/META-INF/webapp-classloading.xml new file mode 100644 index 0000000000..fa44619195 --- /dev/null +++ b/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.controller.service.impl/src/main/webapp/META-INF/webapp-classloading.xml @@ -0,0 +1,33 @@ + + + + + + + + + false + + + CXF,Carbon + diff --git a/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.controller.service.impl/src/main/webapp/WEB-INF/cxf-servlet.xml b/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.controller.service.impl/src/main/webapp/WEB-INF/cxf-servlet.xml new file mode 100644 index 0000000000..291443c92f --- /dev/null +++ b/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.controller.service.impl/src/main/webapp/WEB-INF/cxf-servlet.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.controller.service.impl/src/main/webapp/WEB-INF/web.xml b/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.controller.service.impl/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 0000000000..c62aa61000 --- /dev/null +++ b/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.controller.service.impl/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,62 @@ + + + Arduino + Arduino + + + CXFServlet + org.apache.cxf.transport.servlet.CXFServlet + 1 + + + + + CXFServlet + /* + + + + isAdminService + false + + + doAuthentication + false + + + isSharedWithAllTenants + true + + + providerTenantDomain + carbon.super + + + + managed-api-enabled + true + + + managed-api-owner + admin + + + managed-api-context-template + /arduino/{version} + + + managed-api-application + arduino + + + managed-api-isSecured + true + + + + + diff --git a/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.manager.service.impl/pom.xml b/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.manager.service.impl/pom.xml new file mode 100644 index 0000000000..9824474d5b --- /dev/null +++ b/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.manager.service.impl/pom.xml @@ -0,0 +1,180 @@ + + + + + + arduino-plugin + org.wso2.carbon.devicemgt-plugins + 2.1.0-SNAPSHOT + ../pom.xml + + + + 4.0.0 + org.wso2.carbon.device.mgt.iot.arduino.manager.service.impl + war + WSO2 Carbon - IoT Server Arduino ManagerService API + WSO2 Carbon - Arduino ManagerService API Implementation + http://wso2.org + + + + + org.wso2.carbon + org.wso2.carbon.utils + provided + + + + + org.wso2.carbon.devicemgt + org.wso2.carbon.device.mgt.common + provided + + + org.wso2.carbon.devicemgt + org.wso2.carbon.device.mgt.core + provided + + + org.apache.axis2.wso2 + axis2-client + + + + + + org.wso2.carbon.devicemgt + org.wso2.carbon.device.mgt.analytics + provided + + + org.apache.axis2.wso2 + axis2-client + + + + + + + + org.apache.cxf + cxf-rt-frontend-jaxws + provided + + + org.apache.cxf + cxf-rt-frontend-jaxrs + provided + + + org.apache.cxf + cxf-rt-transports-http + provided + + + + + org.eclipse.paho + org.eclipse.paho.client.mqttv3 + provided + + + + + org.apache.httpcomponents + httpasyncclient + 4.1 + provided + + + org.wso2.carbon.devicemgt-plugins + org.wso2.carbon.device.mgt.iot + provided + + + org.wso2.carbon.devicemgt-plugins + org.wso2.carbon.device.mgt.iot.arduino.plugin.impl + provided + + + + + org.codehaus.jackson + jackson-core-asl + + + org.codehaus.jackson + jackson-jaxrs + + + javax + javaee-web-api + provided + + + javax.ws.rs + jsr311-api + provided + + + org.wso2.carbon.devicemgt + org.wso2.carbon.apimgt.annotations + provided + + + org.wso2.carbon.devicemgt + org.wso2.carbon.apimgt.webapp.publisher + provided + + + org.wso2.carbon.devicemgt + org.wso2.carbon.device.mgt.jwt.client.extension + provided + + + org.wso2.carbon.devicemgt + org.wso2.carbon.apimgt.application.extension + provided + + + + + + + + maven-compiler-plugin + + UTF-8 + ${wso2.maven.compiler.source} + ${wso2.maven.compiler.target} + + + + maven-war-plugin + + arduino_mgt + + + + + + \ No newline at end of file diff --git a/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.manager.service.impl/src/main/java/org/wso2/carbon/device/mgt/iot/arduino/manager/service/impl/ArduinoManagerService.java b/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.manager.service.impl/src/main/java/org/wso2/carbon/device/mgt/iot/arduino/manager/service/impl/ArduinoManagerService.java new file mode 100644 index 0000000000..6d5969c588 --- /dev/null +++ b/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.manager.service.impl/src/main/java/org/wso2/carbon/device/mgt/iot/arduino/manager/service/impl/ArduinoManagerService.java @@ -0,0 +1,274 @@ +/* + * Copyright (c) 2016, 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.wso2.carbon.device.mgt.iot.arduino.manager.service.impl; + +import org.wso2.carbon.apimgt.application.extension.APIManagementProviderService; +import org.wso2.carbon.apimgt.application.extension.dto.ApiApplicationKey; +import org.wso2.carbon.apimgt.application.extension.exception.APIManagerException; +import org.wso2.carbon.context.PrivilegedCarbonContext; +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.arduino.manager.service.impl.util.APIUtil; +import org.wso2.carbon.device.mgt.iot.arduino.plugin.constants.ArduinoConstants; +import org.wso2.carbon.device.mgt.iot.exception.DeviceControllerException; +import org.wso2.carbon.device.mgt.iot.util.ZipArchive; +import org.wso2.carbon.device.mgt.iot.util.ZipUtil; +import org.wso2.carbon.device.mgt.jwt.client.extension.JWTClient; +import org.wso2.carbon.device.mgt.jwt.client.extension.JWTClientManager; +import org.wso2.carbon.device.mgt.jwt.client.extension.dto.AccessTokenInfo; +import org.wso2.carbon.device.mgt.jwt.client.extension.exception.JWTClientException; +import org.wso2.carbon.user.api.UserStoreException; + +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.MediaType; +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; + +public class ArduinoManagerService { + + @Context //injected response proxy supporting multiple thread + private HttpServletResponse response; + private static final String KEY_TYPE = "PRODUCTION"; + private static ApiApplicationKey apiApplicationKey; + + @Path("manager/device/{device_id}") + @DELETE + public void removeDevice(@PathParam("device_id") String deviceId, @Context HttpServletResponse response) { + DeviceIdentifier deviceIdentifier = new DeviceIdentifier(); + deviceIdentifier.setId(deviceId); + deviceIdentifier.setType(ArduinoConstants.DEVICE_TYPE); + try { + boolean removed = APIUtil.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()); + } + } + + @Path("manager/device/{device_id}") + @PUT + public boolean updateDevice(@PathParam("device_id") String deviceId, @QueryParam("name") String name, + @Context HttpServletResponse response) { + DeviceIdentifier deviceIdentifier = new DeviceIdentifier(); + deviceIdentifier.setId(deviceId); + deviceIdentifier.setType(ArduinoConstants.DEVICE_TYPE); + try { + Device device = APIUtil.getDeviceManagementService().getDevice(deviceIdentifier); + device.setDeviceIdentifier(deviceId); + device.getEnrolmentInfo().setDateOfLastUpdate(new Date().getTime()); + device.setName(name); + device.setType(ArduinoConstants.DEVICE_TYPE); + boolean updated = APIUtil.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; + } + } + + @Path("manager/device/{device_id}") + @GET + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public Device getDevice(@PathParam("device_id") String deviceId) { + DeviceIdentifier deviceIdentifier = new DeviceIdentifier(); + deviceIdentifier.setId(deviceId); + deviceIdentifier.setType(ArduinoConstants.DEVICE_TYPE); + try { + return APIUtil.getDeviceManagementService().getDevice(deviceIdentifier); + } catch (DeviceManagementException e) { + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + return null; + } + } + + @Path("manager/devices") + @GET + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public Device[] getArduinoDevices() { + try { + List userDevices = APIUtil.getDeviceManagementService().getDevicesOfUser( + APIUtil.getAuthenticatedUser()); + ArrayList userDevicesforArduino = new ArrayList<>(); + for (Device device : userDevices) { + if (device.getType().equals(ArduinoConstants.DEVICE_TYPE) && + device.getEnrolmentInfo().getStatus().equals(EnrolmentInfo.Status.ACTIVE)) { + userDevicesforArduino.add(device); + } + } + return userDevicesforArduino.toArray(new Device[]{}); + } catch (DeviceManagementException e) { + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + return null; + } + } + + @Path("manager/device/download") + @GET + @Produces("application/octet-stream") + public Response downloadSketch(@QueryParam("deviceName") String customDeviceName) { + try { + ZipArchive zipFile = createDownloadFile(APIUtil.getAuthenticatedUser(), customDeviceName); + Response.ResponseBuilder rb = Response.ok(zipFile.getZipFile()); + rb.header("Content-Disposition", "attachment; filename=\"" + zipFile.getFileName() + "\""); + return rb.build(); + } catch (IllegalArgumentException ex) { + return Response.status(400).entity(ex.getMessage()).build();//bad request + } catch (DeviceManagementException ex) { + return Response.status(500).entity(ex.getMessage()).build(); + } catch (DeviceControllerException ex) { + return Response.status(500).entity(ex.getMessage()).build(); + } catch (JWTClientException ex) { + return Response.status(500).entity(ex.getMessage()).build(); + } catch (APIManagerException ex) { + return Response.status(500).entity(ex.getMessage()).build(); + } catch (UserStoreException ex) { + return Response.status(500).entity(ex.getMessage()).build(); + } + } + + @Path("manager/device/generate_link") + @GET + public Response generateSketchLink(@QueryParam("deviceName") String deviceName) { + try { + ZipArchive zipFile = createDownloadFile(APIUtil.getAuthenticatedUser(), deviceName); + Response.ResponseBuilder rb = Response.ok(zipFile.getDeviceId()); + return rb.build(); + } catch (IllegalArgumentException ex) { + return Response.status(400).entity(ex.getMessage()).build();//bad request + } catch (DeviceManagementException ex) { + return Response.status(500).entity(ex.getMessage()).build(); + } catch (JWTClientException ex) { + return Response.status(500).entity(ex.getMessage()).build(); + } catch (DeviceControllerException ex) { + return Response.status(500).entity(ex.getMessage()).build(); + } catch (APIManagerException ex) { + return Response.status(500).entity(ex.getMessage()).build(); + } catch (UserStoreException ex) { + return Response.status(500).entity(ex.getMessage()).build(); + } + } + + private ZipArchive createDownloadFile(String owner, String deviceName) + throws DeviceManagementException, JWTClientException, DeviceControllerException, APIManagerException, + UserStoreException { + if (owner == null) { + throw new IllegalArgumentException("Error on createDownloadFile() Owner is null!"); + } + //create new device id + String deviceId = shortUUID(); + String applicationUsername = + PrivilegedCarbonContext.getThreadLocalCarbonContext().getUserRealm().getRealmConfiguration() + .getAdminUserName(); + if (apiApplicationKey == null) { + APIManagementProviderService apiManagementProviderService = APIUtil.getAPIManagementProviderService(); + String[] tags = {ArduinoConstants.DEVICE_TYPE}; + apiApplicationKey = apiManagementProviderService.generateAndRetrieveApplicationKeys( + ArduinoConstants.DEVICE_TYPE, tags, KEY_TYPE, applicationUsername, true); + } + JWTClient jwtClient = JWTClientManager.getInstance().getJWTClient(); + String scopes = "device_type_" + ArduinoConstants.DEVICE_TYPE + " device_" + deviceId; + AccessTokenInfo accessTokenInfo = jwtClient.getAccessToken(apiApplicationKey.getConsumerKey(), + apiApplicationKey.getConsumerSecret(), owner, + scopes); + //create token + String accessToken = accessTokenInfo.getAccess_token(); + String refreshToken = accessTokenInfo.getRefresh_token(); + //Register the device with CDMF + boolean status = register(deviceId, deviceName); + if (!status) { + String msg = "Error occurred while registering the device with " + "id: " + deviceId + " owner:" + owner; + throw new DeviceManagementException(msg); + } + ZipUtil ziputil = new ZipUtil(); + ZipArchive zipFile = ziputil.createZipFile(owner, APIUtil.getTenantDomainOftheUser(), + ArduinoConstants.DEVICE_TYPE, deviceId, + deviceName, accessToken, refreshToken); + zipFile.setDeviceId(deviceId); + return zipFile; + } + + 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); + } + + private boolean register(String deviceId, String name) { + DeviceIdentifier deviceIdentifier = new DeviceIdentifier(); + deviceIdentifier.setId(deviceId); + deviceIdentifier.setType(ArduinoConstants.DEVICE_TYPE); + try { + if (APIUtil.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(ArduinoConstants.DEVICE_TYPE); + enrolmentInfo.setOwner(APIUtil.getAuthenticatedUser()); + device.setEnrolmentInfo(enrolmentInfo); + boolean added = APIUtil.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; + } + } +} diff --git a/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.manager.service.impl/src/main/java/org/wso2/carbon/device/mgt/iot/arduino/manager/service/impl/util/APIUtil.java b/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.manager.service.impl/src/main/java/org/wso2/carbon/device/mgt/iot/arduino/manager/service/impl/util/APIUtil.java new file mode 100644 index 0000000000..5ffe0198cf --- /dev/null +++ b/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.manager.service.impl/src/main/java/org/wso2/carbon/device/mgt/iot/arduino/manager/service/impl/util/APIUtil.java @@ -0,0 +1,55 @@ +package org.wso2.carbon.device.mgt.iot.arduino.manager.service.impl.util; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.apimgt.application.extension.APIManagementProviderService; +import org.wso2.carbon.context.PrivilegedCarbonContext; +import org.wso2.carbon.device.mgt.core.service.DeviceManagementProviderService; + +/** + * This class provides utility functions used by REST-API. + */ +public class APIUtil { + + private static Log log = LogFactory.getLog(APIUtil.class); + + public static String getAuthenticatedUser() { + PrivilegedCarbonContext threadLocalCarbonContext = PrivilegedCarbonContext.getThreadLocalCarbonContext(); + String username = threadLocalCarbonContext.getUsername(); + String tenantDomain = threadLocalCarbonContext.getTenantDomain(); + if (username.endsWith(tenantDomain)) { + return username.substring(0, username.lastIndexOf("@")); + } + return username; + } + + public static String getTenantDomainOftheUser() { + PrivilegedCarbonContext threadLocalCarbonContext = PrivilegedCarbonContext.getThreadLocalCarbonContext(); + String tenantDomain = threadLocalCarbonContext.getTenantDomain(); + return tenantDomain; + } + + public static DeviceManagementProviderService getDeviceManagementService() { + PrivilegedCarbonContext ctx = PrivilegedCarbonContext.getThreadLocalCarbonContext(); + DeviceManagementProviderService deviceManagementProviderService = + (DeviceManagementProviderService) ctx.getOSGiService(DeviceManagementProviderService.class, null); + if (deviceManagementProviderService == null) { + String msg = "Device Management service has not initialized."; + log.error(msg); + throw new IllegalStateException(msg); + } + return deviceManagementProviderService; + } + + public static APIManagementProviderService getAPIManagementProviderService() { + PrivilegedCarbonContext ctx = PrivilegedCarbonContext.getThreadLocalCarbonContext(); + APIManagementProviderService apiManagementProviderService = + (APIManagementProviderService) ctx.getOSGiService(APIManagementProviderService.class, null); + if (apiManagementProviderService == null) { + String msg = "API management provider service has not initialized."; + log.error(msg); + throw new IllegalStateException(msg); + } + return apiManagementProviderService; + } +} diff --git a/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.manager.service.impl/src/main/webapp/META-INF/permissions.xml b/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.manager.service.impl/src/main/webapp/META-INF/permissions.xml new file mode 100644 index 0000000000..5d829b8500 --- /dev/null +++ b/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.manager.service.impl/src/main/webapp/META-INF/permissions.xml @@ -0,0 +1,59 @@ + + + + + + + + + + Get device + /device-mgt/user/devices/list + /manager/device/{device_id} + GET + + + + Remove device + /device-mgt/user/devices/remove + /manager/device/{device_id} + DELETE + + + + Download device + /device-mgt/user/devices/add + /manager/device/download + GET + + + + Update device + /device-mgt/user/devices/update + /manager/device/{device_id} + PUT + + + \ No newline at end of file diff --git a/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.manager.service.impl/src/main/webapp/META-INF/webapp-classloading.xml b/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.manager.service.impl/src/main/webapp/META-INF/webapp-classloading.xml new file mode 100644 index 0000000000..fa44619195 --- /dev/null +++ b/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.manager.service.impl/src/main/webapp/META-INF/webapp-classloading.xml @@ -0,0 +1,33 @@ + + + + + + + + + false + + + CXF,Carbon + diff --git a/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.manager.service.impl/src/main/webapp/WEB-INF/cxf-servlet.xml b/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.manager.service.impl/src/main/webapp/WEB-INF/cxf-servlet.xml new file mode 100644 index 0000000000..98bfb6d536 --- /dev/null +++ b/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.manager.service.impl/src/main/webapp/WEB-INF/cxf-servlet.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + diff --git a/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.manager.service.impl/src/main/webapp/WEB-INF/web.xml b/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.manager.service.impl/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 0000000000..e2c67f55aa --- /dev/null +++ b/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.manager.service.impl/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,51 @@ + + + Arduino + Arduino + + + CXFServlet + org.apache.cxf.transport.servlet.CXFServlet + 1 + + + + CXFServlet + /* + + + + isAdminService + false + + + doAuthentication + true + + + + + managed-api-enabled + false + + + managed-api-owner + admin + + + managed-api-context-template + /arduino/{version} + + + managed-api-application + arduino + + + managed-api-isSecured + true + + diff --git a/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.plugin.impl/pom.xml b/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.plugin.impl/pom.xml new file mode 100644 index 0000000000..0c870b6111 --- /dev/null +++ b/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.plugin.impl/pom.xml @@ -0,0 +1,124 @@ + + + + + + + arduino-plugin + org.wso2.carbon.devicemgt-plugins + 2.1.0-SNAPSHOT + ../pom.xml + + + 4.0.0 + org.wso2.carbon.device.mgt.iot.arduino.plugin.impl + bundle + WSO2 Carbon - IoT Server Arduino Management Plugin + WSO2 Carbon - Arduino Management/Control Plugin Implementation + http://wso2.org + + + + + org.apache.felix + maven-scr-plugin + + + maven-compiler-plugin + + 1.7 + 1.7 + + 2.3.2 + + + org.apache.felix + maven-bundle-plugin + 1.4.0 + true + + + ${project.artifactId} + ${project.artifactId} + ${carbon.devicemgt.plugins.version} + IoT Server Arduino Impl Bundle + org.wso2.carbon.device.mgt.iot.arduino.plugin.internal + + org.osgi.framework, + org.osgi.service.component, + org.apache.commons.logging, + javax.xml.bind.*;resolution:=optional, + javax.naming;resolution:=optional, + javax.sql;resolution:=optional, + javax.xml.bind.annotation.*;resolution:=optional, + javax.xml.parsers;resolution:=optional, + javax.net;resolution:=optional, + javax.net.ssl;resolution:=optional, + org.w3c.dom;resolution:=optional, + org.wso2.carbon.device.mgt.common.*, + org.wso2.carbon.device.mgt.common, + org.wso2.carbon.context.*, + org.wso2.carbon.ndatasource.core, + org.wso2.carbon.device.mgt.iot.*, + org.wso2.carbon.device.mgt.extensions.feature.mgt.*, + org.wso2.carbon.utils.* + + + + !org.wso2.carbon.device.mgt.iot.arduino.plugin.internal, + org.wso2.carbon.device.mgt.iot.arduino.plugin.* + + + + + + + + + + org.eclipse.osgi + org.eclipse.osgi + + + org.eclipse.osgi + org.eclipse.osgi.services + + + org.wso2.carbon + org.wso2.carbon.logging + + + org.wso2.carbon.devicemgt + org.wso2.carbon.device.mgt.common + + + org.wso2.carbon + org.wso2.carbon.ndatasource.core + + + org.wso2.carbon.devicemgt + org.wso2.carbon.device.mgt.extensions + + + org.wso2.carbon + org.wso2.carbon.utils + + + \ No newline at end of file diff --git a/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.plugin.impl/src/main/java/org/wso2/carbon/device/mgt/iot/arduino/plugin/constants/ArduinoConstants.java b/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.plugin.impl/src/main/java/org/wso2/carbon/device/mgt/iot/arduino/plugin/constants/ArduinoConstants.java new file mode 100644 index 0000000000..6a93500750 --- /dev/null +++ b/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.plugin.impl/src/main/java/org/wso2/carbon/device/mgt/iot/arduino/plugin/constants/ArduinoConstants.java @@ -0,0 +1,39 @@ +/* + * 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.wso2.carbon.device.mgt.iot.arduino.plugin.constants; + +public class ArduinoConstants { + public final static String DEVICE_TYPE = "arduino"; + public final static String DEVICE_PLUGIN_DEVICE_NAME = "DEVICE_NAME"; + public final static String DEVICE_PLUGIN_DEVICE_ID = "ARDUINO_DEVICE_ID"; + public final static String STATE_ON = "ON"; + public final static String STATE_OFF = "OFF"; + + public static final String URL_PREFIX = "http://"; + public static final String BULB_CONTEXT = "/BULB/"; + public static final String SONAR_CONTEXT = "/HUMIDITY/"; + public static final String TEMPERATURE_CONTEXT = "/TEMPERATURE/"; + + //type of the sensor + public static final String SENSOR_TEMPERATURE = "temperature"; + //sensor events summerized table name + public static final String TEMPERATURE_EVENT_TABLE = "DEVICE_TEMPERATURE_SUMMARY"; + public static final String DATA_SOURCE_NAME = "jdbc/ArduinoDM_DB"; + +} diff --git a/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.plugin.impl/src/main/java/org/wso2/carbon/device/mgt/iot/arduino/plugin/exception/ArduinoDeviceMgtPluginException.java b/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.plugin.impl/src/main/java/org/wso2/carbon/device/mgt/iot/arduino/plugin/exception/ArduinoDeviceMgtPluginException.java new file mode 100644 index 0000000000..93dc15683c --- /dev/null +++ b/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.plugin.impl/src/main/java/org/wso2/carbon/device/mgt/iot/arduino/plugin/exception/ArduinoDeviceMgtPluginException.java @@ -0,0 +1,56 @@ +/* + * 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.wso2.carbon.device.mgt.iot.arduino.plugin.exception; + + +public class ArduinoDeviceMgtPluginException extends Exception{ + + private String errorMessage; + + public String getErrorMessage() { + return errorMessage; + } + + public void setErrorMessage(String errorMessage) { + this.errorMessage = errorMessage; + } + + public ArduinoDeviceMgtPluginException(String msg, Exception nestedEx) { + super(msg, nestedEx); + setErrorMessage(msg); + } + + public ArduinoDeviceMgtPluginException(String message, Throwable cause) { + super(message, cause); + setErrorMessage(message); + } + + public ArduinoDeviceMgtPluginException(String msg) { + super(msg); + setErrorMessage(msg); + } + + public ArduinoDeviceMgtPluginException() { + super(); + } + + public ArduinoDeviceMgtPluginException(Throwable cause) { + super(cause); + } + +} diff --git a/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.plugin.impl/src/main/java/org/wso2/carbon/device/mgt/iot/arduino/plugin/impl/ArduinoManager.java b/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.plugin.impl/src/main/java/org/wso2/carbon/device/mgt/iot/arduino/plugin/impl/ArduinoManager.java new file mode 100644 index 0000000000..5c7174d96b --- /dev/null +++ b/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.plugin.impl/src/main/java/org/wso2/carbon/device/mgt/iot/arduino/plugin/impl/ArduinoManager.java @@ -0,0 +1,259 @@ +/* + * 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.wso2.carbon.device.mgt.iot.arduino.plugin.impl; + + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +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.DeviceManager; +import org.wso2.carbon.device.mgt.common.EnrolmentInfo; +import org.wso2.carbon.device.mgt.common.FeatureManager; +import org.wso2.carbon.device.mgt.common.configuration.mgt.TenantConfiguration; +import org.wso2.carbon.device.mgt.common.license.mgt.License; +import org.wso2.carbon.device.mgt.common.license.mgt.LicenseManagementException; +import org.wso2.carbon.device.mgt.iot.arduino.plugin.exception.ArduinoDeviceMgtPluginException; +import org.wso2.carbon.device.mgt.iot.arduino.plugin.impl.dao.ArduinoDAO; +import org.wso2.carbon.device.mgt.iot.arduino.plugin.impl.feature.ArduinoFeatureManager; +import java.util.List; + + +/** + * This represents the Arduino implementation of DeviceManagerService. + */ +public class ArduinoManager implements DeviceManager { + + private static final ArduinoDAO arduinoDAO = new ArduinoDAO(); + private static final Log log = LogFactory.getLog(ArduinoManager.class); + private ArduinoFeatureManager arduinoFeatureManager = new ArduinoFeatureManager(); + + @Override + public FeatureManager getFeatureManager() { + return arduinoFeatureManager; + } + + @Override + public boolean saveConfiguration(TenantConfiguration tenantConfiguration) + throws DeviceManagementException { + //TODO implement this + return false; + } + + @Override + public TenantConfiguration getConfiguration() throws DeviceManagementException { + //TODO implement this + return null; + } + + @Override + public boolean enrollDevice(Device device) throws DeviceManagementException { + boolean status; + try { + if (log.isDebugEnabled()) { + log.debug("Enrolling a new Arduino device : " + device.getDeviceIdentifier()); + } + ArduinoDAO.beginTransaction(); + status = arduinoDAO.getDeviceDAO().addDevice(device); + ArduinoDAO.commitTransaction(); + } catch (ArduinoDeviceMgtPluginException e) { + try { + ArduinoDAO.rollbackTransaction(); + } catch (ArduinoDeviceMgtPluginException iotDAOEx) { + String msg = "Error occurred while roll back the device enrol transaction :" + device.toString(); + log.warn(msg, iotDAOEx); + } + String msg = "Error while enrolling the Arduino device : " + device.getDeviceIdentifier(); + log.error(msg, e); + throw new DeviceManagementException(msg, e); + } + return status; + } + + @Override + public boolean modifyEnrollment(Device device) throws DeviceManagementException { + boolean status; + try { + if (log.isDebugEnabled()) { + log.debug("Modifying the Arduino device enrollment data"); + } + ArduinoDAO.beginTransaction(); + status = arduinoDAO.getDeviceDAO().updateDevice(device); + ArduinoDAO.commitTransaction(); + } catch (ArduinoDeviceMgtPluginException e) { + try { + ArduinoDAO.rollbackTransaction(); + } catch (ArduinoDeviceMgtPluginException iotDAOEx) { + String msg = "Error occurred while roll back the update device transaction :" + device.toString(); + log.warn(msg, iotDAOEx); + } + String msg = "Error while updating the enrollment of the Arduino device : " + + device.getDeviceIdentifier(); + log.error(msg, e); + throw new DeviceManagementException(msg, e); + } + return status; + } + + @Override + public boolean disenrollDevice(DeviceIdentifier deviceId) throws DeviceManagementException { + boolean status; + try { + if (log.isDebugEnabled()) { + log.debug("Dis-enrolling Arduino device : " + deviceId); + } + ArduinoDAO.beginTransaction(); + status = arduinoDAO.getDeviceDAO().deleteDevice(deviceId.getId()); + ArduinoDAO.commitTransaction(); + } catch (ArduinoDeviceMgtPluginException e) { + try { + ArduinoDAO.rollbackTransaction(); + } catch (ArduinoDeviceMgtPluginException iotDAOEx) { + String msg = "Error occurred while roll back the device dis enrol transaction :" + deviceId.toString(); + log.warn(msg, iotDAOEx); + } + String msg = "Error while removing the Arduino device : " + deviceId.getId(); + log.error(msg, e); + throw new DeviceManagementException(msg, e); + } + return status; + } + + @Override + public boolean isEnrolled(DeviceIdentifier deviceId) throws DeviceManagementException { + boolean isEnrolled = false; + try { + if (log.isDebugEnabled()) { + log.debug("Checking the enrollment of Arduino device : " + deviceId.getId()); + } + Device iotDevice = arduinoDAO.getDeviceDAO().getDevice(deviceId.getId()); + if (iotDevice != null) { + isEnrolled = true; + } + } catch (ArduinoDeviceMgtPluginException e) { + String msg = "Error while checking the enrollment status of Arduino device : " + + deviceId.getId(); + log.error(msg, e); + throw new DeviceManagementException(msg, e); + } + return isEnrolled; + } + + @Override + public boolean isActive(DeviceIdentifier deviceId) throws DeviceManagementException { + return true; + } + + @Override + public boolean setActive(DeviceIdentifier deviceId, boolean status) + throws DeviceManagementException { + return true; + } + + @Override + public Device getDevice(DeviceIdentifier deviceId) throws DeviceManagementException { + Device device; + try { + if (log.isDebugEnabled()) { + log.debug("Getting the details of Arduino device : " + deviceId.getId()); + } + device = arduinoDAO.getDeviceDAO().getDevice(deviceId.getId()); + } catch (ArduinoDeviceMgtPluginException e) { + String msg = "Error while fetching the Arduino device : " + deviceId.getId(); + log.error(msg, e); + throw new DeviceManagementException(msg, e); + } + return device; + } + + @Override + public boolean setOwnership(DeviceIdentifier deviceId, String ownershipType) + throws DeviceManagementException { + return true; + } + + public boolean isClaimable(DeviceIdentifier deviceIdentifier) throws DeviceManagementException { + return false; + } + + @Override + public boolean setStatus(DeviceIdentifier deviceId, String currentOwner, + EnrolmentInfo.Status status) throws DeviceManagementException { + return false; + } + + @Override + public License getLicense(String s) throws LicenseManagementException { + return null; + } + + @Override + public void addLicense(License license) throws LicenseManagementException { + + } + + @Override + public boolean requireDeviceAuthorization() { + return false; + } + + @Override + public boolean updateDeviceInfo(DeviceIdentifier deviceIdentifier, Device device) throws DeviceManagementException { + boolean status; + try { + if (log.isDebugEnabled()) { + log.debug( + "updating the details of Arduino device : " + deviceIdentifier); + } + ArduinoDAO.beginTransaction(); + status = arduinoDAO.getDeviceDAO().updateDevice(device); + ArduinoDAO.commitTransaction(); + } catch (ArduinoDeviceMgtPluginException e) { + try { + ArduinoDAO.rollbackTransaction(); + } catch (ArduinoDeviceMgtPluginException iotDAOEx) { + String msg = "Error occurred while roll back the update device info transaction :" + device.toString(); + log.warn(msg, iotDAOEx); + } + String msg = + "Error while updating the Arduino device : " + deviceIdentifier; + log.error(msg, e); + throw new DeviceManagementException(msg, e); + } + return status; + } + + @Override + public List getAllDevices() throws DeviceManagementException { + List devices; + try { + if (log.isDebugEnabled()) { + log.debug("Fetching the details of all Arduino devices"); + } + devices = arduinoDAO.getDeviceDAO().getAllDevices(); + } catch (ArduinoDeviceMgtPluginException e) { + String msg = "Error while fetching all Arduino devices."; + log.error(msg, e); + throw new DeviceManagementException(msg, e); + } + return devices; + } + +} \ No newline at end of file diff --git a/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.plugin.impl/src/main/java/org/wso2/carbon/device/mgt/iot/arduino/plugin/impl/ArduinoManagerService.java b/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.plugin.impl/src/main/java/org/wso2/carbon/device/mgt/iot/arduino/plugin/impl/ArduinoManagerService.java new file mode 100644 index 0000000000..15f1ff60c1 --- /dev/null +++ b/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.plugin.impl/src/main/java/org/wso2/carbon/device/mgt/iot/arduino/plugin/impl/ArduinoManagerService.java @@ -0,0 +1,108 @@ +/* + * 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.wso2.carbon.device.mgt.iot.arduino.plugin.impl; + +import org.wso2.carbon.device.mgt.common.DeviceIdentifier; +import org.wso2.carbon.device.mgt.common.DeviceManagementException; +import org.wso2.carbon.device.mgt.common.DeviceManager; +import org.wso2.carbon.device.mgt.common.app.mgt.Application; +import org.wso2.carbon.device.mgt.common.app.mgt.ApplicationManagementException; +import org.wso2.carbon.device.mgt.common.app.mgt.ApplicationManager; +import org.wso2.carbon.device.mgt.common.operation.mgt.Operation; +import org.wso2.carbon.device.mgt.common.spi.DeviceManagementService; +import org.wso2.carbon.device.mgt.iot.arduino.plugin.constants.ArduinoConstants; + +import java.util.List; + +public class ArduinoManagerService implements DeviceManagementService { + private DeviceManager deviceManager; + + @Override + public String getType() { + return ArduinoConstants.DEVICE_TYPE; + } + + @Override + public String getProviderTenantDomain() { + return "carbon.super"; + } + + @Override + public boolean isSharedWithAllTenants() { + return true; + } + + @Override + public void init() throws DeviceManagementException { + deviceManager=new ArduinoManager(); + + } + + @Override + public DeviceManager getDeviceManager() { + return deviceManager; + } + + @Override + public ApplicationManager getApplicationManager() { + return null; + } + + @Override + public void notifyOperationToDevices(Operation operation, List deviceIds) + throws DeviceManagementException { + + } + + @Override + public Application[] getApplications(String domain, int pageNumber, int size) + throws ApplicationManagementException { + return new Application[0]; + } + + @Override + public void updateApplicationStatus(DeviceIdentifier deviceId, Application application, + String status) throws ApplicationManagementException { + + } + + @Override + public String getApplicationStatus(DeviceIdentifier deviceId, Application application) + throws ApplicationManagementException { + return null; + } + + @Override + public void installApplicationForDevices(Operation operation, List deviceIdentifiers) + throws ApplicationManagementException { + + } + + @Override + public void installApplicationForUsers(Operation operation, List userNameList) + throws ApplicationManagementException { + + } + + @Override + public void installApplicationForUserRoles(Operation operation, List userRoleList) + throws ApplicationManagementException { + + } +} diff --git a/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.plugin.impl/src/main/java/org/wso2/carbon/device/mgt/iot/arduino/plugin/impl/dao/ArduinoDAO.java b/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.plugin.impl/src/main/java/org/wso2/carbon/device/mgt/iot/arduino/plugin/impl/dao/ArduinoDAO.java new file mode 100644 index 0000000000..133f0c74dd --- /dev/null +++ b/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.plugin.impl/src/main/java/org/wso2/carbon/device/mgt/iot/arduino/plugin/impl/dao/ArduinoDAO.java @@ -0,0 +1,125 @@ +/* + * 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.wso2.carbon.device.mgt.iot.arduino.plugin.impl.dao; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.device.mgt.iot.arduino.plugin.constants.ArduinoConstants; +import org.wso2.carbon.device.mgt.iot.arduino.plugin.exception.ArduinoDeviceMgtPluginException; +import org.wso2.carbon.device.mgt.iot.arduino.plugin.impl.dao.impl.ArduinoDeviceDAOImpl; +import javax.naming.Context; +import javax.naming.InitialContext; +import javax.naming.NamingException; +import javax.sql.DataSource; +import java.sql.Connection; +import java.sql.SQLException; + +public class ArduinoDAO { + + private static final Log log = LogFactory.getLog(ArduinoDAO.class); + static DataSource dataSource; + private static ThreadLocal currentConnection = new ThreadLocal(); + + public ArduinoDAO() { + initArduinoDAO(); + } + + public static void initArduinoDAO() { + try { + Context ctx = new InitialContext(); + dataSource = (DataSource) ctx.lookup(ArduinoConstants.DATA_SOURCE_NAME); + } catch (NamingException e) { + log.error("Error while looking up the data source: " + ArduinoConstants.DATA_SOURCE_NAME); + } + } + + public ArduinoDeviceDAOImpl getDeviceDAO() { + return new ArduinoDeviceDAOImpl(); + } + + public static void beginTransaction() throws ArduinoDeviceMgtPluginException { + try { + Connection conn = dataSource.getConnection(); + conn.setAutoCommit(false); + currentConnection.set(conn); + } catch (SQLException e) { + throw new ArduinoDeviceMgtPluginException("Error occurred while retrieving datasource connection", e); + } + } + + public static Connection getConnection() throws ArduinoDeviceMgtPluginException { + if (currentConnection.get() == null) { + try { + currentConnection.set(dataSource.getConnection()); + } catch (SQLException e) { + throw new ArduinoDeviceMgtPluginException("Error occurred while retrieving data source connection", e); + } + } + return currentConnection.get(); + } + + public static void commitTransaction() throws ArduinoDeviceMgtPluginException { + 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 ArduinoDeviceMgtPluginException("Error occurred while committing the transaction", e); + } finally { + closeConnection(); + } + } + + public static void closeConnection() throws ArduinoDeviceMgtPluginException { + + 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 ArduinoDeviceMgtPluginException { + 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 ArduinoDeviceMgtPluginException("Error occurred while rollback the transaction", e); + } finally { + closeConnection(); + } + } +} \ No newline at end of file diff --git a/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.plugin.impl/src/main/java/org/wso2/carbon/device/mgt/iot/arduino/plugin/impl/dao/impl/ArduinoDeviceDAOImpl.java b/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.plugin.impl/src/main/java/org/wso2/carbon/device/mgt/iot/arduino/plugin/impl/dao/impl/ArduinoDeviceDAOImpl.java new file mode 100644 index 0000000000..1bb0f164f1 --- /dev/null +++ b/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.plugin.impl/src/main/java/org/wso2/carbon/device/mgt/iot/arduino/plugin/impl/dao/impl/ArduinoDeviceDAOImpl.java @@ -0,0 +1,196 @@ +/* + * 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.wso2.carbon.device.mgt.iot.arduino.plugin.impl.dao.impl; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.device.mgt.common.Device; +import org.wso2.carbon.device.mgt.iot.arduino.plugin.exception.ArduinoDeviceMgtPluginException; +import org.wso2.carbon.device.mgt.iot.arduino.plugin.impl.util.ArduinoUtils; +import org.wso2.carbon.device.mgt.iot.arduino.plugin.constants.ArduinoConstants; +import org.wso2.carbon.device.mgt.iot.arduino.plugin.impl.dao.ArduinoDAO; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; + +/** + * Implements CRUD for arduino Devices. + */ +public class ArduinoDeviceDAOImpl { + + + private static final Log log = LogFactory.getLog(ArduinoDeviceDAOImpl.class); + + public Device getDevice(String deviceId) throws ArduinoDeviceMgtPluginException { + Connection conn = null; + PreparedStatement stmt = null; + Device device = null; + ResultSet resultSet = null; + try { + conn = ArduinoDAO.getConnection(); + String selectDBQuery = + "SELECT ARDUINO_DEVICE_ID, DEVICE_NAME FROM ARDUINO_DEVICE WHERE ARDUINO_DEVICE_ID = ?"; + stmt = conn.prepareStatement(selectDBQuery); + stmt.setString(1, deviceId); + resultSet = stmt.executeQuery(); + + if (resultSet.next()) { + device = new Device(); + device.setName(resultSet.getString(ArduinoConstants.DEVICE_PLUGIN_DEVICE_NAME)); + if (log.isDebugEnabled()) { + log.debug("Arduino device " + deviceId + " data has been fetched from " + + "Arduino database."); + } + } + } catch (SQLException e) { + String msg = "Error occurred while fetching Arduino device : '" + deviceId + "'"; + log.error(msg, e); + throw new ArduinoDeviceMgtPluginException(msg, e); + } finally { + ArduinoUtils.cleanupResources(stmt, resultSet); + ArduinoDAO.closeConnection(); + } + return device; + } + + public boolean addDevice(Device iotDevice) throws ArduinoDeviceMgtPluginException { + boolean status = false; + Connection conn = null; + PreparedStatement stmt = null; + try { + conn = ArduinoDAO.getConnection(); + String createDBQuery = + "INSERT INTO ARDUINO_DEVICE(ARDUINO_DEVICE_ID, DEVICE_NAME) VALUES (?, ?)"; + + stmt = conn.prepareStatement(createDBQuery); + stmt.setString(1, iotDevice.getDeviceIdentifier()); + stmt.setString(2,iotDevice.getName()); + int rows = stmt.executeUpdate(); + if (rows > 0) { + status = true; + if (log.isDebugEnabled()) { + log.debug("Arduino device " + iotDevice.getDeviceIdentifier() + " data has been" + + " added to the Arduino database."); + } + } + } catch (SQLException e) { + String msg = "Error occurred while adding the Arduino device '" + + iotDevice.getDeviceIdentifier() + "' to the Arduino db."; + log.error(msg, e); + throw new ArduinoDeviceMgtPluginException(msg, e); + } finally { + ArduinoUtils.cleanupResources(stmt, null); + } + return status; + } + + public boolean updateDevice(Device iotDevice) throws ArduinoDeviceMgtPluginException { + boolean status = false; + Connection conn = null; + PreparedStatement stmt = null; + try { + conn = ArduinoDAO.getConnection(); + String updateDBQuery = + "UPDATE ARDUINO_DEVICE SET DEVICE_NAME = ? WHERE ARDUINO_DEVICE_ID = ?"; + + stmt = conn.prepareStatement(updateDBQuery); + stmt.setString(1, iotDevice.getName()); + stmt.setString(2, iotDevice.getDeviceIdentifier()); + int rows = stmt.executeUpdate(); + if (rows > 0) { + status = true; + if (log.isDebugEnabled()) { + log.debug("Arduino device " + iotDevice.getDeviceIdentifier() + " data has been" + + " modified."); + } + } + } catch (SQLException e) { + String msg = "Error occurred while modifying the Arduino device '" + iotDevice.getDeviceIdentifier() + + "' data."; + log.error(msg, e); + throw new ArduinoDeviceMgtPluginException(msg, e); + } finally { + ArduinoUtils.cleanupResources(stmt, null); + } + return status; + } + + public boolean deleteDevice(String iotDeviceId) throws ArduinoDeviceMgtPluginException { + boolean status = false; + Connection conn = null; + PreparedStatement stmt = null; + try { + conn = ArduinoDAO.getConnection(); + String deleteDBQuery = + "DELETE FROM ARDUINO_DEVICE WHERE ARDUINO_DEVICE_ID = ?"; + stmt = conn.prepareStatement(deleteDBQuery); + stmt.setString(1, iotDeviceId); + int rows = stmt.executeUpdate(); + if (rows > 0) { + status = true; + if (log.isDebugEnabled()) { + log.debug("Arduino device " + iotDeviceId + " data has deleted" + + " from the Arduino database."); + } + } + } catch (SQLException e) { + String msg = "Error occurred while deleting Arduino device " + iotDeviceId; + log.error(msg, e); + throw new ArduinoDeviceMgtPluginException(msg, e); + } finally { + ArduinoUtils.cleanupResources(stmt, null); + } + return status; + } + + public List getAllDevices() throws ArduinoDeviceMgtPluginException { + + Connection conn = null; + PreparedStatement stmt = null; + ResultSet resultSet = null; + Device device; + List devices = new ArrayList(); + try { + conn = ArduinoDAO.getConnection(); + String selectDBQuery = + "SELECT ARDUINO_DEVICE_ID, DEVICE_NAME FROM ARDUINO_DEVICE"; + stmt = conn.prepareStatement(selectDBQuery); + resultSet = stmt.executeQuery(); + while (resultSet.next()) { + device = new Device(); + device.setDeviceIdentifier(resultSet.getString(ArduinoConstants.DEVICE_PLUGIN_DEVICE_ID)); + device.setName(resultSet.getString(ArduinoConstants.DEVICE_PLUGIN_DEVICE_NAME)); + } + if (log.isDebugEnabled()) { + log.debug("All Arduino device details have fetched from Arduino database."); + } + return devices; + } catch (SQLException e) { + String msg = "Error occurred while fetching all Arduino device data'"; + log.error(msg, e); + throw new ArduinoDeviceMgtPluginException(msg, e); + } finally { + ArduinoUtils.cleanupResources(stmt, resultSet); + ArduinoDAO.closeConnection(); + } + } + } \ No newline at end of file diff --git a/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.plugin.impl/src/main/java/org/wso2/carbon/device/mgt/iot/arduino/plugin/impl/feature/ArduinoFeatureManager.java b/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.plugin.impl/src/main/java/org/wso2/carbon/device/mgt/iot/arduino/plugin/impl/feature/ArduinoFeatureManager.java new file mode 100644 index 0000000000..d693f8e5bc --- /dev/null +++ b/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.plugin.impl/src/main/java/org/wso2/carbon/device/mgt/iot/arduino/plugin/impl/feature/ArduinoFeatureManager.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2016, 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. + */ + +package org.wso2.carbon.device.mgt.iot.arduino.plugin.impl.feature; + +import org.wso2.carbon.device.mgt.common.DeviceManagementException; +import org.wso2.carbon.device.mgt.common.Feature; +import org.wso2.carbon.device.mgt.common.FeatureManager; +import org.wso2.carbon.device.mgt.extensions.feature.mgt.GenericFeatureManager; +import org.wso2.carbon.device.mgt.iot.arduino.plugin.constants.ArduinoConstants; + +import java.util.List; + +public class ArduinoFeatureManager implements FeatureManager { + @Override + public boolean addFeature(Feature feature) throws DeviceManagementException { + return false; + } + + @Override + public boolean addFeatures(List features) throws DeviceManagementException { + return false; + } + + @Override + public Feature getFeature(String name) throws DeviceManagementException { + GenericFeatureManager genericFeatureManager = GenericFeatureManager.getInstance(); + return genericFeatureManager.getFeature(ArduinoConstants.DEVICE_TYPE, name); + } + + @Override + public List getFeatures() throws DeviceManagementException { + GenericFeatureManager genericFeatureManager = GenericFeatureManager.getInstance(); + return genericFeatureManager.getFeatures(ArduinoConstants.DEVICE_TYPE); + } + + @Override + public boolean removeFeature(String name) throws DeviceManagementException { + return false; + } + + @Override + public boolean addSupportedFeaturesToDB() throws DeviceManagementException { + return false; + } +} diff --git a/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.plugin.impl/src/main/java/org/wso2/carbon/device/mgt/iot/arduino/plugin/impl/util/ArduinoUtils.java b/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.plugin.impl/src/main/java/org/wso2/carbon/device/mgt/iot/arduino/plugin/impl/util/ArduinoUtils.java new file mode 100644 index 0000000000..f5e3642742 --- /dev/null +++ b/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.plugin.impl/src/main/java/org/wso2/carbon/device/mgt/iot/arduino/plugin/impl/util/ArduinoUtils.java @@ -0,0 +1,114 @@ +/* + * 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.wso2.carbon.device.mgt.iot.arduino.plugin.impl.util; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.device.mgt.common.Device; +import org.wso2.carbon.device.mgt.iot.arduino.plugin.constants.ArduinoConstants; +import org.wso2.carbon.device.mgt.iot.arduino.plugin.exception.ArduinoDeviceMgtPluginException; + +import javax.naming.Context; +import javax.naming.InitialContext; +import javax.naming.NamingException; +import javax.sql.DataSource; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.List; +import java.util.Map; + +/** + * Contains utility methods used by Arduino plugin. + */ +public class ArduinoUtils { + + private static Log log = LogFactory.getLog(ArduinoUtils.class); + + public static String getDeviceProperty(List deviceProperties, String propertyKey) { + String deviceProperty = ""; + for(Device.Property property :deviceProperties){ + if(propertyKey.equals(property.getName())){ + deviceProperty = property.getValue(); + } + } + return deviceProperty; + } + + public static Device.Property getProperty(String property, String value) { + if (property != null) { + Device.Property prop = new Device.Property(); + prop.setName(property); + prop.setValue(value); + return prop; + } + return null; + } + + public static void cleanupResources(Connection conn, PreparedStatement stmt, ResultSet rs) { + if (rs != null) { + try { + rs.close(); + } catch (SQLException e) { + log.warn("Error occurred while closing result set", e); + } + } + if (stmt != null) { + try { + stmt.close(); + } catch (SQLException e) { + log.warn("Error occurred while closing prepared statement", e); + } + } + if (conn != null) { + try { + conn.close(); + } catch (SQLException e) { + log.warn("Error occurred while closing database connection", e); + } + } + } + + public static void cleanupResources(PreparedStatement stmt, ResultSet rs) { + cleanupResources(null, stmt, rs); + } + + /** + * Creates the device management schema. + */ + public static void setupDeviceManagementSchema() throws ArduinoDeviceMgtPluginException { + try { + Context ctx = new InitialContext(); + DataSource dataSource = (DataSource) ctx.lookup(ArduinoConstants.DATA_SOURCE_NAME); + DeviceSchemaInitializer initializer = + new DeviceSchemaInitializer(dataSource); + log.info("Initializing device management repository database schema"); + initializer.createRegistryDatabase(); + + } catch (NamingException e) { + log.error("Error while looking up the data source: " + ArduinoConstants.DATA_SOURCE_NAME); + } catch (Exception e) { + throw new ArduinoDeviceMgtPluginException("Error occurred while initializing Iot Device " + + "Management database schema", e); + } + } + + +} diff --git a/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.plugin.impl/src/main/java/org/wso2/carbon/device/mgt/iot/arduino/plugin/impl/util/DeviceSchemaInitializer.java b/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.plugin.impl/src/main/java/org/wso2/carbon/device/mgt/iot/arduino/plugin/impl/util/DeviceSchemaInitializer.java new file mode 100644 index 0000000000..3567e776f9 --- /dev/null +++ b/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.plugin.impl/src/main/java/org/wso2/carbon/device/mgt/iot/arduino/plugin/impl/util/DeviceSchemaInitializer.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2016, 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.wso2.carbon.device.mgt.iot.arduino.plugin.impl.util; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.utils.CarbonUtils; +import org.wso2.carbon.utils.dbcreator.DatabaseCreator; + +import javax.sql.DataSource; +import java.io.File; + +/** + * Provides methods for initializing the database script. + */ +public class DeviceSchemaInitializer extends DatabaseCreator{ + + private static final Log log = LogFactory.getLog(DeviceSchemaInitializer.class); + private static final String setupSQLScriptBaseLocation = CarbonUtils.getCarbonHome() + File.separator + "dbscripts" + + File.separator + "cdm" + File.separator + "plugins" + File.separator; + + public DeviceSchemaInitializer(DataSource dataSource) { + super(dataSource); + } + + @Override + protected String getDbScriptLocation(String databaseType) { + String scriptName = databaseType + ".sql"; + if (log.isDebugEnabled()) { + log.debug("Loading database script from :" + scriptName); + } + return setupSQLScriptBaseLocation.replaceFirst("DBTYPE", databaseType) + scriptName; + } +} diff --git a/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.plugin.impl/src/main/java/org/wso2/carbon/device/mgt/iot/arduino/plugin/internal/ArduinoManagementServiceComponent.java b/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.plugin.impl/src/main/java/org/wso2/carbon/device/mgt/iot/arduino/plugin/internal/ArduinoManagementServiceComponent.java new file mode 100644 index 0000000000..fc5fe40a3b --- /dev/null +++ b/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.plugin.impl/src/main/java/org/wso2/carbon/device/mgt/iot/arduino/plugin/internal/ArduinoManagementServiceComponent.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2016, 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.wso2.carbon.device.mgt.iot.arduino.plugin.internal; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.osgi.framework.BundleContext; +import org.osgi.framework.ServiceRegistration; +import org.osgi.service.component.ComponentContext; +import org.wso2.carbon.device.mgt.common.spi.DeviceManagementService; +import org.wso2.carbon.device.mgt.iot.arduino.plugin.exception.ArduinoDeviceMgtPluginException; +import org.wso2.carbon.device.mgt.iot.arduino.plugin.impl.ArduinoManagerService; +import org.wso2.carbon.device.mgt.iot.arduino.plugin.impl.util.ArduinoUtils; +/** + * @scr.component name="org.wso2.carbon.device.mgt.iot.arduino.internal.ArduinoManagementServiceComponent" + * immediate="true" + */ +public class ArduinoManagementServiceComponent { + + private static final Log log = LogFactory.getLog(ArduinoManagementServiceComponent.class); + private ServiceRegistration arduinoServiceRegRef; + + protected void activate(ComponentContext ctx) { + if (log.isDebugEnabled()) { + log.debug("Activating Arduino Device Management Service Component"); + } + try { + BundleContext bundleContext = ctx.getBundleContext(); + arduinoServiceRegRef = + bundleContext.registerService(DeviceManagementService.class.getName(), + new ArduinoManagerService(), null); + String setupOption = System.getProperty("setup"); + if (setupOption != null) { + if (log.isDebugEnabled()) { + log.debug( + "-Dsetup is enabled. Iot Device management repository schema initialization is about " + + "to begin"); + } + try { + ArduinoUtils.setupDeviceManagementSchema(); + } catch (ArduinoDeviceMgtPluginException e) { + log.error("Exception occurred while initializing device management database schema", e); + } + } + if (log.isDebugEnabled()) { + log.debug("Arduino Device Management Service Component has been successfully activated"); + } + } catch (Throwable e) { + log.error("Error occurred while activating Arduino Device Management Service Component", e); + } + } + + protected void deactivate(ComponentContext ctx) { + if (log.isDebugEnabled()) { + log.debug("De-activating Arduino Device Management Service Component"); + } + try { + if (arduinoServiceRegRef != null) { + arduinoServiceRegRef.unregister(); + } + + if (log.isDebugEnabled()) { + log.debug( + "Arduino Device Management Service Component has been successfully de-activated"); + } + } catch (Throwable e) { + log.error("Error occurred while de-activating Arduino Device Management bundle", e); + } + } +} diff --git a/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.ui/pom.xml b/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.ui/pom.xml new file mode 100644 index 0000000000..950c0b4388 --- /dev/null +++ b/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.ui/pom.xml @@ -0,0 +1,62 @@ + + + + + + + + arduino-plugin + org.wso2.carbon.devicemgt-plugins + 2.1.0-SNAPSHOT + ../pom.xml + + + 4.0.0 + org.wso2.carbon.device.mgt.iot.arduino.ui + WSO2 Carbon - IoT Server Arduino UI + pom + + + + + maven-assembly-plugin + 2.5.5 + + ${project.artifactId}-${carbon.device.mgt.version} + false + + src/assembly/src.xml + + + + + create-archive + package + + single + + + + + + + + \ No newline at end of file diff --git a/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.ui/src/assembly/src.xml b/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.ui/src/assembly/src.xml new file mode 100644 index 0000000000..2797034e07 --- /dev/null +++ b/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.ui/src/assembly/src.xml @@ -0,0 +1,36 @@ + + + + src + + zip + + false + ${basedir}/src + + + ${basedir}/src/main/resources/jaggeryapps/devicemgt + / + true + + + \ No newline at end of file diff --git a/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.arduino.device-view/device-view.hbs b/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.arduino.device-view/device-view.hbs new file mode 100644 index 0000000000..d4294f8d2b --- /dev/null +++ b/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.arduino.device-view/device-view.hbs @@ -0,0 +1,68 @@ +{{#zone "topCss"}} + +{{/zone}} + +{{#zone "device-thumbnail"}} + +{{/zone}} + +{{#zone "device-opetations"}} +
+ Operations +
+
+ {{unit "iot.unit.device.operation-bar" device=device}} +
+{{/zone}} + +{{#zone "device-detail-properties"}} +
+ +
+
+ +
+
Device Statistics
+ {{unit "iot.unit.device.stats" device=device}} +
+
+
Operations Log
+
+ +
+
+ Not available yet +
+
+
+
+
+
+
+
+{{/zone}} \ No newline at end of file diff --git a/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.arduino.device-view/device-view.js b/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.arduino.device-view/device-view.js new file mode 100644 index 0000000000..3198cf40f7 --- /dev/null +++ b/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.arduino.device-view/device-view.js @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2016, 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("device-view.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") { + var viewModel = {}; + var deviceInfo = device.properties.DEVICE_INFO; + if (deviceInfo != undefined && String(deviceInfo.toString()).length > 0) { + deviceInfo = parse(stringify(deviceInfo)); + viewModel.system = device.properties.IMEI; + viewModel.machine = "Arduino"; + viewModel.vendor = device.properties.VENDOR; + } + device.viewModel = viewModel; + return {"device": device}; + } else { + response.sendError(404, "Device Id " + deviceId + "of type " + deviceType + " cannot be found!"); + exit(); + } + } +} \ No newline at end of file diff --git a/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.arduino.device-view/device-view.json b/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.arduino.device-view/device-view.json new file mode 100644 index 0000000000..9eecd8f5bf --- /dev/null +++ b/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.arduino.device-view/device-view.json @@ -0,0 +1,3 @@ +{ + "version": "1.0.0" +} \ No newline at end of file diff --git a/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.arduino.device-view/public/images/ardunio-icon.png b/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.arduino.device-view/public/images/ardunio-icon.png new file mode 100644 index 0000000000..d478b0e303 Binary files /dev/null and b/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.arduino.device-view/public/images/ardunio-icon.png differ diff --git a/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.arduino.device-view/public/images/thumb.png b/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.arduino.device-view/public/images/thumb.png new file mode 100644 index 0000000000..ad8170f33d Binary files /dev/null and b/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.arduino.device-view/public/images/thumb.png differ diff --git a/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.arduino.type-view/public/images/ardunio-icon.png b/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.arduino.type-view/public/images/ardunio-icon.png new file mode 100644 index 0000000000..d478b0e303 Binary files /dev/null and b/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.arduino.type-view/public/images/ardunio-icon.png differ diff --git a/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.arduino.type-view/public/images/myDevices_analytics.png b/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.arduino.type-view/public/images/myDevices_analytics.png new file mode 100644 index 0000000000..69c96ea1c8 Binary files /dev/null and b/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.arduino.type-view/public/images/myDevices_analytics.png differ diff --git a/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.arduino.type-view/public/images/schematicsGuide.png b/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.arduino.type-view/public/images/schematicsGuide.png new file mode 100644 index 0000000000..d2d1466abf Binary files /dev/null and b/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.arduino.type-view/public/images/schematicsGuide.png differ diff --git a/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.arduino.type-view/public/images/thumb.png b/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.arduino.type-view/public/images/thumb.png new file mode 100644 index 0000000000..ad8170f33d Binary files /dev/null and b/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.arduino.type-view/public/images/thumb.png differ diff --git a/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.arduino.type-view/public/js/download.js b/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.arduino.type-view/public/js/download.js new file mode 100644 index 0000000000..0c22e50997 --- /dev/null +++ b/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.arduino.type-view/public/js/download.js @@ -0,0 +1,184 @@ +/* + * Copyright (c) 2016, 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. + */ + +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; + } + }); +} + +/* + * 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() { + var deviceName; + $('.new-device-name').each(function () { + if (this.value != "") { + deviceName = this.value; + } + }); + var deviceNameFormat = /^[^~?!#$:;%^*`+={}\[\]\\()|<>,'"]{1,30}$/; + if (deviceName && deviceNameFormat.test(deviceName)) { + $('#downloadForm').submit(); + hidePopup(); + $(modalPopupContent).html($('#device-agent-downloading-content').html()); + showPopup(); + setTimeout(function () { + hidePopup(); + }, 1000); + }else { + $("#invalid-username-error-msg span").text("Invalid device name"); + $("#invalid-username-error-msg").removeClass("hidden"); + } +} + +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/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.arduino.type-view/public/js/jquery.validate.js b/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.arduino.type-view/public/js/jquery.validate.js new file mode 100644 index 0000000000..fe7ecf07a6 --- /dev/null +++ b/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.arduino.type-view/public/js/jquery.validate.js @@ -0,0 +1,1227 @@ +/* + * Copyright (c) 2016, 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($) { + +$.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/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.arduino.type-view/type-view.hbs b/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.arduino.type-view/type-view.hbs new file mode 100644 index 0000000000..1d34030f0d --- /dev/null +++ b/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.arduino.type-view/type-view.hbs @@ -0,0 +1,336 @@ +
+

Arduino

+
+
+ +
+ +
+ +
+ +
+ +
+

What it Does

+
+

Connect Arduino UNO board to WSO2 IoT Server and visualize sensor + data.

+
+

What You Need

+
+
    +
  • + ITEM 01 +    Arduino UNO Board. +
  • +
  • + ITEM 02 +    Adafruit Wifi Shield for Arduino. +
  • +
  • + ITEM 03 +    LED bulb connected to Pin 13. + (If not available, will use the one on the board.) +
  • +
  • + ITEM 04 +    Resister( e.g 330 ohms ) +
  • +
  • + STEP 05 +    Proceed to [Prepare] section. +
+
+ + View API + + + Download Sketch + +
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+ +
+ +
+ +

+
+ +
+

Prepare

+
+
    +
  • + 01 +    Wifi-Shield mounted onto the Arduino-UNO board +
  • +
  • + 02 +    Connect LED bulb to arduino as follows +

    + + + +
    +
  • + +
  • + 03 +    Download Arduino-Sketch from [Download Sketch] link above. +
  • +
  • + 04 +    Unzip the downloaded Arduino Agent +
  • +
  • + 05 +    Create a folder called "ArduinoBoardSketch" and move all source files + inside +
  • +
  • + 06 +    Open ArduinoBoardSketch.h and provide appropriate values for + [WLAN_SSID], + [WLAN_PASS], + [SERVICE_PORT], + [server] and + [deviceIP] + variables according to your network. +
  • +
  • + 07 +    Burn the sketch onto your Arduino board and let the program run. +
  • +
  • +                + + + + +    Arduino will publish it's internal temperature to WSO2-IoT-Server +

    +                + + + + +    LED on PIN 13 can be controlled from Device Management page. +
  • +
+
+
+ +
+
+

Try Out

+
+
    +
  • + 01 +    You can view all your connected devices at + [Device Management] page. +
  • +
  • + 02 +    Select one of connected devices and check for available control + operations and monitor Real-Time data. +
  • +
  • + 03 +    You can also view analytics of the data published to IoT-Server by + navigating to Device Analytics page. +
  • +
+
+
+
+

Click on the image to zoom

+
+ + + +
+
+
+ +{{#zone "topCss"}} + +{{/zone}} + +{{#zone "bottomJs"}} + {{js "/js/download.js"}} + {{js "/js/jquery.validate.js"}} + +{{/zone}} +Status API Training Shop Blog About Pricing diff --git a/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.arduino.type-view/type-view.json b/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.arduino.type-view/type-view.json new file mode 100644 index 0000000000..9eecd8f5bf --- /dev/null +++ b/components/iot-plugins/arduino-plugin/org.wso2.carbon.device.mgt.iot.arduino.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.arduino.type-view/type-view.json @@ -0,0 +1,3 @@ +{ + "version": "1.0.0" +} \ No newline at end of file diff --git a/components/iot-plugins/arduino-plugin/pom.xml b/components/iot-plugins/arduino-plugin/pom.xml new file mode 100644 index 0000000000..9e08a4120d --- /dev/null +++ b/components/iot-plugins/arduino-plugin/pom.xml @@ -0,0 +1,63 @@ + + + + + + + org.wso2.carbon.devicemgt-plugins + iot-plugins + 2.1.0-SNAPSHOT + ../pom.xml + + + 4.0.0 + org.wso2.carbon.devicemgt-plugins + arduino-plugin + pom + WSO2 Carbon - Arduino Plugin + http://wso2.org + + + org.wso2.carbon.device.mgt.iot.arduino.analytics + org.wso2.carbon.device.mgt.iot.arduino.controller.service.impl + org.wso2.carbon.device.mgt.iot.arduino.manager.service.impl + org.wso2.carbon.device.mgt.iot.arduino.plugin.impl + org.wso2.carbon.device.mgt.iot.arduino.ui + + + + + + + org.apache.felix + maven-scr-plugin + 1.7.2 + + + generate-scr-scrdescriptor + + scr + + + + + + + + diff --git a/components/iot-plugins/camera-plugin/org.wso2.carbon.device.mgt.iot.camera.controller.service.impl/pom.xml b/components/iot-plugins/camera-plugin/org.wso2.carbon.device.mgt.iot.camera.controller.service.impl/pom.xml new file mode 100644 index 0000000000..b1930e8f0a --- /dev/null +++ b/components/iot-plugins/camera-plugin/org.wso2.carbon.device.mgt.iot.camera.controller.service.impl/pom.xml @@ -0,0 +1,261 @@ + + + + + + camera-plugin + org.wso2.carbon.devicemgt-plugins + 2.1.0-SNAPSHOT + ../pom.xml + + + 4.0.0 + org.wso2.carbon.device.mgt.iot.camera.controller.service.impl + war + WSO2 Carbon - IoT Server Camera API + WSO2 Carbon - Camera Service Controller API Implementation + http://wso2.org + + + + + org.wso2.carbon.devicemgt + org.wso2.carbon.device.mgt.common + provided + + + org.wso2.carbon.devicemgt + org.wso2.carbon.device.mgt.core + provided + + + org.apache.axis2.wso2 + axis2-client + + + + + + org.wso2.carbon.devicemgt + org.wso2.carbon.device.mgt.analytics + provided + + + org.apache.axis2.wso2 + axis2-client + + + + + + org.wso2.carbon.devicemgt + org.wso2.carbon.certificate.mgt.core + provided + + + commons-codec.wso2 + commons-codec + + + + + + + + org.apache.cxf + cxf-rt-frontend-jaxws + provided + + + org.apache.cxf + cxf-rt-frontend-jaxrs + provided + + + org.apache.cxf + cxf-rt-transports-http + provided + + + + + org.eclipse.paho + org.eclipse.paho.client.mqttv3 + provided + + + + + org.apache.httpcomponents + httpasyncclient + 4.1 + provided + + + org.wso2.carbon.devicemgt-plugins + org.wso2.carbon.device.mgt.iot + provided + + + org.wso2.carbon.devicemgt-plugins + org.wso2.carbon.device.mgt.iot.camera.plugin.impl + provided + + + + + org.codehaus.jackson + jackson-core-asl + + + org.codehaus.jackson + jackson-jaxrs + + + javax + javaee-web-api + provided + + + javax.ws.rs + jsr311-api + provided + + + commons-httpclient.wso2 + commons-httpclient + provided + + + + org.wso2.carbon + org.wso2.carbon.utils + provided + + + 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.wso2 + commons-codec + + + + org.wso2.carbon.devicemgt + org.wso2.carbon.apimgt.annotations + provided + + + + org.igniterealtime.smack.wso2 + smack + provided + + + org.igniterealtime.smack.wso2 + smackx + provided + + + org.wso2.carbon.devicemgt + org.wso2.carbon.apimgt.webapp.publisher + provided + + + + + + + + + maven-compiler-plugin + + UTF-8 + ${wso2.maven.compiler.source} + ${wso2.maven.compiler.target} + + + + maven-war-plugin + + camera + + + + + + diff --git a/components/iot-plugins/camera-plugin/org.wso2.carbon.device.mgt.iot.camera.manager.service.impl/pom.xml b/components/iot-plugins/camera-plugin/org.wso2.carbon.device.mgt.iot.camera.manager.service.impl/pom.xml new file mode 100644 index 0000000000..98667f028a --- /dev/null +++ b/components/iot-plugins/camera-plugin/org.wso2.carbon.device.mgt.iot.camera.manager.service.impl/pom.xml @@ -0,0 +1,261 @@ + + + + + + camera-plugin + org.wso2.carbon.devicemgt-plugins + 2.1.0-SNAPSHOT + ../pom.xml + + + 4.0.0 + org.wso2.carbon.device.mgt.iot.camera.manager.service.impl + war + WSO2 Carbon - IoT Server Camera API + WSO2 Carbon - Camera Service Management API Implementation + http://wso2.org + + + + + org.wso2.carbon.devicemgt + org.wso2.carbon.device.mgt.common + provided + + + org.wso2.carbon.devicemgt + org.wso2.carbon.device.mgt.core + provided + + + org.apache.axis2.wso2 + axis2-client + + + + + + org.wso2.carbon.devicemgt + org.wso2.carbon.device.mgt.analytics + provided + + + org.apache.axis2.wso2 + axis2-client + + + + + + org.wso2.carbon.devicemgt + org.wso2.carbon.certificate.mgt.core + provided + + + commons-codec.wso2 + commons-codec + + + + + + + + org.apache.cxf + cxf-rt-frontend-jaxws + provided + + + org.apache.cxf + cxf-rt-frontend-jaxrs + provided + + + org.apache.cxf + cxf-rt-transports-http + provided + + + + + org.eclipse.paho + org.eclipse.paho.client.mqttv3 + provided + + + + + org.apache.httpcomponents + httpasyncclient + 4.1 + provided + + + org.wso2.carbon.devicemgt-plugins + org.wso2.carbon.device.mgt.iot + provided + + + org.wso2.carbon.devicemgt-plugins + org.wso2.carbon.device.mgt.iot.camera.plugin.impl + provided + + + + + org.codehaus.jackson + jackson-core-asl + + + org.codehaus.jackson + jackson-jaxrs + + + javax + javaee-web-api + provided + + + javax.ws.rs + jsr311-api + provided + + + commons-httpclient.wso2 + commons-httpclient + provided + + + + org.wso2.carbon + org.wso2.carbon.utils + provided + + + 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.wso2 + commons-codec + + + + org.wso2.carbon.devicemgt + org.wso2.carbon.apimgt.annotations + provided + + + + org.igniterealtime.smack.wso2 + smack + provided + + + org.igniterealtime.smack.wso2 + smackx + provided + + + org.wso2.carbon.devicemgt + org.wso2.carbon.apimgt.webapp.publisher + provided + + + + + + + + + maven-compiler-plugin + + UTF-8 + ${wso2.maven.compiler.source} + ${wso2.maven.compiler.target} + + + + maven-war-plugin + + camera_mgt + + + + + + diff --git a/components/iot-plugins/camera-plugin/org.wso2.carbon.device.mgt.iot.camera.plugin.impl/pom.xml b/components/iot-plugins/camera-plugin/org.wso2.carbon.device.mgt.iot.camera.plugin.impl/pom.xml new file mode 100644 index 0000000000..bd3c82af6d --- /dev/null +++ b/components/iot-plugins/camera-plugin/org.wso2.carbon.device.mgt.iot.camera.plugin.impl/pom.xml @@ -0,0 +1,123 @@ + + + + + + + camera-plugin + org.wso2.carbon.devicemgt-plugins + 2.1.0-SNAPSHOT + ../pom.xml + + + 4.0.0 + org.wso2.carbon.device.mgt.iot.camera.plugin.impl + bundle + WSO2 Carbon - IoT Server Camera Management Plugin + WSO2 Carbon - Camera Management/Control Plugin Implementation + http://wso2.org + + + + + org.apache.felix + maven-scr-plugin + + + maven-compiler-plugin + + 1.7 + 1.7 + + 2.3.2 + + + org.apache.felix + maven-bundle-plugin + 1.4.0 + true + + + ${project.artifactId} + ${project.artifactId} + ${carbon.devicemgt.plugins.version} + IoT Server Camera Impl Bundle + org.wso2.carbon.device.mgt.iot.camera.plugin.internal + + org.osgi.framework, + org.osgi.service.component, + org.apache.commons.logging, + javax.xml.bind.*, + javax.naming, + javax.sql, + javax.xml.bind.annotation.*, + javax.xml.parsers, + javax.net, + javax.net.ssl, + org.w3c.dom, + org.wso2.carbon.device.mgt.common.*, + org.wso2.carbon.device.mgt.common, + org.wso2.carbon.context.*, + org.wso2.carbon.ndatasource.core, + org.wso2.carbon.device.mgt.iot.*, + + + + !org.wso2.carbon.device.mgt.iot.camera.plugin.internal, + org.wso2.carbon.device.mgt.iot.camera.plugin.* + + + + + + + + + + org.eclipse.osgi + org.eclipse.osgi + + + org.eclipse.osgi + org.eclipse.osgi.services + + + + org.wso2.carbon + org.wso2.carbon.logging + + + + org.wso2.carbon.devicemgt + org.wso2.carbon.device.mgt.common + + + org.wso2.carbon + org.wso2.carbon.ndatasource.core + + + + org.wso2.carbon.devicemgt-plugins + org.wso2.carbon.device.mgt.iot + + + + + \ No newline at end of file diff --git a/components/iot-plugins/camera-plugin/org.wso2.carbon.device.mgt.iot.camera.plugin.impl/src/main/java/org/wso2/carbon/device/mgt/iot/camera/plugin/constants/CameraConstants.java b/components/iot-plugins/camera-plugin/org.wso2.carbon.device.mgt.iot.camera.plugin.impl/src/main/java/org/wso2/carbon/device/mgt/iot/camera/plugin/constants/CameraConstants.java new file mode 100644 index 0000000000..fa9b91d83e --- /dev/null +++ b/components/iot-plugins/camera-plugin/org.wso2.carbon.device.mgt.iot.camera.plugin.impl/src/main/java/org/wso2/carbon/device/mgt/iot/camera/plugin/constants/CameraConstants.java @@ -0,0 +1,27 @@ +/* + * 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.wso2.carbon.device.mgt.iot.camera.plugin.constants; + +public class CameraConstants { + public final static String DEVICE_TYPE = "camera"; + public final static String DEVICE_PLUGIN_DEVICE_NAME = "DEVICE_NAME"; + public final static String DEVICE_PLUGIN_DEVICE_ID = "CAMERA_DEVICE_ID"; + public final static String STATE_ON = "ON"; + public final static String STATE_OFF = "OFF"; +} diff --git a/components/iot-plugins/camera-plugin/org.wso2.carbon.device.mgt.iot.camera.plugin.impl/src/main/java/org/wso2/carbon/device/mgt/iot/camera/plugin/impl/CameraManager.java b/components/iot-plugins/camera-plugin/org.wso2.carbon.device.mgt.iot.camera.plugin.impl/src/main/java/org/wso2/carbon/device/mgt/iot/camera/plugin/impl/CameraManager.java new file mode 100644 index 0000000000..1498a4b58c --- /dev/null +++ b/components/iot-plugins/camera-plugin/org.wso2.carbon.device.mgt.iot.camera.plugin.impl/src/main/java/org/wso2/carbon/device/mgt/iot/camera/plugin/impl/CameraManager.java @@ -0,0 +1,281 @@ +/* + * 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.wso2.carbon.device.mgt.iot.camera.plugin.impl; + + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +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.DeviceManager; +import org.wso2.carbon.device.mgt.common.EnrolmentInfo; +import org.wso2.carbon.device.mgt.common.FeatureManager; +import org.wso2.carbon.device.mgt.common.configuration.mgt.TenantConfiguration; +import org.wso2.carbon.device.mgt.common.license.mgt.License; +import org.wso2.carbon.device.mgt.common.license.mgt.LicenseManagementException; +import org.wso2.carbon.device.mgt.iot.camera.plugin.impl.dao.CameraDAO; +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.dto.IotDevice; +import org.wso2.carbon.device.mgt.iot.util.iotdevice.util.IotDeviceManagementUtil; + +import java.util.ArrayList; +import java.util.List; + + +/** + * This represents the FireAlarm implementation of DeviceManagerService. + */ +public class CameraManager implements DeviceManager { + + private static final IotDeviceManagementDAOFactory iotDeviceManagementDAOFactory = new CameraDAO(); + private static final Log log = LogFactory.getLog(CameraManager.class); + + + @Override + public FeatureManager getFeatureManager() { + return null; + } + + @Override + public boolean saveConfiguration(TenantConfiguration tenantConfiguration) + throws DeviceManagementException { + //TODO implement this + return false; + } + + @Override + public TenantConfiguration getConfiguration() throws DeviceManagementException { + //TODO implement this + return null; + } + + @Override + public boolean enrollDevice(Device device) throws DeviceManagementException { + boolean status; + IotDevice iotDevice = IotDeviceManagementUtil.convertToIotDevice(device); + try { + if (log.isDebugEnabled()) { + log.debug("Enrolling a new Camera device : " + device.getDeviceIdentifier()); + } + CameraDAO.beginTransaction(); + status = iotDeviceManagementDAOFactory.getIotDeviceDAO().addIotDevice( + iotDevice); + CameraDAO.commitTransaction(); + } catch (IotDeviceManagementDAOException e) { + try { + CameraDAO.rollbackTransaction(); + } catch (IotDeviceManagementDAOException iotDAOEx) { + String msg = "Error occurred while roll back the device enrol transaction :" + device.toString(); + log.warn(msg, iotDAOEx); + } + String msg = "Error while enrolling the Camera device : " + device.getDeviceIdentifier(); + log.error(msg, e); + throw new DeviceManagementException(msg, e); + } + return status; + } + + @Override + public boolean modifyEnrollment(Device device) throws DeviceManagementException { + boolean status; + IotDevice iotDevice = IotDeviceManagementUtil.convertToIotDevice(device); + try { + if (log.isDebugEnabled()) { + log.debug("Modifying the Camera device enrollment data"); + } + CameraDAO.beginTransaction(); + status = iotDeviceManagementDAOFactory.getIotDeviceDAO() + .updateIotDevice(iotDevice); + CameraDAO.commitTransaction(); + } catch (IotDeviceManagementDAOException e) { + try { + CameraDAO.rollbackTransaction(); + } catch (IotDeviceManagementDAOException iotDAOEx) { + String msg = "Error occurred while roll back the update device transaction :" + device.toString(); + log.warn(msg, iotDAOEx); + } + String msg = "Error while updating the enrollment of the Camera device : " + + device.getDeviceIdentifier(); + log.error(msg, e); + throw new DeviceManagementException(msg, e); + } + return status; + } + + @Override + public boolean disenrollDevice(DeviceIdentifier deviceId) throws DeviceManagementException { + boolean status; + try { + if (log.isDebugEnabled()) { + log.debug("Dis-enrolling Camera device : " + deviceId); + } + CameraDAO.beginTransaction(); + status = iotDeviceManagementDAOFactory.getIotDeviceDAO() + .deleteIotDevice(deviceId.getId()); + CameraDAO.commitTransaction(); + } catch (IotDeviceManagementDAOException e) { + try { + CameraDAO.rollbackTransaction(); + } catch (IotDeviceManagementDAOException iotDAOEx) { + String msg = "Error occurred while roll back the device dis enrol transaction :" + deviceId.toString(); + log.warn(msg, iotDAOEx); + } + String msg = "Error while removing the Camera device : " + deviceId.getId(); + log.error(msg, e); + throw new DeviceManagementException(msg, e); + } + return status; + } + + @Override + public boolean isEnrolled(DeviceIdentifier deviceId) throws DeviceManagementException { + boolean isEnrolled = false; + try { + if (log.isDebugEnabled()) { + log.debug("Checking the enrollment of Camera device : " + deviceId.getId()); + } + IotDevice iotDevice = + iotDeviceManagementDAOFactory.getIotDeviceDAO().getIotDevice( + deviceId.getId()); + if (iotDevice != null) { + isEnrolled = true; + } + } catch (IotDeviceManagementDAOException e) { + String msg = "Error while checking the enrollment status of Camera device : " + + deviceId.getId(); + log.error(msg, e); + throw new DeviceManagementException(msg, e); + } + return isEnrolled; + } + + @Override + public boolean isActive(DeviceIdentifier deviceId) throws DeviceManagementException { + return true; + } + + @Override + public boolean setActive(DeviceIdentifier deviceId, boolean status) + throws DeviceManagementException { + return true; + } + + @Override + public Device getDevice(DeviceIdentifier deviceId) throws DeviceManagementException { + Device device; + try { + if (log.isDebugEnabled()) { + log.debug("Getting the details of Camera device : " + deviceId.getId()); + } + IotDevice iotDevice = iotDeviceManagementDAOFactory.getIotDeviceDAO(). + getIotDevice(deviceId.getId()); + device = IotDeviceManagementUtil.convertToDevice(iotDevice); + } catch (IotDeviceManagementDAOException e) { + String msg = "Error while fetching the Camera device : " + deviceId.getId(); + log.error(msg, e); + throw new DeviceManagementException(msg, e); + } + return device; + } + + @Override + public boolean setOwnership(DeviceIdentifier deviceId, String ownershipType) + throws DeviceManagementException { + return true; + } + + public boolean isClaimable(DeviceIdentifier deviceIdentifier) throws DeviceManagementException { + return false; + } + + @Override + public boolean setStatus(DeviceIdentifier deviceId, String currentOwner, + EnrolmentInfo.Status status) throws DeviceManagementException { + return false; + } + + @Override + public License getLicense(String s) throws LicenseManagementException { + return null; + } + + @Override + public void addLicense(License license) throws LicenseManagementException { + + } + + @Override + public boolean requireDeviceAuthorization() { + return false; + } + + @Override + public boolean updateDeviceInfo(DeviceIdentifier deviceIdentifier, Device device) throws DeviceManagementException { + boolean status; + IotDevice iotDevice = IotDeviceManagementUtil.convertToIotDevice(device); + try { + if (log.isDebugEnabled()) { + log.debug( + "updating the details of Camera device : " + deviceIdentifier); + } + CameraDAO.beginTransaction(); + status = iotDeviceManagementDAOFactory.getIotDeviceDAO() + .updateIotDevice(iotDevice); + CameraDAO.commitTransaction(); + } catch (IotDeviceManagementDAOException e) { + try { + CameraDAO.rollbackTransaction(); + } catch (IotDeviceManagementDAOException iotDAOEx) { + String msg = "Error occurred while roll back the update device info transaction :" + device.toString(); + log.warn(msg, iotDAOEx); + } + String msg = + "Error while updating the Camera device : " + deviceIdentifier; + log.error(msg, e); + throw new DeviceManagementException(msg, e); + } + return status; + } + + @Override + public List getAllDevices() throws DeviceManagementException { + List devices = null; + try { + if (log.isDebugEnabled()) { + log.debug("Fetching the details of all Camera devices"); + } + List iotDevices = + iotDeviceManagementDAOFactory.getIotDeviceDAO().getAllIotDevices(); + if (iotDevices != null) { + devices = new ArrayList(); + for (IotDevice iotDevice : iotDevices) { + devices.add(IotDeviceManagementUtil.convertToDevice(iotDevice)); + } + } + } catch (IotDeviceManagementDAOException e) { + String msg = "Error while fetching all Camera devices."; + log.error(msg, e); + throw new DeviceManagementException(msg, e); + } + return devices; + } + +} \ No newline at end of file diff --git a/components/iot-plugins/camera-plugin/org.wso2.carbon.device.mgt.iot.camera.plugin.impl/src/main/java/org/wso2/carbon/device/mgt/iot/camera/plugin/impl/CameraManagerService.java b/components/iot-plugins/camera-plugin/org.wso2.carbon.device.mgt.iot.camera.plugin.impl/src/main/java/org/wso2/carbon/device/mgt/iot/camera/plugin/impl/CameraManagerService.java new file mode 100644 index 0000000000..a6241b940b --- /dev/null +++ b/components/iot-plugins/camera-plugin/org.wso2.carbon.device.mgt.iot.camera.plugin.impl/src/main/java/org/wso2/carbon/device/mgt/iot/camera/plugin/impl/CameraManagerService.java @@ -0,0 +1,112 @@ +/* + * 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.wso2.carbon.device.mgt.iot.camera.plugin.impl; + +import org.wso2.carbon.device.mgt.common.DeviceIdentifier; +import org.wso2.carbon.device.mgt.common.DeviceManagementException; +import org.wso2.carbon.device.mgt.common.DeviceManager; +import org.wso2.carbon.device.mgt.common.app.mgt.Application; +import org.wso2.carbon.device.mgt.common.app.mgt.ApplicationManagementException; +import org.wso2.carbon.device.mgt.common.app.mgt.ApplicationManager; +import org.wso2.carbon.device.mgt.common.operation.mgt.Operation; +import org.wso2.carbon.device.mgt.common.spi.DeviceManagementService; +import org.wso2.carbon.device.mgt.iot.camera.plugin.constants.CameraConstants; + +import java.util.List; + +public class CameraManagerService implements DeviceManagementService{ + private DeviceManager deviceManager; + @Override + public String getType() { + return CameraConstants.DEVICE_TYPE; + } + + + @Override + public String getProviderTenantDomain() { + return "carbon.super"; + } + + @Override + public boolean isSharedWithAllTenants() { + return true; + } + + @Override + public String[] getSharedTenantsDomain() { + return new String[0]; + } + + @Override + public void init() throws DeviceManagementException { + this.deviceManager=new CameraManager(); + } + + @Override + public DeviceManager getDeviceManager() { + return deviceManager; + } + + @Override + public ApplicationManager getApplicationManager() { + return null; + } + + @Override + public void notifyOperationToDevices(Operation operation, List deviceIds) + throws DeviceManagementException { + + } + + @Override + public Application[] getApplications(String domain, int pageNumber, int size) + throws ApplicationManagementException { + return new Application[0]; + } + + @Override + public void updateApplicationStatus(DeviceIdentifier deviceId, Application application, + String status) throws ApplicationManagementException { + + } + + @Override + public String getApplicationStatus(DeviceIdentifier deviceId, Application application) + throws ApplicationManagementException { + return null; + } + + @Override + public void installApplicationForDevices(Operation operation, List deviceIdentifiers) + throws ApplicationManagementException { + + } + + @Override + public void installApplicationForUsers(Operation operation, List userNameList) + throws ApplicationManagementException { + + } + + @Override + public void installApplicationForUserRoles(Operation operation, List userRoleList) + throws ApplicationManagementException { + + } +} diff --git a/components/iot-plugins/camera-plugin/org.wso2.carbon.device.mgt.iot.camera.plugin.impl/src/main/java/org/wso2/carbon/device/mgt/iot/camera/plugin/impl/dao/CameraDAO.java b/components/iot-plugins/camera-plugin/org.wso2.carbon.device.mgt.iot.camera.plugin.impl/src/main/java/org/wso2/carbon/device/mgt/iot/camera/plugin/impl/dao/CameraDAO.java new file mode 100644 index 0000000000..35571d851c --- /dev/null +++ b/components/iot-plugins/camera-plugin/org.wso2.carbon.device.mgt.iot.camera.plugin.impl/src/main/java/org/wso2/carbon/device/mgt/iot/camera/plugin/impl/dao/CameraDAO.java @@ -0,0 +1,121 @@ +/* + * 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.wso2.carbon.device.mgt.iot.camera.plugin.impl.dao; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.device.mgt.iot.camera.plugin.constants.CameraConstants; +import org.wso2.carbon.device.mgt.iot.camera.plugin.impl.dao.impl.CameraDeviceDAOImpl; +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 CameraDAO extends IotDeviceManagementDAOFactory implements IotDeviceManagementDAOFactoryInterface { + + private static final Log log = LogFactory.getLog(CameraDAO.class); + static DataSource dataSource; + private static ThreadLocal currentConnection = new ThreadLocal(); + + public CameraDAO() { + initFireAlarmDAO(); + } + + public static void initFireAlarmDAO() { + dataSource = getDataSourceMap().get(CameraConstants.DEVICE_TYPE); + } + + @Override public IotDeviceDAO getIotDeviceDAO() { + return new CameraDeviceDAOImpl(); + } + + 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/components/iot-plugins/camera-plugin/org.wso2.carbon.device.mgt.iot.camera.plugin.impl/src/main/java/org/wso2/carbon/device/mgt/iot/camera/plugin/impl/dao/impl/CameraDeviceDAOImpl.java b/components/iot-plugins/camera-plugin/org.wso2.carbon.device.mgt.iot.camera.plugin.impl/src/main/java/org/wso2/carbon/device/mgt/iot/camera/plugin/impl/dao/impl/CameraDeviceDAOImpl.java new file mode 100644 index 0000000000..59e68335b1 --- /dev/null +++ b/components/iot-plugins/camera-plugin/org.wso2.carbon.device.mgt.iot.camera.plugin.impl/src/main/java/org/wso2/carbon/device/mgt/iot/camera/plugin/impl/dao/impl/CameraDeviceDAOImpl.java @@ -0,0 +1,237 @@ +/* + * 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.wso2.carbon.device.mgt.iot.camera.plugin.impl.dao.impl; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.device.mgt.iot.camera.plugin.constants.CameraConstants; +import org.wso2.carbon.device.mgt.iot.camera.plugin.impl.dao.CameraDAO; +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 camera Devices. + */ +public class CameraDeviceDAOImpl implements IotDeviceDAO{ + + + private static final Log log = LogFactory.getLog(CameraDeviceDAOImpl.class); + + @Override + public IotDevice getIotDevice(String iotDeviceId) + throws IotDeviceManagementDAOException { + Connection conn = null; + PreparedStatement stmt = null; + IotDevice iotDevice = null; + ResultSet resultSet = null; + try { + conn = CameraDAO.getConnection(); + String selectDBQuery = + "SELECT CAMERA_DEVICE_ID, DEVICE_NAME" + + " FROM CAMERA_DEVICE WHERE CAMERA_DEVICE_ID = ?"; + stmt = conn.prepareStatement(selectDBQuery); + stmt.setString(1, iotDeviceId); + resultSet = stmt.executeQuery(); + + if (resultSet.next()) { + iotDevice = new IotDevice(); + iotDevice.setIotDeviceName(resultSet.getString( + CameraConstants.DEVICE_PLUGIN_DEVICE_NAME)); + Map propertyMap = new HashMap(); + + + + iotDevice.setDeviceProperties(propertyMap); + + if (log.isDebugEnabled()) { + log.debug("Camera device " + iotDeviceId + " data has been fetched from " + + "Camera database."); + } + } + } catch (SQLException e) { + String msg = "Error occurred while fetching Camera device : '" + iotDeviceId + "'"; + log.error(msg, e); + throw new IotDeviceManagementDAOException(msg, e); + } finally { + IotDeviceManagementDAOUtil.cleanupResources(stmt, resultSet); + CameraDAO.closeConnection(); + } + + return iotDevice; + } + + @Override + public boolean addIotDevice(IotDevice iotDevice) + throws IotDeviceManagementDAOException { + boolean status = false; + Connection conn = null; + PreparedStatement stmt = null; + try { + conn = CameraDAO.getConnection(); + String createDBQuery = + "INSERT INTO CAMERA_DEVICE(CAMERA_DEVICE_ID, DEVICE_NAME) VALUES (?, ?)"; + + stmt = conn.prepareStatement(createDBQuery); + stmt.setString(1, iotDevice.getIotDeviceId()); + stmt.setString(2,iotDevice.getIotDeviceName()); + if (iotDevice.getDeviceProperties() == null) { + iotDevice.setDeviceProperties(new HashMap()); + } + + + int rows = stmt.executeUpdate(); + if (rows > 0) { + status = true; + if (log.isDebugEnabled()) { + log.debug("Camera device " + iotDevice.getIotDeviceId() + " data has been" + + " added to the Camere database."); + } + } + } catch (SQLException e) { + String msg = "Error occurred while adding the Camera device '" + + iotDevice.getIotDeviceId() + "' to the Camera 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 = CameraDAO.getConnection(); + String updateDBQuery = + "UPDATE CAMERA_DEVICE SET DEVICE_NAME = ? WHERE CAMERA_DEVICE_ID = ?"; + + stmt = conn.prepareStatement(updateDBQuery); + + if (iotDevice.getDeviceProperties() == null) { + iotDevice.setDeviceProperties(new HashMap()); + } + stmt.setString(1, iotDevice.getIotDeviceName()); + + stmt.setString(2, iotDevice.getIotDeviceId()); + int rows = stmt.executeUpdate(); + if (rows > 0) { + status = true; + if (log.isDebugEnabled()) { + log.debug("Camera device " + iotDevice.getIotDeviceId() + " data has been" + + " modified."); + } + } + } catch (SQLException e) { + String msg = "Error occurred while modifying the Camera 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 = CameraDAO.getConnection(); + String deleteDBQuery = + "DELETE FROM CAMERA_DEVICE WHERE CAMERA_DEVICE_ID = ?"; + stmt = conn.prepareStatement(deleteDBQuery); + stmt.setString(1, iotDeviceId); + int rows = stmt.executeUpdate(); + if (rows > 0) { + status = true; + if (log.isDebugEnabled()) { + log.debug("Camera device " + iotDeviceId + " data has deleted from the Camera database."); + } + } + } catch (SQLException e) { + String msg = "Error occurred while deleting Camera 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 = CameraDAO.getConnection(); + String selectDBQuery = + "SELECT CAMERA_DEVICE_ID, DEVICE_NAME " + + "FROM CAMERA_DEVICE"; + stmt = conn.prepareStatement(selectDBQuery); + resultSet = stmt.executeQuery(); + while (resultSet.next()) { + iotDevice = new IotDevice(); + iotDevice.setIotDeviceId(resultSet.getString(CameraConstants.DEVICE_PLUGIN_DEVICE_ID)); + iotDevice.setIotDeviceName(resultSet.getString(CameraConstants.DEVICE_PLUGIN_DEVICE_NAME)); + + Map propertyMap = new HashMap(); + + iotDevice.setDeviceProperties(propertyMap); + iotDevices.add(iotDevice); + } + if (log.isDebugEnabled()) { + log.debug("All Camera device details have fetched from Firealarm database."); + } + return iotDevices; + } catch (SQLException e) { + String msg = "Error occurred while fetching all Camera device data'"; + log.error(msg, e); + throw new IotDeviceManagementDAOException(msg, e); + } finally { + IotDeviceManagementDAOUtil.cleanupResources(stmt, resultSet); + CameraDAO.closeConnection(); + } + + } + + } \ No newline at end of file diff --git a/components/iot-plugins/camera-plugin/org.wso2.carbon.device.mgt.iot.camera.plugin.impl/src/main/java/org/wso2/carbon/device/mgt/iot/camera/plugin/impl/util/VirtualFireAlarmUtils.java b/components/iot-plugins/camera-plugin/org.wso2.carbon.device.mgt.iot.camera.plugin.impl/src/main/java/org/wso2/carbon/device/mgt/iot/camera/plugin/impl/util/VirtualFireAlarmUtils.java new file mode 100644 index 0000000000..b47c6d84a2 --- /dev/null +++ b/components/iot-plugins/camera-plugin/org.wso2.carbon.device.mgt.iot.camera.plugin.impl/src/main/java/org/wso2/carbon/device/mgt/iot/camera/plugin/impl/util/VirtualFireAlarmUtils.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.wso2.carbon.device.mgt.iot.camera.plugin.impl.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 VirtualFireAlarmUtils { + + private static Log log = LogFactory.getLog(VirtualFireAlarmUtils.class); + + public static String getDeviceProperty(Map deviceProperties, String property) { + + String deviceProperty = deviceProperties.get(property); + + if (deviceProperty == null) { + return ""; + } + + return deviceProperty; + } + + +} diff --git a/components/iot-plugins/camera-plugin/org.wso2.carbon.device.mgt.iot.camera.plugin.impl/src/main/java/org/wso2/carbon/device/mgt/iot/camera/plugin/internal/CameraManagementServiceComponent.java b/components/iot-plugins/camera-plugin/org.wso2.carbon.device.mgt.iot.camera.plugin.impl/src/main/java/org/wso2/carbon/device/mgt/iot/camera/plugin/internal/CameraManagementServiceComponent.java new file mode 100644 index 0000000000..3546dd987d --- /dev/null +++ b/components/iot-plugins/camera-plugin/org.wso2.carbon.device.mgt.iot.camera.plugin.impl/src/main/java/org/wso2/carbon/device/mgt/iot/camera/plugin/internal/CameraManagementServiceComponent.java @@ -0,0 +1,98 @@ +/* + * 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.wso2.carbon.device.mgt.iot.camera.plugin.internal; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.osgi.framework.BundleContext; +import org.osgi.framework.ServiceRegistration; +import org.osgi.service.component.ComponentContext; +import org.wso2.carbon.device.mgt.common.spi.DeviceManagementService; +import org.wso2.carbon.device.mgt.iot.camera.plugin.impl.CameraManagerService; +import org.wso2.carbon.device.mgt.iot.service.DeviceTypeService; + + +/** + * @scr.component name="org.wso2.carbon.device.mgt.iot.camera.plugin.internal + * .CameraManagementServiceComponent" + * immediate="true" + * @scr.reference name="org.wso2.carbon.device.mgt.iot.service.DeviceTypeServiceImpl" + * interface="org.wso2.carbon.device.mgt.iot.service.DeviceTypeService" + * cardinality="1..1" + * policy="dynamic" + * bind="setDeviceTypeService" + * unbind="unsetDeviceTypeService" + */ +public class CameraManagementServiceComponent { + + private static final Log log = LogFactory.getLog(CameraManagementServiceComponent.class); + private ServiceRegistration firealarmServiceRegRef; + + protected void activate(ComponentContext ctx) { + if (log.isDebugEnabled()) { + log.debug("Activating Camera Device Management Service Component"); + } + try { + BundleContext bundleContext = ctx.getBundleContext(); + firealarmServiceRegRef = + bundleContext.registerService(DeviceManagementService.class.getName(), + new CameraManagerService(), null); + + if (log.isDebugEnabled()) { + log.debug( + "Camera Device Management Service Component has been successfully activated"); + } + } catch (Throwable e) { + log.error( + "Error occurred while activating Camera Device Management Service Component", e); + } + } + + protected void deactivate(ComponentContext ctx) { + if (log.isDebugEnabled()) { + log.debug("De-activating Camera Device Management Service Component"); + } + try { + if (firealarmServiceRegRef != null) { + firealarmServiceRegRef.unregister(); + } + + if (log.isDebugEnabled()) { + log.debug( + "Camera Device Management Service Component has been successfully de-activated"); + } + } catch (Throwable e) { + log.error( + "Error occurred while de-activating Camera Device Management bundle", e); + } + } + + protected void setDeviceTypeService(DeviceTypeService deviceTypeService) { + /* This is to avoid this component getting initialized before the common registered */ + if (log.isDebugEnabled()) { + log.debug("Data source service set to mobile service component"); + } + } + + protected void unsetDeviceTypeService(DeviceTypeService deviceTypeService) { + //do nothing + } + + +} diff --git a/components/iot-plugins/camera-plugin/org.wso2.carbon.device.mgt.iot.camera.ui/pom.xml b/components/iot-plugins/camera-plugin/org.wso2.carbon.device.mgt.iot.camera.ui/pom.xml new file mode 100644 index 0000000000..89bc215a27 --- /dev/null +++ b/components/iot-plugins/camera-plugin/org.wso2.carbon.device.mgt.iot.camera.ui/pom.xml @@ -0,0 +1,62 @@ + + + + + + + + camera-plugin + org.wso2.carbon.devicemgt-plugins + 2.1.0-SNAPSHOT + ../pom.xml + + + 4.0.0 + org.wso2.carbon.device.mgt.iot.camera.ui + WSO2 Carbon - IoT Server Camera UI + pom + + + + + maven-assembly-plugin + 2.5.5 + + ${project.artifactId}-${carbon.device.mgt.version} + false + + src/assembly/src.xml + + + + + create-archive + package + + single + + + + + + + + \ No newline at end of file diff --git a/components/iot-plugins/camera-plugin/pom.xml b/components/iot-plugins/camera-plugin/pom.xml new file mode 100644 index 0000000000..ebeed88cc7 --- /dev/null +++ b/components/iot-plugins/camera-plugin/pom.xml @@ -0,0 +1,63 @@ + + + + + + + org.wso2.carbon.devicemgt-plugins + iot-plugins + 2.1.0-SNAPSHOT + ../pom.xml + + + 4.0.0 + org.wso2.carbon.devicemgt-plugins + camera-plugin + pom + WSO2 Carbon - Arduino Plugin + http://wso2.org + + + org.wso2.carbon.device.mgt.iot.camera.controller.service.impl + org.wso2.carbon.device.mgt.iot.camera.manager.service.impl + org.wso2.carbon.device.mgt.iot.camera.plugin.impl + org.wso2.carbon.device.mgt.iot.camera.ui + + + + + + + org.apache.felix + maven-scr-plugin + 1.7.2 + + + generate-scr-scrdescriptor + + scr + + + + + + + + + diff --git a/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.controller.api/pom.xml b/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.controller.api/pom.xml new file mode 100644 index 0000000000..104e025e70 --- /dev/null +++ b/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.controller.api/pom.xml @@ -0,0 +1,243 @@ + + + + + digital-display-plugin + org.wso2.carbon.devicemgt-plugins + 2.1.0-SNAPSHOT + ../pom.xml + + + 4.0.0 + org.wso2.carbon.device.mgt.iot.digitaldisplay.controller.api + war + WSO2 Carbon - IoT Server DigitalDisplay API + WSO2 Carbon - Digital Display Service API Implementation + http://wso2.org + + + + + org.wso2.carbon.devicemgt + org.wso2.carbon.device.mgt.common + provided + + + org.wso2.carbon.devicemgt + org.wso2.carbon.device.mgt.core + provided + + + org.apache.axis2.wso2 + axis2-client + + + + + + org.wso2.carbon.devicemgt + org.wso2.carbon.device.mgt.analytics + provided + + + org.apache.axis2.wso2 + axis2-client + + + + + + org.wso2.carbon.devicemgt + org.wso2.carbon.certificate.mgt.core + provided + + + commons-codec.wso2 + commons-codec + + + + + + + org.apache.cxf + cxf-rt-frontend-jaxws + provided + + + org.apache.cxf + cxf-rt-frontend-jaxrs + provided + + + org.apache.cxf + cxf-rt-transports-http + provided + + + + + org.eclipse.paho + org.eclipse.paho.client.mqttv3 + provided + + + + + org.apache.httpcomponents + httpasyncclient + 4.1 + provided + + + org.wso2.carbon.devicemgt-plugins + org.wso2.carbon.device.mgt.iot + provided + + + org.wso2.carbon.devicemgt-plugins + org.wso2.carbon.device.mgt.iot.digitaldisplay.plugin + provided + + + + + org.codehaus.jackson + jackson-core-asl + + + org.codehaus.jackson + jackson-jaxrs + + + javax + javaee-web-api + provided + + + javax.ws.rs + jsr311-api + provided + + + + commons-httpclient.wso2 + commons-httpclient + provided + + + + org.wso2.carbon + org.wso2.carbon.utils + provided + + + 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.wso2 + commons-codec + + + + org.igniterealtime.smack.wso2 + smack + provided + + + org.igniterealtime.smack.wso2 + smackx + provided + + + + org.wso2.carbon.devicemgt + org.wso2.carbon.apimgt.annotations + provided + + + org.wso2.carbon.devicemgt + org.wso2.carbon.device.mgt.extensions + provided + + + + + + + + maven-compiler-plugin + + UTF-8 + ${wso2.maven.compiler.source} + ${wso2.maven.compiler.target} + + + + maven-war-plugin + + digital_display + + + + + + \ No newline at end of file diff --git a/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.controller.api/src/main/java/org/wso2/carbon/device/mgt/iot/digitaldisplay/controller/api/DigitalDisplayControllerService.java b/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.controller.api/src/main/java/org/wso2/carbon/device/mgt/iot/digitaldisplay/controller/api/DigitalDisplayControllerService.java new file mode 100644 index 0000000000..dde096d505 --- /dev/null +++ b/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.controller.api/src/main/java/org/wso2/carbon/device/mgt/iot/digitaldisplay/controller/api/DigitalDisplayControllerService.java @@ -0,0 +1,432 @@ +/* + * Copyright (c) 2016, 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.wso2.carbon.device.mgt.iot.digitaldisplay.controller.api; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.apimgt.annotations.api.API; +import org.wso2.carbon.context.PrivilegedCarbonContext; +import org.wso2.carbon.device.mgt.common.DeviceManagementException; +import org.wso2.carbon.device.mgt.extensions.feature.mgt.annotations.DeviceType; +import org.wso2.carbon.device.mgt.extensions.feature.mgt.annotations.Feature; +import org.wso2.carbon.device.mgt.iot.controlqueue.mqtt.MqttConfig; +import org.wso2.carbon.device.mgt.iot.digitaldisplay.controller.api.exception.DigitalDisplayException; +import org.wso2.carbon.device.mgt.iot.digitaldisplay.controller.api.util.DigitalDisplayMQTTConnector; +import org.wso2.carbon.device.mgt.iot.digitaldisplay.plugin.constants.DigitalDisplayConstants; +import org.wso2.carbon.device.mgt.iot.service.IoTServerStartupListener; +import org.wso2.carbon.device.mgt.iot.transport.TransportHandlerException; +import javax.servlet.http.HttpServletResponse; +import javax.websocket.server.PathParam; +import javax.ws.rs.FormParam; +import javax.ws.rs.HeaderParam; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.Response; + + +@API(name = "digital_display", version = "1.0.0", context = "/digital_display", tags = {"digital_display"}) +@DeviceType(value = "digital_display") +public class DigitalDisplayControllerService { + + private static Log log = LogFactory.getLog(DigitalDisplayControllerService.class); + private static DigitalDisplayMQTTConnector digitalDisplayMQTTConnector; + + private boolean waitForServerStartup() { + while (!IoTServerStartupListener.isServerReady()) { + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + return true; + } + } + return false; + } + + public DigitalDisplayMQTTConnector getDigitalDisplayMQTTConnector() { + return DigitalDisplayControllerService.digitalDisplayMQTTConnector; + } + + public void setDigitalDisplayMQTTConnector(final + DigitalDisplayMQTTConnector digitalDisplayMQTTConnector) { + + Runnable connector = new Runnable() { + public void run() { + if (waitForServerStartup()) { + return; + } + DigitalDisplayControllerService.digitalDisplayMQTTConnector = digitalDisplayMQTTConnector; + if (MqttConfig.getInstance().isEnabled()) { + digitalDisplayMQTTConnector.connect(); + } else { + log.warn("MQTT disabled in 'devicemgt-config.xml'. " + + "Hence, DigitalDisplayMQTTConnector not started."); + } + } + }; + Thread connectorThread = new Thread(connector); + connectorThread.setDaemon(true); + connectorThread.start(); + } + + /** + * Restart the running browser in the given digital display. + * + * @param deviceId id of the controlling digital display + * @param sessionId web socket id of the method invoke client + * @param response response type of the method + */ + @Path("device/{deviceId}/restart-browser") + @POST + @Feature(code = "restart-browser", name = "Restart Browser", type = "operation", + description = "Restart Browser in Digital Display") + public void restartBrowser(@PathParam("deviceId") String deviceId, + @HeaderParam("sessionId") String sessionId, + @Context HttpServletResponse response) { + try { + sendCommandViaMQTT(deviceId, sessionId + "::" + DigitalDisplayConstants.RESTART_BROWSER_CONSTANT + "::", ""); + response.setStatus(Response.Status.OK.getStatusCode()); + } catch (DeviceManagementException e) { + log.error(e); + response.setStatus(Response.Status.UNAUTHORIZED.getStatusCode()); + } catch (DigitalDisplayException e) { + log.error(e); + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + } finally { + PrivilegedCarbonContext.endTenantFlow(); + } + } + + /** + * Terminate all running processes. If this execute we have to reboot digital display manually. + * + * @param deviceId id of the controlling digital display + * @param sessionId web socket id of the method invoke client + * @param response response type of the method + */ + @Path("device/{deviceId}/terminate-display") + @POST + @Feature(code = "terminate-display", name = "Terminate Display", type = "operation", + description = "Terminate all running process in Digital Display") + public void terminateDisplay(@PathParam("deviceId") String deviceId, @HeaderParam("sessionId") String sessionId, + @Context HttpServletResponse response) { + try { + sendCommandViaMQTT(deviceId, sessionId + "::" + DigitalDisplayConstants.TERMINATE_DISPLAY_CONSTANT + "::", ""); + response.setStatus(Response.Status.OK.getStatusCode()); + } catch (DeviceManagementException e) { + log.error(e); + response.setStatus(Response.Status.UNAUTHORIZED.getStatusCode()); + } catch (DigitalDisplayException e) { + log.error(e); + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + } finally { + PrivilegedCarbonContext.endTenantFlow(); + } + + } + + /** + * Reboot running digital display + * + * @param deviceId id of the controlling digital display + * @param sessionId web socket id of the method invoke client + * @param response response type of the method + */ + @Path("device/{deviceId}/restart-display") + @POST + @Feature(code = "restart-display", name = "Restart Display", type = "operation", + description = "Restart Digital Display") + public void restartDisplay(@PathParam("deviceId") String deviceId, + @HeaderParam("sessionId") String sessionId, + @Context HttpServletResponse response) { + try { + sendCommandViaMQTT(deviceId, sessionId + "::" + DigitalDisplayConstants.RESTART_DISPLAY_CONSTANT + "::", ""); + response.setStatus(Response.Status.OK.getStatusCode()); + } catch (DeviceManagementException e) { + log.error(e); + response.setStatus(Response.Status.UNAUTHORIZED.getStatusCode()); + } catch (DigitalDisplayException e) { + log.error(e); + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + } finally { + PrivilegedCarbonContext.endTenantFlow(); + } + } + + /** + * Search through the sequence and edit requested resource + * + * @param deviceId id of the controlling digital display + * @param sessionId web socket id of the method invoke client + * @param response response type of the method + * @param name name of page need to change + * @param attribute this can be path,time or type + * @param newValue page is used to replace path + */ + @Path("device/{deviceId}/edit-sequence") + @POST + @Feature(code = "edit-sequence", name = "Edit Sequence", type = "operation", + description = "Search through the sequence and edit requested resource in Digital Display") + public void editSequence(@PathParam("deviceId") String deviceId, @FormParam("name") String name, + @FormParam("attribute") String attribute, @FormParam("new-value") String newValue, + @HeaderParam("sessionId") String sessionId, @Context HttpServletResponse response) { + try { + String params = name + "|" + attribute + "|" + newValue; + sendCommandViaMQTT(deviceId, sessionId + "::" + DigitalDisplayConstants.EDIT_SEQUENCE_CONSTANT + "::", params); + response.setStatus(Response.Status.OK.getStatusCode()); + } catch (DeviceManagementException e) { + log.error(e); + response.setStatus(Response.Status.UNAUTHORIZED.getStatusCode()); + } catch (DigitalDisplayException e) { + log.error(e); + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + } finally { + PrivilegedCarbonContext.endTenantFlow(); + } + + } + + @Path("device/{deviceId}/upload-content") + @POST + @Feature(code = "upload-content", name = "Upload Content", type = "operation", + description = "Search through the sequence and edit requested resource in Digital Display") + public void uploadContent(@PathParam("deviceId") String deviceId, @FormParam("remote-path") String remotePath, + @FormParam("screen-name") String screenName, @HeaderParam("sessionId") String sessionId, + @Context HttpServletResponse response) { + try { + String params = remotePath + "|" + screenName; + sendCommandViaMQTT(deviceId, sessionId + "::" + DigitalDisplayConstants.UPLOAD_CONTENT_CONSTANT + "::", + params); + response.setStatus(Response.Status.OK.getStatusCode()); + } catch (DeviceManagementException e) { + log.error(e); + response.setStatus(Response.Status.UNAUTHORIZED.getStatusCode()); + } catch (DigitalDisplayException e) { + log.error(e); + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + } finally { + PrivilegedCarbonContext.endTenantFlow(); + } + } + + /** + * Add new resource end to the existing sequence + * + * @param deviceId id of the controlling digital display + * @param sessionId web socket id of the method invoke client + * @param response response type of the method + * @param type type of new resource + * @param time new resource visible time + * @param path URL of the new resource + */ + @Path("device/{deviceId}/add-resource") + @POST + @Feature(code = "add-resource", name = "Add Resource", type = "operation", + description = "Add new resource end to the existing sequence in Digital Display") + public void addNewResource(@PathParam("deviceId") String deviceId, + @FormParam("type") String type, + @FormParam("time") String time, + @FormParam("path") String path, + @FormParam("name") String name, + @FormParam("position") String position, + @HeaderParam("sessionId") String sessionId, + @Context HttpServletResponse response) { + String params; + try { + if (position.isEmpty()) { + params = type + "|" + time + "|" + path + "|" + name; + } else { + params = type + "|" + time + "|" + path + "|" + name + + "|" + "after=" + position; + } + sendCommandViaMQTT(deviceId, sessionId + "::" + DigitalDisplayConstants.ADD_NEW_RESOURCE_CONSTANT + "::", params); + response.setStatus(Response.Status.OK.getStatusCode()); + } catch (DeviceManagementException e) { + log.error(e); + response.setStatus(Response.Status.UNAUTHORIZED.getStatusCode()); + } catch (DigitalDisplayException e) { + log.error(e); + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + } finally { + PrivilegedCarbonContext.endTenantFlow(); + } + } + + /** + * Delete a resource in sequence + * + * @param deviceId id of the controlling digital display + * @param sessionId web socket id of the method invoke client + * @param response response type of the method + * @param name name of the page no need to delete + */ + @Path("device/{deviceId}/remove-resource") + @POST + @Feature(code = "remove-resource", name = "Remove Resource", type = "operation", + description = "Delete a resource from sequence in Digital Display") + public void removeResource(@PathParam("deviceId") String deviceId, @FormParam("name") String name, + @HeaderParam("sessionId") String sessionId, @Context HttpServletResponse response) { + try { + sendCommandViaMQTT(deviceId, sessionId + "::" + DigitalDisplayConstants.REMOVE_RESOURCE_CONSTANT + "::", name); + response.setStatus(Response.Status.OK.getStatusCode()); + } catch (DeviceManagementException e) { + log.error(e); + response.setStatus(Response.Status.UNAUTHORIZED.getStatusCode()); + } catch (DigitalDisplayException e) { + log.error(e); + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + } finally { + PrivilegedCarbonContext.endTenantFlow(); + } + } + + /** + * Restart HTTP in running display + * + * @param deviceId id of the controlling digital display + * @param sessionId web socket id of the method invoke client + * @param response response type of the method + */ + @Path("device/{deviceId}/restart-server") + @POST + @Feature(code = "restart-server", name = "Restart Server", type = "operation", + description = "Stop HTTP Server running in Digital Display") + public void restartServer(@PathParam("deviceId") String deviceId, @HeaderParam("sessionId") String sessionId, + @Context HttpServletResponse response) { + try { + sendCommandViaMQTT(deviceId, sessionId + "::" + DigitalDisplayConstants.RESTART_SERVER_CONSTANT + "::", ""); + response.setStatus(Response.Status.OK.getStatusCode()); + } catch (DeviceManagementException e) { + log.error(e); + response.setStatus(Response.Status.UNAUTHORIZED.getStatusCode()); + } catch (DigitalDisplayException e) { + log.error(e); + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + } finally { + PrivilegedCarbonContext.endTenantFlow(); + } + } + + /** + * Get screenshot of running display + * + * @param deviceId id of the controlling digital display + * @param sessionId web socket id of the method invoke client + * @param response response type of the method + */ + @Path("device/{deviceId}/screenshot") + @POST + @Feature(code = "screenshot", name = "Take Screenshot", type = "operation", + description = "Show current view in Digital Display") + public void showScreenshot(@PathParam("deviceId") String deviceId, + @HeaderParam("sessionId") String sessionId, + @Context HttpServletResponse response) { + try { + sendCommandViaMQTT(deviceId, sessionId + "::" + DigitalDisplayConstants.SCREENSHOT_CONSTANT + "::", ""); + response.setStatus(Response.Status.OK.getStatusCode()); + } catch (DeviceManagementException e) { + log.error(e); + response.setStatus(Response.Status.UNAUTHORIZED.getStatusCode()); + } catch (DigitalDisplayException e) { + log.error(e); + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + } finally { + PrivilegedCarbonContext.endTenantFlow(); + } + } + + /** + * Get statistics of running display + * + * @param deviceId id of the controlling digital display + * @param sessionId web socket id of the method invoke client + * @param response response type of the method + */ + @Path("device/{deviceId}/get-device-status") + @POST + @Feature(code = "get-device-status", name = "Get Device Statistics", type = "operation", + description = "Current status in Digital Display") + public void getDevicestatus(@PathParam("deviceId") String deviceId, @HeaderParam("sessionId") String sessionId, + @Context HttpServletResponse response) { + try { + sendCommandViaMQTT(deviceId, sessionId + "::" + DigitalDisplayConstants.GET_DEVICE_STATUS_CONSTANT + "::", ""); + response.setStatus(Response.Status.OK.getStatusCode()); + } catch (DeviceManagementException e) { + log.error(e); + response.setStatus(Response.Status.UNAUTHORIZED.getStatusCode()); + } catch (DigitalDisplayException e) { + log.error(e); + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + } finally { + PrivilegedCarbonContext.endTenantFlow(); + } + } + + /** + * Stop specific display + * + * @param deviceId id of the controlling digital display + * @param sessionId web socket id of the method invoke client + * @param response response type of the method + */ + @Path("device/{deviceId}/get-content-list") + @POST + @Feature(code = "get-content-list", name = "Get Content List", type = "operation", + description = "Content List in Digital Display") + public void getResources(@PathParam("deviceId") String deviceId, @HeaderParam("sessionId") String sessionId, + @Context HttpServletResponse response) { + try { + sendCommandViaMQTT(deviceId, sessionId + "::" + DigitalDisplayConstants.GET_CONTENTLIST_CONSTANT + "::", ""); + response.setStatus(Response.Status.OK.getStatusCode()); + } catch (DeviceManagementException e) { + log.error(e); + response.setStatus(Response.Status.UNAUTHORIZED.getStatusCode()); + } catch (DigitalDisplayException e) { + log.error(e); + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + } finally { + PrivilegedCarbonContext.endTenantFlow(); + } + } + + /** + * Send message via MQTT protocol + * + * @param deviceId id of the target digital display + * @param operation operation need to execute + * @param param parameters need to given operation + * @throws DeviceManagementException + * @throws DigitalDisplayException + */ + private void sendCommandViaMQTT(String deviceId, String operation, String param) + throws DeviceManagementException, DigitalDisplayException { + String topic = String.format(DigitalDisplayConstants.PUBLISH_TOPIC, deviceId); + String payload = operation + param; + try { + digitalDisplayMQTTConnector.publishToDigitalDisplay(topic, payload, 2, false); + } catch (TransportHandlerException e) { + String errorMessage = "Error publishing data to device with ID " + deviceId; + throw new DigitalDisplayException(errorMessage, e); + } finally { + PrivilegedCarbonContext.endTenantFlow(); + } + } + +} diff --git a/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.controller.api/src/main/java/org/wso2/carbon/device/mgt/iot/digitaldisplay/controller/api/dto/DeviceJSON.java b/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.controller.api/src/main/java/org/wso2/carbon/device/mgt/iot/digitaldisplay/controller/api/dto/DeviceJSON.java new file mode 100644 index 0000000000..309a6cf9f7 --- /dev/null +++ b/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.controller.api/src/main/java/org/wso2/carbon/device/mgt/iot/digitaldisplay/controller/api/dto/DeviceJSON.java @@ -0,0 +1,33 @@ +/* + * 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.wso2.carbon.device.mgt.iot.digitaldisplay.controller.api.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; +} diff --git a/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.controller.api/src/main/java/org/wso2/carbon/device/mgt/iot/digitaldisplay/controller/api/exception/DigitalDisplayException.java b/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.controller.api/src/main/java/org/wso2/carbon/device/mgt/iot/digitaldisplay/controller/api/exception/DigitalDisplayException.java new file mode 100644 index 0000000000..c80d9e61a3 --- /dev/null +++ b/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.controller.api/src/main/java/org/wso2/carbon/device/mgt/iot/digitaldisplay/controller/api/exception/DigitalDisplayException.java @@ -0,0 +1,44 @@ +package org.wso2.carbon.device.mgt.iot.digitaldisplay.controller.api.exception; + +/** + * Created by nuwan on 12/2/15. + */ +public class DigitalDisplayException extends Exception { + + private static final long serialVersionUID = 2736466230451105441L; + + private String errorMessage; + + public String getErrorMessage() { + return errorMessage; + } + + public void setErrorMessage(String errorMessage) { + this.errorMessage = errorMessage; + } + + public DigitalDisplayException(String msg, DigitalDisplayException nestedEx) { + super(msg, nestedEx); + setErrorMessage(msg); + } + + public DigitalDisplayException(String message, Throwable cause) { + super(message, cause); + setErrorMessage(message); + } + + public DigitalDisplayException(String msg) { + super(msg); + setErrorMessage(msg); + } + + public DigitalDisplayException() { + super(); + } + + public DigitalDisplayException(Throwable cause) { + super(cause); + } + + +} diff --git a/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.controller.api/src/main/java/org/wso2/carbon/device/mgt/iot/digitaldisplay/controller/api/model/ScreenShotModel.java b/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.controller.api/src/main/java/org/wso2/carbon/device/mgt/iot/digitaldisplay/controller/api/model/ScreenShotModel.java new file mode 100644 index 0000000000..e20f6991ab --- /dev/null +++ b/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.controller.api/src/main/java/org/wso2/carbon/device/mgt/iot/digitaldisplay/controller/api/model/ScreenShotModel.java @@ -0,0 +1,33 @@ +package org.wso2.carbon.device.mgt.iot.digitaldisplay.controller.api.model; + +public class ScreenShotModel { + + private String[] screenShotData; + private int length; + + public ScreenShotModel(){ + + } + + public ScreenShotModel(String[] screenShotData , int length){ + this.screenShotData = screenShotData; + this.length = length; + } + + public void setScreenShotData(String[] screrenShotData){ + this.screenShotData = screenShotData; + } + + public void setLength(int length){ + this.length = length; + } + + public String[] getScreenShotData(){ + return this.screenShotData; + } + + public int getLength(){ + return this.length; + } + +} diff --git a/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.controller.api/src/main/java/org/wso2/carbon/device/mgt/iot/digitaldisplay/controller/api/util/DigitalDisplayMQTTConnector.java b/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.controller.api/src/main/java/org/wso2/carbon/device/mgt/iot/digitaldisplay/controller/api/util/DigitalDisplayMQTTConnector.java new file mode 100644 index 0000000000..b356640cae --- /dev/null +++ b/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.controller.api/src/main/java/org/wso2/carbon/device/mgt/iot/digitaldisplay/controller/api/util/DigitalDisplayMQTTConnector.java @@ -0,0 +1,185 @@ +package org.wso2.carbon.device.mgt.iot.digitaldisplay.controller.api.util; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.eclipse.paho.client.mqttv3.MqttException; +import org.eclipse.paho.client.mqttv3.MqttMessage; +import org.json.JSONObject; +import org.wso2.carbon.device.mgt.iot.controlqueue.mqtt.MqttConfig; +import org.wso2.carbon.device.mgt.iot.digitaldisplay.controller.api.model.ScreenShotModel; +import org.wso2.carbon.device.mgt.iot.digitaldisplay.controller.api.websocket.DigitalDisplayWebSocketServerEndPoint; +import org.wso2.carbon.device.mgt.iot.digitaldisplay.plugin.constants.DigitalDisplayConstants; +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.util.HashMap; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ScheduledFuture; + +@SuppressWarnings("no JAX-WS annotation") +public class DigitalDisplayMQTTConnector extends MQTTTransportHandler { + + private static Log log = LogFactory.getLog(DigitalDisplayMQTTConnector.class); + private static final String MQTT_TOPIC_APPENDER = "wso2/iot"; + private static final String subscribeTopic = + MQTT_TOPIC_APPENDER + "/" + DigitalDisplayConstants.DEVICE_TYPE + "/+/digital_display_publisher"; + + private static String iotServerSubscriber = UUID.randomUUID().toString().substring(0, 5); + + private ScheduledFuture dataPushServiceHandler; + + private Map screenshots = new HashMap<>(); + + private DigitalDisplayMQTTConnector() { + super(iotServerSubscriber, DigitalDisplayConstants.DEVICE_TYPE, + MqttConfig.getInstance().getMqttQueueEndpoint(), subscribeTopic); + } + + @Override + public void connect() { + Runnable connector = new Runnable() { + public void run() { + while (!isConnected()) { + try { + String brokerUsername = MqttConfig.getInstance().getMqttQueueUsername(); + String brokerPassword = MqttConfig.getInstance().getMqttQueuePassword(); + setUsernameAndPassword(brokerUsername, brokerPassword); + connectToQueue(); + } catch (TransportHandlerException e) { + log.error("Connection to MQTT Broker at: " + mqttBrokerEndPoint + " failed", e); + try { + Thread.sleep(timeoutInterval); + } catch (InterruptedException ex) { + log.error("MQTT-Connector: Thread Sleep Interrupt Exception.", ex); + } + } + + try { + subscribeToQueue(); + } catch (TransportHandlerException e) { + log.warn("Subscription to MQTT Broker at: " + mqttBrokerEndPoint + " failed", e); + } + } + } + }; + + Thread connectorThread = new Thread(connector); + connectorThread.setDaemon(true); + connectorThread.start(); + } + + @Override + public void processIncomingMessage(MqttMessage message, String... messageParams) { + String topic = messageParams[0]; + String[] topicParams = topic.split("/"); + String owner = topicParams[2]; + String deviceId = topicParams[4]; + String[] messageData = message.toString().split("::"); + + if (log.isDebugEnabled()) { + log.debug("Received MQTT message for: [OWNER-" + owner + "] & [DEVICE.ID-" + deviceId + "]"); + } + + String sessionId = messageData[0]; + if (messageData.length == 2) { + String responseMessage = messageData[1]; + DigitalDisplayWebSocketServerEndPoint.sendMessage(sessionId, new StringBuilder(responseMessage)); + } else if (messageData.length == 3) { + String response = messageData[2]; + JSONObject schreenShot = new JSONObject(response); + String picId = schreenShot.getString("pic_id"); + String data = schreenShot.getString("data"); + int pos = schreenShot.getInt("pos"); + int length = schreenShot.getInt("size"); + createScreenShot(sessionId, picId, pos, length, data); + } + } + + private void createScreenShot(String sessionId, String picId, int pos, int length, String data) { + + ScreenShotModel screenShotModel = screenshots.get(picId); + + if (screenShotModel == null) { + screenShotModel = new ScreenShotModel(); + screenShotModel.setScreenShotData(new String[length + 1]); + screenShotModel.setLength(0); + screenshots.put(picId, screenShotModel); + } + if (screenShotModel.getLength() <= length) { + screenShotModel.getScreenShotData()[pos] = data; + screenShotModel.setLength(screenShotModel.getLength() + 1); + if (screenShotModel.getLength() == (length + 1)) { + StringBuilder displayScreenShot = new StringBuilder("Screenshot||"); + for (String screenshot : screenShotModel.getScreenShotData()) { + displayScreenShot.append(screenshot); + } + screenshots.remove(picId); + DigitalDisplayWebSocketServerEndPoint.sendMessage(sessionId, displayScreenShot); + } + } + } + + public void publishToDigitalDisplay(String topic, String payLoad, int qos, boolean retained) + throws TransportHandlerException { + if (log.isDebugEnabled()) { + log.debug("Publishing message [" + payLoad + "to topic [" + topic + "]."); + } + publishToQueue(topic, payLoad, qos, retained); + } + + @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); + } + + try { + Thread.sleep(timeoutInterval); + } catch (InterruptedException e1) { + log.error("MQTT-Terminator: Thread Sleep Interrupt Exception"); + } + } + } + } + }; + + Thread terminatorThread = new Thread(stopConnection); + terminatorThread.setDaemon(true); + terminatorThread.start(); + } + + + @Override + public void publishDeviceData() throws TransportHandlerException { + + } + + @Override + public void publishDeviceData(MqttMessage publishData) throws TransportHandlerException { + + } + + @Override + public void publishDeviceData(String... publishData) throws TransportHandlerException { + + } + + @Override + public void processIncomingMessage() { + + } + + @Override + public void processIncomingMessage(MqttMessage message) throws TransportHandlerException { + + } + +} diff --git a/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.controller.api/src/main/java/org/wso2/carbon/device/mgt/iot/digitaldisplay/controller/api/websocket/DigitalDisplayWebSocketServerEndPoint.java b/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.controller.api/src/main/java/org/wso2/carbon/device/mgt/iot/digitaldisplay/controller/api/websocket/DigitalDisplayWebSocketServerEndPoint.java new file mode 100644 index 0000000000..5ac54331e8 --- /dev/null +++ b/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.controller.api/src/main/java/org/wso2/carbon/device/mgt/iot/digitaldisplay/controller/api/websocket/DigitalDisplayWebSocketServerEndPoint.java @@ -0,0 +1,64 @@ +package org.wso2.carbon.device.mgt.iot.digitaldisplay.controller.api.websocket; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import javax.inject.Singleton; +import javax.websocket.*; +import javax.websocket.server.PathParam; +import javax.websocket.server.ServerEndpoint; +import java.util.HashMap; +import java.util.Map; + +@ServerEndpoint(value = "/{sessionId}") +@Singleton +public class DigitalDisplayWebSocketServerEndPoint { + + private static Log log = LogFactory.getLog(DigitalDisplayWebSocketServerEndPoint.class); + private static Map clientSessions = new HashMap<>(); + + /** + * This method will be invoked when a client requests for a + * WebSocket connection. + * + * @param userSession the userSession which is opened. + */ + @OnOpen + public void onOpen(Session userSession, @PathParam("sessionId") String sessionId) { + clientSessions.put(sessionId, userSession); + } + + /** + * This method will be invoked when a client closes a WebSocket + * connection. + * + * @param userSession the userSession which is opened. + */ + @OnClose + public void onClose(Session userSession) { + clientSessions.values().remove(userSession); + + } + + @OnError + public void onError(Throwable t) { + log.error("Error occurred " + t); + } + + /** + * This method will be invoked when a message received from device + * to send client. + * + * @param sessionId the client of message to be sent. + * @param message the message sent by device to client + */ + public static void sendMessage(String sessionId, StringBuilder message) { + Session session = clientSessions.get(sessionId); + if (session != null) { + session.getAsyncRemote().sendText(message.toString()); + } else { + log.error("Client already disconnected."); + } + } + +} diff --git a/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.controller.api/src/main/webapp/META-INF/webapp-classloading.xml b/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.controller.api/src/main/webapp/META-INF/webapp-classloading.xml new file mode 100644 index 0000000000..fa44619195 --- /dev/null +++ b/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.controller.api/src/main/webapp/META-INF/webapp-classloading.xml @@ -0,0 +1,33 @@ + + + + + + + + + false + + + CXF,Carbon + diff --git a/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.controller.api/src/main/webapp/WEB-INF/cxf-servlet.xml b/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.controller.api/src/main/webapp/WEB-INF/cxf-servlet.xml new file mode 100644 index 0000000000..edb9981264 --- /dev/null +++ b/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.controller.api/src/main/webapp/WEB-INF/cxf-servlet.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.controller.api/src/main/webapp/WEB-INF/web.xml b/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.controller.api/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 0000000000..c6fb5fe502 --- /dev/null +++ b/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.controller.api/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,77 @@ + + + + Digital-Display-Agent-Webapp + + JAX-WS/JAX-RS MDM Android Endpoint + JAX-WS/JAX-RS Servlet + CXFServlet + + org.apache.cxf.transport.servlet.CXFServlet + + 1 + + + CXFServlet + /* + + + + isSharedWithAllTenants + true + + + providerTenantDomain + carbon.super + + + isAdminService + false + + + doAuthentication + true + + + + + managed-api-enabled + true + + + managed-api-owner + admin + + + managed-api-context-template + /digital_display/{version} + + + managed-api-application + digital_display + + + managed-api-isSecured + true + + + diff --git a/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.manager.api/pom.xml b/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.manager.api/pom.xml new file mode 100644 index 0000000000..b68cd2afd5 --- /dev/null +++ b/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.manager.api/pom.xml @@ -0,0 +1,246 @@ + + + + + digital-display-plugin + org.wso2.carbon.devicemgt-plugins + 2.1.0-SNAPSHOT + ../pom.xml + + + 4.0.0 + org.wso2.carbon.device.mgt.iot.digitaldisplay.manager.api + war + WSO2 Carbon - IoT Server DigitalDisplay API + WSO2 Carbon - Digital Display Service API Implementation + http://wso2.org + + + + + org.wso2.carbon.devicemgt + org.wso2.carbon.device.mgt.common + provided + + + org.wso2.carbon.devicemgt + org.wso2.carbon.device.mgt.core + provided + + + org.apache.axis2.wso2 + axis2-client + + + + + + org.wso2.carbon.devicemgt + org.wso2.carbon.device.mgt.analytics + provided + + + org.apache.axis2.wso2 + axis2-client + + + + + + org.wso2.carbon.devicemgt + org.wso2.carbon.certificate.mgt.core + provided + + + commons-codec.wso2 + commons-codec + + + + + + + org.apache.cxf + cxf-rt-frontend-jaxws + provided + + + org.apache.cxf + cxf-rt-frontend-jaxrs + provided + + + org.apache.cxf + cxf-rt-transports-http + provided + + + + + org.eclipse.paho + org.eclipse.paho.client.mqttv3 + provided + + + + + org.apache.httpcomponents + httpasyncclient + 4.1 + provided + + + org.wso2.carbon.devicemgt-plugins + org.wso2.carbon.device.mgt.iot + provided + + + org.wso2.carbon.devicemgt-plugins + org.wso2.carbon.device.mgt.iot.digitaldisplay.plugin + provided + + + + + org.codehaus.jackson + jackson-core-asl + + + org.codehaus.jackson + jackson-jaxrs + + + javax + javaee-web-api + provided + + + javax.ws.rs + jsr311-api + provided + + + + commons-httpclient.wso2 + commons-httpclient + provided + + + + org.wso2.carbon.devicemgt + org.wso2.carbon.apimgt.webapp.publisher + provided + + + + org.wso2.carbon + org.wso2.carbon.utils + provided + + + 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.wso2 + commons-codec + + + org.igniterealtime.smack.wso2 + smack + provided + + + org.igniterealtime.smack.wso2 + smackx + provided + + + org.wso2.carbon.devicemgt + org.wso2.carbon.device.mgt.jwt.client.extension + provided + + + org.wso2.carbon.devicemgt + org.wso2.carbon.apimgt.application.extension + provided + + + + + + + maven-compiler-plugin + + UTF-8 + ${wso2.maven.compiler.source} + ${wso2.maven.compiler.target} + + + + maven-war-plugin + + digital_display_mgt + + + + + + \ No newline at end of file diff --git a/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.manager.api/src/main/java/org/wso2/carbon/device/mgt/iot/digitaldisplay/manager/api/DigitalDisplayManagerService.java b/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.manager.api/src/main/java/org/wso2/carbon/device/mgt/iot/digitaldisplay/manager/api/DigitalDisplayManagerService.java new file mode 100644 index 0000000000..063a23d392 --- /dev/null +++ b/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.manager.api/src/main/java/org/wso2/carbon/device/mgt/iot/digitaldisplay/manager/api/DigitalDisplayManagerService.java @@ -0,0 +1,243 @@ +/* + * Copyright (c) 2016, 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.wso2.carbon.device.mgt.iot.digitaldisplay.manager.api; + +import org.apache.commons.io.FileUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.apimgt.application.extension.APIManagementProviderService; +import org.wso2.carbon.apimgt.application.extension.dto.ApiApplicationKey; +import org.wso2.carbon.apimgt.application.extension.exception.APIManagerException; +import org.wso2.carbon.context.PrivilegedCarbonContext; +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.digitaldisplay.manager.api.util.APIUtil; +import org.wso2.carbon.device.mgt.iot.exception.DeviceControllerException; +import org.wso2.carbon.device.mgt.iot.util.ZipArchive; +import org.wso2.carbon.device.mgt.iot.util.ZipUtil; +import org.wso2.carbon.device.mgt.iot.digitaldisplay.plugin.constants.DigitalDisplayConstants; +import org.wso2.carbon.device.mgt.jwt.client.extension.JWTClient; +import org.wso2.carbon.device.mgt.jwt.client.extension.JWTClientManager; +import org.wso2.carbon.device.mgt.jwt.client.extension.dto.AccessTokenInfo; +import org.wso2.carbon.device.mgt.jwt.client.extension.exception.JWTClientException; +import org.wso2.carbon.user.api.UserStoreException; + +import javax.servlet.http.HttpServletResponse; +import javax.ws.rs.*; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.util.Date; +import java.util.UUID; + +public class DigitalDisplayManagerService { + + private static Log log = LogFactory.getLog(DigitalDisplayManagerService.class); + @Context //injected response proxy supporting multiple thread + private HttpServletResponse response; + private static final String KEY_TYPE = "PRODUCTION"; + private static ApiApplicationKey apiApplicationKey; + + @Path("manager/device") + @POST + public boolean register(@QueryParam("deviceId") String deviceId, @QueryParam("name") String name) { + DeviceIdentifier deviceIdentifier = new DeviceIdentifier(); + deviceIdentifier.setId(deviceId); + deviceIdentifier.setType(DigitalDisplayConstants.DEVICE_TYPE); + try { + if (APIUtil.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); + device.setName(name); + device.setType(DigitalDisplayConstants.DEVICE_TYPE); + enrolmentInfo.setOwner(APIUtil.getAuthenticatedUser()); + device.setEnrolmentInfo(enrolmentInfo); + boolean added = APIUtil.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; + } finally { + PrivilegedCarbonContext.endTenantFlow(); + } + } + + @Path("manager/device/{device_id}") + @DELETE + public void removeDevice(@PathParam("device_id") String deviceId, @Context HttpServletResponse response) { + DeviceIdentifier deviceIdentifier = new DeviceIdentifier(); + deviceIdentifier.setId(deviceId); + deviceIdentifier.setType(DigitalDisplayConstants.DEVICE_TYPE); + try { + boolean removed = APIUtil.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 { + PrivilegedCarbonContext.endTenantFlow(); + } + } + + @Path("manager/device/{device_id}") + @PUT + public boolean updateDevice(@PathParam("device_id") String deviceId, + @QueryParam("name") String name, + @Context HttpServletResponse response) { + DeviceIdentifier deviceIdentifier = new DeviceIdentifier(); + deviceIdentifier.setId(deviceId); + deviceIdentifier.setType(DigitalDisplayConstants.DEVICE_TYPE); + try { + Device device = APIUtil.getDeviceManagementService().getDevice(deviceIdentifier); + device.setDeviceIdentifier(deviceId); + device.getEnrolmentInfo().setDateOfLastUpdate(new Date().getTime()); + device.setName(name); + device.setType(DigitalDisplayConstants.DEVICE_TYPE); + boolean updated = APIUtil.getDeviceManagementService().modifyEnrollment(device); + if (updated) { + response.setStatus(Response.Status.OK.getStatusCode()); + + } else { + response.setStatus(Response.Status.NOT_ACCEPTABLE.getStatusCode()); + } + return updated; + } catch (DeviceManagementException e) { + log.error(e.getErrorMessage()); + return false; + } finally { + PrivilegedCarbonContext.endTenantFlow(); + } + } + + @Path("manager/device/{device_id}") + @GET + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public Device getDevice(@PathParam("device_id") String deviceId) { + DeviceIdentifier deviceIdentifier = new DeviceIdentifier(); + deviceIdentifier.setId(deviceId); + deviceIdentifier.setType(DigitalDisplayConstants.DEVICE_TYPE); + try { + return APIUtil.getDeviceManagementService().getDevice(deviceIdentifier); + } catch (DeviceManagementException ex) { + log.error("Error occurred while retrieving device with Id " + deviceId + "\n" + ex); + return null; + } finally { + PrivilegedCarbonContext.endTenantFlow(); + } + } + + @Path("manager/device/{sketch_type}/download") + @GET + @Produces(MediaType.APPLICATION_JSON) + public Response downloadSketch(@QueryParam("deviceName") String deviceName, + @PathParam("sketch_type") String + sketchType) { + try { + ZipArchive zipFile = createDownloadFile(APIUtil.getAuthenticatedUser(), deviceName, sketchType); + Response.ResponseBuilder response = Response.ok(FileUtils.readFileToByteArray(zipFile.getZipFile())); + response.type("application/zip"); + response.header("Content-Disposition", "attachment; filename=\"" + zipFile.getFileName() + "\""); + return response.build(); + } catch (IllegalArgumentException ex) { + return Response.status(400).entity(ex.getMessage()).build();//bad request + } catch (DeviceManagementException ex) { + return Response.status(500).entity(ex.getMessage()).build(); + } catch (JWTClientException ex) { + return Response.status(500).entity(ex.getMessage()).build(); + } catch (DeviceControllerException ex) { + return Response.status(500).entity(ex.getMessage()).build(); + } catch (APIManagerException ex) { + return Response.status(500).entity(ex.getMessage()).build(); + } catch (IOException ex) { + return Response.status(500).entity(ex.getMessage()).build(); + } catch (UserStoreException ex) { + return Response.status(500).entity(ex.getMessage()).build(); + } finally { + PrivilegedCarbonContext.endTenantFlow(); + } + } + + private ZipArchive createDownloadFile(String owner, String deviceName, String sketchType) + throws DeviceManagementException, JWTClientException, DeviceControllerException, APIManagerException, + UserStoreException { + if (owner == null) { + throw new IllegalArgumentException("Error on createDownloadFile() Owner is null!"); + } + //create new device id + String deviceId = shortUUID(); + if (apiApplicationKey == null) { + String applicationUsername = + PrivilegedCarbonContext.getThreadLocalCarbonContext().getUserRealm().getRealmConfiguration().getAdminUserName(); + APIManagementProviderService apiManagementProviderService = APIUtil.getAPIManagementProviderService(); + String[] tags = {DigitalDisplayConstants.DEVICE_TYPE}; + apiApplicationKey = apiManagementProviderService.generateAndRetrieveApplicationKeys( + DigitalDisplayConstants.DEVICE_TYPE, tags, KEY_TYPE, applicationUsername, true); + } + JWTClient jwtClient = JWTClientManager.getInstance().getJWTClient(); + String scopes = "device_type_" + DigitalDisplayConstants.DEVICE_TYPE + " device_" + deviceId; + AccessTokenInfo accessTokenInfo = jwtClient.getAccessToken(apiApplicationKey.getConsumerKey(), + apiApplicationKey.getConsumerSecret(), owner, scopes); + //create token + String accessToken = accessTokenInfo.getAccess_token(); + String refreshToken = accessTokenInfo.getRefresh_token(); + //adding registering data + boolean status; + //Register the device with CDMF + status = register(deviceId, deviceName); + if (!status) { + String msg = "Error occurred while registering the device with " + "id: " + deviceId + " owner:" + owner; + throw new DeviceManagementException(msg); + } + ZipUtil ziputil = new ZipUtil(); + ZipArchive zipFile = ziputil.createZipFile(owner, APIUtil.getTenantDomainOftheUser(), sketchType, deviceId, + deviceName, accessToken, refreshToken); + zipFile.setDeviceId(deviceId); + return zipFile; + } + + 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); + } + +} diff --git a/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.manager.api/src/main/java/org/wso2/carbon/device/mgt/iot/digitaldisplay/manager/api/util/APIUtil.java b/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.manager.api/src/main/java/org/wso2/carbon/device/mgt/iot/digitaldisplay/manager/api/util/APIUtil.java new file mode 100644 index 0000000000..a593b67cfb --- /dev/null +++ b/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.manager.api/src/main/java/org/wso2/carbon/device/mgt/iot/digitaldisplay/manager/api/util/APIUtil.java @@ -0,0 +1,54 @@ +package org.wso2.carbon.device.mgt.iot.digitaldisplay.manager.api.util; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.apimgt.application.extension.APIManagementProviderService; +import org.wso2.carbon.context.PrivilegedCarbonContext; +import org.wso2.carbon.device.mgt.core.service.DeviceManagementProviderService; +/** + * This class provides utility functions used by REST-API. + */ +public class APIUtil { + + private static Log log = LogFactory.getLog(APIUtil.class); + + public static String getAuthenticatedUser() { + PrivilegedCarbonContext threadLocalCarbonContext = PrivilegedCarbonContext.getThreadLocalCarbonContext(); + String username = threadLocalCarbonContext.getUsername(); + String tenantDomain = threadLocalCarbonContext.getTenantDomain(); + if (username.endsWith(tenantDomain)) { + return username.substring(0, username.lastIndexOf("@")); + } + return username; + } + + public static String getTenantDomainOftheUser() { + PrivilegedCarbonContext threadLocalCarbonContext = PrivilegedCarbonContext.getThreadLocalCarbonContext(); + String tenantDomain = threadLocalCarbonContext.getTenantDomain(); + return tenantDomain; + } + + public static DeviceManagementProviderService getDeviceManagementService() { + PrivilegedCarbonContext ctx = PrivilegedCarbonContext.getThreadLocalCarbonContext(); + DeviceManagementProviderService deviceManagementProviderService = + (DeviceManagementProviderService) ctx.getOSGiService(DeviceManagementProviderService.class, null); + if (deviceManagementProviderService == null) { + String msg = "Device Management service has not initialized."; + log.error(msg); + throw new IllegalStateException(msg); + } + return deviceManagementProviderService; + } + + public static APIManagementProviderService getAPIManagementProviderService() { + PrivilegedCarbonContext ctx = PrivilegedCarbonContext.getThreadLocalCarbonContext(); + APIManagementProviderService apiManagementProviderService = + (APIManagementProviderService) ctx.getOSGiService(APIManagementProviderService.class, null); + if (apiManagementProviderService == null) { + String msg = "API management provider service has not initialized."; + log.error(msg); + throw new IllegalStateException(msg); + } + return apiManagementProviderService; + } +} diff --git a/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.manager.api/src/main/webapp/META-INF/permissions.xml b/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.manager.api/src/main/webapp/META-INF/permissions.xml new file mode 100644 index 0000000000..91ed832578 --- /dev/null +++ b/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.manager.api/src/main/webapp/META-INF/permissions.xml @@ -0,0 +1,66 @@ + + + + + + + + + + Get device + /device-mgt/user/devices/list + /manager/device/{device_id} + GET + + + + Add device + /device-mgt/user/devices/add + /manager/device + POST + + + + Remove device + /device-mgt/user/devices/remove + /manager/device/{device_id} + DELETE + + + + Download device + /device-mgt/user/devices/add + /manager/device/{sketch_type}/download + GET + + + + Update device + /device-mgt/user/devices/update + /manager/device/{device_id} + POST + + + \ No newline at end of file diff --git a/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.manager.api/src/main/webapp/META-INF/webapp-classloading.xml b/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.manager.api/src/main/webapp/META-INF/webapp-classloading.xml new file mode 100644 index 0000000000..fa44619195 --- /dev/null +++ b/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.manager.api/src/main/webapp/META-INF/webapp-classloading.xml @@ -0,0 +1,33 @@ + + + + + + + + + false + + + CXF,Carbon + diff --git a/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.manager.api/src/main/webapp/WEB-INF/cxf-servlet.xml b/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.manager.api/src/main/webapp/WEB-INF/cxf-servlet.xml new file mode 100644 index 0000000000..14cc354269 --- /dev/null +++ b/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.manager.api/src/main/webapp/WEB-INF/cxf-servlet.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + diff --git a/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.manager.api/src/main/webapp/WEB-INF/web.xml b/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.manager.api/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 0000000000..e5ae03197f --- /dev/null +++ b/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.manager.api/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,68 @@ + + + + Digital-Display-Agent-Webapp + + JAX-WS/JAX-RS MDM Android Endpoint + JAX-WS/JAX-RS Servlet + CXFServlet + org.apache.cxf.transport.servlet.CXFServlet + 1 + + + CXFServlet + /* + + + + isAdminService + false + + + doAuthentication + true + + + + + managed-api-enabled + false + + + managed-api-owner + admin + + + managed-api-context-template + /digital_display/{version} + + + managed-api-application + digital_display + + + managed-api-isSecured + true + + + + diff --git a/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.plugin/pom.xml b/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.plugin/pom.xml new file mode 100644 index 0000000000..6cce70392b --- /dev/null +++ b/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.plugin/pom.xml @@ -0,0 +1,106 @@ + + + + + digital-display-plugin + org.wso2.carbon.devicemgt-plugins + 2.1.0-SNAPSHOT + ../pom.xml + + + 4.0.0 + org.wso2.carbon.device.mgt.iot.digitaldisplay.plugin + bundle + WSO2 Carbon - IoT Server DigitalDisplay Management Plugin + WSO2 Carbon - Digital Display Device-Type Plugin Implementation + http://wso2.org + + + + + org.apache.felix + maven-scr-plugin + + + maven-compiler-plugin + + 1.7 + 1.7 + + 2.3.2 + + + org.apache.felix + maven-bundle-plugin + 1.4.0 + true + + + ${project.artifactId} + ${project.artifactId} + ${carbon.devicemgt.plugins.version} + IoT Server Impl Bundle + org.wso2.carbon.device.mgt.iot.digitaldisplay.plugin.internal + + org.osgi.framework, + org.osgi.service.component, + org.apache.commons.logging, + javax.xml.bind.*;resolution:=optional, + javax.naming;resolution:=optional, + javax.sql;resolution:=optional, + javax.xml.bind.annotation.*;resolution:=optional, + javax.xml.parsers;resolution:=optional, + javax.net, + javax.net.ssl, + org.w3c.dom, + org.wso2.carbon.device.mgt.common.*, + org.wso2.carbon.device.mgt.common, + org.wso2.carbon.context.*, + org.wso2.carbon.ndatasource.core, + org.wso2.carbon.device.mgt.iot.*, + org.wso2.carbon.device.mgt.extensions.feature.mgt.*, + org.wso2.carbon.utils.* + + + !org.wso2.carbon.device.mgt.iot.digitaldisplay.plugin.internal, + org.wso2.carbon.device.mgt.iot.digitaldisplay.plugin.* + + + + + + + + + + org.eclipse.osgi + org.eclipse.osgi + + + org.eclipse.osgi + org.eclipse.osgi.services + + + org.wso2.carbon + org.wso2.carbon.logging + + + org.wso2.carbon.devicemgt + org.wso2.carbon.device.mgt.common + + + org.wso2.carbon + org.wso2.carbon.ndatasource.core + + + org.wso2.carbon.devicemgt + org.wso2.carbon.device.mgt.extensions + + + org.wso2.carbon + org.wso2.carbon.utils + + + \ No newline at end of file diff --git a/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.plugin/src/main/java/org/wso2/carbon/device/mgt/iot/digitaldisplay/plugin/constants/DigitalDisplayConstants.java b/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.plugin/src/main/java/org/wso2/carbon/device/mgt/iot/digitaldisplay/plugin/constants/DigitalDisplayConstants.java new file mode 100644 index 0000000000..00ef15972c --- /dev/null +++ b/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.plugin/src/main/java/org/wso2/carbon/device/mgt/iot/digitaldisplay/plugin/constants/DigitalDisplayConstants.java @@ -0,0 +1,37 @@ +/* +* Copyright (c) 2014, 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. +*/ + +package org.wso2.carbon.device.mgt.iot.digitaldisplay.plugin.constants; + +public class DigitalDisplayConstants { + + public final static String DEVICE_TYPE = "digital_display"; + public final static String DEVICE_PLUGIN_DEVICE_NAME = "DEVICE_NAME"; + public final static String DEVICE_PLUGIN_DEVICE_ID = "DIGITAL_DISPLAY_DEVICE_ID"; + public final static String RESTART_SERVER_CONSTANT = "restart_server"; + public final static String RESTART_DISPLAY_CONSTANT = "restart_display"; + public final static String RESTART_BROWSER_CONSTANT = "restart_browser"; + public final static String TERMINATE_DISPLAY_CONSTANT = "terminate_display"; + public final static String EDIT_SEQUENCE_CONSTANT = "edit_sequence"; + public final static String UPLOAD_CONTENT_CONSTANT = "upload_content"; + public final static String ADD_NEW_RESOURCE_CONSTANT = "add_new_resource"; + public final static String REMOVE_RESOURCE_CONSTANT = "remove_resources"; + public final static String SCREENSHOT_CONSTANT = "get_screenshot"; + public final static String GET_CONTENTLIST_CONSTANT = "get_content_list"; + public final static String GET_DEVICE_STATUS_CONSTANT = "get_device_status"; + public final static String PUBLISH_TOPIC = "wso2/iot/digital_display/%s/digital_display_subscriber"; + public static final String DATA_SOURCE_NAME = "jdbc/DigitalDisplayDM_DB"; +} diff --git a/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.plugin/src/main/java/org/wso2/carbon/device/mgt/iot/digitaldisplay/plugin/exception/DigitalDisplayDeviceMgtPluginException.java b/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.plugin/src/main/java/org/wso2/carbon/device/mgt/iot/digitaldisplay/plugin/exception/DigitalDisplayDeviceMgtPluginException.java new file mode 100644 index 0000000000..6b184e57b2 --- /dev/null +++ b/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.plugin/src/main/java/org/wso2/carbon/device/mgt/iot/digitaldisplay/plugin/exception/DigitalDisplayDeviceMgtPluginException.java @@ -0,0 +1,56 @@ +/* + * 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.wso2.carbon.device.mgt.iot.digitaldisplay.plugin.exception; + + +public class DigitalDisplayDeviceMgtPluginException extends Exception{ + + private String errorMessage; + + public String getErrorMessage() { + return errorMessage; + } + + public void setErrorMessage(String errorMessage) { + this.errorMessage = errorMessage; + } + + public DigitalDisplayDeviceMgtPluginException(String msg, Exception nestedEx) { + super(msg, nestedEx); + setErrorMessage(msg); + } + + public DigitalDisplayDeviceMgtPluginException(String message, Throwable cause) { + super(message, cause); + setErrorMessage(message); + } + + public DigitalDisplayDeviceMgtPluginException(String msg) { + super(msg); + setErrorMessage(msg); + } + + public DigitalDisplayDeviceMgtPluginException() { + super(); + } + + public DigitalDisplayDeviceMgtPluginException(Throwable cause) { + super(cause); + } + +} diff --git a/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.plugin/src/main/java/org/wso2/carbon/device/mgt/iot/digitaldisplay/plugin/impl/DigitalDisplayManager.java b/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.plugin/src/main/java/org/wso2/carbon/device/mgt/iot/digitaldisplay/plugin/impl/DigitalDisplayManager.java new file mode 100644 index 0000000000..f1e2befa35 --- /dev/null +++ b/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.plugin/src/main/java/org/wso2/carbon/device/mgt/iot/digitaldisplay/plugin/impl/DigitalDisplayManager.java @@ -0,0 +1,251 @@ +/* + * 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.wso2.carbon.device.mgt.iot.digitaldisplay.plugin.impl; + + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.device.mgt.common.*; +import org.wso2.carbon.device.mgt.common.configuration.mgt.TenantConfiguration; +import org.wso2.carbon.device.mgt.common.license.mgt.License; +import org.wso2.carbon.device.mgt.common.license.mgt.LicenseManagementException; +import org.wso2.carbon.device.mgt.iot.digitaldisplay.plugin.exception.DigitalDisplayDeviceMgtPluginException; +import org.wso2.carbon.device.mgt.iot.digitaldisplay.plugin.impl.dao.DigitalDisplayDAO; +import org.wso2.carbon.device.mgt.iot.digitaldisplay.plugin.impl.feature.DigitalDisplayFeatureManager; +import java.util.List; + + +/** + * This represents the DigitalDisplay implementation of DeviceManagerService. + */ +public class DigitalDisplayManager implements DeviceManager { + + private static final DigitalDisplayDAO digitalDisplayDAO = new DigitalDisplayDAO(); + private static final Log log = LogFactory.getLog(DigitalDisplayManager.class); + private FeatureManager featureManager = new DigitalDisplayFeatureManager(); + @Override + public FeatureManager getFeatureManager() { + return featureManager; + } + + @Override + public boolean saveConfiguration(TenantConfiguration tenantConfiguration) + throws DeviceManagementException { + return false; + } + + @Override + public TenantConfiguration getConfiguration() throws DeviceManagementException { + return null; + } + + @Override + public boolean enrollDevice(Device device) throws DeviceManagementException { + boolean status; + try { + if (log.isDebugEnabled()) { + log.debug("Enrolling a new DigitalDisplay device : " + device.getDeviceIdentifier()); + } + DigitalDisplayDAO.beginTransaction(); + status = digitalDisplayDAO.getDeviceDAO().addDevice(device); + DigitalDisplayDAO.commitTransaction(); + } catch (DigitalDisplayDeviceMgtPluginException e) { + try { + DigitalDisplayDAO.rollbackTransaction(); + } catch (DigitalDisplayDeviceMgtPluginException iotDAOEx) { + String msg = "Error occurred while roll back the device enrol transaction :" + device.toString(); + log.warn(msg, iotDAOEx); + } + String msg = "Error while enrolling the DigitalDisplay device : " + device.getDeviceIdentifier(); + log.error(msg, e); + throw new DeviceManagementException(msg, e); + } + return status; + } + + @Override + public boolean modifyEnrollment(Device device) throws DeviceManagementException { + boolean status; + try { + if (log.isDebugEnabled()) { + log.debug("Modifying the DigitalDisplay device enrollment data"); + } + DigitalDisplayDAO.beginTransaction(); + status = digitalDisplayDAO.getDeviceDAO().updateDevice(device); + DigitalDisplayDAO.commitTransaction(); + } catch (DigitalDisplayDeviceMgtPluginException e) { + try { + DigitalDisplayDAO.rollbackTransaction(); + } catch (DigitalDisplayDeviceMgtPluginException iotDAOEx) { + String msg = "Error occurred while roll back the update device transaction :" + device.toString(); + log.warn(msg, iotDAOEx); + } + String msg = "Error while updating the enrollment of the DigitalDisplay device : " + + device.getDeviceIdentifier(); + log.error(msg, e); + throw new DeviceManagementException(msg, e); + } + return status; + } + + @Override + public boolean disenrollDevice(DeviceIdentifier deviceId) throws DeviceManagementException { + boolean status; + try { + if (log.isDebugEnabled()) { + log.debug("Dis-enrolling DigitalDisplay device : " + deviceId); + } + DigitalDisplayDAO.beginTransaction(); + status = digitalDisplayDAO.getDeviceDAO().deleteDevice(deviceId.getId()); + DigitalDisplayDAO.commitTransaction(); + } catch (DigitalDisplayDeviceMgtPluginException e) { + try { + DigitalDisplayDAO.rollbackTransaction(); + } catch (DigitalDisplayDeviceMgtPluginException iotDAOEx) { + String msg = "Error occurred while roll back the device dis enrol transaction :" + deviceId.toString(); + log.warn(msg, iotDAOEx); + } + String msg = "Error while removing the DigitalDisplay device : " + deviceId.getId(); + log.error(msg, e); + throw new DeviceManagementException(msg, e); + } + return status; + } + + @Override + public boolean isEnrolled(DeviceIdentifier deviceId) throws DeviceManagementException { + boolean isEnrolled = false; + try { + if (log.isDebugEnabled()) { + log.debug("Checking the enrollment of DigitalDisplay device : " + deviceId.getId()); + } + Device iotDevice = digitalDisplayDAO.getDeviceDAO().getDevice(deviceId.getId()); + if (iotDevice != null) { + isEnrolled = true; + } + } catch (DigitalDisplayDeviceMgtPluginException e) { + String msg = "Error while checking the enrollment status of DigitalDisplay device : " + + deviceId.getId(); + log.error(msg, e); + throw new DeviceManagementException(msg, e); + } + return isEnrolled; + } + + @Override + public boolean isActive(DeviceIdentifier deviceId) throws DeviceManagementException { + return true; + } + + @Override + public boolean setActive(DeviceIdentifier deviceId, boolean status) + throws DeviceManagementException { + return true; + } + + @Override + public Device getDevice(DeviceIdentifier deviceId) throws DeviceManagementException { + Device device; + try { + if (log.isDebugEnabled()) { + log.debug("Getting the details of DigitalDisplay device : " + deviceId.getId()); + } + device = digitalDisplayDAO.getDeviceDAO().getDevice(deviceId.getId()); + } catch (DigitalDisplayDeviceMgtPluginException e) { + String msg = "Error while fetching the DigitalDisplay device : " + deviceId.getId(); + log.error(msg, e); + throw new DeviceManagementException(msg, e); + } + return device; + } + + @Override + public boolean setOwnership(DeviceIdentifier deviceId, String ownershipType) + throws DeviceManagementException { + return true; + } + + public boolean isClaimable(DeviceIdentifier deviceIdentifier) throws DeviceManagementException { + return false; + } + + @Override + public boolean setStatus(DeviceIdentifier deviceId, String currentOwner, + EnrolmentInfo.Status status) throws DeviceManagementException { + return false; + } + + @Override + public License getLicense(String s) throws LicenseManagementException { + return null; + } + + @Override + public void addLicense(License license) throws LicenseManagementException { + + } + + @Override + public boolean requireDeviceAuthorization() { + return true; + } + + @Override + public boolean updateDeviceInfo(DeviceIdentifier deviceIdentifier, Device device) throws DeviceManagementException { + boolean status; + try { + if (log.isDebugEnabled()) { + log.debug( + "updating the details of DigitalDisplay device : " + deviceIdentifier); + } + DigitalDisplayDAO.beginTransaction(); + status = digitalDisplayDAO.getDeviceDAO().updateDevice(device); + DigitalDisplayDAO.commitTransaction(); + } catch (DigitalDisplayDeviceMgtPluginException e) { + try { + DigitalDisplayDAO.rollbackTransaction(); + } catch (DigitalDisplayDeviceMgtPluginException iotDAOEx) { + String msg = "Error occurred while roll back the update device info transaction :" + device.toString(); + log.warn(msg, iotDAOEx); + } + String msg = + "Error while updating the DigitalDisplay device : " + deviceIdentifier; + log.error(msg, e); + throw new DeviceManagementException(msg, e); + } + return status; + } + + @Override + public List getAllDevices() throws DeviceManagementException { + List devices; + try { + if (log.isDebugEnabled()) { + log.debug("Fetching the details of all DigitalDisplay devices"); + } + devices = digitalDisplayDAO.getDeviceDAO().getAllDevices(); + } catch (DigitalDisplayDeviceMgtPluginException e) { + String msg = "Error while fetching all DigitalDisplay devices."; + log.error(msg, e); + throw new DeviceManagementException(msg, e); + } + return devices; + } + +} \ No newline at end of file diff --git a/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.plugin/src/main/java/org/wso2/carbon/device/mgt/iot/digitaldisplay/plugin/impl/DigitalDisplayManagerService.java b/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.plugin/src/main/java/org/wso2/carbon/device/mgt/iot/digitaldisplay/plugin/impl/DigitalDisplayManagerService.java new file mode 100644 index 0000000000..429694b41b --- /dev/null +++ b/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.plugin/src/main/java/org/wso2/carbon/device/mgt/iot/digitaldisplay/plugin/impl/DigitalDisplayManagerService.java @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2016, 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. + */ + +package org.wso2.carbon.device.mgt.iot.digitaldisplay.plugin.impl; + +import org.wso2.carbon.device.mgt.common.DeviceIdentifier; +import org.wso2.carbon.device.mgt.common.DeviceManagementException; +import org.wso2.carbon.device.mgt.common.DeviceManager; +import org.wso2.carbon.device.mgt.common.app.mgt.Application; +import org.wso2.carbon.device.mgt.common.app.mgt.ApplicationManagementException; +import org.wso2.carbon.device.mgt.common.app.mgt.ApplicationManager; +import org.wso2.carbon.device.mgt.common.operation.mgt.Operation; +import org.wso2.carbon.device.mgt.common.spi.DeviceManagementService; +import org.wso2.carbon.device.mgt.iot.digitaldisplay.plugin.constants.DigitalDisplayConstants; + +import java.util.List; + +public class DigitalDisplayManagerService implements DeviceManagementService{ + private DeviceManager deviceManager; + @Override + public String getType() { + return DigitalDisplayConstants.DEVICE_TYPE; + } + + @Override + public String getProviderTenantDomain() { + return "carbon.super"; + } + + @Override + public boolean isSharedWithAllTenants() { + return true; + } + + @Override + public void init() throws DeviceManagementException { + deviceManager= new DigitalDisplayManager(); + } + + @Override + public DeviceManager getDeviceManager() { + return deviceManager; + } + + @Override + public ApplicationManager getApplicationManager() { + return null; + } + + @Override + public void notifyOperationToDevices(Operation operation, List list) throws DeviceManagementException { + + } + + @Override + public Application[] getApplications(String domain, int pageNumber, int size) + throws ApplicationManagementException { + return new Application[0]; + } + + @Override + public void updateApplicationStatus(DeviceIdentifier deviceId, Application application, + String status) throws ApplicationManagementException { + + } + + @Override + public String getApplicationStatus(DeviceIdentifier deviceId, Application application) + throws ApplicationManagementException { + return null; + } + + @Override + public void installApplicationForDevices(Operation operation, List list) throws ApplicationManagementException { + + } + + @Override + public void installApplicationForUsers(Operation operation, List list) throws ApplicationManagementException { + + } + + @Override + public void installApplicationForUserRoles(Operation operation, List list) throws ApplicationManagementException { + + } + +} diff --git a/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.plugin/src/main/java/org/wso2/carbon/device/mgt/iot/digitaldisplay/plugin/impl/dao/DigitalDisplayDAO.java b/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.plugin/src/main/java/org/wso2/carbon/device/mgt/iot/digitaldisplay/plugin/impl/dao/DigitalDisplayDAO.java new file mode 100644 index 0000000000..01e9d702ca --- /dev/null +++ b/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.plugin/src/main/java/org/wso2/carbon/device/mgt/iot/digitaldisplay/plugin/impl/dao/DigitalDisplayDAO.java @@ -0,0 +1,126 @@ +/* + * 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.wso2.carbon.device.mgt.iot.digitaldisplay.plugin.impl.dao; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.device.mgt.iot.digitaldisplay.plugin.exception.DigitalDisplayDeviceMgtPluginException; +import org.wso2.carbon.device.mgt.iot.digitaldisplay.plugin.constants.DigitalDisplayConstants; +import org.wso2.carbon.device.mgt.iot.digitaldisplay.plugin.impl.dao.impl.DigitalDisplayDeviceDAOImpl; + +import javax.naming.Context; +import javax.naming.InitialContext; +import javax.naming.NamingException; +import javax.sql.DataSource; +import java.sql.Connection; +import java.sql.SQLException; + +public class DigitalDisplayDAO { + + private static final Log log = LogFactory.getLog(DigitalDisplayDAO.class); + static DataSource dataSource; // package local variable + private static ThreadLocal currentConnection = new ThreadLocal(); + + public DigitalDisplayDAO() { + initDigitalDisplayDAO(); + } + + public DigitalDisplayDeviceDAOImpl getDeviceDAO() { + return new DigitalDisplayDeviceDAOImpl(); + } + + public static void initDigitalDisplayDAO(){ + try { + Context ctx = new InitialContext(); + dataSource = (DataSource) ctx.lookup(DigitalDisplayConstants.DATA_SOURCE_NAME); + } catch (NamingException e) { + log.error("Error while looking up the data source: " + DigitalDisplayConstants.DATA_SOURCE_NAME); + } + } + + public static void beginTransaction() throws DigitalDisplayDeviceMgtPluginException { + try { + Connection conn = dataSource.getConnection(); + conn.setAutoCommit(false); + currentConnection.set(conn); + } catch (SQLException e) { + throw new DigitalDisplayDeviceMgtPluginException("Error occurred while retrieving datasource connection", e); + } + } + + public static Connection getConnection() throws DigitalDisplayDeviceMgtPluginException { + if (currentConnection.get() == null) { + try { + currentConnection.set(dataSource.getConnection()); + } catch (SQLException e) { + throw new DigitalDisplayDeviceMgtPluginException("Error occurred while retrieving data source connection", + e); + } + } + return currentConnection.get(); + } + + public static void commitTransaction() throws DigitalDisplayDeviceMgtPluginException { + 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 DigitalDisplayDeviceMgtPluginException("Error occurred while committing the transaction", e); + } finally { + closeConnection(); + } + } + + public static void closeConnection() throws DigitalDisplayDeviceMgtPluginException { + 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 DigitalDisplayDeviceMgtPluginException { + 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 DigitalDisplayDeviceMgtPluginException("Error occurred while rollback the transaction", e); + } finally { + closeConnection(); + } + } +} \ No newline at end of file diff --git a/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.plugin/src/main/java/org/wso2/carbon/device/mgt/iot/digitaldisplay/plugin/impl/dao/impl/DigitalDisplayDeviceDAOImpl.java b/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.plugin/src/main/java/org/wso2/carbon/device/mgt/iot/digitaldisplay/plugin/impl/dao/impl/DigitalDisplayDeviceDAOImpl.java new file mode 100644 index 0000000000..8df3567ff3 --- /dev/null +++ b/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.plugin/src/main/java/org/wso2/carbon/device/mgt/iot/digitaldisplay/plugin/impl/dao/impl/DigitalDisplayDeviceDAOImpl.java @@ -0,0 +1,194 @@ +/* + * 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.wso2.carbon.device.mgt.iot.digitaldisplay.plugin.impl.dao.impl; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.device.mgt.common.Device; +import org.wso2.carbon.device.mgt.iot.digitaldisplay.plugin.constants.DigitalDisplayConstants; +import org.wso2.carbon.device.mgt.iot.digitaldisplay.plugin.exception.DigitalDisplayDeviceMgtPluginException; +import org.wso2.carbon.device.mgt.iot.digitaldisplay.plugin.impl.dao.DigitalDisplayDAO; +import org.wso2.carbon.device.mgt.iot.digitaldisplay.plugin.impl.util.DigitalDisplayUtils; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; + +/** + * Implements CRUD for digital display Devices. + */ +public class DigitalDisplayDeviceDAOImpl { + private static final Log log = LogFactory.getLog(DigitalDisplayDeviceDAOImpl.class); + + public Device getDevice(String iotDeviceId) throws DigitalDisplayDeviceMgtPluginException { + Connection conn = null; + PreparedStatement stmt = null; + Device device = null; + ResultSet resultSet = null; + try { + conn = DigitalDisplayDAO.getConnection(); + String selectDBQuery = + "SELECT DIGITAL_DISPLAY_DEVICE_ID, DEVICE_NAME" + + " FROM DIGITAL_DISPLAY_DEVICE WHERE DIGITAL_DISPLAY_DEVICE_ID = ?"; + stmt = conn.prepareStatement(selectDBQuery); + stmt.setString(1, iotDeviceId); + resultSet = stmt.executeQuery(); + if (resultSet.next()) { + device = new Device(); + device.setName(resultSet.getString( + DigitalDisplayConstants.DEVICE_PLUGIN_DEVICE_NAME)); + if (log.isDebugEnabled()) { + log.debug("Digital Display device " + iotDeviceId + " data has been fetched from " + + "Digital Display database."); + } + } + } catch (SQLException e) { + String msg = "Error occurred while fetching Digital Display device : '" + iotDeviceId + "'"; + log.error(msg, e); + throw new DigitalDisplayDeviceMgtPluginException(msg, e); + } finally { + DigitalDisplayUtils.cleanupResources(stmt, resultSet); + DigitalDisplayDAO.closeConnection(); + } + return device; + } + + public boolean addDevice(Device device) throws DigitalDisplayDeviceMgtPluginException { + boolean status = false; + Connection conn = null; + PreparedStatement stmt = null; + try { + conn = DigitalDisplayDAO.getConnection(); + String createDBQuery = + "INSERT INTO DIGITAL_DISPLAY_DEVICE(DIGITAL_DISPLAY_DEVICE_ID, DEVICE_NAME) VALUES (?, ?)"; + stmt = conn.prepareStatement(createDBQuery); + stmt.setString(1, device.getDeviceIdentifier()); + stmt.setString(2, device.getName()); + int rows = stmt.executeUpdate(); + if (rows > 0) { + status = true; + if (log.isDebugEnabled()) { + log.debug("Digital Display device " + device.getDeviceIdentifier() + " data has been" + + " added to the Digital Display database."); + } + } + } catch (SQLException e) { + String msg = "Error occurred while adding the Digital Display device '" + + device.getDeviceIdentifier() + "' to the Digital Display db."; + log.error(msg, e); + throw new DigitalDisplayDeviceMgtPluginException(msg, e); + } finally { + DigitalDisplayUtils.cleanupResources(stmt, null); + } + return status; + } + + public boolean updateDevice(Device device) throws DigitalDisplayDeviceMgtPluginException { + boolean status = false; + Connection conn = null; + PreparedStatement stmt = null; + try { + conn = DigitalDisplayDAO.getConnection(); + String updateDBQuery = + "UPDATE DIGITAL_DISPLAY_DEVICE SET DEVICE_NAME = ? WHERE DIGITAL_DISPLAY_DEVICE_ID = ?"; + stmt = conn.prepareStatement(updateDBQuery); + stmt.setString(1, device.getName()); + stmt.setString(2, device.getDeviceIdentifier()); + int rows = stmt.executeUpdate(); + if (rows > 0) { + status = true; + if (log.isDebugEnabled()) { + log.debug("Digital Display device " + device.getDeviceIdentifier() + " data has been" + + " modified."); + } + } + } catch (SQLException e) { + String msg = "Error occurred while modifying the Digital Display device '" + + device.getDeviceIdentifier() + "' data."; + log.error(msg, e); + throw new DigitalDisplayDeviceMgtPluginException(msg, e); + } finally { + DigitalDisplayUtils.cleanupResources(stmt, null); + } + return status; + } + + public boolean deleteDevice(String iotDeviceId) throws DigitalDisplayDeviceMgtPluginException { + boolean status = false; + Connection conn = null; + PreparedStatement stmt = null; + try { + conn = DigitalDisplayDAO.getConnection(); + String deleteDBQuery = + "DELETE FROM DIGITAL_DISPLAY_DEVICE WHERE DIGITAL_DISPLAY_DEVICE_ID = ?"; + stmt = conn.prepareStatement(deleteDBQuery); + stmt.setString(1, iotDeviceId); + int rows = stmt.executeUpdate(); + if (rows > 0) { + status = true; + if (log.isDebugEnabled()) { + log.debug("Digital Display device " + iotDeviceId + " data has deleted" + + " from the Digital Display database."); + } + } + } catch (SQLException e) { + String msg = "Error occurred while deleting Digital Display device " + iotDeviceId; + log.error(msg, e); + throw new DigitalDisplayDeviceMgtPluginException(msg, e); + } finally { + DigitalDisplayUtils.cleanupResources(stmt, null); + } + return status; + } + + public List getAllDevices() throws DigitalDisplayDeviceMgtPluginException { + Connection conn = null; + PreparedStatement stmt = null; + ResultSet resultSet = null; + Device iotDevice; + List iotDevices = new ArrayList(); + + try { + conn = DigitalDisplayDAO.getConnection(); + String selectDBQuery = + "SELECT DIGITAL_DISPLAY_DEVICE_ID, DEVICE_NAME FROM DIGITAL_DISPLAY_DEVICE"; + stmt = conn.prepareStatement(selectDBQuery); + resultSet = stmt.executeQuery(); + while (resultSet.next()) { + iotDevice = new Device(); + iotDevice.setDeviceIdentifier(resultSet.getString(DigitalDisplayConstants.DEVICE_PLUGIN_DEVICE_ID)); + iotDevice.setName(resultSet.getString(DigitalDisplayConstants.DEVICE_PLUGIN_DEVICE_NAME)); + iotDevices.add(iotDevice); + } + if (log.isDebugEnabled()) { + log.debug("All Digital Display device details have fetched from Digital Display database."); + } + return iotDevices; + } catch (SQLException e) { + String msg = "Error occurred while fetching all Digital Display device data'"; + log.error(msg, e); + throw new DigitalDisplayDeviceMgtPluginException(msg, e); + } finally { + DigitalDisplayUtils.cleanupResources(stmt, resultSet); + DigitalDisplayDAO.closeConnection(); + } + } +} \ No newline at end of file diff --git a/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.plugin/src/main/java/org/wso2/carbon/device/mgt/iot/digitaldisplay/plugin/impl/feature/DigitalDisplayFeatureManager.java b/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.plugin/src/main/java/org/wso2/carbon/device/mgt/iot/digitaldisplay/plugin/impl/feature/DigitalDisplayFeatureManager.java new file mode 100644 index 0000000000..5ed07461af --- /dev/null +++ b/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.plugin/src/main/java/org/wso2/carbon/device/mgt/iot/digitaldisplay/plugin/impl/feature/DigitalDisplayFeatureManager.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2016, 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. + */ + +package org.wso2.carbon.device.mgt.iot.digitaldisplay.plugin.impl.feature; + +import org.wso2.carbon.device.mgt.common.DeviceManagementException; +import org.wso2.carbon.device.mgt.common.Feature; +import org.wso2.carbon.device.mgt.common.FeatureManager; +import org.wso2.carbon.device.mgt.extensions.feature.mgt.GenericFeatureManager; +import org.wso2.carbon.device.mgt.iot.digitaldisplay.plugin.constants.DigitalDisplayConstants; + +import java.util.List; + +public class DigitalDisplayFeatureManager implements FeatureManager { + @Override + public boolean addFeature(Feature feature) throws DeviceManagementException { + return false; + } + + @Override + public boolean addFeatures(List features) throws DeviceManagementException { + return false; + } + + @Override + public Feature getFeature(String name) throws DeviceManagementException { + GenericFeatureManager genericFeatureManager = GenericFeatureManager.getInstance(); + return genericFeatureManager.getFeature(DigitalDisplayConstants.DEVICE_TYPE, name); + } + + @Override + public List getFeatures() throws DeviceManagementException { + GenericFeatureManager genericFeatureManager = GenericFeatureManager.getInstance(); + return genericFeatureManager.getFeatures(DigitalDisplayConstants.DEVICE_TYPE); + } + + @Override + public boolean removeFeature(String name) throws DeviceManagementException { + return false; + } + + @Override + public boolean addSupportedFeaturesToDB() throws DeviceManagementException { + return false; + } +} diff --git a/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.plugin/src/main/java/org/wso2/carbon/device/mgt/iot/digitaldisplay/plugin/impl/util/DeviceSchemaInitializer.java b/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.plugin/src/main/java/org/wso2/carbon/device/mgt/iot/digitaldisplay/plugin/impl/util/DeviceSchemaInitializer.java new file mode 100644 index 0000000000..a527d26d73 --- /dev/null +++ b/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.plugin/src/main/java/org/wso2/carbon/device/mgt/iot/digitaldisplay/plugin/impl/util/DeviceSchemaInitializer.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2016, 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.wso2.carbon.device.mgt.iot.digitaldisplay.plugin.impl.util; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.utils.CarbonUtils; +import org.wso2.carbon.utils.dbcreator.DatabaseCreator; + +import javax.sql.DataSource; +import java.io.File; + +/** + * Provides methods for initializing the database script. + */ +public class DeviceSchemaInitializer extends DatabaseCreator{ + + private static final Log log = LogFactory.getLog(DeviceSchemaInitializer.class); + private static final String setupSQLScriptBaseLocation = CarbonUtils.getCarbonHome() + File.separator + "dbscripts" + + File.separator + "cdm" + File.separator + "plugins" + File.separator; + + public DeviceSchemaInitializer(DataSource dataSource) { + super(dataSource); + } + + @Override + protected String getDbScriptLocation(String databaseType) { + String scriptName = databaseType + ".sql"; + if (log.isDebugEnabled()) { + log.debug("Loading database script from :" + scriptName); + } + return setupSQLScriptBaseLocation.replaceFirst("DBTYPE", databaseType) + scriptName; + } +} diff --git a/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.plugin/src/main/java/org/wso2/carbon/device/mgt/iot/digitaldisplay/plugin/impl/util/DigitalDisplayUtils.java b/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.plugin/src/main/java/org/wso2/carbon/device/mgt/iot/digitaldisplay/plugin/impl/util/DigitalDisplayUtils.java new file mode 100644 index 0000000000..2e071d3618 --- /dev/null +++ b/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.plugin/src/main/java/org/wso2/carbon/device/mgt/iot/digitaldisplay/plugin/impl/util/DigitalDisplayUtils.java @@ -0,0 +1,112 @@ +/* + * 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.wso2.carbon.device.mgt.iot.digitaldisplay.plugin.impl.util; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.device.mgt.common.Device; +import org.wso2.carbon.device.mgt.iot.digitaldisplay.plugin.constants.DigitalDisplayConstants; +import org.wso2.carbon.device.mgt.iot.digitaldisplay.plugin.exception.DigitalDisplayDeviceMgtPluginException; + +import javax.naming.Context; +import javax.naming.InitialContext; +import javax.naming.NamingException; +import javax.sql.DataSource; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.List; +import java.util.Map; + +/** + * Contains utility methods used by Digital Display plugin. + */ +public class DigitalDisplayUtils { + + private static Log log = LogFactory.getLog(DigitalDisplayUtils.class); + + public static String getDeviceProperty(List deviceProperties, String propertyKey) { + String deviceProperty = ""; + for(Device.Property property :deviceProperties){ + if(propertyKey.equals(property.getName())){ + deviceProperty = property.getValue(); + } + } + return deviceProperty; + } + + public static Device.Property getProperty(String property, String value) { + if (property != null) { + Device.Property prop = new Device.Property(); + prop.setName(property); + prop.setValue(value); + return prop; + } + return null; + } + + public static void cleanupResources(Connection conn, PreparedStatement stmt, ResultSet rs) { + if (rs != null) { + try { + rs.close(); + } catch (SQLException e) { + log.warn("Error occurred while closing result set", e); + } + } + if (stmt != null) { + try { + stmt.close(); + } catch (SQLException e) { + log.warn("Error occurred while closing prepared statement", e); + } + } + if (conn != null) { + try { + conn.close(); + } catch (SQLException e) { + log.warn("Error occurred while closing database connection", e); + } + } + } + + public static void cleanupResources(PreparedStatement stmt, ResultSet rs) { + cleanupResources(null, stmt, rs); + } + + /** + * Creates the device management schema. + */ + public static void setupDeviceManagementSchema() throws DigitalDisplayDeviceMgtPluginException { + try { + Context ctx = new InitialContext(); + DataSource dataSource = (DataSource) ctx.lookup(DigitalDisplayConstants.DATA_SOURCE_NAME); + DeviceSchemaInitializer initializer = + new DeviceSchemaInitializer(dataSource); + log.info("Initializing device management repository database schema"); + initializer.createRegistryDatabase(); + + } catch (NamingException e) { + log.error("Error while looking up the data source: " + DigitalDisplayConstants.DATA_SOURCE_NAME); + } catch (Exception e) { + throw new DigitalDisplayDeviceMgtPluginException("Error occurred while initializing Iot Device " + + "Management database schema", e); + } + } +} diff --git a/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.plugin/src/main/java/org/wso2/carbon/device/mgt/iot/digitaldisplay/plugin/internal/DigitalDisplayManagementServiceComponent.java b/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.plugin/src/main/java/org/wso2/carbon/device/mgt/iot/digitaldisplay/plugin/internal/DigitalDisplayManagementServiceComponent.java new file mode 100644 index 0000000000..52a6fdec85 --- /dev/null +++ b/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.plugin/src/main/java/org/wso2/carbon/device/mgt/iot/digitaldisplay/plugin/internal/DigitalDisplayManagementServiceComponent.java @@ -0,0 +1,85 @@ +/* + * 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.wso2.carbon.device.mgt.iot.digitaldisplay.plugin.internal; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.osgi.framework.BundleContext; +import org.osgi.framework.ServiceRegistration; +import org.osgi.service.component.ComponentContext; +import org.wso2.carbon.device.mgt.common.spi.DeviceManagementService; +import org.wso2.carbon.device.mgt.iot.digitaldisplay.plugin.exception.DigitalDisplayDeviceMgtPluginException; +import org.wso2.carbon.device.mgt.iot.digitaldisplay.plugin.impl.DigitalDisplayManagerService; +import org.wso2.carbon.device.mgt.iot.digitaldisplay.plugin.impl.util.DigitalDisplayUtils; + +/** + * @scr.component name="org.wso2.carbon.device.mgt.iot.digitaldisplay.plugin.internal.DigitalDisplayManagementServiceComponent" + * immediate="true" + */ +public class DigitalDisplayManagementServiceComponent { + + private ServiceRegistration digitalDisplayServiceRegRef; + private static final Log log = LogFactory.getLog(DigitalDisplayManagementServiceComponent.class); + + protected void activate(ComponentContext ctx) { + if (log.isDebugEnabled()) { + log.debug("Activating Digital Display Management Service Component"); + } + try { + BundleContext bundleContext = ctx.getBundleContext(); + digitalDisplayServiceRegRef = + bundleContext.registerService(DeviceManagementService.class.getName(), new + DigitalDisplayManagerService(), null); + String setupOption = System.getProperty("setup"); + if (setupOption != null) { + if (log.isDebugEnabled()) { + log.debug("-Dsetup is enabled. Iot Device management repository schema initialization is about " + + "to begin"); + } + try { + DigitalDisplayUtils.setupDeviceManagementSchema(); + } catch (DigitalDisplayDeviceMgtPluginException e) { + log.error("Exception occurred while initializing device management database schema", e); + } + } + if (log.isDebugEnabled()) { + log.debug("Digital Display Management Service Component has been successfully activated"); + } + } catch (Throwable e) { + log.error("Error occurred while activating Digital Display Management Service Component", e); + } + } + + protected void deactivate(ComponentContext ctx) { + if (log.isDebugEnabled()) { + log.debug("De-activating DigitalDisplay Management Service Component"); + } + try { + if (digitalDisplayServiceRegRef != null) { + digitalDisplayServiceRegRef.unregister(); + } + if (log.isDebugEnabled()) { + log.debug("DigitalDisplay Management Service Component has been successfully de-activated"); + } + } catch (Throwable e) { + log.error("Error occurred while de-activating Iot Device Management bundle", e); + } + } + +} diff --git a/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.ui/pom.xml b/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.ui/pom.xml new file mode 100644 index 0000000000..ea1b8629a3 --- /dev/null +++ b/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.ui/pom.xml @@ -0,0 +1,62 @@ + + + + + + + + digital-display-plugin + org.wso2.carbon.devicemgt-plugins + 2.1.0-SNAPSHOT + ../pom.xml + + + 4.0.0 + org.wso2.carbon.device.mgt.iot.digitaldisplay.ui + WSO2 Carbon - IoT Server Digital Display UI + pom + + + + + maven-assembly-plugin + 2.5.5 + + ${project.artifactId}-${carbon.device.mgt.version} + false + + src/assembly/src.xml + + + + + create-archive + package + + single + + + + + + + + \ No newline at end of file diff --git a/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.ui/src/assembly/src.xml b/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.ui/src/assembly/src.xml new file mode 100644 index 0000000000..2797034e07 --- /dev/null +++ b/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.ui/src/assembly/src.xml @@ -0,0 +1,36 @@ + + + + src + + zip + + false + ${basedir}/src + + + ${basedir}/src/main/resources/jaggeryapps/devicemgt + / + true + + + \ No newline at end of file diff --git a/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.digital_display.device-view/device-view.hbs b/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.digital_display.device-view/device-view.hbs new file mode 100644 index 0000000000..64d5eac674 --- /dev/null +++ b/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.digital_display.device-view/device-view.hbs @@ -0,0 +1,98 @@ +{{#zone "topCss"}} + +{{/zone}} + +{{#zone "device-thumbnail"}} + +{{/zone}} + +{{#zone "operation-status"}} + +{{/zone}} + +{{#zone "device-opetations"}} +
+ Operations +
+
+ {{unit "iot.unit.device.operation-bar" device=device}} +
+{{/zone}} + +{{#zone "device-detail-properties"}} + +
+
+ +
+
+ +
+
+ Display Status +
+
+
+
Display View
+ + + +
+
+
Content List
+
    +
    +
    +
    Device Statistics
    +
      +
      +
      +
      + +
      +
      +
      Operations Log
      +
      + +
      +
      + Not available yet +
      +
      +
      +
      +
      +
      +
      +
      +{{/zone}} + +{{#zone "bottomJs"}} + + {{js "js/websocket.js"}} +{{/zone}} \ No newline at end of file diff --git a/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.digital_display.device-view/device-view.js b/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.digital_display.device-view/device-view.js new file mode 100644 index 0000000000..9c203853b7 --- /dev/null +++ b/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.digital_display.device-view/device-view.js @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2016, 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("device-view.js"); + var deviceType = context.uriParams.deviceType; + var deviceId = request.getParameter("id"); + + var getProperty = require("process").getProperty; + var port = getProperty("carbon.https.port"); + var host = getProperty("carbon.local.ip"); + var sessionId = session.getId(); + 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") { + return {"device": device}; + } else { + response.sendError(404, "Device Id " + deviceId + "of type " + deviceType + " cannot be found!"); + exit(); + } + } +} \ No newline at end of file diff --git a/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.digital_display.device-view/device-view.json b/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.digital_display.device-view/device-view.json new file mode 100644 index 0000000000..9eecd8f5bf --- /dev/null +++ b/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.digital_display.device-view/device-view.json @@ -0,0 +1,3 @@ +{ + "version": "1.0.0" +} \ No newline at end of file diff --git a/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.digital_display.device-view/public/images/default-screen.png b/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.digital_display.device-view/public/images/default-screen.png new file mode 100644 index 0000000000..df11c247a2 Binary files /dev/null and b/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.digital_display.device-view/public/images/default-screen.png differ diff --git a/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.digital_display.device-view/public/images/display-icon.png b/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.digital_display.device-view/public/images/display-icon.png new file mode 100644 index 0000000000..90342742a6 Binary files /dev/null and b/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.digital_display.device-view/public/images/display-icon.png differ diff --git a/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.digital_display.device-view/public/images/thumb.png b/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.digital_display.device-view/public/images/thumb.png new file mode 100644 index 0000000000..3d2a829c6f Binary files /dev/null and b/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.digital_display.device-view/public/images/thumb.png differ diff --git a/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.digital_display.device-view/public/js/websocket.js b/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.digital_display.device-view/public/js/websocket.js new file mode 100644 index 0000000000..3075ba3263 --- /dev/null +++ b/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.digital_display.device-view/public/js/websocket.js @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2016, 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. + */ + +var displayClient = null; + +window.onload = function () { + connect(); +}; + +window.onbeforeunload = function () { + disconnect(); +} + +function connect() { + displayClient = new WebSocket("wss://" + host + ":" + port + "/digital_display/" + sessionId); + + displayClient.onmessage = function (event) { + var message = event.data; + var operationDiv = document.getElementById("div-operation-status"); + var type = message.split('||')[0]; + var reply = message.split('||')[1]; + + if (type.valueOf() == new String("Screenshot").valueOf()) { + document.getElementById('img').setAttribute('src', 'data:image/png;base64,' + reply); + document.getElementById('zoom-image').setAttribute('href', 'data:image/png;base64,' + reply); + } else if (type.valueOf() == new String("Success").valueOf()) { + $('#div-operation-status').removeClass('hidden'); + $('#div-operation-status').text(reply); + operationDiv.style.backgroundColor = '#DFF2BF'; + operationDiv.style.color = '#4F8A10'; + } else if (type.valueOf() == new String("Failed").valueOf()) { + $('#div-operation-status').removeClass('hidden'); + $('#div-operation-status').text(reply); + operationDiv.style.backgroundColor = '#FFBABA'; + operationDiv.style.color = '#D8000C'; + } else if (type.valueOf() == new String("ContentList").valueOf()) { + var resources = reply.split("-"); + var ul = document.getElementById("content-list"); + ul.innerHTML = ""; + for (i = 0; i < resources.length; i++) { + var li = document.createElement("li"); + li.appendChild(document.createTextNode(resources[i])); + ul.appendChild(li); + } + } else if (type.valueOf() == new String("DeviceStatus").valueOf()) { + var resources = reply.split("-"); + var ul = document.getElementById("device-statics"); + ul.innerHTML = ""; + for (i = 0; i < resources.length; i++) { + var li = document.createElement("li"); + li.appendChild(document.createTextNode(resources[i])); + ul.appendChild(li); + } + } + + setTimeout(function () { + $('#div-operation-status').addClass('hidden'); + }, 10000); + + }; + +} + +function disconnect() { + displayClient.close(); +} \ No newline at end of file diff --git a/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.digital_display.type-view/public/images/display-icon.png b/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.digital_display.type-view/public/images/display-icon.png new file mode 100644 index 0000000000..90342742a6 Binary files /dev/null and b/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.digital_display.type-view/public/images/display-icon.png differ diff --git a/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.digital_display.type-view/public/images/myDevices_analytics.png b/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.digital_display.type-view/public/images/myDevices_analytics.png new file mode 100644 index 0000000000..8f55999a3c Binary files /dev/null and b/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.digital_display.type-view/public/images/myDevices_analytics.png differ diff --git a/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.digital_display.type-view/public/images/operations/add-resource.png b/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.digital_display.type-view/public/images/operations/add-resource.png new file mode 100644 index 0000000000..d00ee206b1 Binary files /dev/null and b/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.digital_display.type-view/public/images/operations/add-resource.png differ diff --git a/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.digital_display.type-view/public/images/operations/edit-sequence.png b/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.digital_display.type-view/public/images/operations/edit-sequence.png new file mode 100644 index 0000000000..a194713b5e Binary files /dev/null and b/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.digital_display.type-view/public/images/operations/edit-sequence.png differ diff --git a/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.digital_display.type-view/public/images/operations/get-content-list.png b/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.digital_display.type-view/public/images/operations/get-content-list.png new file mode 100644 index 0000000000..ebda779273 Binary files /dev/null and b/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.digital_display.type-view/public/images/operations/get-content-list.png differ diff --git a/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.digital_display.type-view/public/images/operations/get-device-status.png b/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.digital_display.type-view/public/images/operations/get-device-status.png new file mode 100644 index 0000000000..caf3f39ef3 Binary files /dev/null and b/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.digital_display.type-view/public/images/operations/get-device-status.png differ diff --git a/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.digital_display.type-view/public/images/operations/remove-resource.png b/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.digital_display.type-view/public/images/operations/remove-resource.png new file mode 100644 index 0000000000..fc36e88284 Binary files /dev/null and b/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.digital_display.type-view/public/images/operations/remove-resource.png differ diff --git a/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.digital_display.type-view/public/images/operations/restart-browser.png b/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.digital_display.type-view/public/images/operations/restart-browser.png new file mode 100644 index 0000000000..cbf28a1739 Binary files /dev/null and b/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.digital_display.type-view/public/images/operations/restart-browser.png differ diff --git a/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.digital_display.type-view/public/images/operations/restart-display.png b/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.digital_display.type-view/public/images/operations/restart-display.png new file mode 100644 index 0000000000..ab98a02fe3 Binary files /dev/null and b/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.digital_display.type-view/public/images/operations/restart-display.png differ diff --git a/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.digital_display.type-view/public/images/operations/restart-server.png b/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.digital_display.type-view/public/images/operations/restart-server.png new file mode 100644 index 0000000000..eb947a87fb Binary files /dev/null and b/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.digital_display.type-view/public/images/operations/restart-server.png differ diff --git a/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.digital_display.type-view/public/images/operations/screenshot.png b/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.digital_display.type-view/public/images/operations/screenshot.png new file mode 100644 index 0000000000..8a5bca3d1a Binary files /dev/null and b/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.digital_display.type-view/public/images/operations/screenshot.png differ diff --git a/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.digital_display.type-view/public/images/operations/terminate-display.png b/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.digital_display.type-view/public/images/operations/terminate-display.png new file mode 100755 index 0000000000..9919cfbb5a Binary files /dev/null and b/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.digital_display.type-view/public/images/operations/terminate-display.png differ diff --git a/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.digital_display.type-view/public/images/operations/upload-content.png b/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.digital_display.type-view/public/images/operations/upload-content.png new file mode 100644 index 0000000000..c93072691a Binary files /dev/null and b/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.digital_display.type-view/public/images/operations/upload-content.png differ diff --git a/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.digital_display.type-view/public/images/schematicsGuide.png b/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.digital_display.type-view/public/images/schematicsGuide.png new file mode 100644 index 0000000000..aec732f5f7 Binary files /dev/null and b/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.digital_display.type-view/public/images/schematicsGuide.png differ diff --git a/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.digital_display.type-view/public/images/thumb.png b/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.digital_display.type-view/public/images/thumb.png new file mode 100644 index 0000000000..3d2a829c6f Binary files /dev/null and b/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.digital_display.type-view/public/images/thumb.png differ diff --git a/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.digital_display.type-view/public/js/download.js b/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.digital_display.type-view/public/js/download.js new file mode 100644 index 0000000000..19e9bf2ce9 --- /dev/null +++ b/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.digital_display.type-view/public/js/download.js @@ -0,0 +1,191 @@ +/* + * Copyright (c) 2016, 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. + */ + +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() { + var deviceName; + $('.new-device-name').each(function () { + if (this.value != "") { + deviceName = this.value; + } + }); + var deviceNameFormat = /^[^~?!#$:;%^*`+={}\[\]\\()|<>,'"]{1,30}$/; + if (deviceName && deviceNameFormat.test(deviceName)) { + $('#downloadForm').submit(); + hidePopup(); + $(modalPopupContent).html($('#device-agent-downloading-content').html()); + showPopup(); + setTimeout(function () { + hidePopup(); + }, 1000); + }else { + $("#invalid-username-error-msg span").text("Invalid device name"); + $("#invalid-username-error-msg").removeClass("hidden"); + } +} + +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/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.digital_display.type-view/public/js/jquery.validate.js b/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.digital_display.type-view/public/js/jquery.validate.js new file mode 100644 index 0000000000..fe7ecf07a6 --- /dev/null +++ b/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.digital_display.type-view/public/js/jquery.validate.js @@ -0,0 +1,1227 @@ +/* + * Copyright (c) 2016, 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($) { + +$.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/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.digital_display.type-view/type-view.hbs b/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.digital_display.type-view/type-view.hbs new file mode 100644 index 0000000000..c516ec2b6e --- /dev/null +++ b/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.digital_display.type-view/type-view.hbs @@ -0,0 +1,292 @@ +
      +

      Digital Display

      +
      +
      + +
      + +
      + +
      + +
      + +
      +

      What it Does

      +
      +

      Connect a Digital Message Display to WSO2 IoT Server and manage + it.

      +
      +

      What You Need

      +
      +
        +
      • + ITEM 01 +    Raspberry Pi with SD Card(Internet Enabled [Wifi or Ethernet]). +
      • +
      • + ITEM 02 +    A Digital Display with HDMI Cable. +
      • +
      • + STEP 03 +    Proceed to [Prepare] section. +
      +
      + + View API   + + + Download Agent + +
      + +
      +
      + +
      + +
      + +
      +
      + +
      +
      + +
      +
      + +
      +
      + +
      +

      +
      +
      +

      Prepare

      +
      +
        +
      • + 01 +    Connect a monitor to your RaspberryPi via HDMI cable. +
      • +
      • + 02 +    Configure RaspberryPi to connect to the Internet. +
      • +
      • + 03 +    Go ahead and [Download Agent] the Digital Display Agent. +
      • +
      • + 04 +    Unzip downloaded agent. +
      • +
      • + 05 +    Start Agent by running installpackages.sh followed by wso2Agent.sh +
      • +
      +
      +
      +
      +

      Digital-Display Setup

      +
      +

      Click on the image to zoom

      +
      + + + +
      +
      +
      + +
      +

      Try Out

      +
      +
        +
      • + 01 +    You can view all your connected devices at + [Device Management] page. +
      • +
      • + 02 +    Select one of connected devices and check for available control + operations. +
      +
      +

      Click on the image to zoom

      +
      + + + +
      +
      + + +{{#zone "bottomJs"}} + {{js "/js/download.js"}} + {{js "/js/jquery.validate.js"}} +{{/zone}} diff --git a/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.digital_display.type-view/type-view.json b/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.digital_display.type-view/type-view.json new file mode 100644 index 0000000000..9eecd8f5bf --- /dev/null +++ b/components/iot-plugins/digital-display-plugin/org.wso2.carbon.device.mgt.iot.digitaldisplay.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.digital_display.type-view/type-view.json @@ -0,0 +1,3 @@ +{ + "version": "1.0.0" +} \ No newline at end of file diff --git a/components/iot-plugins/digital-display-plugin/pom.xml b/components/iot-plugins/digital-display-plugin/pom.xml new file mode 100644 index 0000000000..ff50040e37 --- /dev/null +++ b/components/iot-plugins/digital-display-plugin/pom.xml @@ -0,0 +1,61 @@ + + + + + + + org.wso2.carbon.devicemgt-plugins + iot-plugins + 2.1.0-SNAPSHOT + ../pom.xml + + + 4.0.0 + digital-display-plugin + pom + WSO2 Carbon - Arduino Plugin + http://wso2.org + + + org.wso2.carbon.device.mgt.iot.digitaldisplay.controller.api + org.wso2.carbon.device.mgt.iot.digitaldisplay.manager.api + org.wso2.carbon.device.mgt.iot.digitaldisplay.plugin + org.wso2.carbon.device.mgt.iot.digitaldisplay.ui + + + + + + + org.apache.felix + maven-scr-plugin + 1.7.2 + + + generate-scr-scrdescriptor + + scr + + + + + + + + diff --git a/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.controller.api/pom.xml b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.controller.api/pom.xml new file mode 100644 index 0000000000..5f70578fb5 --- /dev/null +++ b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.controller.api/pom.xml @@ -0,0 +1,254 @@ + + + + drone-analyzer-plugin + org.wso2.carbon.devicemgt-plugins + 2.1.0-SNAPSHOT + ../pom.xml + + + 4.0.0 + org.wso2.carbon.device.mgt.iot.droneanalyzer.controller.api + war + WSO2 Carbon - IoT Server Drone Analyzer Controller API + http://maven.apache.org + + + + + org.wso2.carbon.devicemgt + org.wso2.carbon.device.mgt.common + provided + + + org.wso2.carbon.devicemgt + org.wso2.carbon.device.mgt.core + provided + + + org.apache.axis2.wso2 + axis2-client + + + + + + org.wso2.carbon.devicemgt + org.wso2.carbon.device.mgt.analytics + provided + + + org.apache.axis2.wso2 + axis2-client + + + + + + org.wso2.carbon.devicemgt + org.wso2.carbon.certificate.mgt.core + provided + + + commons-codec.wso2 + commons-codec + + + + + + + + org.apache.cxf + cxf-rt-frontend-jaxws + provided + + + org.apache.cxf + cxf-rt-frontend-jaxrs + provided + + + org.apache.cxf + cxf-rt-transports-http + provided + + + + + org.eclipse.paho + org.eclipse.paho.client.mqttv3 + provided + + + + + org.springframework + spring-context + + + + + org.apache.httpcomponents + httpasyncclient + 4.1 + provided + + + org.wso2.carbon.devicemgt-plugins + org.wso2.carbon.device.mgt.iot + provided + + + org.wso2.carbon.devicemgt-plugins + org.wso2.carbon.device.mgt.iot.droneanalyzer.plugin + provided + + + + + org.codehaus.jackson + jackson-core-asl + + + org.codehaus.jackson + jackson-jaxrs + + + javax + javaee-web-api + provided + + + javax.ws.rs + jsr311-api + provided + + + commons-httpclient.wso2 + commons-httpclient + provided + + + + org.wso2.carbon + org.wso2.carbon.utils + provided + + + 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.wso2.carbon.devicemgt + org.wso2.carbon.device.mgt.extensions + provided + + + + commons-codec.wso2 + commons-codec + + + + org.igniterealtime.smack.wso2 + smack + provided + + + org.igniterealtime.smack.wso2 + smackx + provided + + + org.apache.commons + commons-collections4 + 4.0 + + + + org.wso2.carbon.devicemgt + org.wso2.carbon.apimgt.annotations + provided + + + + + + + + + + maven-compiler-plugin + + UTF-8 + ${wso2.maven.compiler.source} + ${wso2.maven.compiler.target} + + + + maven-war-plugin + + src/main/webapp/WEB-INF/web.xml + drone_analyzer + + + + + diff --git a/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.controller.api/src/main/java/org/wso2/carbon/device/mgt/iot/droneanalyzer/controller/api/impl/DroneControllerService.java b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.controller.api/src/main/java/org/wso2/carbon/device/mgt/iot/droneanalyzer/controller/api/impl/DroneControllerService.java new file mode 100644 index 0000000000..17ea3c5d08 --- /dev/null +++ b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.controller.api/src/main/java/org/wso2/carbon/device/mgt/iot/droneanalyzer/controller/api/impl/DroneControllerService.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2016, 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.wso2.carbon.device.mgt.iot.droneanalyzer.controller.api.impl; + +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.apimgt.annotations.api.API; +import org.wso2.carbon.context.PrivilegedCarbonContext; +import org.wso2.carbon.device.mgt.common.DeviceManagementException; +import org.wso2.carbon.device.mgt.extensions.feature.mgt.annotations.DeviceType; +import org.wso2.carbon.device.mgt.iot.droneanalyzer.controller.api.impl.util.DroneAnalyzerServiceUtils; +import org.wso2.carbon.device.mgt.iot.droneanalyzer.plugin.controller.DroneController; +import org.wso2.carbon.device.mgt.iot.droneanalyzer.plugin.controller.impl.DroneControllerImpl; +import javax.servlet.http.HttpServletResponse; +import javax.ws.rs.*; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.Response; +import java.util.concurrent.ConcurrentHashMap; + +@API( name="drone_analyzer", version="1.0.0", context="/drone_analyzer", tags = {"drone_analyzer"}) +@DeviceType( value = "drone_analyzer") +public class DroneControllerService { + + private static org.apache.commons.logging.Log log = LogFactory.getLog(DroneControllerService.class); + @Context //injected response proxy supporting multiple thread + private HttpServletResponse response; + private ConcurrentHashMap deviceToIpMap = new ConcurrentHashMap<>(); + private DroneController droneController = new DroneControllerImpl(); + + + @Path("controller/register/{deviceId}/{ip}/{port}") + @POST + public Response registerDeviceIP(@PathParam("deviceId") String deviceId, @PathParam("ip") String deviceIP, + @PathParam("port") String devicePort, @Context HttpServletResponse response) { + try { + String result; + String deviceHttpEndpoint = deviceIP + ":" + devicePort; + deviceToIpMap.put(deviceId, deviceHttpEndpoint); + result = "Device-IP Registered"; + response.setStatus(Response.Status.OK.getStatusCode()); + if (log.isDebugEnabled()) { + log.debug(result); + } + return Response.ok(Response.Status.OK.getStatusCode()).build(); + } finally { + PrivilegedCarbonContext.endTenantFlow(); + } + } + + @Path("controller/device/{deviceId}/send_command") + @POST + /*@Feature( code="send_command", name="Send Command", type="operation", + description="Send Commands to Drone")*/ + public Response droneController(@PathParam("deviceId") String deviceId, + @FormParam("action") String action, @FormParam("duration") String duration, + @FormParam("speed") String speed){ + try { + DroneAnalyzerServiceUtils.sendControlCommand(droneController, deviceId, action, Double.valueOf(speed), + Double.valueOf(duration)); + return Response.status(Response.Status.ACCEPTED).build(); + } catch (DeviceManagementException e) { + log.error("Drone command didn't success. Try again, \n"+ e); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build(); + } finally { + PrivilegedCarbonContext.endTenantFlow(); + } + } +} diff --git a/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.controller.api/src/main/java/org/wso2/carbon/device/mgt/iot/droneanalyzer/controller/api/impl/DroneRealTimeService.java b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.controller.api/src/main/java/org/wso2/carbon/device/mgt/iot/droneanalyzer/controller/api/impl/DroneRealTimeService.java new file mode 100644 index 0000000000..c0c85bc94e --- /dev/null +++ b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.controller.api/src/main/java/org/wso2/carbon/device/mgt/iot/droneanalyzer/controller/api/impl/DroneRealTimeService.java @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2016, 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.wso2.carbon.device.mgt.iot.droneanalyzer.controller.api.impl; + + +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.device.mgt.iot.controlqueue.xmpp.XmppConfig; +import org.wso2.carbon.device.mgt.iot.droneanalyzer.controller.api.impl.transport.DroneAnalyzerXMPPConnector; +import org.wso2.carbon.device.mgt.iot.droneanalyzer.controller.api.impl.trasformer.MessageTransformer; +import org.wso2.carbon.device.mgt.iot.service.IoTServerStartupListener; +import org.wso2.carbon.device.mgt.iot.droneanalyzer.plugin.constants.DroneConstants; +import javax.websocket.OnClose; +import javax.websocket.OnError; +import javax.websocket.OnMessage; +import javax.websocket.OnOpen; +import javax.websocket.Session; +import javax.websocket.server.ServerEndpoint; +import java.io.IOException; + +@ServerEndpoint("/datastream/drone_status") +public class DroneRealTimeService { + + private static org.apache.commons.logging.Log log = LogFactory.getLog(DroneRealTimeService.class); + private MessageTransformer messageController; + private DroneAnalyzerXMPPConnector xmppConnector; + + public DroneRealTimeService() { + Runnable connector = new Runnable() { + public void run() { + if (waitForServerStartup()) { + return; + } + messageController = new MessageTransformer(); + xmppConnector = new DroneAnalyzerXMPPConnector(messageController); + if (XmppConfig.getInstance().isEnabled()){ + xmppConnector.connect(); + } else { + log.warn("XMPP disabled in 'devicemgt-config.xml'. Hence, DroneAnalyzerXMPPConnector not started."); + } + } + }; + Thread connectorThread = new Thread(connector); + connectorThread.setDaemon(true); + connectorThread.start(); + } + + private boolean waitForServerStartup() { + while (!IoTServerStartupListener.isServerReady()) { + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + return true; + } + } + return false; + } + + @OnOpen + public void onOpen(Session session){ + log.info(session.getId() + " has opened a connection"); + try { + session.getBasicRemote().sendText("Connection Established"); + } catch (IOException e) { + log.error( e.getMessage()+"\n"+ e); + } + } + + @OnMessage + public void onMessage(String message, Session session){ + while (true) { + try { + if (messageController != null && !messageController.isEmptyQueue()) { + String messageControllerMessage = messageController.getMessage(); + session.getBasicRemote().sendText(messageControllerMessage); + break; + } + Thread.sleep(DroneConstants.MINIMUM_TIME_DURATION); + } catch (IOException | InterruptedException ex) { + log.error(ex.getMessage(), ex); + break; + } + } + } + + @OnClose + public void onClose(Session session){ + try { + xmppConnector.disconnect(); + log.info("XMPP connection is disconnected"); + } + catch (Exception e) { + log.error(e.getMessage() + "\n" + e); + } + log.info("Session " + session.getId() + " has ended"); + } + + @OnError + public void onError(Session session, Throwable t) { + try { + session.getBasicRemote().sendText("Connection closed"); + xmppConnector.disconnect(); + log.info("XMPP connection is disconnected"); + } catch (Exception e) { + log.error(e.getMessage()+"\n"+ e); + } + log.info("Session " + session.getId() + " has ended"); + } + +} \ No newline at end of file diff --git a/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.controller.api/src/main/java/org/wso2/carbon/device/mgt/iot/droneanalyzer/controller/api/impl/exception/DroneAnalyzerException.java b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.controller.api/src/main/java/org/wso2/carbon/device/mgt/iot/droneanalyzer/controller/api/impl/exception/DroneAnalyzerException.java new file mode 100644 index 0000000000..7421c59328 --- /dev/null +++ b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.controller.api/src/main/java/org/wso2/carbon/device/mgt/iot/droneanalyzer/controller/api/impl/exception/DroneAnalyzerException.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.wso2.carbon.device.mgt.iot.droneanalyzer.controller.api.impl.exception; + +public class DroneAnalyzerException extends Exception { + private static final long serialVersionUID = 118512086958330189L; + + public DroneAnalyzerException(String errorMessage) { + super(errorMessage); + } + + public DroneAnalyzerException(String errorMessage, Throwable throwable) { + super(errorMessage, throwable); + } +} diff --git a/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.controller.api/src/main/java/org/wso2/carbon/device/mgt/iot/droneanalyzer/controller/api/impl/transport/DroneAnalyzerXMPPConnector.java b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.controller.api/src/main/java/org/wso2/carbon/device/mgt/iot/droneanalyzer/controller/api/impl/transport/DroneAnalyzerXMPPConnector.java new file mode 100644 index 0000000000..a4130c06b4 --- /dev/null +++ b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.controller.api/src/main/java/org/wso2/carbon/device/mgt/iot/droneanalyzer/controller/api/impl/transport/DroneAnalyzerXMPPConnector.java @@ -0,0 +1,160 @@ +/* + * Copyright (c) 2016, 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.wso2.carbon.device.mgt.iot.droneanalyzer.controller.api.impl.transport; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.jivesoftware.smack.packet.Message; +import org.wso2.carbon.device.mgt.iot.controlqueue.xmpp.XmppConfig; +import org.wso2.carbon.device.mgt.iot.droneanalyzer.controller.api.impl.trasformer.MessageTransformer; +import org.wso2.carbon.device.mgt.iot.droneanalyzer.plugin.constants.DroneConstants; +import org.wso2.carbon.device.mgt.iot.transport.TransportHandlerException; +import org.wso2.carbon.device.mgt.iot.transport.xmpp.XMPPTransportHandler; + +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +public class DroneAnalyzerXMPPConnector extends XMPPTransportHandler { + private static Log log = LogFactory.getLog(DroneAnalyzerXMPPConnector.class); + + private static String xmppServerIP; + private static String xmppAdminUsername; + private static String xmppAdminPassword; + private static String xmppAdminAccountJID; + private MessageTransformer messageTransformer; + private ScheduledFuture connectorServiceHandler; + private ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor(); + + public DroneAnalyzerXMPPConnector(MessageTransformer messageTransformer) { + super(XmppConfig.getInstance().getXmppServerIP(), XmppConfig.getInstance().getSERVER_CONNECTION_PORT()); + this.messageTransformer = messageTransformer; + } + + @Override + public void connect() { + Runnable connector = new Runnable() { + @Override + public void run() { + if (!isConnected()) { + try { + initConnector(); + connectToServer(); + loginToServer(xmppAdminUsername, xmppAdminPassword, null); + setFilterOnReceiver(xmppAdminAccountJID); + } catch (TransportHandlerException e) { + if (log.isDebugEnabled()) { + log.warn("Connection/Login to XMPP server at: " + server + " as " + + xmppAdminUsername + " failed for device-type [" + + DroneConstants.DEVICE_TYPE + "].", e); + } + } + } + } + }; + connectorServiceHandler = service.scheduleAtFixedRate(connector, 0, timeoutInterval, TimeUnit.MILLISECONDS); + } + + public void initConnector() { + xmppServerIP = XmppConfig.getInstance().getXmppServerIP(); + xmppAdminUsername = XmppConfig.getInstance().getXmppUsername(); + xmppAdminPassword = XmppConfig.getInstance().getXmppPassword(); + xmppAdminAccountJID = xmppAdminUsername + "@" + xmppServerIP; + } + + @Override + public void processIncomingMessage(Message message) throws TransportHandlerException { + try { + String from = message.getFrom(); + String inboundMessage = message.getBody(); + int indexOfSlash = from.indexOf("/"); + if (indexOfSlash == 0) { + if (log.isDebugEnabled()) { + log.debug("Required resource not available."); + } + } else { + String resource = from.substring(indexOfSlash + 1, from.length()); + if ((inboundMessage != null) && (resource.equals(DroneConstants.MESSAGE_RESOURCE))) { + messageTransformer.messageTranslater(inboundMessage); + } else { + if (log.isDebugEnabled()) { + log.debug("Message is empty or it is not belongs to " + xmppAdminUsername); + } + } + } + } catch (ArrayIndexOutOfBoundsException e) { + log.error("Wrong message format: input message", e); + } catch (RuntimeException e) { + log.error("Unexpected error has been occurred, ", e); + } + } + + @Override + public void publishDeviceData(String... publishData) throws TransportHandlerException { + String xmppJID = publishData[0]; + String xmppMessage = publishData[1]; + String xmppSubject = publishData[2]; + sendXMPPMessage(xmppJID, xmppMessage, xmppSubject); + } + + public void disconnect() { + Runnable stopConnection = new Runnable() { + public void run() { + while (isConnected()) { + connectorServiceHandler.cancel(true); + closeConnection(); + if (log.isDebugEnabled()) { + log.warn("Unable to 'STOP' connection to XMPP server at: " + server + + " for user - " + xmppAdminUsername); + } + try { + Thread.sleep(timeoutInterval); + } catch (InterruptedException e1) { + log.error("XMPP-Terminator: Thread Sleep Interrupt Exception for " + + DroneConstants.DEVICE_TYPE + " type.", e1); + } + } + } + }; + Thread terminatorThread = new Thread(stopConnection); + terminatorThread.setDaemon(true); + terminatorThread.start(); + } + + @Override + public void processIncomingMessage() throws TransportHandlerException { + + } + + @Override + public void processIncomingMessage(Message message, String... messageParams) throws TransportHandlerException { + + } + + @Override + public void publishDeviceData() throws TransportHandlerException { + + } + + @Override + public void publishDeviceData(Message publishData) throws TransportHandlerException { + + } +} diff --git a/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.controller.api/src/main/java/org/wso2/carbon/device/mgt/iot/droneanalyzer/controller/api/impl/trasformer/MessageTransformer.java b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.controller.api/src/main/java/org/wso2/carbon/device/mgt/iot/droneanalyzer/controller/api/impl/trasformer/MessageTransformer.java new file mode 100644 index 0000000000..74d0807717 --- /dev/null +++ b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.controller.api/src/main/java/org/wso2/carbon/device/mgt/iot/droneanalyzer/controller/api/impl/trasformer/MessageTransformer.java @@ -0,0 +1,134 @@ +/* + * 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.wso2.carbon.device.mgt.iot.droneanalyzer.controller.api.impl.trasformer; + + +import org.apache.commons.collections4.queue.CircularFifoQueue; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.codehaus.jackson.JsonNode; +import org.codehaus.jackson.JsonProcessingException; +import org.codehaus.jackson.map.ObjectMapper; +import org.wso2.carbon.device.mgt.iot.droneanalyzer.plugin.constants.DroneConstants; +import org.wso2.carbon.device.mgt.iot.droneanalyzer.plugin.constants.MessageConfig; + +import java.io.IOException; + + +public class MessageTransformer { + + private Log log = LogFactory.getLog(MessageTransformer.class); + private CircularFifoQueue sharedQueue; + + private String outboundMessageFormatForSimulator = "{\"quatanium_val\":[%f, %f, %f, %f]," + + "\"basicParam\":{\"velocity\":[%f, %f, %f], \"global_location\":[%f, %f, %f]},\"battery_level\":%f, \"device_type\":\"SIMULATOR\"}"; + private String outboundMessageFormatForIrisDrone = "{\"quatanium_val\":[%f, %f, %f]," + + "\"basicParam\":{\"velocity\":[%f, %f, %f], \"global_location\":[%f, %f, %f]},\"battery_level\":%f," + + "\"device_type\":\"IRIS_DRONE\"}"; + + public MessageTransformer(){ + sharedQueue = new CircularFifoQueue(DroneConstants.MAXIMUM_BUFFERE_SIZE_OF_SHARED_QUEUE); + } + + private void messageTranslaterForSimulator(JsonNode inbound_message){ + JsonNode node = inbound_message; + String outboundMessage; + try { + JsonNode velocity = node.get(MessageConfig.OUT_BASIC_PARAM_VAL).get(MessageConfig.OUT_BASIC_PARAM_VELOCITY); + JsonNode globalLocation = node.get(MessageConfig.OUT_BASIC_PARAM_VAL).get( + MessageConfig.OUT_BASIC_PARAM_GLOBAL_LOCATION); + JsonNode quataniumVals = node.get(MessageConfig.OUT_QUATANNIM_VAL); + JsonNode batteryLevel = node.get(MessageConfig.OUT_BATTERY_LEVEL); + outboundMessage = String.format(outboundMessageFormatForSimulator, sTd(quataniumVals.get(0)), + sTd(quataniumVals.get(1)), sTd(quataniumVals.get(2)), sTd(quataniumVals.get(0)), + sTd(velocity.get(0)), sTd(velocity.get(1)), sTd(velocity.get(2)), sTd(globalLocation.get(0)), + sTd(globalLocation.get(1)), sTd(globalLocation.get(2)), sTd(batteryLevel)); + sharedQueue.add(outboundMessage); + } catch (Exception e) { + log.error(e.getMessage()+",\n"+ e); + } + } + + private void messageTranslaterForIRISDrone(JsonNode inbound_message){ + JsonNode node = inbound_message; + String outboundMessage; + try { + + JsonNode velocity = node.get(MessageConfig.OUT_BASIC_PARAM_VAL).get(MessageConfig.OUT_BASIC_PARAM_VELOCITY); + JsonNode globalLocation = node.get(MessageConfig.OUT_BASIC_PARAM_VAL).get( + MessageConfig.OUT_BASIC_PARAM_GLOBAL_LOCATION); + JsonNode quataniumVals = node.get(MessageConfig.OUT_QUATANNIM_VAL); + JsonNode batteryLevel = node.get(MessageConfig.OUT_BATTERY_LEVEL); + outboundMessage = String.format(outboundMessageFormatForIrisDrone, sTd(quataniumVals.get(0)), + sTd(quataniumVals.get(1)), sTd(quataniumVals.get(2)), sTd(velocity.get(0)), + sTd(velocity.get(1)), sTd(velocity.get(2)), sTd(globalLocation.get(0)), + sTd(globalLocation.get(1)), sTd(globalLocation.get(2)), sTd(batteryLevel)); + sharedQueue.add(outboundMessage); + + }catch (Exception e) { + log.error(e.getMessage()+",\n"+ e); + } + } + + public void messageTranslater(String inbound_message){ + JsonNode actualMessage; + ObjectMapper objectMapper = new ObjectMapper(); + try { + actualMessage = objectMapper.readValue(inbound_message, JsonNode.class); + JsonNode deviceType = actualMessage.get(MessageConfig.IN_DEVICE_TYPE); + switch (deviceType.getTextValue()) { + case MessageConfig.IN_IRIS_DRONE: + messageTranslaterForIRISDrone(actualMessage); + break; + case MessageConfig.IN_SIMULATOR: + messageTranslaterForSimulator(actualMessage); + break; + default: + if(log.isDebugEnabled()){ + log.debug("Wrong message format"); + } + } + } catch (JsonProcessingException e) { + log.error("Incoming message might be corrupted, "+ e); + } catch (IOException e) { + log.error(e.getMessage(), e); + } + } + + private double sTd(JsonNode s) + { + return Double.parseDouble(s.toString()); + } + + public String getMessage() { + try{ + if(sharedQueue.isEmpty() || sharedQueue == null){ + return ""; + } + return sharedQueue.remove(); + }catch(Exception e) { + log.error("There is no more messages to send or internal server error has been occurred, \n"+ e ); + return ""; + } + } + + public boolean isEmptyQueue(){ + return sharedQueue != null? sharedQueue.isEmpty():false; + } + +} diff --git a/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.controller.api/src/main/java/org/wso2/carbon/device/mgt/iot/droneanalyzer/controller/api/impl/util/DroneAnalyzerServiceUtils.java b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.controller.api/src/main/java/org/wso2/carbon/device/mgt/iot/droneanalyzer/controller/api/impl/util/DroneAnalyzerServiceUtils.java new file mode 100644 index 0000000000..0b6a3abff6 --- /dev/null +++ b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.controller.api/src/main/java/org/wso2/carbon/device/mgt/iot/droneanalyzer/controller/api/impl/util/DroneAnalyzerServiceUtils.java @@ -0,0 +1,96 @@ + +/* + * 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.wso2.carbon.device.mgt.iot.droneanalyzer.controller.api.impl.util; + +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.device.mgt.common.DeviceManagementException; +import org.wso2.carbon.device.mgt.iot.controlqueue.xmpp.XmppConfig; +import org.wso2.carbon.device.mgt.iot.droneanalyzer.controller.api.impl.transport.DroneAnalyzerXMPPConnector; +import org.wso2.carbon.device.mgt.iot.droneanalyzer.plugin.constants.DroneConstants; +import org.wso2.carbon.device.mgt.iot.droneanalyzer.plugin.controller.DroneController; +import org.wso2.carbon.device.mgt.iot.transport.TransportHandlerException; + +import java.io.File; + +public class DroneAnalyzerServiceUtils { + + private static final String SUPER_TENANT = "carbon.super"; + private static org.apache.commons.logging.Log log = LogFactory.getLog(DroneAnalyzerServiceUtils.class); + + public static void sendCommandViaXMPP(String deviceOwner, String deviceId, String resource, + String state, DroneAnalyzerXMPPConnector droneXMPPConnector) + throws DeviceManagementException, TransportHandlerException { + + String xmppServerDomain = XmppConfig.getInstance().getXmppEndpoint(); + int indexOfChar = xmppServerDomain.lastIndexOf(File.separator); + if (indexOfChar != -1) { + xmppServerDomain = xmppServerDomain.substring((indexOfChar + 1), xmppServerDomain.length()); + } + indexOfChar = xmppServerDomain.indexOf(":"); + if (indexOfChar != -1) { + xmppServerDomain = xmppServerDomain.substring(0, indexOfChar); + } + String clientToConnect = deviceId + "@" + xmppServerDomain + File.separator + deviceOwner; + String message = resource.replace("/", "") + ":" + state; + droneXMPPConnector.publishDeviceData(clientToConnect, message, "CONTROL-REQUEST"); + } + + public static boolean sendControlCommand(DroneController controller, String deviceId, String action, + double speed, double duration) + throws DeviceManagementException { + boolean controlState = false; + try{ + switch (action){ + case DroneConstants.TAKE_OFF: + controlState = controller.takeoff(); + break; + case DroneConstants.LAND: + controlState = controller.land(); + break; + case DroneConstants.BACK: + controlState = controller.back(speed, duration); + break; + case DroneConstants.CLOCK_WISE: + controlState = controller.clockwise(speed, duration); + break; + case DroneConstants.COUNTER_CLOCKWISE: + controlState = controller.conterClockwise(speed, duration); + break; + case DroneConstants.DOWN: + controlState = controller.down(speed, duration); + break; + case DroneConstants.FRONT: + controlState = controller.back(speed, duration); + break; + case DroneConstants.FORWARD: + controlState = controller.clockwise(speed, duration); + break; + case DroneConstants.UP: + controlState = controller.up(speed, duration); + break; + default: + log.error("Invalid command"); + break; + } + }catch(Exception e){ + log.error(e.getMessage()+ "\n"+ e); + } + return controlState; + } +} diff --git a/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.controller.api/src/main/webapp/META-INF/webapp-classloading.xml b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.controller.api/src/main/webapp/META-INF/webapp-classloading.xml new file mode 100644 index 0000000000..fa44619195 --- /dev/null +++ b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.controller.api/src/main/webapp/META-INF/webapp-classloading.xml @@ -0,0 +1,33 @@ + + + + + + + + + false + + + CXF,Carbon + diff --git a/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.controller.api/src/main/webapp/WEB-INF/cxf-servlet.xml b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.controller.api/src/main/webapp/WEB-INF/cxf-servlet.xml new file mode 100644 index 0000000000..cbaf62e255 --- /dev/null +++ b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.controller.api/src/main/webapp/WEB-INF/cxf-servlet.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.controller.api/src/main/webapp/WEB-INF/web.xml b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.controller.api/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 0000000000..2a9646df69 --- /dev/null +++ b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.controller.api/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,49 @@ + + + WSO2 IoT Server + WSO2 IoT Server + + 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 + /drone_analyzer/{version} + + + managed-api-application + drone_analyzer + + + managed-api-isSecured + true + + diff --git a/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.manager.api/pom.xml b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.manager.api/pom.xml new file mode 100644 index 0000000000..34bf4cacdb --- /dev/null +++ b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.manager.api/pom.xml @@ -0,0 +1,261 @@ + + + + drone-analyzer-plugin + org.wso2.carbon.devicemgt-plugins + 2.1.0-SNAPSHOT + ../pom.xml + + + 4.0.0 + org.wso2.carbon.device.mgt.iot.droneanalyzer.manager.api + war + WSO2 Carbon - IoT Server Drone Analyzer Manager API + http://maven.apache.org + + + + + org.wso2.carbon.devicemgt + org.wso2.carbon.device.mgt.common + provided + + + org.wso2.carbon.devicemgt + org.wso2.carbon.device.mgt.core + provided + + + org.apache.axis2.wso2 + axis2-client + + + + + + org.wso2.carbon.devicemgt + org.wso2.carbon.device.mgt.analytics + provided + + + org.apache.axis2.wso2 + axis2-client + + + + + + org.wso2.carbon.devicemgt + org.wso2.carbon.certificate.mgt.core + provided + + + commons-codec.wso2 + commons-codec + + + + + + + + org.apache.cxf + cxf-rt-frontend-jaxws + provided + + + org.apache.cxf + cxf-rt-frontend-jaxrs + provided + + + org.apache.cxf + cxf-rt-transports-http + provided + + + + + org.eclipse.paho + org.eclipse.paho.client.mqttv3 + provided + + + + + org.springframework + spring-context + + + + + org.apache.httpcomponents + httpasyncclient + 4.1 + provided + + + org.wso2.carbon.devicemgt-plugins + org.wso2.carbon.device.mgt.iot + provided + + + org.wso2.carbon.devicemgt-plugins + org.wso2.carbon.device.mgt.iot.droneanalyzer.plugin + provided + + + + + org.codehaus.jackson + jackson-core-asl + + + org.codehaus.jackson + jackson-jaxrs + + + javax + javaee-web-api + provided + + + javax.ws.rs + jsr311-api + provided + + + commons-httpclient.wso2 + commons-httpclient + provided + + + + org.wso2.carbon + org.wso2.carbon.utils + provided + + + 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.wso2.carbon.devicemgt + org.wso2.carbon.apimgt.webapp.publisher + provided + + + + commons-codec.wso2 + commons-codec + + + + org.igniterealtime.smack.wso2 + smack + provided + + + org.igniterealtime.smack.wso2 + smackx + provided + + + org.apache.commons + commons-collections4 + 4.0 + + + org.wso2.carbon.devicemgt + org.wso2.carbon.apimgt.annotations + provided + + + org.wso2.carbon.devicemgt + org.wso2.carbon.device.mgt.jwt.client.extension + provided + + + org.wso2.carbon.devicemgt + org.wso2.carbon.apimgt.application.extension + provided + + + + + + + + maven-compiler-plugin + + UTF-8 + ${wso2.maven.compiler.source} + ${wso2.maven.compiler.target} + + + + maven-war-plugin + + src/main/webapp/WEB-INF/web.xml + drone_analyzer_mgt + + + + + diff --git a/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.manager.api/src/main/java/org/wso2/carbon/device/mgt/iot/droneanalyzer/manager/api/impl/DroneManagerService.java b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.manager.api/src/main/java/org/wso2/carbon/device/mgt/iot/droneanalyzer/manager/api/impl/DroneManagerService.java new file mode 100644 index 0000000000..149c9070e3 --- /dev/null +++ b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.manager.api/src/main/java/org/wso2/carbon/device/mgt/iot/droneanalyzer/manager/api/impl/DroneManagerService.java @@ -0,0 +1,319 @@ +/* + * Copyright (c) 2016, 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.wso2.carbon.device.mgt.iot.droneanalyzer.manager.api.impl; + +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.apimgt.application.extension.APIManagementProviderService; +import org.wso2.carbon.apimgt.application.extension.dto.ApiApplicationKey; +import org.wso2.carbon.apimgt.application.extension.exception.APIManagerException; +import org.wso2.carbon.context.PrivilegedCarbonContext; +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.controlqueue.xmpp.XmppAccount; +import org.wso2.carbon.device.mgt.iot.controlqueue.xmpp.XmppConfig; +import org.wso2.carbon.device.mgt.iot.controlqueue.xmpp.XmppServerClient; +import org.wso2.carbon.device.mgt.iot.droneanalyzer.manager.api.impl.util.APIUtil; +import org.wso2.carbon.device.mgt.iot.droneanalyzer.plugin.constants.DroneConstants; +import org.wso2.carbon.device.mgt.iot.exception.DeviceControllerException; +import org.wso2.carbon.device.mgt.iot.util.ZipArchive; +import org.wso2.carbon.device.mgt.iot.util.ZipUtil; +import org.wso2.carbon.device.mgt.jwt.client.extension.JWTClient; +import org.wso2.carbon.device.mgt.jwt.client.extension.JWTClientManager; +import org.wso2.carbon.device.mgt.jwt.client.extension.dto.AccessTokenInfo; +import org.wso2.carbon.device.mgt.jwt.client.extension.exception.JWTClientException; +import org.wso2.carbon.user.api.UserStoreException; + +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; + +public class DroneManagerService { + + private static org.apache.commons.logging.Log log = LogFactory.getLog(DroneManagerService.class); + @Context //injected response proxy supporting multiple thread + private HttpServletResponse response; + private static final String KEY_TYPE = "PRODUCTION"; + private static ApiApplicationKey apiApplicationKey; + + @Path("manager/device/register") + @POST + public boolean register(@QueryParam("deviceId") String deviceId, @QueryParam("name") String name) { + try { + DeviceIdentifier deviceIdentifier = new DeviceIdentifier(); + deviceIdentifier.setId(deviceId); + deviceIdentifier.setType(DroneConstants.DEVICE_TYPE); + if (APIUtil.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(DroneConstants.DEVICE_TYPE); + enrolmentInfo.setOwner(APIUtil.getAuthenticatedUser()); + device.setEnrolmentInfo(enrolmentInfo); + boolean added = APIUtil.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; + } finally { + PrivilegedCarbonContext.endTenantFlow(); + } + } + + @Path("manager/device/{device_id}") + @DELETE + public void removeDevice(@PathParam("device_id") String deviceId, @Context HttpServletResponse response) { + try { + DeviceIdentifier deviceIdentifier = new DeviceIdentifier(); + deviceIdentifier.setId(deviceId); + deviceIdentifier.setType(DroneConstants.DEVICE_TYPE); + boolean removed = APIUtil.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 { + PrivilegedCarbonContext.endTenantFlow(); + } + } + + @Path("manager/device/{device_id}") + @PUT + public boolean updateDevice(@PathParam("device_id") String deviceId, @QueryParam("name") String name, + @Context HttpServletResponse response) { + try { + DeviceIdentifier deviceIdentifier = new DeviceIdentifier(); + deviceIdentifier.setId(deviceId); + deviceIdentifier.setType(DroneConstants.DEVICE_TYPE); + Device device = APIUtil.getDeviceManagementService().getDevice(deviceIdentifier); + device.setDeviceIdentifier(deviceId); + device.getEnrolmentInfo().setDateOfLastUpdate(new Date().getTime()); + device.setName(name); + device.setType(DroneConstants.DEVICE_TYPE); + boolean updated = APIUtil.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 { + PrivilegedCarbonContext.endTenantFlow(); + } + } + + @Path("manager/device/{device_id}") + @GET + @Consumes("application/json") + @Produces("application/json") + public Device getDevice(@PathParam("device_id") String deviceId) { + try { + DeviceIdentifier deviceIdentifier = new DeviceIdentifier(); + deviceIdentifier.setId(deviceId); + deviceIdentifier.setType(DroneConstants.DEVICE_TYPE); + return APIUtil.getDeviceManagementService().getDevice(deviceIdentifier); + } catch (DeviceManagementException e) { + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + return null; + } finally { + PrivilegedCarbonContext.endTenantFlow(); + } + } + + @Path("manager/devices") + @GET + @Consumes("application/json") + @Produces("application/json") + public Device[] getDroneDevices() { + try { + List userDevices = APIUtil.getDeviceManagementService().getDevicesOfUser(APIUtil.getAuthenticatedUser()); + ArrayList userDevicesforDrone = new ArrayList<>(); + for (Device device : userDevices) { + if (device.getType().equals(DroneConstants.DEVICE_TYPE) && + device.getEnrolmentInfo().getStatus().equals( + EnrolmentInfo.Status.ACTIVE)) { + userDevicesforDrone.add(device); + } + } + return userDevicesforDrone.toArray(new Device[]{}); + } catch (DeviceManagementException e) { + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + return null; + } finally { + PrivilegedCarbonContext.endTenantFlow(); + } + } + + + + @Path("manager/device/{sketch_type}/download") + @GET + @Produces("application/octet-stream") + public Response downloadSketch(@QueryParam("deviceName") String deviceName, + @PathParam("sketch_type") String sketchType) { + try { + //create new device id + String deviceId = shortUUID(); + //create token + String token = UUID.randomUUID().toString(); + String refreshToken = UUID.randomUUID().toString(); + //adding registering data + boolean status = register(deviceId, deviceName); + if (!status) { + return Response.status(500).entity( + "Error occurred while registering the device with " + "id: " + deviceId + + " owner:" + APIUtil.getAuthenticatedUser()).build(); + + } + ZipUtil ziputil = new ZipUtil(); + ZipArchive zipFile; + try { + zipFile = ziputil.createZipFile(APIUtil.getAuthenticatedUser(), APIUtil.getTenantDomainOftheUser(), + sketchType, deviceId, deviceName, token, refreshToken); + } catch (DeviceManagementException ex) { + return Response.status(500).entity("Error occurred while creating zip file").build(); + } + Response.ResponseBuilder rb = Response.ok(zipFile.getZipFile()); + rb.header("Content-Disposition", "attachment; filename=\"" + zipFile.getFileName() + "\""); + return rb.build(); + } finally { + PrivilegedCarbonContext.endTenantFlow(); + } + } + + @Path("manager/device/{sketch_type}/generate_link") + @GET + public Response generateSketchLink(@QueryParam("deviceName") String deviceName, + @PathParam("sketch_type") String sketchType) { + try { + ZipArchive zipFile = createDownloadFile(deviceName, sketchType); + Response.ResponseBuilder rb = Response.ok(zipFile.getDeviceId()); + return rb.build(); + } catch (IllegalArgumentException ex) { + return Response.status(400).entity(ex.getMessage()).build(); + } catch (DeviceManagementException ex) { + return Response.status(500).entity(ex.getMessage()).build(); + } catch (JWTClientException ex) { + return Response.status(500).entity(ex.getMessage()).build(); + } catch (DeviceControllerException ex) { + return Response.status(500).entity(ex.getMessage()).build(); + } catch (APIManagerException ex) { + return Response.status(500).entity(ex.getMessage()).build(); + } catch (UserStoreException ex) { + return Response.status(500).entity(ex.getMessage()).build(); + } finally { + PrivilegedCarbonContext.endTenantFlow(); + } + } + + private ZipArchive createDownloadFile(String deviceName, String sketchType) + throws DeviceManagementException, JWTClientException, APIManagerException, DeviceControllerException, + UserStoreException { + //create new device id + String deviceId = shortUUID(); + if (apiApplicationKey == null) { + String applicationUsername = PrivilegedCarbonContext.getThreadLocalCarbonContext().getUserRealm().getRealmConfiguration() + .getAdminUserName(); + APIManagementProviderService apiManagementProviderService = APIUtil.getAPIManagementProviderService(); + String[] tags = {DroneConstants.DEVICE_TYPE}; + apiApplicationKey = apiManagementProviderService.generateAndRetrieveApplicationKeys( + DroneConstants.DEVICE_TYPE, tags, KEY_TYPE, applicationUsername, true); + } + JWTClient jwtClient = JWTClientManager.getInstance().getJWTClient(); + String owner = PrivilegedCarbonContext.getThreadLocalCarbonContext().getUsername(); + String scopes = "device_type_" + DroneConstants.DEVICE_TYPE + " device_" + deviceId; + AccessTokenInfo accessTokenInfo = jwtClient.getAccessToken(apiApplicationKey.getConsumerKey(), + apiApplicationKey.getConsumerSecret(), owner, scopes); + //create token + String accessToken = accessTokenInfo.getAccess_token(); + String refreshToken = accessTokenInfo.getRefresh_token(); + //adding registering data + XmppAccount newXmppAccount = new XmppAccount(); + newXmppAccount.setAccountName(APIUtil.getAuthenticatedUser() + "_" + deviceId); + newXmppAccount.setUsername(deviceId); + newXmppAccount.setPassword(accessToken); + newXmppAccount.setEmail(deviceId + "@wso2.com"); + XmppServerClient xmppServerClient = new XmppServerClient(); + xmppServerClient.initControlQueue(); + boolean status; + if (XmppConfig.getInstance().isEnabled()) { + status = xmppServerClient.createXMPPAccount(newXmppAccount); + if (!status) { + String msg = "XMPP Account was not created for device - " + deviceId + " of owner - " + + APIUtil.getAuthenticatedUser() + ".XMPP might have been disabled in org.wso2.carbon.device.mgt.iot.common.config.server.configs"; + log.warn(msg); + throw new DeviceManagementException(msg); + } + } + //Register the device with CDMF + status = register(deviceId, deviceName); + if (!status) { + String msg = "Error occurred while registering the device with " + "id: " + deviceId + + " owner:" + APIUtil.getAuthenticatedUser(); + throw new DeviceManagementException(msg); + } + ZipUtil ziputil = new ZipUtil(); + ZipArchive zipFile = ziputil.createZipFile(APIUtil.getAuthenticatedUser(), APIUtil.getTenantDomainOftheUser(), + sketchType, deviceId, deviceName, accessToken, refreshToken); + zipFile.setDeviceId(deviceId); + return zipFile; + } + + 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); + } +} diff --git a/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.manager.api/src/main/java/org/wso2/carbon/device/mgt/iot/droneanalyzer/manager/api/impl/util/APIUtil.java b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.manager.api/src/main/java/org/wso2/carbon/device/mgt/iot/droneanalyzer/manager/api/impl/util/APIUtil.java new file mode 100644 index 0000000000..e9980fa5a9 --- /dev/null +++ b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.manager.api/src/main/java/org/wso2/carbon/device/mgt/iot/droneanalyzer/manager/api/impl/util/APIUtil.java @@ -0,0 +1,55 @@ +package org.wso2.carbon.device.mgt.iot.droneanalyzer.manager.api.impl.util; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.apimgt.application.extension.APIManagementProviderService; +import org.wso2.carbon.context.PrivilegedCarbonContext; +import org.wso2.carbon.device.mgt.core.service.DeviceManagementProviderService; + +/** + * This class provides utility functions used by REST-API. + */ +public class APIUtil { + + private static Log log = LogFactory.getLog(APIUtil.class); + + public static String getAuthenticatedUser() { + PrivilegedCarbonContext threadLocalCarbonContext = PrivilegedCarbonContext.getThreadLocalCarbonContext(); + String username = threadLocalCarbonContext.getUsername(); + String tenantDomain = threadLocalCarbonContext.getTenantDomain(); + if (username.endsWith(tenantDomain)) { + return username.substring(0, username.lastIndexOf("@")); + } + return username; + } + + public static String getTenantDomainOftheUser() { + PrivilegedCarbonContext threadLocalCarbonContext = PrivilegedCarbonContext.getThreadLocalCarbonContext(); + String tenantDomain = threadLocalCarbonContext.getTenantDomain(); + return tenantDomain; + } + + public static DeviceManagementProviderService getDeviceManagementService() { + PrivilegedCarbonContext ctx = PrivilegedCarbonContext.getThreadLocalCarbonContext(); + DeviceManagementProviderService deviceManagementProviderService = + (DeviceManagementProviderService) ctx.getOSGiService(DeviceManagementProviderService.class, null); + if (deviceManagementProviderService == null) { + String msg = "Device Management service has not initialized."; + log.error(msg); + throw new IllegalStateException(msg); + } + return deviceManagementProviderService; + } + + public static APIManagementProviderService getAPIManagementProviderService() { + PrivilegedCarbonContext ctx = PrivilegedCarbonContext.getThreadLocalCarbonContext(); + APIManagementProviderService apiManagementProviderService = + (APIManagementProviderService) ctx.getOSGiService(APIManagementProviderService.class, null); + if (apiManagementProviderService == null) { + String msg = "API management provider service has not initialized."; + log.error(msg); + throw new IllegalStateException(msg); + } + return apiManagementProviderService; + } +} diff --git a/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.manager.api/src/main/webapp/META-INF/webapp-classloading.xml b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.manager.api/src/main/webapp/META-INF/webapp-classloading.xml new file mode 100644 index 0000000000..fa44619195 --- /dev/null +++ b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.manager.api/src/main/webapp/META-INF/webapp-classloading.xml @@ -0,0 +1,33 @@ + + + + + + + + + false + + + CXF,Carbon + diff --git a/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.manager.api/src/main/webapp/WEB-INF/cxf-servlet.xml b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.manager.api/src/main/webapp/WEB-INF/cxf-servlet.xml new file mode 100644 index 0000000000..3a8849969d --- /dev/null +++ b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.manager.api/src/main/webapp/WEB-INF/cxf-servlet.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.manager.api/src/main/webapp/WEB-INF/web.xml b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.manager.api/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 0000000000..6947c58e1a --- /dev/null +++ b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.manager.api/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,62 @@ + + + WSO2 IoT Server + WSO2 IoT Server + + + CXFServlet + org.apache.cxf.transport.servlet.CXFServlet + 1 + + + + + CXFServlet + /* + + + + isAdminService + false + + + doAuthentication + true + + + + + managed-api-enabled + false + + + managed-api-owner + admin + + + managed-api-context-template + /drone_analyzer/{version} + + + managed-api-application + drone_analyzer + + + managed-api-isSecured + true + + + + + + + + + + + + diff --git a/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.plugin/pom.xml b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.plugin/pom.xml new file mode 100644 index 0000000000..a3482fca4b --- /dev/null +++ b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.plugin/pom.xml @@ -0,0 +1,108 @@ + + + + drone-analyzer-plugin + org.wso2.carbon.devicemgt-plugins + 2.1.0-SNAPSHOT + ../pom.xml + + + 4.0.0 + org.wso2.carbon.device.mgt.iot.droneanalyzer.plugin + bundle + WSO2 Carbon - IoT Server Drone Analyzer Device-Type Plugin + WSO2 Carbon - IoT Server Drone Analyzer Device-Type Plugin Implementation + http://maven.apache.org + + + UTF-8 + + + + + + org.apache.felix + maven-scr-plugin + + + maven-compiler-plugin + + 1.7 + 1.7 + + 2.3.2 + + + org.apache.felix + maven-bundle-plugin + 1.4.0 + true + + + ${project.artifactId} + ${project.artifactId} + ${carbon.devicemgt.plugins.version} + IoT Server Impl Bundle + org.wso2.carbon.device.mgt.iot.droneanalyzer.plugin.internal + + org.osgi.framework, + org.osgi.service.component, + org.apache.commons.logging, + javax.xml.bind.*;resolution:=optional, + javax.naming;resolution:=optional, + javax.sql;resolution:=optional, + javax.xml.bind.annotation.*;resolution:=optional, + javax.xml.parsers;resolution:=optional, + javax.net;resolution:=optional, + javax.net.ssl;resolution:=optional, + org.w3c.dom;resolution:=optional, + org.wso2.carbon.device.mgt.common.*, + org.wso2.carbon.device.mgt.common, + org.wso2.carbon.context.*, + org.wso2.carbon.ndatasource.core, + org.wso2.carbon.device.mgt.iot.*, + org.wso2.carbon.device.mgt.extensions.feature.mgt.*, + org.wso2.carbon.utils.* + + + !org.wso2.carbon.device.mgt.iot.droneanalyzer.plugin.internal, + org.wso2.carbon.device.mgt.iot.droneanalyzer.plugin.* + + + + + + + + + + org.eclipse.osgi + org.eclipse.osgi + + + org.eclipse.osgi + org.eclipse.osgi.services + + + org.wso2.carbon + org.wso2.carbon.logging + + + org.wso2.carbon.devicemgt + org.wso2.carbon.device.mgt.common + + + org.wso2.carbon + org.wso2.carbon.ndatasource.core + + + org.wso2.carbon.devicemgt + org.wso2.carbon.device.mgt.extensions + + + org.wso2.carbon + org.wso2.carbon.utils + + + diff --git a/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.plugin/src/main/java/org/wso2/carbon/device/mgt/iot/droneanalyzer/plugin/constants/DroneConstants.java b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.plugin/src/main/java/org/wso2/carbon/device/mgt/iot/droneanalyzer/plugin/constants/DroneConstants.java new file mode 100644 index 0000000000..03b950f109 --- /dev/null +++ b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.plugin/src/main/java/org/wso2/carbon/device/mgt/iot/droneanalyzer/plugin/constants/DroneConstants.java @@ -0,0 +1,41 @@ +/* + * 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.wso2.carbon.device.mgt.iot.droneanalyzer.plugin.constants; + +public class DroneConstants { + + public final static String DEVICE_PLUGIN_DEVICE_NAME = "DEVICE_NAME"; + public final static String DEVICE_PLUGIN_DEVICE_ID = "DRONE_DEVICE_ID"; + public final static String MESSAGE_RESOURCE = "drone_current_status"; + public static final String DEVICE_TYPE = "drone_analyzer"; + public static final int MINIMUM_TIME_DURATION = 150; + public static final int MAXIMUM_BUFFERE_SIZE_OF_SHARED_QUEUE = 10; + public static final String DEVICE_ID = "drone_type1"; + public final static String TAKE_OFF = "takeoff"; + public final static String LAND = "land"; + public final static String UP = "up"; + public final static String DOWN = "down"; + public final static String BACK = "back"; + public final static String FORWARD = "forward"; + public final static String FRONT = "front"; + public final static String CLOCK_WISE = "clockwise"; + public final static String COUNTER_CLOCKWISE = "counterClockwise"; + public static final String DATA_SOURCE_NAME = "jdbc/DroneAnalyzerDM_DB"; + +} diff --git a/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.plugin/src/main/java/org/wso2/carbon/device/mgt/iot/droneanalyzer/plugin/constants/MessageConfig.java b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.plugin/src/main/java/org/wso2/carbon/device/mgt/iot/droneanalyzer/plugin/constants/MessageConfig.java new file mode 100644 index 0000000000..d6d64b3593 --- /dev/null +++ b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.plugin/src/main/java/org/wso2/carbon/device/mgt/iot/droneanalyzer/plugin/constants/MessageConfig.java @@ -0,0 +1,40 @@ +/* + * 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.wso2.carbon.device.mgt.iot.droneanalyzer.plugin.constants; + +public class MessageConfig { + + public static final String IN_QUATANNIM_VAL = "quatanium_val"; + public static final String IN_DEVICE_TYPE = "device_type"; + public static final String IN_IRIS_DRONE = "IRIS_DRONE"; + public static final String IN_SIMULATOR = "SIMULATOR"; + public static final String OUT_QUATANNIM_VAL = "quatanium_val"; + public static final String IN_ACCELETOMETER_VAL = "accelerometer"; + public static final String OUT_ACCELETOMETER_VAL = "accelerometer"; + public static final String IN_GYROSCOPE_VAL = "gyroscope"; + public static final String OUT_GYROSCOPE_VAL = "gyroscope"; + public static final String IN_MAGNETOMETER_VAL = "magnetometer"; + public static final String OUT_MAGNETOMETER_VAL = "magnetometer"; + public static final String IN_BASIC_PARAM_VAL = "basicParam"; + public static final String OUT_BASIC_PARAM_VAL = "basicParam"; + public static final String OUT_BASIC_PARAM_VELOCITY = "velocity"; + public static final String OUT_BASIC_PARAM_GLOBAL_LOCATION = "global_location"; + public static final String OUT_BATTERY_LEVEL = "battery_level"; + +} diff --git a/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.plugin/src/main/java/org/wso2/carbon/device/mgt/iot/droneanalyzer/plugin/controller/DroneController.java b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.plugin/src/main/java/org/wso2/carbon/device/mgt/iot/droneanalyzer/plugin/controller/DroneController.java new file mode 100644 index 0000000000..0bf48f4ea5 --- /dev/null +++ b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.plugin/src/main/java/org/wso2/carbon/device/mgt/iot/droneanalyzer/plugin/controller/DroneController.java @@ -0,0 +1,32 @@ +/* + * 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.wso2.carbon.device.mgt.iot.droneanalyzer.plugin.controller; + +public interface DroneController { + boolean takeoff(); + boolean land(); + boolean up(double speed, double duration); + boolean down(double speed, double duration); + boolean left(double speed, double duration); + boolean right(double speed, double duration); + boolean front(double speed, double duration); + boolean back(double speed, double duration); + boolean clockwise(double speed, double duration); + boolean conterClockwise(double speed, double duration); +} diff --git a/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.plugin/src/main/java/org/wso2/carbon/device/mgt/iot/droneanalyzer/plugin/controller/impl/DroneControllerImpl.java b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.plugin/src/main/java/org/wso2/carbon/device/mgt/iot/droneanalyzer/plugin/controller/impl/DroneControllerImpl.java new file mode 100644 index 0000000000..b9fd5e0734 --- /dev/null +++ b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.plugin/src/main/java/org/wso2/carbon/device/mgt/iot/droneanalyzer/plugin/controller/impl/DroneControllerImpl.java @@ -0,0 +1,73 @@ +/* + * 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.wso2.carbon.device.mgt.iot.droneanalyzer.plugin.controller.impl; + +import org.wso2.carbon.device.mgt.iot.droneanalyzer.plugin.controller.DroneController; + +public class DroneControllerImpl implements DroneController{ + + @Override + public boolean takeoff() { + return false; + } + + @Override + public boolean land() { + return false; + } + + @Override + public boolean up(double speed, double duration) { + return false; + } + + @Override + public boolean down(double speed, double duration) { + return false; + } + + @Override + public boolean left(double speed, double duration) { + return false; + } + + @Override + public boolean right(double speed, double duration) { + return false; + } + + @Override + public boolean front(double speed, double duration) { + return false; + } + + @Override + public boolean back(double speed, double duration) { + return false; + } + + @Override + public boolean clockwise(double speed, double duration) { + return false; + } + + @Override + public boolean conterClockwise(double speed, double duration) { + return false; + } +} diff --git a/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.plugin/src/main/java/org/wso2/carbon/device/mgt/iot/droneanalyzer/plugin/exception/DroneAnalyzerDeviceMgtPluginException.java b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.plugin/src/main/java/org/wso2/carbon/device/mgt/iot/droneanalyzer/plugin/exception/DroneAnalyzerDeviceMgtPluginException.java new file mode 100644 index 0000000000..e3d118ef61 --- /dev/null +++ b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.plugin/src/main/java/org/wso2/carbon/device/mgt/iot/droneanalyzer/plugin/exception/DroneAnalyzerDeviceMgtPluginException.java @@ -0,0 +1,56 @@ +/* + * 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.wso2.carbon.device.mgt.iot.droneanalyzer.plugin.exception; + + +public class DroneAnalyzerDeviceMgtPluginException extends Exception{ + + private String errorMessage; + + public String getErrorMessage() { + return errorMessage; + } + + public void setErrorMessage(String errorMessage) { + this.errorMessage = errorMessage; + } + + public DroneAnalyzerDeviceMgtPluginException(String msg, Exception nestedEx) { + super(msg, nestedEx); + setErrorMessage(msg); + } + + public DroneAnalyzerDeviceMgtPluginException(String message, Throwable cause) { + super(message, cause); + setErrorMessage(message); + } + + public DroneAnalyzerDeviceMgtPluginException(String msg) { + super(msg); + setErrorMessage(msg); + } + + public DroneAnalyzerDeviceMgtPluginException() { + super(); + } + + public DroneAnalyzerDeviceMgtPluginException(Throwable cause) { + super(cause); + } + +} diff --git a/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.plugin/src/main/java/org/wso2/carbon/device/mgt/iot/droneanalyzer/plugin/impl/DroneAnalyzerManager.java b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.plugin/src/main/java/org/wso2/carbon/device/mgt/iot/droneanalyzer/plugin/impl/DroneAnalyzerManager.java new file mode 100644 index 0000000000..a880188853 --- /dev/null +++ b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.plugin/src/main/java/org/wso2/carbon/device/mgt/iot/droneanalyzer/plugin/impl/DroneAnalyzerManager.java @@ -0,0 +1,250 @@ +/* + * 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.wso2.carbon.device.mgt.iot.droneanalyzer.plugin.impl; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.device.mgt.common.*; +import org.wso2.carbon.device.mgt.common.configuration.mgt.TenantConfiguration; +import org.wso2.carbon.device.mgt.common.license.mgt.License; +import org.wso2.carbon.device.mgt.common.license.mgt.LicenseManagementException; +import org.wso2.carbon.device.mgt.iot.droneanalyzer.plugin.exception.DroneAnalyzerDeviceMgtPluginException; +import org.wso2.carbon.device.mgt.iot.droneanalyzer.plugin.impl.dao.DroneAnalyzerDAO; +import org.wso2.carbon.device.mgt.iot.droneanalyzer.plugin.impl.feature.DroneAnalyzerFeatureManager; +import java.util.List; + +/** + * This represents the Drone Analyzer service implementation of DeviceManagerService. + */ +public class DroneAnalyzerManager implements DeviceManager { + + private static final DroneAnalyzerDAO droneAnalyzerDAO = new DroneAnalyzerDAO(); + private static final Log log = LogFactory.getLog(DroneAnalyzerManager.class); + private FeatureManager droneFeatureManager = new DroneAnalyzerFeatureManager(); + @Override + public FeatureManager getFeatureManager() { + return droneFeatureManager; + } + + @Override + public boolean saveConfiguration(TenantConfiguration tenantConfiguration) + throws DeviceManagementException { + //TODO implement this + return false; + } + + @Override + public TenantConfiguration getConfiguration() throws DeviceManagementException { + //TODO implement this + return null; + } + + @Override + public boolean enrollDevice(Device device) throws DeviceManagementException { + boolean status; + try { + if (log.isDebugEnabled()) { + log.debug("Enrolling a new drone device : " + device.getDeviceIdentifier()); + } + DroneAnalyzerDAO.beginTransaction(); + status = droneAnalyzerDAO.getDeviceDAO().addDevice(device); + DroneAnalyzerDAO.commitTransaction(); + } catch (DroneAnalyzerDeviceMgtPluginException e) { + try { + DroneAnalyzerDAO.rollbackTransaction(); + } catch (DroneAnalyzerDeviceMgtPluginException iotDAOEx) { + String msg = "Error occurred while roll back the device enrol transaction :" + device.toString(); + log.warn(msg, iotDAOEx); + } + String msg = "Error while enrolling the drone device : " + device.getDeviceIdentifier(); + log.error(msg, e); + throw new DeviceManagementException(msg, e); + } + return status; + } + + @Override + public boolean modifyEnrollment(Device device) throws DeviceManagementException { + boolean status; + try { + if (log.isDebugEnabled()) { + log.debug("Modifying the Virtual Firealarm device enrollment data"); + } + DroneAnalyzerDAO.beginTransaction(); + status = droneAnalyzerDAO.getDeviceDAO().updateDevice(device); + DroneAnalyzerDAO.commitTransaction(); + } catch (DroneAnalyzerDeviceMgtPluginException e) { + try { + DroneAnalyzerDAO.rollbackTransaction(); + } catch (DroneAnalyzerDeviceMgtPluginException iotDAOEx) { + String msg = "Error occurred while roll back the update device transaction :" + device.toString(); + log.warn(msg, iotDAOEx); + } + String msg = "Error while updating the enrollment of the drone device : " + + device.getDeviceIdentifier(); + log.error(msg, e); + throw new DeviceManagementException(msg, e); + } + return status; + } + + @Override + public boolean disenrollDevice(DeviceIdentifier deviceId) throws DeviceManagementException { + boolean status; + try { + if (log.isDebugEnabled()) { + log.debug("Dis-enrolling drone device : " + deviceId); + } + DroneAnalyzerDAO.beginTransaction(); + status = droneAnalyzerDAO.getDeviceDAO().deleteIotDevice(deviceId.getId()); + DroneAnalyzerDAO.commitTransaction(); + } catch (DroneAnalyzerDeviceMgtPluginException e) { + try { + DroneAnalyzerDAO.rollbackTransaction(); + } catch (DroneAnalyzerDeviceMgtPluginException iotDAOEx) { + String msg = "Error occurred while roll back the device dis enrol transaction :" + deviceId.toString(); + log.warn(msg, iotDAOEx); + } + String msg = "Error while removing the drone device : " + deviceId.getId(); + log.error(msg, e); + throw new DeviceManagementException(msg, e); + } + return status; + } + + @Override + public boolean isEnrolled(DeviceIdentifier deviceId) throws DeviceManagementException { + boolean isEnrolled = false; + try { + if (log.isDebugEnabled()) { + log.debug("Checking the enrollment of Drone device : " + deviceId.getId()); + } + Device iotDevice = droneAnalyzerDAO.getDeviceDAO().getDevice(deviceId.getId()); + if (iotDevice != null) { + isEnrolled = true; + } + } catch (DroneAnalyzerDeviceMgtPluginException e) { + String msg = "Error while checking the enrollment status of Drone device : " + + deviceId.getId(); + log.error(msg, e); + throw new DeviceManagementException(msg, e); + } + return isEnrolled; + } + + @Override + public boolean isActive(DeviceIdentifier deviceId) throws DeviceManagementException { + return true; + } + + @Override + public boolean setActive(DeviceIdentifier deviceId, boolean status) + throws DeviceManagementException { + return true; + } + + @Override + public Device getDevice(DeviceIdentifier deviceId) throws DeviceManagementException { + Device device; + try { + if (log.isDebugEnabled()) { + log.debug("Getting the details of Drone device : " + deviceId.getId()); + } + device = droneAnalyzerDAO.getDeviceDAO().getDevice(deviceId.getId()); + } catch (DroneAnalyzerDeviceMgtPluginException e) { + String msg = "Error while fetching the Drone device : " + deviceId.getId(); + log.error(msg, e); + throw new DeviceManagementException(msg, e); + } + return device; + } + + @Override + public boolean setOwnership(DeviceIdentifier deviceId, String ownershipType) + throws DeviceManagementException { + return true; + } + + public boolean isClaimable(DeviceIdentifier deviceIdentifier) throws DeviceManagementException { + return false; + } + + @Override + public boolean setStatus(DeviceIdentifier deviceId, String currentOwner, + EnrolmentInfo.Status status) throws DeviceManagementException { + return false; + } + + @Override + public License getLicense(String s) throws LicenseManagementException { + return null; + } + + @Override + public void addLicense(License license) throws LicenseManagementException { + + } + + @Override + public boolean updateDeviceInfo(DeviceIdentifier deviceIdentifier, Device device) throws DeviceManagementException { + boolean status; + try { + if (log.isDebugEnabled()) { + log.debug( + "updating the details of Drone device : " + deviceIdentifier); + } + DroneAnalyzerDAO.beginTransaction(); + status = droneAnalyzerDAO.getDeviceDAO().updateDevice(device); + DroneAnalyzerDAO.commitTransaction(); + } catch (DroneAnalyzerDeviceMgtPluginException e) { + try { + DroneAnalyzerDAO.rollbackTransaction(); + } catch (DroneAnalyzerDeviceMgtPluginException iotDAOEx) { + String msg = "Error occurred while roll back the update device info transaction :" + device.toString(); + log.warn(msg, iotDAOEx); + } + String msg = + "Error while updating the Drone device : " + deviceIdentifier; + log.error(msg, e); + throw new DeviceManagementException(msg, e); + } + return status; + } + + @Override + public List getAllDevices() throws DeviceManagementException { + List devices = null; + try { + if (log.isDebugEnabled()) { + log.debug("Fetching the details of all Drone devices"); + } + devices = droneAnalyzerDAO.getDeviceDAO().getAllDevices(); + } catch (DroneAnalyzerDeviceMgtPluginException e) { + String msg = "Error while fetching all Drone devices."; + log.error(msg, e); + throw new DeviceManagementException(msg, e); + } + return devices; + } + + @Override + public boolean requireDeviceAuthorization() { + return true; + } +} diff --git a/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.plugin/src/main/java/org/wso2/carbon/device/mgt/iot/droneanalyzer/plugin/impl/DroneAnalyzerManagerService.java b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.plugin/src/main/java/org/wso2/carbon/device/mgt/iot/droneanalyzer/plugin/impl/DroneAnalyzerManagerService.java new file mode 100644 index 0000000000..5af1a9008a --- /dev/null +++ b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.plugin/src/main/java/org/wso2/carbon/device/mgt/iot/droneanalyzer/plugin/impl/DroneAnalyzerManagerService.java @@ -0,0 +1,106 @@ +/* + * 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.wso2.carbon.device.mgt.iot.droneanalyzer.plugin.impl; + +import org.wso2.carbon.device.mgt.common.DeviceIdentifier; +import org.wso2.carbon.device.mgt.common.DeviceManagementException; +import org.wso2.carbon.device.mgt.common.DeviceManager; +import org.wso2.carbon.device.mgt.common.app.mgt.Application; +import org.wso2.carbon.device.mgt.common.app.mgt.ApplicationManagementException; +import org.wso2.carbon.device.mgt.common.app.mgt.ApplicationManager; +import org.wso2.carbon.device.mgt.common.operation.mgt.Operation; +import org.wso2.carbon.device.mgt.common.spi.DeviceManagementService; +import org.wso2.carbon.device.mgt.iot.droneanalyzer.plugin.constants.DroneConstants; + +import java.util.List; + + +public class DroneAnalyzerManagerService implements DeviceManagementService { + private DeviceManager deviceManager; + @Override + public String getType() { + return DroneConstants.DEVICE_TYPE; + } + + @Override + public void init() throws DeviceManagementException { + this.deviceManager = new DroneAnalyzerManager(); + } + + @Override + public DeviceManager getDeviceManager() { + return deviceManager; + } + + @Override + public ApplicationManager getApplicationManager() { + return null; + } + + @Override + public void notifyOperationToDevices(Operation operation, List list) + throws DeviceManagementException { + + } + + @Override + public Application[] getApplications(String s, int i, int i1) throws ApplicationManagementException { + return new Application[0]; + } + + @Override + public void updateApplicationStatus(DeviceIdentifier deviceIdentifier, Application application, String s) + throws ApplicationManagementException { + + } + + @Override + public String getApplicationStatus(DeviceIdentifier deviceIdentifier, Application application) + throws ApplicationManagementException { + return null; + } + + @Override + public void installApplicationForDevices(Operation operation, List list) + throws ApplicationManagementException { + + } + + @Override + public void installApplicationForUsers(Operation operation, List list) + throws ApplicationManagementException { + + } + + @Override + public void installApplicationForUserRoles(Operation operation, List list) + throws ApplicationManagementException { + + } + + @Override + public String getProviderTenantDomain() { + return "carbon.super"; + } + + @Override + public boolean isSharedWithAllTenants() { + return true; + } + +} diff --git a/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.plugin/src/main/java/org/wso2/carbon/device/mgt/iot/droneanalyzer/plugin/impl/dao/DroneAnalyzerDAO.java b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.plugin/src/main/java/org/wso2/carbon/device/mgt/iot/droneanalyzer/plugin/impl/dao/DroneAnalyzerDAO.java new file mode 100644 index 0000000000..26896bc777 --- /dev/null +++ b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.plugin/src/main/java/org/wso2/carbon/device/mgt/iot/droneanalyzer/plugin/impl/dao/DroneAnalyzerDAO.java @@ -0,0 +1,126 @@ +/* + * 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.wso2.carbon.device.mgt.iot.droneanalyzer.plugin.impl.dao; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.device.mgt.iot.droneanalyzer.plugin.constants.DroneConstants; +import org.wso2.carbon.device.mgt.iot.droneanalyzer.plugin.exception.DroneAnalyzerDeviceMgtPluginException; +import org.wso2.carbon.device.mgt.iot.droneanalyzer.plugin.impl.dao.impl.DroneAnalyzerDeviceDAOImpl; +import javax.naming.Context; +import javax.naming.InitialContext; +import javax.naming.NamingException; +import javax.sql.DataSource; +import java.sql.Connection; +import java.sql.SQLException; + + +public class DroneAnalyzerDAO { + + private static final Log log = LogFactory.getLog(DroneAnalyzerDAO.class); + static DataSource dataSource; + private static ThreadLocal currentConnection = new ThreadLocal(); + + public DroneAnalyzerDAO() { + initDroneAnalyzerDAO(); + } + + public static void initDroneAnalyzerDAO() { + try { + Context ctx = new InitialContext(); + dataSource = (DataSource) ctx.lookup(DroneConstants.DATA_SOURCE_NAME); + } catch (NamingException e) { + log.error("Error while looking up the data source: " + DroneConstants.DATA_SOURCE_NAME); + } + } + + public DroneAnalyzerDeviceDAOImpl getDeviceDAO() { + return new DroneAnalyzerDeviceDAOImpl(); + } + + public static void beginTransaction() throws DroneAnalyzerDeviceMgtPluginException { + try { + Connection conn = dataSource.getConnection(); + conn.setAutoCommit(false); + currentConnection.set(conn); + } catch (SQLException e) { + throw new DroneAnalyzerDeviceMgtPluginException("Error occurred while retrieving datasource connection", e); + } + } + + public static Connection getConnection() throws DroneAnalyzerDeviceMgtPluginException { + + if (currentConnection != null && currentConnection.get() == null) { + try { + currentConnection.set(dataSource.getConnection()); + } catch (SQLException e) { + throw new DroneAnalyzerDeviceMgtPluginException("Error occurred while retrieving data source connection", e); + } + } + return currentConnection.get(); + } + + public static void commitTransaction() throws DroneAnalyzerDeviceMgtPluginException { + 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 DroneAnalyzerDeviceMgtPluginException("Error occurred while committing the transaction", e); + } finally { + closeConnection(); + } + } + + public static void closeConnection() throws DroneAnalyzerDeviceMgtPluginException { + 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 DroneAnalyzerDeviceMgtPluginException { + 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 DroneAnalyzerDeviceMgtPluginException("Error occurred while rollback the transaction", e); + } finally { + closeConnection(); + } + } +} diff --git a/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.plugin/src/main/java/org/wso2/carbon/device/mgt/iot/droneanalyzer/plugin/impl/dao/impl/DroneAnalyzerDeviceDAOImpl.java b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.plugin/src/main/java/org/wso2/carbon/device/mgt/iot/droneanalyzer/plugin/impl/dao/impl/DroneAnalyzerDeviceDAOImpl.java new file mode 100644 index 0000000000..35ce12860a --- /dev/null +++ b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.plugin/src/main/java/org/wso2/carbon/device/mgt/iot/droneanalyzer/plugin/impl/dao/impl/DroneAnalyzerDeviceDAOImpl.java @@ -0,0 +1,194 @@ +/* + * 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.wso2.carbon.device.mgt.iot.droneanalyzer.plugin.impl.dao.impl; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.device.mgt.common.Device; +import org.wso2.carbon.device.mgt.iot.droneanalyzer.plugin.constants.DroneConstants; +import org.wso2.carbon.device.mgt.iot.droneanalyzer.plugin.exception.DroneAnalyzerDeviceMgtPluginException; +import org.wso2.carbon.device.mgt.iot.droneanalyzer.plugin.impl.dao.DroneAnalyzerDAO; +import org.wso2.carbon.device.mgt.iot.droneanalyzer.plugin.impl.util.DroneAnalyzerUtils; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +/** + * Implements CRUD for drone analyzer. + */ +public class DroneAnalyzerDeviceDAOImpl { + + private static final Log log = LogFactory.getLog(DroneAnalyzerDeviceDAOImpl.class); + + public Device getDevice(String deviceId) throws DroneAnalyzerDeviceMgtPluginException { + Connection conn = null; + PreparedStatement stmt = null; + Device device = null; + ResultSet resultSet = null; + try { + conn = DroneAnalyzerDAO.getConnection(); + if(conn == null){ + log.error("Database connection hasn't been created"); + } + String selectDBQuery = + "SELECT DRONE_DEVICE_ID, DEVICE_NAME FROM DRONE_DEVICE WHERE DRONE_DEVICE_ID = ?"; + stmt = conn.prepareStatement(selectDBQuery); + stmt.setString(1, deviceId); + resultSet = stmt.executeQuery(); + + if (resultSet.next()) { + device = new Device(); + if (log.isDebugEnabled()) { + log.debug("Drone device " + deviceId + " data has been fetched from " + + "Drone database."); + } + } + } catch (SQLException e) { + String msg = "Error occurred while fetching drone device : '" + deviceId + "'"; + log.error(msg, e); + throw new DroneAnalyzerDeviceMgtPluginException(msg, e); + } finally { + DroneAnalyzerUtils.cleanupResources(stmt, resultSet); + DroneAnalyzerDAO.closeConnection(); + } + return device; + } + + public boolean addDevice(Device device) throws DroneAnalyzerDeviceMgtPluginException { + boolean status = false; + Connection conn = null; + PreparedStatement stmt = null; + try { + conn = DroneAnalyzerDAO.getConnection(); + String createDBQuery = + "INSERT INTO DRONE_DEVICE(DRONE_DEVICE_ID, DEVICE_NAME) VALUES (?, ?)"; + stmt = conn.prepareStatement(createDBQuery); + stmt.setString(1, device.getDeviceIdentifier()); + stmt.setString(2, device.getName()); + int rows = stmt.executeUpdate(); + if (rows > 0) { + status = true; + if (log.isDebugEnabled()) { + log.debug("drone device " + device.getDeviceIdentifier() + " data has been" + + " added to the drone database."); + } + } + } catch (SQLException e) { + String msg = "Error occurred while adding the drone device '" + + device.getDeviceIdentifier() + "' to the drone db."; + log.error(msg, e); + throw new DroneAnalyzerDeviceMgtPluginException(msg, e); + } finally { + DroneAnalyzerUtils.cleanupResources(stmt, null); + } + return status; + } + + public boolean updateDevice(Device device) throws DroneAnalyzerDeviceMgtPluginException { + boolean status = false; + Connection conn = null; + PreparedStatement stmt = null; + try { + conn = DroneAnalyzerDAO.getConnection(); + String updateDBQuery = + "UPDATE DRONE_DEVICE SET DEVICE_NAME = ? WHERE DRONE_DEVICE_ID = ?"; + stmt = conn.prepareStatement(updateDBQuery); + stmt.setString(1, device.getName()); + stmt.setString(2, device.getDeviceIdentifier()); + int rows = stmt.executeUpdate(); + if (rows > 0) { + status = true; + if (log.isDebugEnabled()) { + log.debug("Drone device " + device.getDeviceIdentifier() + " data has been" + + " modified."); + } + } + } catch (SQLException e) { + String msg = "Error occurred while modifying the Drone device '" + device.getDeviceIdentifier() + "' data."; + log.error(msg, e); + throw new DroneAnalyzerDeviceMgtPluginException(msg, e); + } finally { + DroneAnalyzerUtils.cleanupResources(stmt, null); + } + return status; + } + + public boolean deleteIotDevice(String iotDeviceId) throws DroneAnalyzerDeviceMgtPluginException { + boolean status = false; + Connection conn = null; + PreparedStatement stmt = null; + try { + conn = DroneAnalyzerDAO.getConnection(); + System.out.println("delete device "); + String deleteDBQuery = + "DELETE FROM DRONE_DEVICE WHERE DRONE_DEVICE_ID = ?"; + stmt = conn.prepareStatement(deleteDBQuery); + stmt.setString(1, iotDeviceId); + int rows = stmt.executeUpdate(); + if (rows > 0) { + status = true; + if (log.isDebugEnabled()) { + log.debug("Drone device " + iotDeviceId + " data has deleted" + + " from the drone database."); + } + } + } catch (SQLException e) { + String msg = "Error occurred while deleting drone device " + iotDeviceId; + log.error(msg, e); + throw new DroneAnalyzerDeviceMgtPluginException(msg, e); + } finally { + DroneAnalyzerUtils.cleanupResources(stmt, null); + } + return status; + } + + public List getAllDevices() throws DroneAnalyzerDeviceMgtPluginException { + Connection conn = null; + PreparedStatement stmt = null; + ResultSet resultSet = null; + Device iotDevice; + List iotDevices = new ArrayList(); + try { + conn = DroneAnalyzerDAO.getConnection(); + String selectDBQuery = + "SELECT DRONE_DEVICE_ID, DEVICE_NAME " + + "FROM DRONE_DEVICE"; + stmt = conn.prepareStatement(selectDBQuery); + resultSet = stmt.executeQuery(); + while (resultSet.next()) { + iotDevice = new Device(); + iotDevice.setDeviceIdentifier(resultSet.getString(DroneConstants.DEVICE_PLUGIN_DEVICE_ID)); + iotDevice.setName(resultSet.getString(DroneConstants.DEVICE_PLUGIN_DEVICE_NAME)); + } + if (log.isDebugEnabled()) { + log.debug("All drone device details have fetched from drone database."); + } + return iotDevices; + } catch (SQLException e) { + String msg = "Error occurred while fetching all drone device data'"; + log.error(msg, e); + throw new DroneAnalyzerDeviceMgtPluginException(msg, e); + } finally { + DroneAnalyzerUtils.cleanupResources(stmt, resultSet); + DroneAnalyzerDAO.closeConnection(); + } + } +} diff --git a/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.plugin/src/main/java/org/wso2/carbon/device/mgt/iot/droneanalyzer/plugin/impl/feature/DroneAnalyzerFeatureManager.java b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.plugin/src/main/java/org/wso2/carbon/device/mgt/iot/droneanalyzer/plugin/impl/feature/DroneAnalyzerFeatureManager.java new file mode 100644 index 0000000000..6a9127cfda --- /dev/null +++ b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.plugin/src/main/java/org/wso2/carbon/device/mgt/iot/droneanalyzer/plugin/impl/feature/DroneAnalyzerFeatureManager.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2016, 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. + */ +package org.wso2.carbon.device.mgt.iot.droneanalyzer.plugin.impl.feature; + +import org.wso2.carbon.device.mgt.common.DeviceManagementException; +import org.wso2.carbon.device.mgt.common.Feature; +import org.wso2.carbon.device.mgt.common.FeatureManager; +import org.wso2.carbon.device.mgt.extensions.feature.mgt.GenericFeatureManager; +import org.wso2.carbon.device.mgt.iot.droneanalyzer.plugin.constants.DroneConstants; + +import java.util.List; + +public class DroneAnalyzerFeatureManager implements FeatureManager { + @Override + public boolean addFeature(Feature feature) throws DeviceManagementException { + return false; + } + + @Override + public boolean addFeatures(List features) throws DeviceManagementException { + return false; + } + + @Override + public Feature getFeature(String name) throws DeviceManagementException { + GenericFeatureManager genericFeatureManager = GenericFeatureManager.getInstance(); + return genericFeatureManager.getFeature(DroneConstants.DEVICE_TYPE, name); + } + + @Override + public List getFeatures() throws DeviceManagementException { + GenericFeatureManager genericFeatureManager = GenericFeatureManager.getInstance(); + return genericFeatureManager.getFeatures(DroneConstants.DEVICE_TYPE); + } + + @Override + public boolean removeFeature(String name) throws DeviceManagementException { + return false; + } + + @Override + public boolean addSupportedFeaturesToDB() throws DeviceManagementException { + return false; + } +} diff --git a/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.plugin/src/main/java/org/wso2/carbon/device/mgt/iot/droneanalyzer/plugin/impl/util/DeviceSchemaInitializer.java b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.plugin/src/main/java/org/wso2/carbon/device/mgt/iot/droneanalyzer/plugin/impl/util/DeviceSchemaInitializer.java new file mode 100644 index 0000000000..e96b4e0b1e --- /dev/null +++ b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.plugin/src/main/java/org/wso2/carbon/device/mgt/iot/droneanalyzer/plugin/impl/util/DeviceSchemaInitializer.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2016, 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.wso2.carbon.device.mgt.iot.droneanalyzer.plugin.impl.util; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.utils.CarbonUtils; +import org.wso2.carbon.utils.dbcreator.DatabaseCreator; + +import javax.sql.DataSource; +import java.io.File; + +/** + * Provides methods for initializing the database script. + */ +public class DeviceSchemaInitializer extends DatabaseCreator{ + + private static final Log log = LogFactory.getLog(DeviceSchemaInitializer.class); + private static final String setupSQLScriptBaseLocation = CarbonUtils.getCarbonHome() + File.separator + "dbscripts" + + File.separator + "cdm" + File.separator + "plugins" + File.separator; + + public DeviceSchemaInitializer(DataSource dataSource) { + super(dataSource); + } + + @Override + protected String getDbScriptLocation(String databaseType) { + String scriptName = databaseType + ".sql"; + if (log.isDebugEnabled()) { + log.debug("Loading database script from :" + scriptName); + } + return setupSQLScriptBaseLocation.replaceFirst("DBTYPE", databaseType) + scriptName; + } +} diff --git a/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.plugin/src/main/java/org/wso2/carbon/device/mgt/iot/droneanalyzer/plugin/impl/util/DroneAnalyzerUtils.java b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.plugin/src/main/java/org/wso2/carbon/device/mgt/iot/droneanalyzer/plugin/impl/util/DroneAnalyzerUtils.java new file mode 100644 index 0000000000..e65c0f0c4f --- /dev/null +++ b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.plugin/src/main/java/org/wso2/carbon/device/mgt/iot/droneanalyzer/plugin/impl/util/DroneAnalyzerUtils.java @@ -0,0 +1,108 @@ +/* + * 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.wso2.carbon.device.mgt.iot.droneanalyzer.plugin.impl.util; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.device.mgt.common.Device; +import org.wso2.carbon.device.mgt.iot.droneanalyzer.plugin.constants.DroneConstants; +import org.wso2.carbon.device.mgt.iot.droneanalyzer.plugin.exception.DroneAnalyzerDeviceMgtPluginException; +import javax.naming.Context; +import javax.naming.InitialContext; +import javax.naming.NamingException; +import javax.sql.DataSource; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.List; + +/** + * Contains utility methods used by Drone Analyzer plugin. + */ +public class DroneAnalyzerUtils { + private static Log log = LogFactory.getLog(DroneAnalyzerUtils.class); + + public static String getDeviceProperty(List deviceProperties, String propertyKey) { + String deviceProperty = ""; + for(Device.Property property :deviceProperties){ + if(propertyKey.equals(property.getName())){ + deviceProperty = property.getValue(); + } + } + return deviceProperty; + } + + public static Device.Property getProperty(String property, String value) { + if (property != null) { + Device.Property prop = new Device.Property(); + prop.setName(property); + prop.setValue(value); + return prop; + } + return null; + } + + public static void cleanupResources(Connection conn, PreparedStatement stmt, ResultSet rs) { + if (rs != null) { + try { + rs.close(); + } catch (SQLException e) { + log.warn("Error occurred while closing result set", e); + } + } + if (stmt != null) { + try { + stmt.close(); + } catch (SQLException e) { + log.warn("Error occurred while closing prepared statement", e); + } + } + if (conn != null) { + try { + conn.close(); + } catch (SQLException e) { + log.warn("Error occurred while closing database connection", e); + } + } + } + + public static void cleanupResources(PreparedStatement stmt, ResultSet rs) { + cleanupResources(null, stmt, rs); + } + + /** + * Creates the device management schema. + */ + public static void setupDeviceManagementSchema() throws DroneAnalyzerDeviceMgtPluginException { + try { + Context ctx = new InitialContext(); + DataSource dataSource = (DataSource) ctx.lookup(DroneConstants.DATA_SOURCE_NAME); + DeviceSchemaInitializer initializer = + new DeviceSchemaInitializer(dataSource); + log.info("Initializing device management repository database schema"); + initializer.createRegistryDatabase(); + + } catch (NamingException e) { + log.error("Error while looking up the data source: " + DroneConstants.DATA_SOURCE_NAME); + } catch (Exception e) { + throw new DroneAnalyzerDeviceMgtPluginException("Error occurred while initializing Iot Device " + + "Management database schema", e); + } + } +} diff --git a/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.plugin/src/main/java/org/wso2/carbon/device/mgt/iot/droneanalyzer/plugin/internal/DroneAnalyzerManagementServiceComponent.java b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.plugin/src/main/java/org/wso2/carbon/device/mgt/iot/droneanalyzer/plugin/internal/DroneAnalyzerManagementServiceComponent.java new file mode 100644 index 0000000000..01e6487732 --- /dev/null +++ b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.plugin/src/main/java/org/wso2/carbon/device/mgt/iot/droneanalyzer/plugin/internal/DroneAnalyzerManagementServiceComponent.java @@ -0,0 +1,89 @@ +/* + * 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.wso2.carbon.device.mgt.iot.droneanalyzer.plugin.internal; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.osgi.framework.BundleContext; +import org.osgi.framework.ServiceRegistration; +import org.osgi.service.component.ComponentContext; +import org.wso2.carbon.device.mgt.common.spi.DeviceManagementService; +import org.wso2.carbon.device.mgt.iot.droneanalyzer.plugin.exception.DroneAnalyzerDeviceMgtPluginException; +import org.wso2.carbon.device.mgt.iot.droneanalyzer.plugin.impl.DroneAnalyzerManagerService; +import org.wso2.carbon.device.mgt.iot.droneanalyzer.plugin.impl.util.DroneAnalyzerUtils; + +/** + * @scr.component name="org.wso2.carbon.device.mgt.iot.droneanalyzer.plugin.internal + * .DroneAnalyzerManagementServiceComponent" + * immediate="true" + */ +public class DroneAnalyzerManagementServiceComponent { + private ServiceRegistration firealarmServiceRegRef; + + private static final Log log = LogFactory.getLog( + DroneAnalyzerManagementServiceComponent.class); + + protected void activate(ComponentContext ctx) { + if (log.isDebugEnabled()) { + log.debug("Activating Drone Analyzer Management Service Component"); + } + try { + BundleContext bundleContext = ctx.getBundleContext(); + firealarmServiceRegRef = + bundleContext.registerService(DeviceManagementService.class.getName(), + new DroneAnalyzerManagerService(), null); + String setupOption = System.getProperty("setup"); + if (setupOption != null) { + if (log.isDebugEnabled()) { + log.debug( + "-Dsetup is enabled. Iot Device management repository schema initialization is about " + + "to begin"); + } + try { + DroneAnalyzerUtils.setupDeviceManagementSchema(); + } catch (DroneAnalyzerDeviceMgtPluginException e) { + log.error("Exception occurred while initializing device management database schema", e); + } + } + if (log.isDebugEnabled()) { + log.debug( + "Drone Analyzer Device Management Service Component has been successfully activated"); + } + } catch (Throwable e) { + log.error("Error occurred while activating Drone Analyzer Device Management Service Component", e); + } + } + + protected void deactivate(ComponentContext ctx) { + if (log.isDebugEnabled()) { + log.debug("De-activating Drone Analyzer Device Management Service Component"); + } + try { + if (firealarmServiceRegRef != null) { + firealarmServiceRegRef.unregister(); + } + if (log.isDebugEnabled()) { + log.debug("Drone Analyzer Device Management Service Component has been successfully de-activated"); + } + } catch (Throwable e) { + log.error( + "Error occurred while de-activating Drone Analyzer Device Management bundle",e); + } + } +} diff --git a/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/pom.xml b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/pom.xml new file mode 100644 index 0000000000..5b7426489a --- /dev/null +++ b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/pom.xml @@ -0,0 +1,62 @@ + + + + + + + + drone-analyzer-plugin + org.wso2.carbon.devicemgt-plugins + 2.1.0-SNAPSHOT + ../pom.xml + + + 4.0.0 + org.wso2.carbon.device.mgt.iot.droneanalyzer.ui + WSO2 Carbon - IoT Server Drone Analyzer UI + pom + + + + + maven-assembly-plugin + 2.5.5 + + ${project.artifactId}-${carbon.device.mgt.version} + false + + src/assembly/src.xml + + + + + create-archive + package + + single + + + + + + + + \ No newline at end of file diff --git a/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/src/assembly/src.xml b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/src/assembly/src.xml new file mode 100644 index 0000000000..2797034e07 --- /dev/null +++ b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/src/assembly/src.xml @@ -0,0 +1,36 @@ + + + + src + + zip + + false + ${basedir}/src + + + ${basedir}/src/main/resources/jaggeryapps/devicemgt + / + true + + + \ No newline at end of file diff --git a/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.drone_analyzer.device-view/device-view.hbs b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.drone_analyzer.device-view/device-view.hbs new file mode 100644 index 0000000000..1d32daed4e --- /dev/null +++ b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.drone_analyzer.device-view/device-view.hbs @@ -0,0 +1,52 @@ +{{#zone "device-thumbnail"}} + +{{/zone}} +{{#zone "device-opetations"}} +
      + {{unit "iot.unit.device.operation-bar" device=device}} +
      +
      +
      Device Statistics
      + {{unit "iot.unit.device.droneanalyzer.statistics" device=device}} +
      + +{{/zone}} +{{#zone "device-detail-properties"}} +
      +
      + +
      +
      + +
      +
      +
      Operations Log
      +
      + +
      +
      + Not available yet +
      +
      +
      +
      +
      +
      +
      +
      +{{/zone}} \ No newline at end of file diff --git a/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.drone_analyzer.device-view/device-view.js b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.drone_analyzer.device-view/device-view.js new file mode 100644 index 0000000000..1fcbacc5be --- /dev/null +++ b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.drone_analyzer.device-view/device-view.js @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2016, 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("device-view.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") { + return {"device": device}; + } else { + response.sendError(404, "Device Id " + deviceId + "of type " + deviceType + " cannot be found!"); + exit(); + } + } +} diff --git a/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.drone_analyzer.device-view/device-view.json b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.drone_analyzer.device-view/device-view.json new file mode 100644 index 0000000000..9eecd8f5bf --- /dev/null +++ b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.drone_analyzer.device-view/device-view.json @@ -0,0 +1,3 @@ +{ + "version": "1.0.0" +} \ No newline at end of file diff --git a/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.drone_analyzer.device-view/public/images/drone-icon.png b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.drone_analyzer.device-view/public/images/drone-icon.png new file mode 100644 index 0000000000..0c7744ff42 Binary files /dev/null and b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.drone_analyzer.device-view/public/images/drone-icon.png differ diff --git a/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.drone_analyzer.device-view/public/images/thumb.png b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.drone_analyzer.device-view/public/images/thumb.png new file mode 100644 index 0000000000..0c7744ff42 Binary files /dev/null and b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.drone_analyzer.device-view/public/images/thumb.png differ diff --git a/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.drone_analyzer.type-view/public/images/devices_analytics.png b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.drone_analyzer.type-view/public/images/devices_analytics.png new file mode 100644 index 0000000000..cb87401d5b Binary files /dev/null and b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.drone_analyzer.type-view/public/images/devices_analytics.png differ diff --git a/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.drone_analyzer.type-view/public/images/drone-icon.png b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.drone_analyzer.type-view/public/images/drone-icon.png new file mode 100644 index 0000000000..0c7744ff42 Binary files /dev/null and b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.drone_analyzer.type-view/public/images/drone-icon.png differ diff --git a/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.drone_analyzer.type-view/public/images/schematicsGuide.png b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.drone_analyzer.type-view/public/images/schematicsGuide.png new file mode 100644 index 0000000000..a755f05187 Binary files /dev/null and b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.drone_analyzer.type-view/public/images/schematicsGuide.png differ diff --git a/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.drone_analyzer.type-view/public/images/thumb.png b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.drone_analyzer.type-view/public/images/thumb.png new file mode 100644 index 0000000000..b74b20f9f8 Binary files /dev/null and b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.drone_analyzer.type-view/public/images/thumb.png differ diff --git a/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.drone_analyzer.type-view/public/js/download.js b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.drone_analyzer.type-view/public/js/download.js new file mode 100644 index 0000000000..0c22e50997 --- /dev/null +++ b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.drone_analyzer.type-view/public/js/download.js @@ -0,0 +1,184 @@ +/* + * Copyright (c) 2016, 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. + */ + +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; + } + }); +} + +/* + * 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() { + var deviceName; + $('.new-device-name').each(function () { + if (this.value != "") { + deviceName = this.value; + } + }); + var deviceNameFormat = /^[^~?!#$:;%^*`+={}\[\]\\()|<>,'"]{1,30}$/; + if (deviceName && deviceNameFormat.test(deviceName)) { + $('#downloadForm').submit(); + hidePopup(); + $(modalPopupContent).html($('#device-agent-downloading-content').html()); + showPopup(); + setTimeout(function () { + hidePopup(); + }, 1000); + }else { + $("#invalid-username-error-msg span").text("Invalid device name"); + $("#invalid-username-error-msg").removeClass("hidden"); + } +} + +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/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.drone_analyzer.type-view/public/js/jquery.validate.js b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.drone_analyzer.type-view/public/js/jquery.validate.js new file mode 100644 index 0000000000..fe7ecf07a6 --- /dev/null +++ b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.drone_analyzer.type-view/public/js/jquery.validate.js @@ -0,0 +1,1227 @@ +/* + * Copyright (c) 2016, 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($) { + +$.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/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.drone_analyzer.type-view/type-view.hbs b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.drone_analyzer.type-view/type-view.hbs new file mode 100644 index 0000000000..d02b3354fe --- /dev/null +++ b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.drone_analyzer.type-view/type-view.hbs @@ -0,0 +1,315 @@ +
      +

      Drone Analyzer

      +
      +
      +
      + +
      +
      + +
      +
      +

      What it Does

      +
      +

      Connect an + [IRIS+] Drone to + WSO2 IoT Server and visualize statistics. +

      +
      + +

      What You Need

      +
      +
        +
      • + ITEM 01 +    IRIS+ Drone. +
      • +
      • + ITEM 02 +    USB to Micro USB cable or Telemetry Radio receiver. +
      • +
      • + STEP 03 +    Proceed to [Prepare] section. +
      +
      + View API   + + Download Agent + +
      + +
      +
      + +
      +
      + +
      +
      + +
      +
      + +
      +
      + +
      +
      + +
      +

      +
      +
      +

      Prepare

      +
      +
        +
      • + 01 +    Connect your IRIS+ Drone to your computer using either USB to Micro + USB cable or Telemetry Radio receiver. +
      • +
      • + 02 +    Click on [Download Agent] button above to get IRIS+ Drone agent. +
      • +
      • + 03 +    Once you have downloaded the agent please run + "[startService.sh]" script with root privileges. +
      • +
      • + 04 +    Then you will be prompted to enter time interval (in seconds) between + successive Data-Pushes to the XMPP server, connection target and communication speed. So + below table will help you to find what is the correct connection target and + communication speed. +
      • +
        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        +
        CONNECTION-TYPE
        +
        +
        CONNECTION-STRING
        +
        Linux computer connected to the vehicle via USB e.g. /dev/ttyUSB0
        Linux computer connected to the drone via Serial port e.g. /dev/ttyAMA0 (also set baud=57600)
        OSX computer connected to the drone via USBe.g. dev/cu.usbmodem1
        Windows computer connected to the drone via USB (in this case on COM14)e.g. com14
        Windows computer connected to the drone using a 3DR Telemetry Radio on + COM14 + e.g. com14 (also set baud=57600)
        +
        +
      +
      +
      +
      +

      IRIS+ Drone Connected to a computer

      +
      +

      Click on the image to zoom

      +
      + + + +
      +
      +
      +
      +

      Try Out

      +
      +
        +
      • + 01 +    You can view all your connected devices at + [Device Management] page. +
      • +
      • + 02 +    Select one of connected devices and view stats which are published by + the device. +
      • +
      +
      +

      Click on the image to zoom

      +
      + + + +
      +
      + +{{#zone "topCss"}} + +{{/zone}} + +{{#zone "bottomJs"}} + {{js "/js/download.js"}} + {{js "/js/jquery.validate.js"}} +{{/zone}} \ No newline at end of file diff --git a/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.drone_analyzer.type-view/type-view.json b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.drone_analyzer.type-view/type-view.json new file mode 100644 index 0000000000..9eecd8f5bf --- /dev/null +++ b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.drone_analyzer.type-view/type-view.json @@ -0,0 +1,3 @@ +{ + "version": "1.0.0" +} \ No newline at end of file diff --git a/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.device.droneanalyzer.statistics/public/css/main-app.css b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.device.droneanalyzer.statistics/public/css/main-app.css new file mode 100644 index 0000000000..9f7521295d --- /dev/null +++ b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.device.droneanalyzer.statistics/public/css/main-app.css @@ -0,0 +1,220 @@ +/* + * Copyright (c) 2016, 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. + */ +.box { + margin-top: 10px; + margin-bottom: 10px; +} + +.box-inner { + border: 1px solid #DEDEDE; + border-radius: 3px; + -webkit-border-radius: 3px; + -moz-border-radius: 3px; + box-shadow: 0 0 10px rgba(189, 189, 189, 0.4); + -webkit-box-shadow: 0 0 10px rgba(189, 189, 189, 0.4); + -moz-box-shadow: 0 0 10px rgba(189, 189, 189, 0.4); +} + +.box-header { + border: none; + padding-top: 5px; + border-bottom: 1px solid #DEDEDE; + border-radius: 3px 3px 0 0; + -webkit-border-radius: 3px 3px 0 0; + -moz-border-radius: 3px 3px 0 0; + height: 35px; + min-height: 35px !important; + margin-bottom: 0; + font-weight: bold; + font-size: 16px; + background: -moz-linear-gradient(top, rgba(255, 255, 255, 0) 0%, rgba(0, 0, 0, 0.1) 100%); + background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, rgba(255, 255, 255, 0)), color-stop(100%, rgba(0, 0, 0, 0.1))); + background: -webkit-linear-gradient(top, rgba(255, 255, 255, 0) 0%, rgba(0, 0, 0, 0.1) 100%); + background: -o-linear-gradient(top, rgba(255, 255, 255, 0) 0%, rgba(0, 0, 0, 0.1) 100%); + background: -ms-linear-gradient(top, rgba(255, 255, 255, 0) 0%, rgba(0, 0, 0, 0.1) 100%); + background: linear-gradient(to bottom, rgba(255, 255, 255, 0) 0%, rgba(0, 0, 0, 0.1) 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00ffffff', endColorstr='#1a000000', GradientType=0); + +} + +.box-header h2 { + font-size: 15px; + width: auto; + clear: none; + float: left; + line-height: 25px; + white-space: nowrap; + font-weight: bold; + margin-top: 0; + margin-bottom: 0; +} + +.box-header h3 { + font-size: 13px; + width: auto; + clear: none; + float: left; + line-height: 25px; + white-space: nowrap; +} + +.box-header h2 > i { + margin-top: 1px; +} + +.box-icon { + float: right; +} + +.box-icon a { + clear: none; + float: left; + margin: 0 2px; + height: 20px; + width: 5px; + margin-top: 1px; +} + +.box-icon a i { + margin-left: -6px; + top: -1px; +} + +.box-content { + padding: 10px; +} + +.btn-round { + border-radius: 40px; + -webkit-border-radius: 40px; + -moz-border-radius: 40px; + font-size: 12px; + padding-top: 4px; +} + +.navbar-brand { + font-family: 'Shojumaru', cursive, Arial, serif; + letter-spacing: 2px; + text-shadow: 1px 1px 5px rgba(0, 0, 0, 0.5); + width: 183px; + font-size: 17px; +} + +.navbar-brand img { + float: left; + height: 20px; + width: 20px; + margin-right: 5px; +} + +.navbar-brand span { + float: left; +} + +.navbar-search { + margin-left: 10px; + margin-top: 7px; +} + +.navbar-inner { + padding-top: 5px; + padding-bottom: 5px; + line-height: 30px; + height: 60px; +} + +.navbar-inner .btn-group { + margin: 7px 5px 0 5px; +} + +.bs-icons li { + list-style: none; +} + +.box-content .nav-tabs { + margin-right: -10px; + margin-left: -10px; +} + +.box-content.buttons { + min-height: 297px; +} + +.sidebar-nav .nav-header { + display: block; + padding: 3px 15px; + font-size: 11px; + font-weight: bold; + line-height: 18px; + color: #999999; + text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5); + text-transform: uppercase; +} + +.navbar { + border-radius: 0; +} +.circle { + background: none repeat scroll 0 0 #191919; + border-radius: 50px; + height: 50px; + padding: 10px; + width: 50px; + color: #fff; +} + +.padding-top-double { + padding-top: 20px; +} +.padding-double { + padding: 20px; +} +.grey { + color: #333; +} +hr { + display: block; + height: 1px; + border: 0; + border-top: 1px solid #7f7f7f; + margin: 1em 0; + padding: 0; + opacity: 0.2; +} +.light-grey { + color: #7c7c7c; +} +.uppercase { + text-transform: uppercase; +} +.grey-bg { + background-color: #f6f4f4; +} + +path { + stroke: black; + stroke-width: 2; + fill: none; +} + +.axis path, .axis line { + fill: none; + stroke: #ccc; + stroke-width: 2; + shape-rendering: crispEdges; +} diff --git a/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.device.droneanalyzer.statistics/public/images/drone_position_controller/background_drone.png b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.device.droneanalyzer.statistics/public/images/drone_position_controller/background_drone.png new file mode 100644 index 0000000000..c3ce7dc40d Binary files /dev/null and b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.device.droneanalyzer.statistics/public/images/drone_position_controller/background_drone.png differ diff --git a/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.device.droneanalyzer.statistics/public/images/drone_position_controller/direction_drone.png b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.device.droneanalyzer.statistics/public/images/drone_position_controller/direction_drone.png new file mode 100644 index 0000000000..0f5dd65963 Binary files /dev/null and b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.device.droneanalyzer.statistics/public/images/drone_position_controller/direction_drone.png differ diff --git a/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.device.droneanalyzer.statistics/public/images/drone_position_controller/pitch_drone.png b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.device.droneanalyzer.statistics/public/images/drone_position_controller/pitch_drone.png new file mode 100644 index 0000000000..84afb8f188 Binary files /dev/null and b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.device.droneanalyzer.statistics/public/images/drone_position_controller/pitch_drone.png differ diff --git a/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.device.droneanalyzer.statistics/public/images/no_video_preview.gif b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.device.droneanalyzer.statistics/public/images/no_video_preview.gif new file mode 100644 index 0000000000..d8ae1430bf Binary files /dev/null and b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.device.droneanalyzer.statistics/public/images/no_video_preview.gif differ diff --git a/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.device.droneanalyzer.statistics/public/js/3dobject_controller/3dObjectControler.js b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.device.droneanalyzer.statistics/public/js/3dobject_controller/3dObjectControler.js new file mode 100644 index 0000000000..711acfcd64 --- /dev/null +++ b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.device.droneanalyzer.statistics/public/js/3dobject_controller/3dObjectControler.js @@ -0,0 +1,206 @@ +/* + * Copyright (c) 2016, 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. + */ + +var camera, scene, renderer; +var cameraControls; +var clock = new THREE.Clock(); + +var ground = true; +var circle1, plate1; + +var object_maker = function () { + var make_object = this; + make_object.fillScene = function () { + scene = new THREE.Scene(); + scene.fog = new THREE.Fog(0x808080, 2000, 4000); + + + var ambientLight = new THREE.AmbientLight(0x222222); + var light = new THREE.DirectionalLight(0xFFFFFF, 1.0); + light.position.set(200, 400, 500); + + var light2 = new THREE.DirectionalLight(0xFFFFFF, 1.0); + light2.position.set(-500, 250, -200); + + scene.add(ambientLight); + scene.add(light); + scene.add(light2); + + if (ground) { + Coordinates.drawGround({size: 10000}); + } + + + var robotBaseMaterial = new THREE.MeshPhongMaterial({color: 0x6E23BB, specular: 0x6E23BB, shininess: 20}); + var robotForearmMaterial = new THREE.MeshPhongMaterial({color: 0xF4C154, specular: 0xF4C154, shininess: 100}); + + circle1 = new THREE.Object3D(); + var circleLength = 40; + make_object.addCircles(circle1, circleLength, robotBaseMaterial); + circle1.position.y = circleLength * 2; + scene.add(circle1); + + plate1 = new THREE.Object3D(); + var plateLength = 40; + make_object.addPlates(plate1, plateLength, robotForearmMaterial); + plate1.position.y = circleLength / 8; + circle1.add(plate1); + + }, + + make_object.addPlates = function (part, plateLength, material) { + + var cylinder = new THREE.Mesh( + new THREE.CylinderGeometry(5, 5, 40, 32), material); + cylinder.rotation.x = 90 * Math.PI / 180; + cylinder.position.x = plateLength + 15; + part.add(cylinder); + cylinder = new THREE.Mesh( + new THREE.CylinderGeometry(5, 5, 40, 32), material); + cylinder.rotation.x = 90 * Math.PI / 180; + cylinder.rotation.z = 90 * Math.PI / 180; + cylinder.position.x = plateLength + 15; + part.add(cylinder); + + cylinder = new THREE.Mesh( + new THREE.CylinderGeometry(5, 5, 40, 32), material); + cylinder.rotation.x = 90 * Math.PI / 180; + cylinder.position.x = -plateLength - 15; + part.add(cylinder); + cylinder = new THREE.Mesh( + new THREE.CylinderGeometry(5, 5, 40, 32), material); + cylinder.rotation.x = 90 * Math.PI / 180; + cylinder.rotation.z = 90 * Math.PI / 180; + cylinder.position.x = -plateLength - 15; + part.add(cylinder); + + cylinder = new THREE.Mesh( + new THREE.CylinderGeometry(5, 5, 40, 32), material); + cylinder.rotation.x = 90 * Math.PI / 180; + cylinder.position.z = -plateLength - 15; + part.add(cylinder); + cylinder = new THREE.Mesh( + new THREE.CylinderGeometry(5, 5, 40, 32), material); + cylinder.rotation.x = 90 * Math.PI / 180; + cylinder.rotation.z = 90 * Math.PI / 180; + cylinder.position.z = -plateLength - 15; + part.add(cylinder); + // + cylinder = new THREE.Mesh( + new THREE.CylinderGeometry(5, 5, 40, 32), material); + cylinder.rotation.x = 90 * Math.PI / 180; + cylinder.position.z = plateLength + 15; + part.add(cylinder); + cylinder = new THREE.Mesh( + new THREE.CylinderGeometry(5, 5, 40, 32), material); + cylinder.rotation.x = 90 * Math.PI / 180; + cylinder.rotation.z = 90 * Math.PI / 180; + cylinder.position.z = plateLength + 15; + part.add(cylinder); + + }, + + make_object.addCircles = function (part, circleLength, material) { + var circle = new THREE.Mesh( + new THREE.TorusGeometry(40, 10, 40, 20, 6.3), material); + circle.position.x = circleLength + 10; + circle.rotation.x = 90 * Math.PI / 180; + part.add(circle); + circle = new THREE.Mesh( + new THREE.TorusGeometry(40, 10, 40, 20, 6.3), material); + circle.position.x = -circleLength - 10; + circle.rotation.x = 90 * Math.PI / 180; + part.add(circle); + circle = new THREE.Mesh( + new THREE.TorusGeometry(40, 10, 40, 20, 6.3), material); + circle.position.z = -circleLength - 10; + circle.rotation.x = 90 * Math.PI / 180; + part.add(circle); + circle = new THREE.Mesh( + new THREE.TorusGeometry(40, 10, 40, 20, 6.3), material); + circle.position.z = circleLength + 10; + circle.rotation.x = 90 * Math.PI / 180; + part.add(circle); + }, + + make_object.init = function (holder, object_width, object_height) { + var canvasRatio = 1; + renderer = new THREE.WebGLRenderer({antialias: true}); + renderer.gammaInput = true; + renderer.gammaOutput = true; + renderer.setSize(object_width, object_height); + renderer.setClearColorHex(0xAAAAAA, 1.0); + + $(holder).append(renderer.domElement); + camera = new THREE.PerspectiveCamera(30, canvasRatio, 1, 10000); + camera.position.set(-510, 240, 100); + cameraControls = new THREE.OrbitAndPanControls(camera, renderer.domElement); + cameraControls.target.set(0, 100, 0); + make_object.fillScene(); + + }, + make_object.animate = function () { + window.requestAnimationFrame(make_object.animate); + make_object.render(); + }, + make_object.render = function () { + var delta = clock.getDelta(); + cameraControls.update(delta); + circle1.rotation.z = config_api.effectController.uz; + circle1.rotation.y = config_api.effectController.uy; // yaw + circle1.rotation.x = config_api.effectController.ux; // roll + circle1.position.z = config_api.effectController.fz; + circle1.position.x = config_api.effectController.fx; + renderer.render(scene, camera); + }, + make_object.get_heading_attitude_bank = function (data) { + if (data.length < 4) { + return {"heading": data[0], "attitude": data[1], "bank": data[2]}; + } else { + + var heading = Math.atan2(2 * data[1] * data[3] - 2 * data[0] * data[1], 1 - 2 * data[1] * data[1] + - 2 * data[2] * data[2]); + var bank = Math.atan2(2 * data[0] * data[3] - 2 * data[1] * data[2], 1 - 2 * data[0] * data[0] + - 2 * data[2] * data[2]); + var attitude = Math.asin(2 * data[0] * data[1] + 2 * data[2] * data[3]); + + return { + "heading": isNaN(heading) ? 0 : heading, + "bank": isNaN(bank) ? 0 : bank, + "attitude": isNaN(attitude) ? 0 : attitude + }; + } + }, + make_object.set_heading_attitude_bank = function (data) { + config_api.effectController.uy = data.heading; + config_api.effectController.uz = data.attitude; + config_api.effectController.ux = data.bank; + }, + make_object.set_heading = function (holder, heading) { + var r = (180 / Math.PI) * parseFloat(heading); + $(holder).rotate(r); + + }, + make_object.set_bank = function (holder, bank) { + var r = (180 / Math.PI) * parseFloat(bank); + $(holder).rotate(r); + } + +}; + + diff --git a/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.device.droneanalyzer.statistics/public/js/3dobject_controller/Coordinates.js b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.device.droneanalyzer.statistics/public/js/3dobject_controller/Coordinates.js new file mode 100644 index 0000000000..10624ccef8 --- /dev/null +++ b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.device.droneanalyzer.statistics/public/js/3dobject_controller/Coordinates.js @@ -0,0 +1,166 @@ +/* + * Copyright (c) 2016, 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. + */ + +/*global THREE, scene*/ +var Coordinates = { + drawGrid:function(params) { + params = params || {}; + var size = params.size !== undefined ? params.size:100; + var scale = params.scale !== undefined ? params.scale:0.1; + var orientation = params.orientation !== undefined ? params.orientation:"x"; + var grid = new THREE.Mesh( + new THREE.PlaneGeometry(size, size, size * scale, size * scale), + new THREE.MeshBasicMaterial({ color: 0x555555, wireframe: true }) + ); + // Yes, these are poorly labeled! It would be a mess to fix. + // What's really going on here: + // "x" means "rotate 90 degrees around x", etc. + // So "x" really means "show a grid with a normal of Y" + // "y" means "show a grid with a normal of X" + // "z" means (logically enough) "show a grid with a normal of Z" + if (orientation === "x") { + grid.rotation.x = - Math.PI / 2; + } else if (orientation === "y") { + grid.rotation.y = - Math.PI / 2; + } else if (orientation === "z") { + grid.rotation.z = - Math.PI / 2; + } + + scene.add(grid); + }, + drawGround:function(params) { + params = params || {}; + var size = params.size !== undefined ? params.size:100; + var color = params.color !== undefined ? params.color:0xFFFFFF; + var ground = new THREE.Mesh( + new THREE.PlaneGeometry(size, size), + // When we use a ground plane we use directional lights, so illuminating + // just the corners is sufficient. + // Use MeshPhongMaterial if you want to capture per-pixel lighting: + // new THREE.MeshPhongMaterial({ color: color, specular: 0x000000, + new THREE.MeshLambertMaterial({ color: color, + // polygonOffset moves the plane back from the eye a bit, so that the lines on top of + // the grid do not have z-fighting with the grid: + // Factor == 1 moves it back relative to the slope (more on-edge means move back farther) + // Units == 4 is a fixed amount to move back, and 4 is usually a good value + polygonOffset: true, polygonOffsetFactor: 1.0, polygonOffsetUnits: 4.0 + })); + ground.rotation.x = - Math.PI / 2; + scene.add(ground); + }, + drawAxes:function(params) { + // x = red, y = green, z = blue (RGB = xyz) + params = params || {}; + var axisRadius = params.axisRadius !== undefined ? params.axisRadius:0.04; + var axisLength = params.axisLength !== undefined ? params.axisLength:11; + var axisTess = params.axisTess !== undefined ? params.axisTess:48; + var axisOrientation = params.axisOrientation !== undefined ? params.axisOrientation:"x"; + + var axisMaterial = new THREE.MeshLambertMaterial({ color: 0x000000, side: THREE.DoubleSide }); + var axis = new THREE.Mesh( + new THREE.CylinderGeometry(axisRadius, axisRadius, axisLength, axisTess, 1, true), + axisMaterial + ); + if (axisOrientation === "x") { + axis.rotation.z = - Math.PI / 2; + axis.position.x = axisLength/2-1; + } else if (axisOrientation === "y") { + axis.position.y = axisLength/2-1; + } + + scene.add( axis ); + + var arrow = new THREE.Mesh( + new THREE.CylinderGeometry(0, 4*axisRadius, 8*axisRadius, axisTess, 1, true), + axisMaterial + ); + if (axisOrientation === "x") { + arrow.rotation.z = - Math.PI / 2; + arrow.position.x = axisLength - 1 + axisRadius*4/2; + } else if (axisOrientation === "y") { + arrow.position.y = axisLength - 1 + axisRadius*4/2; + } + + scene.add( arrow ); + + }, + drawAllAxes:function(params) { + params = params || {}; + var axisRadius = params.axisRadius !== undefined ? params.axisRadius:0.04; + var axisLength = params.axisLength !== undefined ? params.axisLength:11; + var axisTess = params.axisTess !== undefined ? params.axisTess:48; + + var axisXMaterial = new THREE.MeshLambertMaterial({ color: 0xFF0000 }); + var axisYMaterial = new THREE.MeshLambertMaterial({ color: 0x00FF00 }); + var axisZMaterial = new THREE.MeshLambertMaterial({ color: 0x0000FF }); + axisXMaterial.side = THREE.DoubleSide; + axisYMaterial.side = THREE.DoubleSide; + axisZMaterial.side = THREE.DoubleSide; + var axisX = new THREE.Mesh( + new THREE.CylinderGeometry(axisRadius, axisRadius, axisLength, axisTess, 1, true), + axisXMaterial + ); + var axisY = new THREE.Mesh( + new THREE.CylinderGeometry(axisRadius, axisRadius, axisLength, axisTess, 1, true), + axisYMaterial + ); + var axisZ = new THREE.Mesh( + new THREE.CylinderGeometry(axisRadius, axisRadius, axisLength, axisTess, 1, true), + axisZMaterial + ); + axisX.rotation.z = - Math.PI / 2; + axisX.position.x = axisLength/2-1; + + axisY.position.y = axisLength/2-1; + + axisZ.rotation.y = - Math.PI / 2; + axisZ.rotation.z = - Math.PI / 2; + axisZ.position.z = axisLength/2-1; + + scene.add( axisX ); + scene.add( axisY ); + scene.add( axisZ ); + + var arrowX = new THREE.Mesh( + new THREE.CylinderGeometry(0, 4*axisRadius, 4*axisRadius, axisTess, 1, true), + axisXMaterial + ); + var arrowY = new THREE.Mesh( + new THREE.CylinderGeometry(0, 4*axisRadius, 4*axisRadius, axisTess, 1, true), + axisYMaterial + ); + var arrowZ = new THREE.Mesh( + new THREE.CylinderGeometry(0, 4*axisRadius, 4*axisRadius, axisTess, 1, true), + axisZMaterial + ); + arrowX.rotation.z = - Math.PI / 2; + arrowX.position.x = axisLength - 1 + axisRadius*4/2; + + arrowY.position.y = axisLength - 1 + axisRadius*4/2; + + arrowZ.rotation.z = - Math.PI / 2; + arrowZ.rotation.y = - Math.PI / 2; + arrowZ.position.z = axisLength - 1 + axisRadius*4/2; + + scene.add( arrowX ); + scene.add( arrowY ); + scene.add( arrowZ ); + + } + +}; \ No newline at end of file diff --git a/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.device.droneanalyzer.statistics/public/js/3dobject_controller/OrbitAndPanControls.js b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.device.droneanalyzer.statistics/public/js/3dobject_controller/OrbitAndPanControls.js new file mode 100644 index 0000000000..246ba0d9b5 --- /dev/null +++ b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.device.droneanalyzer.statistics/public/js/3dobject_controller/OrbitAndPanControls.js @@ -0,0 +1,549 @@ +/* + * Copyright (c) 2016, 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. + */ + +/** + * @author qiao / https://github.com/qiao + * @author mrdoob / http://mrdoob.com + * @author alteredq / http://alteredqualia.com/ + * @author WestLangley / http://github.com/WestLangley + * @author erich666 / http://erichaines.com + */ +/*global THREE, console */ + +THREE.OrbitAndPanControls = function ( object, domElement ) { + + THREE.EventDispatcher.call( this ); + + this.enabled = true; + + this.object = object; + this.domElement = ( domElement !== undefined ) ? domElement : document; + + // API + + this.enabled = true; + + this.target = new THREE.Vector3(); + // center is old, deprecated; use "target" instead + this.center = this.target; + + // This option actually enables dollying in and out + this.noZoom = false; + this.zoomSpeed = 1.0; + + this.noRotate = false; + this.rotateSpeed = 1.0; + + this.noPan = false; + + this.autoRotate = false; + this.autoRotateSpeed = 2.0; // 30 seconds per round when fps is 60 + + this.minPolarAngle = 0; // radians + this.maxPolarAngle = Math.PI; // radians + + this.minDistance = 0; + this.maxDistance = Infinity; + + this.noKeys = false; + this.keys = { LEFT: 37, UP: 38, RIGHT: 39, BOTTOM: 40 }; + + // internals + + var scope = this; + + var EPS = 0.000001; + + var rotateStart = new THREE.Vector2(); + var rotateEnd = new THREE.Vector2(); + var rotateDelta = new THREE.Vector2(); + + var panStart = new THREE.Vector2(); + var panEnd = new THREE.Vector2(); + var panDelta = new THREE.Vector2(); + + var dollyStart = new THREE.Vector2(); + var dollyEnd = new THREE.Vector2(); + var dollyDelta = new THREE.Vector2(); + + var phiDelta = 0; + var thetaDelta = 0; + var scale = 1; + var pan = new THREE.Vector3(); + + var lastPosition = new THREE.Vector3(); + + var STATE = { NONE : -1, ROTATE : 0, DOLLY : 1, PAN : 2, TOUCH_ROTATE : 3, TOUCH_DOLLY : 4, TOUCH_PAN : 5 }; + var state = STATE.NONE; + + // events + + var changeEvent = { type: 'change' }; + + + this.rotateLeft = function ( angle ) { + + if ( angle === undefined ) { + + angle = getAutoRotationAngle(); + + } + + thetaDelta -= angle; + + }; + + this.rotateUp = function ( angle ) { + + if ( angle === undefined ) { + + angle = getAutoRotationAngle(); + + } + + phiDelta -= angle; + + }; + + // pass in distance in world space to move left + this.panLeft = function ( distance ) { + + var panOffset = new THREE.Vector3(); + var te = this.object.matrix.elements; + // get X column of matrix + panOffset.set( te[0], te[1], te[2] ); + panOffset.multiplyScalar(-distance); + + pan.add( panOffset ); + + }; + + // pass in distance in world space to move up + this.panUp = function ( distance ) { + + var panOffset = new THREE.Vector3(); + var te = this.object.matrix.elements; + // get Y column of matrix + panOffset.set( te[4], te[5], te[6] ); + panOffset.multiplyScalar(distance); + + pan.add( panOffset ); + }; + + // main entry point; pass in Vector2 of change desired in pixel space, + // right and down are positive + this.pan = function ( delta ) { + + if ( scope.object.fov !== undefined ) + { + // perspective + var position = scope.object.position; + var offset = position.clone().sub( scope.target ); + var targetDistance = offset.length(); + + // half of the fov is center to top of screen + targetDistance *= Math.tan( (scope.object.fov/2) * Math.PI / 180.0 ); + // we actually don't use screenWidth, since perspective camera is fixed to screen height + scope.panLeft( 2 * delta.x * targetDistance / scope.domElement.height ); + scope.panUp( 2 * delta.y * targetDistance / scope.domElement.height ); + } + else if ( scope.object.top !== undefined ) + { + // orthographic + scope.panLeft( delta.x * (scope.object.right - scope.object.left) / scope.domElement.width ); + scope.panUp( delta.y * (scope.object.top - scope.object.bottom) / scope.domElement.height ); + } + else + { + // camera neither orthographic or perspective - warn user + console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' ); + } + }; + + this.dollyIn = function ( dollyScale ) { + + if ( dollyScale === undefined ) { + + dollyScale = getZoomScale(); + + } + + scale /= dollyScale; + + }; + + this.dollyOut = function ( dollyScale ) { + + if ( dollyScale === undefined ) { + + dollyScale = getZoomScale(); + + } + + scale *= dollyScale; + + }; + + this.update = function () { + + var position = this.object.position; + var offset = position.clone().sub( this.target ); + + // angle from z-axis around y-axis + + var theta = Math.atan2( offset.x, offset.z ); + + // angle from y-axis + + var phi = Math.atan2( Math.sqrt( offset.x * offset.x + offset.z * offset.z ), offset.y ); + + if ( this.autoRotate ) { + + this.rotateLeft( getAutoRotationAngle() ); + + } + + theta += thetaDelta; + phi += phiDelta; + + // restrict phi to be between desired limits + phi = Math.max( this.minPolarAngle, Math.min( this.maxPolarAngle, phi ) ); + + // restrict phi to be betwee EPS and PI-EPS + phi = Math.max( EPS, Math.min( Math.PI - EPS, phi ) ); + + var radius = offset.length() * scale; + + // restrict radius to be between desired limits + radius = Math.max( this.minDistance, Math.min( this.maxDistance, radius ) ); + + // move target to panned location + this.target.add( pan ); + + offset.x = radius * Math.sin( phi ) * Math.sin( theta ); + offset.y = radius * Math.cos( phi ); + offset.z = radius * Math.sin( phi ) * Math.cos( theta ); + + position.copy( this.target ).add( offset ); + + this.object.lookAt( this.target ); + + thetaDelta = 0; + phiDelta = 0; + scale = 1; + pan.set(0,0,0); + + if ( lastPosition.distanceTo( this.object.position ) > 0 ) { + + this.dispatchEvent( changeEvent ); + + lastPosition.copy( this.object.position ); + + } + + }; + + + function getAutoRotationAngle() { + + return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed; + + } + + function getZoomScale() { + + return Math.pow( 0.95, scope.zoomSpeed ); + + } + + function onMouseDown( event ) { + + if ( scope.enabled === false ) { return; } + event.preventDefault(); + + if ( event.button === 0 ) { + if ( scope.noRotate === true ) { return; } + + state = STATE.ROTATE; + + rotateStart.set( event.clientX, event.clientY ); + + } else if ( event.button === 1 ) { + if ( scope.noZoom === true ) { return; } + + state = STATE.DOLLY; + + dollyStart.set( event.clientX, event.clientY ); + + } else if ( event.button === 2 ) { + if ( scope.noPan === true ) { return; } + + state = STATE.PAN; + + panStart.set( event.clientX, event.clientY ); + + } + + document.addEventListener( 'mousemove', onMouseMove, false ); + document.addEventListener( 'mouseup', onMouseUp, false ); + + } + + function onMouseMove( event ) { + + if ( scope.enabled === false ) { return; } + + event.preventDefault(); + + if ( state === STATE.ROTATE ) { + if ( scope.noRotate === true ) { return; } + + rotateEnd.set( event.clientX, event.clientY ); + rotateDelta.subVectors( rotateEnd, rotateStart ); + + // rotating across whole screen goes 360 degrees around + scope.rotateLeft( 2 * Math.PI * rotateDelta.x / scope.domElement.width * scope.rotateSpeed ); + // rotating up and down along whole screen attempts to go 360, but limited to 180 + scope.rotateUp( 2 * Math.PI * rotateDelta.y / scope.domElement.height * scope.rotateSpeed ); + + rotateStart.copy( rotateEnd ); + + } else if ( state === STATE.DOLLY ) { + if ( scope.noZoom === true ) { return; } + + dollyEnd.set( event.clientX, event.clientY ); + dollyDelta.subVectors( dollyEnd, dollyStart ); + + if ( dollyDelta.y > 0 ) { + + scope.dollyIn(); + + } else { + + scope.dollyOut(); + + } + + dollyStart.copy( dollyEnd ); + + } else if ( state === STATE.PAN ) { + if ( scope.noPan === true ) { return; } + + panEnd.set( event.clientX, event.clientY ); + panDelta.subVectors( panEnd, panStart ); + + scope.pan( panDelta ); + + panStart.copy( panEnd ); + + } + + } + + function onMouseUp( /* event */ ) { + + if ( scope.enabled === false ) { return; } + + document.removeEventListener( 'mousemove', onMouseMove, false ); + document.removeEventListener( 'mouseup', onMouseUp, false ); + + state = STATE.NONE; + + } + + function onMouseWheel( event ) { + // this is needed when the program is inside an iframe + // to prevent scrolling the whole page + event.preventDefault(); + if ( scope.enabled === false ) { return; } + if ( scope.noZoom === true ) { return; } + + var delta = 0; + + if ( event.wheelDelta ) { // WebKit / Opera / Explorer 9 + + delta = event.wheelDelta; + + } else if ( event.detail ) { // Firefox + + delta = - event.detail; + + } + + if ( delta > 0 ) { + + scope.dollyOut(); + + } else { + + scope.dollyIn(); + + } + + } + + function onKeyDown( event ) { + + if ( scope.enabled === false ) { return; } + if ( scope.noKeys === true ) { return; } + if ( scope.noPan === true ) { return; } + + // pan a pixel - I guess for precise positioning? + switch ( event.keyCode ) { + + case scope.keys.UP: + scope.pan( new THREE.Vector2( 0, 1 ) ); + break; + case scope.keys.BOTTOM: + scope.pan( new THREE.Vector2( 0, -1 ) ); + break; + case scope.keys.LEFT: + scope.pan( new THREE.Vector2( 1, 0 ) ); + break; + case scope.keys.RIGHT: + scope.pan( new THREE.Vector2( -1, 0 ) ); + break; + } + + } + + function touchstart( event ) { + + if ( scope.enabled === false ) { return; } + + switch ( event.touches.length ) { + + case 1: // one-fingered touch: rotate + if ( scope.noRotate === true ) { return; } + + state = STATE.TOUCH_ROTATE; + + rotateStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); + break; + + case 2: // two-fingered touch: dolly + if ( scope.noZoom === true ) { return; } + + state = STATE.TOUCH_DOLLY; + + var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; + var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; + var distance = Math.sqrt( dx * dx + dy * dy ); + dollyStart.set( 0, distance ); + break; + + case 3: // three-fingered touch: pan + if ( scope.noPan === true ) { return; } + + state = STATE.TOUCH_PAN; + + panStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); + break; + + default: + state = STATE.NONE; + + } + } + + function touchmove( event ) { + + if ( scope.enabled === false ) { return; } + + event.preventDefault(); + event.stopPropagation(); + + switch ( event.touches.length ) { + + case 1: // one-fingered touch: rotate + if ( scope.noRotate === true ) { return; } + if ( state !== STATE.TOUCH_ROTATE ) { return; } + + rotateEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); + rotateDelta.subVectors( rotateEnd, rotateStart ); + + // rotating across whole screen goes 360 degrees around + scope.rotateLeft( 2 * Math.PI * rotateDelta.x / scope.domElement.width * scope.rotateSpeed ); + // rotating up and down along whole screen attempts to go 360, but limited to 180 + scope.rotateUp( 2 * Math.PI * rotateDelta.y / scope.domElement.height * scope.rotateSpeed ); + + rotateStart.copy( rotateEnd ); + break; + + case 2: // two-fingered touch: dolly + if ( scope.noZoom === true ) { return; } + if ( state !== STATE.TOUCH_DOLLY ) { return; } + + var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; + var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; + var distance = Math.sqrt( dx * dx + dy * dy ); + + dollyEnd.set( 0, distance ); + dollyDelta.subVectors( dollyEnd, dollyStart ); + + if ( dollyDelta.y > 0 ) { + + scope.dollyOut(); + + } else { + + scope.dollyIn(); + + } + + dollyStart.copy( dollyEnd ); + break; + + case 3: // three-fingered touch: pan + if ( scope.noPan === true ) { return; } + if ( state !== STATE.TOUCH_PAN ) { return; } + + panEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); + panDelta.subVectors( panEnd, panStart ); + + scope.pan( panDelta ); + + panStart.copy( panEnd ); + break; + + default: + state = STATE.NONE; + + } + + } + + function touchend( /* event */ ) { + + if ( scope.enabled === false ) { return; } + + state = STATE.NONE; + } + + this.domElement.addEventListener( 'contextmenu', function ( event ) { event.preventDefault(); }, false ); + this.domElement.addEventListener( 'mousedown', onMouseDown, false ); + this.domElement.addEventListener( 'mousewheel', onMouseWheel, false ); + this.domElement.addEventListener( 'DOMMouseScroll', onMouseWheel, false ); // firefox + + this.domElement.addEventListener( 'keydown', onKeyDown, false ); + + this.domElement.addEventListener( 'touchstart', touchstart, false ); + this.domElement.addEventListener( 'touchend', touchend, false ); + this.domElement.addEventListener( 'touchmove', touchmove, false ); + +}; diff --git a/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.device.droneanalyzer.statistics/public/js/3dobject_controller/three.min.js b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.device.droneanalyzer.statistics/public/js/3dobject_controller/three.min.js new file mode 100644 index 0000000000..f088be7afc --- /dev/null +++ b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.device.droneanalyzer.statistics/public/js/3dobject_controller/three.min.js @@ -0,0 +1,724 @@ +/* + * Copyright (c) 2016, 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. + */ + +// three.js - http://github.com/mrdoob/three.js +'use strict';var THREE=THREE||{REVISION:"56"};self.console=self.console||{info:function(){},log:function(){},debug:function(){},warn:function(){},error:function(){}};self.Int32Array=self.Int32Array||Array;self.Float32Array=self.Float32Array||Array;String.prototype.trim=String.prototype.trim||function(){return this.replace(/^\s+|\s+$/g,"")}; +THREE.extend=function(a,b){if(Object.keys)for(var c=Object.keys(b),d=0,e=c.length;d>16&255)/255;this.g=(a>>8&255)/255;this.b=(a&255)/255;return this},setRGB:function(a,b,c){this.r=a;this.g=b;this.b=c;return this},setHSV:function(a,b,c){console.log("DEPRECATED: Color's .setHSV() will be removed. Use .setHSL( h, s, l ) instead.");return this.setHSL(a,b*c/(1>(a=(2-b)*c)?a:2-a),a/2)},setHSL:function(a, +b,c){if(0===b)this.r=this.g=this.b=c;else{var d=function(a,b,c){0>c&&(c+=1);1c?b:c<2/3?a+6*(b-a)*(2/3-c):a},b=0.5>=c?c*(1+b):c+b-c*b,c=2*c-b;this.r=d(c,b,a+1/3);this.g=d(c,b,a);this.b=d(c,b,a-1/3)}return this},setStyle:function(a){if(/^rgb\((\d+),(\d+),(\d+)\)$/i.test(a))return a=/^rgb\((\d+),(\d+),(\d+)\)$/i.exec(a),this.r=Math.min(255,parseInt(a[1],10))/255,this.g=Math.min(255,parseInt(a[2],10))/255,this.b=Math.min(255,parseInt(a[3],10))/255,this;if(/^rgb\((\d+)\%,(\d+)\%,(\d+)\%\)$/i.test(a))return a= +/^rgb\((\d+)\%,(\d+)\%,(\d+)\%\)$/i.exec(a),this.r=Math.min(100,parseInt(a[1],10))/100,this.g=Math.min(100,parseInt(a[2],10))/100,this.b=Math.min(100,parseInt(a[3],10))/100,this;if(/^\#([0-9a-f]{6})$/i.test(a))return a=/^\#([0-9a-f]{6})$/i.exec(a),this.setHex(parseInt(a[1],16)),this;if(/^\#([0-9a-f])([0-9a-f])([0-9a-f])$/i.test(a))return a=/^\#([0-9a-f])([0-9a-f])([0-9a-f])$/i.exec(a),this.setHex(parseInt(a[1]+a[1]+a[2]+a[2]+a[3]+a[3],16)),this;if(/^(\w+)$/i.test(a))return this.setHex(THREE.ColorKeywords[a]), +this},copy:function(a){this.r=a.r;this.g=a.g;this.b=a.b;return this},copyGammaToLinear:function(a){this.r=a.r*a.r;this.g=a.g*a.g;this.b=a.b*a.b;return this},copyLinearToGamma:function(a){this.r=Math.sqrt(a.r);this.g=Math.sqrt(a.g);this.b=Math.sqrt(a.b);return this},convertGammaToLinear:function(){var a=this.r,b=this.g,c=this.b;this.r=a*a;this.g=b*b;this.b=c*c;return this},convertLinearToGamma:function(){this.r=Math.sqrt(this.r);this.g=Math.sqrt(this.g);this.b=Math.sqrt(this.b);return this},getHex:function(){return 255* +this.r<<16^255*this.g<<8^255*this.b<<0},getHexString:function(){return("000000"+this.getHex().toString(16)).slice(-6)},getHSL:function(){var a={h:0,s:0,l:0};return function(){var b=this.r,c=this.g,d=this.b,e=Math.max(b,c,d),f=Math.min(b,c,d),g,h=(f+e)/2;if(f===e)f=g=0;else{var i=e-f,f=0.5>=h?i/(e+f):i/(2-e-f);switch(e){case b:g=(c-d)/i+(cf&&c>b?(c=2*Math.sqrt(1+c-f-b),this.w=(i-g)/c,this.x=0.25*c,this.y=(a+e)/c,this.z=(d+h)/c):f>b?(c=2*Math.sqrt(1+f-c-b),this.w=(d-h)/c,this.x=(a+e)/c,this.y=0.25*c,this.z=(g+i)/c):(c=2*Math.sqrt(1+b-c-f),this.w=(e-a)/c,this.x=(d+h)/c,this.y=(g+i)/c,this.z=0.25*c);return this},inverse:function(){this.conjugate().normalize(); +return this},conjugate:function(){this.x*=-1;this.y*=-1;this.z*=-1;return this},lengthSq:function(){return this.x*this.x+this.y*this.y+this.z*this.z+this.w*this.w},length:function(){return Math.sqrt(this.x*this.x+this.y*this.y+this.z*this.z+this.w*this.w)},normalize:function(){var a=this.length();0===a?(this.z=this.y=this.x=0,this.w=1):(a=1/a,this.x*=a,this.y*=a,this.z*=a,this.w*=a);return this},multiply:function(a,b){return void 0!==b?(console.warn("DEPRECATED: Quaternion's .multiply() now only accepts one argument. Use .multiplyQuaternions( a, b ) instead."), +this.multiplyQuaternions(a,b)):this.multiplyQuaternions(this,a)},multiplyQuaternions:function(a,b){var c=a.x,d=a.y,e=a.z,f=a.w,g=b.x,h=b.y,i=b.z,k=b.w;this.x=c*k+f*g+d*i-e*h;this.y=d*k+f*h+e*g-c*i;this.z=e*k+f*i+c*h-d*g;this.w=f*k-c*g-d*h-e*i;return this},multiplyVector3:function(a){console.warn("DEPRECATED: Quaternion's .multiplyVector3() has been removed. Use is now vector.applyQuaternion( quaternion ) instead.");return a.applyQuaternion(this)},slerp:function(a,b){var c=this.x,d=this.y,e=this.z, +f=this.w,g=f*a.w+c*a.x+d*a.y+e*a.z;0>g?(this.w=-a.w,this.x=-a.x,this.y=-a.y,this.z=-a.z,g=-g):this.copy(a);if(1<=g)return this.w=f,this.x=c,this.y=d,this.z=e,this;var h=Math.acos(g),i=Math.sqrt(1-g*g);if(0.001>Math.abs(i))return this.w=0.5*(f+this.w),this.x=0.5*(c+this.x),this.y=0.5*(d+this.y),this.z=0.5*(e+this.z),this;g=Math.sin((1-b)*h)/i;h=Math.sin(b*h)/i;this.w=f*g+this.w*h;this.x=c*g+this.x*h;this.y=d*g+this.y*h;this.z=e*g+this.z*h;return this},equals:function(a){return a.x===this.x&&a.y=== +this.y&&a.z===this.z&&a.w===this.w},clone:function(){return new THREE.Quaternion(this.x,this.y,this.z,this.w)}});THREE.Quaternion.slerp=function(a,b,c,d){return c.copy(a).slerp(b,d)};THREE.Vector2=function(a,b){this.x=a||0;this.y=b||0}; +THREE.extend(THREE.Vector2.prototype,{set:function(a,b){this.x=a;this.y=b;return this},setX:function(a){this.x=a;return this},setY:function(a){this.y=a;return this},setComponent:function(a,b){switch(a){case 0:this.x=b;break;case 1:this.y=b;break;default:throw Error("index is out of range: "+a);}},getComponent:function(a){switch(a){case 0:return this.x;case 1:return this.y;default:throw Error("index is out of range: "+a);}},copy:function(a){this.x=a.x;this.y=a.y;return this},add:function(a,b){if(void 0!== +b)return console.warn("DEPRECATED: Vector2's .add() now only accepts one argument. Use .addVectors( a, b ) instead."),this.addVectors(a,b);this.x+=a.x;this.y+=a.y;return this},addVectors:function(a,b){this.x=a.x+b.x;this.y=a.y+b.y;return this},addScalar:function(a){this.x+=a;this.y+=a;return this},sub:function(a,b){if(void 0!==b)return console.warn("DEPRECATED: Vector2's .sub() now only accepts one argument. Use .subVectors( a, b ) instead."),this.subVectors(a,b);this.x-=a.x;this.y-=a.y;return this}, +subVectors:function(a,b){this.x=a.x-b.x;this.y=a.y-b.y;return this},multiplyScalar:function(a){this.x*=a;this.y*=a;return this},divideScalar:function(a){0!==a?(this.x/=a,this.y/=a):this.set(0,0);return this},min:function(a){this.x>a.x&&(this.x=a.x);this.y>a.y&&(this.y=a.y);return this},max:function(a){this.xb.x&&(this.x=b.x);this.yb.y&&(this.y=b.y);return this},negate:function(){return this.multiplyScalar(-1)}, +dot:function(a){return this.x*a.x+this.y*a.y},lengthSq:function(){return this.x*this.x+this.y*this.y},length:function(){return Math.sqrt(this.x*this.x+this.y*this.y)},normalize:function(){return this.divideScalar(this.length())},distanceTo:function(a){return Math.sqrt(this.distanceToSquared(a))},distanceToSquared:function(a){var b=this.x-a.x,a=this.y-a.y;return b*b+a*a},setLength:function(a){var b=this.length();0!==b&&a!==b&&this.multiplyScalar(a/b);return this},lerp:function(a,b){this.x+=(a.x-this.x)* +b;this.y+=(a.y-this.y)*b;return this},equals:function(a){return a.x===this.x&&a.y===this.y},toArray:function(){return[this.x,this.y]},clone:function(){return new THREE.Vector2(this.x,this.y)}});THREE.Vector3=function(a,b,c){this.x=a||0;this.y=b||0;this.z=c||0}; +THREE.extend(THREE.Vector3.prototype,{set:function(a,b,c){this.x=a;this.y=b;this.z=c;return this},setX:function(a){this.x=a;return this},setY:function(a){this.y=a;return this},setZ:function(a){this.z=a;return this},setComponent:function(a,b){switch(a){case 0:this.x=b;break;case 1:this.y=b;break;case 2:this.z=b;break;default:throw Error("index is out of range: "+a);}},getComponent:function(a){switch(a){case 0:return this.x;case 1:return this.y;case 2:return this.z;default:throw Error("index is out of range: "+ +a);}},copy:function(a){this.x=a.x;this.y=a.y;this.z=a.z;return this},add:function(a,b){if(void 0!==b)return console.warn("DEPRECATED: Vector3's .add() now only accepts one argument. Use .addVectors( a, b ) instead."),this.addVectors(a,b);this.x+=a.x;this.y+=a.y;this.z+=a.z;return this},addScalar:function(a){this.x+=a;this.y+=a;this.z+=a;return this},addVectors:function(a,b){this.x=a.x+b.x;this.y=a.y+b.y;this.z=a.z+b.z;return this},sub:function(a,b){if(void 0!==b)return console.warn("DEPRECATED: Vector3's .sub() now only accepts one argument. Use .subVectors( a, b ) instead."), +this.subVectors(a,b);this.x-=a.x;this.y-=a.y;this.z-=a.z;return this},subVectors:function(a,b){this.x=a.x-b.x;this.y=a.y-b.y;this.z=a.z-b.z;return this},multiply:function(a,b){if(void 0!==b)return console.warn("DEPRECATED: Vector3's .multiply() now only accepts one argument. Use .multiplyVectors( a, b ) instead."),this.multiplyVectors(a,b);this.x*=a.x;this.y*=a.y;this.z*=a.z;return this},multiplyScalar:function(a){this.x*=a;this.y*=a;this.z*=a;return this},multiplyVectors:function(a,b){this.x=a.x* +b.x;this.y=a.y*b.y;this.z=a.z*b.z;return this},applyMatrix3:function(a){var b=this.x,c=this.y,d=this.z,a=a.elements;this.x=a[0]*b+a[3]*c+a[6]*d;this.y=a[1]*b+a[4]*c+a[7]*d;this.z=a[2]*b+a[5]*c+a[8]*d;return this},applyMatrix4:function(a){var b=this.x,c=this.y,d=this.z,a=a.elements;this.x=a[0]*b+a[4]*c+a[8]*d+a[12];this.y=a[1]*b+a[5]*c+a[9]*d+a[13];this.z=a[2]*b+a[6]*c+a[10]*d+a[14];return this},applyProjection:function(a){var b=this.x,c=this.y,d=this.z,a=a.elements,e=1/(a[3]*b+a[7]*c+a[11]*d+a[15]); +this.x=(a[0]*b+a[4]*c+a[8]*d+a[12])*e;this.y=(a[1]*b+a[5]*c+a[9]*d+a[13])*e;this.z=(a[2]*b+a[6]*c+a[10]*d+a[14])*e;return this},applyQuaternion:function(a){var b=this.x,c=this.y,d=this.z,e=a.x,f=a.y,g=a.z,a=a.w,h=a*b+f*d-g*c,i=a*c+g*b-e*d,k=a*d+e*c-f*b,b=-e*b-f*c-g*d;this.x=h*a+b*-e+i*-g-k*-f;this.y=i*a+b*-f+k*-e-h*-g;this.z=k*a+b*-g+h*-f-i*-e;return this},applyEuler:function(){var a=new THREE.Quaternion;return function(b,c){var d=a.setFromEuler(b,c);this.applyQuaternion(d);return this}}(),applyAxisAngle:function(){var a= +new THREE.Quaternion;return function(b,c){var d=a.setFromAxisAngle(b,c);this.applyQuaternion(d);return this}}(),transformDirection:function(a){var b=this.x,c=this.y,d=this.z,a=a.elements;this.x=a[0]*b+a[4]*c+a[8]*d;this.y=a[1]*b+a[5]*c+a[9]*d;this.z=a[2]*b+a[6]*c+a[10]*d;this.normalize();return this},divide:function(a){this.x/=a.x;this.y/=a.y;this.z/=a.z;return this},divideScalar:function(a){0!==a?(this.x/=a,this.y/=a,this.z/=a):this.z=this.y=this.x=0;return this},min:function(a){this.x>a.x&&(this.x= +a.x);this.y>a.y&&(this.y=a.y);this.z>a.z&&(this.z=a.z);return this},max:function(a){this.xb.x&&(this.x=b.x);this.yb.y&&(this.y=b.y);this.zb.z&&(this.z=b.z);return this},negate:function(){return this.multiplyScalar(-1)},dot:function(a){return this.x*a.x+this.y*a.y+this.z*a.z},lengthSq:function(){return this.x*this.x+this.y* +this.y+this.z*this.z},length:function(){return Math.sqrt(this.x*this.x+this.y*this.y+this.z*this.z)},lengthManhattan:function(){return Math.abs(this.x)+Math.abs(this.y)+Math.abs(this.z)},normalize:function(){return this.divideScalar(this.length())},setLength:function(a){var b=this.length();0!==b&&a!==b&&this.multiplyScalar(a/b);return this},lerp:function(a,b){this.x+=(a.x-this.x)*b;this.y+=(a.y-this.y)*b;this.z+=(a.z-this.z)*b;return this},cross:function(a,b){if(void 0!==b)return console.warn("DEPRECATED: Vector3's .cross() now only accepts one argument. Use .crossVectors( a, b ) instead."), +this.crossVectors(a,b);var c=this.x,d=this.y,e=this.z;this.x=d*a.z-e*a.y;this.y=e*a.x-c*a.z;this.z=c*a.y-d*a.x;return this},crossVectors:function(a,b){this.x=a.y*b.z-a.z*b.y;this.y=a.z*b.x-a.x*b.z;this.z=a.x*b.y-a.y*b.x;return this},projectOnVector:function(){var a=new THREE.Vector3;return function(b){a.copy(b).normalize();b=this.dot(a);return this.copy(a).multiplyScalar(b)}}(),projectOnPlane:function(){var a=new THREE.Vector3;return function(b){a.copy(this).projectOnVector(b);return this.sub(a)}}(), +reflect:function(){var a=new THREE.Vector3;return function(b){a.copy(this).projectOnVector(b).multiplyScalar(2);return this.subVectors(a,this)}}(),angleTo:function(a){a=this.dot(a)/(this.length()*a.length());return Math.acos(THREE.Math.clamp(a,-1,1))},distanceTo:function(a){return Math.sqrt(this.distanceToSquared(a))},distanceToSquared:function(a){var b=this.x-a.x,c=this.y-a.y,a=this.z-a.z;return b*b+c*c+a*a},getPositionFromMatrix:function(a){this.x=a.elements[12];this.y=a.elements[13];this.z=a.elements[14]; +return this},setEulerFromRotationMatrix:function(a,b){function c(a){return Math.min(Math.max(a,-1),1)}var d=a.elements,e=d[0],f=d[4],g=d[8],h=d[1],i=d[5],k=d[9],l=d[2],m=d[6],d=d[10];void 0===b||"XYZ"===b?(this.y=Math.asin(c(g)),0.99999>Math.abs(g)?(this.x=Math.atan2(-k,d),this.z=Math.atan2(-f,e)):(this.x=Math.atan2(m,i),this.z=0)):"YXZ"===b?(this.x=Math.asin(-c(k)),0.99999>Math.abs(k)?(this.y=Math.atan2(g,d),this.z=Math.atan2(h,i)):(this.y=Math.atan2(-l,e),this.z=0)):"ZXY"===b?(this.x=Math.asin(c(m)), +0.99999>Math.abs(m)?(this.y=Math.atan2(-l,d),this.z=Math.atan2(-f,i)):(this.y=0,this.z=Math.atan2(h,e))):"ZYX"===b?(this.y=Math.asin(-c(l)),0.99999>Math.abs(l)?(this.x=Math.atan2(m,d),this.z=Math.atan2(h,e)):(this.x=0,this.z=Math.atan2(-f,i))):"YZX"===b?(this.z=Math.asin(c(h)),0.99999>Math.abs(h)?(this.x=Math.atan2(-k,i),this.y=Math.atan2(-l,e)):(this.x=0,this.y=Math.atan2(g,d))):"XZY"===b&&(this.z=Math.asin(-c(f)),0.99999>Math.abs(f)?(this.x=Math.atan2(m,i),this.y=Math.atan2(g,e)):(this.x=Math.atan2(-k, +d),this.y=0));return this},setEulerFromQuaternion:function(a,b){function c(a){return Math.min(Math.max(a,-1),1)}var d=a.x*a.x,e=a.y*a.y,f=a.z*a.z,g=a.w*a.w;void 0===b||"XYZ"===b?(this.x=Math.atan2(2*(a.x*a.w-a.y*a.z),g-d-e+f),this.y=Math.asin(c(2*(a.x*a.z+a.y*a.w))),this.z=Math.atan2(2*(a.z*a.w-a.x*a.y),g+d-e-f)):"YXZ"===b?(this.x=Math.asin(c(2*(a.x*a.w-a.y*a.z))),this.y=Math.atan2(2*(a.x*a.z+a.y*a.w),g-d-e+f),this.z=Math.atan2(2*(a.x*a.y+a.z*a.w),g-d+e-f)):"ZXY"===b?(this.x=Math.asin(c(2*(a.x*a.w+ +a.y*a.z))),this.y=Math.atan2(2*(a.y*a.w-a.z*a.x),g-d-e+f),this.z=Math.atan2(2*(a.z*a.w-a.x*a.y),g-d+e-f)):"ZYX"===b?(this.x=Math.atan2(2*(a.x*a.w+a.z*a.y),g-d-e+f),this.y=Math.asin(c(2*(a.y*a.w-a.x*a.z))),this.z=Math.atan2(2*(a.x*a.y+a.z*a.w),g+d-e-f)):"YZX"===b?(this.x=Math.atan2(2*(a.x*a.w-a.z*a.y),g-d+e-f),this.y=Math.atan2(2*(a.y*a.w-a.x*a.z),g+d-e-f),this.z=Math.asin(c(2*(a.x*a.y+a.z*a.w)))):"XZY"===b&&(this.x=Math.atan2(2*(a.x*a.w+a.y*a.z),g-d+e-f),this.y=Math.atan2(2*(a.x*a.z+a.y*a.w),g+d- +e-f),this.z=Math.asin(c(2*(a.z*a.w-a.x*a.y))));return this},getScaleFromMatrix:function(a){var b=this.set(a.elements[0],a.elements[1],a.elements[2]).length(),c=this.set(a.elements[4],a.elements[5],a.elements[6]).length(),a=this.set(a.elements[8],a.elements[9],a.elements[10]).length();this.x=b;this.y=c;this.z=a;return this},equals:function(a){return a.x===this.x&&a.y===this.y&&a.z===this.z},toArray:function(){return[this.x,this.y,this.z]},clone:function(){return new THREE.Vector3(this.x,this.y,this.z)}});THREE.Vector4=function(a,b,c,d){this.x=a||0;this.y=b||0;this.z=c||0;this.w=void 0!==d?d:1}; +THREE.extend(THREE.Vector4.prototype,{set:function(a,b,c,d){this.x=a;this.y=b;this.z=c;this.w=d;return this},setX:function(a){this.x=a;return this},setY:function(a){this.y=a;return this},setZ:function(a){this.z=a;return this},setW:function(a){this.w=a;return this},setComponent:function(a,b){switch(a){case 0:this.x=b;break;case 1:this.y=b;break;case 2:this.z=b;break;case 3:this.w=b;break;default:throw Error("index is out of range: "+a);}},getComponent:function(a){switch(a){case 0:return this.x;case 1:return this.y; +case 2:return this.z;case 3:return this.w;default:throw Error("index is out of range: "+a);}},copy:function(a){this.x=a.x;this.y=a.y;this.z=a.z;this.w=void 0!==a.w?a.w:1;return this},add:function(a,b){if(void 0!==b)return console.warn("DEPRECATED: Vector4's .add() now only accepts one argument. Use .addVectors( a, b ) instead."),this.addVectors(a,b);this.x+=a.x;this.y+=a.y;this.z+=a.z;this.w+=a.w;return this},addScalar:function(a){this.x+=a;this.y+=a;this.z+=a;this.w+=a;return this},addVectors:function(a, +b){this.x=a.x+b.x;this.y=a.y+b.y;this.z=a.z+b.z;this.w=a.w+b.w;return this},sub:function(a,b){if(void 0!==b)return console.warn("DEPRECATED: Vector4's .sub() now only accepts one argument. Use .subVectors( a, b ) instead."),this.subVectors(a,b);this.x-=a.x;this.y-=a.y;this.z-=a.z;this.w-=a.w;return this},subVectors:function(a,b){this.x=a.x-b.x;this.y=a.y-b.y;this.z=a.z-b.z;this.w=a.w-b.w;return this},multiplyScalar:function(a){this.x*=a;this.y*=a;this.z*=a;this.w*=a;return this},applyMatrix4:function(a){var b= +this.x,c=this.y,d=this.z,e=this.w,a=a.elements;this.x=a[0]*b+a[4]*c+a[8]*d+a[12]*e;this.y=a[1]*b+a[5]*c+a[9]*d+a[13]*e;this.z=a[2]*b+a[6]*c+a[10]*d+a[14]*e;this.w=a[3]*b+a[7]*c+a[11]*d+a[15]*e;return this},divideScalar:function(a){0!==a?(this.x/=a,this.y/=a,this.z/=a,this.w/=a):(this.z=this.y=this.x=0,this.w=1);return this},setAxisAngleFromQuaternion:function(a){this.w=2*Math.acos(a.w);var b=Math.sqrt(1-a.w*a.w);1E-4>b?(this.x=1,this.z=this.y=0):(this.x=a.x/b,this.y=a.y/b,this.z=a.z/b);return this}, +setAxisAngleFromRotationMatrix:function(a){var b,c,d,a=a.elements,e=a[0];d=a[4];var f=a[8],g=a[1],h=a[5],i=a[9];c=a[2];b=a[6];var k=a[10];if(0.01>Math.abs(d-g)&&0.01>Math.abs(f-c)&&0.01>Math.abs(i-b)){if(0.1>Math.abs(d+g)&&0.1>Math.abs(f+c)&&0.1>Math.abs(i+b)&&0.1>Math.abs(e+h+k-3))return this.set(1,0,0,0),this;a=Math.PI;e=(e+1)/2;h=(h+1)/2;k=(k+1)/2;d=(d+g)/4;f=(f+c)/4;i=(i+b)/4;e>h&&e>k?0.01>e?(b=0,d=c=0.707106781):(b=Math.sqrt(e),c=d/b,d=f/b):h>k?0.01>h?(b=0.707106781,c=0,d=0.707106781):(c=Math.sqrt(h), +b=d/c,d=i/c):0.01>k?(c=b=0.707106781,d=0):(d=Math.sqrt(k),b=f/d,c=i/d);this.set(b,c,d,a);return this}a=Math.sqrt((b-i)*(b-i)+(f-c)*(f-c)+(g-d)*(g-d));0.001>Math.abs(a)&&(a=1);this.x=(b-i)/a;this.y=(f-c)/a;this.z=(g-d)/a;this.w=Math.acos((e+h+k-1)/2);return this},min:function(a){this.x>a.x&&(this.x=a.x);this.y>a.y&&(this.y=a.y);this.z>a.z&&(this.z=a.z);this.w>a.w&&(this.w=a.w);return this},max:function(a){this.xb.x&&(this.x=b.x);this.yb.y&&(this.y=b.y);this.zb.z&&(this.z=b.z);this.wb.w&&(this.w=b.w);return this},negate:function(){return this.multiplyScalar(-1)},dot:function(a){return this.x*a.x+this.y*a.y+this.z*a.z+this.w*a.w},lengthSq:function(){return this.x*this.x+this.y*this.y+this.z*this.z+this.w*this.w},length:function(){return Math.sqrt(this.x*this.x+this.y*this.y+ +this.z*this.z+this.w*this.w)},lengthManhattan:function(){return Math.abs(this.x)+Math.abs(this.y)+Math.abs(this.z)+Math.abs(this.w)},normalize:function(){return this.divideScalar(this.length())},setLength:function(a){var b=this.length();0!==b&&a!==b&&this.multiplyScalar(a/b);return this},lerp:function(a,b){this.x+=(a.x-this.x)*b;this.y+=(a.y-this.y)*b;this.z+=(a.z-this.z)*b;this.w+=(a.w-this.w)*b;return this},equals:function(a){return a.x===this.x&&a.y===this.y&&a.z===this.z&&a.w===this.w},toArray:function(){return[this.x, +this.y,this.z,this.w]},clone:function(){return new THREE.Vector4(this.x,this.y,this.z,this.w)}});THREE.Line3=function(a,b){this.start=void 0!==a?a:new THREE.Vector3;this.end=void 0!==b?b:new THREE.Vector3}; +THREE.extend(THREE.Line3.prototype,{set:function(a,b){this.start.copy(a);this.end.copy(b);return this},copy:function(a){this.start.copy(a.start);this.end.copy(a.end);return this},center:function(a){return(a||new THREE.Vector3).addVectors(this.start,this.end).multiplyScalar(0.5)},delta:function(a){return(a||new THREE.Vector3).subVectors(this.end,this.start)},distanceSq:function(){return this.start.distanceToSquared(this.end)},distance:function(){return this.start.distanceTo(this.end)},at:function(a, +b){var c=b||new THREE.Vector3;return this.delta(c).multiplyScalar(a).add(this.start)},closestPointToPointParameter:function(){var a=new THREE.Vector3,b=new THREE.Vector3;return function(c,d){a.subVectors(c,this.start);b.subVectors(this.end,this.start);var e=b.dot(b),e=b.dot(a)/e;d&&(e=THREE.Math.clamp(e,0,1));return e}}(),closestPointToPoint:function(a,b,c){a=this.closestPointToPointParameter(a,b);c=c||new THREE.Vector3;return this.delta(c).multiplyScalar(a).add(this.start)},applyMatrix4:function(a){this.start.applyMatrix4(a); +this.end.applyMatrix4(a);return this},equals:function(a){return a.start.equals(this.start)&&a.end.equals(this.end)},clone:function(){return(new THREE.Line3).copy(this)}});THREE.Box2=function(a,b){this.min=void 0!==a?a:new THREE.Vector2(Infinity,Infinity);this.max=void 0!==b?b:new THREE.Vector2(-Infinity,-Infinity)}; +THREE.extend(THREE.Box2.prototype,{set:function(a,b){this.min.copy(a);this.max.copy(b);return this},setFromPoints:function(a){if(0this.max.x&&(this.max.x=b.x),b.ythis.max.y&&(this.max.y=b.y)}else this.makeEmpty();return this},setFromCenterAndSize:function(){var a=new THREE.Vector2;return function(b,c){var d=a.copy(c).multiplyScalar(0.5);this.min.copy(b).sub(d); +this.max.copy(b).add(d);return this}}(),copy:function(a){this.min.copy(a.min);this.max.copy(a.max);return this},makeEmpty:function(){this.min.x=this.min.y=Infinity;this.max.x=this.max.y=-Infinity;return this},empty:function(){return this.max.xthis.max.x||a.ythis.max.y?!1:!0},containsBox:function(a){return this.min.x<=a.min.x&&a.max.x<=this.max.x&&this.min.y<=a.min.y&&a.max.y<=this.max.y?!0:!1},getParameter:function(a){return new THREE.Vector2((a.x-this.min.x)/(this.max.x-this.min.x),(a.y-this.min.y)/ +(this.max.y-this.min.y))},isIntersectionBox:function(a){return a.max.xthis.max.x||a.max.ythis.max.y?!1:!0},clampPoint:function(a,b){return(b||new THREE.Vector2).copy(a).clamp(this.min,this.max)},distanceToPoint:function(){var a=new THREE.Vector2;return function(b){return a.copy(b).clamp(this.min,this.max).sub(b).length()}}(),intersect:function(a){this.min.max(a.min);this.max.min(a.max);return this},union:function(a){this.min.min(a.min);this.max.max(a.max); +return this},translate:function(a){this.min.add(a);this.max.add(a);return this},equals:function(a){return a.min.equals(this.min)&&a.max.equals(this.max)},clone:function(){return(new THREE.Box2).copy(this)}});THREE.Box3=function(a,b){this.min=void 0!==a?a:new THREE.Vector3(Infinity,Infinity,Infinity);this.max=void 0!==b?b:new THREE.Vector3(-Infinity,-Infinity,-Infinity)}; +THREE.extend(THREE.Box3.prototype,{set:function(a,b){this.min.copy(a);this.max.copy(b);return this},setFromPoints:function(a){if(0this.max.x&&(this.max.x=b.x),b.ythis.max.y&&(this.max.y=b.y),b.zthis.max.z&&(this.max.z=b.z)}else this.makeEmpty();return this},setFromCenterAndSize:function(){var a=new THREE.Vector3; +return function(b,c){var d=a.copy(c).multiplyScalar(0.5);this.min.copy(b).sub(d);this.max.copy(b).add(d);return this}}(),copy:function(a){this.min.copy(a.min);this.max.copy(a.max);return this},makeEmpty:function(){this.min.x=this.min.y=this.min.z=Infinity;this.max.x=this.max.y=this.max.z=-Infinity;return this},empty:function(){return this.max.xthis.max.x||a.ythis.max.y||a.zthis.max.z?!1:!0},containsBox:function(a){return this.min.x<=a.min.x&&a.max.x<= +this.max.x&&this.min.y<=a.min.y&&a.max.y<=this.max.y&&this.min.z<=a.min.z&&a.max.z<=this.max.z?!0:!1},getParameter:function(a){return new THREE.Vector3((a.x-this.min.x)/(this.max.x-this.min.x),(a.y-this.min.y)/(this.max.y-this.min.y),(a.z-this.min.z)/(this.max.z-this.min.z))},isIntersectionBox:function(a){return a.max.xthis.max.x||a.max.ythis.max.y||a.max.zthis.max.z?!1:!0},clampPoint:function(a,b){return(b||new THREE.Vector3).copy(a).clamp(this.min, +this.max)},distanceToPoint:function(){var a=new THREE.Vector3;return function(b){return a.copy(b).clamp(this.min,this.max).sub(b).length()}}(),getBoundingSphere:function(){var a=new THREE.Vector3;return function(b){b=b||new THREE.Sphere;b.center=this.center();b.radius=0.5*this.size(a).length();return b}}(),intersect:function(a){this.min.max(a.min);this.max.min(a.max);return this},union:function(a){this.min.min(a.min);this.max.max(a.max);return this},applyMatrix4:function(){var a=[new THREE.Vector3, +new THREE.Vector3,new THREE.Vector3,new THREE.Vector3,new THREE.Vector3,new THREE.Vector3,new THREE.Vector3,new THREE.Vector3];return function(b){a[0].set(this.min.x,this.min.y,this.min.z).applyMatrix4(b);a[1].set(this.min.x,this.min.y,this.max.z).applyMatrix4(b);a[2].set(this.min.x,this.max.y,this.min.z).applyMatrix4(b);a[3].set(this.min.x,this.max.y,this.max.z).applyMatrix4(b);a[4].set(this.max.x,this.min.y,this.min.z).applyMatrix4(b);a[5].set(this.max.x,this.min.y,this.max.z).applyMatrix4(b);a[6].set(this.max.x, +this.max.y,this.min.z).applyMatrix4(b);a[7].set(this.max.x,this.max.y,this.max.z).applyMatrix4(b);this.makeEmpty();this.setFromPoints(a);return this}}(),translate:function(a){this.min.add(a);this.max.add(a);return this},equals:function(a){return a.min.equals(this.min)&&a.max.equals(this.max)},clone:function(){return(new THREE.Box3).copy(this)}});THREE.Matrix3=function(a,b,c,d,e,f,g,h,i){this.elements=new Float32Array(9);this.set(void 0!==a?a:1,b||0,c||0,d||0,void 0!==e?e:1,f||0,g||0,h||0,void 0!==i?i:1)}; +THREE.extend(THREE.Matrix3.prototype,{set:function(a,b,c,d,e,f,g,h,i){var k=this.elements;k[0]=a;k[3]=b;k[6]=c;k[1]=d;k[4]=e;k[7]=f;k[2]=g;k[5]=h;k[8]=i;return this},identity:function(){this.set(1,0,0,0,1,0,0,0,1);return this},copy:function(a){a=a.elements;this.set(a[0],a[3],a[6],a[1],a[4],a[7],a[2],a[5],a[8]);return this},multiplyVector3:function(a){console.warn("DEPRECATED: Matrix3's .multiplyVector3() has been removed. Use vector.applyMatrix3( matrix ) instead.");return a.applyMatrix3(this)},multiplyVector3Array:function(){var a= +new THREE.Vector3;return function(b){for(var c=0,d=b.length;c=this.radius},containsPoint:function(a){return a.distanceToSquared(this.center)<=this.radius*this.radius},distanceToPoint:function(a){return a.distanceTo(this.center)- +this.radius},intersectsSphere:function(a){var b=this.radius+a.radius;return a.center.distanceToSquared(this.center)<=b*b},clampPoint:function(a,b){var c=this.center.distanceToSquared(a),d=b||new THREE.Vector3;d.copy(a);c>this.radius*this.radius&&(d.sub(this.center).normalize(),d.multiplyScalar(this.radius).add(this.center));return d},getBoundingBox:function(a){a=a||new THREE.Box3;a.set(this.center,this.center);a.expandByScalar(this.radius);return a},applyMatrix4:function(a){this.center.applyMatrix4(a); +this.radius*=a.getMaxScaleOnAxis();return this},translate:function(a){this.center.add(a);return this},equals:function(a){return a.center.equals(this.center)&&a.radius===this.radius},clone:function(){return(new THREE.Sphere).copy(this)}});THREE.Frustum=function(a,b,c,d,e,f){this.planes=[void 0!==a?a:new THREE.Plane,void 0!==b?b:new THREE.Plane,void 0!==c?c:new THREE.Plane,void 0!==d?d:new THREE.Plane,void 0!==e?e:new THREE.Plane,void 0!==f?f:new THREE.Plane]}; +THREE.extend(THREE.Frustum.prototype,{set:function(a,b,c,d,e,f){var g=this.planes;g[0].copy(a);g[1].copy(b);g[2].copy(c);g[3].copy(d);g[4].copy(e);g[5].copy(f);return this},copy:function(a){for(var b=this.planes,c=0;6>c;c++)b[c].copy(a.planes[c]);return this},setFromMatrix:function(a){var b=this.planes,c=a.elements,a=c[0],d=c[1],e=c[2],f=c[3],g=c[4],h=c[5],i=c[6],k=c[7],l=c[8],m=c[9],n=c[10],s=c[11],r=c[12],p=c[13],q=c[14],c=c[15];b[0].setComponents(f-a,k-g,s-l,c-r).normalize();b[1].setComponents(f+ +a,k+g,s+l,c+r).normalize();b[2].setComponents(f+d,k+h,s+m,c+p).normalize();b[3].setComponents(f-d,k-h,s-m,c-p).normalize();b[4].setComponents(f-e,k-i,s-n,c-q).normalize();b[5].setComponents(f+e,k+i,s+n,c+q).normalize();return this},intersectsObject:function(){var a=new THREE.Vector3;return function(b){var c=b.matrixWorld,d=this.planes,b=-b.geometry.boundingSphere.radius*c.getMaxScaleOnAxis();a.getPositionFromMatrix(c);for(c=0;6>c;c++)if(d[c].distanceToPoint(a)d;d++)if(b[d].distanceToPoint(c)c;c++)if(0>b[c].distanceToPoint(a))return!1;return!0},clone:function(){return(new THREE.Frustum).copy(this)}});THREE.Plane=function(a,b){this.normal=void 0!==a?a:new THREE.Vector3(1,0,0);this.constant=void 0!==b?b:0}; +THREE.extend(THREE.Plane.prototype,{set:function(a,b){this.normal.copy(a);this.constant=b;return this},setComponents:function(a,b,c,d){this.normal.set(a,b,c);this.constant=d;return this},setFromNormalAndCoplanarPoint:function(a,b){this.normal.copy(a);this.constant=-b.dot(this.normal);return this},setFromCoplanarPoints:function(){var a=new THREE.Vector3,b=new THREE.Vector3;return function(c,d,e){d=a.subVectors(e,d).cross(b.subVectors(c,d)).normalize();this.setFromNormalAndCoplanarPoint(d,c);return this}}(), +copy:function(a){this.normal.copy(a.normal);this.constant=a.constant;return this},normalize:function(){var a=1/this.normal.length();this.normal.multiplyScalar(a);this.constant*=a;return this},negate:function(){this.constant*=-1;this.normal.negate();return this},distanceToPoint:function(a){return this.normal.dot(a)+this.constant},distanceToSphere:function(a){return this.distanceToPoint(a.center)-a.radius},projectPoint:function(a,b){return this.orthoPoint(a,b).sub(a).negate()},orthoPoint:function(a, +b){var c=this.distanceToPoint(a);return(b||new THREE.Vector3).copy(this.normal).multiplyScalar(c)},isIntersectionLine:function(a){var b=this.distanceToPoint(a.start),a=this.distanceToPoint(a.end);return 0>b&&0a&&0f||1c?c:a},clampBottom:function(a,b){return a=c)return 1;a=(a-b)/(c-b);return a*a*(3-2*a)},smootherstep:function(a,b,c){if(a<=b)return 0;if(a>=c)return 1;a=(a-b)/(c-b);return a*a*a*(a*(6*a-15)+10)},random16:function(){return(65280*Math.random()+255*Math.random())/65535},randInt:function(a,b){return a+Math.floor(Math.random()*(b-a+1))},randFloat:function(a, +b){return a+Math.random()*(b-a)},randFloatSpread:function(a){return a*(0.5-Math.random())},sign:function(a){return 0>a?-1:0this.points.length-2?this.points.length-1:f+1;c[3]=f>this.points.length-3?this.points.length-1: +f+2;k=this.points[c[0]];l=this.points[c[1]];m=this.points[c[2]];n=this.points[c[3]];h=g*g;i=g*h;d.x=b(k.x,l.x,m.x,n.x,g,h,i);d.y=b(k.y,l.y,m.y,n.y,g,h,i);d.z=b(k.z,l.z,m.z,n.z,g,h,i);return d};this.getControlPointsArray=function(){var a,b,c=this.points.length,d=[];for(a=0;a=b.x+b.y}}(); +THREE.extend(THREE.Triangle.prototype,{constructor:THREE.Triangle,set:function(a,b,c){this.a.copy(a);this.b.copy(b);this.c.copy(c);return this},setFromPointsAndIndices:function(a,b,c,d){this.a.copy(a[b]);this.b.copy(a[c]);this.c.copy(a[d]);return this},copy:function(a){this.a.copy(a.a);this.b.copy(a.b);this.c.copy(a.c);return this},area:function(){var a=new THREE.Vector3,b=new THREE.Vector3;return function(){a.subVectors(this.c,this.b);b.subVectors(this.a,this.b);return 0.5*a.cross(b).length()}}(), +midpoint:function(a){return(a||new THREE.Vector3).addVectors(this.a,this.b).add(this.c).multiplyScalar(1/3)},normal:function(a){return THREE.Triangle.normal(this.a,this.b,this.c,a)},plane:function(a){return(a||new THREE.Plane).setFromCoplanarPoints(this.a,this.b,this.c)},barycoordFromPoint:function(a,b){return THREE.Triangle.barycoordFromPoint(a,this.a,this.b,this.c,b)},containsPoint:function(a){return THREE.Triangle.containsPoint(a,this.a,this.b,this.c)},equals:function(a){return a.a.equals(this.a)&& +a.b.equals(this.b)&&a.c.equals(this.c)},clone:function(){return(new THREE.Triangle).copy(this)}});THREE.Vertex=function(a){console.warn("THREE.Vertex has been DEPRECATED. Use THREE.Vector3 instead.");return a};THREE.UV=function(a,b){console.warn("THREE.UV has been DEPRECATED. Use THREE.Vector2 instead.");return new THREE.Vector2(a,b)};THREE.Clock=function(a){this.autoStart=void 0!==a?a:!0;this.elapsedTime=this.oldTime=this.startTime=0;this.running=!1}; +THREE.extend(THREE.Clock.prototype,{start:function(){this.oldTime=this.startTime=void 0!==window.performance&&void 0!==window.performance.now?window.performance.now():Date.now();this.running=!0},stop:function(){this.getElapsedTime();this.running=!1},getElapsedTime:function(){this.getDelta();return this.elapsedTime},getDelta:function(){var a=0;this.autoStart&&!this.running&&this.start();if(this.running){var b=void 0!==window.performance&&void 0!==window.performance.now?window.performance.now():Date.now(), +a=0.001*(b-this.oldTime);this.oldTime=b;this.elapsedTime+=a}return a}});THREE.EventDispatcher=function(){var a={};this.addEventListener=function(b,c){void 0===a[b]&&(a[b]=[]);-1===a[b].indexOf(c)&&a[b].push(c)};this.removeEventListener=function(b,c){var d=a[b].indexOf(c);-1!==d&&a[b].splice(d,1)};this.dispatchEvent=function(b){var c=a[b.type];if(void 0!==c){b.target=this;for(var d=0,e=c.length;dh.scale.x)return k;k.push({distance:i,point:h.position,face:null,object:h})}else if(h instanceof +a.Mesh){f.getPositionFromMatrix(h.matrixWorld);b.set(f,h.geometry.boundingSphere.radius*h.matrixWorld.getMaxScaleOnAxis());if(!i.ray.isIntersectionSphere(b))return k;var s=h.geometry,r=s.vertices,p=h.material instanceof a.MeshFaceMaterial,q=!0===p?h.material.materials:null,y=h.material.side,v,z,t,A=i.precision;h.matrixRotationWorld.extractRotation(h.matrixWorld);g.getInverse(h.matrixWorld);c.copy(i.ray).applyMatrix4(g);for(var I=0,C=s.faces.length;IG)){y=y.side;if(y!==a.DoubleSide&&(v=c.direction.dot(d.normal),!(y===a.FrontSide?0>v:0i.far)){e=c.at(G,e);if(x instanceof a.Face3){if(y=r[x.a],v=r[x.b],z=r[x.c],!a.Triangle.containsPoint(e,y,v,z))continue}else if(x instanceof a.Face4){if(y=r[x.a],v=r[x.b],z=r[x.c],t=r[x.d],!a.Triangle.containsPoint(e,y,v,t)&&!a.Triangle.containsPoint(e,v,z,t))continue}else throw Error("face type not supported"); +k.push({distance:G,point:i.ray.at(G),face:x,faceIndex:I,object:h})}}}}}},k=function(a,b,c){for(var a=a.getDescendants(),d=0,e=a.length;de&&0>f||0>g&&0>h)return!1;0>e?c=Math.max(c,e/(e-f)):0>f&&(d=Math.min(d,e/(e-f)));0>g?c=Math.max(c,g/(g-h)):0>h&&(d=Math.min(d,g/(g-h)));if(d< +c)return!1;a.lerp(b,c);b.lerp(a,1-d);return!0}var e,f,g=[],h=0,i,k,l=[],m=0,n,s,r=[],p=0,q,y=[],v=0,z,t,A=[],I=0,C,x,G=[],J=0,E={objects:[],sprites:[],lights:[],elements:[]},H=new THREE.Vector3,B=new THREE.Vector4,W=new THREE.Box3(new THREE.Vector3(-1,-1,-1),new THREE.Vector3(1,1,1)),F=new THREE.Box3,K=Array(3),L=Array(4),U=new THREE.Matrix4,fa=new THREE.Matrix4,Ca,$a=new THREE.Matrix4,M=new THREE.Matrix3,ca=new THREE.Matrix3,qa=new THREE.Vector3,ha=new THREE.Frustum,ra=new THREE.Vector4,N=new THREE.Vector4; +this.projectVector=function(a,b){b.matrixWorldInverse.getInverse(b.matrixWorld);fa.multiplyMatrices(b.projectionMatrix,b.matrixWorldInverse);return a.applyProjection(fa)};this.unprojectVector=function(a,b){b.projectionMatrixInverse.getInverse(b.projectionMatrix);fa.multiplyMatrices(b.matrixWorld,b.projectionMatrixInverse);return a.applyProjection(fa)};this.pickingRay=function(a,b){a.z=-1;var c=new THREE.Vector3(a.x,a.y,1);this.unprojectVector(a,b);this.unprojectVector(c,b);c.sub(a).normalize();return new THREE.Raycaster(a, +c)};this.projectScene=function(g,h,m,Pa){var ta=!1,ka,aa,pa,Y,da,la,Z,oa,gb,nb,ia,Wa,ab;x=t=q=s=0;E.elements.length=0;g.updateMatrixWorld();void 0===h.parent&&h.updateMatrixWorld();U.copy(h.matrixWorldInverse.getInverse(h.matrixWorld));fa.multiplyMatrices(h.projectionMatrix,U);ca.getInverse(U);ca.transpose();ha.setFromMatrix(fa);f=0;E.objects.length=0;E.sprites.length=0;E.lights.length=0;var Fa=function(b){for(var c=0,d=b.children.length;ci.positionScreen.x||1i.positionScreen.y||1i.positionScreen.z||1(Z.positionScreen.x-Y.positionScreen.x)*(da.positionScreen.y-Y.positionScreen.y)-(Z.positionScreen.y-Y.positionScreen.y)*(da.positionScreen.x-Y.positionScreen.x),la===THREE.DoubleSide||ta===(la===THREE.FrontSide))s===p?(ia=new THREE.RenderableFace3,r.push(ia),p++,s++,n=ia):n=r[s++],n.v1.copy(Y),n.v2.copy(da),n.v3.copy(Z);else continue; +else continue;else if(aa instanceof THREE.Face4)if(Y=l[aa.a],da=l[aa.b],Z=l[aa.c],ia=l[aa.d],L[0]=Y.positionScreen,L[1]=da.positionScreen,L[2]=Z.positionScreen,L[3]=ia.positionScreen,!0===Y.visible||!0===da.visible||!0===Z.visible||!0===ia.visible||W.isIntersectionBox(F.setFromPoints(L)))if(ta=0>(ia.positionScreen.x-Y.positionScreen.x)*(da.positionScreen.y-Y.positionScreen.y)-(ia.positionScreen.y-Y.positionScreen.y)*(da.positionScreen.x-Y.positionScreen.x)||0>(da.positionScreen.x-Z.positionScreen.x)* +(ia.positionScreen.y-Z.positionScreen.y)-(da.positionScreen.y-Z.positionScreen.y)*(ia.positionScreen.x-Z.positionScreen.x),la===THREE.DoubleSide||ta===(la===THREE.FrontSide)){if(q===v){var ub=new THREE.RenderableFace4;y.push(ub);v++;q++;n=ub}else n=y[q++];n.v1.copy(Y);n.v2.copy(da);n.v3.copy(Z);n.v4.copy(ia)}else continue;else continue;n.normalModel.copy(aa.normal);!1===ta&&(la===THREE.BackSide||la===THREE.DoubleSide)&&n.normalModel.negate();n.normalModel.applyMatrix3(M).normalize();n.normalModelView.copy(n.normalModel).applyMatrix3(ca); +n.centroidModel.copy(aa.centroid).applyMatrix4(Ca);Z=aa.vertexNormals;Y=0;for(da=Z.length;YB.z&&(x===J?(ta=new THREE.RenderableParticle,G.push(ta),J++,x++,C=ta):C=G[x++],C.object=oa,C.x=B.x/B.w,C.y=B.y/B.w,C.z=B.z,C.rotation=oa.rotation.z,C.scale.x=oa.scale.x*Math.abs(C.x-(B.x+h.projectionMatrix.elements[0])/ +(B.w+h.projectionMatrix.elements[12])),C.scale.y=oa.scale.y*Math.abs(C.y-(B.y+h.projectionMatrix.elements[5])/(B.w+h.projectionMatrix.elements[13])),C.material=oa.material,E.elements.push(C)));!0===Pa&&E.elements.sort(c);return E}};THREE.Face3=function(a,b,c,d,e,f){this.a=a;this.b=b;this.c=c;this.normal=d instanceof THREE.Vector3?d:new THREE.Vector3;this.vertexNormals=d instanceof Array?d:[];this.color=e instanceof THREE.Color?e:new THREE.Color;this.vertexColors=e instanceof Array?e:[];this.vertexTangents=[];this.materialIndex=void 0!==f?f:0;this.centroid=new THREE.Vector3}; +THREE.Face3.prototype={constructor:THREE.Face3,clone:function(){var a=new THREE.Face3(this.a,this.b,this.c);a.normal.copy(this.normal);a.color.copy(this.color);a.centroid.copy(this.centroid);a.materialIndex=this.materialIndex;var b,c;b=0;for(c=this.vertexNormals.length;be?-1:1,f.vertexTangents[d]=new THREE.Vector4(B.x,B.y,B.z,e)}this.hasTangents=!0},computeLineDistances:function(){for(var a=0,b=this.vertices,c=0,d=b.length;ci;i++)if(h[i]==h[(i+1)%3]){e.push(f);break}}else if(a instanceof THREE.Face4){a.a=c[a.a];a.b=c[a.b];a.c=c[a.c];a.d=c[a.d];h=[a.a,a.b,a.c,a.d];d=-1;for(i=0;4>i;i++)h[i]==h[(i+1)%4]&&(0<=d&&e.push(f),d=i);if(0<=d){h.splice(d,1);var l=new THREE.Face3(h[0],h[1],h[2],a.normal,a.color,a.materialIndex);h=0;for(i=this.faceVertexUvs.length;hb.max.x&&(b.max.x=c),db.max.y&&(b.max.y=d),eb.max.z&&(b.max.z=e)}if(void 0===a||0===a.length)this.boundingBox.min.set(0,0,0),this.boundingBox.max.set(0,0,0)},computeBoundingSphere:function(){null===this.boundingSphere&&(this.boundingSphere= +new THREE.Sphere);var a=this.attributes.position.array;if(a){for(var b,c=0,d,e,f=0,g=a.length;fc&&(c=b);this.boundingSphere.radius=Math.sqrt(c)}},computeVertexNormals:function(){if(this.attributes.position){var a,b,c,d;a=this.attributes.position.array.length;if(void 0===this.attributes.normal)this.attributes.normal={itemSize:3,array:new Float32Array(a),numItems:a};else{a=0;for(b=this.attributes.normal.array.length;aqa?-1:1;h[4*a]=U.x;h[4*a+1]=U.y;h[4*a+2]=U.z;h[4*a+3]=M}if(void 0===this.attributes.index||void 0===this.attributes.position|| +void 0===this.attributes.normal||void 0===this.attributes.uv)console.warn("Missing required attributes (index, position, normal or uv) in BufferGeometry.computeTangents()");else{var b=this.attributes.index.array,c=this.attributes.position.array,d=this.attributes.normal.array,e=this.attributes.uv.array,f=c.length/3;if(void 0===this.attributes.tangent){var g=4*f;this.attributes.tangent={itemSize:4,array:new Float32Array(g),numItems:g}}for(var h=this.attributes.tangent.array,i=[],k=[],g=0;ga.length?".":a.join("/"))+"/"},initMaterials:function(a,b){for(var c=[],d=0;da.opacity)i.transparent=a.transparent;void 0!==a.depthTest&&(i.depthTest=a.depthTest);void 0!==a.depthWrite&&(i.depthWrite=a.depthWrite);void 0!==a.visible&&(i.visible=a.visible);void 0!==a.flipSided&&(i.side=THREE.BackSide); +void 0!==a.doubleSided&&(i.side=THREE.DoubleSide);void 0!==a.wireframe&&(i.wireframe=a.wireframe);void 0!==a.vertexColors&&("face"===a.vertexColors?i.vertexColors=THREE.FaceColors:a.vertexColors&&(i.vertexColors=THREE.VertexColors));a.colorDiffuse?i.color=f(a.colorDiffuse):a.DbgColor&&(i.color=a.DbgColor);a.colorSpecular&&(i.specular=f(a.colorSpecular));a.colorAmbient&&(i.ambient=f(a.colorAmbient));a.transparency&&(i.opacity=a.transparency);a.specularCoef&&(i.shininess=a.specularCoef);a.mapDiffuse&& +b&&e(i,"map",a.mapDiffuse,a.mapDiffuseRepeat,a.mapDiffuseOffset,a.mapDiffuseWrap,a.mapDiffuseAnisotropy);a.mapLight&&b&&e(i,"lightMap",a.mapLight,a.mapLightRepeat,a.mapLightOffset,a.mapLightWrap,a.mapLightAnisotropy);a.mapBump&&b&&e(i,"bumpMap",a.mapBump,a.mapBumpRepeat,a.mapBumpOffset,a.mapBumpWrap,a.mapBumpAnisotropy);a.mapNormal&&b&&e(i,"normalMap",a.mapNormal,a.mapNormalRepeat,a.mapNormalOffset,a.mapNormalWrap,a.mapNormalAnisotropy);a.mapSpecular&&b&&e(i,"specularMap",a.mapSpecular,a.mapSpecularRepeat, +a.mapSpecularOffset,a.mapSpecularWrap,a.mapSpecularAnisotropy);a.mapBumpScale&&(i.bumpScale=a.mapBumpScale);a.mapNormal?(h=THREE.ShaderLib.normalmap,k=THREE.UniformsUtils.clone(h.uniforms),k.tNormal.value=i.normalMap,a.mapNormalFactor&&k.uNormalScale.value.set(a.mapNormalFactor,a.mapNormalFactor),i.map&&(k.tDiffuse.value=i.map,k.enableDiffuse.value=!0),i.specularMap&&(k.tSpecular.value=i.specularMap,k.enableSpecular.value=!0),i.lightMap&&(k.tAO.value=i.lightMap,k.enableAO.value=!0),k.uDiffuseColor.value.setHex(i.color), +k.uSpecularColor.value.setHex(i.specular),k.uAmbientColor.value.setHex(i.ambient),k.uShininess.value=i.shininess,void 0!==i.opacity&&(k.uOpacity.value=i.opacity),h=new THREE.ShaderMaterial({fragmentShader:h.fragmentShader,vertexShader:h.vertexShader,uniforms:k,lights:!0,fog:!0}),i.transparent&&(h.transparent=!0)):h=new THREE[h](i);void 0!==a.DbgName&&(h.name=a.DbgName);return h}};THREE.ImageLoader=function(){THREE.EventDispatcher.call(this);this.crossOrigin=null};THREE.ImageLoader.prototype={constructor:THREE.ImageLoader,load:function(a,b){var c=this;void 0===b&&(b=new Image);b.addEventListener("load",function(){c.dispatchEvent({type:"load",content:b})},!1);b.addEventListener("error",function(){c.dispatchEvent({type:"error",message:"Couldn't load URL ["+a+"]"})},!1);c.crossOrigin&&(b.crossOrigin=c.crossOrigin);b.src=a}};THREE.JSONLoader=function(a){THREE.Loader.call(this,a);this.withCredentials=!1};THREE.JSONLoader.prototype=Object.create(THREE.Loader.prototype);THREE.JSONLoader.prototype.load=function(a,b,c){c=c&&"string"===typeof c?c:this.extractUrlBase(a);this.onLoadStart();this.loadAjaxJSON(this,a,b,c)}; +THREE.JSONLoader.prototype.loadAjaxJSON=function(a,b,c,d,e){var f=new XMLHttpRequest,g=0;f.onreadystatechange=function(){if(f.readyState===f.DONE)if(200===f.status||0===f.status){if(f.responseText){var h=JSON.parse(f.responseText);a.createModel(h,c,d)}else console.warn("THREE.JSONLoader: ["+b+"] seems to be unreachable or file there is empty");a.onLoadComplete()}else console.error("THREE.JSONLoader: Couldn't load ["+b+"] ["+f.status+"]");else f.readyState===f.LOADING?e&&(0===g&&(g=f.getResponseHeader("Content-Length")), +e({total:g,loaded:f.responseText.length})):f.readyState===f.HEADERS_RECEIVED&&(g=f.getResponseHeader("Content-Length"))};f.open("GET",b,!0);f.withCredentials=this.withCredentials;f.send(null)}; +THREE.JSONLoader.prototype.createModel=function(a,b,c){var d=new THREE.Geometry,e=void 0!==a.scale?1/a.scale:1,f,g,h,i,k,l,m,n,s,r,p,q,y,v,z,t=a.faces;r=a.vertices;var A=a.normals,I=a.colors,C=0;for(f=0;fF.parameters.opacity&& +(F.parameters.transparent=!0);F.parameters.normalMap?(E=THREE.ShaderLib.normalmap,B=THREE.UniformsUtils.clone(E.uniforms),q=F.parameters.color,W=F.parameters.specular,p=F.parameters.ambient,H=F.parameters.shininess,B.tNormal.value=x.textures[F.parameters.normalMap],F.parameters.normalScale&&B.uNormalScale.value.set(F.parameters.normalScale[0],F.parameters.normalScale[1]),F.parameters.map&&(B.tDiffuse.value=F.parameters.map,B.enableDiffuse.value=!0),F.parameters.envMap&&(B.tCube.value=F.parameters.envMap, +B.enableReflection.value=!0,B.uReflectivity.value=F.parameters.reflectivity),F.parameters.lightMap&&(B.tAO.value=F.parameters.lightMap,B.enableAO.value=!0),F.parameters.specularMap&&(B.tSpecular.value=x.textures[F.parameters.specularMap],B.enableSpecular.value=!0),F.parameters.displacementMap&&(B.tDisplacement.value=x.textures[F.parameters.displacementMap],B.enableDisplacement.value=!0,B.uDisplacementBias.value=F.parameters.displacementBias,B.uDisplacementScale.value=F.parameters.displacementScale), +B.uDiffuseColor.value.setHex(q),B.uSpecularColor.value.setHex(W),B.uAmbientColor.value.setHex(p),B.uShininess.value=H,F.parameters.opacity&&(B.uOpacity.value=F.parameters.opacity),s=new THREE.ShaderMaterial({fragmentShader:E.fragmentShader,vertexShader:E.vertexShader,uniforms:B,lights:!0,fog:!0})):s=new THREE[F.type](F.parameters);x.materials[K]=s}for(K in J.materials)if(F=J.materials[K],F.parameters.materials){L=[];for(q=0;qh.end&&(h.end=e);b||(b=g)}}a.firstAnimation=b}; +THREE.MorphAnimMesh.prototype.setAnimationLabel=function(a,b,c){this.geometry.animations||(this.geometry.animations={});this.geometry.animations[a]={start:b,end:c}};THREE.MorphAnimMesh.prototype.playAnimation=function(a,b){var c=this.geometry.animations[a];c?(this.setFrameRange(c.start,c.end),this.duration=1E3*((c.end-c.start)/b),this.time=0):console.warn("animation["+a+"] undefined")}; +THREE.MorphAnimMesh.prototype.updateAnimation=function(a){var b=this.duration/this.length;this.time+=this.direction*a;if(this.mirroredLoop){if(this.time>this.duration||0>this.time)this.direction*=-1,this.time>this.duration&&(this.time=this.duration,this.directionBackwards=!0),0>this.time&&(this.time=0,this.directionBackwards=!1)}else this.time%=this.duration,0>this.time&&(this.time+=this.duration);a=this.startKeyframe+THREE.Math.clamp(Math.floor(this.time/b),0,this.length-1);a!==this.currentKeyframe&& +(this.morphTargetInfluences[this.lastKeyframe]=0,this.morphTargetInfluences[this.currentKeyframe]=1,this.morphTargetInfluences[a]=0,this.lastKeyframe=this.currentKeyframe,this.currentKeyframe=a);b=this.time%b/b;this.directionBackwards&&(b=1-b);this.morphTargetInfluences[this.currentKeyframe]=b;this.morphTargetInfluences[this.lastKeyframe]=1-b}; +THREE.MorphAnimMesh.prototype.clone=function(a){void 0===a&&(a=new THREE.MorphAnimMesh(this.geometry,this.material));a.duration=this.duration;a.mirroredLoop=this.mirroredLoop;a.time=this.time;a.lastKeyframe=this.lastKeyframe;a.currentKeyframe=this.currentKeyframe;a.direction=this.direction;a.directionBackwards=this.directionBackwards;THREE.Mesh.prototype.clone.call(this,a);return a};THREE.Ribbon=function(a,b){THREE.Object3D.call(this);this.geometry=a;this.material=b};THREE.Ribbon.prototype=Object.create(THREE.Object3D.prototype);THREE.Ribbon.prototype.clone=function(a){void 0===a&&(a=new THREE.Ribbon(this.geometry,this.material));THREE.Object3D.prototype.clone.call(this,a);return a};THREE.LOD=function(){THREE.Object3D.call(this);this.LODs=[]};THREE.LOD.prototype=Object.create(THREE.Object3D.prototype);THREE.LOD.prototype.addLevel=function(a,b){void 0===b&&(b=0);for(var b=Math.abs(b),c=0;c=this.LODs[b].visibleAtDistance)this.LODs[b-1].object3D.visible=!1,this.LODs[b].object3D.visible=!0;else break;for(;bI&&t.clearRect(Ka.min.x|0,Ka.min.y|0,Ka.max.x-Ka.min.x|0,Ka.max.y-Ka.min.y|0),0=j||(j*=f.intensity,c.add(gb.multiplyScalar(j)))}else f instanceof THREE.PointLight&&(g=bb.getPositionFromMatrix(f.matrixWorld),j=b.dot(bb.subVectors(g,a).normalize()),0>=j||(j*=0==f.distance?1:1-Math.min(a.distanceTo(g)/f.distance,1),0!=j&&(j*=f.intensity,c.add(gb.multiplyScalar(j)))))}}function x(a,d,e,f,g,j,h,i){l.info.render.vertices+= +3;l.info.render.faces++;b(i.opacity);c(i.blending);M=a.positionScreen.x;ca=a.positionScreen.y;qa=d.positionScreen.x;ha=d.positionScreen.y;ra=e.positionScreen.x;N=e.positionScreen.y;y(M,ca,qa,ha,ra,N);(i instanceof THREE.MeshLambertMaterial||i instanceof THREE.MeshPhongMaterial)&&null===i.map?(Z.copy(i.color),oa.copy(i.emissive),i.vertexColors===THREE.FaceColors&&Z.multiply(h.color),!0===gc?!1===i.wireframe&&i.shading==THREE.SmoothShading&&3==h.vertexNormalsLength?(pa.copy(vb),Y.copy(vb),da.copy(vb), +q(h.v1.positionWorld,h.vertexNormalsModel[0],pa),q(h.v2.positionWorld,h.vertexNormalsModel[1],Y),q(h.v3.positionWorld,h.vertexNormalsModel[2],da),pa.multiply(Z).add(oa),Y.multiply(Z).add(oa),da.multiply(Z).add(oa),la.addColors(Y,da).multiplyScalar(0.5),Fa=E(pa,Y,da,la),G(M,ca,qa,ha,ra,N,0,0,1,0,0,1,Fa)):(aa.copy(vb),q(h.centroidModel,h.normalModel,aa),aa.multiply(Z).add(oa),!0===i.wireframe?C(aa,i.wireframeLinewidth,i.wireframeLinecap,i.wireframeLinejoin):A(aa)):!0===i.wireframe?C(i.color,i.wireframeLinewidth, +i.wireframeLinecap,i.wireframeLinejoin):A(i.color)):i instanceof THREE.MeshBasicMaterial||i instanceof THREE.MeshLambertMaterial||i instanceof THREE.MeshPhongMaterial?null!==i.map?i.map.mapping instanceof THREE.UVMapping&&(Xa=h.uvs[0],F(M,ca,qa,ha,ra,N,Xa[f].x,Xa[f].y,Xa[g].x,Xa[g].y,Xa[j].x,Xa[j].y,i.map)):null!==i.envMap?i.envMap.mapping instanceof THREE.SphericalReflectionMapping&&(bb.copy(h.vertexNormalsModelView[f]),ub=0.5*bb.x+0.5,Ib=0.5*bb.y+0.5,bb.copy(h.vertexNormalsModelView[g]),Jb=0.5* +bb.x+0.5,fc=0.5*bb.y+0.5,bb.copy(h.vertexNormalsModelView[j]),Ab=0.5*bb.x+0.5,mc=0.5*bb.y+0.5,F(M,ca,qa,ha,ra,N,ub,Ib,Jb,fc,Ab,mc,i.envMap)):(aa.copy(i.color),i.vertexColors===THREE.FaceColors&&aa.multiply(h.color),!0===i.wireframe?C(aa,i.wireframeLinewidth,i.wireframeLinecap,i.wireframeLinejoin):A(aa)):i instanceof THREE.MeshDepthMaterial?(Wa=p.near,ab=p.far,pa.r=pa.g=pa.b=1-k(a.positionScreen.z*a.positionScreen.w,Wa,ab),Y.r=Y.g=Y.b=1-k(d.positionScreen.z*d.positionScreen.w,Wa,ab),da.r=da.g=da.b= +1-k(e.positionScreen.z*e.positionScreen.w,Wa,ab),la.addColors(Y,da).multiplyScalar(0.5),Fa=E(pa,Y,da,la),G(M,ca,qa,ha,ra,N,0,0,1,0,0,1,Fa)):i instanceof THREE.MeshNormalMaterial&&(i.shading==THREE.FlatShading?(a=h.normalModelView,aa.setRGB(a.x,a.y,a.z).multiplyScalar(0.5).addScalar(0.5),!0===i.wireframe?C(aa,i.wireframeLinewidth,i.wireframeLinecap,i.wireframeLinejoin):A(aa)):i.shading==THREE.SmoothShading&&(a=h.vertexNormalsModelView[f],pa.setRGB(a.x,a.y,a.z).multiplyScalar(0.5).addScalar(0.5),a= +h.vertexNormalsModelView[g],Y.setRGB(a.x,a.y,a.z).multiplyScalar(0.5).addScalar(0.5),a=h.vertexNormalsModelView[j],da.setRGB(a.x,a.y,a.z).multiplyScalar(0.5).addScalar(0.5),la.addColors(Y,da).multiplyScalar(0.5),Fa=E(pa,Y,da,la),G(M,ca,qa,ha,ra,N,0,0,1,0,0,1,Fa)))}function y(a,b,c,d,e,f){t.beginPath();t.moveTo(a,b);t.lineTo(c,d);t.lineTo(e,f);t.closePath()}function B(a,b,c,d,e,f,g,j){t.beginPath();t.moveTo(a,b);t.lineTo(c,d);t.lineTo(e,f);t.lineTo(g,j);t.closePath()}function C(a,b,c,j){d(b);e(c); +f(j);g(a.getStyle());t.stroke();Va.expandByScalar(2*b)}function A(a){h(a.getStyle());t.fill()}function F(a,b,c,d,e,f,g,j,i,wa,k,l,n){if(!(n instanceof THREE.DataTexture||void 0===n.image||0==n.image.width)){if(!0===n.needsUpdate){var m=n.wrapS==THREE.RepeatWrapping,hb=n.wrapT==THREE.RepeatWrapping;nb[n.id]=t.createPattern(n.image,!0===m&&!0===hb?"repeat":!0===m&&!1===hb?"repeat-x":!1===m&&!0===hb?"repeat-y":"no-repeat");n.needsUpdate=!1}void 0===nb[n.id]?h("rgba(0,0,0,1)"):h(nb[n.id]);var m=n.offset.x/ +n.repeat.x,hb=n.offset.y/n.repeat.y,p=n.image.width*n.repeat.x,q=n.image.height*n.repeat.y,g=(g+m)*p,j=(1-j+hb)*q,c=c-a,d=d-b,e=e-a,f=f-b,i=(i+m)*p-g,wa=(1-wa+hb)*q-j,k=(k+m)*p-g,l=(1-l+hb)*q-j,m=i*l-k*wa;0===m?(void 0===ia[n.id]&&(b=document.createElement("canvas"),b.width=n.image.width,b.height=n.image.height,b=b.getContext("2d"),b.drawImage(n.image,0,0),ia[n.id]=b.getImageData(0,0,n.image.width,n.image.height).data),b=ia[n.id],g=4*(Math.floor(g)+Math.floor(j)*n.image.width),aa.setRGB(b[g]/255, +b[g+1]/255,b[g+2]/255),A(aa)):(m=1/m,n=(l*c-wa*e)*m,wa=(l*d-wa*f)*m,c=(i*e-k*c)*m,d=(i*f-k*d)*m,a=a-n*g-c*j,g=b-wa*g-d*j,t.save(),t.transform(n,wa,c,d,a,g),t.fill(),t.restore())}}function G(a,b,c,d,e,f,g,j,i,h,wa,k,n){var l,m;l=n.width-1;m=n.height-1;g*=l;j*=m;c-=a;d-=b;e-=a;f-=b;i=i*l-g;h=h*m-j;wa=wa*l-g;k=k*m-j;m=1/(i*k-wa*h);l=(k*c-h*e)*m;h=(k*d-h*f)*m;c=(i*e-wa*c)*m;d=(i*f-wa*d)*m;a=a-l*g-c*j;b=b-h*g-d*j;t.save();t.transform(l,h,c,d,a,b);t.clip();t.drawImage(n,0,0);t.restore()}function E(a,b, +c,d){Ra[0]=255*a.r|0;Ra[1]=255*a.g|0;Ra[2]=255*a.b|0;Ra[4]=255*b.r|0;Ra[5]=255*b.g|0;Ra[6]=255*b.b|0;Ra[8]=255*c.r|0;Ra[9]=255*c.g|0;Ra[10]=255*c.b|0;Ra[12]=255*d.r|0;Ra[13]=255*d.g|0;Ra[14]=255*d.b|0;j.putImageData(yb,0,0);Sa.drawImage(xb,0,0);return cb}function I(a,b){var c=b.x-a.x,d=b.y-a.y,e=c*c+d*d;0!==e&&(e=1/Math.sqrt(e),c*=e,d*=e,b.x+=c,b.y+=d,a.x-=c,a.y-=d)}if(!1===p instanceof THREE.Camera)console.error("THREE.CanvasRenderer.render: camera is not an instance of THREE.Camera.");else{!0=== +this.autoClear&&this.clear();t.setTransform(1,0,0,-1,v,z);l.info.render.vertices=0;l.info.render.faces=0;m=r.projectScene(a,p,this.sortObjects,this.sortElements);n=m.elements;s=m.lights;gc=0>1,nd=H.height>>1,Cb=wa.scale.x*v,Db=wa.scale.y*z,Bb=Cb*md,hb=Db*nd,Va.min.set(X.x-Bb,X.y-hb),Va.max.set(X.x+Bb,X.y+hb),!1!==pb.isIntersectionBox(Va)&&(t.save(),t.translate(X.x,X.y),t.rotate(-wa.rotation),t.scale(Cb,-Db),t.translate(-md,-nd),t.drawImage(H,0,0),t.restore())):P instanceof THREE.ParticleCanvasMaterial&&(Bb=wa.scale.x*v,hb=wa.scale.y*z,Va.min.set(X.x-Bb,X.y-hb),Va.max.set(X.x+Bb,X.y+hb),!1!==pb.isIntersectionBox(Va)&&(g(P.color.getStyle()), +h(P.color.getStyle()),t.save(),t.translate(X.x,X.y),t.rotate(-wa.rotation),t.scale(Bb,hb),P.program(t),t.restore()))}else if(H instanceof THREE.RenderableLine)K=H.v1,L=H.v2,K.positionScreen.x*=v,K.positionScreen.y*=z,L.positionScreen.x*=v,L.positionScreen.y*=z,Va.setFromPoints([K.positionScreen,L.positionScreen]),!0===pb.isIntersectionBox(Va)&&(X=K,wa=L,b(P.opacity),c(P.blending),t.beginPath(),t.moveTo(X.positionScreen.x,X.positionScreen.y),t.lineTo(wa.positionScreen.x,wa.positionScreen.y),P instanceof +THREE.LineBasicMaterial?(d(P.linewidth),e(P.linecap),f(P.linejoin),g(P.color.getStyle()),i(null,null),t.stroke(),Va.expandByScalar(2*P.linewidth)):P instanceof THREE.LineDashedMaterial&&(d(P.linewidth),e(P.linecap),f(P.linejoin),g(P.color.getStyle()),i(P.dashSize,P.gapSize),t.stroke(),Va.expandByScalar(2*P.linewidth)));else if(H instanceof THREE.RenderableFace3){K=H.v1;L=H.v2;U=H.v3;if(-1>K.positionScreen.z||1L.positionScreen.z||1 +U.positionScreen.z||1K.positionScreen.z|| +1L.positionScreen.z||1U.positionScreen.z||1fa.positionScreen.z||1 0\nuniform vec3 directionalLightColor[ MAX_DIR_LIGHTS ];\nuniform vec3 directionalLightDirection[ MAX_DIR_LIGHTS ];\n#endif\n#if MAX_HEMI_LIGHTS > 0\nuniform vec3 hemisphereLightSkyColor[ MAX_HEMI_LIGHTS ];\nuniform vec3 hemisphereLightGroundColor[ MAX_HEMI_LIGHTS ];\nuniform vec3 hemisphereLightDirection[ MAX_HEMI_LIGHTS ];\n#endif\n#if MAX_POINT_LIGHTS > 0\nuniform vec3 pointLightColor[ MAX_POINT_LIGHTS ];\nuniform vec3 pointLightPosition[ MAX_POINT_LIGHTS ];\nuniform float pointLightDistance[ MAX_POINT_LIGHTS ];\n#endif\n#if MAX_SPOT_LIGHTS > 0\nuniform vec3 spotLightColor[ MAX_SPOT_LIGHTS ];\nuniform vec3 spotLightPosition[ MAX_SPOT_LIGHTS ];\nuniform vec3 spotLightDirection[ MAX_SPOT_LIGHTS ];\nuniform float spotLightDistance[ MAX_SPOT_LIGHTS ];\nuniform float spotLightAngleCos[ MAX_SPOT_LIGHTS ];\nuniform float spotLightExponent[ MAX_SPOT_LIGHTS ];\n#endif\n#ifdef WRAP_AROUND\nuniform vec3 wrapRGB;\n#endif", +lights_lambert_vertex:"vLightFront = vec3( 0.0 );\n#ifdef DOUBLE_SIDED\nvLightBack = vec3( 0.0 );\n#endif\ntransformedNormal = normalize( transformedNormal );\n#if MAX_DIR_LIGHTS > 0\nfor( int i = 0; i < MAX_DIR_LIGHTS; i ++ ) {\nvec4 lDirection = viewMatrix * vec4( directionalLightDirection[ i ], 0.0 );\nvec3 dirVector = normalize( lDirection.xyz );\nfloat dotProduct = dot( transformedNormal, dirVector );\nvec3 directionalLightWeighting = vec3( max( dotProduct, 0.0 ) );\n#ifdef DOUBLE_SIDED\nvec3 directionalLightWeightingBack = vec3( max( -dotProduct, 0.0 ) );\n#ifdef WRAP_AROUND\nvec3 directionalLightWeightingHalfBack = vec3( max( -0.5 * dotProduct + 0.5, 0.0 ) );\n#endif\n#endif\n#ifdef WRAP_AROUND\nvec3 directionalLightWeightingHalf = vec3( max( 0.5 * dotProduct + 0.5, 0.0 ) );\ndirectionalLightWeighting = mix( directionalLightWeighting, directionalLightWeightingHalf, wrapRGB );\n#ifdef DOUBLE_SIDED\ndirectionalLightWeightingBack = mix( directionalLightWeightingBack, directionalLightWeightingHalfBack, wrapRGB );\n#endif\n#endif\nvLightFront += directionalLightColor[ i ] * directionalLightWeighting;\n#ifdef DOUBLE_SIDED\nvLightBack += directionalLightColor[ i ] * directionalLightWeightingBack;\n#endif\n}\n#endif\n#if MAX_POINT_LIGHTS > 0\nfor( int i = 0; i < MAX_POINT_LIGHTS; i ++ ) {\nvec4 lPosition = viewMatrix * vec4( pointLightPosition[ i ], 1.0 );\nvec3 lVector = lPosition.xyz - mvPosition.xyz;\nfloat lDistance = 1.0;\nif ( pointLightDistance[ i ] > 0.0 )\nlDistance = 1.0 - min( ( length( lVector ) / pointLightDistance[ i ] ), 1.0 );\nlVector = normalize( lVector );\nfloat dotProduct = dot( transformedNormal, lVector );\nvec3 pointLightWeighting = vec3( max( dotProduct, 0.0 ) );\n#ifdef DOUBLE_SIDED\nvec3 pointLightWeightingBack = vec3( max( -dotProduct, 0.0 ) );\n#ifdef WRAP_AROUND\nvec3 pointLightWeightingHalfBack = vec3( max( -0.5 * dotProduct + 0.5, 0.0 ) );\n#endif\n#endif\n#ifdef WRAP_AROUND\nvec3 pointLightWeightingHalf = vec3( max( 0.5 * dotProduct + 0.5, 0.0 ) );\npointLightWeighting = mix( pointLightWeighting, pointLightWeightingHalf, wrapRGB );\n#ifdef DOUBLE_SIDED\npointLightWeightingBack = mix( pointLightWeightingBack, pointLightWeightingHalfBack, wrapRGB );\n#endif\n#endif\nvLightFront += pointLightColor[ i ] * pointLightWeighting * lDistance;\n#ifdef DOUBLE_SIDED\nvLightBack += pointLightColor[ i ] * pointLightWeightingBack * lDistance;\n#endif\n}\n#endif\n#if MAX_SPOT_LIGHTS > 0\nfor( int i = 0; i < MAX_SPOT_LIGHTS; i ++ ) {\nvec4 lPosition = viewMatrix * vec4( spotLightPosition[ i ], 1.0 );\nvec3 lVector = lPosition.xyz - mvPosition.xyz;\nfloat spotEffect = dot( spotLightDirection[ i ], normalize( spotLightPosition[ i ] - worldPosition.xyz ) );\nif ( spotEffect > spotLightAngleCos[ i ] ) {\nspotEffect = max( pow( spotEffect, spotLightExponent[ i ] ), 0.0 );\nfloat lDistance = 1.0;\nif ( spotLightDistance[ i ] > 0.0 )\nlDistance = 1.0 - min( ( length( lVector ) / spotLightDistance[ i ] ), 1.0 );\nlVector = normalize( lVector );\nfloat dotProduct = dot( transformedNormal, lVector );\nvec3 spotLightWeighting = vec3( max( dotProduct, 0.0 ) );\n#ifdef DOUBLE_SIDED\nvec3 spotLightWeightingBack = vec3( max( -dotProduct, 0.0 ) );\n#ifdef WRAP_AROUND\nvec3 spotLightWeightingHalfBack = vec3( max( -0.5 * dotProduct + 0.5, 0.0 ) );\n#endif\n#endif\n#ifdef WRAP_AROUND\nvec3 spotLightWeightingHalf = vec3( max( 0.5 * dotProduct + 0.5, 0.0 ) );\nspotLightWeighting = mix( spotLightWeighting, spotLightWeightingHalf, wrapRGB );\n#ifdef DOUBLE_SIDED\nspotLightWeightingBack = mix( spotLightWeightingBack, spotLightWeightingHalfBack, wrapRGB );\n#endif\n#endif\nvLightFront += spotLightColor[ i ] * spotLightWeighting * lDistance * spotEffect;\n#ifdef DOUBLE_SIDED\nvLightBack += spotLightColor[ i ] * spotLightWeightingBack * lDistance * spotEffect;\n#endif\n}\n}\n#endif\n#if MAX_HEMI_LIGHTS > 0\nfor( int i = 0; i < MAX_HEMI_LIGHTS; i ++ ) {\nvec4 lDirection = viewMatrix * vec4( hemisphereLightDirection[ i ], 0.0 );\nvec3 lVector = normalize( lDirection.xyz );\nfloat dotProduct = dot( transformedNormal, lVector );\nfloat hemiDiffuseWeight = 0.5 * dotProduct + 0.5;\nfloat hemiDiffuseWeightBack = -0.5 * dotProduct + 0.5;\nvLightFront += mix( hemisphereLightGroundColor[ i ], hemisphereLightSkyColor[ i ], hemiDiffuseWeight );\n#ifdef DOUBLE_SIDED\nvLightBack += mix( hemisphereLightGroundColor[ i ], hemisphereLightSkyColor[ i ], hemiDiffuseWeightBack );\n#endif\n}\n#endif\nvLightFront = vLightFront * diffuse + ambient * ambientLightColor + emissive;\n#ifdef DOUBLE_SIDED\nvLightBack = vLightBack * diffuse + ambient * ambientLightColor + emissive;\n#endif", +lights_phong_pars_vertex:"#ifndef PHONG_PER_PIXEL\n#if MAX_POINT_LIGHTS > 0\nuniform vec3 pointLightPosition[ MAX_POINT_LIGHTS ];\nuniform float pointLightDistance[ MAX_POINT_LIGHTS ];\nvarying vec4 vPointLight[ MAX_POINT_LIGHTS ];\n#endif\n#if MAX_SPOT_LIGHTS > 0\nuniform vec3 spotLightPosition[ MAX_SPOT_LIGHTS ];\nuniform float spotLightDistance[ MAX_SPOT_LIGHTS ];\nvarying vec4 vSpotLight[ MAX_SPOT_LIGHTS ];\n#endif\n#endif\n#if MAX_SPOT_LIGHTS > 0 || defined( USE_BUMPMAP )\nvarying vec3 vWorldPosition;\n#endif", +lights_phong_vertex:"#ifndef PHONG_PER_PIXEL\n#if MAX_POINT_LIGHTS > 0\nfor( int i = 0; i < MAX_POINT_LIGHTS; i ++ ) {\nvec4 lPosition = viewMatrix * vec4( pointLightPosition[ i ], 1.0 );\nvec3 lVector = lPosition.xyz - mvPosition.xyz;\nfloat lDistance = 1.0;\nif ( pointLightDistance[ i ] > 0.0 )\nlDistance = 1.0 - min( ( length( lVector ) / pointLightDistance[ i ] ), 1.0 );\nvPointLight[ i ] = vec4( lVector, lDistance );\n}\n#endif\n#if MAX_SPOT_LIGHTS > 0\nfor( int i = 0; i < MAX_SPOT_LIGHTS; i ++ ) {\nvec4 lPosition = viewMatrix * vec4( spotLightPosition[ i ], 1.0 );\nvec3 lVector = lPosition.xyz - mvPosition.xyz;\nfloat lDistance = 1.0;\nif ( spotLightDistance[ i ] > 0.0 )\nlDistance = 1.0 - min( ( length( lVector ) / spotLightDistance[ i ] ), 1.0 );\nvSpotLight[ i ] = vec4( lVector, lDistance );\n}\n#endif\n#endif\n#if MAX_SPOT_LIGHTS > 0 || defined( USE_BUMPMAP )\nvWorldPosition = worldPosition.xyz;\n#endif", +lights_phong_pars_fragment:"uniform vec3 ambientLightColor;\n#if MAX_DIR_LIGHTS > 0\nuniform vec3 directionalLightColor[ MAX_DIR_LIGHTS ];\nuniform vec3 directionalLightDirection[ MAX_DIR_LIGHTS ];\n#endif\n#if MAX_HEMI_LIGHTS > 0\nuniform vec3 hemisphereLightSkyColor[ MAX_HEMI_LIGHTS ];\nuniform vec3 hemisphereLightGroundColor[ MAX_HEMI_LIGHTS ];\nuniform vec3 hemisphereLightDirection[ MAX_HEMI_LIGHTS ];\n#endif\n#if MAX_POINT_LIGHTS > 0\nuniform vec3 pointLightColor[ MAX_POINT_LIGHTS ];\n#ifdef PHONG_PER_PIXEL\nuniform vec3 pointLightPosition[ MAX_POINT_LIGHTS ];\nuniform float pointLightDistance[ MAX_POINT_LIGHTS ];\n#else\nvarying vec4 vPointLight[ MAX_POINT_LIGHTS ];\n#endif\n#endif\n#if MAX_SPOT_LIGHTS > 0\nuniform vec3 spotLightColor[ MAX_SPOT_LIGHTS ];\nuniform vec3 spotLightPosition[ MAX_SPOT_LIGHTS ];\nuniform vec3 spotLightDirection[ MAX_SPOT_LIGHTS ];\nuniform float spotLightAngleCos[ MAX_SPOT_LIGHTS ];\nuniform float spotLightExponent[ MAX_SPOT_LIGHTS ];\n#ifdef PHONG_PER_PIXEL\nuniform float spotLightDistance[ MAX_SPOT_LIGHTS ];\n#else\nvarying vec4 vSpotLight[ MAX_SPOT_LIGHTS ];\n#endif\n#endif\n#if MAX_SPOT_LIGHTS > 0 || defined( USE_BUMPMAP )\nvarying vec3 vWorldPosition;\n#endif\n#ifdef WRAP_AROUND\nuniform vec3 wrapRGB;\n#endif\nvarying vec3 vViewPosition;\nvarying vec3 vNormal;", +lights_phong_fragment:"vec3 normal = normalize( vNormal );\nvec3 viewPosition = normalize( vViewPosition );\n#ifdef DOUBLE_SIDED\nnormal = normal * ( -1.0 + 2.0 * float( gl_FrontFacing ) );\n#endif\n#ifdef USE_NORMALMAP\nnormal = perturbNormal2Arb( -viewPosition, normal );\n#elif defined( USE_BUMPMAP )\nnormal = perturbNormalArb( -vViewPosition, normal, dHdxy_fwd() );\n#endif\n#if MAX_POINT_LIGHTS > 0\nvec3 pointDiffuse = vec3( 0.0 );\nvec3 pointSpecular = vec3( 0.0 );\nfor ( int i = 0; i < MAX_POINT_LIGHTS; i ++ ) {\n#ifdef PHONG_PER_PIXEL\nvec4 lPosition = viewMatrix * vec4( pointLightPosition[ i ], 1.0 );\nvec3 lVector = lPosition.xyz + vViewPosition.xyz;\nfloat lDistance = 1.0;\nif ( pointLightDistance[ i ] > 0.0 )\nlDistance = 1.0 - min( ( length( lVector ) / pointLightDistance[ i ] ), 1.0 );\nlVector = normalize( lVector );\n#else\nvec3 lVector = normalize( vPointLight[ i ].xyz );\nfloat lDistance = vPointLight[ i ].w;\n#endif\nfloat dotProduct = dot( normal, lVector );\n#ifdef WRAP_AROUND\nfloat pointDiffuseWeightFull = max( dotProduct, 0.0 );\nfloat pointDiffuseWeightHalf = max( 0.5 * dotProduct + 0.5, 0.0 );\nvec3 pointDiffuseWeight = mix( vec3 ( pointDiffuseWeightFull ), vec3( pointDiffuseWeightHalf ), wrapRGB );\n#else\nfloat pointDiffuseWeight = max( dotProduct, 0.0 );\n#endif\npointDiffuse += diffuse * pointLightColor[ i ] * pointDiffuseWeight * lDistance;\nvec3 pointHalfVector = normalize( lVector + viewPosition );\nfloat pointDotNormalHalf = max( dot( normal, pointHalfVector ), 0.0 );\nfloat pointSpecularWeight = specularStrength * max( pow( pointDotNormalHalf, shininess ), 0.0 );\n#ifdef PHYSICALLY_BASED_SHADING\nfloat specularNormalization = ( shininess + 2.0001 ) / 8.0;\nvec3 schlick = specular + vec3( 1.0 - specular ) * pow( 1.0 - dot( lVector, pointHalfVector ), 5.0 );\npointSpecular += schlick * pointLightColor[ i ] * pointSpecularWeight * pointDiffuseWeight * lDistance * specularNormalization;\n#else\npointSpecular += specular * pointLightColor[ i ] * pointSpecularWeight * pointDiffuseWeight * lDistance;\n#endif\n}\n#endif\n#if MAX_SPOT_LIGHTS > 0\nvec3 spotDiffuse = vec3( 0.0 );\nvec3 spotSpecular = vec3( 0.0 );\nfor ( int i = 0; i < MAX_SPOT_LIGHTS; i ++ ) {\n#ifdef PHONG_PER_PIXEL\nvec4 lPosition = viewMatrix * vec4( spotLightPosition[ i ], 1.0 );\nvec3 lVector = lPosition.xyz + vViewPosition.xyz;\nfloat lDistance = 1.0;\nif ( spotLightDistance[ i ] > 0.0 )\nlDistance = 1.0 - min( ( length( lVector ) / spotLightDistance[ i ] ), 1.0 );\nlVector = normalize( lVector );\n#else\nvec3 lVector = normalize( vSpotLight[ i ].xyz );\nfloat lDistance = vSpotLight[ i ].w;\n#endif\nfloat spotEffect = dot( spotLightDirection[ i ], normalize( spotLightPosition[ i ] - vWorldPosition ) );\nif ( spotEffect > spotLightAngleCos[ i ] ) {\nspotEffect = max( pow( spotEffect, spotLightExponent[ i ] ), 0.0 );\nfloat dotProduct = dot( normal, lVector );\n#ifdef WRAP_AROUND\nfloat spotDiffuseWeightFull = max( dotProduct, 0.0 );\nfloat spotDiffuseWeightHalf = max( 0.5 * dotProduct + 0.5, 0.0 );\nvec3 spotDiffuseWeight = mix( vec3 ( spotDiffuseWeightFull ), vec3( spotDiffuseWeightHalf ), wrapRGB );\n#else\nfloat spotDiffuseWeight = max( dotProduct, 0.0 );\n#endif\nspotDiffuse += diffuse * spotLightColor[ i ] * spotDiffuseWeight * lDistance * spotEffect;\nvec3 spotHalfVector = normalize( lVector + viewPosition );\nfloat spotDotNormalHalf = max( dot( normal, spotHalfVector ), 0.0 );\nfloat spotSpecularWeight = specularStrength * max( pow( spotDotNormalHalf, shininess ), 0.0 );\n#ifdef PHYSICALLY_BASED_SHADING\nfloat specularNormalization = ( shininess + 2.0001 ) / 8.0;\nvec3 schlick = specular + vec3( 1.0 - specular ) * pow( 1.0 - dot( lVector, spotHalfVector ), 5.0 );\nspotSpecular += schlick * spotLightColor[ i ] * spotSpecularWeight * spotDiffuseWeight * lDistance * specularNormalization * spotEffect;\n#else\nspotSpecular += specular * spotLightColor[ i ] * spotSpecularWeight * spotDiffuseWeight * lDistance * spotEffect;\n#endif\n}\n}\n#endif\n#if MAX_DIR_LIGHTS > 0\nvec3 dirDiffuse = vec3( 0.0 );\nvec3 dirSpecular = vec3( 0.0 );\nfor( int i = 0; i < MAX_DIR_LIGHTS; i ++ ) {\nvec4 lDirection = viewMatrix * vec4( directionalLightDirection[ i ], 0.0 );\nvec3 dirVector = normalize( lDirection.xyz );\nfloat dotProduct = dot( normal, dirVector );\n#ifdef WRAP_AROUND\nfloat dirDiffuseWeightFull = max( dotProduct, 0.0 );\nfloat dirDiffuseWeightHalf = max( 0.5 * dotProduct + 0.5, 0.0 );\nvec3 dirDiffuseWeight = mix( vec3( dirDiffuseWeightFull ), vec3( dirDiffuseWeightHalf ), wrapRGB );\n#else\nfloat dirDiffuseWeight = max( dotProduct, 0.0 );\n#endif\ndirDiffuse += diffuse * directionalLightColor[ i ] * dirDiffuseWeight;\nvec3 dirHalfVector = normalize( dirVector + viewPosition );\nfloat dirDotNormalHalf = max( dot( normal, dirHalfVector ), 0.0 );\nfloat dirSpecularWeight = specularStrength * max( pow( dirDotNormalHalf, shininess ), 0.0 );\n#ifdef PHYSICALLY_BASED_SHADING\nfloat specularNormalization = ( shininess + 2.0001 ) / 8.0;\nvec3 schlick = specular + vec3( 1.0 - specular ) * pow( 1.0 - dot( dirVector, dirHalfVector ), 5.0 );\ndirSpecular += schlick * directionalLightColor[ i ] * dirSpecularWeight * dirDiffuseWeight * specularNormalization;\n#else\ndirSpecular += specular * directionalLightColor[ i ] * dirSpecularWeight * dirDiffuseWeight;\n#endif\n}\n#endif\n#if MAX_HEMI_LIGHTS > 0\nvec3 hemiDiffuse = vec3( 0.0 );\nvec3 hemiSpecular = vec3( 0.0 );\nfor( int i = 0; i < MAX_HEMI_LIGHTS; i ++ ) {\nvec4 lDirection = viewMatrix * vec4( hemisphereLightDirection[ i ], 0.0 );\nvec3 lVector = normalize( lDirection.xyz );\nfloat dotProduct = dot( normal, lVector );\nfloat hemiDiffuseWeight = 0.5 * dotProduct + 0.5;\nvec3 hemiColor = mix( hemisphereLightGroundColor[ i ], hemisphereLightSkyColor[ i ], hemiDiffuseWeight );\nhemiDiffuse += diffuse * hemiColor;\nvec3 hemiHalfVectorSky = normalize( lVector + viewPosition );\nfloat hemiDotNormalHalfSky = 0.5 * dot( normal, hemiHalfVectorSky ) + 0.5;\nfloat hemiSpecularWeightSky = specularStrength * max( pow( hemiDotNormalHalfSky, shininess ), 0.0 );\nvec3 lVectorGround = -lVector;\nvec3 hemiHalfVectorGround = normalize( lVectorGround + viewPosition );\nfloat hemiDotNormalHalfGround = 0.5 * dot( normal, hemiHalfVectorGround ) + 0.5;\nfloat hemiSpecularWeightGround = specularStrength * max( pow( hemiDotNormalHalfGround, shininess ), 0.0 );\n#ifdef PHYSICALLY_BASED_SHADING\nfloat dotProductGround = dot( normal, lVectorGround );\nfloat specularNormalization = ( shininess + 2.0001 ) / 8.0;\nvec3 schlickSky = specular + vec3( 1.0 - specular ) * pow( 1.0 - dot( lVector, hemiHalfVectorSky ), 5.0 );\nvec3 schlickGround = specular + vec3( 1.0 - specular ) * pow( 1.0 - dot( lVectorGround, hemiHalfVectorGround ), 5.0 );\nhemiSpecular += hemiColor * specularNormalization * ( schlickSky * hemiSpecularWeightSky * max( dotProduct, 0.0 ) + schlickGround * hemiSpecularWeightGround * max( dotProductGround, 0.0 ) );\n#else\nhemiSpecular += specular * hemiColor * ( hemiSpecularWeightSky + hemiSpecularWeightGround ) * hemiDiffuseWeight;\n#endif\n}\n#endif\nvec3 totalDiffuse = vec3( 0.0 );\nvec3 totalSpecular = vec3( 0.0 );\n#if MAX_DIR_LIGHTS > 0\ntotalDiffuse += dirDiffuse;\ntotalSpecular += dirSpecular;\n#endif\n#if MAX_HEMI_LIGHTS > 0\ntotalDiffuse += hemiDiffuse;\ntotalSpecular += hemiSpecular;\n#endif\n#if MAX_POINT_LIGHTS > 0\ntotalDiffuse += pointDiffuse;\ntotalSpecular += pointSpecular;\n#endif\n#if MAX_SPOT_LIGHTS > 0\ntotalDiffuse += spotDiffuse;\ntotalSpecular += spotSpecular;\n#endif\n#ifdef METAL\ngl_FragColor.xyz = gl_FragColor.xyz * ( emissive + totalDiffuse + ambientLightColor * ambient + totalSpecular );\n#else\ngl_FragColor.xyz = gl_FragColor.xyz * ( emissive + totalDiffuse + ambientLightColor * ambient ) + totalSpecular;\n#endif", +color_pars_fragment:"#ifdef USE_COLOR\nvarying vec3 vColor;\n#endif",color_fragment:"#ifdef USE_COLOR\ngl_FragColor = gl_FragColor * vec4( vColor, opacity );\n#endif",color_pars_vertex:"#ifdef USE_COLOR\nvarying vec3 vColor;\n#endif",color_vertex:"#ifdef USE_COLOR\n#ifdef GAMMA_INPUT\nvColor = color * color;\n#else\nvColor = color;\n#endif\n#endif",skinning_pars_vertex:"#ifdef USE_SKINNING\n#ifdef BONE_TEXTURE\nuniform sampler2D boneTexture;\nmat4 getBoneMatrix( const in float i ) {\nfloat j = i * 4.0;\nfloat x = mod( j, N_BONE_PIXEL_X );\nfloat y = floor( j / N_BONE_PIXEL_X );\nconst float dx = 1.0 / N_BONE_PIXEL_X;\nconst float dy = 1.0 / N_BONE_PIXEL_Y;\ny = dy * ( y + 0.5 );\nvec4 v1 = texture2D( boneTexture, vec2( dx * ( x + 0.5 ), y ) );\nvec4 v2 = texture2D( boneTexture, vec2( dx * ( x + 1.5 ), y ) );\nvec4 v3 = texture2D( boneTexture, vec2( dx * ( x + 2.5 ), y ) );\nvec4 v4 = texture2D( boneTexture, vec2( dx * ( x + 3.5 ), y ) );\nmat4 bone = mat4( v1, v2, v3, v4 );\nreturn bone;\n}\n#else\nuniform mat4 boneGlobalMatrices[ MAX_BONES ];\nmat4 getBoneMatrix( const in float i ) {\nmat4 bone = boneGlobalMatrices[ int(i) ];\nreturn bone;\n}\n#endif\n#endif", +skinbase_vertex:"#ifdef USE_SKINNING\nmat4 boneMatX = getBoneMatrix( skinIndex.x );\nmat4 boneMatY = getBoneMatrix( skinIndex.y );\n#endif",skinning_vertex:"#ifdef USE_SKINNING\n#ifdef USE_MORPHTARGETS\nvec4 skinVertex = vec4( morphed, 1.0 );\n#else\nvec4 skinVertex = vec4( position, 1.0 );\n#endif\nvec4 skinned = boneMatX * skinVertex * skinWeight.x;\nskinned \t += boneMatY * skinVertex * skinWeight.y;\n#endif",morphtarget_pars_vertex:"#ifdef USE_MORPHTARGETS\n#ifndef USE_MORPHNORMALS\nuniform float morphTargetInfluences[ 8 ];\n#else\nuniform float morphTargetInfluences[ 4 ];\n#endif\n#endif", +morphtarget_vertex:"#ifdef USE_MORPHTARGETS\nvec3 morphed = vec3( 0.0 );\nmorphed += ( morphTarget0 - position ) * morphTargetInfluences[ 0 ];\nmorphed += ( morphTarget1 - position ) * morphTargetInfluences[ 1 ];\nmorphed += ( morphTarget2 - position ) * morphTargetInfluences[ 2 ];\nmorphed += ( morphTarget3 - position ) * morphTargetInfluences[ 3 ];\n#ifndef USE_MORPHNORMALS\nmorphed += ( morphTarget4 - position ) * morphTargetInfluences[ 4 ];\nmorphed += ( morphTarget5 - position ) * morphTargetInfluences[ 5 ];\nmorphed += ( morphTarget6 - position ) * morphTargetInfluences[ 6 ];\nmorphed += ( morphTarget7 - position ) * morphTargetInfluences[ 7 ];\n#endif\nmorphed += position;\n#endif", +default_vertex:"vec4 mvPosition;\n#ifdef USE_SKINNING\nmvPosition = modelViewMatrix * skinned;\n#endif\n#if !defined( USE_SKINNING ) && defined( USE_MORPHTARGETS )\nmvPosition = modelViewMatrix * vec4( morphed, 1.0 );\n#endif\n#if !defined( USE_SKINNING ) && ! defined( USE_MORPHTARGETS )\nmvPosition = modelViewMatrix * vec4( position, 1.0 );\n#endif\ngl_Position = projectionMatrix * mvPosition;",morphnormal_vertex:"#ifdef USE_MORPHNORMALS\nvec3 morphedNormal = vec3( 0.0 );\nmorphedNormal += ( morphNormal0 - normal ) * morphTargetInfluences[ 0 ];\nmorphedNormal += ( morphNormal1 - normal ) * morphTargetInfluences[ 1 ];\nmorphedNormal += ( morphNormal2 - normal ) * morphTargetInfluences[ 2 ];\nmorphedNormal += ( morphNormal3 - normal ) * morphTargetInfluences[ 3 ];\nmorphedNormal += normal;\n#endif", +skinnormal_vertex:"#ifdef USE_SKINNING\nmat4 skinMatrix = skinWeight.x * boneMatX;\nskinMatrix \t+= skinWeight.y * boneMatY;\n#ifdef USE_MORPHNORMALS\nvec4 skinnedNormal = skinMatrix * vec4( morphedNormal, 0.0 );\n#else\nvec4 skinnedNormal = skinMatrix * vec4( normal, 0.0 );\n#endif\n#endif",defaultnormal_vertex:"vec3 objectNormal;\n#ifdef USE_SKINNING\nobjectNormal = skinnedNormal.xyz;\n#endif\n#if !defined( USE_SKINNING ) && defined( USE_MORPHNORMALS )\nobjectNormal = morphedNormal;\n#endif\n#if !defined( USE_SKINNING ) && ! defined( USE_MORPHNORMALS )\nobjectNormal = normal;\n#endif\n#ifdef FLIP_SIDED\nobjectNormal = -objectNormal;\n#endif\nvec3 transformedNormal = normalMatrix * objectNormal;", +shadowmap_pars_fragment:"#ifdef USE_SHADOWMAP\nuniform sampler2D shadowMap[ MAX_SHADOWS ];\nuniform vec2 shadowMapSize[ MAX_SHADOWS ];\nuniform float shadowDarkness[ MAX_SHADOWS ];\nuniform float shadowBias[ MAX_SHADOWS ];\nvarying vec4 vShadowCoord[ MAX_SHADOWS ];\nfloat unpackDepth( const in vec4 rgba_depth ) {\nconst vec4 bit_shift = vec4( 1.0 / ( 256.0 * 256.0 * 256.0 ), 1.0 / ( 256.0 * 256.0 ), 1.0 / 256.0, 1.0 );\nfloat depth = dot( rgba_depth, bit_shift );\nreturn depth;\n}\n#endif",shadowmap_fragment:"#ifdef USE_SHADOWMAP\n#ifdef SHADOWMAP_DEBUG\nvec3 frustumColors[3];\nfrustumColors[0] = vec3( 1.0, 0.5, 0.0 );\nfrustumColors[1] = vec3( 0.0, 1.0, 0.8 );\nfrustumColors[2] = vec3( 0.0, 0.5, 1.0 );\n#endif\n#ifdef SHADOWMAP_CASCADE\nint inFrustumCount = 0;\n#endif\nfloat fDepth;\nvec3 shadowColor = vec3( 1.0 );\nfor( int i = 0; i < MAX_SHADOWS; i ++ ) {\nvec3 shadowCoord = vShadowCoord[ i ].xyz / vShadowCoord[ i ].w;\nbvec4 inFrustumVec = bvec4 ( shadowCoord.x >= 0.0, shadowCoord.x <= 1.0, shadowCoord.y >= 0.0, shadowCoord.y <= 1.0 );\nbool inFrustum = all( inFrustumVec );\n#ifdef SHADOWMAP_CASCADE\ninFrustumCount += int( inFrustum );\nbvec3 frustumTestVec = bvec3( inFrustum, inFrustumCount == 1, shadowCoord.z <= 1.0 );\n#else\nbvec2 frustumTestVec = bvec2( inFrustum, shadowCoord.z <= 1.0 );\n#endif\nbool frustumTest = all( frustumTestVec );\nif ( frustumTest ) {\nshadowCoord.z += shadowBias[ i ];\n#if defined( SHADOWMAP_TYPE_PCF )\nfloat shadow = 0.0;\nconst float shadowDelta = 1.0 / 9.0;\nfloat xPixelOffset = 1.0 / shadowMapSize[ i ].x;\nfloat yPixelOffset = 1.0 / shadowMapSize[ i ].y;\nfloat dx0 = -1.25 * xPixelOffset;\nfloat dy0 = -1.25 * yPixelOffset;\nfloat dx1 = 1.25 * xPixelOffset;\nfloat dy1 = 1.25 * yPixelOffset;\nfDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx0, dy0 ) ) );\nif ( fDepth < shadowCoord.z ) shadow += shadowDelta;\nfDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( 0.0, dy0 ) ) );\nif ( fDepth < shadowCoord.z ) shadow += shadowDelta;\nfDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx1, dy0 ) ) );\nif ( fDepth < shadowCoord.z ) shadow += shadowDelta;\nfDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx0, 0.0 ) ) );\nif ( fDepth < shadowCoord.z ) shadow += shadowDelta;\nfDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy ) );\nif ( fDepth < shadowCoord.z ) shadow += shadowDelta;\nfDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx1, 0.0 ) ) );\nif ( fDepth < shadowCoord.z ) shadow += shadowDelta;\nfDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx0, dy1 ) ) );\nif ( fDepth < shadowCoord.z ) shadow += shadowDelta;\nfDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( 0.0, dy1 ) ) );\nif ( fDepth < shadowCoord.z ) shadow += shadowDelta;\nfDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx1, dy1 ) ) );\nif ( fDepth < shadowCoord.z ) shadow += shadowDelta;\nshadowColor = shadowColor * vec3( ( 1.0 - shadowDarkness[ i ] * shadow ) );\n#elif defined( SHADOWMAP_TYPE_PCF_SOFT )\nfloat shadow = 0.0;\nfloat xPixelOffset = 1.0 / shadowMapSize[ i ].x;\nfloat yPixelOffset = 1.0 / shadowMapSize[ i ].y;\nfloat dx0 = -1.0 * xPixelOffset;\nfloat dy0 = -1.0 * yPixelOffset;\nfloat dx1 = 1.0 * xPixelOffset;\nfloat dy1 = 1.0 * yPixelOffset;\nmat3 shadowKernel;\nmat3 depthKernel;\ndepthKernel[0][0] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx0, dy0 ) ) );\nif ( depthKernel[0][0] < shadowCoord.z ) shadowKernel[0][0] = 0.25;\nelse shadowKernel[0][0] = 0.0;\ndepthKernel[0][1] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx0, 0.0 ) ) );\nif ( depthKernel[0][1] < shadowCoord.z ) shadowKernel[0][1] = 0.25;\nelse shadowKernel[0][1] = 0.0;\ndepthKernel[0][2] = unpackDepth( texture2D( shadowMap[ i], shadowCoord.xy + vec2( dx0, dy1 ) ) );\nif ( depthKernel[0][2] < shadowCoord.z ) shadowKernel[0][2] = 0.25;\nelse shadowKernel[0][2] = 0.0;\ndepthKernel[1][0] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( 0.0, dy0 ) ) );\nif ( depthKernel[1][0] < shadowCoord.z ) shadowKernel[1][0] = 0.25;\nelse shadowKernel[1][0] = 0.0;\ndepthKernel[1][1] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy ) );\nif ( depthKernel[1][1] < shadowCoord.z ) shadowKernel[1][1] = 0.25;\nelse shadowKernel[1][1] = 0.0;\ndepthKernel[1][2] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( 0.0, dy1 ) ) );\nif ( depthKernel[1][2] < shadowCoord.z ) shadowKernel[1][2] = 0.25;\nelse shadowKernel[1][2] = 0.0;\ndepthKernel[2][0] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx1, dy0 ) ) );\nif ( depthKernel[2][0] < shadowCoord.z ) shadowKernel[2][0] = 0.25;\nelse shadowKernel[2][0] = 0.0;\ndepthKernel[2][1] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx1, 0.0 ) ) );\nif ( depthKernel[2][1] < shadowCoord.z ) shadowKernel[2][1] = 0.25;\nelse shadowKernel[2][1] = 0.0;\ndepthKernel[2][2] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx1, dy1 ) ) );\nif ( depthKernel[2][2] < shadowCoord.z ) shadowKernel[2][2] = 0.25;\nelse shadowKernel[2][2] = 0.0;\nvec2 fractionalCoord = 1.0 - fract( shadowCoord.xy * shadowMapSize[i].xy );\nshadowKernel[0] = mix( shadowKernel[1], shadowKernel[0], fractionalCoord.x );\nshadowKernel[1] = mix( shadowKernel[2], shadowKernel[1], fractionalCoord.x );\nvec4 shadowValues;\nshadowValues.x = mix( shadowKernel[0][1], shadowKernel[0][0], fractionalCoord.y );\nshadowValues.y = mix( shadowKernel[0][2], shadowKernel[0][1], fractionalCoord.y );\nshadowValues.z = mix( shadowKernel[1][1], shadowKernel[1][0], fractionalCoord.y );\nshadowValues.w = mix( shadowKernel[1][2], shadowKernel[1][1], fractionalCoord.y );\nshadow = dot( shadowValues, vec4( 1.0 ) );\nshadowColor = shadowColor * vec3( ( 1.0 - shadowDarkness[ i ] * shadow ) );\n#else\nvec4 rgbaDepth = texture2D( shadowMap[ i ], shadowCoord.xy );\nfloat fDepth = unpackDepth( rgbaDepth );\nif ( fDepth < shadowCoord.z )\nshadowColor = shadowColor * vec3( 1.0 - shadowDarkness[ i ] );\n#endif\n}\n#ifdef SHADOWMAP_DEBUG\n#ifdef SHADOWMAP_CASCADE\nif ( inFrustum && inFrustumCount == 1 ) gl_FragColor.xyz *= frustumColors[ i ];\n#else\nif ( inFrustum ) gl_FragColor.xyz *= frustumColors[ i ];\n#endif\n#endif\n}\n#ifdef GAMMA_OUTPUT\nshadowColor *= shadowColor;\n#endif\ngl_FragColor.xyz = gl_FragColor.xyz * shadowColor;\n#endif", +shadowmap_pars_vertex:"#ifdef USE_SHADOWMAP\nvarying vec4 vShadowCoord[ MAX_SHADOWS ];\nuniform mat4 shadowMatrix[ MAX_SHADOWS ];\n#endif",shadowmap_vertex:"#ifdef USE_SHADOWMAP\nfor( int i = 0; i < MAX_SHADOWS; i ++ ) {\nvShadowCoord[ i ] = shadowMatrix[ i ] * worldPosition;\n}\n#endif",alphatest_fragment:"#ifdef ALPHATEST\nif ( gl_FragColor.a < ALPHATEST ) discard;\n#endif",linear_to_gamma_fragment:"#ifdef GAMMA_OUTPUT\ngl_FragColor.xyz = sqrt( gl_FragColor.xyz );\n#endif"}; +THREE.UniformsUtils={merge:function(a){var b,c,d,e={};for(b=0;b dashSize ) {\ndiscard;\n}\ngl_FragColor = vec4( diffuse, opacity );",THREE.ShaderChunk.color_fragment,THREE.ShaderChunk.fog_fragment,"}"].join("\n")},depth:{uniforms:{mNear:{type:"f",value:1},mFar:{type:"f",value:2E3},opacity:{type:"f", +value:1}},vertexShader:"void main() {\ngl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );\n}",fragmentShader:"uniform float mNear;\nuniform float mFar;\nuniform float opacity;\nvoid main() {\nfloat depth = gl_FragCoord.z / gl_FragCoord.w;\nfloat color = 1.0 - smoothstep( mNear, mFar, depth );\ngl_FragColor = vec4( vec3( color ), opacity );\n}"},normal:{uniforms:{opacity:{type:"f",value:1}},vertexShader:"varying vec3 vNormal;\nvoid main() {\nvec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );\nvNormal = normalize( normalMatrix * normal );\ngl_Position = projectionMatrix * mvPosition;\n}", +fragmentShader:"uniform float opacity;\nvarying vec3 vNormal;\nvoid main() {\ngl_FragColor = vec4( 0.5 * normalize( vNormal ) + 0.5, opacity );\n}"},normalmap:{uniforms:THREE.UniformsUtils.merge([THREE.UniformsLib.fog,THREE.UniformsLib.lights,THREE.UniformsLib.shadowmap,{enableAO:{type:"i",value:0},enableDiffuse:{type:"i",value:0},enableSpecular:{type:"i",value:0},enableReflection:{type:"i",value:0},enableDisplacement:{type:"i",value:0},tDisplacement:{type:"t",value:null},tDiffuse:{type:"t",value:null}, +tCube:{type:"t",value:null},tNormal:{type:"t",value:null},tSpecular:{type:"t",value:null},tAO:{type:"t",value:null},uNormalScale:{type:"v2",value:new THREE.Vector2(1,1)},uDisplacementBias:{type:"f",value:0},uDisplacementScale:{type:"f",value:1},uDiffuseColor:{type:"c",value:new THREE.Color(16777215)},uSpecularColor:{type:"c",value:new THREE.Color(1118481)},uAmbientColor:{type:"c",value:new THREE.Color(16777215)},uShininess:{type:"f",value:30},uOpacity:{type:"f",value:1},useRefract:{type:"i",value:0}, +uRefractionRatio:{type:"f",value:0.98},uReflectivity:{type:"f",value:0.5},uOffset:{type:"v2",value:new THREE.Vector2(0,0)},uRepeat:{type:"v2",value:new THREE.Vector2(1,1)},wrapRGB:{type:"v3",value:new THREE.Vector3(1,1,1)}}]),fragmentShader:["uniform vec3 uAmbientColor;\nuniform vec3 uDiffuseColor;\nuniform vec3 uSpecularColor;\nuniform float uShininess;\nuniform float uOpacity;\nuniform bool enableDiffuse;\nuniform bool enableSpecular;\nuniform bool enableAO;\nuniform bool enableReflection;\nuniform sampler2D tDiffuse;\nuniform sampler2D tNormal;\nuniform sampler2D tSpecular;\nuniform sampler2D tAO;\nuniform samplerCube tCube;\nuniform vec2 uNormalScale;\nuniform bool useRefract;\nuniform float uRefractionRatio;\nuniform float uReflectivity;\nvarying vec3 vTangent;\nvarying vec3 vBinormal;\nvarying vec3 vNormal;\nvarying vec2 vUv;\nuniform vec3 ambientLightColor;\n#if MAX_DIR_LIGHTS > 0\nuniform vec3 directionalLightColor[ MAX_DIR_LIGHTS ];\nuniform vec3 directionalLightDirection[ MAX_DIR_LIGHTS ];\n#endif\n#if MAX_HEMI_LIGHTS > 0\nuniform vec3 hemisphereLightSkyColor[ MAX_HEMI_LIGHTS ];\nuniform vec3 hemisphereLightGroundColor[ MAX_HEMI_LIGHTS ];\nuniform vec3 hemisphereLightDirection[ MAX_HEMI_LIGHTS ];\n#endif\n#if MAX_POINT_LIGHTS > 0\nuniform vec3 pointLightColor[ MAX_POINT_LIGHTS ];\nuniform vec3 pointLightPosition[ MAX_POINT_LIGHTS ];\nuniform float pointLightDistance[ MAX_POINT_LIGHTS ];\n#endif\n#if MAX_SPOT_LIGHTS > 0\nuniform vec3 spotLightColor[ MAX_SPOT_LIGHTS ];\nuniform vec3 spotLightPosition[ MAX_SPOT_LIGHTS ];\nuniform vec3 spotLightDirection[ MAX_SPOT_LIGHTS ];\nuniform float spotLightAngleCos[ MAX_SPOT_LIGHTS ];\nuniform float spotLightExponent[ MAX_SPOT_LIGHTS ];\nuniform float spotLightDistance[ MAX_SPOT_LIGHTS ];\n#endif\n#ifdef WRAP_AROUND\nuniform vec3 wrapRGB;\n#endif\nvarying vec3 vWorldPosition;\nvarying vec3 vViewPosition;", +THREE.ShaderChunk.shadowmap_pars_fragment,THREE.ShaderChunk.fog_pars_fragment,"void main() {\ngl_FragColor = vec4( vec3( 1.0 ), uOpacity );\nvec3 specularTex = vec3( 1.0 );\nvec3 normalTex = texture2D( tNormal, vUv ).xyz * 2.0 - 1.0;\nnormalTex.xy *= uNormalScale;\nnormalTex = normalize( normalTex );\nif( enableDiffuse ) {\n#ifdef GAMMA_INPUT\nvec4 texelColor = texture2D( tDiffuse, vUv );\ntexelColor.xyz *= texelColor.xyz;\ngl_FragColor = gl_FragColor * texelColor;\n#else\ngl_FragColor = gl_FragColor * texture2D( tDiffuse, vUv );\n#endif\n}\nif( enableAO ) {\n#ifdef GAMMA_INPUT\nvec4 aoColor = texture2D( tAO, vUv );\naoColor.xyz *= aoColor.xyz;\ngl_FragColor.xyz = gl_FragColor.xyz * aoColor.xyz;\n#else\ngl_FragColor.xyz = gl_FragColor.xyz * texture2D( tAO, vUv ).xyz;\n#endif\n}\nif( enableSpecular )\nspecularTex = texture2D( tSpecular, vUv ).xyz;\nmat3 tsb = mat3( normalize( vTangent ), normalize( vBinormal ), normalize( vNormal ) );\nvec3 finalNormal = tsb * normalTex;\n#ifdef FLIP_SIDED\nfinalNormal = -finalNormal;\n#endif\nvec3 normal = normalize( finalNormal );\nvec3 viewPosition = normalize( vViewPosition );\n#if MAX_POINT_LIGHTS > 0\nvec3 pointDiffuse = vec3( 0.0 );\nvec3 pointSpecular = vec3( 0.0 );\nfor ( int i = 0; i < MAX_POINT_LIGHTS; i ++ ) {\nvec4 lPosition = viewMatrix * vec4( pointLightPosition[ i ], 1.0 );\nvec3 pointVector = lPosition.xyz + vViewPosition.xyz;\nfloat pointDistance = 1.0;\nif ( pointLightDistance[ i ] > 0.0 )\npointDistance = 1.0 - min( ( length( pointVector ) / pointLightDistance[ i ] ), 1.0 );\npointVector = normalize( pointVector );\n#ifdef WRAP_AROUND\nfloat pointDiffuseWeightFull = max( dot( normal, pointVector ), 0.0 );\nfloat pointDiffuseWeightHalf = max( 0.5 * dot( normal, pointVector ) + 0.5, 0.0 );\nvec3 pointDiffuseWeight = mix( vec3 ( pointDiffuseWeightFull ), vec3( pointDiffuseWeightHalf ), wrapRGB );\n#else\nfloat pointDiffuseWeight = max( dot( normal, pointVector ), 0.0 );\n#endif\npointDiffuse += pointDistance * pointLightColor[ i ] * uDiffuseColor * pointDiffuseWeight;\nvec3 pointHalfVector = normalize( pointVector + viewPosition );\nfloat pointDotNormalHalf = max( dot( normal, pointHalfVector ), 0.0 );\nfloat pointSpecularWeight = specularTex.r * max( pow( pointDotNormalHalf, uShininess ), 0.0 );\n#ifdef PHYSICALLY_BASED_SHADING\nfloat specularNormalization = ( uShininess + 2.0001 ) / 8.0;\nvec3 schlick = uSpecularColor + vec3( 1.0 - uSpecularColor ) * pow( 1.0 - dot( pointVector, pointHalfVector ), 5.0 );\npointSpecular += schlick * pointLightColor[ i ] * pointSpecularWeight * pointDiffuseWeight * pointDistance * specularNormalization;\n#else\npointSpecular += pointDistance * pointLightColor[ i ] * uSpecularColor * pointSpecularWeight * pointDiffuseWeight;\n#endif\n}\n#endif\n#if MAX_SPOT_LIGHTS > 0\nvec3 spotDiffuse = vec3( 0.0 );\nvec3 spotSpecular = vec3( 0.0 );\nfor ( int i = 0; i < MAX_SPOT_LIGHTS; i ++ ) {\nvec4 lPosition = viewMatrix * vec4( spotLightPosition[ i ], 1.0 );\nvec3 spotVector = lPosition.xyz + vViewPosition.xyz;\nfloat spotDistance = 1.0;\nif ( spotLightDistance[ i ] > 0.0 )\nspotDistance = 1.0 - min( ( length( spotVector ) / spotLightDistance[ i ] ), 1.0 );\nspotVector = normalize( spotVector );\nfloat spotEffect = dot( spotLightDirection[ i ], normalize( spotLightPosition[ i ] - vWorldPosition ) );\nif ( spotEffect > spotLightAngleCos[ i ] ) {\nspotEffect = max( pow( spotEffect, spotLightExponent[ i ] ), 0.0 );\n#ifdef WRAP_AROUND\nfloat spotDiffuseWeightFull = max( dot( normal, spotVector ), 0.0 );\nfloat spotDiffuseWeightHalf = max( 0.5 * dot( normal, spotVector ) + 0.5, 0.0 );\nvec3 spotDiffuseWeight = mix( vec3 ( spotDiffuseWeightFull ), vec3( spotDiffuseWeightHalf ), wrapRGB );\n#else\nfloat spotDiffuseWeight = max( dot( normal, spotVector ), 0.0 );\n#endif\nspotDiffuse += spotDistance * spotLightColor[ i ] * uDiffuseColor * spotDiffuseWeight * spotEffect;\nvec3 spotHalfVector = normalize( spotVector + viewPosition );\nfloat spotDotNormalHalf = max( dot( normal, spotHalfVector ), 0.0 );\nfloat spotSpecularWeight = specularTex.r * max( pow( spotDotNormalHalf, uShininess ), 0.0 );\n#ifdef PHYSICALLY_BASED_SHADING\nfloat specularNormalization = ( uShininess + 2.0001 ) / 8.0;\nvec3 schlick = uSpecularColor + vec3( 1.0 - uSpecularColor ) * pow( 1.0 - dot( spotVector, spotHalfVector ), 5.0 );\nspotSpecular += schlick * spotLightColor[ i ] * spotSpecularWeight * spotDiffuseWeight * spotDistance * specularNormalization * spotEffect;\n#else\nspotSpecular += spotDistance * spotLightColor[ i ] * uSpecularColor * spotSpecularWeight * spotDiffuseWeight * spotEffect;\n#endif\n}\n}\n#endif\n#if MAX_DIR_LIGHTS > 0\nvec3 dirDiffuse = vec3( 0.0 );\nvec3 dirSpecular = vec3( 0.0 );\nfor( int i = 0; i < MAX_DIR_LIGHTS; i++ ) {\nvec4 lDirection = viewMatrix * vec4( directionalLightDirection[ i ], 0.0 );\nvec3 dirVector = normalize( lDirection.xyz );\n#ifdef WRAP_AROUND\nfloat directionalLightWeightingFull = max( dot( normal, dirVector ), 0.0 );\nfloat directionalLightWeightingHalf = max( 0.5 * dot( normal, dirVector ) + 0.5, 0.0 );\nvec3 dirDiffuseWeight = mix( vec3( directionalLightWeightingFull ), vec3( directionalLightWeightingHalf ), wrapRGB );\n#else\nfloat dirDiffuseWeight = max( dot( normal, dirVector ), 0.0 );\n#endif\ndirDiffuse += directionalLightColor[ i ] * uDiffuseColor * dirDiffuseWeight;\nvec3 dirHalfVector = normalize( dirVector + viewPosition );\nfloat dirDotNormalHalf = max( dot( normal, dirHalfVector ), 0.0 );\nfloat dirSpecularWeight = specularTex.r * max( pow( dirDotNormalHalf, uShininess ), 0.0 );\n#ifdef PHYSICALLY_BASED_SHADING\nfloat specularNormalization = ( uShininess + 2.0001 ) / 8.0;\nvec3 schlick = uSpecularColor + vec3( 1.0 - uSpecularColor ) * pow( 1.0 - dot( dirVector, dirHalfVector ), 5.0 );\ndirSpecular += schlick * directionalLightColor[ i ] * dirSpecularWeight * dirDiffuseWeight * specularNormalization;\n#else\ndirSpecular += directionalLightColor[ i ] * uSpecularColor * dirSpecularWeight * dirDiffuseWeight;\n#endif\n}\n#endif\n#if MAX_HEMI_LIGHTS > 0\nvec3 hemiDiffuse = vec3( 0.0 );\nvec3 hemiSpecular = vec3( 0.0 );\nfor( int i = 0; i < MAX_HEMI_LIGHTS; i ++ ) {\nvec4 lDirection = viewMatrix * vec4( hemisphereLightDirection[ i ], 0.0 );\nvec3 lVector = normalize( lDirection.xyz );\nfloat dotProduct = dot( normal, lVector );\nfloat hemiDiffuseWeight = 0.5 * dotProduct + 0.5;\nvec3 hemiColor = mix( hemisphereLightGroundColor[ i ], hemisphereLightSkyColor[ i ], hemiDiffuseWeight );\nhemiDiffuse += uDiffuseColor * hemiColor;\nvec3 hemiHalfVectorSky = normalize( lVector + viewPosition );\nfloat hemiDotNormalHalfSky = 0.5 * dot( normal, hemiHalfVectorSky ) + 0.5;\nfloat hemiSpecularWeightSky = specularTex.r * max( pow( hemiDotNormalHalfSky, uShininess ), 0.0 );\nvec3 lVectorGround = -lVector;\nvec3 hemiHalfVectorGround = normalize( lVectorGround + viewPosition );\nfloat hemiDotNormalHalfGround = 0.5 * dot( normal, hemiHalfVectorGround ) + 0.5;\nfloat hemiSpecularWeightGround = specularTex.r * max( pow( hemiDotNormalHalfGround, uShininess ), 0.0 );\n#ifdef PHYSICALLY_BASED_SHADING\nfloat dotProductGround = dot( normal, lVectorGround );\nfloat specularNormalization = ( uShininess + 2.0001 ) / 8.0;\nvec3 schlickSky = uSpecularColor + vec3( 1.0 - uSpecularColor ) * pow( 1.0 - dot( lVector, hemiHalfVectorSky ), 5.0 );\nvec3 schlickGround = uSpecularColor + vec3( 1.0 - uSpecularColor ) * pow( 1.0 - dot( lVectorGround, hemiHalfVectorGround ), 5.0 );\nhemiSpecular += hemiColor * specularNormalization * ( schlickSky * hemiSpecularWeightSky * max( dotProduct, 0.0 ) + schlickGround * hemiSpecularWeightGround * max( dotProductGround, 0.0 ) );\n#else\nhemiSpecular += uSpecularColor * hemiColor * ( hemiSpecularWeightSky + hemiSpecularWeightGround ) * hemiDiffuseWeight;\n#endif\n}\n#endif\nvec3 totalDiffuse = vec3( 0.0 );\nvec3 totalSpecular = vec3( 0.0 );\n#if MAX_DIR_LIGHTS > 0\ntotalDiffuse += dirDiffuse;\ntotalSpecular += dirSpecular;\n#endif\n#if MAX_HEMI_LIGHTS > 0\ntotalDiffuse += hemiDiffuse;\ntotalSpecular += hemiSpecular;\n#endif\n#if MAX_POINT_LIGHTS > 0\ntotalDiffuse += pointDiffuse;\ntotalSpecular += pointSpecular;\n#endif\n#if MAX_SPOT_LIGHTS > 0\ntotalDiffuse += spotDiffuse;\ntotalSpecular += spotSpecular;\n#endif\n#ifdef METAL\ngl_FragColor.xyz = gl_FragColor.xyz * ( totalDiffuse + ambientLightColor * uAmbientColor + totalSpecular );\n#else\ngl_FragColor.xyz = gl_FragColor.xyz * ( totalDiffuse + ambientLightColor * uAmbientColor ) + totalSpecular;\n#endif\nif ( enableReflection ) {\nvec3 vReflect;\nvec3 cameraToVertex = normalize( vWorldPosition - cameraPosition );\nif ( useRefract ) {\nvReflect = refract( cameraToVertex, normal, uRefractionRatio );\n} else {\nvReflect = reflect( cameraToVertex, normal );\n}\nvec4 cubeColor = textureCube( tCube, vec3( -vReflect.x, vReflect.yz ) );\n#ifdef GAMMA_INPUT\ncubeColor.xyz *= cubeColor.xyz;\n#endif\ngl_FragColor.xyz = mix( gl_FragColor.xyz, cubeColor.xyz, specularTex.r * uReflectivity );\n}", +THREE.ShaderChunk.shadowmap_fragment,THREE.ShaderChunk.linear_to_gamma_fragment,THREE.ShaderChunk.fog_fragment,"}"].join("\n"),vertexShader:["attribute vec4 tangent;\nuniform vec2 uOffset;\nuniform vec2 uRepeat;\nuniform bool enableDisplacement;\n#ifdef VERTEX_TEXTURES\nuniform sampler2D tDisplacement;\nuniform float uDisplacementScale;\nuniform float uDisplacementBias;\n#endif\nvarying vec3 vTangent;\nvarying vec3 vBinormal;\nvarying vec3 vNormal;\nvarying vec2 vUv;\nvarying vec3 vWorldPosition;\nvarying vec3 vViewPosition;", +THREE.ShaderChunk.skinning_pars_vertex,THREE.ShaderChunk.shadowmap_pars_vertex,"void main() {",THREE.ShaderChunk.skinbase_vertex,THREE.ShaderChunk.skinnormal_vertex,"#ifdef USE_SKINNING\nvNormal = normalize( normalMatrix * skinnedNormal.xyz );\nvec4 skinnedTangent = skinMatrix * vec4( tangent.xyz, 0.0 );\nvTangent = normalize( normalMatrix * skinnedTangent.xyz );\n#else\nvNormal = normalize( normalMatrix * normal );\nvTangent = normalize( normalMatrix * tangent.xyz );\n#endif\nvBinormal = normalize( cross( vNormal, vTangent ) * tangent.w );\nvUv = uv * uRepeat + uOffset;\nvec3 displacedPosition;\n#ifdef VERTEX_TEXTURES\nif ( enableDisplacement ) {\nvec3 dv = texture2D( tDisplacement, uv ).xyz;\nfloat df = uDisplacementScale * dv.x + uDisplacementBias;\ndisplacedPosition = position + normalize( normal ) * df;\n} else {\n#ifdef USE_SKINNING\nvec4 skinVertex = vec4( position, 1.0 );\nvec4 skinned = boneMatX * skinVertex * skinWeight.x;\nskinned \t += boneMatY * skinVertex * skinWeight.y;\ndisplacedPosition = skinned.xyz;\n#else\ndisplacedPosition = position;\n#endif\n}\n#else\n#ifdef USE_SKINNING\nvec4 skinVertex = vec4( position, 1.0 );\nvec4 skinned = boneMatX * skinVertex * skinWeight.x;\nskinned \t += boneMatY * skinVertex * skinWeight.y;\ndisplacedPosition = skinned.xyz;\n#else\ndisplacedPosition = position;\n#endif\n#endif\nvec4 mvPosition = modelViewMatrix * vec4( displacedPosition, 1.0 );\nvec4 worldPosition = modelMatrix * vec4( displacedPosition, 1.0 );\ngl_Position = projectionMatrix * mvPosition;\nvWorldPosition = worldPosition.xyz;\nvViewPosition = -mvPosition.xyz;\n#ifdef USE_SHADOWMAP\nfor( int i = 0; i < MAX_SHADOWS; i ++ ) {\nvShadowCoord[ i ] = shadowMatrix[ i ] * worldPosition;\n}\n#endif\n}"].join("\n")}, +cube:{uniforms:{tCube:{type:"t",value:null},tFlip:{type:"f",value:-1}},vertexShader:"varying vec3 vWorldPosition;\nvoid main() {\nvec4 worldPosition = modelMatrix * vec4( position, 1.0 );\nvWorldPosition = worldPosition.xyz;\ngl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );\n}",fragmentShader:"uniform samplerCube tCube;\nuniform float tFlip;\nvarying vec3 vWorldPosition;\nvoid main() {\ngl_FragColor = textureCube( tCube, vec3( tFlip * vWorldPosition.x, vWorldPosition.yz ) );\n}"}, +depthRGBA:{uniforms:{},vertexShader:[THREE.ShaderChunk.morphtarget_pars_vertex,THREE.ShaderChunk.skinning_pars_vertex,"void main() {",THREE.ShaderChunk.skinbase_vertex,THREE.ShaderChunk.morphtarget_vertex,THREE.ShaderChunk.skinning_vertex,THREE.ShaderChunk.default_vertex,"}"].join("\n"),fragmentShader:"vec4 pack_depth( const in float depth ) {\nconst vec4 bit_shift = vec4( 256.0 * 256.0 * 256.0, 256.0 * 256.0, 256.0, 1.0 );\nconst vec4 bit_mask = vec4( 0.0, 1.0 / 256.0, 1.0 / 256.0, 1.0 / 256.0 );\nvec4 res = fract( depth * bit_shift );\nres -= res.xxyz * bit_mask;\nreturn res;\n}\nvoid main() {\ngl_FragData[ 0 ] = pack_depth( gl_FragCoord.z );\n}"}};THREE.WebGLRenderer=function(a){function b(a){if(a.__webglCustomAttributesList)for(var b in a.__webglCustomAttributesList)j.deleteBuffer(a.__webglCustomAttributesList[b].buffer)}function c(a,b){var c=a.vertices.length,d=b.material;if(d.attributes){void 0===a.__webglCustomAttributesList&&(a.__webglCustomAttributesList=[]);for(var e in d.attributes){var f=d.attributes[e];if(!f.__webglInitialized||f.createUniqueBuffers){f.__webglInitialized=!0;var g=1;"v2"===f.type?g=2:"v3"===f.type?g=3:"v4"===f.type? +g=4:"c"===f.type&&(g=3);f.size=g;f.array=new Float32Array(c*g);f.buffer=j.createBuffer();f.buffer.belongsToAttribute=e;f.needsUpdate=!0}a.__webglCustomAttributesList.push(f)}}}function d(a,b){var c=b.geometry,d=a.faces3,h=a.faces4,i=3*d.length+4*h.length,k=1*d.length+2*h.length,h=3*d.length+4*h.length,d=e(b,a),n=g(d),l=f(d),m=d.vertexColors?d.vertexColors:!1;a.__vertexArray=new Float32Array(3*i);l&&(a.__normalArray=new Float32Array(3*i));c.hasTangents&&(a.__tangentArray=new Float32Array(4*i));m&& +(a.__colorArray=new Float32Array(3*i));if(n){if(0l;l++)N.autoScaleCubemaps&&!f?(m=k,r=l,t=c.image[l],y=gd,t.width<=y&&t.height<=y||(z=Math.max(t.width,t.height),v=Math.floor(t.width*y/z),y=Math.floor(t.height*y/z),z=document.createElement("canvas"),z.width=v,z.height=y,z.getContext("2d").drawImage(t,0,0,t.width,t.height,0,0,v,y),t=z),m[r]=t):k[l]=c.image[l];l=k[0];m=0===(l.width&l.width-1)&&0===(l.height&l.height-1);r=L(c.format); +t=L(c.type);W(j.TEXTURE_CUBE_MAP,c,m);for(l=0;6>l;l++)if(f){y=k[l].mipmaps;z=0;for(B=y.length;z=Mc&&console.warn("WebGLRenderer: trying to use "+ +a+" texture units while this GPU supports only "+Mc);Y+=1;return a}function C(a,b){a._modelViewMatrix.multiplyMatrices(b.matrixWorldInverse,a.matrixWorld);a._normalMatrix.getInverse(a._modelViewMatrix);a._normalMatrix.transpose()}function x(a,b,c,d){a[b]=c.r*c.r*d;a[b+1]=c.g*c.g*d;a[b+2]=c.b*c.b*d}function G(a,b,c,d){a[b]=c.r*d;a[b+1]=c.g*d;a[b+2]=c.b*d}function J(a){a!==ub&&(j.lineWidth(a),ub=a)}function E(a,b,c){ab!==a&&(a?j.enable(j.POLYGON_OFFSET_FILL):j.disable(j.POLYGON_OFFSET_FILL),ab=a);if(a&& +(Fa!==b||Xa!==c))j.polygonOffset(b,c),Fa=b,Xa=c}function H(a){for(var a=a.split("\n"),b=0,c=a.length;bb;b++)j.deleteFramebuffer(a.__webglFramebuffer[b]),j.deleteRenderbuffer(a.__webglRenderbuffer[b]); +else j.deleteFramebuffer(a.__webglFramebuffer),j.deleteRenderbuffer(a.__webglRenderbuffer);N.info.memory.textures--},X=function(a){a=a.target;a.removeEventListener("dispose",X);Pc(a)},Pc=function(a){var b=a.program;if(void 0!==b){a.program=void 0;var c,d,e=!1,a=0;for(c=Ma.length;ad.numSupportedMorphTargets?(i.sort(n),i.length=d.numSupportedMorphTargets):i.length>d.numSupportedMorphNormals?i.sort(n):0===i.length&&i.push([0,0]);for(h=0;hxa;xa++)nc=ab[xa],Fb[kb]=nc.x,Fb[kb+1]=nc.y,Fb[kb+2]=nc.z,kb+=3;else for(xa=0;3>xa;xa++)Fb[kb]=Xa.x,Fb[kb+1]=Xa.y,Fb[kb+2]=Xa.z,kb+=3;D=0;for(V=va.length;Dxa;xa++)nc= +ab[xa],Fb[kb]=nc.x,Fb[kb+1]=nc.y,Fb[kb+2]=nc.z,kb+=3;else for(xa=0;4>xa;xa++)Fb[kb]=Xa.x,Fb[kb+1]=Xa.y,Fb[kb+2]=Xa.z,kb+=3;j.bindBuffer(j.ARRAY_BUFFER,sa.__webglNormalBuffer);j.bufferData(j.ARRAY_BUFFER,Fb,Pa)}if(Cc&&pd&&bb){D=0;for(V=ua.length;Dxa;xa++)Ab=pb[xa],Dc[ic]=Ab.x,Dc[ic+1]=Ab.y,ic+=2;D=0;for(V=va.length;Dxa;xa++)Ab=pb[xa],Dc[ic]=Ab.x,Dc[ic+1]=Ab.y,ic+=2;0xa;xa++)yc=ub[xa],Ec[jc]=yc.x,Ec[jc+1]=yc.y,jc+=2;D=0;for(V=va.length;Dxa;xa++)yc=ub[xa],Ec[jc]=yc.x,Ec[jc+1]=yc.y,jc+=2;0f;f++){a.__webglFramebuffer[f]=j.createFramebuffer();a.__webglRenderbuffer[f]=j.createRenderbuffer();j.texImage2D(j.TEXTURE_CUBE_MAP_POSITIVE_X+f,0,d,a.width,a.height,0,d,e,null);var g=a,i=j.TEXTURE_CUBE_MAP_POSITIVE_X+f;j.bindFramebuffer(j.FRAMEBUFFER, +a.__webglFramebuffer[f]);j.framebufferTexture2D(j.FRAMEBUFFER,j.COLOR_ATTACHMENT0,i,g.__webglTexture,0);F(a.__webglRenderbuffer[f],a)}c&&j.generateMipmap(j.TEXTURE_CUBE_MAP)}else a.__webglFramebuffer=j.createFramebuffer(),a.__webglRenderbuffer=a.shareDepthFrom?a.shareDepthFrom.__webglRenderbuffer:j.createRenderbuffer(),j.bindTexture(j.TEXTURE_2D,a.__webglTexture),W(j.TEXTURE_2D,a,c),j.texImage2D(j.TEXTURE_2D,0,d,a.width,a.height,0,d,e,null),d=j.TEXTURE_2D,j.bindFramebuffer(j.FRAMEBUFFER,a.__webglFramebuffer), +j.framebufferTexture2D(j.FRAMEBUFFER,j.COLOR_ATTACHMENT0,d,a.__webglTexture,0),a.shareDepthFrom?a.depthBuffer&&!a.stencilBuffer?j.framebufferRenderbuffer(j.FRAMEBUFFER,j.DEPTH_ATTACHMENT,j.RENDERBUFFER,a.__webglRenderbuffer):a.depthBuffer&&a.stencilBuffer&&j.framebufferRenderbuffer(j.FRAMEBUFFER,j.DEPTH_STENCIL_ATTACHMENT,j.RENDERBUFFER,a.__webglRenderbuffer):F(a.__webglRenderbuffer,a),c&&j.generateMipmap(j.TEXTURE_2D);b?j.bindTexture(j.TEXTURE_CUBE_MAP,null):j.bindTexture(j.TEXTURE_2D,null);j.bindRenderbuffer(j.RENDERBUFFER, +null);j.bindFramebuffer(j.FRAMEBUFFER,null)}a?(b=b?a.__webglFramebuffer[a.activeCubeFace]:a.__webglFramebuffer,c=a.width,a=a.height,e=d=0):(b=null,c=fc,a=Ab,d=Ib,e=Jb);b!==Pa&&(j.bindFramebuffer(j.FRAMEBUFFER,b),j.viewport(d,e,c,a),Pa=b);mc=c;pb=a};this.shadowMapPlugin=new THREE.ShadowMapPlugin;this.addPrePlugin(this.shadowMapPlugin);this.addPostPlugin(new THREE.SpritePlugin);this.addPostPlugin(new THREE.LensFlarePlugin)};THREE.WebGLRenderTarget=function(a,b,c){THREE.EventDispatcher.call(this);this.width=a;this.height=b;c=c||{};this.wrapS=void 0!==c.wrapS?c.wrapS:THREE.ClampToEdgeWrapping;this.wrapT=void 0!==c.wrapT?c.wrapT:THREE.ClampToEdgeWrapping;this.magFilter=void 0!==c.magFilter?c.magFilter:THREE.LinearFilter;this.minFilter=void 0!==c.minFilter?c.minFilter:THREE.LinearMipMapLinearFilter;this.anisotropy=void 0!==c.anisotropy?c.anisotropy:1;this.offset=new THREE.Vector2(0,0);this.repeat=new THREE.Vector2(1,1); +this.format=void 0!==c.format?c.format:THREE.RGBAFormat;this.type=void 0!==c.type?c.type:THREE.UnsignedByteType;this.depthBuffer=void 0!==c.depthBuffer?c.depthBuffer:!0;this.stencilBuffer=void 0!==c.stencilBuffer?c.stencilBuffer:!0;this.generateMipmaps=!0;this.shareDepthFrom=null}; +THREE.WebGLRenderTarget.prototype.clone=function(){var a=new THREE.WebGLRenderTarget(this.width,this.height);a.wrapS=this.wrapS;a.wrapT=this.wrapT;a.magFilter=this.magFilter;a.minFilter=this.minFilter;a.anisotropy=this.anisotropy;a.offset.copy(this.offset);a.repeat.copy(this.repeat);a.format=this.format;a.type=this.type;a.depthBuffer=this.depthBuffer;a.stencilBuffer=this.stencilBuffer;a.generateMipmaps=this.generateMipmaps;a.shareDepthFrom=this.shareDepthFrom;return a}; +THREE.WebGLRenderTarget.prototype.dispose=function(){this.dispatchEvent({type:"dispose"})};THREE.WebGLRenderTargetCube=function(a,b,c){THREE.WebGLRenderTarget.call(this,a,b,c);this.activeCubeFace=0};THREE.WebGLRenderTargetCube.prototype=Object.create(THREE.WebGLRenderTarget.prototype);THREE.RenderableVertex=function(){this.positionWorld=new THREE.Vector3;this.positionScreen=new THREE.Vector4;this.visible=!0};THREE.RenderableVertex.prototype.copy=function(a){this.positionWorld.copy(a.positionWorld);this.positionScreen.copy(a.positionScreen)};THREE.RenderableFace3=function(){this.v1=new THREE.RenderableVertex;this.v2=new THREE.RenderableVertex;this.v3=new THREE.RenderableVertex;this.centroidModel=new THREE.Vector3;this.normalModel=new THREE.Vector3;this.normalModelView=new THREE.Vector3;this.vertexNormalsLength=0;this.vertexNormalsModel=[new THREE.Vector3,new THREE.Vector3,new THREE.Vector3];this.vertexNormalsModelView=[new THREE.Vector3,new THREE.Vector3,new THREE.Vector3];this.material=this.color=null;this.uvs=[[]];this.z=null};THREE.RenderableFace4=function(){this.v1=new THREE.RenderableVertex;this.v2=new THREE.RenderableVertex;this.v3=new THREE.RenderableVertex;this.v4=new THREE.RenderableVertex;this.centroidModel=new THREE.Vector3;this.normalModel=new THREE.Vector3;this.normalModelView=new THREE.Vector3;this.vertexNormalsLength=0;this.vertexNormalsModel=[new THREE.Vector3,new THREE.Vector3,new THREE.Vector3,new THREE.Vector3];this.vertexNormalsModelView=[new THREE.Vector3,new THREE.Vector3,new THREE.Vector3,new THREE.Vector3]; +this.material=this.color=null;this.uvs=[[]];this.z=null};THREE.RenderableObject=function(){this.z=this.object=null};THREE.RenderableParticle=function(){this.rotation=this.z=this.y=this.x=this.object=null;this.scale=new THREE.Vector2;this.material=null};THREE.RenderableLine=function(){this.z=null;this.v1=new THREE.RenderableVertex;this.v2=new THREE.RenderableVertex;this.material=null};THREE.GeometryUtils={merge:function(a,b){var c,d,e=a.vertices.length,f=b instanceof THREE.Mesh?b.geometry:b,g=a.vertices,h=f.vertices,i=a.faces,k=f.faces,l=a.faceVertexUvs[0],f=f.faceVertexUvs[0];b instanceof THREE.Mesh&&(b.matrixAutoUpdate&&b.updateMatrix(),c=b.matrix,d=new THREE.Matrix3,d.getInverse(c),d.transpose());for(var m=0,n=h.length;ma?b(c,e-1):k[e]>8&255,i>>16&255,i>>24&255)),d}d.mipmapCount=1;h[2]&131072&&!1!==b&&(d.mipmapCount=Math.max(1,h[7]));d.isCubemap=h[28]&512?!0:!1;d.width=h[4];d.height=h[3];for(var h=h[1]+4,f=d.width,g=d.height,i=d.isCubemap? +6:1,k=0;km-1?0:m-1,s=m+1>e-1?e-1:m+1,r=0>l-1?0:l-1,p=l+1>d-1?d-1:l+1,q=[],y=[0,0,h[4*(m*d+l)]/255*b];q.push([-1,0,h[4*(m*d+r)]/255*b]);q.push([-1,-1,h[4*(n*d+r)]/255*b]);q.push([0,-1,h[4*(n*d+l)]/255*b]);q.push([1,-1,h[4*(n*d+p)]/255*b]);q.push([1,0,h[4*(m*d+p)]/255*b]);q.push([1,1,h[4*(s*d+p)]/255*b]);q.push([0,1,h[4*(s*d+l)]/255*b]);q.push([-1,1,h[4*(s*d+r)]/255*b]);n=[];r=q.length;for(s=0;se)return null;var f=[],g=[],h=[],i,k,l;if(0=m--){console.log("Warning, unable to triangulate polygon!");break}i=k;e<=i&&(i=0);k=i+1;e<=k&&(k=0);l=k+1;e<=l&&(l=0);var n;a:{var s=n=void 0,r=void 0,p=void 0,q=void 0,y=void 0,v=void 0,z=void 0,t= +void 0,s=a[g[i]].x,r=a[g[i]].y,p=a[g[k]].x,q=a[g[k]].y,y=a[g[l]].x,v=a[g[l]].y;if(1E-10>(p-s)*(v-r)-(q-r)*(y-s))n=!1;else{var A=void 0,I=void 0,C=void 0,x=void 0,G=void 0,J=void 0,E=void 0,H=void 0,B=void 0,W=void 0,B=H=E=t=z=void 0,A=y-p,I=v-q,C=s-y,x=r-v,G=p-s,J=q-r;for(n=0;ni)g=d+1;else if(0b&&(b=0);1d.length-2?d.length-1:a+1;c[3]=a>d.length-3?d.length-1:a+2;b.x=THREE.Curve.Utils.interpolate(d[c[0]].x,d[c[1]].x,d[c[2]].x,d[c[3]].x,e);b.y=THREE.Curve.Utils.interpolate(d[c[0]].y,d[c[1]].y,d[c[2]].y,d[c[3]].y,e);return b}; +THREE.EllipseCurve=function(a,b,c,d,e,f,g){this.aX=a;this.aY=b;this.xRadius=c;this.yRadius=d;this.aStartAngle=e;this.aEndAngle=f;this.aClockwise=g};THREE.EllipseCurve.prototype=Object.create(THREE.Curve.prototype);THREE.EllipseCurve.prototype.getPoint=function(a){var b=this.aEndAngle-this.aStartAngle;this.aClockwise||(a=1-a);b=this.aStartAngle+a*b;a=this.aX+this.xRadius*Math.cos(b);b=this.aY+this.yRadius*Math.sin(b);return new THREE.Vector2(a,b)}; +THREE.ArcCurve=function(a,b,c,d,e,f){THREE.EllipseCurve.call(this,a,b,c,c,d,e,f)};THREE.ArcCurve.prototype=Object.create(THREE.EllipseCurve.prototype); +THREE.Curve.Utils={tangentQuadraticBezier:function(a,b,c,d){return 2*(1-a)*(c-b)+2*a*(d-c)},tangentCubicBezier:function(a,b,c,d,e){return-3*b*(1-a)*(1-a)+3*c*(1-a)*(1-a)-6*a*c*(1-a)+6*a*d*(1-a)-3*a*a*d+3*a*a*e},tangentSpline:function(a){return 6*a*a-6*a+(3*a*a-4*a+1)+(-6*a*a+6*a)+(3*a*a-2*a)},interpolate:function(a,b,c,d,e){var a=0.5*(c-a),d=0.5*(d-b),f=e*e;return(2*b-2*c+a+d)*e*f+(-3*b+3*c-2*a-d)*f+a*e+b}}; +THREE.Curve.create=function(a,b){a.prototype=Object.create(THREE.Curve.prototype);a.prototype.getPoint=b;return a};THREE.LineCurve3=THREE.Curve.create(function(a,b){this.v1=a;this.v2=b},function(a){var b=new THREE.Vector3;b.subVectors(this.v2,this.v1);b.multiplyScalar(a);b.add(this.v1);return b}); +THREE.QuadraticBezierCurve3=THREE.Curve.create(function(a,b,c){this.v0=a;this.v1=b;this.v2=c},function(a){var b,c;b=THREE.Shape.Utils.b2(a,this.v0.x,this.v1.x,this.v2.x);c=THREE.Shape.Utils.b2(a,this.v0.y,this.v1.y,this.v2.y);a=THREE.Shape.Utils.b2(a,this.v0.z,this.v1.z,this.v2.z);return new THREE.Vector3(b,c,a)}); +THREE.CubicBezierCurve3=THREE.Curve.create(function(a,b,c,d){this.v0=a;this.v1=b;this.v2=c;this.v3=d},function(a){var b,c;b=THREE.Shape.Utils.b3(a,this.v0.x,this.v1.x,this.v2.x,this.v3.x);c=THREE.Shape.Utils.b3(a,this.v0.y,this.v1.y,this.v2.y,this.v3.y);a=THREE.Shape.Utils.b3(a,this.v0.z,this.v1.z,this.v2.z,this.v3.z);return new THREE.Vector3(b,c,a)}); +THREE.SplineCurve3=THREE.Curve.create(function(a){this.points=void 0==a?[]:a},function(a){var b=new THREE.Vector3,c=[],d=this.points,e,a=(d.length-1)*a;e=Math.floor(a);a-=e;c[0]=0==e?e:e-1;c[1]=e;c[2]=e>d.length-2?d.length-1:e+1;c[3]=e>d.length-3?d.length-1:e+2;e=d[c[0]];var f=d[c[1]],g=d[c[2]],c=d[c[3]];b.x=THREE.Curve.Utils.interpolate(e.x,f.x,g.x,c.x,a);b.y=THREE.Curve.Utils.interpolate(e.y,f.y,g.y,c.y,a);b.z=THREE.Curve.Utils.interpolate(e.z,f.z,g.z,c.z,a);return b}); +THREE.ClosedSplineCurve3=THREE.Curve.create(function(a){this.points=void 0==a?[]:a},function(a){var b=new THREE.Vector3,c=[],d=this.points,e;e=(d.length-0)*a;a=Math.floor(e);e-=a;a+=0=b)return b=c[a]-b,a=this.curves[a],b=1-b/a.getLength(),a.getPointAt(b);a++}return null};THREE.CurvePath.prototype.getLength=function(){var a=this.getCurveLengths();return a[a.length-1]}; +THREE.CurvePath.prototype.getCurveLengths=function(){if(this.cacheLengths&&this.cacheLengths.length==this.curves.length)return this.cacheLengths;var a=[],b=0,c,d=this.curves.length;for(c=0;cb?b=h.x:h.xc?c=h.y:h.yd?d=h.z:h.zMath.abs(d.x-c[0].x)&&1E-10>Math.abs(d.y-c[0].y)&&c.splice(c.length-1,1);b&&c.push(c[0]);return c}; +THREE.Path.prototype.toShapes=function(){var a,b,c,d,e=[],f=new THREE.Path;a=0;for(b=this.actions.length;a +h&&(h+=c.length);h%=c.length;0>g&&(g+=k.length);g%=k.length;e=0<=h-1?h-1:c.length-1;f=0<=g-1?g-1:k.length-1;p=[k[g],c[h],c[e]];p=THREE.FontUtils.Triangulate.area(p);q=[k[g],k[f],c[h]];q=THREE.FontUtils.Triangulate.area(q);m+n>p+q&&(h=s,g=l,0>h&&(h+=c.length),h%=c.length,0>g&&(g+=k.length),g%=k.length,e=0<=h-1?h-1:c.length-1,f=0<=g-1?g-1:k.length-1);m=c.slice(0,h);n=c.slice(h);s=k.slice(g);l=k.slice(0,g);f=[k[g],k[f],c[h]];r.push([k[g],c[h],c[e]]);r.push(f);c=m.concat(s).concat(l).concat(n)}return{shape:c, +isolatedPts:r,allpoints:d}},triangulateShape:function(a,b){var c=THREE.Shape.Utils.removeHoles(a,b),d=c.allpoints,e=c.isolatedPts,c=THREE.FontUtils.Triangulate(c.shape,!1),f,g,h,i,k={};f=0;for(g=d.length;fd;d++)i=h[d].x+":"+h[d].y,i=k[i],void 0!==i&&(h[d]=i)}f=0;for(g=e.length;fd;d++)i=h[d].x+":"+h[d].y,i=k[i],void 0!==i&&(h[d]=i)}return c.concat(e)}, +isClockWise:function(a){return 0>THREE.FontUtils.Triangulate.area(a)},b2p0:function(a,b){var c=1-a;return c*c*b},b2p1:function(a,b){return 2*(1-a)*a*b},b2p2:function(a,b){return a*a*b},b2:function(a,b,c,d){return this.b2p0(a,b)+this.b2p1(a,c)+this.b2p2(a,d)},b3p0:function(a,b){var c=1-a;return c*c*c*b},b3p1:function(a,b){var c=1-a;return 3*c*c*a*b},b3p2:function(a,b){return 3*(1-a)*a*a*b},b3p3:function(a,b){return a*a*a*b},b3:function(a,b,c,d,e){return this.b3p0(a,b)+this.b3p1(a,c)+this.b3p2(a,d)+ +this.b3p3(a,e)}};THREE.AnimationHandler=function(){var a=[],b={},c={update:function(b){for(var c=0;ca.hierarchy[c].keys[d].time&& +(a.hierarchy[c].keys[d].time=0),void 0!==a.hierarchy[c].keys[d].rot&&!(a.hierarchy[c].keys[d].rot instanceof THREE.Quaternion)){var h=a.hierarchy[c].keys[d].rot;a.hierarchy[c].keys[d].rot=new THREE.Quaternion(h[0],h[1],h[2],h[3])}if(a.hierarchy[c].keys.length&&void 0!==a.hierarchy[c].keys[0].morphTargets){h={};for(d=0;ds;s++){c=b[s];g=i.prevKey[c];h=i.nextKey[c];if(h.time<=l){if(kd||1d?0:1;if("pos"===c)if(c=a.position,this.interpolationType===THREE.AnimationHandler.LINEAR)c.x=e[0]+(f[0]-e[0])*d,c.y=e[1]+(f[1]-e[1])*d,c.z=e[2]+ +(f[2]-e[2])*d;else{if(this.interpolationType===THREE.AnimationHandler.CATMULLROM||this.interpolationType===THREE.AnimationHandler.CATMULLROM_FORWARD)this.points[0]=this.getPrevKeyWith("pos",m,g.index-1).pos,this.points[1]=e,this.points[2]=f,this.points[3]=this.getNextKeyWith("pos",m,h.index+1).pos,d=0.33*d+0.33,e=this.interpolateCatmullRom(this.points,d),c.x=e[0],c.y=e[1],c.z=e[2],this.interpolationType===THREE.AnimationHandler.CATMULLROM_FORWARD&&(d=this.interpolateCatmullRom(this.points,1.01*d), +this.target.set(d[0],d[1],d[2]),this.target.sub(c),this.target.y=0,this.target.normalize(),d=Math.atan2(this.target.x,this.target.z),a.rotation.set(0,d,0))}else"rot"===c?THREE.Quaternion.slerp(e,f,a.quaternion,d):"scl"===c&&(c=a.scale,c.x=e[0]+(f[0]-e[0])*d,c.y=e[1]+(f[1]-e[1])*d,c.z=e[2]+(f[2]-e[2])*d)}}}}; +THREE.Animation.prototype.interpolateCatmullRom=function(a,b){var c=[],d=[],e,f,g,h,i,k;e=(a.length-1)*b;f=Math.floor(e);e-=f;c[0]=0===f?f:f-1;c[1]=f;c[2]=f>a.length-2?f:f+1;c[3]=f>a.length-3?f:f+2;f=a[c[0]];h=a[c[1]];i=a[c[2]];k=a[c[3]];c=e*e;g=e*c;d[0]=this.interpolate(f[0],h[0],i[0],k[0],e,c,g);d[1]=this.interpolate(f[1],h[1],i[1],k[1],e,c,g);d[2]=this.interpolate(f[2],h[2],i[2],k[2],e,c,g);return d}; +THREE.Animation.prototype.interpolate=function(a,b,c,d,e,f,g){a=0.5*(c-a);d=0.5*(d-b);return(2*(b-c)+a+d)*g+(-3*(b-c)-2*a-d)*f+a*e+b};THREE.Animation.prototype.getNextKeyWith=function(a,b,c){for(var d=this.data.hierarchy[b].keys,c=this.interpolationType===THREE.AnimationHandler.CATMULLROM||this.interpolationType===THREE.AnimationHandler.CATMULLROM_FORWARD?c=g?b.interpolate(c,g):b.interpolate(c,c.time)}this.data.hierarchy[a].node.updateMatrix();d.matrixWorldNeedsUpdate=!0}}if(this.JITCompile&&void 0===f[0][e]){this.hierarchy[0].updateMatrixWorld(!0);for(a=0;ag?(b=Math.atan2(b.y-a.y,b.x-a.x),a=Math.atan2(c.y-a.y,c.x-a.x),b>a&&(a+=2*Math.PI),c=(b+a)/2,a=-Math.cos(c),c=-Math.sin(c),new THREE.Vector2(a,c)):d.multiplyScalar(g).add(i).sub(a).clone()}function e(c,d){var e,f;for(M=c.length;0<=--M;){e=M;f=M-1;0>f&&(f=c.length-1);for(var g=0,i=s+2*l, +g=0;gMath.abs(c-i)?[new THREE.Vector2(b,1-e),new THREE.Vector2(d,1-f),new THREE.Vector2(k,1-g),new THREE.Vector2(m,1-a)]:[new THREE.Vector2(c,1-e),new THREE.Vector2(i,1-f),new THREE.Vector2(l,1-g),new THREE.Vector2(n,1-a)]}};THREE.ExtrudeGeometry.__v1=new THREE.Vector2;THREE.ExtrudeGeometry.__v2=new THREE.Vector2;THREE.ExtrudeGeometry.__v3=new THREE.Vector2;THREE.ExtrudeGeometry.__v4=new THREE.Vector2; +THREE.ExtrudeGeometry.__v5=new THREE.Vector2;THREE.ExtrudeGeometry.__v6=new THREE.Vector2;THREE.ShapeGeometry=function(a,b){THREE.Geometry.call(this);!1===a instanceof Array&&(a=[a]);this.shapebb=a[a.length-1].getBoundingBox();this.addShapeList(a,b);this.computeCentroids();this.computeFaceNormals()};THREE.ShapeGeometry.prototype=Object.create(THREE.Geometry.prototype);THREE.ShapeGeometry.prototype.addShapeList=function(a,b){for(var c=0,d=a.length;cd?(d=new THREE.Face3(a.index,b.index,c.index,[a.clone(),b.clone(),c.clone()]),d.centroid.add(a).add(b).add(c).divideScalar(3),d.normal=d.centroid.clone().normalize(),i.faces.push(d),d=Math.atan2(d.centroid.z,-d.centroid.x),i.faceVertexUvs[0].push([h(a.uv, +a,d),h(b.uv,b,d),h(c.uv,c,d)])):(d-=1,f(a,g(a,b),g(a,c),d),f(g(a,b),b,g(b,c),d),f(g(a,c),g(b,c),c,d),f(g(a,b),g(b,c),g(a,c),d))}function g(a,b){m[a.index]||(m[a.index]=[]);m[b.index]||(m[b.index]=[]);var c=m[a.index][b.index];void 0===c&&(m[a.index][b.index]=m[b.index][a.index]=c=e((new THREE.Vector3).addVectors(a,b).divideScalar(2)));return c}function h(a,b,c){0>c&&1===a.x&&(a=new THREE.Vector2(a.x-1,a.y));0===b.x&&0===b.z&&(a=new THREE.Vector2(c/2/Math.PI+0.5,a.y));return a}THREE.Geometry.call(this); +for(var c=c||1,d=d||0,i=this,k=0,l=a.length;k=l){for(k=0;3>k;k++){l=[i[k],i[(k+1)%3]];m=!0;for(n=0;nh;h++)void 0===f[g[h]]&&(f[g[h]]=e++,this.vertices.push(a[g[h]])),g[h]=f[g[h]]}for(d=0;db.y?this.rotation.set(Math.PI,0,0):(a=THREE.ArrowHelper.__v2.set(b.z,0,-b.x).normalize(),b=Math.acos(b.y),a=THREE.ArrowHelper.__q1.setFromAxisAngle(a,b),this.rotation.setEulerFromQuaternion(a,this.eulerOrder))}; +THREE.ArrowHelper.prototype.setLength=function(a){this.scale.set(a,a,a)};THREE.ArrowHelper.prototype.setColor=function(a){this.line.material.color.setHex(a);this.cone.material.color.setHex(a)};THREE.ArrowHelper.__v1=new THREE.Vector3;THREE.ArrowHelper.__v2=new THREE.Vector3;THREE.ArrowHelper.__q1=new THREE.Quaternion;THREE.CameraHelper=function(a){function b(a,b,d){c(a,d);c(b,d)}function c(a,b){d.geometry.vertices.push(new THREE.Vector3);d.geometry.colors.push(new THREE.Color(b));void 0===d.pointMap[a]&&(d.pointMap[a]=[]);d.pointMap[a].push(d.geometry.vertices.length-1)}THREE.Line.call(this);var d=this;this.geometry=new THREE.Geometry;this.material=new THREE.LineBasicMaterial({color:16777215,vertexColors:THREE.FaceColors});this.type=THREE.LinePieces;this.matrixWorld=a.matrixWorld;this.matrixAutoUpdate=!1;this.pointMap= +{};b("n1","n2",16755200);b("n2","n4",16755200);b("n4","n3",16755200);b("n3","n1",16755200);b("f1","f2",16755200);b("f2","f4",16755200);b("f4","f3",16755200);b("f3","f1",16755200);b("n1","f1",16755200);b("n2","f2",16755200);b("n3","f3",16755200);b("n4","f4",16755200);b("p","n1",16711680);b("p","n2",16711680);b("p","n3",16711680);b("p","n4",16711680);b("u1","u2",43775);b("u2","u3",43775);b("u3","u1",43775);b("c","t",16777215);b("p","c",3355443);b("cn1","cn2",3355443);b("cn3","cn4",3355443);b("cf1", +"cf2",3355443);b("cf3","cf4",3355443);this.camera=a;this.update(a)};THREE.CameraHelper.prototype=Object.create(THREE.Line.prototype); +THREE.CameraHelper.prototype.update=function(){function a(a,d,e,f){THREE.CameraHelper.__v.set(d,e,f);THREE.CameraHelper.__projector.unprojectVector(THREE.CameraHelper.__v,THREE.CameraHelper.__c);a=b.pointMap[a];if(void 0!==a){d=0;for(e=a.length;dh.end&&(h.end=f);c||(c=i)}}for(i in d)h=d[i],this.createAnimation(i,h.start,h.end,a);this.firstAnimation=c}; +THREE.MorphBlendMesh.prototype.setAnimationDirectionForward=function(a){if(a=this.animationsMap[a])a.direction=1,a.directionBackwards=!1};THREE.MorphBlendMesh.prototype.setAnimationDirectionBackward=function(a){if(a=this.animationsMap[a])a.direction=-1,a.directionBackwards=!0};THREE.MorphBlendMesh.prototype.setAnimationFPS=function(a,b){var c=this.animationsMap[a];c&&(c.fps=b,c.duration=(c.end-c.start)/c.fps)}; +THREE.MorphBlendMesh.prototype.setAnimationDuration=function(a,b){var c=this.animationsMap[a];c&&(c.duration=b,c.fps=(c.end-c.start)/c.duration)};THREE.MorphBlendMesh.prototype.setAnimationWeight=function(a,b){var c=this.animationsMap[a];c&&(c.weight=b)};THREE.MorphBlendMesh.prototype.setAnimationTime=function(a,b){var c=this.animationsMap[a];c&&(c.time=b)};THREE.MorphBlendMesh.prototype.getAnimationTime=function(a){var b=0;if(a=this.animationsMap[a])b=a.time;return b}; +THREE.MorphBlendMesh.prototype.getAnimationDuration=function(a){var b=-1;if(a=this.animationsMap[a])b=a.duration;return b};THREE.MorphBlendMesh.prototype.playAnimation=function(a){var b=this.animationsMap[a];b?(b.time=0,b.active=!0):console.warn("animation["+a+"] undefined")};THREE.MorphBlendMesh.prototype.stopAnimation=function(a){if(a=this.animationsMap[a])a.active=!1}; +THREE.MorphBlendMesh.prototype.update=function(a){for(var b=0,c=this.animationsList.length;bd.duration||0>d.time)d.direction*=-1,d.time>d.duration&&(d.time=d.duration,d.directionBackwards=!0),0>d.time&&(d.time=0,d.directionBackwards=!1)}else d.time%=d.duration,0>d.time&&(d.time+=d.duration);var f=d.startFrame+THREE.Math.clamp(Math.floor(d.time/e),0,d.length-1),g=d.weight; +f!==d.currentFrame&&(this.morphTargetInfluences[d.lastFrame]=0,this.morphTargetInfluences[d.currentFrame]=1*g,this.morphTargetInfluences[f]=0,d.lastFrame=d.currentFrame,d.currentFrame=f);e=d.time%e/e;d.directionBackwards&&(e=1-e);this.morphTargetInfluences[d.currentFrame]=e*g;this.morphTargetInfluences[d.lastFrame]=(1-e)*g}}};THREE.LensFlarePlugin=function(){function a(a,c){var d=b.createProgram(),e=b.createShader(b.FRAGMENT_SHADER),f=b.createShader(b.VERTEX_SHADER),g="precision "+c+" float;\n";b.shaderSource(e,g+a.fragmentShader);b.shaderSource(f,g+a.vertexShader);b.compileShader(e);b.compileShader(f);b.attachShader(d,e);b.attachShader(d,f);b.linkProgram(d);return d}var b,c,d,e,f,g,h,i,k,l,m,n,s;this.init=function(r){b=r.context;c=r;d=r.getPrecision();e=new Float32Array(16);f=new Uint16Array(6);r=0;e[r++]=-1;e[r++]=-1; +e[r++]=0;e[r++]=0;e[r++]=1;e[r++]=-1;e[r++]=1;e[r++]=0;e[r++]=1;e[r++]=1;e[r++]=1;e[r++]=1;e[r++]=-1;e[r++]=1;e[r++]=0;e[r++]=1;r=0;f[r++]=0;f[r++]=1;f[r++]=2;f[r++]=0;f[r++]=2;f[r++]=3;g=b.createBuffer();h=b.createBuffer();b.bindBuffer(b.ARRAY_BUFFER,g);b.bufferData(b.ARRAY_BUFFER,e,b.STATIC_DRAW);b.bindBuffer(b.ELEMENT_ARRAY_BUFFER,h);b.bufferData(b.ELEMENT_ARRAY_BUFFER,f,b.STATIC_DRAW);i=b.createTexture();k=b.createTexture();b.bindTexture(b.TEXTURE_2D,i);b.texImage2D(b.TEXTURE_2D,0,b.RGB,16,16, +0,b.RGB,b.UNSIGNED_BYTE,null);b.texParameteri(b.TEXTURE_2D,b.TEXTURE_WRAP_S,b.CLAMP_TO_EDGE);b.texParameteri(b.TEXTURE_2D,b.TEXTURE_WRAP_T,b.CLAMP_TO_EDGE);b.texParameteri(b.TEXTURE_2D,b.TEXTURE_MAG_FILTER,b.NEAREST);b.texParameteri(b.TEXTURE_2D,b.TEXTURE_MIN_FILTER,b.NEAREST);b.bindTexture(b.TEXTURE_2D,k);b.texImage2D(b.TEXTURE_2D,0,b.RGBA,16,16,0,b.RGBA,b.UNSIGNED_BYTE,null);b.texParameteri(b.TEXTURE_2D,b.TEXTURE_WRAP_S,b.CLAMP_TO_EDGE);b.texParameteri(b.TEXTURE_2D,b.TEXTURE_WRAP_T,b.CLAMP_TO_EDGE); +b.texParameteri(b.TEXTURE_2D,b.TEXTURE_MAG_FILTER,b.NEAREST);b.texParameteri(b.TEXTURE_2D,b.TEXTURE_MIN_FILTER,b.NEAREST);0>=b.getParameter(b.MAX_VERTEX_TEXTURE_IMAGE_UNITS)?(l=!1,m=a(THREE.ShaderFlares.lensFlare,d)):(l=!0,m=a(THREE.ShaderFlares.lensFlareVertexTexture,d));n={};s={};n.vertex=b.getAttribLocation(m,"position");n.uv=b.getAttribLocation(m,"uv");s.renderType=b.getUniformLocation(m,"renderType");s.map=b.getUniformLocation(m,"map");s.occlusionMap=b.getUniformLocation(m,"occlusionMap");s.opacity= +b.getUniformLocation(m,"opacity");s.color=b.getUniformLocation(m,"color");s.scale=b.getUniformLocation(m,"scale");s.rotation=b.getUniformLocation(m,"rotation");s.screenPosition=b.getUniformLocation(m,"screenPosition")};this.render=function(a,d,e,f){var a=a.__webglFlares,v=a.length;if(v){var z=new THREE.Vector3,t=f/e,A=0.5*e,I=0.5*f,C=16/f,x=new THREE.Vector2(C*t,C),G=new THREE.Vector3(1,1,0),J=new THREE.Vector2(1,1),E=s,C=n;b.useProgram(m);b.enableVertexAttribArray(n.vertex);b.enableVertexAttribArray(n.uv); +b.uniform1i(E.occlusionMap,0);b.uniform1i(E.map,1);b.bindBuffer(b.ARRAY_BUFFER,g);b.vertexAttribPointer(C.vertex,2,b.FLOAT,!1,16,0);b.vertexAttribPointer(C.uv,2,b.FLOAT,!1,16,8);b.bindBuffer(b.ELEMENT_ARRAY_BUFFER,h);b.disable(b.CULL_FACE);b.depthMask(!1);var H,B,W,F,K;for(H=0;Hx;x++)t[x]=new THREE.Vector3,v[x]=new THREE.Vector3;t=A.shadowCascadeNearZ[z];A=A.shadowCascadeFarZ[z];v[0].set(-1,-1,t);v[1].set(1,-1,t);v[2].set(-1, +1,t);v[3].set(1,1,t);v[4].set(-1,-1,A);v[5].set(1,-1,A);v[6].set(-1,1,A);v[7].set(1,1,A);C.originalCamera=n;v=new THREE.Gyroscope;v.position=p.shadowCascadeOffset;v.add(C);v.add(C.target);n.add(v);p.shadowCascadeArray[y]=C;console.log("Created virtualLight",C)}z=p;t=y;A=z.shadowCascadeArray[t];A.position.copy(z.position);A.target.position.copy(z.target.position);A.lookAt(A.target);A.shadowCameraVisible=z.shadowCameraVisible;A.shadowDarkness=z.shadowDarkness;A.shadowBias=z.shadowCascadeBias[t];v=z.shadowCascadeNearZ[t]; +z=z.shadowCascadeFarZ[t];A=A.pointsFrustum;A[0].z=v;A[1].z=v;A[2].z=v;A[3].z=v;A[4].z=z;A[5].z=z;A[6].z=z;A[7].z=z;I[q]=C;q++}else I[q]=p,q++;s=0;for(r=I.length;sz;z++)t=A[z],t.copy(v[z]),THREE.ShadowMapPlugin.__projector.unprojectVector(t,y),t.applyMatrix4(q.matrixWorldInverse),t.xk.x&&(k.x=t.x),t.yk.y&&(k.y=t.y),t.zk.z&& +(k.z=t.z);q.left=i.x;q.right=k.x;q.top=k.y;q.bottom=i.y;q.updateProjectionMatrix()}q=p.shadowMap;v=p.shadowMatrix;y=p.shadowCamera;y.position.getPositionFromMatrix(p.matrixWorld);l.getPositionFromMatrix(p.target.matrixWorld);y.lookAt(l);y.updateMatrixWorld();y.matrixWorldInverse.getInverse(y.matrixWorld);p.cameraHelper&&(p.cameraHelper.visible=p.shadowCameraVisible);p.shadowCameraVisible&&p.cameraHelper.update();v.set(0.5,0,0,0.5,0,0.5,0,0.5,0,0,0.5,0.5,0,0,0,1);v.multiply(y.projectionMatrix);v.multiply(y.matrixWorldInverse); +h.multiplyMatrices(y.projectionMatrix,y.matrixWorldInverse);g.setFromMatrix(h);b.setRenderTarget(q);b.clear();A=m.__webglObjects;p=0;for(q=A.length;p 0 ) {\nfloat depth = gl_FragCoord.z / gl_FragCoord.w;\nfloat fogFactor = 0.0;\nif ( fogType == 1 ) {\nfogFactor = smoothstep( fogNear, fogFar, depth );\n} else {\nconst float LOG2 = 1.442695;\nfloat fogFactor = exp2( - fogDensity * fogDensity * depth * depth * LOG2 );\nfogFactor = 1.0 - clamp( fogFactor, 0.0, 1.0 );\n}\ngl_FragColor = mix( gl_FragColor, vec4( fogColor, gl_FragColor.w ), fogFactor );\n}\n}"}}; diff --git a/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.device.droneanalyzer.statistics/public/js/common/ajax_handler.js b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.device.droneanalyzer.statistics/public/js/common/ajax_handler.js new file mode 100644 index 0000000000..63d536fb66 --- /dev/null +++ b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.device.droneanalyzer.statistics/public/js/common/ajax_handler.js @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2016, 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. + */ +var ajax_handler = function () { + var api = this; + api.response = "v"; + api.ajaxRequest = function (url, type, data, dataType, callback) { + var response; + $.ajax({ + url: url, + type: type, + dataType: dataType, + success: function (data, success) { + api.response = data; + console.log(" success " + JSON.stringify(success)); + console.log(" data " + JSON.stringify(data)); + callback(data, success); + }, + error: function (jqxhr, textStatus, error) { + var err = textStatus + ', ' + error; + console.log("Request Failed: " + err); + callback(data, error); + api.response = data; + }, + data: data + }); + return api.response; + }; + api.makeJSONObject = function () { + var object = {}; + for (var i = 0; i < arguments.length - 1; i = i + 2) { + object[arguments[i]] = arguments[i + 1]; + } + return object; + } +}; \ No newline at end of file diff --git a/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.device.droneanalyzer.statistics/public/js/common/general_handler b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.device.droneanalyzer.statistics/public/js/common/general_handler new file mode 100644 index 0000000000..2e8f4b172b --- /dev/null +++ b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.device.droneanalyzer.statistics/public/js/common/general_handler @@ -0,0 +1,101 @@ +/* + * 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. + */ + +$('.btn-minimize').click(function (e) { + e.preventDefault(); + var $target = $(this).parent().parent().next('.box-content'); + if ($target.is(':visible')) { + $('i', $(this)).removeClass('glyphicon-chevron-up').addClass('glyphicon-chevron-down'); + checkAndDisable($(this).parent().attr('id')); + + } + else { + $('i', $(this)).removeClass('glyphicon-chevron-down').addClass('glyphicon-chevron-up'); + checkAndEnable($(this).parent().attr('id')); + } + $target.slideToggle(); +}); + +function checkAndEnable(id) { + + //console.log("enable: " + id); + if (id === "RealtimePlotting") { + config_api.modules_status.realtimePlotting = true; + } + else if (id === "SensorReadings") { + config_api.modules_status.sensorReadings = true; + } else if (id === "AngleOfRotation_2") { + config_api.modules_status.angleOfRotation_2 = true; + } else if (id === "AngleOfRotation_1") { + config_api.modules_status.angleOfRotation_1 = true; + } +} + +function checkAndDisable(id) { + //console.log("disable: " + id); + if (id === "RealtimePlotting") { + config_api.modules_status.realtimePlotting = false; + } + else if (id === "SensorReadings") { + config_api.modules_status.sensorReadings = false; + } else if (id === "AngleOfRotation_2") { + config_api.modules_status.angleOfRotation_2 = false; + } else if (id === "AngleOfRotation_1") { + config_api.modules_status.angleOfRotation_1 = false; + } +} + +function isJSON(data) { + try { + return JSON.parse(data); + } + catch (error) { + return null; + } +} + +function Queue() { + var a = [], b = ''; + this.enqueue = function (b) { + a.push([this.getLength() - 1 <= 0 ? 0 : this.getLength() - 1, b]); + }; + this.dequeue = function () { + if (0 != a.length) { + var c = a[b]; + 2 * ++b >= a.length && (a = a.slice(b), b = 0); + return c + } + }; + this.getLength = function () { + return a.length - b; + }; + this.isEmpty = function () { + return 0 == a.length; + }; + + this.peek = function () { + return 0 < a.length ? a[b] : void 0 + }; + this.getData = function () { + return a; + }; + this.make_fixed_size = function (start, end) { + a = a.slice(start, end); + } +} + diff --git a/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.device.droneanalyzer.statistics/public/js/common/websocket_api b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.device.droneanalyzer.statistics/public/js/common/websocket_api new file mode 100644 index 0000000000..6dc690c6c3 --- /dev/null +++ b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.device.droneanalyzer.statistics/public/js/common/websocket_api @@ -0,0 +1,60 @@ +/* + * 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. + */ + + +var webSocket_api = function () { + var api = this; + api.wekSocket; + api.message = function (holder) { + return $("#" + holder + ""); + }, + api.openSocket = function (url, callback) { + if (api.wekSocket !== undefined && api.wekSocket.readyState !== WebSocket.CLOSED) { + console.log("WebSocket is already opened."); + + } else { + api.wekSocket = new WebSocket(url); + } + api.wekSocket.onopen = function (event) { + if (event.data === undefined) + return; + api.writeResponse(event.data); + callback(event.data); + }; + api.wekSocket.onmessage = function (event) { + api.writeResponse(event.data); + }; + + api.wekSocket.onclose = function (event) { + api.writeResponse(event.data); + }; + }, + api.send = function (message) { + api.wekSocket.send(message); + }, + + api.closeSocket = function () { + api.wekSocket.close(); + }, + + api.writeResponse = function (arg) { + console.log(arg); + } +} + + diff --git a/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.device.droneanalyzer.statistics/public/js/config/config.js b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.device.droneanalyzer.statistics/public/js/config/config.js new file mode 100644 index 0000000000..fd234991ce --- /dev/null +++ b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.device.droneanalyzer.statistics/public/js/config/config.js @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2016, 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. + */ + +var config_api = function () { + var config_api = this; + var context_controller = "/drone_analyzer/controller/send_command"; + config_api.config_3dobject_holder = "#virtualDrone"; + config_api.realtime_plotting_update_interval = 30; + config_api.realtime_plotting_totalPoints = 30; + config_api.realtime_plotting_data_window = {}; + config_api.effectController = {uy: 70.0, uz: 15.0, ux: 10.0, fx: 2.0, fz: 15.0, Tmax: 1}; + config_api.drone_control = context_controller; + config_api.drone_controlType = "POST"; + config_api.drone_controlDataType = "json"; + config_api.web_socket_endpoint = "/drone_analyzer/datastream/drone_status"; + config_api.modules_status = { + "realtimePlotting": false, + "sensorReadings": false, + "angleOfRotation_2": false, + "angleOfRotation_1": false + }; +}; \ No newline at end of file diff --git a/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.device.droneanalyzer.statistics/public/js/d3.min.js b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.device.droneanalyzer.statistics/public/js/d3.min.js new file mode 100644 index 0000000000..be855941e0 --- /dev/null +++ b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.device.droneanalyzer.statistics/public/js/d3.min.js @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2016, 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(){function n(n){return n&&(n.ownerDocument||n.document||n).documentElement}function t(n){return n&&(n.ownerDocument&&n.ownerDocument.defaultView||n.document&&n||n.defaultView)}function e(n,t){return t>n?-1:n>t?1:n>=t?0:NaN}function r(n){return null===n?NaN:+n}function u(n){return!isNaN(n)}function i(n){return{left:function(t,e,r,u){for(arguments.length<3&&(r=0),arguments.length<4&&(u=t.length);u>r;){var i=r+u>>>1;n(t[i],e)<0?r=i+1:u=i}return r},right:function(t,e,r,u){for(arguments.length<3&&(r=0),arguments.length<4&&(u=t.length);u>r;){var i=r+u>>>1;n(t[i],e)>0?u=i:r=i+1}return r}}}function a(n){return n.length}function o(n){for(var t=1;n*t%1;)t*=10;return t}function l(n,t){for(var e in t)Object.defineProperty(n.prototype,e,{value:t[e],enumerable:!1})}function c(){this._=Object.create(null)}function s(n){return(n+="")===xa||n[0]===ba?ba+n:n}function f(n){return(n+="")[0]===ba?n.slice(1):n}function h(n){return s(n)in this._}function g(n){return(n=s(n))in this._&&delete this._[n]}function p(){var n=[];for(var t in this._)n.push(f(t));return n}function v(){var n=0;for(var t in this._)++n;return n}function d(){for(var n in this._)return!1;return!0}function m(){this._=Object.create(null)}function y(n){return n}function M(n,t,e){return function(){var r=e.apply(t,arguments);return r===t?n:r}}function x(n,t){if(t in n)return t;t=t.charAt(0).toUpperCase()+t.slice(1);for(var e=0,r=_a.length;r>e;++e){var u=_a[e]+t;if(u in n)return u}}function b(){}function _(){}function w(n){function t(){for(var t,r=e,u=-1,i=r.length;++ue;e++)for(var u,i=n[e],a=0,o=i.length;o>a;a++)(u=i[a])&&t(u,a,e);return n}function Z(n){return Sa(n,za),n}function V(n){var t,e;return function(r,u,i){var a,o=n[i].update,l=o.length;for(i!=e&&(e=i,t=0),u>=t&&(t=u+1);!(a=o[t])&&++t0&&(n=n.slice(0,o));var c=La.get(n);return c&&(n=c,l=B),o?t?u:r:t?b:i}function $(n,t){return function(e){var r=oa.event;oa.event=e,t[0]=this.__data__;try{n.apply(this,t)}finally{oa.event=r}}}function B(n,t){var e=$(n,t);return function(n){var t=this,r=n.relatedTarget;r&&(r===t||8&r.compareDocumentPosition(t))||e.call(t,n)}}function W(e){var r=".dragsuppress-"+ ++Ta,u="click"+r,i=oa.select(t(e)).on("touchmove"+r,S).on("dragstart"+r,S).on("selectstart"+r,S);if(null==qa&&(qa="onselectstart"in e?!1:x(e.style,"userSelect")),qa){var a=n(e).style,o=a[qa];a[qa]="none"}return function(n){if(i.on(r,null),qa&&(a[qa]=o),n){var t=function(){i.on(u,null)};i.on(u,function(){S(),t()},!0),setTimeout(t,0)}}}function J(n,e){e.changedTouches&&(e=e.changedTouches[0]);var r=n.ownerSVGElement||n;if(r.createSVGPoint){var u=r.createSVGPoint();if(0>Ra){var i=t(n);if(i.scrollX||i.scrollY){r=oa.select("body").append("svg").style({position:"absolute",top:0,left:0,margin:0,padding:0,border:"none"},"important");var a=r[0][0].getScreenCTM();Ra=!(a.f||a.e),r.remove()}}return Ra?(u.x=e.pageX,u.y=e.pageY):(u.x=e.clientX,u.y=e.clientY),u=u.matrixTransform(n.getScreenCTM().inverse()),[u.x,u.y]}var o=n.getBoundingClientRect();return[e.clientX-o.left-n.clientLeft,e.clientY-o.top-n.clientTop]}function G(){return oa.event.changedTouches[0].identifier}function K(n){return n>0?1:0>n?-1:0}function Q(n,t,e){return(t[0]-n[0])*(e[1]-n[1])-(t[1]-n[1])*(e[0]-n[0])}function nn(n){return n>1?0:-1>n?ja:Math.acos(n)}function tn(n){return n>1?Ha:-1>n?-Ha:Math.asin(n)}function en(n){return((n=Math.exp(n))-1/n)/2}function rn(n){return((n=Math.exp(n))+1/n)/2}function un(n){return((n=Math.exp(2*n))-1)/(n+1)}function an(n){return(n=Math.sin(n/2))*n}function on(){}function ln(n,t,e){return this instanceof ln?(this.h=+n,this.s=+t,void(this.l=+e)):arguments.length<2?n instanceof ln?new ln(n.h,n.s,n.l):_n(""+n,wn,ln):new ln(n,t,e)}function cn(n,t,e){function r(n){return n>360?n-=360:0>n&&(n+=360),60>n?i+(a-i)*n/60:180>n?a:240>n?i+(a-i)*(240-n)/60:i}function u(n){return Math.round(255*r(n))}var i,a;return n=isNaN(n)?0:(n%=360)<0?n+360:n,t=isNaN(t)?0:0>t?0:t>1?1:t,e=0>e?0:e>1?1:e,a=.5>=e?e*(1+t):e+t-e*t,i=2*e-a,new yn(u(n+120),u(n),u(n-120))}function sn(n,t,e){return this instanceof sn?(this.h=+n,this.c=+t,void(this.l=+e)):arguments.length<2?n instanceof sn?new sn(n.h,n.c,n.l):n instanceof hn?pn(n.l,n.a,n.b):pn((n=Sn((n=oa.rgb(n)).r,n.g,n.b)).l,n.a,n.b):new sn(n,t,e)}function fn(n,t,e){return isNaN(n)&&(n=0),isNaN(t)&&(t=0),new hn(e,Math.cos(n*=Oa)*t,Math.sin(n)*t)}function hn(n,t,e){return this instanceof hn?(this.l=+n,this.a=+t,void(this.b=+e)):arguments.length<2?n instanceof hn?new hn(n.l,n.a,n.b):n instanceof sn?fn(n.h,n.c,n.l):Sn((n=yn(n)).r,n.g,n.b):new hn(n,t,e)}function gn(n,t,e){var r=(n+16)/116,u=r+t/500,i=r-e/200;return u=vn(u)*Ka,r=vn(r)*Qa,i=vn(i)*no,new yn(mn(3.2404542*u-1.5371385*r-.4985314*i),mn(-.969266*u+1.8760108*r+.041556*i),mn(.0556434*u-.2040259*r+1.0572252*i))}function pn(n,t,e){return n>0?new sn(Math.atan2(e,t)*Ia,Math.sqrt(t*t+e*e),n):new sn(NaN,NaN,n)}function vn(n){return n>.206893034?n*n*n:(n-4/29)/7.787037}function dn(n){return n>.008856?Math.pow(n,1/3):7.787037*n+4/29}function mn(n){return Math.round(255*(.00304>=n?12.92*n:1.055*Math.pow(n,1/2.4)-.055))}function yn(n,t,e){return this instanceof yn?(this.r=~~n,this.g=~~t,void(this.b=~~e)):arguments.length<2?n instanceof yn?new yn(n.r,n.g,n.b):_n(""+n,yn,cn):new yn(n,t,e)}function Mn(n){return new yn(n>>16,n>>8&255,255&n)}function xn(n){return Mn(n)+""}function bn(n){return 16>n?"0"+Math.max(0,n).toString(16):Math.min(255,n).toString(16)}function _n(n,t,e){var r,u,i,a=0,o=0,l=0;if(r=/([a-z]+)\((.*)\)/.exec(n=n.toLowerCase()))switch(u=r[2].split(","),r[1]){case"hsl":return e(parseFloat(u[0]),parseFloat(u[1])/100,parseFloat(u[2])/100);case"rgb":return t(Nn(u[0]),Nn(u[1]),Nn(u[2]))}return(i=ro.get(n))?t(i.r,i.g,i.b):(null==n||"#"!==n.charAt(0)||isNaN(i=parseInt(n.slice(1),16))||(4===n.length?(a=(3840&i)>>4,a=a>>4|a,o=240&i,o=o>>4|o,l=15&i,l=l<<4|l):7===n.length&&(a=(16711680&i)>>16,o=(65280&i)>>8,l=255&i)),t(a,o,l))}function wn(n,t,e){var r,u,i=Math.min(n/=255,t/=255,e/=255),a=Math.max(n,t,e),o=a-i,l=(a+i)/2;return o?(u=.5>l?o/(a+i):o/(2-a-i),r=n==a?(t-e)/o+(e>t?6:0):t==a?(e-n)/o+2:(n-t)/o+4,r*=60):(r=NaN,u=l>0&&1>l?0:r),new ln(r,u,l)}function Sn(n,t,e){n=kn(n),t=kn(t),e=kn(e);var r=dn((.4124564*n+.3575761*t+.1804375*e)/Ka),u=dn((.2126729*n+.7151522*t+.072175*e)/Qa),i=dn((.0193339*n+.119192*t+.9503041*e)/no);return hn(116*u-16,500*(r-u),200*(u-i))}function kn(n){return(n/=255)<=.04045?n/12.92:Math.pow((n+.055)/1.055,2.4)}function Nn(n){var t=parseFloat(n);return"%"===n.charAt(n.length-1)?Math.round(2.55*t):t}function En(n){return"function"==typeof n?n:function(){return n}}function An(n){return function(t,e,r){return 2===arguments.length&&"function"==typeof e&&(r=e,e=null),Cn(t,e,n,r)}}function Cn(n,t,e,r){function u(){var n,t=l.status;if(!t&&Ln(l)||t>=200&&300>t||304===t){try{n=e.call(i,l)}catch(r){return void a.error.call(i,r)}a.load.call(i,n)}else a.error.call(i,l)}var i={},a=oa.dispatch("beforesend","progress","load","error"),o={},l=new XMLHttpRequest,c=null;return!this.XDomainRequest||"withCredentials"in l||!/^(http(s)?:)?\/\//.test(n)||(l=new XDomainRequest),"onload"in l?l.onload=l.onerror=u:l.onreadystatechange=function(){l.readyState>3&&u()},l.onprogress=function(n){var t=oa.event;oa.event=n;try{a.progress.call(i,l)}finally{oa.event=t}},i.header=function(n,t){return n=(n+"").toLowerCase(),arguments.length<2?o[n]:(null==t?delete o[n]:o[n]=t+"",i)},i.mimeType=function(n){return arguments.length?(t=null==n?null:n+"",i):t},i.responseType=function(n){return arguments.length?(c=n,i):c},i.response=function(n){return e=n,i},["get","post"].forEach(function(n){i[n]=function(){return i.send.apply(i,[n].concat(ca(arguments)))}}),i.send=function(e,r,u){if(2===arguments.length&&"function"==typeof r&&(u=r,r=null),l.open(e,n,!0),null==t||"accept"in o||(o.accept=t+",*/*"),l.setRequestHeader)for(var s in o)l.setRequestHeader(s,o[s]);return null!=t&&l.overrideMimeType&&l.overrideMimeType(t),null!=c&&(l.responseType=c),null!=u&&i.on("error",u).on("load",function(n){u(null,n)}),a.beforesend.call(i,l),l.send(null==r?null:r),i},i.abort=function(){return l.abort(),i},oa.rebind(i,a,"on"),null==r?i:i.get(zn(r))}function zn(n){return 1===n.length?function(t,e){n(null==t?e:null)}:n}function Ln(n){var t=n.responseType;return t&&"text"!==t?n.response:n.responseText}function qn(n,t,e){var r=arguments.length;2>r&&(t=0),3>r&&(e=Date.now());var u=e+t,i={c:n,t:u,n:null};return io?io.n=i:uo=i,io=i,ao||(oo=clearTimeout(oo),ao=1,lo(Tn)),i}function Tn(){var n=Rn(),t=Dn()-n;t>24?(isFinite(t)&&(clearTimeout(oo),oo=setTimeout(Tn,t)),ao=0):(ao=1,lo(Tn))}function Rn(){for(var n=Date.now(),t=uo;t;)n>=t.t&&t.c(n-t.t)&&(t.c=null),t=t.n;return n}function Dn(){for(var n,t=uo,e=1/0;t;)t.c?(t.t8?function(n){return n/e}:function(n){return n*e},symbol:n}}function Un(n){var t=n.decimal,e=n.thousands,r=n.grouping,u=n.currency,i=r&&e?function(n,t){for(var u=n.length,i=[],a=0,o=r[0],l=0;u>0&&o>0&&(l+o+1>t&&(o=Math.max(1,t-l)),i.push(n.substring(u-=o,u+o)),!((l+=o+1)>t));)o=r[a=(a+1)%r.length];return i.reverse().join(e)}:y;return function(n){var e=so.exec(n),r=e[1]||" ",a=e[2]||">",o=e[3]||"-",l=e[4]||"",c=e[5],s=+e[6],f=e[7],h=e[8],g=e[9],p=1,v="",d="",m=!1,y=!0;switch(h&&(h=+h.substring(1)),(c||"0"===r&&"="===a)&&(c=r="0",a="="),g){case"n":f=!0,g="g";break;case"%":p=100,d="%",g="f";break;case"p":p=100,d="%",g="r";break;case"b":case"o":case"x":case"X":"#"===l&&(v="0"+g.toLowerCase());case"c":y=!1;case"d":m=!0,h=0;break;case"s":p=-1,g="r"}"$"===l&&(v=u[0],d=u[1]),"r"!=g||h||(g="g"),null!=h&&("g"==g?h=Math.max(1,Math.min(21,h)):("e"==g||"f"==g)&&(h=Math.max(0,Math.min(20,h)))),g=fo.get(g)||Fn;var M=c&&f;return function(n){var e=d;if(m&&n%1)return"";var u=0>n||0===n&&0>1/n?(n=-n,"-"):"-"===o?"":o;if(0>p){var l=oa.formatPrefix(n,h);n=l.scale(n),e=l.symbol+d}else n*=p;n=g(n,h);var x,b,_=n.lastIndexOf(".");if(0>_){var w=y?n.lastIndexOf("e"):-1;0>w?(x=n,b=""):(x=n.substring(0,w),b=n.substring(w))}else x=n.substring(0,_),b=t+n.substring(_+1);!c&&f&&(x=i(x,1/0));var S=v.length+x.length+b.length+(M?0:u.length),k=s>S?new Array(S=s-S+1).join(r):"";return M&&(x=i(k+x,k.length?s-b.length:1/0)),u+=v,n=x+b,("<"===a?u+n+k:">"===a?k+u+n:"^"===a?k.substring(0,S>>=1)+u+n+k.substring(S):u+(M?n:k+n))+e}}}function Fn(n){return n+""}function Hn(){this._=new Date(arguments.length>1?Date.UTC.apply(this,arguments):arguments[0])}function On(n,t,e){function r(t){var e=n(t),r=i(e,1);return r-t>t-e?e:r}function u(e){return t(e=n(new go(e-1)),1),e}function i(n,e){return t(n=new go(+n),e),n}function a(n,r,i){var a=u(n),o=[];if(i>1)for(;r>a;)e(a)%i||o.push(new Date(+a)),t(a,1);else for(;r>a;)o.push(new Date(+a)),t(a,1);return o}function o(n,t,e){try{go=Hn;var r=new Hn;return r._=n,a(r,t,e)}finally{go=Date}}n.floor=n,n.round=r,n.ceil=u,n.offset=i,n.range=a;var l=n.utc=In(n);return l.floor=l,l.round=In(r),l.ceil=In(u),l.offset=In(i),l.range=o,n}function In(n){return function(t,e){try{go=Hn;var r=new Hn;return r._=t,n(r,e)._}finally{go=Date}}}function Yn(n){function t(n){function t(t){for(var e,u,i,a=[],o=-1,l=0;++oo;){if(r>=c)return-1;if(u=t.charCodeAt(o++),37===u){if(a=t.charAt(o++),i=C[a in vo?t.charAt(o++):a],!i||(r=i(n,e,r))<0)return-1}else if(u!=e.charCodeAt(r++))return-1}return r}function r(n,t,e){_.lastIndex=0;var r=_.exec(t.slice(e));return r?(n.w=w.get(r[0].toLowerCase()),e+r[0].length):-1}function u(n,t,e){x.lastIndex=0;var r=x.exec(t.slice(e));return r?(n.w=b.get(r[0].toLowerCase()),e+r[0].length):-1}function i(n,t,e){N.lastIndex=0;var r=N.exec(t.slice(e));return r?(n.m=E.get(r[0].toLowerCase()),e+r[0].length):-1}function a(n,t,e){S.lastIndex=0;var r=S.exec(t.slice(e));return r?(n.m=k.get(r[0].toLowerCase()),e+r[0].length):-1}function o(n,t,r){return e(n,A.c.toString(),t,r)}function l(n,t,r){return e(n,A.x.toString(),t,r)}function c(n,t,r){return e(n,A.X.toString(),t,r)}function s(n,t,e){var r=M.get(t.slice(e,e+=2).toLowerCase());return null==r?-1:(n.p=r,e)}var f=n.dateTime,h=n.date,g=n.time,p=n.periods,v=n.days,d=n.shortDays,m=n.months,y=n.shortMonths;t.utc=function(n){function e(n){try{go=Hn;var t=new go;return t._=n,r(t)}finally{go=Date}}var r=t(n);return e.parse=function(n){try{go=Hn;var t=r.parse(n);return t&&t._}finally{go=Date}},e.toString=r.toString,e},t.multi=t.utc.multi=ct;var M=oa.map(),x=Vn(v),b=Xn(v),_=Vn(d),w=Xn(d),S=Vn(m),k=Xn(m),N=Vn(y),E=Xn(y);p.forEach(function(n,t){M.set(n.toLowerCase(),t)});var A={a:function(n){return d[n.getDay()]},A:function(n){return v[n.getDay()]},b:function(n){return y[n.getMonth()]},B:function(n){return m[n.getMonth()]},c:t(f),d:function(n,t){return Zn(n.getDate(),t,2)},e:function(n,t){return Zn(n.getDate(),t,2)},H:function(n,t){return Zn(n.getHours(),t,2)},I:function(n,t){return Zn(n.getHours()%12||12,t,2)},j:function(n,t){return Zn(1+ho.dayOfYear(n),t,3)},L:function(n,t){return Zn(n.getMilliseconds(),t,3)},m:function(n,t){return Zn(n.getMonth()+1,t,2)},M:function(n,t){return Zn(n.getMinutes(),t,2)},p:function(n){return p[+(n.getHours()>=12)]},S:function(n,t){return Zn(n.getSeconds(),t,2)},U:function(n,t){return Zn(ho.sundayOfYear(n),t,2)},w:function(n){return n.getDay()},W:function(n,t){return Zn(ho.mondayOfYear(n),t,2)},x:t(h),X:t(g),y:function(n,t){return Zn(n.getFullYear()%100,t,2)},Y:function(n,t){return Zn(n.getFullYear()%1e4,t,4)},Z:ot,"%":function(){return"%"}},C={a:r,A:u,b:i,B:a,c:o,d:tt,e:tt,H:rt,I:rt,j:et,L:at,m:nt,M:ut,p:s,S:it,U:Bn,w:$n,W:Wn,x:l,X:c,y:Gn,Y:Jn,Z:Kn,"%":lt};return t}function Zn(n,t,e){var r=0>n?"-":"",u=(r?-n:n)+"",i=u.length;return r+(e>i?new Array(e-i+1).join(t)+u:u)}function Vn(n){return new RegExp("^(?:"+n.map(oa.requote).join("|")+")","i")}function Xn(n){for(var t=new c,e=-1,r=n.length;++e68?1900:2e3)}function nt(n,t,e){mo.lastIndex=0;var r=mo.exec(t.slice(e,e+2));return r?(n.m=r[0]-1,e+r[0].length):-1}function tt(n,t,e){mo.lastIndex=0;var r=mo.exec(t.slice(e,e+2));return r?(n.d=+r[0],e+r[0].length):-1}function et(n,t,e){mo.lastIndex=0;var r=mo.exec(t.slice(e,e+3));return r?(n.j=+r[0],e+r[0].length):-1}function rt(n,t,e){mo.lastIndex=0;var r=mo.exec(t.slice(e,e+2));return r?(n.H=+r[0],e+r[0].length):-1}function ut(n,t,e){mo.lastIndex=0;var r=mo.exec(t.slice(e,e+2));return r?(n.M=+r[0],e+r[0].length):-1}function it(n,t,e){mo.lastIndex=0;var r=mo.exec(t.slice(e,e+2));return r?(n.S=+r[0],e+r[0].length):-1}function at(n,t,e){mo.lastIndex=0;var r=mo.exec(t.slice(e,e+3));return r?(n.L=+r[0],e+r[0].length):-1}function ot(n){var t=n.getTimezoneOffset(),e=t>0?"-":"+",r=Ma(t)/60|0,u=Ma(t)%60;return e+Zn(r,"0",2)+Zn(u,"0",2)}function lt(n,t,e){yo.lastIndex=0;var r=yo.exec(t.slice(e,e+1));return r?e+r[0].length:-1}function ct(n){for(var t=n.length,e=-1;++e=0?1:-1,o=a*e,l=Math.cos(t),c=Math.sin(t),s=i*c,f=u*l+s*Math.cos(o),h=s*a*Math.sin(o);So.add(Math.atan2(h,f)),r=n,u=l,i=c}var t,e,r,u,i;ko.point=function(a,o){ko.point=n,r=(t=a)*Oa,u=Math.cos(o=(e=o)*Oa/2+ja/4),i=Math.sin(o)},ko.lineEnd=function(){n(t,e)}}function dt(n){var t=n[0],e=n[1],r=Math.cos(e);return[r*Math.cos(t),r*Math.sin(t),Math.sin(e)]}function mt(n,t){return n[0]*t[0]+n[1]*t[1]+n[2]*t[2]}function yt(n,t){return[n[1]*t[2]-n[2]*t[1],n[2]*t[0]-n[0]*t[2],n[0]*t[1]-n[1]*t[0]]}function Mt(n,t){n[0]+=t[0],n[1]+=t[1],n[2]+=t[2]}function xt(n,t){return[n[0]*t,n[1]*t,n[2]*t]}function bt(n){var t=Math.sqrt(n[0]*n[0]+n[1]*n[1]+n[2]*n[2]);n[0]/=t,n[1]/=t,n[2]/=t}function _t(n){return[Math.atan2(n[1],n[0]),tn(n[2])]}function wt(n,t){return Ma(n[0]-t[0])o;++o)u.point((e=n[o])[0],e[1]);return void u.lineEnd()}var l=new Tt(e,n,null,!0),c=new Tt(e,null,l,!1);l.o=c,i.push(l),a.push(c),l=new Tt(r,n,null,!1),c=new Tt(r,null,l,!0),l.o=c,i.push(l),a.push(c)}}),a.sort(t),qt(i),qt(a),i.length){for(var o=0,l=e,c=a.length;c>o;++o)a[o].e=l=!l;for(var s,f,h=i[0];;){for(var g=h,p=!0;g.v;)if((g=g.n)===h)return;s=g.z,u.lineStart();do{if(g.v=g.o.v=!0,g.e){if(p)for(var o=0,c=s.length;c>o;++o)u.point((f=s[o])[0],f[1]);else r(g.x,g.n.x,1,u);g=g.n}else{if(p){s=g.p.z;for(var o=s.length-1;o>=0;--o)u.point((f=s[o])[0],f[1])}else r(g.x,g.p.x,-1,u);g=g.p}g=g.o,s=g.z,p=!p}while(!g.v);u.lineEnd()}}}function qt(n){if(t=n.length){for(var t,e,r=0,u=n[0];++r0){for(b||(i.polygonStart(),b=!0),i.lineStart();++a1&&2&t&&e.push(e.pop().concat(e.shift())),g.push(e.filter(Dt))}var g,p,v,d=t(i),m=u.invert(r[0],r[1]),y={point:a,lineStart:l,lineEnd:c,polygonStart:function(){y.point=s,y.lineStart=f,y.lineEnd=h,g=[],p=[]},polygonEnd:function(){y.point=a,y.lineStart=l,y.lineEnd=c,g=oa.merge(g);var n=Ot(m,p);g.length?(b||(i.polygonStart(),b=!0),Lt(g,jt,n,e,i)):n&&(b||(i.polygonStart(),b=!0),i.lineStart(),e(null,null,1,i),i.lineEnd()),b&&(i.polygonEnd(),b=!1),g=p=null},sphere:function(){i.polygonStart(),i.lineStart(),e(null,null,1,i),i.lineEnd(),i.polygonEnd()}},M=Pt(),x=t(M),b=!1;return y}}function Dt(n){return n.length>1}function Pt(){var n,t=[];return{lineStart:function(){t.push(n=[])},point:function(t,e){n.push([t,e])},lineEnd:b,buffer:function(){var e=t;return t=[],n=null,e},rejoin:function(){t.length>1&&t.push(t.pop().concat(t.shift()))}}}function jt(n,t){return((n=n.x)[0]<0?n[1]-Ha-Da:Ha-n[1])-((t=t.x)[0]<0?t[1]-Ha-Da:Ha-t[1])}function Ut(n){var t,e=NaN,r=NaN,u=NaN;return{lineStart:function(){n.lineStart(),t=1},point:function(i,a){var o=i>0?ja:-ja,l=Ma(i-e);Ma(l-ja)0?Ha:-Ha),n.point(u,r),n.lineEnd(),n.lineStart(),n.point(o,r),n.point(i,r),t=0):u!==o&&l>=ja&&(Ma(e-u)Da?Math.atan((Math.sin(t)*(i=Math.cos(r))*Math.sin(e)-Math.sin(r)*(u=Math.cos(t))*Math.sin(n))/(u*i*a)):(t+r)/2}function Ht(n,t,e,r){var u;if(null==n)u=e*Ha,r.point(-ja,u),r.point(0,u),r.point(ja,u),r.point(ja,0),r.point(ja,-u),r.point(0,-u),r.point(-ja,-u),r.point(-ja,0),r.point(-ja,u);else if(Ma(n[0]-t[0])>Da){var i=n[0]o;++o){var c=t[o],s=c.length;if(s)for(var f=c[0],h=f[0],g=f[1]/2+ja/4,p=Math.sin(g),v=Math.cos(g),d=1;;){d===s&&(d=0),n=c[d];var m=n[0],y=n[1]/2+ja/4,M=Math.sin(y),x=Math.cos(y),b=m-h,_=b>=0?1:-1,w=_*b,S=w>ja,k=p*M;if(So.add(Math.atan2(k*_*Math.sin(w),v*x+k*Math.cos(w))),i+=S?b+_*Ua:b,S^h>=e^m>=e){var N=yt(dt(f),dt(n));bt(N);var E=yt(u,N);bt(E);var A=(S^b>=0?-1:1)*tn(E[2]);(r>A||r===A&&(N[0]||N[1]))&&(a+=S^b>=0?1:-1)}if(!d++)break;h=m,p=M,v=x,f=n}}return(-Da>i||Da>i&&0>So)^1&a}function It(n){function t(n,t){return Math.cos(n)*Math.cos(t)>i}function e(n){var e,i,l,c,s;return{lineStart:function(){c=l=!1,s=1},point:function(f,h){var g,p=[f,h],v=t(f,h),d=a?v?0:u(f,h):v?u(f+(0>f?ja:-ja),h):0;if(!e&&(c=l=v)&&n.lineStart(),v!==l&&(g=r(e,p),(wt(e,g)||wt(p,g))&&(p[0]+=Da,p[1]+=Da,v=t(p[0],p[1]))),v!==l)s=0,v?(n.lineStart(),g=r(p,e),n.point(g[0],g[1])):(g=r(e,p),n.point(g[0],g[1]),n.lineEnd()),e=g;else if(o&&e&&a^v){var m;d&i||!(m=r(p,e,!0))||(s=0,a?(n.lineStart(),n.point(m[0][0],m[0][1]),n.point(m[1][0],m[1][1]),n.lineEnd()):(n.point(m[1][0],m[1][1]),n.lineEnd(),n.lineStart(),n.point(m[0][0],m[0][1])))}!v||e&&wt(e,p)||n.point(p[0],p[1]),e=p,l=v,i=d},lineEnd:function(){l&&n.lineEnd(),e=null},clean:function(){return s|(c&&l)<<1}}}function r(n,t,e){var r=dt(n),u=dt(t),a=[1,0,0],o=yt(r,u),l=mt(o,o),c=o[0],s=l-c*c;if(!s)return!e&&n;var f=i*l/s,h=-i*c/s,g=yt(a,o),p=xt(a,f),v=xt(o,h);Mt(p,v);var d=g,m=mt(p,d),y=mt(d,d),M=m*m-y*(mt(p,p)-1);if(!(0>M)){var x=Math.sqrt(M),b=xt(d,(-m-x)/y);if(Mt(b,p),b=_t(b),!e)return b;var _,w=n[0],S=t[0],k=n[1],N=t[1];w>S&&(_=w,w=S,S=_);var E=S-w,A=Ma(E-ja)E;if(!A&&k>N&&(_=k,k=N,N=_),C?A?k+N>0^b[1]<(Ma(b[0]-w)ja^(w<=b[0]&&b[0]<=S)){var z=xt(d,(-m+x)/y);return Mt(z,p),[b,_t(z)]}}}function u(t,e){var r=a?n:ja-n,u=0;return-r>t?u|=1:t>r&&(u|=2),-r>e?u|=4:e>r&&(u|=8),u}var i=Math.cos(n),a=i>0,o=Ma(i)>Da,l=ve(n,6*Oa);return Rt(t,e,l,a?[0,-n]:[-ja,n-ja])}function Yt(n,t,e,r){return function(u){var i,a=u.a,o=u.b,l=a.x,c=a.y,s=o.x,f=o.y,h=0,g=1,p=s-l,v=f-c;if(i=n-l,p||!(i>0)){if(i/=p,0>p){if(h>i)return;g>i&&(g=i)}else if(p>0){if(i>g)return;i>h&&(h=i)}if(i=e-l,p||!(0>i)){if(i/=p,0>p){if(i>g)return;i>h&&(h=i)}else if(p>0){if(h>i)return;g>i&&(g=i)}if(i=t-c,v||!(i>0)){if(i/=v,0>v){if(h>i)return;g>i&&(g=i)}else if(v>0){if(i>g)return;i>h&&(h=i)}if(i=r-c,v||!(0>i)){if(i/=v,0>v){if(i>g)return;i>h&&(h=i)}else if(v>0){if(h>i)return;g>i&&(g=i)}return h>0&&(u.a={x:l+h*p,y:c+h*v}),1>g&&(u.b={x:l+g*p,y:c+g*v}),u}}}}}}function Zt(n,t,e,r){function u(r,u){return Ma(r[0]-n)0?0:3:Ma(r[0]-e)0?2:1:Ma(r[1]-t)0?1:0:u>0?3:2}function i(n,t){return a(n.x,t.x)}function a(n,t){var e=u(n,1),r=u(t,1);return e!==r?e-r:0===e?t[1]-n[1]:1===e?n[0]-t[0]:2===e?n[1]-t[1]:t[0]-n[0]}return function(o){function l(n){for(var t=0,e=d.length,r=n[1],u=0;e>u;++u)for(var i,a=1,o=d[u],l=o.length,c=o[0];l>a;++a)i=o[a],c[1]<=r?i[1]>r&&Q(c,i,n)>0&&++t:i[1]<=r&&Q(c,i,n)<0&&--t,c=i;return 0!==t}function c(i,o,l,c){var s=0,f=0;if(null==i||(s=u(i,l))!==(f=u(o,l))||a(i,o)<0^l>0){do c.point(0===s||3===s?n:e,s>1?r:t);while((s=(s+l+4)%4)!==f)}else c.point(o[0],o[1])}function s(u,i){return u>=n&&e>=u&&i>=t&&r>=i}function f(n,t){s(n,t)&&o.point(n,t)}function h(){C.point=p,d&&d.push(m=[]),S=!0,w=!1,b=_=NaN}function g(){v&&(p(y,M),x&&w&&E.rejoin(),v.push(E.buffer())),C.point=f,w&&o.lineEnd()}function p(n,t){n=Math.max(-Fo,Math.min(Fo,n)),t=Math.max(-Fo,Math.min(Fo,t));var e=s(n,t);if(d&&m.push([n,t]),S)y=n,M=t,x=e,S=!1,e&&(o.lineStart(),o.point(n,t));else if(e&&w)o.point(n,t);else{var r={a:{x:b,y:_},b:{x:n,y:t}};A(r)?(w||(o.lineStart(),o.point(r.a.x,r.a.y)),o.point(r.b.x,r.b.y),e||o.lineEnd(),k=!1):e&&(o.lineStart(),o.point(n,t),k=!1)}b=n,_=t,w=e}var v,d,m,y,M,x,b,_,w,S,k,N=o,E=Pt(),A=Yt(n,t,e,r),C={point:f,lineStart:h,lineEnd:g,polygonStart:function(){o=E,v=[],d=[],k=!0},polygonEnd:function(){o=N,v=oa.merge(v);var t=l([n,r]),e=k&&t,u=v.length;(e||u)&&(o.polygonStart(),e&&(o.lineStart(),c(null,null,1,o),o.lineEnd()),u&&Lt(v,i,t,c,o),o.polygonEnd()),v=d=m=null}};return C}}function Vt(n){var t=0,e=ja/3,r=oe(n),u=r(t,e);return u.parallels=function(n){return arguments.length?r(t=n[0]*ja/180,e=n[1]*ja/180):[t/ja*180,e/ja*180]},u}function Xt(n,t){function e(n,t){var e=Math.sqrt(i-2*u*Math.sin(t))/u;return[e*Math.sin(n*=u),a-e*Math.cos(n)]}var r=Math.sin(n),u=(r+Math.sin(t))/2,i=1+r*(2*u-r),a=Math.sqrt(i)/u;return e.invert=function(n,t){var e=a-t;return[Math.atan2(n,e)/u,tn((i-(n*n+e*e)*u*u)/(2*u))]},e}function $t(){function n(n,t){Oo+=u*n-r*t,r=n,u=t}var t,e,r,u;Xo.point=function(i,a){Xo.point=n,t=r=i,e=u=a},Xo.lineEnd=function(){n(t,e)}}function Bt(n,t){Io>n&&(Io=n),n>Zo&&(Zo=n),Yo>t&&(Yo=t),t>Vo&&(Vo=t)}function Wt(){function n(n,t){a.push("M",n,",",t,i)}function t(n,t){a.push("M",n,",",t),o.point=e}function e(n,t){a.push("L",n,",",t)}function r(){o.point=n}function u(){a.push("Z")}var i=Jt(4.5),a=[],o={point:n,lineStart:function(){o.point=t},lineEnd:r,polygonStart:function(){o.lineEnd=u},polygonEnd:function(){o.lineEnd=r,o.point=n},pointRadius:function(n){return i=Jt(n),o},result:function(){if(a.length){var n=a.join("");return a=[],n}}};return o}function Jt(n){return"m0,"+n+"a"+n+","+n+" 0 1,1 0,"+-2*n+"a"+n+","+n+" 0 1,1 0,"+2*n+"z"}function Gt(n,t){Ao+=n,Co+=t,++zo}function Kt(){function n(n,r){var u=n-t,i=r-e,a=Math.sqrt(u*u+i*i);Lo+=a*(t+n)/2,qo+=a*(e+r)/2,To+=a,Gt(t=n,e=r)}var t,e;Bo.point=function(r,u){Bo.point=n,Gt(t=r,e=u)}}function Qt(){Bo.point=Gt}function ne(){function n(n,t){var e=n-r,i=t-u,a=Math.sqrt(e*e+i*i);Lo+=a*(r+n)/2,qo+=a*(u+t)/2,To+=a,a=u*n-r*t,Ro+=a*(r+n),Do+=a*(u+t),Po+=3*a,Gt(r=n,u=t)}var t,e,r,u;Bo.point=function(i,a){Bo.point=n,Gt(t=r=i,e=u=a)},Bo.lineEnd=function(){n(t,e)}}function te(n){function t(t,e){n.moveTo(t+a,e),n.arc(t,e,a,0,Ua)}function e(t,e){n.moveTo(t,e),o.point=r}function r(t,e){n.lineTo(t,e)}function u(){o.point=t}function i(){n.closePath()}var a=4.5,o={point:t,lineStart:function(){o.point=e},lineEnd:u,polygonStart:function(){o.lineEnd=i},polygonEnd:function(){o.lineEnd=u,o.point=t},pointRadius:function(n){return a=n,o},result:b};return o}function ee(n){function t(n){return(o?r:e)(n)}function e(t){return ie(t,function(e,r){e=n(e,r),t.point(e[0],e[1])})}function r(t){function e(e,r){e=n(e,r),t.point(e[0],e[1])}function r(){M=NaN,S.point=i,t.lineStart()}function i(e,r){var i=dt([e,r]),a=n(e,r);u(M,x,y,b,_,w,M=a[0],x=a[1],y=e,b=i[0],_=i[1],w=i[2],o,t),t.point(M,x)}function a(){S.point=e,t.lineEnd()}function l(){r(),S.point=c,S.lineEnd=s}function c(n,t){ +i(f=n,h=t),g=M,p=x,v=b,d=_,m=w,S.point=i}function s(){u(M,x,y,b,_,w,g,p,f,v,d,m,o,t),S.lineEnd=a,a()}var f,h,g,p,v,d,m,y,M,x,b,_,w,S={point:e,lineStart:r,lineEnd:a,polygonStart:function(){t.polygonStart(),S.lineStart=l},polygonEnd:function(){t.polygonEnd(),S.lineStart=r}};return S}function u(t,e,r,o,l,c,s,f,h,g,p,v,d,m){var y=s-t,M=f-e,x=y*y+M*M;if(x>4*i&&d--){var b=o+g,_=l+p,w=c+v,S=Math.sqrt(b*b+_*_+w*w),k=Math.asin(w/=S),N=Ma(Ma(w)-1)i||Ma((y*z+M*L)/x-.5)>.3||a>o*g+l*p+c*v)&&(u(t,e,r,o,l,c,A,C,N,b/=S,_/=S,w,d,m),m.point(A,C),u(A,C,N,b,_,w,s,f,h,g,p,v,d,m))}}var i=.5,a=Math.cos(30*Oa),o=16;return t.precision=function(n){return arguments.length?(o=(i=n*n)>0&&16,t):Math.sqrt(i)},t}function re(n){var t=ee(function(t,e){return n([t*Ia,e*Ia])});return function(n){return le(t(n))}}function ue(n){this.stream=n}function ie(n,t){return{point:t,sphere:function(){n.sphere()},lineStart:function(){n.lineStart()},lineEnd:function(){n.lineEnd()},polygonStart:function(){n.polygonStart()},polygonEnd:function(){n.polygonEnd()}}}function ae(n){return oe(function(){return n})()}function oe(n){function t(n){return n=o(n[0]*Oa,n[1]*Oa),[n[0]*h+l,c-n[1]*h]}function e(n){return n=o.invert((n[0]-l)/h,(c-n[1])/h),n&&[n[0]*Ia,n[1]*Ia]}function r(){o=Ct(a=fe(m,M,x),i);var n=i(v,d);return l=g-n[0]*h,c=p+n[1]*h,u()}function u(){return s&&(s.valid=!1,s=null),t}var i,a,o,l,c,s,f=ee(function(n,t){return n=i(n,t),[n[0]*h+l,c-n[1]*h]}),h=150,g=480,p=250,v=0,d=0,m=0,M=0,x=0,b=Uo,_=y,w=null,S=null;return t.stream=function(n){return s&&(s.valid=!1),s=le(b(a,f(_(n)))),s.valid=!0,s},t.clipAngle=function(n){return arguments.length?(b=null==n?(w=n,Uo):It((w=+n)*Oa),u()):w},t.clipExtent=function(n){return arguments.length?(S=n,_=n?Zt(n[0][0],n[0][1],n[1][0],n[1][1]):y,u()):S},t.scale=function(n){return arguments.length?(h=+n,r()):h},t.translate=function(n){return arguments.length?(g=+n[0],p=+n[1],r()):[g,p]},t.center=function(n){return arguments.length?(v=n[0]%360*Oa,d=n[1]%360*Oa,r()):[v*Ia,d*Ia]},t.rotate=function(n){return arguments.length?(m=n[0]%360*Oa,M=n[1]%360*Oa,x=n.length>2?n[2]%360*Oa:0,r()):[m*Ia,M*Ia,x*Ia]},oa.rebind(t,f,"precision"),function(){return i=n.apply(this,arguments),t.invert=i.invert&&e,r()}}function le(n){return ie(n,function(t,e){n.point(t*Oa,e*Oa)})}function ce(n,t){return[n,t]}function se(n,t){return[n>ja?n-Ua:-ja>n?n+Ua:n,t]}function fe(n,t,e){return n?t||e?Ct(ge(n),pe(t,e)):ge(n):t||e?pe(t,e):se}function he(n){return function(t,e){return t+=n,[t>ja?t-Ua:-ja>t?t+Ua:t,e]}}function ge(n){var t=he(n);return t.invert=he(-n),t}function pe(n,t){function e(n,t){var e=Math.cos(t),o=Math.cos(n)*e,l=Math.sin(n)*e,c=Math.sin(t),s=c*r+o*u;return[Math.atan2(l*i-s*a,o*r-c*u),tn(s*i+l*a)]}var r=Math.cos(n),u=Math.sin(n),i=Math.cos(t),a=Math.sin(t);return e.invert=function(n,t){var e=Math.cos(t),o=Math.cos(n)*e,l=Math.sin(n)*e,c=Math.sin(t),s=c*i-l*a;return[Math.atan2(l*i+c*a,o*r+s*u),tn(s*r-o*u)]},e}function ve(n,t){var e=Math.cos(n),r=Math.sin(n);return function(u,i,a,o){var l=a*t;null!=u?(u=de(e,u),i=de(e,i),(a>0?i>u:u>i)&&(u+=a*Ua)):(u=n+a*Ua,i=n-.5*l);for(var c,s=u;a>0?s>i:i>s;s-=l)o.point((c=_t([e,-r*Math.cos(s),-r*Math.sin(s)]))[0],c[1])}}function de(n,t){var e=dt(t);e[0]-=n,bt(e);var r=nn(-e[1]);return((-e[2]<0?-r:r)+2*Math.PI-Da)%(2*Math.PI)}function me(n,t,e){var r=oa.range(n,t-Da,e).concat(t);return function(n){return r.map(function(t){return[n,t]})}}function ye(n,t,e){var r=oa.range(n,t-Da,e).concat(t);return function(n){return r.map(function(t){return[t,n]})}}function Me(n){return n.source}function xe(n){return n.target}function be(n,t,e,r){var u=Math.cos(t),i=Math.sin(t),a=Math.cos(r),o=Math.sin(r),l=u*Math.cos(n),c=u*Math.sin(n),s=a*Math.cos(e),f=a*Math.sin(e),h=2*Math.asin(Math.sqrt(an(r-t)+u*a*an(e-n))),g=1/Math.sin(h),p=h?function(n){var t=Math.sin(n*=h)*g,e=Math.sin(h-n)*g,r=e*l+t*s,u=e*c+t*f,a=e*i+t*o;return[Math.atan2(u,r)*Ia,Math.atan2(a,Math.sqrt(r*r+u*u))*Ia]}:function(){return[n*Ia,t*Ia]};return p.distance=h,p}function _e(){function n(n,u){var i=Math.sin(u*=Oa),a=Math.cos(u),o=Ma((n*=Oa)-t),l=Math.cos(o);Wo+=Math.atan2(Math.sqrt((o=a*Math.sin(o))*o+(o=r*i-e*a*l)*o),e*i+r*a*l),t=n,e=i,r=a}var t,e,r;Jo.point=function(u,i){t=u*Oa,e=Math.sin(i*=Oa),r=Math.cos(i),Jo.point=n},Jo.lineEnd=function(){Jo.point=Jo.lineEnd=b}}function we(n,t){function e(t,e){var r=Math.cos(t),u=Math.cos(e),i=n(r*u);return[i*u*Math.sin(t),i*Math.sin(e)]}return e.invert=function(n,e){var r=Math.sqrt(n*n+e*e),u=t(r),i=Math.sin(u),a=Math.cos(u);return[Math.atan2(n*i,r*a),Math.asin(r&&e*i/r)]},e}function Se(n,t){function e(n,t){a>0?-Ha+Da>t&&(t=-Ha+Da):t>Ha-Da&&(t=Ha-Da);var e=a/Math.pow(u(t),i);return[e*Math.sin(i*n),a-e*Math.cos(i*n)]}var r=Math.cos(n),u=function(n){return Math.tan(ja/4+n/2)},i=n===t?Math.sin(n):Math.log(r/Math.cos(t))/Math.log(u(t)/u(n)),a=r*Math.pow(u(n),i)/i;return i?(e.invert=function(n,t){var e=a-t,r=K(i)*Math.sqrt(n*n+e*e);return[Math.atan2(n,e)/i,2*Math.atan(Math.pow(a/r,1/i))-Ha]},e):Ne}function ke(n,t){function e(n,t){var e=i-t;return[e*Math.sin(u*n),i-e*Math.cos(u*n)]}var r=Math.cos(n),u=n===t?Math.sin(n):(r-Math.cos(t))/(t-n),i=r/u+n;return Ma(u)u;u++){for(;r>1&&Q(n[e[r-2]],n[e[r-1]],n[u])<=0;)--r;e[r++]=u}return e.slice(0,r)}function qe(n,t){return n[0]-t[0]||n[1]-t[1]}function Te(n,t,e){return(e[0]-t[0])*(n[1]-t[1])<(e[1]-t[1])*(n[0]-t[0])}function Re(n,t,e,r){var u=n[0],i=e[0],a=t[0]-u,o=r[0]-i,l=n[1],c=e[1],s=t[1]-l,f=r[1]-c,h=(o*(l-c)-f*(u-i))/(f*a-o*s);return[u+h*a,l+h*s]}function De(n){var t=n[0],e=n[n.length-1];return!(t[0]-e[0]||t[1]-e[1])}function Pe(){rr(this),this.edge=this.site=this.circle=null}function je(n){var t=ll.pop()||new Pe;return t.site=n,t}function Ue(n){Be(n),il.remove(n),ll.push(n),rr(n)}function Fe(n){var t=n.circle,e=t.x,r=t.cy,u={x:e,y:r},i=n.P,a=n.N,o=[n];Ue(n);for(var l=i;l.circle&&Ma(e-l.circle.x)s;++s)c=o[s],l=o[s-1],nr(c.edge,l.site,c.site,u);l=o[0],c=o[f-1],c.edge=Ke(l.site,c.site,null,u),$e(l),$e(c)}function He(n){for(var t,e,r,u,i=n.x,a=n.y,o=il._;o;)if(r=Oe(o,a)-i,r>Da)o=o.L;else{if(u=i-Ie(o,a),!(u>Da)){r>-Da?(t=o.P,e=o):u>-Da?(t=o,e=o.N):t=e=o;break}if(!o.R){t=o;break}o=o.R}var l=je(n);if(il.insert(t,l),t||e){if(t===e)return Be(t),e=je(t.site),il.insert(l,e),l.edge=e.edge=Ke(t.site,l.site),$e(t),void $e(e);if(!e)return void(l.edge=Ke(t.site,l.site));Be(t),Be(e);var c=t.site,s=c.x,f=c.y,h=n.x-s,g=n.y-f,p=e.site,v=p.x-s,d=p.y-f,m=2*(h*d-g*v),y=h*h+g*g,M=v*v+d*d,x={x:(d*y-g*M)/m+s,y:(h*M-v*y)/m+f};nr(e.edge,c,p,x),l.edge=Ke(c,n,null,x),e.edge=Ke(n,p,null,x),$e(t),$e(e)}}function Oe(n,t){var e=n.site,r=e.x,u=e.y,i=u-t;if(!i)return r;var a=n.P;if(!a)return-(1/0);e=a.site;var o=e.x,l=e.y,c=l-t;if(!c)return o;var s=o-r,f=1/i-1/c,h=s/c;return f?(-h+Math.sqrt(h*h-2*f*(s*s/(-2*c)-l+c/2+u-i/2)))/f+r:(r+o)/2}function Ie(n,t){var e=n.N;if(e)return Oe(e,t);var r=n.site;return r.y===t?r.x:1/0}function Ye(n){this.site=n,this.edges=[]}function Ze(n){for(var t,e,r,u,i,a,o,l,c,s,f=n[0][0],h=n[1][0],g=n[0][1],p=n[1][1],v=ul,d=v.length;d--;)if(i=v[d],i&&i.prepare())for(o=i.edges,l=o.length,a=0;l>a;)s=o[a].end(),r=s.x,u=s.y,c=o[++a%l].start(),t=c.x,e=c.y,(Ma(r-t)>Da||Ma(u-e)>Da)&&(o.splice(a,0,new tr(Qe(i.site,s,Ma(r-f)Da?{x:f,y:Ma(t-f)Da?{x:Ma(e-p)Da?{x:h,y:Ma(t-h)Da?{x:Ma(e-g)=-Pa)){var g=l*l+c*c,p=s*s+f*f,v=(f*g-c*p)/h,d=(l*p-s*g)/h,f=d+o,m=cl.pop()||new Xe;m.arc=n,m.site=u,m.x=v+a,m.y=f+Math.sqrt(v*v+d*d),m.cy=f,n.circle=m;for(var y=null,M=ol._;M;)if(m.yd||d>=o)return;if(h>p){if(i){if(i.y>=c)return}else i={x:d,y:l};e={x:d,y:c}}else{if(i){if(i.yr||r>1)if(h>p){if(i){if(i.y>=c)return}else i={x:(l-u)/r,y:l};e={x:(c-u)/r,y:c}}else{if(i){if(i.yg){if(i){if(i.x>=o)return}else i={x:a,y:r*a+u};e={x:o,y:r*o+u}}else{if(i){if(i.xi||f>a||r>h||u>g)){if(p=n.point){var p,v=t-n.x,d=e-n.y,m=v*v+d*d;if(l>m){var y=Math.sqrt(l=m);r=t-y,u=e-y,i=t+y,a=e+y,o=p}}for(var M=n.nodes,x=.5*(s+h),b=.5*(f+g),_=t>=x,w=e>=b,S=w<<1|_,k=S+4;k>S;++S)if(n=M[3&S])switch(3&S){case 0:c(n,s,f,x,b);break;case 1:c(n,x,f,h,b);break;case 2:c(n,s,b,x,g);break;case 3:c(n,x,b,h,g)}}}(n,r,u,i,a),o}function vr(n,t){n=oa.rgb(n),t=oa.rgb(t);var e=n.r,r=n.g,u=n.b,i=t.r-e,a=t.g-r,o=t.b-u;return function(n){return"#"+bn(Math.round(e+i*n))+bn(Math.round(r+a*n))+bn(Math.round(u+o*n))}}function dr(n,t){var e,r={},u={};for(e in n)e in t?r[e]=Mr(n[e],t[e]):u[e]=n[e];for(e in t)e in n||(u[e]=t[e]);return function(n){for(e in r)u[e]=r[e](n);return u}}function mr(n,t){return n=+n,t=+t,function(e){return n*(1-e)+t*e}}function yr(n,t){var e,r,u,i=fl.lastIndex=hl.lastIndex=0,a=-1,o=[],l=[];for(n+="",t+="";(e=fl.exec(n))&&(r=hl.exec(t));)(u=r.index)>i&&(u=t.slice(i,u),o[a]?o[a]+=u:o[++a]=u),(e=e[0])===(r=r[0])?o[a]?o[a]+=r:o[++a]=r:(o[++a]=null,l.push({i:a,x:mr(e,r)})),i=hl.lastIndex;return ir;++r)o[(e=l[r]).i]=e.x(n);return o.join("")})}function Mr(n,t){for(var e,r=oa.interpolators.length;--r>=0&&!(e=oa.interpolators[r](n,t)););return e}function xr(n,t){var e,r=[],u=[],i=n.length,a=t.length,o=Math.min(n.length,t.length);for(e=0;o>e;++e)r.push(Mr(n[e],t[e]));for(;i>e;++e)u[e]=n[e];for(;a>e;++e)u[e]=t[e];return function(n){for(e=0;o>e;++e)u[e]=r[e](n);return u}}function br(n){return function(t){return 0>=t?0:t>=1?1:n(t)}}function _r(n){return function(t){return 1-n(1-t)}}function wr(n){return function(t){return.5*(.5>t?n(2*t):2-n(2-2*t))}}function Sr(n){return n*n}function kr(n){return n*n*n}function Nr(n){if(0>=n)return 0;if(n>=1)return 1;var t=n*n,e=t*n;return 4*(.5>n?e:3*(n-t)+e-.75)}function Er(n){return function(t){return Math.pow(t,n)}}function Ar(n){return 1-Math.cos(n*Ha)}function Cr(n){return Math.pow(2,10*(n-1))}function zr(n){return 1-Math.sqrt(1-n*n)}function Lr(n,t){var e;return arguments.length<2&&(t=.45),arguments.length?e=t/Ua*Math.asin(1/n):(n=1,e=t/4),function(r){return 1+n*Math.pow(2,-10*r)*Math.sin((r-e)*Ua/t)}}function qr(n){return n||(n=1.70158),function(t){return t*t*((n+1)*t-n)}}function Tr(n){return 1/2.75>n?7.5625*n*n:2/2.75>n?7.5625*(n-=1.5/2.75)*n+.75:2.5/2.75>n?7.5625*(n-=2.25/2.75)*n+.9375:7.5625*(n-=2.625/2.75)*n+.984375}function Rr(n,t){n=oa.hcl(n),t=oa.hcl(t);var e=n.h,r=n.c,u=n.l,i=t.h-e,a=t.c-r,o=t.l-u;return isNaN(a)&&(a=0,r=isNaN(r)?t.c:r),isNaN(i)?(i=0,e=isNaN(e)?t.h:e):i>180?i-=360:-180>i&&(i+=360),function(n){return fn(e+i*n,r+a*n,u+o*n)+""}}function Dr(n,t){n=oa.hsl(n),t=oa.hsl(t);var e=n.h,r=n.s,u=n.l,i=t.h-e,a=t.s-r,o=t.l-u;return isNaN(a)&&(a=0,r=isNaN(r)?t.s:r),isNaN(i)?(i=0,e=isNaN(e)?t.h:e):i>180?i-=360:-180>i&&(i+=360),function(n){return cn(e+i*n,r+a*n,u+o*n)+""}}function Pr(n,t){n=oa.lab(n),t=oa.lab(t);var e=n.l,r=n.a,u=n.b,i=t.l-e,a=t.a-r,o=t.b-u;return function(n){return gn(e+i*n,r+a*n,u+o*n)+""}}function jr(n,t){return t-=n,function(e){return Math.round(n+t*e)}}function Ur(n){var t=[n.a,n.b],e=[n.c,n.d],r=Hr(t),u=Fr(t,e),i=Hr(Or(e,t,-u))||0;t[0]*e[1]180?t+=360:t-n>180&&(n+=360),r.push({i:e.push(Ir(e)+"rotate(",null,")")-2,x:mr(n,t)})):t&&e.push(Ir(e)+"rotate("+t+")")}function Vr(n,t,e,r){n!==t?r.push({i:e.push(Ir(e)+"skewX(",null,")")-2,x:mr(n,t)}):t&&e.push(Ir(e)+"skewX("+t+")")}function Xr(n,t,e,r){if(n[0]!==t[0]||n[1]!==t[1]){var u=e.push(Ir(e)+"scale(",null,",",null,")");r.push({i:u-4,x:mr(n[0],t[0])},{i:u-2,x:mr(n[1],t[1])})}else(1!==t[0]||1!==t[1])&&e.push(Ir(e)+"scale("+t+")")}function $r(n,t){var e=[],r=[];return n=oa.transform(n),t=oa.transform(t),Yr(n.translate,t.translate,e,r),Zr(n.rotate,t.rotate,e,r),Vr(n.skew,t.skew,e,r),Xr(n.scale,t.scale,e,r),n=t=null,function(n){for(var t,u=-1,i=r.length;++u=0;)e.push(u[r])}function au(n,t){for(var e=[n],r=[];null!=(n=e.pop());)if(r.push(n),(i=n.children)&&(u=i.length))for(var u,i,a=-1;++ae;++e)(t=n[e][1])>u&&(r=e,u=t);return r}function mu(n){return n.reduce(yu,0)}function yu(n,t){return n+t[1]}function Mu(n,t){return xu(n,Math.ceil(Math.log(t.length)/Math.LN2+1))}function xu(n,t){for(var e=-1,r=+n[0],u=(n[1]-r)/t,i=[];++e<=t;)i[e]=u*e+r;return i}function bu(n){return[oa.min(n),oa.max(n)]}function _u(n,t){return n.value-t.value}function wu(n,t){var e=n._pack_next;n._pack_next=t,t._pack_prev=n,t._pack_next=e,e._pack_prev=t}function Su(n,t){n._pack_next=t,t._pack_prev=n}function ku(n,t){var e=t.x-n.x,r=t.y-n.y,u=n.r+t.r;return.999*u*u>e*e+r*r}function Nu(n){function t(n){s=Math.min(n.x-n.r,s),f=Math.max(n.x+n.r,f),h=Math.min(n.y-n.r,h),g=Math.max(n.y+n.r,g)}if((e=n.children)&&(c=e.length)){var e,r,u,i,a,o,l,c,s=1/0,f=-(1/0),h=1/0,g=-(1/0);if(e.forEach(Eu),r=e[0],r.x=-r.r,r.y=0,t(r),c>1&&(u=e[1],u.x=u.r,u.y=0,t(u),c>2))for(i=e[2],zu(r,u,i),t(i),wu(r,i),r._pack_prev=i,wu(i,u),u=r._pack_next,a=3;c>a;a++){zu(r,u,i=e[a]);var p=0,v=1,d=1;for(o=u._pack_next;o!==u;o=o._pack_next,v++)if(ku(o,i)){p=1;break}if(1==p)for(l=r._pack_prev;l!==o._pack_prev&&!ku(l,i);l=l._pack_prev,d++);p?(d>v||v==d&&u.ra;a++)i=e[a],i.x-=m,i.y-=y,M=Math.max(M,i.r+Math.sqrt(i.x*i.x+i.y*i.y));n.r=M,e.forEach(Au)}}function Eu(n){n._pack_next=n._pack_prev=n}function Au(n){delete n._pack_next,delete n._pack_prev}function Cu(n,t,e,r){var u=n.children;if(n.x=t+=r*n.x,n.y=e+=r*n.y,n.r*=r,u)for(var i=-1,a=u.length;++i=0;)t=u[i],t.z+=e,t.m+=e,e+=t.s+(r+=t.c)}function Pu(n,t,e){return n.a.parent===t.parent?n.a:e}function ju(n){return 1+oa.max(n,function(n){return n.y})}function Uu(n){return n.reduce(function(n,t){return n+t.x},0)/n.length}function Fu(n){var t=n.children;return t&&t.length?Fu(t[0]):n}function Hu(n){var t,e=n.children;return e&&(t=e.length)?Hu(e[t-1]):n}function Ou(n){return{x:n.x,y:n.y,dx:n.dx,dy:n.dy}}function Iu(n,t){var e=n.x+t[3],r=n.y+t[0],u=n.dx-t[1]-t[3],i=n.dy-t[0]-t[2];return 0>u&&(e+=u/2,u=0),0>i&&(r+=i/2,i=0),{x:e,y:r,dx:u,dy:i}}function Yu(n){var t=n[0],e=n[n.length-1];return e>t?[t,e]:[e,t]}function Zu(n){return n.rangeExtent?n.rangeExtent():Yu(n.range())}function Vu(n,t,e,r){var u=e(n[0],n[1]),i=r(t[0],t[1]);return function(n){return i(u(n))}}function Xu(n,t){var e,r=0,u=n.length-1,i=n[r],a=n[u];return i>a&&(e=r,r=u,u=e,e=i,i=a,a=e),n[r]=t.floor(i),n[u]=t.ceil(a),n}function $u(n){return n?{floor:function(t){return Math.floor(t/n)*n},ceil:function(t){return Math.ceil(t/n)*n}}:wl}function Bu(n,t,e,r){var u=[],i=[],a=0,o=Math.min(n.length,t.length)-1;for(n[o]2?Bu:Vu,l=r?Wr:Br;return a=u(n,t,l,e),o=u(t,n,l,Mr),i}function i(n){return a(n)}var a,o;return i.invert=function(n){return o(n)},i.domain=function(t){return arguments.length?(n=t.map(Number),u()):n},i.range=function(n){return arguments.length?(t=n,u()):t},i.rangeRound=function(n){return i.range(n).interpolate(jr)},i.clamp=function(n){return arguments.length?(r=n,u()):r},i.interpolate=function(n){return arguments.length?(e=n,u()):e},i.ticks=function(t){return Qu(n,t)},i.tickFormat=function(t,e){return ni(n,t,e)},i.nice=function(t){return Gu(n,t),u()},i.copy=function(){return Wu(n,t,e,r)},u()}function Ju(n,t){return oa.rebind(n,t,"range","rangeRound","interpolate","clamp")}function Gu(n,t){return Xu(n,$u(Ku(n,t)[2]))}function Ku(n,t){null==t&&(t=10);var e=Yu(n),r=e[1]-e[0],u=Math.pow(10,Math.floor(Math.log(r/t)/Math.LN10)),i=t/r*u;return.15>=i?u*=10:.35>=i?u*=5:.75>=i&&(u*=2),e[0]=Math.ceil(e[0]/u)*u,e[1]=Math.floor(e[1]/u)*u+.5*u,e[2]=u,e}function Qu(n,t){return oa.range.apply(oa,Ku(n,t))}function ni(n,t,e){var r=Ku(n,t);if(e){var u=so.exec(e);if(u.shift(),"s"===u[8]){var i=oa.formatPrefix(Math.max(Ma(r[0]),Ma(r[1])));return u[7]||(u[7]="."+ti(i.scale(r[2]))),u[8]="f",e=oa.format(u.join("")),function(n){return e(i.scale(n))+i.symbol}}u[7]||(u[7]="."+ei(u[8],r)),e=u.join("")}else e=",."+ti(r[2])+"f";return oa.format(e)}function ti(n){return-Math.floor(Math.log(n)/Math.LN10+.01)}function ei(n,t){var e=ti(t[2]);return n in Sl?Math.abs(e-ti(Math.max(Ma(t[0]),Ma(t[1]))))+ +("e"!==n):e-2*("%"===n)}function ri(n,t,e,r){function u(n){return(e?Math.log(0>n?0:n):-Math.log(n>0?0:-n))/Math.log(t)}function i(n){return e?Math.pow(t,n):-Math.pow(t,-n)}function a(t){return n(u(t))}return a.invert=function(t){return i(n.invert(t))},a.domain=function(t){return arguments.length?(e=t[0]>=0,n.domain((r=t.map(Number)).map(u)),a):r},a.base=function(e){return arguments.length?(t=+e,n.domain(r.map(u)),a):t},a.nice=function(){var t=Xu(r.map(u),e?Math:Nl);return n.domain(t),r=t.map(i),a},a.ticks=function(){var n=Yu(r),a=[],o=n[0],l=n[1],c=Math.floor(u(o)),s=Math.ceil(u(l)),f=t%1?2:t;if(isFinite(s-c)){if(e){for(;s>c;c++)for(var h=1;f>h;h++)a.push(i(c)*h);a.push(i(c))}else for(a.push(i(c));c++0;h--)a.push(i(c)*h);for(c=0;a[c]l;s--);a=a.slice(c,s)}return a},a.tickFormat=function(n,t){if(!arguments.length)return kl;arguments.length<2?t=kl:"function"!=typeof t&&(t=oa.format(t));var r,o=Math.max(.1,n/a.ticks().length),l=e?(r=1e-12,Math.ceil):(r=-1e-12,Math.floor);return function(n){return n/i(l(u(n)+r))<=o?t(n):""}},a.copy=function(){return ri(n.copy(),t,e,r)},Ju(a,n)}function ui(n,t,e){function r(t){return n(u(t))}var u=ii(t),i=ii(1/t);return r.invert=function(t){return i(n.invert(t))},r.domain=function(t){return arguments.length?(n.domain((e=t.map(Number)).map(u)),r):e},r.ticks=function(n){return Qu(e,n)},r.tickFormat=function(n,t){return ni(e,n,t)},r.nice=function(n){return r.domain(Gu(e,n))},r.exponent=function(a){return arguments.length?(u=ii(t=a),i=ii(1/t),n.domain(e.map(u)),r):t},r.copy=function(){return ui(n.copy(),t,e)},Ju(r,n)}function ii(n){return function(t){return 0>t?-Math.pow(-t,n):Math.pow(t,n)}}function ai(n,t){function e(e){return i[((u.get(e)||("range"===t.t?u.set(e,n.push(e)):NaN))-1)%i.length]}function r(t,e){return oa.range(n.length).map(function(n){return t+e*n})}var u,i,a;return e.domain=function(r){if(!arguments.length)return n;n=[],u=new c;for(var i,a=-1,o=r.length;++ae?[NaN,NaN]:[e>0?o[e-1]:n[0],et?NaN:t/i+n,[t,t+1/i]},r.copy=function(){return li(n,t,e)},u()}function ci(n,t){function e(e){return e>=e?t[oa.bisect(n,e)]:void 0}return e.domain=function(t){return arguments.length?(n=t,e):n},e.range=function(n){return arguments.length?(t=n,e):t},e.invertExtent=function(e){return e=t.indexOf(e),[n[e-1],n[e]]},e.copy=function(){return ci(n,t)},e}function si(n){function t(n){return+n}return t.invert=t,t.domain=t.range=function(e){return arguments.length?(n=e.map(t),t):n},t.ticks=function(t){return Qu(n,t)},t.tickFormat=function(t,e){return ni(n,t,e)},t.copy=function(){return si(n)},t}function fi(){return 0}function hi(n){return n.innerRadius}function gi(n){return n.outerRadius}function pi(n){return n.startAngle}function vi(n){return n.endAngle}function di(n){return n&&n.padAngle}function mi(n,t,e,r){return(n-e)*t-(t-r)*n>0?0:1}function yi(n,t,e,r,u){var i=n[0]-t[0],a=n[1]-t[1],o=(u?r:-r)/Math.sqrt(i*i+a*a),l=o*a,c=-o*i,s=n[0]+l,f=n[1]+c,h=t[0]+l,g=t[1]+c,p=(s+h)/2,v=(f+g)/2,d=h-s,m=g-f,y=d*d+m*m,M=e-r,x=s*g-h*f,b=(0>m?-1:1)*Math.sqrt(Math.max(0,M*M*y-x*x)),_=(x*m-d*b)/y,w=(-x*d-m*b)/y,S=(x*m+d*b)/y,k=(-x*d+m*b)/y,N=_-p,E=w-v,A=S-p,C=k-v;return N*N+E*E>A*A+C*C&&(_=S,w=k),[[_-l,w-c],[_*e/M,w*e/M]]}function Mi(n){function t(t){function a(){c.push("M",i(n(s),o))}for(var l,c=[],s=[],f=-1,h=t.length,g=En(e),p=En(r);++f1?n.join("L"):n+"Z"}function bi(n){return n.join("L")+"Z"}function _i(n){for(var t=0,e=n.length,r=n[0],u=[r[0],",",r[1]];++t1&&u.push("H",r[0]),u.join("")}function wi(n){for(var t=0,e=n.length,r=n[0],u=[r[0],",",r[1]];++t1){o=t[1],i=n[l],l++,r+="C"+(u[0]+a[0])+","+(u[1]+a[1])+","+(i[0]-o[0])+","+(i[1]-o[1])+","+i[0]+","+i[1];for(var c=2;c9&&(u=3*t/Math.sqrt(u),a[o]=u*e,a[o+1]=u*r));for(o=-1;++o<=l;)u=(n[Math.min(l,o+1)][0]-n[Math.max(0,o-1)][0])/(6*(1+a[o]*a[o])),i.push([u||0,a[o]*u||0]);return i}function Fi(n){return n.length<3?xi(n):n[0]+Ai(n,Ui(n))}function Hi(n){for(var t,e,r,u=-1,i=n.length;++u=t?a(n-t):void(s.c=a)}function a(e){var u=p.active,i=p[u];i&&(i.timer.c=null,i.timer.t=NaN,--p.count,delete p[u],i.event&&i.event.interrupt.call(n,n.__data__,i.index));for(var a in p)if(r>+a){var c=p[a];c.timer.c=null,c.timer.t=NaN,--p.count,delete p[a]}s.c=o,qn(function(){return s.c&&o(e||1)&&(s.c=null,s.t=NaN),1},0,l),p.active=r,v.event&&v.event.start.call(n,n.__data__,t),g=[],v.tween.forEach(function(e,r){(r=r.call(n,n.__data__,t))&&g.push(r)}),h=v.ease,f=v.duration}function o(u){for(var i=u/f,a=h(i),o=g.length;o>0;)g[--o].call(n,a);return i>=1?(v.event&&v.event.end.call(n,n.__data__,t),--p.count?delete p[r]:delete n[e],1):void 0}var l,s,f,h,g,p=n[e]||(n[e]={active:0,count:0}),v=p[r];v||(l=u.time,s=qn(i,0,l),v=p[r]={tween:new c,time:l,timer:s,delay:u.delay,duration:u.duration,ease:u.ease,index:t},u=null,++p.count)}function na(n,t,e){n.attr("transform",function(n){var r=t(n);return"translate("+(isFinite(r)?r:e(n))+",0)"})}function ta(n,t,e){n.attr("transform",function(n){var r=t(n);return"translate(0,"+(isFinite(r)?r:e(n))+")"})}function ea(n){return n.toISOString()}function ra(n,t,e){function r(t){return n(t)}function u(n,e){var r=n[1]-n[0],u=r/e,i=oa.bisect(Gl,u);return i==Gl.length?[t.year,Ku(n.map(function(n){return n/31536e6}),e)[2]]:i?t[u/Gl[i-1]1?{floor:function(t){for(;e(t=n.floor(t));)t=ua(t-1);return t},ceil:function(t){for(;e(t=n.ceil(t));)t=ua(+t+1);return t}}:n))},r.ticks=function(n,t){var e=Yu(r.domain()),i=null==n?u(e,10):"number"==typeof n?u(e,n):!n.range&&[{range:n},t];return i&&(n=i[0],t=i[1]),n.range(e[0],ua(+e[1]+1),1>t?1:t)},r.tickFormat=function(){return e},r.copy=function(){return ra(n.copy(),t,e)},Ju(r,n)}function ua(n){return new Date(n)}function ia(n){return JSON.parse(n.responseText)}function aa(n){var t=sa.createRange();return t.selectNode(sa.body),t.createContextualFragment(n.responseText)}var oa={version:"3.5.10"},la=[].slice,ca=function(n){return la.call(n)},sa=this.document;if(sa)try{ca(sa.documentElement.childNodes)[0].nodeType}catch(fa){ca=function(n){for(var t=n.length,e=new Array(t);t--;)e[t]=n[t];return e}}if(Date.now||(Date.now=function(){return+new Date}),sa)try{sa.createElement("DIV").style.setProperty("opacity",0,"")}catch(ha){var ga=this.Element.prototype,pa=ga.setAttribute,va=ga.setAttributeNS,da=this.CSSStyleDeclaration.prototype,ma=da.setProperty;ga.setAttribute=function(n,t){pa.call(this,n,t+"")},ga.setAttributeNS=function(n,t,e){va.call(this,n,t,e+"")},da.setProperty=function(n,t,e){ma.call(this,n,t+"",e)}}oa.ascending=e,oa.descending=function(n,t){return n>t?-1:t>n?1:t>=n?0:NaN},oa.min=function(n,t){var e,r,u=-1,i=n.length;if(1===arguments.length){for(;++u=r){e=r;break}for(;++ur&&(e=r)}else{for(;++u=r){e=r;break}for(;++ur&&(e=r)}return e},oa.max=function(n,t){var e,r,u=-1,i=n.length;if(1===arguments.length){for(;++u=r){e=r;break}for(;++ue&&(e=r)}else{for(;++u=r){e=r;break}for(;++ue&&(e=r)}return e},oa.extent=function(n,t){var e,r,u,i=-1,a=n.length;if(1===arguments.length){for(;++i=r){e=u=r;break}for(;++ir&&(e=r),r>u&&(u=r))}else{for(;++i=r){e=u=r;break}for(;++ir&&(e=r),r>u&&(u=r))}return[e,u]},oa.sum=function(n,t){var e,r=0,i=n.length,a=-1;if(1===arguments.length)for(;++a1?l/(s-1):void 0},oa.deviation=function(){var n=oa.variance.apply(this,arguments);return n?Math.sqrt(n):n};var ya=i(e);oa.bisectLeft=ya.left,oa.bisect=oa.bisectRight=ya.right,oa.bisector=function(n){return i(1===n.length?function(t,r){return e(n(t),r)}:n)},oa.shuffle=function(n,t,e){(i=arguments.length)<3&&(e=n.length,2>i&&(t=0));for(var r,u,i=e-t;i;)u=Math.random()*i--|0,r=n[i+t],n[i+t]=n[u+t],n[u+t]=r;return n},oa.permute=function(n,t){for(var e=t.length,r=new Array(e);e--;)r[e]=n[t[e]];return r},oa.pairs=function(n){for(var t,e=0,r=n.length-1,u=n[0],i=new Array(0>r?0:r);r>e;)i[e]=[t=u,u=n[++e]];return i},oa.zip=function(){if(!(r=arguments.length))return[];for(var n=-1,t=oa.min(arguments,a),e=new Array(t);++n=0;)for(r=n[u],t=r.length;--t>=0;)e[--a]=r[t];return e};var Ma=Math.abs;oa.range=function(n,t,e){if(arguments.length<3&&(e=1,arguments.length<2&&(t=n,n=0)),(t-n)/e===1/0)throw new Error("infinite range");var r,u=[],i=o(Ma(e)),a=-1;if(n*=i,t*=i,e*=i,0>e)for(;(r=n+e*++a)>t;)u.push(r/i);else for(;(r=n+e*++a)=i.length)return r?r.call(u,a):e?a.sort(e):a;for(var l,s,f,h,g=-1,p=a.length,v=i[o++],d=new c;++g=i.length)return n;var r=[],u=a[e++];return n.forEach(function(n,u){r.push({key:n,values:t(u,e)})}),u?r.sort(function(n,t){return u(n.key,t.key)}):r}var e,r,u={},i=[],a=[];return u.map=function(t,e){return n(e,t,0)},u.entries=function(e){return t(n(oa.map,e,0),0)},u.key=function(n){return i.push(n),u},u.sortKeys=function(n){return a[i.length-1]=n,u},u.sortValues=function(n){return e=n,u},u.rollup=function(n){return r=n,u},u},oa.set=function(n){var t=new m;if(n)for(var e=0,r=n.length;r>e;++e)t.add(n[e]);return t},l(m,{has:h,add:function(n){return this._[s(n+="")]=!0,n},remove:g,values:p,size:v,empty:d,forEach:function(n){for(var t in this._)n.call(this,f(t))}}),oa.behavior={},oa.rebind=function(n,t){for(var e,r=1,u=arguments.length;++r=0&&(r=n.slice(e+1),n=n.slice(0,e)),n)return arguments.length<2?this[n].on(r):this[n].on(r,t);if(2===arguments.length){if(null==t)for(n in this)this.hasOwnProperty(n)&&this[n].on(r,null);return this}},oa.event=null,oa.requote=function(n){return n.replace(wa,"\\$&")};var wa=/[\\\^\$\*\+\?\|\[\]\(\)\.\{\}]/g,Sa={}.__proto__?function(n,t){n.__proto__=t}:function(n,t){for(var e in t)n[e]=t[e]},ka=function(n,t){return t.querySelector(n)},Na=function(n,t){return t.querySelectorAll(n)},Ea=function(n,t){var e=n.matches||n[x(n,"matchesSelector")];return(Ea=function(n,t){return e.call(n,t)})(n,t)};"function"==typeof Sizzle&&(ka=function(n,t){return Sizzle(n,t)[0]||null},Na=Sizzle,Ea=Sizzle.matchesSelector),oa.selection=function(){return oa.select(sa.documentElement)};var Aa=oa.selection.prototype=[];Aa.select=function(n){var t,e,r,u,i=[];n=A(n);for(var a=-1,o=this.length;++a=0&&"xmlns"!==(e=n.slice(0,t))&&(n=n.slice(t+1)),Ca.hasOwnProperty(e)?{space:Ca[e],local:n}:n}},Aa.attr=function(n,t){if(arguments.length<2){if("string"==typeof n){var e=this.node();return n=oa.ns.qualify(n),n.local?e.getAttributeNS(n.space,n.local):e.getAttribute(n)}for(t in n)this.each(z(t,n[t]));return this}return this.each(z(n,t))},Aa.classed=function(n,t){if(arguments.length<2){if("string"==typeof n){var e=this.node(),r=(n=T(n)).length,u=-1;if(t=e.classList){for(;++uu){if("string"!=typeof n){2>u&&(e="");for(r in n)this.each(P(r,n[r],e));return this}if(2>u){var i=this.node();return t(i).getComputedStyle(i,null).getPropertyValue(n)}r=""}return this.each(P(n,e,r))},Aa.property=function(n,t){if(arguments.length<2){if("string"==typeof n)return this.node()[n];for(t in n)this.each(j(t,n[t]));return this}return this.each(j(n,t))},Aa.text=function(n){return arguments.length?this.each("function"==typeof n?function(){var t=n.apply(this,arguments);this.textContent=null==t?"":t}:null==n?function(){this.textContent=""}:function(){this.textContent=n}):this.node().textContent},Aa.html=function(n){return arguments.length?this.each("function"==typeof n?function(){var t=n.apply(this,arguments);this.innerHTML=null==t?"":t}:null==n?function(){this.innerHTML=""}:function(){this.innerHTML=n}):this.node().innerHTML},Aa.append=function(n){return n=U(n),this.select(function(){return this.appendChild(n.apply(this,arguments))})},Aa.insert=function(n,t){return n=U(n),t=A(t),this.select(function(){return this.insertBefore(n.apply(this,arguments),t.apply(this,arguments)||null)})},Aa.remove=function(){return this.each(F)},Aa.data=function(n,t){function e(n,e){var r,u,i,a=n.length,f=e.length,h=Math.min(a,f),g=new Array(f),p=new Array(f),v=new Array(a);if(t){var d,m=new c,y=new Array(a);for(r=-1;++rr;++r)p[r]=H(e[r]);for(;a>r;++r)v[r]=n[r]}p.update=g,p.parentNode=g.parentNode=v.parentNode=n.parentNode,o.push(p),l.push(g),s.push(v)}var r,u,i=-1,a=this.length;if(!arguments.length){for(n=new Array(a=(r=this[0]).length);++ii;i++){u.push(t=[]),t.parentNode=(e=this[i]).parentNode;for(var o=0,l=e.length;l>o;o++)(r=e[o])&&n.call(r,r.__data__,o,i)&&t.push(r)}return E(u)},Aa.order=function(){for(var n=-1,t=this.length;++n=0;)(e=r[u])&&(i&&i!==e.nextSibling&&i.parentNode.insertBefore(e,i),i=e);return this},Aa.sort=function(n){n=I.apply(this,arguments);for(var t=-1,e=this.length;++tn;n++)for(var e=this[n],r=0,u=e.length;u>r;r++){var i=e[r];if(i)return i}return null},Aa.size=function(){var n=0;return Y(this,function(){++n}),n};var za=[];oa.selection.enter=Z,oa.selection.enter.prototype=za,za.append=Aa.append,za.empty=Aa.empty,za.node=Aa.node,za.call=Aa.call,za.size=Aa.size,za.select=function(n){for(var t,e,r,u,i,a=[],o=-1,l=this.length;++or){if("string"!=typeof n){2>r&&(t=!1);for(e in n)this.each(X(e,n[e],t));return this}if(2>r)return(r=this.node()["__on"+n])&&r._;e=!1}return this.each(X(n,t,e))};var La=oa.map({mouseenter:"mouseover",mouseleave:"mouseout"});sa&&La.forEach(function(n){"on"+n in sa&&La.remove(n)});var qa,Ta=0;oa.mouse=function(n){return J(n,k())};var Ra=this.navigator&&/WebKit/.test(this.navigator.userAgent)?-1:0;oa.touch=function(n,t,e){if(arguments.length<3&&(e=t,t=k().changedTouches),t)for(var r,u=0,i=t.length;i>u;++u)if((r=t[u]).identifier===e)return J(n,r)},oa.behavior.drag=function(){function n(){this.on("mousedown.drag",i).on("touchstart.drag",a)}function e(n,t,e,i,a){return function(){function o(){var n,e,r=t(h,v);r&&(n=r[0]-M[0],e=r[1]-M[1],p|=n|e,M=r,g({type:"drag",x:r[0]+c[0],y:r[1]+c[1],dx:n,dy:e}))}function l(){t(h,v)&&(m.on(i+d,null).on(a+d,null),y(p),g({type:"dragend"}))}var c,s=this,f=oa.event.target,h=s.parentNode,g=r.of(s,arguments),p=0,v=n(),d=".drag"+(null==v?"":"-"+v),m=oa.select(e(f)).on(i+d,o).on(a+d,l),y=W(f),M=t(h,v);u?(c=u.apply(s,arguments),c=[c.x-M[0],c.y-M[1]]):c=[0,0],g({type:"dragstart"})}}var r=N(n,"drag","dragstart","dragend"),u=null,i=e(b,oa.mouse,t,"mousemove","mouseup"),a=e(G,oa.touch,y,"touchmove","touchend");return n.origin=function(t){return arguments.length?(u=t,n):u},oa.rebind(n,r,"on")},oa.touches=function(n,t){return arguments.length<2&&(t=k().touches),t?ca(t).map(function(t){var e=J(n,t);return e.identifier=t.identifier,e}):[]};var Da=1e-6,Pa=Da*Da,ja=Math.PI,Ua=2*ja,Fa=Ua-Da,Ha=ja/2,Oa=ja/180,Ia=180/ja,Ya=Math.SQRT2,Za=2,Va=4;oa.interpolateZoom=function(n,t){var e,r,u=n[0],i=n[1],a=n[2],o=t[0],l=t[1],c=t[2],s=o-u,f=l-i,h=s*s+f*f;if(Pa>h)r=Math.log(c/a)/Ya,e=function(n){return[u+n*s,i+n*f,a*Math.exp(Ya*n*r)]};else{var g=Math.sqrt(h),p=(c*c-a*a+Va*h)/(2*a*Za*g),v=(c*c-a*a-Va*h)/(2*c*Za*g),d=Math.log(Math.sqrt(p*p+1)-p),m=Math.log(Math.sqrt(v*v+1)-v);r=(m-d)/Ya,e=function(n){var t=n*r,e=rn(d),o=a/(Za*g)*(e*un(Ya*t+d)-en(d));return[u+o*s,i+o*f,a*e/rn(Ya*t+d)]}}return e.duration=1e3*r,e},oa.behavior.zoom=function(){function n(n){n.on(L,f).on($a+".zoom",g).on("dblclick.zoom",p).on(R,h)}function e(n){return[(n[0]-k.x)/k.k,(n[1]-k.y)/k.k]}function r(n){return[n[0]*k.k+k.x,n[1]*k.k+k.y]}function u(n){k.k=Math.max(A[0],Math.min(A[1],n))}function i(n,t){t=r(t),k.x+=n[0]-t[0],k.y+=n[1]-t[1]}function a(t,e,r,a){t.__chart__={x:k.x,y:k.y,k:k.k},u(Math.pow(2,a)),i(d=e,r),t=oa.select(t),C>0&&(t=t.transition().duration(C)),t.call(n.event)}function o(){b&&b.domain(x.range().map(function(n){return(n-k.x)/k.k}).map(x.invert)),w&&w.domain(_.range().map(function(n){return(n-k.y)/k.k}).map(_.invert))}function l(n){z++||n({type:"zoomstart"})}function c(n){o(),n({type:"zoom",scale:k.k,translate:[k.x,k.y]})}function s(n){--z||(n({type:"zoomend"}),d=null)}function f(){function n(){o=1,i(oa.mouse(u),h),c(a)}function r(){f.on(q,null).on(T,null),g(o),s(a)}var u=this,a=D.of(u,arguments),o=0,f=oa.select(t(u)).on(q,n).on(T,r),h=e(oa.mouse(u)),g=W(u);Ol.call(u),l(a)}function h(){function n(){var n=oa.touches(p);return g=k.k,n.forEach(function(n){n.identifier in d&&(d[n.identifier]=e(n))}),n}function t(){var t=oa.event.target;oa.select(t).on(x,r).on(b,o),_.push(t);for(var e=oa.event.changedTouches,u=0,i=e.length;i>u;++u)d[e[u].identifier]=null;var l=n(),c=Date.now();if(1===l.length){if(500>c-M){var s=l[0];a(p,s,d[s.identifier],Math.floor(Math.log(k.k)/Math.LN2)+1),S()}M=c}else if(l.length>1){var s=l[0],f=l[1],h=s[0]-f[0],g=s[1]-f[1];m=h*h+g*g}}function r(){var n,t,e,r,a=oa.touches(p);Ol.call(p);for(var o=0,l=a.length;l>o;++o,r=null)if(e=a[o],r=d[e.identifier]){if(t)break;n=e,t=r}if(r){var s=(s=e[0]-n[0])*s+(s=e[1]-n[1])*s,f=m&&Math.sqrt(s/m);n=[(n[0]+e[0])/2,(n[1]+e[1])/2],t=[(t[0]+r[0])/2,(t[1]+r[1])/2],u(f*g)}M=null,i(n,t),c(v)}function o(){if(oa.event.touches.length){for(var t=oa.event.changedTouches,e=0,r=t.length;r>e;++e)delete d[t[e].identifier];for(var u in d)return void n()}oa.selectAll(_).on(y,null),w.on(L,f).on(R,h),N(),s(v)}var g,p=this,v=D.of(p,arguments),d={},m=0,y=".zoom-"+oa.event.changedTouches[0].identifier,x="touchmove"+y,b="touchend"+y,_=[],w=oa.select(p),N=W(p);t(),l(v),w.on(L,null).on(R,t)}function g(){var n=D.of(this,arguments);y?clearTimeout(y):(Ol.call(this),v=e(d=m||oa.mouse(this)),l(n)),y=setTimeout(function(){y=null,s(n)},50),S(),u(Math.pow(2,.002*Xa())*k.k),i(d,v),c(n)}function p(){var n=oa.mouse(this),t=Math.log(k.k)/Math.LN2;a(this,n,e(n),oa.event.shiftKey?Math.ceil(t)-1:Math.floor(t)+1)}var v,d,m,y,M,x,b,_,w,k={x:0,y:0,k:1},E=[960,500],A=Ba,C=250,z=0,L="mousedown.zoom",q="mousemove.zoom",T="mouseup.zoom",R="touchstart.zoom",D=N(n,"zoomstart","zoom","zoomend");return $a||($a="onwheel"in sa?(Xa=function(){return-oa.event.deltaY*(oa.event.deltaMode?120:1)},"wheel"):"onmousewheel"in sa?(Xa=function(){return oa.event.wheelDelta},"mousewheel"):(Xa=function(){return-oa.event.detail},"MozMousePixelScroll")),n.event=function(n){n.each(function(){var n=D.of(this,arguments),t=k;Fl?oa.select(this).transition().each("start.zoom",function(){k=this.__chart__||{x:0,y:0,k:1},l(n)}).tween("zoom:zoom",function(){var e=E[0],r=E[1],u=d?d[0]:e/2,i=d?d[1]:r/2,a=oa.interpolateZoom([(u-k.x)/k.k,(i-k.y)/k.k,e/k.k],[(u-t.x)/t.k,(i-t.y)/t.k,e/t.k]);return function(t){var r=a(t),o=e/r[2];this.__chart__=k={x:u-r[0]*o,y:i-r[1]*o,k:o},c(n)}}).each("interrupt.zoom",function(){s(n)}).each("end.zoom",function(){s(n)}):(this.__chart__=k,l(n),c(n),s(n))})},n.translate=function(t){return arguments.length?(k={x:+t[0],y:+t[1],k:k.k},o(),n):[k.x,k.y]},n.scale=function(t){return arguments.length?(k={x:k.x,y:k.y,k:null},u(+t),o(),n):k.k},n.scaleExtent=function(t){return arguments.length?(A=null==t?Ba:[+t[0],+t[1]],n):A},n.center=function(t){return arguments.length?(m=t&&[+t[0],+t[1]],n):m},n.size=function(t){return arguments.length?(E=t&&[+t[0],+t[1]],n):E},n.duration=function(t){return arguments.length?(C=+t,n):C},n.x=function(t){return arguments.length?(b=t,x=t.copy(),k={x:0,y:0,k:1},n):b},n.y=function(t){return arguments.length?(w=t,_=t.copy(),k={x:0,y:0,k:1},n):w},oa.rebind(n,D,"on")};var Xa,$a,Ba=[0,1/0];oa.color=on,on.prototype.toString=function(){return this.rgb()+""},oa.hsl=ln;var Wa=ln.prototype=new on;Wa.brighter=function(n){return n=Math.pow(.7,arguments.length?n:1),new ln(this.h,this.s,this.l/n)},Wa.darker=function(n){return n=Math.pow(.7,arguments.length?n:1),new ln(this.h,this.s,n*this.l)},Wa.rgb=function(){return cn(this.h,this.s,this.l)},oa.hcl=sn;var Ja=sn.prototype=new on;Ja.brighter=function(n){return new sn(this.h,this.c,Math.min(100,this.l+Ga*(arguments.length?n:1)))},Ja.darker=function(n){return new sn(this.h,this.c,Math.max(0,this.l-Ga*(arguments.length?n:1)))},Ja.rgb=function(){return fn(this.h,this.c,this.l).rgb()},oa.lab=hn;var Ga=18,Ka=.95047,Qa=1,no=1.08883,to=hn.prototype=new on;to.brighter=function(n){return new hn(Math.min(100,this.l+Ga*(arguments.length?n:1)),this.a,this.b)},to.darker=function(n){return new hn(Math.max(0,this.l-Ga*(arguments.length?n:1)),this.a,this.b)},to.rgb=function(){return gn(this.l,this.a,this.b)},oa.rgb=yn;var eo=yn.prototype=new on;eo.brighter=function(n){n=Math.pow(.7,arguments.length?n:1);var t=this.r,e=this.g,r=this.b,u=30;return t||e||r?(t&&u>t&&(t=u),e&&u>e&&(e=u),r&&u>r&&(r=u),new yn(Math.min(255,t/n),Math.min(255,e/n),Math.min(255,r/n))):new yn(u,u,u)},eo.darker=function(n){return n=Math.pow(.7,arguments.length?n:1),new yn(n*this.r,n*this.g,n*this.b)},eo.hsl=function(){return wn(this.r,this.g,this.b)},eo.toString=function(){return"#"+bn(this.r)+bn(this.g)+bn(this.b)};var ro=oa.map({aliceblue:15792383,antiquewhite:16444375,aqua:65535,aquamarine:8388564,azure:15794175,beige:16119260,bisque:16770244,black:0,blanchedalmond:16772045,blue:255,blueviolet:9055202,brown:10824234,burlywood:14596231,cadetblue:6266528,chartreuse:8388352,chocolate:13789470,coral:16744272,cornflowerblue:6591981,cornsilk:16775388,crimson:14423100,cyan:65535,darkblue:139,darkcyan:35723,darkgoldenrod:12092939,darkgray:11119017,darkgreen:25600,darkgrey:11119017,darkkhaki:12433259,darkmagenta:9109643,darkolivegreen:5597999,darkorange:16747520,darkorchid:10040012,darkred:9109504,darksalmon:15308410,darkseagreen:9419919,darkslateblue:4734347,darkslategray:3100495,darkslategrey:3100495,darkturquoise:52945,darkviolet:9699539,deeppink:16716947,deepskyblue:49151,dimgray:6908265,dimgrey:6908265,dodgerblue:2003199,firebrick:11674146,floralwhite:16775920,forestgreen:2263842,fuchsia:16711935,gainsboro:14474460,ghostwhite:16316671,gold:16766720,goldenrod:14329120,gray:8421504,green:32768,greenyellow:11403055,grey:8421504,honeydew:15794160,hotpink:16738740,indianred:13458524,indigo:4915330,ivory:16777200,khaki:15787660,lavender:15132410,lavenderblush:16773365,lawngreen:8190976,lemonchiffon:16775885,lightblue:11393254,lightcoral:15761536,lightcyan:14745599,lightgoldenrodyellow:16448210,lightgray:13882323,lightgreen:9498256,lightgrey:13882323,lightpink:16758465,lightsalmon:16752762,lightseagreen:2142890,lightskyblue:8900346,lightslategray:7833753,lightslategrey:7833753,lightsteelblue:11584734,lightyellow:16777184,lime:65280,limegreen:3329330,linen:16445670,magenta:16711935,maroon:8388608,mediumaquamarine:6737322,mediumblue:205,mediumorchid:12211667,mediumpurple:9662683,mediumseagreen:3978097,mediumslateblue:8087790,mediumspringgreen:64154,mediumturquoise:4772300,mediumvioletred:13047173,midnightblue:1644912,mintcream:16121850,mistyrose:16770273,moccasin:16770229,navajowhite:16768685,navy:128,oldlace:16643558,olive:8421376,olivedrab:7048739,orange:16753920,orangered:16729344,orchid:14315734,palegoldenrod:15657130,palegreen:10025880,paleturquoise:11529966,palevioletred:14381203,papayawhip:16773077,peachpuff:16767673,peru:13468991,pink:16761035,plum:14524637,powderblue:11591910,purple:8388736,rebeccapurple:6697881,red:16711680,rosybrown:12357519,royalblue:4286945,saddlebrown:9127187,salmon:16416882,sandybrown:16032864,seagreen:3050327,seashell:16774638,sienna:10506797,silver:12632256,skyblue:8900331,slateblue:6970061,slategray:7372944,slategrey:7372944,snow:16775930,springgreen:65407,steelblue:4620980,tan:13808780,teal:32896,thistle:14204888,tomato:16737095,turquoise:4251856,violet:15631086,wheat:16113331,white:16777215,whitesmoke:16119285,yellow:16776960,yellowgreen:10145074});ro.forEach(function(n,t){ro.set(n,Mn(t))}),oa.functor=En,oa.xhr=An(y),oa.dsv=function(n,t){function e(n,e,i){arguments.length<3&&(i=e,e=null);var a=Cn(n,t,null==e?r:u(e),i);return a.row=function(n){return arguments.length?a.response(null==(e=n)?r:u(n)):e},a}function r(n){return e.parse(n.responseText)}function u(n){return function(t){return e.parse(t.responseText,n)}}function i(t){return t.map(a).join(n)}function a(n){return o.test(n)?'"'+n.replace(/\"/g,'""')+'"':n}var o=new RegExp('["'+n+"\n]"),l=n.charCodeAt(0);return e.parse=function(n,t){var r;return e.parseRows(n,function(n,e){if(r)return r(n,e-1);var u=new Function("d","return {"+n.map(function(n,t){return JSON.stringify(n)+": d["+t+"]"}).join(",")+"}");r=t?function(n,e){return t(u(n),e)}:u})},e.parseRows=function(n,t){function e(){if(s>=c)return a;if(u)return u=!1,i;var t=s;if(34===n.charCodeAt(t)){for(var e=t;e++s;){var r=n.charCodeAt(s++),o=1;if(10===r)u=!0;else if(13===r)u=!0,10===n.charCodeAt(s)&&(++s,++o);else if(r!==l)continue;return n.slice(t,s-o)}return n.slice(t)}for(var r,u,i={},a={},o=[],c=n.length,s=0,f=0;(r=e())!==a;){for(var h=[];r!==i&&r!==a;)h.push(r),r=e();t&&null==(h=t(h,f++))||o.push(h)}return o},e.format=function(t){if(Array.isArray(t[0]))return e.formatRows(t);var r=new m,u=[];return t.forEach(function(n){for(var t in n)r.has(t)||u.push(r.add(t))}),[u.map(a).join(n)].concat(t.map(function(t){return u.map(function(n){return a(t[n])}).join(n)})).join("\n")},e.formatRows=function(n){return n.map(i).join("\n")},e},oa.csv=oa.dsv(",","text/csv"),oa.tsv=oa.dsv(" ","text/tab-separated-values");var uo,io,ao,oo,lo=this[x(this,"requestAnimationFrame")]||function(n){setTimeout(n,17)};oa.timer=function(){qn.apply(this,arguments)},oa.timer.flush=function(){Rn(),Dn()},oa.round=function(n,t){return t?Math.round(n*(t=Math.pow(10,t)))/t:Math.round(n)};var co=["y","z","a","f","p","n","\xb5","m","","k","M","G","T","P","E","Z","Y"].map(jn);oa.formatPrefix=function(n,t){var e=0;return(n=+n)&&(0>n&&(n*=-1),t&&(n=oa.round(n,Pn(n,t))),e=1+Math.floor(1e-12+Math.log(n)/Math.LN10),e=Math.max(-24,Math.min(24,3*Math.floor((e-1)/3)))),co[8+e/3]};var so=/(?:([^{])?([<>=^]))?([+\- ])?([$#])?(0)?(\d+)?(,)?(\.-?\d+)?([a-z%])?/i,fo=oa.map({b:function(n){return n.toString(2)},c:function(n){return String.fromCharCode(n)},o:function(n){return n.toString(8)},x:function(n){return n.toString(16)},X:function(n){return n.toString(16).toUpperCase()},g:function(n,t){return n.toPrecision(t)},e:function(n,t){return n.toExponential(t)},f:function(n,t){return n.toFixed(t)},r:function(n,t){return(n=oa.round(n,Pn(n,t))).toFixed(Math.max(0,Math.min(20,Pn(n*(1+1e-15),t))))}}),ho=oa.time={},go=Date;Hn.prototype={getDate:function(){return this._.getUTCDate()},getDay:function(){return this._.getUTCDay()},getFullYear:function(){return this._.getUTCFullYear()},getHours:function(){return this._.getUTCHours()},getMilliseconds:function(){return this._.getUTCMilliseconds()},getMinutes:function(){return this._.getUTCMinutes()},getMonth:function(){return this._.getUTCMonth()},getSeconds:function(){return this._.getUTCSeconds()},getTime:function(){return this._.getTime()},getTimezoneOffset:function(){return 0},valueOf:function(){return this._.valueOf()},setDate:function(){po.setUTCDate.apply(this._,arguments)},setDay:function(){po.setUTCDay.apply(this._,arguments)},setFullYear:function(){po.setUTCFullYear.apply(this._,arguments)},setHours:function(){po.setUTCHours.apply(this._,arguments)},setMilliseconds:function(){po.setUTCMilliseconds.apply(this._,arguments)},setMinutes:function(){po.setUTCMinutes.apply(this._,arguments)},setMonth:function(){po.setUTCMonth.apply(this._,arguments)},setSeconds:function(){po.setUTCSeconds.apply(this._,arguments)},setTime:function(){po.setTime.apply(this._,arguments)}};var po=Date.prototype;ho.year=On(function(n){return n=ho.day(n),n.setMonth(0,1),n},function(n,t){n.setFullYear(n.getFullYear()+t)},function(n){return n.getFullYear()}),ho.years=ho.year.range,ho.years.utc=ho.year.utc.range,ho.day=On(function(n){var t=new go(2e3,0);return t.setFullYear(n.getFullYear(),n.getMonth(),n.getDate()),t},function(n,t){n.setDate(n.getDate()+t)},function(n){return n.getDate()-1}),ho.days=ho.day.range,ho.days.utc=ho.day.utc.range,ho.dayOfYear=function(n){var t=ho.year(n);return Math.floor((n-t-6e4*(n.getTimezoneOffset()-t.getTimezoneOffset()))/864e5)},["sunday","monday","tuesday","wednesday","thursday","friday","saturday"].forEach(function(n,t){t=7-t;var e=ho[n]=On(function(n){return(n=ho.day(n)).setDate(n.getDate()-(n.getDay()+t)%7),n},function(n,t){n.setDate(n.getDate()+7*Math.floor(t))},function(n){var e=ho.year(n).getDay();return Math.floor((ho.dayOfYear(n)+(e+t)%7)/7)-(e!==t)});ho[n+"s"]=e.range,ho[n+"s"].utc=e.utc.range,ho[n+"OfYear"]=function(n){var e=ho.year(n).getDay();return Math.floor((ho.dayOfYear(n)+(e+t)%7)/7)}}),ho.week=ho.sunday,ho.weeks=ho.sunday.range,ho.weeks.utc=ho.sunday.utc.range,ho.weekOfYear=ho.sundayOfYear;var vo={"-":"",_:" ",0:"0"},mo=/^\s*\d+/,yo=/^%/;oa.locale=function(n){return{numberFormat:Un(n),timeFormat:Yn(n)}};var Mo=oa.locale({decimal:".",thousands:",",grouping:[3],currency:["$",""],dateTime:"%a %b %e %X %Y",date:"%m/%d/%Y",time:"%H:%M:%S",periods:["AM","PM"],days:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"], +shortDays:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],months:["January","February","March","April","May","June","July","August","September","October","November","December"],shortMonths:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]});oa.format=Mo.numberFormat,oa.geo={},st.prototype={s:0,t:0,add:function(n){ft(n,this.t,xo),ft(xo.s,this.s,this),this.s?this.t+=xo.t:this.s=xo.t},reset:function(){this.s=this.t=0},valueOf:function(){return this.s}};var xo=new st;oa.geo.stream=function(n,t){n&&bo.hasOwnProperty(n.type)?bo[n.type](n,t):ht(n,t)};var bo={Feature:function(n,t){ht(n.geometry,t)},FeatureCollection:function(n,t){for(var e=n.features,r=-1,u=e.length;++rn?4*ja+n:n,ko.lineStart=ko.lineEnd=ko.point=b}};oa.geo.bounds=function(){function n(n,t){M.push(x=[s=n,h=n]),f>t&&(f=t),t>g&&(g=t)}function t(t,e){var r=dt([t*Oa,e*Oa]);if(m){var u=yt(m,r),i=[u[1],-u[0],0],a=yt(i,u);bt(a),a=_t(a);var l=t-p,c=l>0?1:-1,v=a[0]*Ia*c,d=Ma(l)>180;if(d^(v>c*p&&c*t>v)){var y=a[1]*Ia;y>g&&(g=y)}else if(v=(v+360)%360-180,d^(v>c*p&&c*t>v)){var y=-a[1]*Ia;f>y&&(f=y)}else f>e&&(f=e),e>g&&(g=e);d?p>t?o(s,t)>o(s,h)&&(h=t):o(t,h)>o(s,h)&&(s=t):h>=s?(s>t&&(s=t),t>h&&(h=t)):t>p?o(s,t)>o(s,h)&&(h=t):o(t,h)>o(s,h)&&(s=t)}else n(t,e);m=r,p=t}function e(){b.point=t}function r(){x[0]=s,x[1]=h,b.point=n,m=null}function u(n,e){if(m){var r=n-p;y+=Ma(r)>180?r+(r>0?360:-360):r}else v=n,d=e;ko.point(n,e),t(n,e)}function i(){ko.lineStart()}function a(){u(v,d),ko.lineEnd(),Ma(y)>Da&&(s=-(h=180)),x[0]=s,x[1]=h,m=null}function o(n,t){return(t-=n)<0?t+360:t}function l(n,t){return n[0]-t[0]}function c(n,t){return t[0]<=t[1]?t[0]<=n&&n<=t[1]:nSo?(s=-(h=180),f=-(g=90)):y>Da?g=90:-Da>y&&(f=-90),x[0]=s,x[1]=h}};return function(n){g=h=-(s=f=1/0),M=[],oa.geo.stream(n,b);var t=M.length;if(t){M.sort(l);for(var e,r=1,u=M[0],i=[u];t>r;++r)e=M[r],c(e[0],u)||c(e[1],u)?(o(u[0],e[1])>o(u[0],u[1])&&(u[1]=e[1]),o(e[0],u[1])>o(u[0],u[1])&&(u[0]=e[0])):i.push(u=e);for(var a,e,p=-(1/0),t=i.length-1,r=0,u=i[t];t>=r;u=e,++r)e=i[r],(a=o(u[1],e[0]))>p&&(p=a,s=e[0],h=u[1])}return M=x=null,s===1/0||f===1/0?[[NaN,NaN],[NaN,NaN]]:[[s,f],[h,g]]}}(),oa.geo.centroid=function(n){No=Eo=Ao=Co=zo=Lo=qo=To=Ro=Do=Po=0,oa.geo.stream(n,jo);var t=Ro,e=Do,r=Po,u=t*t+e*e+r*r;return Pa>u&&(t=Lo,e=qo,r=To,Da>Eo&&(t=Ao,e=Co,r=zo),u=t*t+e*e+r*r,Pa>u)?[NaN,NaN]:[Math.atan2(e,t)*Ia,tn(r/Math.sqrt(u))*Ia]};var No,Eo,Ao,Co,zo,Lo,qo,To,Ro,Do,Po,jo={sphere:b,point:St,lineStart:Nt,lineEnd:Et,polygonStart:function(){jo.lineStart=At},polygonEnd:function(){jo.lineStart=Nt}},Uo=Rt(zt,Ut,Ht,[-ja,-ja/2]),Fo=1e9;oa.geo.clipExtent=function(){var n,t,e,r,u,i,a={stream:function(n){return u&&(u.valid=!1),u=i(n),u.valid=!0,u},extent:function(o){return arguments.length?(i=Zt(n=+o[0][0],t=+o[0][1],e=+o[1][0],r=+o[1][1]),u&&(u.valid=!1,u=null),a):[[n,t],[e,r]]}};return a.extent([[0,0],[960,500]])},(oa.geo.conicEqualArea=function(){return Vt(Xt)}).raw=Xt,oa.geo.albers=function(){return oa.geo.conicEqualArea().rotate([96,0]).center([-.6,38.7]).parallels([29.5,45.5]).scale(1070)},oa.geo.albersUsa=function(){function n(n){var i=n[0],a=n[1];return t=null,e(i,a),t||(r(i,a),t)||u(i,a),t}var t,e,r,u,i=oa.geo.albers(),a=oa.geo.conicEqualArea().rotate([154,0]).center([-2,58.5]).parallels([55,65]),o=oa.geo.conicEqualArea().rotate([157,0]).center([-3,19.9]).parallels([8,18]),l={point:function(n,e){t=[n,e]}};return n.invert=function(n){var t=i.scale(),e=i.translate(),r=(n[0]-e[0])/t,u=(n[1]-e[1])/t;return(u>=.12&&.234>u&&r>=-.425&&-.214>r?a:u>=.166&&.234>u&&r>=-.214&&-.115>r?o:i).invert(n)},n.stream=function(n){var t=i.stream(n),e=a.stream(n),r=o.stream(n);return{point:function(n,u){t.point(n,u),e.point(n,u),r.point(n,u)},sphere:function(){t.sphere(),e.sphere(),r.sphere()},lineStart:function(){t.lineStart(),e.lineStart(),r.lineStart()},lineEnd:function(){t.lineEnd(),e.lineEnd(),r.lineEnd()},polygonStart:function(){t.polygonStart(),e.polygonStart(),r.polygonStart()},polygonEnd:function(){t.polygonEnd(),e.polygonEnd(),r.polygonEnd()}}},n.precision=function(t){return arguments.length?(i.precision(t),a.precision(t),o.precision(t),n):i.precision()},n.scale=function(t){return arguments.length?(i.scale(t),a.scale(.35*t),o.scale(t),n.translate(i.translate())):i.scale()},n.translate=function(t){if(!arguments.length)return i.translate();var c=i.scale(),s=+t[0],f=+t[1];return e=i.translate(t).clipExtent([[s-.455*c,f-.238*c],[s+.455*c,f+.238*c]]).stream(l).point,r=a.translate([s-.307*c,f+.201*c]).clipExtent([[s-.425*c+Da,f+.12*c+Da],[s-.214*c-Da,f+.234*c-Da]]).stream(l).point,u=o.translate([s-.205*c,f+.212*c]).clipExtent([[s-.214*c+Da,f+.166*c+Da],[s-.115*c-Da,f+.234*c-Da]]).stream(l).point,n},n.scale(1070)};var Ho,Oo,Io,Yo,Zo,Vo,Xo={point:b,lineStart:b,lineEnd:b,polygonStart:function(){Oo=0,Xo.lineStart=$t},polygonEnd:function(){Xo.lineStart=Xo.lineEnd=Xo.point=b,Ho+=Ma(Oo/2)}},$o={point:Bt,lineStart:b,lineEnd:b,polygonStart:b,polygonEnd:b},Bo={point:Gt,lineStart:Kt,lineEnd:Qt,polygonStart:function(){Bo.lineStart=ne},polygonEnd:function(){Bo.point=Gt,Bo.lineStart=Kt,Bo.lineEnd=Qt}};oa.geo.path=function(){function n(n){return n&&("function"==typeof o&&i.pointRadius(+o.apply(this,arguments)),a&&a.valid||(a=u(i)),oa.geo.stream(n,a)),i.result()}function t(){return a=null,n}var e,r,u,i,a,o=4.5;return n.area=function(n){return Ho=0,oa.geo.stream(n,u(Xo)),Ho},n.centroid=function(n){return Ao=Co=zo=Lo=qo=To=Ro=Do=Po=0,oa.geo.stream(n,u(Bo)),Po?[Ro/Po,Do/Po]:To?[Lo/To,qo/To]:zo?[Ao/zo,Co/zo]:[NaN,NaN]},n.bounds=function(n){return Zo=Vo=-(Io=Yo=1/0),oa.geo.stream(n,u($o)),[[Io,Yo],[Zo,Vo]]},n.projection=function(n){return arguments.length?(u=(e=n)?n.stream||re(n):y,t()):e},n.context=function(n){return arguments.length?(i=null==(r=n)?new Wt:new te(n),"function"!=typeof o&&i.pointRadius(o),t()):r},n.pointRadius=function(t){return arguments.length?(o="function"==typeof t?t:(i.pointRadius(+t),+t),n):o},n.projection(oa.geo.albersUsa()).context(null)},oa.geo.transform=function(n){return{stream:function(t){var e=new ue(t);for(var r in n)e[r]=n[r];return e}}},ue.prototype={point:function(n,t){this.stream.point(n,t)},sphere:function(){this.stream.sphere()},lineStart:function(){this.stream.lineStart()},lineEnd:function(){this.stream.lineEnd()},polygonStart:function(){this.stream.polygonStart()},polygonEnd:function(){this.stream.polygonEnd()}},oa.geo.projection=ae,oa.geo.projectionMutator=oe,(oa.geo.equirectangular=function(){return ae(ce)}).raw=ce.invert=ce,oa.geo.rotation=function(n){function t(t){return t=n(t[0]*Oa,t[1]*Oa),t[0]*=Ia,t[1]*=Ia,t}return n=fe(n[0]%360*Oa,n[1]*Oa,n.length>2?n[2]*Oa:0),t.invert=function(t){return t=n.invert(t[0]*Oa,t[1]*Oa),t[0]*=Ia,t[1]*=Ia,t},t},se.invert=ce,oa.geo.circle=function(){function n(){var n="function"==typeof r?r.apply(this,arguments):r,t=fe(-n[0]*Oa,-n[1]*Oa,0).invert,u=[];return e(null,null,1,{point:function(n,e){u.push(n=t(n,e)),n[0]*=Ia,n[1]*=Ia}}),{type:"Polygon",coordinates:[u]}}var t,e,r=[0,0],u=6;return n.origin=function(t){return arguments.length?(r=t,n):r},n.angle=function(r){return arguments.length?(e=ve((t=+r)*Oa,u*Oa),n):t},n.precision=function(r){return arguments.length?(e=ve(t*Oa,(u=+r)*Oa),n):u},n.angle(90)},oa.geo.distance=function(n,t){var e,r=(t[0]-n[0])*Oa,u=n[1]*Oa,i=t[1]*Oa,a=Math.sin(r),o=Math.cos(r),l=Math.sin(u),c=Math.cos(u),s=Math.sin(i),f=Math.cos(i);return Math.atan2(Math.sqrt((e=f*a)*e+(e=c*s-l*f*o)*e),l*s+c*f*o)},oa.geo.graticule=function(){function n(){return{type:"MultiLineString",coordinates:t()}}function t(){return oa.range(Math.ceil(i/d)*d,u,d).map(h).concat(oa.range(Math.ceil(c/m)*m,l,m).map(g)).concat(oa.range(Math.ceil(r/p)*p,e,p).filter(function(n){return Ma(n%d)>Da}).map(s)).concat(oa.range(Math.ceil(o/v)*v,a,v).filter(function(n){return Ma(n%m)>Da}).map(f))}var e,r,u,i,a,o,l,c,s,f,h,g,p=10,v=p,d=90,m=360,y=2.5;return n.lines=function(){return t().map(function(n){return{type:"LineString",coordinates:n}})},n.outline=function(){return{type:"Polygon",coordinates:[h(i).concat(g(l).slice(1),h(u).reverse().slice(1),g(c).reverse().slice(1))]}},n.extent=function(t){return arguments.length?n.majorExtent(t).minorExtent(t):n.minorExtent()},n.majorExtent=function(t){return arguments.length?(i=+t[0][0],u=+t[1][0],c=+t[0][1],l=+t[1][1],i>u&&(t=i,i=u,u=t),c>l&&(t=c,c=l,l=t),n.precision(y)):[[i,c],[u,l]]},n.minorExtent=function(t){return arguments.length?(r=+t[0][0],e=+t[1][0],o=+t[0][1],a=+t[1][1],r>e&&(t=r,r=e,e=t),o>a&&(t=o,o=a,a=t),n.precision(y)):[[r,o],[e,a]]},n.step=function(t){return arguments.length?n.majorStep(t).minorStep(t):n.minorStep()},n.majorStep=function(t){return arguments.length?(d=+t[0],m=+t[1],n):[d,m]},n.minorStep=function(t){return arguments.length?(p=+t[0],v=+t[1],n):[p,v]},n.precision=function(t){return arguments.length?(y=+t,s=me(o,a,90),f=ye(r,e,y),h=me(c,l,90),g=ye(i,u,y),n):y},n.majorExtent([[-180,-90+Da],[180,90-Da]]).minorExtent([[-180,-80-Da],[180,80+Da]])},oa.geo.greatArc=function(){function n(){return{type:"LineString",coordinates:[t||r.apply(this,arguments),e||u.apply(this,arguments)]}}var t,e,r=Me,u=xe;return n.distance=function(){return oa.geo.distance(t||r.apply(this,arguments),e||u.apply(this,arguments))},n.source=function(e){return arguments.length?(r=e,t="function"==typeof e?null:e,n):r},n.target=function(t){return arguments.length?(u=t,e="function"==typeof t?null:t,n):u},n.precision=function(){return arguments.length?n:0},n},oa.geo.interpolate=function(n,t){return be(n[0]*Oa,n[1]*Oa,t[0]*Oa,t[1]*Oa)},oa.geo.length=function(n){return Wo=0,oa.geo.stream(n,Jo),Wo};var Wo,Jo={sphere:b,point:b,lineStart:_e,lineEnd:b,polygonStart:b,polygonEnd:b},Go=we(function(n){return Math.sqrt(2/(1+n))},function(n){return 2*Math.asin(n/2)});(oa.geo.azimuthalEqualArea=function(){return ae(Go)}).raw=Go;var Ko=we(function(n){var t=Math.acos(n);return t&&t/Math.sin(t)},y);(oa.geo.azimuthalEquidistant=function(){return ae(Ko)}).raw=Ko,(oa.geo.conicConformal=function(){return Vt(Se)}).raw=Se,(oa.geo.conicEquidistant=function(){return Vt(ke)}).raw=ke;var Qo=we(function(n){return 1/n},Math.atan);(oa.geo.gnomonic=function(){return ae(Qo)}).raw=Qo,Ne.invert=function(n,t){return[n,2*Math.atan(Math.exp(t))-Ha]},(oa.geo.mercator=function(){return Ee(Ne)}).raw=Ne;var nl=we(function(){return 1},Math.asin);(oa.geo.orthographic=function(){return ae(nl)}).raw=nl;var tl=we(function(n){return 1/(1+n)},function(n){return 2*Math.atan(n)});(oa.geo.stereographic=function(){return ae(tl)}).raw=tl,Ae.invert=function(n,t){return[-t,2*Math.atan(Math.exp(n))-Ha]},(oa.geo.transverseMercator=function(){var n=Ee(Ae),t=n.center,e=n.rotate;return n.center=function(n){return n?t([-n[1],n[0]]):(n=t(),[n[1],-n[0]])},n.rotate=function(n){return n?e([n[0],n[1],n.length>2?n[2]+90:90]):(n=e(),[n[0],n[1],n[2]-90])},e([0,0,90])}).raw=Ae,oa.geom={},oa.geom.hull=function(n){function t(n){if(n.length<3)return[];var t,u=En(e),i=En(r),a=n.length,o=[],l=[];for(t=0;a>t;t++)o.push([+u.call(this,n[t],t),+i.call(this,n[t],t),t]);for(o.sort(qe),t=0;a>t;t++)l.push([o[t][0],-o[t][1]]);var c=Le(o),s=Le(l),f=s[0]===c[0],h=s[s.length-1]===c[c.length-1],g=[];for(t=c.length-1;t>=0;--t)g.push(n[o[c[t]][2]]);for(t=+f;t=r&&c.x<=i&&c.y>=u&&c.y<=a?[[r,a],[i,a],[i,u],[r,u]]:[];s.point=n[o]}),t}function e(n){return n.map(function(n,t){return{x:Math.round(i(n,t)/Da)*Da,y:Math.round(a(n,t)/Da)*Da,i:t}})}var r=Ce,u=ze,i=r,a=u,o=sl;return n?t(n):(t.links=function(n){return or(e(n)).edges.filter(function(n){return n.l&&n.r}).map(function(t){return{source:n[t.l.i],target:n[t.r.i]}})},t.triangles=function(n){var t=[];return or(e(n)).cells.forEach(function(e,r){for(var u,i,a=e.site,o=e.edges.sort(Ve),l=-1,c=o.length,s=o[c-1].edge,f=s.l===a?s.r:s.l;++l=c,h=r>=s,g=h<<1|f;n.leaf=!1,n=n.nodes[g]||(n.nodes[g]=hr()),f?u=c:o=c,h?a=s:l=s,i(n,t,e,r,u,a,o,l)}var s,f,h,g,p,v,d,m,y,M=En(o),x=En(l);if(null!=t)v=t,d=e,m=r,y=u;else if(m=y=-(v=d=1/0),f=[],h=[],p=n.length,a)for(g=0;p>g;++g)s=n[g],s.xm&&(m=s.x),s.y>y&&(y=s.y),f.push(s.x),h.push(s.y);else for(g=0;p>g;++g){var b=+M(s=n[g],g),_=+x(s,g);v>b&&(v=b),d>_&&(d=_),b>m&&(m=b),_>y&&(y=_),f.push(b),h.push(_)}var w=m-v,S=y-d;w>S?y=d+w:m=v+S;var k=hr();if(k.add=function(n){i(k,n,+M(n,++g),+x(n,g),v,d,m,y)},k.visit=function(n){gr(n,k,v,d,m,y)},k.find=function(n){return pr(k,n[0],n[1],v,d,m,y)},g=-1,null==t){for(;++g=0?n.slice(0,t):n,r=t>=0?n.slice(t+1):"in";return e=pl.get(e)||gl,r=vl.get(r)||y,br(r(e.apply(null,la.call(arguments,1))))},oa.interpolateHcl=Rr,oa.interpolateHsl=Dr,oa.interpolateLab=Pr,oa.interpolateRound=jr,oa.transform=function(n){var t=sa.createElementNS(oa.ns.prefix.svg,"g");return(oa.transform=function(n){if(null!=n){t.setAttribute("transform",n);var e=t.transform.baseVal.consolidate()}return new Ur(e?e.matrix:dl)})(n)},Ur.prototype.toString=function(){return"translate("+this.translate+")rotate("+this.rotate+")skewX("+this.skew+")scale("+this.scale+")"};var dl={a:1,b:0,c:0,d:1,e:0,f:0};oa.interpolateTransform=$r,oa.layout={},oa.layout.bundle=function(){return function(n){for(var t=[],e=-1,r=n.length;++eo*o/m){if(v>l){var c=t.charge/l;n.px-=i*c,n.py-=a*c}return!0}if(t.point&&l&&v>l){var c=t.pointCharge/l;n.px-=i*c,n.py-=a*c}}return!t.charge}}function t(n){n.px=oa.event.x,n.py=oa.event.y,l.resume()}var e,r,u,i,a,o,l={},c=oa.dispatch("start","tick","end"),s=[1,1],f=.9,h=ml,g=yl,p=-30,v=Ml,d=.1,m=.64,M=[],x=[];return l.tick=function(){if((u*=.99)<.005)return e=null,c.end({type:"end",alpha:u=0}),!0;var t,r,l,h,g,v,m,y,b,_=M.length,w=x.length;for(r=0;w>r;++r)l=x[r],h=l.source,g=l.target,y=g.x-h.x,b=g.y-h.y,(v=y*y+b*b)&&(v=u*a[r]*((v=Math.sqrt(v))-i[r])/v,y*=v,b*=v,g.x-=y*(m=h.weight+g.weight?h.weight/(h.weight+g.weight):.5),g.y-=b*m,h.x+=y*(m=1-m),h.y+=b*m);if((m=u*d)&&(y=s[0]/2,b=s[1]/2,r=-1,m))for(;++r<_;)l=M[r],l.x+=(y-l.x)*m,l.y+=(b-l.y)*m;if(p)for(ru(t=oa.geom.quadtree(M),u,o),r=-1;++r<_;)(l=M[r]).fixed||t.visit(n(l));for(r=-1;++r<_;)l=M[r],l.fixed?(l.x=l.px,l.y=l.py):(l.x-=(l.px-(l.px=l.x))*f,l.y-=(l.py-(l.py=l.y))*f);c.tick({type:"tick",alpha:u})},l.nodes=function(n){return arguments.length?(M=n,l):M},l.links=function(n){return arguments.length?(x=n,l):x},l.size=function(n){return arguments.length?(s=n,l):s},l.linkDistance=function(n){return arguments.length?(h="function"==typeof n?n:+n,l):h},l.distance=l.linkDistance,l.linkStrength=function(n){return arguments.length?(g="function"==typeof n?n:+n,l):g},l.friction=function(n){return arguments.length?(f=+n,l):f},l.charge=function(n){return arguments.length?(p="function"==typeof n?n:+n,l):p},l.chargeDistance=function(n){return arguments.length?(v=n*n,l):Math.sqrt(v)},l.gravity=function(n){return arguments.length?(d=+n,l):d},l.theta=function(n){return arguments.length?(m=n*n,l):Math.sqrt(m)},l.alpha=function(n){return arguments.length?(n=+n,u?n>0?u=n:(e.c=null,e.t=NaN,e=null,c.end({type:"end",alpha:u=0})):n>0&&(c.start({type:"start",alpha:u=n}),e=qn(l.tick)),l):u},l.start=function(){function n(n,r){if(!e){for(e=new Array(u),l=0;u>l;++l)e[l]=[];for(l=0;c>l;++l){var i=x[l];e[i.source.index].push(i.target),e[i.target.index].push(i.source)}}for(var a,o=e[t],l=-1,s=o.length;++lt;++t)(r=M[t]).index=t,r.weight=0;for(t=0;c>t;++t)r=x[t],"number"==typeof r.source&&(r.source=M[r.source]),"number"==typeof r.target&&(r.target=M[r.target]),++r.source.weight,++r.target.weight;for(t=0;u>t;++t)r=M[t],isNaN(r.x)&&(r.x=n("x",f)),isNaN(r.y)&&(r.y=n("y",v)),isNaN(r.px)&&(r.px=r.x),isNaN(r.py)&&(r.py=r.y);if(i=[],"function"==typeof h)for(t=0;c>t;++t)i[t]=+h.call(this,x[t],t);else for(t=0;c>t;++t)i[t]=h;if(a=[],"function"==typeof g)for(t=0;c>t;++t)a[t]=+g.call(this,x[t],t);else for(t=0;c>t;++t)a[t]=g;if(o=[],"function"==typeof p)for(t=0;u>t;++t)o[t]=+p.call(this,M[t],t);else for(t=0;u>t;++t)o[t]=p;return l.resume()},l.resume=function(){return l.alpha(.1)},l.stop=function(){return l.alpha(0)},l.drag=function(){return r||(r=oa.behavior.drag().origin(y).on("dragstart.force",Qr).on("drag.force",t).on("dragend.force",nu)),arguments.length?void this.on("mouseover.force",tu).on("mouseout.force",eu).call(r):r},oa.rebind(l,c,"on")};var ml=20,yl=1,Ml=1/0;oa.layout.hierarchy=function(){function n(u){var i,a=[u],o=[];for(u.depth=0;null!=(i=a.pop());)if(o.push(i),(c=e.call(n,i,i.depth))&&(l=c.length)){for(var l,c,s;--l>=0;)a.push(s=c[l]),s.parent=i,s.depth=i.depth+1;r&&(i.value=0),i.children=c}else r&&(i.value=+r.call(n,i,i.depth)||0),delete i.children;return au(u,function(n){var e,u;t&&(e=n.children)&&e.sort(t),r&&(u=n.parent)&&(u.value+=n.value)}),o}var t=cu,e=ou,r=lu;return n.sort=function(e){return arguments.length?(t=e,n):t},n.children=function(t){return arguments.length?(e=t,n):e},n.value=function(t){return arguments.length?(r=t,n):r},n.revalue=function(t){return r&&(iu(t,function(n){n.children&&(n.value=0)}),au(t,function(t){var e;t.children||(t.value=+r.call(n,t,t.depth)||0),(e=t.parent)&&(e.value+=t.value)})),t},n},oa.layout.partition=function(){function n(t,e,r,u){var i=t.children;if(t.x=e,t.y=t.depth*u,t.dx=r,t.dy=u,i&&(a=i.length)){var a,o,l,c=-1;for(r=t.value?r/t.value:0;++cf?-1:1),p=oa.sum(c),v=p?(f-l*g)/p:0,d=oa.range(l),m=[];return null!=e&&d.sort(e===xl?function(n,t){return c[t]-c[n]}:function(n,t){return e(a[n],a[t])}),d.forEach(function(n){m[n]={data:a[n],value:o=c[n],startAngle:s,endAngle:s+=o*v+g,padAngle:h}}),m}var t=Number,e=xl,r=0,u=Ua,i=0;return n.value=function(e){return arguments.length?(t=e,n):t},n.sort=function(t){return arguments.length?(e=t,n):e},n.startAngle=function(t){return arguments.length?(r=t,n):r},n.endAngle=function(t){return arguments.length?(u=t,n):u},n.padAngle=function(t){return arguments.length?(i=t,n):i},n};var xl={};oa.layout.stack=function(){function n(o,l){if(!(h=o.length))return o;var c=o.map(function(e,r){return t.call(n,e,r)}),s=c.map(function(t){return t.map(function(t,e){return[i.call(n,t,e),a.call(n,t,e)]})}),f=e.call(n,s,l);c=oa.permute(c,f),s=oa.permute(s,f);var h,g,p,v,d=r.call(n,s,l),m=c[0].length;for(p=0;m>p;++p)for(u.call(n,c[0][p],v=d[p],s[0][p][1]),g=1;h>g;++g)u.call(n,c[g][p],v+=s[g-1][p][1],s[g][p][1]);return o}var t=y,e=pu,r=vu,u=gu,i=fu,a=hu;return n.values=function(e){return arguments.length?(t=e,n):t},n.order=function(t){return arguments.length?(e="function"==typeof t?t:bl.get(t)||pu,n):e},n.offset=function(t){return arguments.length?(r="function"==typeof t?t:_l.get(t)||vu,n):r},n.x=function(t){return arguments.length?(i=t,n):i},n.y=function(t){return arguments.length?(a=t,n):a},n.out=function(t){return arguments.length?(u=t,n):u},n};var bl=oa.map({"inside-out":function(n){var t,e,r=n.length,u=n.map(du),i=n.map(mu),a=oa.range(r).sort(function(n,t){return u[n]-u[t]}),o=0,l=0,c=[],s=[];for(t=0;r>t;++t)e=a[t],l>o?(o+=i[e],c.push(e)):(l+=i[e],s.push(e));return s.reverse().concat(c)},reverse:function(n){return oa.range(n.length).reverse()},"default":pu}),_l=oa.map({silhouette:function(n){var t,e,r,u=n.length,i=n[0].length,a=[],o=0,l=[];for(e=0;i>e;++e){for(t=0,r=0;u>t;t++)r+=n[t][e][1];r>o&&(o=r),a.push(r)}for(e=0;i>e;++e)l[e]=(o-a[e])/2;return l},wiggle:function(n){var t,e,r,u,i,a,o,l,c,s=n.length,f=n[0],h=f.length,g=[];for(g[0]=l=c=0,e=1;h>e;++e){for(t=0,u=0;s>t;++t)u+=n[t][e][1];for(t=0,i=0,o=f[e][0]-f[e-1][0];s>t;++t){for(r=0,a=(n[t][e][1]-n[t][e-1][1])/(2*o);t>r;++r)a+=(n[r][e][1]-n[r][e-1][1])/o;i+=a*n[t][e][1]}g[e]=l-=u?i/u*o:0,c>l&&(c=l)}for(e=0;h>e;++e)g[e]-=c;return g},expand:function(n){var t,e,r,u=n.length,i=n[0].length,a=1/u,o=[];for(e=0;i>e;++e){for(t=0,r=0;u>t;t++)r+=n[t][e][1];if(r)for(t=0;u>t;t++)n[t][e][1]/=r;else for(t=0;u>t;t++)n[t][e][1]=a}for(e=0;i>e;++e)o[e]=0;return o},zero:vu});oa.layout.histogram=function(){function n(n,i){for(var a,o,l=[],c=n.map(e,this),s=r.call(this,c,i),f=u.call(this,s,c,i),i=-1,h=c.length,g=f.length-1,p=t?1:1/h;++i0)for(i=-1;++i=s[0]&&o<=s[1]&&(a=l[oa.bisect(f,o,1,g)-1],a.y+=p,a.push(n[i]));return l}var t=!0,e=Number,r=bu,u=Mu;return n.value=function(t){return arguments.length?(e=t,n):e},n.range=function(t){return arguments.length?(r=En(t),n):r},n.bins=function(t){return arguments.length?(u="number"==typeof t?function(n){return xu(n,t)}:En(t),n):u},n.frequency=function(e){return arguments.length?(t=!!e,n):t},n},oa.layout.pack=function(){function n(n,i){var a=e.call(this,n,i),o=a[0],l=u[0],c=u[1],s=null==t?Math.sqrt:"function"==typeof t?t:function(){return t};if(o.x=o.y=0,au(o,function(n){n.r=+s(n.value)}),au(o,Nu),r){var f=r*(t?1:Math.max(2*o.r/l,2*o.r/c))/2;au(o,function(n){n.r+=f}),au(o,Nu),au(o,function(n){n.r-=f})}return Cu(o,l/2,c/2,t?1:1/Math.max(2*o.r/l,2*o.r/c)),a}var t,e=oa.layout.hierarchy().sort(_u),r=0,u=[1,1];return n.size=function(t){return arguments.length?(u=t,n):u},n.radius=function(e){return arguments.length?(t=null==e||"function"==typeof e?e:+e,n):t},n.padding=function(t){return arguments.length?(r=+t,n):r},uu(n,e)},oa.layout.tree=function(){function n(n,u){var s=a.call(this,n,u),f=s[0],h=t(f);if(au(h,e),h.parent.m=-h.z,iu(h,r),c)iu(f,i);else{var g=f,p=f,v=f;iu(f,function(n){n.xp.x&&(p=n),n.depth>v.depth&&(v=n)});var d=o(g,p)/2-g.x,m=l[0]/(p.x+o(p,g)/2+d),y=l[1]/(v.depth||1);iu(f,function(n){n.x=(n.x+d)*m,n.y=n.depth*y})}return s}function t(n){for(var t,e={A:null,children:[n]},r=[e];null!=(t=r.pop());)for(var u,i=t.children,a=0,o=i.length;o>a;++a)r.push((i[a]=u={_:i[a],parent:t,children:(u=i[a].children)&&u.slice()||[],A:null,a:null,z:0,m:0,c:0,s:0,t:null,i:a}).a=u);return e.children[0]}function e(n){var t=n.children,e=n.parent.children,r=n.i?e[n.i-1]:null;if(t.length){Du(n);var i=(t[0].z+t[t.length-1].z)/2;r?(n.z=r.z+o(n._,r._),n.m=n.z-i):n.z=i}else r&&(n.z=r.z+o(n._,r._));n.parent.A=u(n,r,n.parent.A||e[0])}function r(n){n._.x=n.z+n.parent.m,n.m+=n.parent.m}function u(n,t,e){if(t){for(var r,u=n,i=n,a=t,l=u.parent.children[0],c=u.m,s=i.m,f=a.m,h=l.m;a=Tu(a),u=qu(u),a&&u;)l=qu(l),i=Tu(i),i.a=n,r=a.z+f-u.z-c+o(a._,u._),r>0&&(Ru(Pu(a,n,e),n,r),c+=r,s+=r),f+=a.m,c+=u.m,h+=l.m,s+=i.m;a&&!Tu(i)&&(i.t=a,i.m+=f-s),u&&!qu(l)&&(l.t=u,l.m+=c-h,e=n)}return e}function i(n){n.x*=l[0],n.y=n.depth*l[1]}var a=oa.layout.hierarchy().sort(null).value(null),o=Lu,l=[1,1],c=null;return n.separation=function(t){return arguments.length?(o=t,n):o},n.size=function(t){return arguments.length?(c=null==(l=t)?i:null,n):c?null:l},n.nodeSize=function(t){return arguments.length?(c=null==(l=t)?null:i,n):c?l:null},uu(n,a)},oa.layout.cluster=function(){function n(n,i){var a,o=t.call(this,n,i),l=o[0],c=0;au(l,function(n){var t=n.children;t&&t.length?(n.x=Uu(t),n.y=ju(t)):(n.x=a?c+=e(n,a):0,n.y=0,a=n)});var s=Fu(l),f=Hu(l),h=s.x-e(s,f)/2,g=f.x+e(f,s)/2;return au(l,u?function(n){n.x=(n.x-l.x)*r[0],n.y=(l.y-n.y)*r[1]}:function(n){n.x=(n.x-h)/(g-h)*r[0],n.y=(1-(l.y?n.y/l.y:1))*r[1]}),o}var t=oa.layout.hierarchy().sort(null).value(null),e=Lu,r=[1,1],u=!1;return n.separation=function(t){return arguments.length?(e=t,n):e},n.size=function(t){return arguments.length?(u=null==(r=t),n):u?null:r},n.nodeSize=function(t){return arguments.length?(u=null!=(r=t),n):u?r:null},uu(n,t)},oa.layout.treemap=function(){function n(n,t){for(var e,r,u=-1,i=n.length;++ut?0:t),e.area=isNaN(r)||0>=r?0:r}function t(e){var i=e.children;if(i&&i.length){var a,o,l,c=f(e),s=[],h=i.slice(),p=1/0,v="slice"===g?c.dx:"dice"===g?c.dy:"slice-dice"===g?1&e.depth?c.dy:c.dx:Math.min(c.dx,c.dy);for(n(h,c.dx*c.dy/e.value),s.area=0;(l=h.length)>0;)s.push(a=h[l-1]),s.area+=a.area,"squarify"!==g||(o=r(s,v))<=p?(h.pop(),p=o):(s.area-=s.pop().area,u(s,v,c,!1),v=Math.min(c.dx,c.dy),s.length=s.area=0,p=1/0);s.length&&(u(s,v,c,!0),s.length=s.area=0),i.forEach(t)}}function e(t){var r=t.children;if(r&&r.length){var i,a=f(t),o=r.slice(),l=[];for(n(o,a.dx*a.dy/t.value),l.area=0;i=o.pop();)l.push(i),l.area+=i.area,null!=i.z&&(u(l,i.z?a.dx:a.dy,a,!o.length),l.length=l.area=0);r.forEach(e)}}function r(n,t){for(var e,r=n.area,u=0,i=1/0,a=-1,o=n.length;++ae&&(i=e),e>u&&(u=e));return r*=r,t*=t,r?Math.max(t*u*p/r,r/(t*i*p)):1/0}function u(n,t,e,r){var u,i=-1,a=n.length,o=e.x,c=e.y,s=t?l(n.area/t):0; +if(t==e.dx){for((r||s>e.dy)&&(s=e.dy);++ie.dx)&&(s=e.dx);++ie&&(t=1),1>e&&(n=0),function(){var e,r,u;do e=2*Math.random()-1,r=2*Math.random()-1,u=e*e+r*r;while(!u||u>1);return n+t*e*Math.sqrt(-2*Math.log(u)/u)}},logNormal:function(){var n=oa.random.normal.apply(oa,arguments);return function(){return Math.exp(n())}},bates:function(n){var t=oa.random.irwinHall(n);return function(){return t()/n}},irwinHall:function(n){return function(){for(var t=0,e=0;n>e;e++)t+=Math.random();return t}}},oa.scale={};var wl={floor:y,ceil:y};oa.scale.linear=function(){return Wu([0,1],[0,1],Mr,!1)};var Sl={s:1,g:1,p:1,r:1,e:1};oa.scale.log=function(){return ri(oa.scale.linear().domain([0,1]),10,!0,[1,10])};var kl=oa.format(".0e"),Nl={floor:function(n){return-Math.ceil(-n)},ceil:function(n){return-Math.floor(-n)}};oa.scale.pow=function(){return ui(oa.scale.linear(),1,[0,1])},oa.scale.sqrt=function(){return oa.scale.pow().exponent(.5)},oa.scale.ordinal=function(){return ai([],{t:"range",a:[[]]})},oa.scale.category10=function(){return oa.scale.ordinal().range(El)},oa.scale.category20=function(){return oa.scale.ordinal().range(Al)},oa.scale.category20b=function(){return oa.scale.ordinal().range(Cl)},oa.scale.category20c=function(){return oa.scale.ordinal().range(zl)};var El=[2062260,16744206,2924588,14034728,9725885,9197131,14907330,8355711,12369186,1556175].map(xn),Al=[2062260,11454440,16744206,16759672,2924588,10018698,14034728,16750742,9725885,12955861,9197131,12885140,14907330,16234194,8355711,13092807,12369186,14408589,1556175,10410725].map(xn),Cl=[3750777,5395619,7040719,10264286,6519097,9216594,11915115,13556636,9202993,12426809,15186514,15190932,8666169,11356490,14049643,15177372,8077683,10834324,13528509,14589654].map(xn),zl=[3244733,7057110,10406625,13032431,15095053,16616764,16625259,16634018,3253076,7652470,10607003,13101504,7695281,10394312,12369372,14342891,6513507,9868950,12434877,14277081].map(xn);oa.scale.quantile=function(){return oi([],[])},oa.scale.quantize=function(){return li(0,1,[0,1])},oa.scale.threshold=function(){return ci([.5],[0,1])},oa.scale.identity=function(){return si([0,1])},oa.svg={},oa.svg.arc=function(){function n(){var n=Math.max(0,+e.apply(this,arguments)),c=Math.max(0,+r.apply(this,arguments)),s=a.apply(this,arguments)-Ha,f=o.apply(this,arguments)-Ha,h=Math.abs(f-s),g=s>f?0:1;if(n>c&&(p=c,c=n,n=p),h>=Fa)return t(c,g)+(n?t(n,1-g):"")+"Z";var p,v,d,m,y,M,x,b,_,w,S,k,N=0,E=0,A=[];if((m=(+l.apply(this,arguments)||0)/2)&&(d=i===Ll?Math.sqrt(n*n+c*c):+i.apply(this,arguments),g||(E*=-1),c&&(E=tn(d/c*Math.sin(m))),n&&(N=tn(d/n*Math.sin(m)))),c){y=c*Math.cos(s+E),M=c*Math.sin(s+E),x=c*Math.cos(f-E),b=c*Math.sin(f-E);var C=Math.abs(f-s-2*E)<=ja?0:1;if(E&&mi(y,M,x,b)===g^C){var z=(s+f)/2;y=c*Math.cos(z),M=c*Math.sin(z),x=b=null}}else y=M=0;if(n){_=n*Math.cos(f-N),w=n*Math.sin(f-N),S=n*Math.cos(s+N),k=n*Math.sin(s+N);var L=Math.abs(s-f+2*N)<=ja?0:1;if(N&&mi(_,w,S,k)===1-g^L){var q=(s+f)/2;_=n*Math.cos(q),w=n*Math.sin(q),S=k=null}}else _=w=0;if(h>Da&&(p=Math.min(Math.abs(c-n)/2,+u.apply(this,arguments)))>.001){v=c>n^g?0:1;var T=p,R=p;if(ja>h){var D=null==S?[_,w]:null==x?[y,M]:Re([y,M],[S,k],[x,b],[_,w]),P=y-D[0],j=M-D[1],U=x-D[0],F=b-D[1],H=1/Math.sin(Math.acos((P*U+j*F)/(Math.sqrt(P*P+j*j)*Math.sqrt(U*U+F*F)))/2),O=Math.sqrt(D[0]*D[0]+D[1]*D[1]);R=Math.min(p,(n-O)/(H-1)),T=Math.min(p,(c-O)/(H+1))}if(null!=x){var I=yi(null==S?[_,w]:[S,k],[y,M],c,T,g),Y=yi([x,b],[_,w],c,T,g);p===T?A.push("M",I[0],"A",T,",",T," 0 0,",v," ",I[1],"A",c,",",c," 0 ",1-g^mi(I[1][0],I[1][1],Y[1][0],Y[1][1]),",",g," ",Y[1],"A",T,",",T," 0 0,",v," ",Y[0]):A.push("M",I[0],"A",T,",",T," 0 1,",v," ",Y[0])}else A.push("M",y,",",M);if(null!=S){var Z=yi([y,M],[S,k],n,-R,g),V=yi([_,w],null==x?[y,M]:[x,b],n,-R,g);p===R?A.push("L",V[0],"A",R,",",R," 0 0,",v," ",V[1],"A",n,",",n," 0 ",g^mi(V[1][0],V[1][1],Z[1][0],Z[1][1]),",",1-g," ",Z[1],"A",R,",",R," 0 0,",v," ",Z[0]):A.push("L",V[0],"A",R,",",R," 0 0,",v," ",Z[0])}else A.push("L",_,",",w)}else A.push("M",y,",",M),null!=x&&A.push("A",c,",",c," 0 ",C,",",g," ",x,",",b),A.push("L",_,",",w),null!=S&&A.push("A",n,",",n," 0 ",L,",",1-g," ",S,",",k);return A.push("Z"),A.join("")}function t(n,t){return"M0,"+n+"A"+n+","+n+" 0 1,"+t+" 0,"+-n+"A"+n+","+n+" 0 1,"+t+" 0,"+n}var e=hi,r=gi,u=fi,i=Ll,a=pi,o=vi,l=di;return n.innerRadius=function(t){return arguments.length?(e=En(t),n):e},n.outerRadius=function(t){return arguments.length?(r=En(t),n):r},n.cornerRadius=function(t){return arguments.length?(u=En(t),n):u},n.padRadius=function(t){return arguments.length?(i=t==Ll?Ll:En(t),n):i},n.startAngle=function(t){return arguments.length?(a=En(t),n):a},n.endAngle=function(t){return arguments.length?(o=En(t),n):o},n.padAngle=function(t){return arguments.length?(l=En(t),n):l},n.centroid=function(){var n=(+e.apply(this,arguments)+ +r.apply(this,arguments))/2,t=(+a.apply(this,arguments)+ +o.apply(this,arguments))/2-Ha;return[Math.cos(t)*n,Math.sin(t)*n]},n};var Ll="auto";oa.svg.line=function(){return Mi(y)};var ql=oa.map({linear:xi,"linear-closed":bi,step:_i,"step-before":wi,"step-after":Si,basis:zi,"basis-open":Li,"basis-closed":qi,bundle:Ti,cardinal:Ei,"cardinal-open":ki,"cardinal-closed":Ni,monotone:Fi});ql.forEach(function(n,t){t.key=n,t.closed=/-closed$/.test(n)});var Tl=[0,2/3,1/3,0],Rl=[0,1/3,2/3,0],Dl=[0,1/6,2/3,1/6];oa.svg.line.radial=function(){var n=Mi(Hi);return n.radius=n.x,delete n.x,n.angle=n.y,delete n.y,n},wi.reverse=Si,Si.reverse=wi,oa.svg.area=function(){return Oi(y)},oa.svg.area.radial=function(){var n=Oi(Hi);return n.radius=n.x,delete n.x,n.innerRadius=n.x0,delete n.x0,n.outerRadius=n.x1,delete n.x1,n.angle=n.y,delete n.y,n.startAngle=n.y0,delete n.y0,n.endAngle=n.y1,delete n.y1,n},oa.svg.chord=function(){function n(n,o){var l=t(this,i,n,o),c=t(this,a,n,o);return"M"+l.p0+r(l.r,l.p1,l.a1-l.a0)+(e(l,c)?u(l.r,l.p1,l.r,l.p0):u(l.r,l.p1,c.r,c.p0)+r(c.r,c.p1,c.a1-c.a0)+u(c.r,c.p1,l.r,l.p0))+"Z"}function t(n,t,e,r){var u=t.call(n,e,r),i=o.call(n,u,r),a=l.call(n,u,r)-Ha,s=c.call(n,u,r)-Ha;return{r:i,a0:a,a1:s,p0:[i*Math.cos(a),i*Math.sin(a)],p1:[i*Math.cos(s),i*Math.sin(s)]}}function e(n,t){return n.a0==t.a0&&n.a1==t.a1}function r(n,t,e){return"A"+n+","+n+" 0 "+ +(e>ja)+",1 "+t}function u(n,t,e,r){return"Q 0,0 "+r}var i=Me,a=xe,o=Ii,l=pi,c=vi;return n.radius=function(t){return arguments.length?(o=En(t),n):o},n.source=function(t){return arguments.length?(i=En(t),n):i},n.target=function(t){return arguments.length?(a=En(t),n):a},n.startAngle=function(t){return arguments.length?(l=En(t),n):l},n.endAngle=function(t){return arguments.length?(c=En(t),n):c},n},oa.svg.diagonal=function(){function n(n,u){var i=t.call(this,n,u),a=e.call(this,n,u),o=(i.y+a.y)/2,l=[i,{x:i.x,y:o},{x:a.x,y:o},a];return l=l.map(r),"M"+l[0]+"C"+l[1]+" "+l[2]+" "+l[3]}var t=Me,e=xe,r=Yi;return n.source=function(e){return arguments.length?(t=En(e),n):t},n.target=function(t){return arguments.length?(e=En(t),n):e},n.projection=function(t){return arguments.length?(r=t,n):r},n},oa.svg.diagonal.radial=function(){var n=oa.svg.diagonal(),t=Yi,e=n.projection;return n.projection=function(n){return arguments.length?e(Zi(t=n)):t},n},oa.svg.symbol=function(){function n(n,r){return(Pl.get(t.call(this,n,r))||$i)(e.call(this,n,r))}var t=Xi,e=Vi;return n.type=function(e){return arguments.length?(t=En(e),n):t},n.size=function(t){return arguments.length?(e=En(t),n):e},n};var Pl=oa.map({circle:$i,cross:function(n){var t=Math.sqrt(n/5)/2;return"M"+-3*t+","+-t+"H"+-t+"V"+-3*t+"H"+t+"V"+-t+"H"+3*t+"V"+t+"H"+t+"V"+3*t+"H"+-t+"V"+t+"H"+-3*t+"Z"},diamond:function(n){var t=Math.sqrt(n/(2*Ul)),e=t*Ul;return"M0,"+-t+"L"+e+",0 0,"+t+" "+-e+",0Z"},square:function(n){var t=Math.sqrt(n)/2;return"M"+-t+","+-t+"L"+t+","+-t+" "+t+","+t+" "+-t+","+t+"Z"},"triangle-down":function(n){var t=Math.sqrt(n/jl),e=t*jl/2;return"M0,"+e+"L"+t+","+-e+" "+-t+","+-e+"Z"},"triangle-up":function(n){var t=Math.sqrt(n/jl),e=t*jl/2;return"M0,"+-e+"L"+t+","+e+" "+-t+","+e+"Z"}});oa.svg.symbolTypes=Pl.keys();var jl=Math.sqrt(3),Ul=Math.tan(30*Oa);Aa.transition=function(n){for(var t,e,r=Fl||++Yl,u=Ki(n),i=[],a=Hl||{time:Date.now(),ease:Nr,delay:0,duration:250},o=-1,l=this.length;++oi;i++){u.push(t=[]);for(var e=this[i],o=0,l=e.length;l>o;o++)(r=e[o])&&n.call(r,r.__data__,o,i)&&t.push(r)}return Wi(u,this.namespace,this.id)},Il.tween=function(n,t){var e=this.id,r=this.namespace;return arguments.length<2?this.node()[r][e].tween.get(n):Y(this,null==t?function(t){t[r][e].tween.remove(n)}:function(u){u[r][e].tween.set(n,t)})},Il.attr=function(n,t){function e(){this.removeAttribute(o)}function r(){this.removeAttributeNS(o.space,o.local)}function u(n){return null==n?e:(n+="",function(){var t,e=this.getAttribute(o);return e!==n&&(t=a(e,n),function(n){this.setAttribute(o,t(n))})})}function i(n){return null==n?r:(n+="",function(){var t,e=this.getAttributeNS(o.space,o.local);return e!==n&&(t=a(e,n),function(n){this.setAttributeNS(o.space,o.local,t(n))})})}if(arguments.length<2){for(t in n)this.attr(t,n[t]);return this}var a="transform"==n?$r:Mr,o=oa.ns.qualify(n);return Ji(this,"attr."+n,t,o.local?i:u)},Il.attrTween=function(n,t){function e(n,e){var r=t.call(this,n,e,this.getAttribute(u));return r&&function(n){this.setAttribute(u,r(n))}}function r(n,e){var r=t.call(this,n,e,this.getAttributeNS(u.space,u.local));return r&&function(n){this.setAttributeNS(u.space,u.local,r(n))}}var u=oa.ns.qualify(n);return this.tween("attr."+n,u.local?r:e)},Il.style=function(n,e,r){function u(){this.style.removeProperty(n)}function i(e){return null==e?u:(e+="",function(){var u,i=t(this).getComputedStyle(this,null).getPropertyValue(n);return i!==e&&(u=Mr(i,e),function(t){this.style.setProperty(n,u(t),r)})})}var a=arguments.length;if(3>a){if("string"!=typeof n){2>a&&(e="");for(r in n)this.style(r,n[r],e);return this}r=""}return Ji(this,"style."+n,e,i)},Il.styleTween=function(n,e,r){function u(u,i){var a=e.call(this,u,i,t(this).getComputedStyle(this,null).getPropertyValue(n));return a&&function(t){this.style.setProperty(n,a(t),r)}}return arguments.length<3&&(r=""),this.tween("style."+n,u)},Il.text=function(n){return Ji(this,"text",n,Gi)},Il.remove=function(){var n=this.namespace;return this.each("end.transition",function(){var t;this[n].count<2&&(t=this.parentNode)&&t.removeChild(this)})},Il.ease=function(n){var t=this.id,e=this.namespace;return arguments.length<1?this.node()[e][t].ease:("function"!=typeof n&&(n=oa.ease.apply(oa,arguments)),Y(this,function(r){r[e][t].ease=n}))},Il.delay=function(n){var t=this.id,e=this.namespace;return arguments.length<1?this.node()[e][t].delay:Y(this,"function"==typeof n?function(r,u,i){r[e][t].delay=+n.call(r,r.__data__,u,i)}:(n=+n,function(r){r[e][t].delay=n}))},Il.duration=function(n){var t=this.id,e=this.namespace;return arguments.length<1?this.node()[e][t].duration:Y(this,"function"==typeof n?function(r,u,i){r[e][t].duration=Math.max(1,n.call(r,r.__data__,u,i))}:(n=Math.max(1,n),function(r){r[e][t].duration=n}))},Il.each=function(n,t){var e=this.id,r=this.namespace;if(arguments.length<2){var u=Hl,i=Fl;try{Fl=e,Y(this,function(t,u,i){Hl=t[r][e],n.call(t,t.__data__,u,i)})}finally{Hl=u,Fl=i}}else Y(this,function(u){var i=u[r][e];(i.event||(i.event=oa.dispatch("start","end","interrupt"))).on(n,t)});return this},Il.transition=function(){for(var n,t,e,r,u=this.id,i=++Yl,a=this.namespace,o=[],l=0,c=this.length;c>l;l++){o.push(n=[]);for(var t=this[l],s=0,f=t.length;f>s;s++)(e=t[s])&&(r=e[a][u],Qi(e,s,a,i,{time:r.time,ease:r.ease,delay:r.delay+r.duration,duration:r.duration})),n.push(e)}return Wi(o,a,i)},oa.svg.axis=function(){function n(n){n.each(function(){var n,c=oa.select(this),s=this.__chart__||e,f=this.__chart__=e.copy(),h=null==l?f.ticks?f.ticks.apply(f,o):f.domain():l,g=null==t?f.tickFormat?f.tickFormat.apply(f,o):y:t,p=c.selectAll(".tick").data(h,f),v=p.enter().insert("g",".domain").attr("class","tick").style("opacity",Da),d=oa.transition(p.exit()).style("opacity",Da).remove(),m=oa.transition(p.order()).style("opacity",1),M=Math.max(u,0)+a,x=Zu(f),b=c.selectAll(".domain").data([0]),_=(b.enter().append("path").attr("class","domain"),oa.transition(b));v.append("line"),v.append("text");var w,S,k,N,E=v.select("line"),A=m.select("line"),C=p.select("text").text(g),z=v.select("text"),L=m.select("text"),q="top"===r||"left"===r?-1:1;if("bottom"===r||"top"===r?(n=na,w="x",k="y",S="x2",N="y2",C.attr("dy",0>q?"0em":".71em").style("text-anchor","middle"),_.attr("d","M"+x[0]+","+q*i+"V0H"+x[1]+"V"+q*i)):(n=ta,w="y",k="x",S="y2",N="x2",C.attr("dy",".32em").style("text-anchor",0>q?"end":"start"),_.attr("d","M"+q*i+","+x[0]+"H0V"+x[1]+"H"+q*i)),E.attr(N,q*u),z.attr(k,q*M),A.attr(S,0).attr(N,q*u),L.attr(w,0).attr(k,q*M),f.rangeBand){var T=f,R=T.rangeBand()/2;s=f=function(n){return T(n)+R}}else s.rangeBand?s=f:d.call(n,f,s);v.call(n,s,f),m.call(n,f,f)})}var t,e=oa.scale.linear(),r=Zl,u=6,i=6,a=3,o=[10],l=null;return n.scale=function(t){return arguments.length?(e=t,n):e},n.orient=function(t){return arguments.length?(r=t in Vl?t+"":Zl,n):r},n.ticks=function(){return arguments.length?(o=ca(arguments),n):o},n.tickValues=function(t){return arguments.length?(l=t,n):l},n.tickFormat=function(e){return arguments.length?(t=e,n):t},n.tickSize=function(t){var e=arguments.length;return e?(u=+t,i=+arguments[e-1],n):u},n.innerTickSize=function(t){return arguments.length?(u=+t,n):u},n.outerTickSize=function(t){return arguments.length?(i=+t,n):i},n.tickPadding=function(t){return arguments.length?(a=+t,n):a},n.tickSubdivide=function(){return arguments.length&&n},n};var Zl="bottom",Vl={top:1,right:1,bottom:1,left:1};oa.svg.brush=function(){function n(t){t.each(function(){var t=oa.select(this).style("pointer-events","all").style("-webkit-tap-highlight-color","rgba(0,0,0,0)").on("mousedown.brush",i).on("touchstart.brush",i),a=t.selectAll(".background").data([0]);a.enter().append("rect").attr("class","background").style("visibility","hidden").style("cursor","crosshair"),t.selectAll(".extent").data([0]).enter().append("rect").attr("class","extent").style("cursor","move");var o=t.selectAll(".resize").data(v,y);o.exit().remove(),o.enter().append("g").attr("class",function(n){return"resize "+n}).style("cursor",function(n){return Xl[n]}).append("rect").attr("x",function(n){return/[ew]$/.test(n)?-3:null}).attr("y",function(n){return/^[ns]/.test(n)?-3:null}).attr("width",6).attr("height",6).style("visibility","hidden"),o.style("display",n.empty()?"none":null);var l,f=oa.transition(t),h=oa.transition(a);c&&(l=Zu(c),h.attr("x",l[0]).attr("width",l[1]-l[0]),r(f)),s&&(l=Zu(s),h.attr("y",l[0]).attr("height",l[1]-l[0]),u(f)),e(f)})}function e(n){n.selectAll(".resize").attr("transform",function(n){return"translate("+f[+/e$/.test(n)]+","+h[+/^s/.test(n)]+")"})}function r(n){n.select(".extent").attr("x",f[0]),n.selectAll(".extent,.n>rect,.s>rect").attr("width",f[1]-f[0])}function u(n){n.select(".extent").attr("y",h[0]),n.selectAll(".extent,.e>rect,.w>rect").attr("height",h[1]-h[0])}function i(){function i(){32==oa.event.keyCode&&(C||(M=null,L[0]-=f[1],L[1]-=h[1],C=2),S())}function v(){32==oa.event.keyCode&&2==C&&(L[0]+=f[1],L[1]+=h[1],C=0,S())}function d(){var n=oa.mouse(b),t=!1;x&&(n[0]+=x[0],n[1]+=x[1]),C||(oa.event.altKey?(M||(M=[(f[0]+f[1])/2,(h[0]+h[1])/2]),L[0]=f[+(n[0]s?(u=r,r=s):u=s),v[0]!=r||v[1]!=u?(e?o=null:a=null,v[0]=r,v[1]=u,!0):void 0}function y(){d(),k.style("pointer-events","all").selectAll(".resize").style("display",n.empty()?"none":null),oa.select("body").style("cursor",null),q.on("mousemove.brush",null).on("mouseup.brush",null).on("touchmove.brush",null).on("touchend.brush",null).on("keydown.brush",null).on("keyup.brush",null),z(),w({type:"brushend"})}var M,x,b=this,_=oa.select(oa.event.target),w=l.of(b,arguments),k=oa.select(b),N=_.datum(),E=!/^(n|s)$/.test(N)&&c,A=!/^(e|w)$/.test(N)&&s,C=_.classed("extent"),z=W(b),L=oa.mouse(b),q=oa.select(t(b)).on("keydown.brush",i).on("keyup.brush",v);if(oa.event.changedTouches?q.on("touchmove.brush",d).on("touchend.brush",y):q.on("mousemove.brush",d).on("mouseup.brush",y),k.interrupt().selectAll("*").interrupt(),C)L[0]=f[0]-L[0],L[1]=h[0]-L[1];else if(N){var T=+/w$/.test(N),R=+/^n/.test(N);x=[f[1-T]-L[0],h[1-R]-L[1]],L[0]=f[T],L[1]=h[R]}else oa.event.altKey&&(M=L.slice());k.style("pointer-events","none").selectAll(".resize").style("display",null),oa.select("body").style("cursor",_.style("cursor")),w({type:"brushstart"}),d()}var a,o,l=N(n,"brushstart","brush","brushend"),c=null,s=null,f=[0,0],h=[0,0],g=!0,p=!0,v=$l[0];return n.event=function(n){n.each(function(){var n=l.of(this,arguments),t={x:f,y:h,i:a,j:o},e=this.__chart__||t;this.__chart__=t,Fl?oa.select(this).transition().each("start.brush",function(){a=e.i,o=e.j,f=e.x,h=e.y,n({type:"brushstart"})}).tween("brush:brush",function(){var e=xr(f,t.x),r=xr(h,t.y);return a=o=null,function(u){f=t.x=e(u),h=t.y=r(u),n({type:"brush",mode:"resize"})}}).each("end.brush",function(){a=t.i,o=t.j,n({type:"brush",mode:"resize"}),n({type:"brushend"})}):(n({type:"brushstart"}),n({type:"brush",mode:"resize"}),n({type:"brushend"}))})},n.x=function(t){return arguments.length?(c=t,v=$l[!c<<1|!s],n):c},n.y=function(t){return arguments.length?(s=t,v=$l[!c<<1|!s],n):s},n.clamp=function(t){return arguments.length?(c&&s?(g=!!t[0],p=!!t[1]):c?g=!!t:s&&(p=!!t),n):c&&s?[g,p]:c?g:s?p:null},n.extent=function(t){var e,r,u,i,l;return arguments.length?(c&&(e=t[0],r=t[1],s&&(e=e[0],r=r[0]),a=[e,r],c.invert&&(e=c(e),r=c(r)),e>r&&(l=e,e=r,r=l),(e!=f[0]||r!=f[1])&&(f=[e,r])),s&&(u=t[0],i=t[1],c&&(u=u[1],i=i[1]),o=[u,i],s.invert&&(u=s(u),i=s(i)),u>i&&(l=u,u=i,i=l),(u!=h[0]||i!=h[1])&&(h=[u,i])),n):(c&&(a?(e=a[0],r=a[1]):(e=f[0],r=f[1],c.invert&&(e=c.invert(e),r=c.invert(r)),e>r&&(l=e,e=r,r=l))),s&&(o?(u=o[0],i=o[1]):(u=h[0],i=h[1],s.invert&&(u=s.invert(u),i=s.invert(i)),u>i&&(l=u,u=i,i=l))),c&&s?[[e,u],[r,i]]:c?[e,r]:s&&[u,i])},n.clear=function(){return n.empty()||(f=[0,0],h=[0,0],a=o=null),n},n.empty=function(){return!!c&&f[0]==f[1]||!!s&&h[0]==h[1]},oa.rebind(n,l,"on")};var Xl={n:"ns-resize",e:"ew-resize",s:"ns-resize",w:"ew-resize",nw:"nwse-resize",ne:"nesw-resize",se:"nwse-resize",sw:"nesw-resize"},$l=[["n","e","s","w","nw","ne","se","sw"],["e","w"],["n","s"],[]],Bl=ho.format=Mo.timeFormat,Wl=Bl.utc,Jl=Wl("%Y-%m-%dT%H:%M:%S.%LZ");Bl.iso=Date.prototype.toISOString&&+new Date("2000-01-01T00:00:00.000Z")?ea:Jl,ea.parse=function(n){var t=new Date(n);return isNaN(t)?null:t},ea.toString=Jl.toString,ho.second=On(function(n){return new go(1e3*Math.floor(n/1e3))},function(n,t){n.setTime(n.getTime()+1e3*Math.floor(t))},function(n){return n.getSeconds()}),ho.seconds=ho.second.range,ho.seconds.utc=ho.second.utc.range,ho.minute=On(function(n){return new go(6e4*Math.floor(n/6e4))},function(n,t){n.setTime(n.getTime()+6e4*Math.floor(t))},function(n){return n.getMinutes()}),ho.minutes=ho.minute.range,ho.minutes.utc=ho.minute.utc.range,ho.hour=On(function(n){var t=n.getTimezoneOffset()/60;return new go(36e5*(Math.floor(n/36e5-t)+t))},function(n,t){n.setTime(n.getTime()+36e5*Math.floor(t))},function(n){return n.getHours()}),ho.hours=ho.hour.range,ho.hours.utc=ho.hour.utc.range,ho.month=On(function(n){return n=ho.day(n),n.setDate(1),n},function(n,t){n.setMonth(n.getMonth()+t)},function(n){return n.getMonth()}),ho.months=ho.month.range,ho.months.utc=ho.month.utc.range;var Gl=[1e3,5e3,15e3,3e4,6e4,3e5,9e5,18e5,36e5,108e5,216e5,432e5,864e5,1728e5,6048e5,2592e6,7776e6,31536e6],Kl=[[ho.second,1],[ho.second,5],[ho.second,15],[ho.second,30],[ho.minute,1],[ho.minute,5],[ho.minute,15],[ho.minute,30],[ho.hour,1],[ho.hour,3],[ho.hour,6],[ho.hour,12],[ho.day,1],[ho.day,2],[ho.week,1],[ho.month,1],[ho.month,3],[ho.year,1]],Ql=Bl.multi([[".%L",function(n){return n.getMilliseconds()}],[":%S",function(n){return n.getSeconds()}],["%I:%M",function(n){return n.getMinutes()}],["%I %p",function(n){return n.getHours()}],["%a %d",function(n){return n.getDay()&&1!=n.getDate()}],["%b %d",function(n){return 1!=n.getDate()}],["%B",function(n){return n.getMonth()}],["%Y",zt]]),nc={range:function(n,t,e){return oa.range(Math.ceil(n/e)*e,+t,e).map(ua)},floor:y,ceil:y};Kl.year=ho.year,ho.scale=function(){return ra(oa.scale.linear(),Kl,Ql)};var tc=Kl.map(function(n){return[n[0].utc,n[1]]}),ec=Wl.multi([[".%L",function(n){return n.getUTCMilliseconds()}],[":%S",function(n){return n.getUTCSeconds()}],["%I:%M",function(n){return n.getUTCMinutes()}],["%I %p",function(n){return n.getUTCHours()}],["%a %d",function(n){return n.getUTCDay()&&1!=n.getUTCDate()}],["%b %d",function(n){return 1!=n.getUTCDate()}],["%B",function(n){return n.getUTCMonth()}],["%Y",zt]]);tc.year=ho.year.utc,ho.scale.utc=function(){return ra(oa.scale.linear(),tc,ec)},oa.text=An(function(n){return n.responseText}),oa.json=function(n,t){return Cn(n,"application/json",ia,t)},oa.html=function(n,t){return Cn(n,"text/html",aa,t)},oa.xml=An(function(n){return n.responseXML}),"function"==typeof define&&define.amd?(this.d3=oa,define(oa)):"object"==typeof module&&module.exports?module.exports=oa:this.d3=oa}(); \ No newline at end of file diff --git a/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.device.droneanalyzer.statistics/public/js/download.js b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.device.droneanalyzer.statistics/public/js/download.js new file mode 100644 index 0000000000..11ed5778ce --- /dev/null +++ b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.device.droneanalyzer.statistics/public/js/download.js @@ -0,0 +1,174 @@ +/* + * Copyright (c) 2016, 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. + */ + +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; + } + }); +} + +/* + * 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 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/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.device.droneanalyzer.statistics/public/js/initJs b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.device.droneanalyzer.statistics/public/js/initJs new file mode 100644 index 0000000000..57f70e56ac --- /dev/null +++ b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.device.droneanalyzer.statistics/public/js/initJs @@ -0,0 +1,21 @@ +/* + * 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. + */ +var ajax_handler = new ajax_handler(); +var config_api = new config_api(); +var plotting = new plotting(); +var object_maker = new object_maker(); \ No newline at end of file diff --git a/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.device.droneanalyzer.statistics/public/js/jQueryRotate.js b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.device.droneanalyzer.statistics/public/js/jQueryRotate.js new file mode 100644 index 0000000000..e3a8fa9c3f --- /dev/null +++ b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.device.droneanalyzer.statistics/public/js/jQueryRotate.js @@ -0,0 +1,357 @@ +/* + * Copyright (c) 2016, 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. + */ + +// VERSION: 2.3 LAST UPDATE: 11.07.2013 +/* + * Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php + * + * Made by Wilq32, wilq32@gmail.com, Wroclaw, Poland, 01.2009 + * Website: http://jqueryrotate.com + */ + +(function($) { + var supportedCSS,supportedCSSOrigin, styles=document.getElementsByTagName("head")[0].style,toCheck="transformProperty WebkitTransform OTransform msTransform MozTransform".split(" "); + for (var a = 0; a < toCheck.length; a++) if (styles[toCheck[a]] !== undefined) { supportedCSS = toCheck[a]; } + if (supportedCSS) { + supportedCSSOrigin = supportedCSS.replace(/[tT]ransform/,"TransformOrigin"); + if (supportedCSSOrigin[0] == "T") supportedCSSOrigin[0] = "t"; + } + + // Bad eval to preven google closure to remove it from code o_O + eval('IE = "v"=="\v"'); + + jQuery.fn.extend({ + rotate:function(parameters) + { + if (this.length===0||typeof parameters=="undefined") return; + if (typeof parameters=="number") parameters={angle:parameters}; + var returned=[]; + for (var i=0,i0=this.length;i this._parameters.duration; + + // TODO: Bug for animatedGif for static rotation ? (to test) + if (checkEnd && !this._parameters.animatedGif) + { + clearTimeout(this._timer); + } + else + { + if (this._canvas||this._vimage||this._img) { + var angle = this._parameters.easing(0, actualTime - this._animateStartTime, this._animateStartAngle, this._parameters.animateTo - this._animateStartAngle, this._parameters.duration); + this._rotate((~~(angle*10))/10); + } + if (this._parameters.step) { + this._parameters.step(this._angle); + } + var self = this; + this._timer = setTimeout(function() + { + self._animate.call(self); + }, 10); + } + + // To fix Bug that prevents using recursive function in callback I moved this function to back + if (this._parameters.callback && checkEnd){ + this._angle = this._parameters.animateTo; + this._rotate(this._angle); + this._parameters.callback.call(this._rootObj); + } + }, + + _rotate : (function() + { + var rad = Math.PI/180; + if (IE) + return function(angle) + { + this._angle = angle; + this._container.style.rotation=(angle%360)+"deg"; + this._vimage.style.top = -(this._rotationCenterY - this._imgHeight/2) + "px"; + this._vimage.style.left = -(this._rotationCenterX - this._imgWidth/2) + "px"; + this._container.style.top = this._rotationCenterY - this._imgHeight/2 + "px"; + this._container.style.left = this._rotationCenterX - this._imgWidth/2 + "px"; + + } + else if (supportedCSS) + return function(angle){ + this._angle = angle; + this._img.style[supportedCSS]="rotate("+(angle%360)+"deg)"; + this._img.style[supportedCSSOrigin]=this._parameters.center.join(" "); + } + else + return function(angle) + { + this._angle = angle; + angle=(angle%360)* rad; + // clear canvas + this._canvas.width = this._width;//+this._widthAdd; + this._canvas.height = this._height;//+this._heightAdd; + + // REMEMBER: all drawings are read from backwards.. so first function is translate, then rotate, then translate, translate.. + this._cnv.translate(this._imgWidth*this._aspectW,this._imgHeight*this._aspectH); // at least center image on screen + this._cnv.translate(this._rotationCenterX,this._rotationCenterY); // we move image back to its orginal + this._cnv.rotate(angle); // rotate image + this._cnv.translate(-this._rotationCenterX,-this._rotationCenterY); // move image to its center, so we can rotate around its center + this._cnv.scale(this._aspectW,this._aspectH); // SCALE - if needed ;) + this._cnv.drawImage(this._img, 0, 0); // First - we draw image + } + + })() + } + + if (IE) + { + Wilq32.PhotoEffect.prototype.createVMLNode=(function(){ + document.createStyleSheet().addRule(".rvml", "behavior:url(#default#VML)"); + try { + !document.namespaces.rvml && document.namespaces.add("rvml", "urn:schemas-microsoft-com:vml"); + return function (tagName) { + return document.createElement(''); + }; + } catch (e) { + return function (tagName) { + return document.createElement('<' + tagName + ' xmlns="urn:schemas-microsoft.com:vml" class="rvml">'); + }; + } + })(); + } + +})(jQuery); diff --git a/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.device.droneanalyzer.statistics/public/js/jquery.validate.js b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.device.droneanalyzer.statistics/public/js/jquery.validate.js new file mode 100644 index 0000000000..fe7ecf07a6 --- /dev/null +++ b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.device.droneanalyzer.statistics/public/js/jquery.validate.js @@ -0,0 +1,1227 @@ +/* + * Copyright (c) 2016, 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($) { + +$.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/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.device.droneanalyzer.statistics/public/js/mainHandler b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.device.droneanalyzer.statistics/public/js/mainHandler new file mode 100644 index 0000000000..28199109a3 --- /dev/null +++ b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.device.droneanalyzer.statistics/public/js/mainHandler @@ -0,0 +1,153 @@ +/* + * 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. + */ + +object_maker.init(config_api.config_3dobject_holder, $("#objectHolder").width(), $("#objectHolder").width()/1.5); +object_maker.animate(); +var flight_dynamics = new flight_dynamics(); +$("#window_size").slider({ + range: "min", + value: 37, + min: 10, + max: 300, + slide: function (event, ui) { + $("#window_size_current_value").html($("#window_size").slider("value")); + + } +}); +$("#window_update").slider({ + range: "min", + value: 234, + min: 100, + max: 1000, + slide: function (event, ui) { + $("#window_update_value").html($("#window_update").slider("value")); + } +}); +$("#replotting").click(function () { + plotting.finishPlotting(function (status) { + if (status) { + plotting.initPlotting(function (status) { + d3.select("#realtimechart").select("svg").remove(); + plotting.realtime_plotting("#realtimechart", "#range_min", "#range_max", "#window_update_value", + 600, $("#realtimechart").height(), "#window_size_current_value", + '#plotting_attribute'); + }); + } else { + $("#realtimechart").html("There is no data to plot"); + } + }); +}); +$('.btn-minimize').click(function (e) { + e.preventDefault(); + var $target = $(this).parent().parent().next('.box-content'); + if ($target.is(':visible')) { + if ($(this).parent().attr('id') === "RealtimePlotting") { + plotting.forceToRedraw(function (status) { + d3.select("#realtimechart").select("svg").remove(); + plotting.realtime_plotting("#realtimechart", "#range_min", "#range_max", "#window_update_value", + 600, $("#realtimechart").height(), "#window_size_current_value", + '#plotting_attribute'); + }); + } + } else { + } +}); +$('#connectionOpen').on('click', function () { + $('#connectionOpen').toggleClass('active'); +}); +$("#xmppConnectionOpen").on('click', function () { + $('#xmppConnectionOpen').toggleClass('active'); + if ($('#xmppConnectionOpen').html() === "Start") { + sendMessage("Start the process", function(state){ + console.log("sending message to server..."+ state); + if(state<2){ + $('#xmppConnectionOpen').html("Stop"); + }else{ + $('#xmppConnectionOpen').html("Start"); + } + }); + + } else if ($('#xmppConnectionOpen').html() === "Stop") { + closeSocket(function(state){ + console.log("closing WebSocket..."+ state); + if(state<2){ + $('#xmppConnectionOpen').html("Stop"); + }else{ + $('#xmppConnectionOpen').html("Start"); + } + }); + $("#connectionOpen").html("Connect to XMPP Server").removeClass("btn btn-info").addClass("btn btn-primary"); + } +}); + +$('.btn-minimize').parent().parent().next('.box-content').hide(); +var webSocket; +config_api.realtime_plotting_data_window["attitude"] = new Queue(); +var current_status = {}; +function openSocket(wssAddress) { + if (webSocket !== undefined && webSocket.readyState == 1) { + writeResponse("WebSocket is already opened."); + } else { + webSocket = new WebSocket(wssAddress+config_api.web_socket_endpoint); + } + webSocket.onopen = function (event) { + if (event === undefined) { + writeResponse("WebSocket cant open " + event); + $("#connectionOpen").html("Connect to server").removeClass("btn btn-info").addClass("btn btn-primary"); + } else { + + if (event["isTrusted"] == true) { + $("#connectionOpen").html("Connected").removeClass("btn btn-primary").addClass("btn btn-info"); + } + writeResponse(JSON.stringify(event)); + + } + }; + webSocket.onmessage = function (event) { + var sender_message = event.data; + sender_message = isJSON(sender_message); + if (sender_message != null) { + flight_dynamics.processingMessage(sender_message); + } else { + writeResponse("Message has been corrupted."); + } + }; +} + +function sendMessage(message, callback) { + if(webSocket.readyState<2){ + webSocket.send(message); + } + callback(webSocket.readyState); +} + +function closeSocket(callback) { + if(webSocket.readyState<2){ + webSocket.close(); + } + callback(webSocket.readyState); +} + +function writeResponse(text) { + console.log(text); +} + +window.onbeforeunload = function () { + webSocket.close(); +}; + diff --git a/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.device.droneanalyzer.statistics/public/js/modules/controller.js b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.device.droneanalyzer.statistics/public/js/modules/controller.js new file mode 100644 index 0000000000..5381923a2c --- /dev/null +++ b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.device.droneanalyzer.statistics/public/js/modules/controller.js @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2016, 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. + */ +$("#module_control button").click(function (index) { + console.log("Asking Server to send the " + $(this).attr('id') + " command to Ar Drone"); + var url = config_api.drone_control; + ajax_handler.ajaxRequest(url, config_api.drone_controlType, {action: $(this).attr('id'), speed: 7, duration: 7}, + config_api.drone_controlDataType, function (data, status) { + console.log(JSON.stringify(data)); + } + ); +}); + + + diff --git a/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.device.droneanalyzer.statistics/public/js/modules/flight_dynamics.js b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.device.droneanalyzer.statistics/public/js/modules/flight_dynamics.js new file mode 100644 index 0000000000..64814b6abf --- /dev/null +++ b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.device.droneanalyzer.statistics/public/js/modules/flight_dynamics.js @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2016, 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. + */ + +var flight_dynamics = function () { + var api = this; + api.processingMessage = function (sender_message) { + if(sender_message.battery_level!= undefined){ + $("#battery_level_holder").width( parseInt(sender_message.battery_level)+"%" ); + $("#battery_level").html(sender_message.battery_level+"%"); + } + if (sender_message.quatanium_val != undefined) { + current_status = object_maker.get_heading_attitude_bank(sender_message.quatanium_val); + object_maker.set_heading_attitude_bank(current_status); + $("#imageTop").animate({rotate: '' + (180 / Math.PI) * 2.890456 + 'deg'}, 2); + } + if (config_api.modules_status.angleOfRotation_2 || config_api.modules_status.angleOfRotation_1) { + console.log(JSON.stringify(current_status)); + object_maker.set_bank("#imageTop", current_status.bank); + object_maker.set_heading("#imageBackSecond", current_status.heading); + + } + if (config_api.modules_status.realtimePlotting) { + if (current_status[$('#plotting_attribute').val()] != undefined) { + plotting.pushData(current_status[$('#plotting_attribute').val()]); + } + } + if (sender_message.basicParam != undefined) { + if (sender_message.basicParam.velocity != undefined) { + var velocity = sender_message.basicParam.velocity; + if (velocity.length == 3) { + $("#velocityx").html(velocity[0]); + $("#velocityy").html(velocity[1]); + $("#velocityz").html(velocity[2]); + } + } else { + $("#velocityx").html(NaN); + $("#velocityy").html(NaN); + $("#velocityz").html(NaN); + } + if (sender_message.basicParam.global_location != undefined) { + var location = sender_message.basicParam.global_location; + if (location.length == 3) { + $("#locationLog").html(location[0]); + $("#locationAlt").html(location[1]); + $("#locationLat").html(location[2]); + } + } else { + $("#locationLog").html(NaN); + $("#locationAlt").html(NaN); + $("#locationLat").html(NaN); + } + } + if (sender_message.battery_voltage != undefined) { + $("#battery_voltage").html(sender_message.battery_voltage); + } else { + $("#battery_voltage").html(NaN); + } + } + +}; + + diff --git a/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.device.droneanalyzer.statistics/public/js/modules/realtime_plotting b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.device.droneanalyzer.statistics/public/js/modules/realtime_plotting new file mode 100644 index 0000000000..064ff2e109 --- /dev/null +++ b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.device.droneanalyzer.statistics/public/js/modules/realtime_plotting @@ -0,0 +1,148 @@ +/* + * 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. + */ +var plotting = function () { + var api = this; + api.isDone = false; + api.current_value = 0; + api.finishPlotting = function (callBack) { + api.isDone = true; + callBack(true); + }, + api.initPlotting = function (callback) { + api.isDone = false; + callback(true); + }, + api.forceToRedraw = function (callback) { + api.isDone == true; + callback(true); + }, + api.pushData = function (new_value) { + console.log(new_value); + api.current_value = new_value; + }, + api.realtime_plotting = function (holder, y_min_hollder, y_max_holder, update_interval_holder, holder_width, + holder_height, window_size_holder, title) { + $(holder).html(); + var init_window = function () { + return 0; + } + api.data = d3.range(parseInt($(window_size_holder).html())).map(init_window); + var margin = {top: 20, right: 20, bottom: 20, left: 40}, + width = holder_width - margin.left - margin.right, + height = holder_height - margin.top - margin.bottom; + var x = d3.scale.linear() + .domain([1, parseInt($(window_size_holder).html()) - 2]) + .range([0, width]); + + var y = d3.scale.linear() + .domain([parseInt($(y_min_hollder).val()), parseInt($(y_max_holder).val())]) + .range([height, 0]); + var line = d3.svg.line() + .interpolate("basis") + .x(function (d, i) { + return x(i); + }) + .y(function (d, i) { + return y(d); + }); + + var svg = d3.select(holder).append("svg") + .attr("width", width + margin.left + margin.right) + .attr("height", height + margin.top + margin.bottom) + .append("g") + .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); + svg.append("defs").append("clipPath") + .attr("id", "clip") + .append("rect") + .attr("width", width) + .attr("height", height); + var axis_x = svg.append("g") + .attr("class", "x_axis") + .attr("transform", "translate(0," + y(0) + ")") + .call(d3.svg.axis().scale(x).orient("bottom")); + var axis_y = svg.append("g") + .attr("class", "y_axis") + .call(d3.svg.axis().scale(y).orient("left")); + var path = svg.append("g") + .attr("clip-path", "url(#clip)") + .append("path") + .datum(api.data) + .attr("class", "line") + .attr("d", line); + svg.append("text") + .attr("class", "yaxis_label") + .attr("transform", "rotate(-90)") + .attr("y", 0 - margin.left - 4) + .attr("x", (0 - (height / 2))) + .attr("dy", "1em") + .style("text-anchor", "middle") + .text($(title).val()); + svg.append("text") + .attr("class", "xaixs_label") + .attr("transform", + "translate(" + (width / 2) + " ," + + (height + margin.bottom) + ")") + .style("text-anchor", "middle") + .text("Window Size"); + svg.append("text") + .attr("class", "title_label") + .attr("x", (width / 2)) + .attr("y", 0 - (margin.top / 4)) + .attr("text-anchor", "middle") + .style("font-size", "16px") + .style("text-decoration", "underline") + .text($(title).val() + " variation within last " + $(window_size_holder).html() + " frames"); + + updateAgain(); + + function updateAgain() { + if (api.isDone)return; + api.data.push(api.current_value); + path + .attr("d", line) + .attr("transform", null) + .transition() + .duration($(update_interval_holder).html()) + .ease("linear") + .attr("transform", "translate(" + x(0) + ",0)") + .each("end", updateAgain); + api.data.shift(); + } + + function rescale() { + y.domain([parseInt($(y_min_hollder).val()), parseInt($(y_max_holder).val())]) + + svg.select(".title_label") + .text($(title).val() + " variation within last " + $(window_size_holder).html() + " frames"); + svg.select(".yaxis_label") + .text($(title).val()); + + } + + function rescale_x() { + x.domain([1, parseInt($(window_size_holder).html()) - 2]).range([0, width]) + svg.select(".x_axis").transition().call(axis_x); + } + + $("#plotting_attribute").change(function () { + rescale(); + }); + + } +} + diff --git a/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.device.droneanalyzer.statistics/statistics.hbs b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.device.droneanalyzer.statistics/statistics.hbs new file mode 100644 index 0000000000..2f004fd746 --- /dev/null +++ b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.device.droneanalyzer.statistics/statistics.hbs @@ -0,0 +1,250 @@ +{{#zone "topCss"}} + {{css "css/main-app.css" }} +{{/zone}} +
      +
      +
      +
      + +
      +
      + +
      +
      +

      Battery Level

      +
      +
      +
      +
      +

      0%

      +
      +
      +
      +
      +
      +
      + +
      +
      +
      +
      +
      +

      Angle of Rotation

      + +
      + +
      +
      +
      +
      + +
      +
      +
      +
      +
      +
      +
      + +
      + +
      +
      +
      +
      + + + +
      +
      +
      +
      +
      +
      +
      + +
      + +
      +
      +
      +
      + + + +
      +
      +
      +
      + +
      +
      +
      +

      Live Video Stream

      + +
      + +
      +
      +
      + video stream +
      +
      +
      +
      +
      + +
      +
      +
      +
      +
      +

      Sensor Readings

      +
      + +
      +
      +
      +
      +

      Location
      latitude:
      + longitude:
      + altitudes:

      +

      Velocity:
      x :
      + y :
      + z :

      +

      Battery Voltage:

      +
      +
      +
      +
      +
      +
      +
      +

      Realtime Plotting

      + +
      + +
      +
      +
      +
      +
      +
      + Window size : +

      + Update period : +
      +

      +
      Y-axis: +
      +

      +
      + +
      +
      +
      +
      +
      +
      +
      +
      +
      +{{#zone "bottomJs"}} + {{js "/js/d3.min.js" }} + {{js "/js/3dobject_controller/three.min.js" }} + {{js "/js/3dobject_controller/Coordinates.js" }} + {{js "/js/3dobject_controller/OrbitAndPanControls.js" }} + {{js "/js/3dobject_controller/3dObjectControler.js" }} + {{js "/js/jQueryRotate.js" }} + {{js "/js/config/config.js" }} + {{js "/js/common/ajax_handler.js" }} + {{js "/js/modules/realtime_plotting" }} + {{js "/js/initJs" }} + {{js "/js/common/general_handler" }} + {{js "/js/common/websocket_api" }} + {{js "/js/modules/controller.js" }} + {{js "/js/modules/flight_dynamics.js" }} + {{js "/js/mainHandler" }} +{{/zone}} + diff --git a/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.device.droneanalyzer.statistics/statistics.js b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.device.droneanalyzer.statistics/statistics.js new file mode 100644 index 0000000000..550eddedcb --- /dev/null +++ b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.device.droneanalyzer.statistics/statistics.js @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2016, 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("statistics.js"); + var serverAddress = require("/app/modules/serverAddress.js").serverAddress; + var wssAddress = serverAddress.getWSSAddress(); + var httpsAddress = serverAddress.getHPPSTSAddress(); + var device = context.unit.params.device; + return { "device": device, "wssAddress": wssAddress, "httpsAddress": httpsAddress}; +} \ No newline at end of file diff --git a/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.device.droneanalyzer.statistics/statistics.json b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.device.droneanalyzer.statistics/statistics.json new file mode 100644 index 0000000000..688e939808 --- /dev/null +++ b/components/iot-plugins/drone-analyzer-plugin/org.wso2.carbon.device.mgt.iot.droneanalyzer.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.device.droneanalyzer.statistics/statistics.json @@ -0,0 +1,3 @@ +{ + "version": "1.0.0" +} \ No newline at end of file diff --git a/components/iot-plugins/drone-analyzer-plugin/pom.xml b/components/iot-plugins/drone-analyzer-plugin/pom.xml new file mode 100644 index 0000000000..4260130be9 --- /dev/null +++ b/components/iot-plugins/drone-analyzer-plugin/pom.xml @@ -0,0 +1,61 @@ + + + + + + + org.wso2.carbon.devicemgt-plugins + iot-plugins + 2.1.0-SNAPSHOT + ../pom.xml + + + 4.0.0 + drone-analyzer-plugin + pom + WSO2 Carbon - Arduino Plugin + http://wso2.org + + + org.wso2.carbon.device.mgt.iot.droneanalyzer.ui + org.wso2.carbon.device.mgt.iot.droneanalyzer.controller.api + org.wso2.carbon.device.mgt.iot.droneanalyzer.manager.api + org.wso2.carbon.device.mgt.iot.droneanalyzer.plugin + + + + + + + org.apache.felix + maven-scr-plugin + 1.7.2 + + + generate-scr-scrdescriptor + + scr + + + + + + + + diff --git a/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.api/pom.xml b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.api/pom.xml new file mode 100644 index 0000000000..b81253fe71 --- /dev/null +++ b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.api/pom.xml @@ -0,0 +1,139 @@ + + + + + + iot-base-plugin + org.wso2.carbon.devicemgt-plugins + 2.1.0-SNAPSHOT + ../pom.xml + + + 4.0.0 + org.wso2.carbon.device.mgt.iot.api + war + WSO2 Carbon - IoT Server API - Common APIs + WSO2 Carbon - IoT Server API for common device functionality + http://wso2.org + + + + + org.wso2.carbon.devicemgt + org.wso2.carbon.device.mgt.common + provided + + + org.wso2.carbon.devicemgt + org.wso2.carbon.device.mgt.core + provided + + + org.wso2.carbon.devicemgt + org.wso2.carbon.policy.mgt.core + provided + + + org.wso2.carbon.devicemgt + org.wso2.carbon.policy.mgt.common + provided + + + + + org.apache.cxf + cxf-rt-frontend-jaxws + provided + + + org.apache.cxf + cxf-rt-frontend-jaxrs + provided + + + org.apache.cxf + cxf-rt-transports-http + provided + + + + + org.wso2.carbon.devicemgt-plugins + org.wso2.carbon.device.mgt.iot + provided + + + + + org.codehaus.jackson + jackson-core-asl + + + org.codehaus.jackson + jackson-jaxrs + + + javax + javaee-web-api + provided + + + javax.ws.rs + jsr311-api + provided + + + + org.wso2.carbon.devicemgt + org.wso2.carbon.device.mgt.analytics + provided + + + + org.wso2.carbon.analytics + org.wso2.carbon.analytics.datasource.commons + provided + + + + + + + + maven-compiler-plugin + + UTF-8 + ${wso2.maven.compiler.source} + ${wso2.maven.compiler.target} + + + + maven-war-plugin + + common + + + + + + + + diff --git a/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.api/src/main/java/log4j.properties b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.api/src/main/java/log4j.properties new file mode 100755 index 0000000000..c271582fa8 --- /dev/null +++ b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.api/src/main/java/log4j.properties @@ -0,0 +1,8 @@ +# Root logger option +log4j.rootLogger=INFO, stdout + +# Direct log messages to stdout +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.Target=System.out +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern=%-5p %d{yyyy-MM-dd HH:mm:ss} %c{1}:%L - %m%n \ No newline at end of file diff --git a/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.api/src/main/java/org/wso2/carbon/device/mgt/iot/api/DeviceUsageDTO.java b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.api/src/main/java/org/wso2/carbon/device/mgt/iot/api/DeviceUsageDTO.java new file mode 100644 index 0000000000..222f00d413 --- /dev/null +++ b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.api/src/main/java/org/wso2/carbon/device/mgt/iot/api/DeviceUsageDTO.java @@ -0,0 +1,42 @@ +/* + * 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.wso2.carbon.device.mgt.iot.api; + +public class DeviceUsageDTO { + + private String time; + private String value; + + public String getTime() { + return time; + } + + public void setTime(String time) { + this.time = time; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + +} diff --git a/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.api/src/main/java/org/wso2/carbon/device/mgt/iot/api/DevicesManagerService.java b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.api/src/main/java/org/wso2/carbon/device/mgt/iot/api/DevicesManagerService.java new file mode 100644 index 0000000000..58841062b0 --- /dev/null +++ b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.api/src/main/java/org/wso2/carbon/device/mgt/iot/api/DevicesManagerService.java @@ -0,0 +1,641 @@ +/* + * 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.wso2.carbon.device.mgt.iot.api; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.context.CarbonContext; +import org.wso2.carbon.context.PrivilegedCarbonContext; +import org.wso2.carbon.device.mgt.common.*; +import org.wso2.carbon.device.mgt.common.configuration.mgt.TenantConfiguration; +import org.wso2.carbon.device.mgt.common.license.mgt.License; +import org.wso2.carbon.device.mgt.core.dto.DeviceType; +import org.wso2.carbon.device.mgt.core.service.DeviceManagementProviderService; +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 javax.jws.WebService; +import javax.servlet.http.HttpServletResponse; +import javax.ws.rs.*; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.Response; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Date; +import java.util.List; + +@WebService public class DevicesManagerService { + + private static Log log = LogFactory.getLog(DevicesManagerService.class); + + @Context //injected response proxy supporting multiple thread + private HttpServletResponse response; + + private PrivilegedCarbonContext ctx; + + private DeviceManagementProviderService getServiceProvider() { + String tenantDomain = CarbonContext.getThreadLocalCarbonContext().getTenantDomain(); + PrivilegedCarbonContext.startTenantFlow(); + ctx = PrivilegedCarbonContext.getThreadLocalCarbonContext(); + ctx.setTenantDomain(tenantDomain, true); + if (log.isDebugEnabled()) { + log.debug("Getting thread local carbon context for tenant domain: " + tenantDomain); + } + return (DeviceManagementProviderService) ctx.getOSGiService(DeviceManagementProviderService.class, null); + } + + private void endTenantFlow() { + PrivilegedCarbonContext.endTenantFlow(); + ctx = null; + if (log.isDebugEnabled()) { + log.debug("Tenant flow ended"); + } + } + + private Device[] getActiveDevices(List devices){ + List activeDevices = new ArrayList<>(); + if (devices != null) { + for (Device device : devices) { + if (device.getEnrolmentInfo().getStatus().equals(EnrolmentInfo.Status.ACTIVE)) { + activeDevices.add(device); + } + } + } + return activeDevices.toArray(new Device[activeDevices.size()]); + } + + @Path("/device/user/{username}/all") + @GET + @Consumes("application/json") + @Produces("application/json") + public Device[] getDevicesOfUser(@PathParam("username") String username) { + try { + List devices = this.getServiceProvider().getDevicesOfUser(username); + return this.getActiveDevices(devices); + } catch (DeviceManagementException e) { + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + return null; + } finally { + this.endTenantFlow(); + } + } + + @Path("/device/user/{username}/ungrouped") + @GET + @Consumes("application/json") + @Produces("application/json") + public Device[] getUnGroupedDevices(@PathParam("username") String username){ + try{ + List devices = this.getServiceProvider().getUnGroupedDevices(username); + return this.getActiveDevices(devices); + } catch (DeviceManagementException e) { + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + return null; + } finally { + this.endTenantFlow(); + } + } + + @Path("/device/user/{username}/all/count") + @GET + @Consumes("application/json") + @Produces("application/json") + public int getDeviceCount(@PathParam("username") String username){ + try { + List devices = this.getServiceProvider().getDevicesOfUser(username); + if (devices != null) { + List activeDevices = new ArrayList<>(); + for (Device device : devices) { + if (device.getEnrolmentInfo().getStatus().equals(EnrolmentInfo.Status.ACTIVE)) { + activeDevices.add(device); + } + } + return activeDevices.size(); + } + return 0; + } catch (DeviceManagementException e) { + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + return 0; + } finally { + this.endTenantFlow(); + } + } + + @Path("/device/type/{type}/identifier/{identifier}") + @GET + @Consumes("application/json") + @Produces("application/json") + public Device getDevice(@PathParam("type") String type, @PathParam("identifier") String identifier){ + + try{ + DeviceIdentifier deviceIdentifier = new DeviceIdentifier(); + deviceIdentifier.setId(identifier); + deviceIdentifier.setType(type); + return this.getServiceProvider().getDevice(deviceIdentifier); + } catch (DeviceManagementException e) { + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + return null; + } finally { + this.endTenantFlow(); + } + } + + @Path("/device/type/all") + @GET + @Consumes("application/json") + @Produces("application/json") + public DeviceType[] getDeviceTypes(){ + try{ + List deviceTypes = this.getServiceProvider().getAvailableDeviceTypes(); + return deviceTypes.toArray(new DeviceType[deviceTypes.size()]); + } catch (DeviceManagementException e) { + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + return null; + } finally { + this.endTenantFlow(); + } + } + + @Path("/device/type/{type}/all") + @GET + @Consumes("application/json") + @Produces("application/json") + public Device[] getAllDevices(@PathParam("type") String type){ + try{ + List devices = this.getServiceProvider().getAllDevices(type); + return this.getActiveDevices(devices); + } catch (DeviceManagementException e) { + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + return null; + } finally { + this.endTenantFlow(); + } + } + + @Path("/device/all") + @GET + @Consumes("application/json") + @Produces("application/json") + public Device[] getAllDevices(){ + try{ + List devices = this.getServiceProvider().getAllDevices(); + return this.getActiveDevices(devices); + } catch (DeviceManagementException e) { + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + return null; + } finally { + this.endTenantFlow(); + } + } + + @Path("/device/enrollment/invitation") + @POST + @Consumes("application/json") + @Produces("application/json") + public void sendEnrolmentInvitation(@FormParam("messageBody") String messageBody, + @FormParam("mailTo") String[] mailTo, @FormParam("ccList") String[] ccList, + @FormParam("bccList") String[] bccList, @FormParam("subject") String subject, + @FormParam("firstName") String firstName, @FormParam("enrolmentUrl") String enrolmentUrl, + @FormParam("title") String title, @FormParam("password") String password, + @FormParam("userName") String userName){ + EmailMessageProperties config = new EmailMessageProperties(); + config.setMessageBody(messageBody); + config.setMailTo(mailTo); + config.setCcList(ccList); + config.setBccList(bccList); + config.setSubject(subject); + config.setFirstName(firstName); + config.setEnrolmentUrl(enrolmentUrl); + config.setTitle(title); + config.setUserName(userName); + config.setPassword(password); + try { + this.getServiceProvider().sendEnrolmentInvitation(config); + } catch (DeviceManagementException e) { + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + } finally { + this.endTenantFlow(); + } + } + + @Path("/device/registration/invitation") + @POST + @Consumes("application/json") + @Produces("application/json") + public void sendRegistrationEmail(@FormParam("messageBody") String messageBody, + @FormParam("mailTo") String[] mailTo, @FormParam("ccList") String[] ccList, + @FormParam("bccList") String[] bccList, @FormParam("subject") String subject, + @FormParam("firstName") String firstName, @FormParam("enrolmentUrl") String enrolmentUrl, + @FormParam("title") String title, @FormParam("password") String password, + @FormParam("userName") String userName){ + EmailMessageProperties config = new EmailMessageProperties(); + config.setMessageBody(messageBody); + config.setMailTo(mailTo); + config.setCcList(ccList); + config.setBccList(bccList); + config.setSubject(subject); + config.setFirstName(firstName); + config.setEnrolmentUrl(enrolmentUrl); + config.setTitle(title); + config.setUserName(userName); + config.setPassword(password); + try { + this.getServiceProvider().sendRegistrationEmail(config); + } catch (DeviceManagementException e) { + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + } finally { + this.endTenantFlow(); + } + } + + @Path("/device/type/{type}/config") + @GET + @Consumes("application/json") + @Produces("application/json") + public TenantConfiguration getConfiguration(@PathParam("type") String type){ + try { + return this.getServiceProvider().getConfiguration(type); + } catch (DeviceManagementException e) { + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + return null; + } finally { + this.endTenantFlow(); + } + } + + @Path("/device/role/{role}/all") + @GET + @Consumes("application/json") + @Produces("application/json") + public Device[] getAllDevicesOfRole(@PathParam("role") String roleName){ + try{ + List devices = this.getServiceProvider().getAllDevicesOfRole(roleName); + return this.getActiveDevices(devices); + } catch (DeviceManagementException e) { + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + return null; + } finally { + this.endTenantFlow(); + } + } + + @Path("/device/name/{name}/all") + @GET + @Consumes("application/json") + @Produces("application/json") + public Device[] getDevicesByName(@PathParam("name") String name) { + try{ + List devices = this.getServiceProvider().getDevicesByName(name); + return this.getActiveDevices(devices); + } catch (DeviceManagementException e) { + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + return null; + } finally { + this.endTenantFlow(); + } + } + + @Path("/device/type/{type}/identifier/{identifier}/status") + @PUT + @Consumes("application/json") + @Produces("application/json") + void updateDeviceEnrolmentInfo(@PathParam("type") String type, @PathParam("identifier") String identifier, + @FormParam("status") EnrolmentInfo.Status status) { + DeviceManagementProviderService providerService = this.getServiceProvider(); + DeviceIdentifier deviceIdentifier = new DeviceIdentifier(); + deviceIdentifier.setType(type); + deviceIdentifier.setId(identifier); + try { + Device device = providerService.getDevice(deviceIdentifier); + providerService.updateDeviceEnrolmentInfo(device, status); + } catch (DeviceManagementException e) { + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + } finally { + this.endTenantFlow(); + } + } + + @Path("/device/status/{status}/all") + @GET + @Consumes("application/json") + @Produces("application/json") + public Device[] getDevicesByStatus(@PathParam("status") EnrolmentInfo.Status status) { + try{ + List devices = this.getServiceProvider().getDevicesByStatus(status); + return this.getActiveDevices(devices); + } catch (DeviceManagementException e) { + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + return null; + } finally { + this.endTenantFlow(); + } + } + + @Path("/device/type/{type}/license") + @GET + @Consumes("application/json") + @Produces("application/json") + public License getLicense(@PathParam("type") String type, @QueryParam("languageCode") String languageCode) { + try{ + return this.getServiceProvider().getLicense(type, languageCode); + } catch (DeviceManagementException e) { + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + return null; + } finally { + this.endTenantFlow(); + } + } + + @Path("/device/type/{type}/license") + @POST + @Consumes("application/json") + @Produces("application/json") + public void addLicense(@PathParam("type") String type, @FormParam("provider") String provider, + @FormParam("name") String name, @FormParam("version") String version, + @FormParam("language") String language, @FormParam("validFrom") Date validFrom, + @FormParam("validTo") Date validTo, @FormParam("text") String text) { + try{ + License license = new License(); + license.setProvider(provider); + license.setName(name); + license.setVersion(version); + license.setLanguage(language); + license.setValidFrom(validFrom); + license.setValidTo(validTo); + license.setText(text); + this.getServiceProvider().addLicense(type, license); + } catch (DeviceManagementException e) { + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + } finally { + this.endTenantFlow(); + } + } + + @Path("/device/type/{type}/identifier/{identifier}") + @PUT + @Consumes("application/json") + @Produces("application/json") + boolean modifyEnrollment(@PathParam("type") String type, @PathParam("identifier") String identifier, + @FormParam("name") String name, @FormParam("description") String description, + @FormParam("groupId") int groupId, @FormParam("enrollmentId") int enrollmentId, + @FormParam("dateOfEnrolment") long dateOfEnrolment, @FormParam("dateOfLastUpdate") long dateOfLastUpdate, + @FormParam("ownership") EnrolmentInfo.OwnerShip ownership, @FormParam("status") EnrolmentInfo.Status status, + @FormParam("owner") String owner){ + + EnrolmentInfo enrolmentInfo = new EnrolmentInfo(); + enrolmentInfo.setId(enrollmentId); + enrolmentInfo.setDateOfEnrolment(dateOfEnrolment); + enrolmentInfo.setDateOfLastUpdate(dateOfLastUpdate); + enrolmentInfo.setOwnership(ownership); + enrolmentInfo.setStatus(status); + enrolmentInfo.setOwner(owner); + + Device device = new Device(); + device.setType(type); + device.setDeviceIdentifier(identifier); + device.setName(name); + device.setDescription(description); + device.setGroupId(groupId); + device.setEnrolmentInfo(enrolmentInfo); + try { + return this.getServiceProvider().modifyEnrollment(device); + } catch (DeviceManagementException e) { + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + return false; + } finally { + this.endTenantFlow(); + } + } + + @Path("/device") + @POST + @Consumes("application/json") + @Produces("application/json") + boolean enrollDevice(@FormParam("type") String type, @FormParam("identifier") String identifier, + @FormParam("name") String name, @FormParam("description") String description, + @FormParam("groupId") int groupId, @FormParam("enrollmentId") int enrollmentId, + @FormParam("dateOfEnrolment") long dateOfEnrolment, @FormParam("dateOfLastUpdate") long dateOfLastUpdate, + @FormParam("ownership") EnrolmentInfo.OwnerShip ownership, @FormParam("status") EnrolmentInfo.Status status, + @FormParam("owner") String owner){ + + EnrolmentInfo enrolmentInfo = new EnrolmentInfo(); + enrolmentInfo.setId(enrollmentId); + enrolmentInfo.setDateOfEnrolment(dateOfEnrolment); + enrolmentInfo.setDateOfLastUpdate(dateOfLastUpdate); + enrolmentInfo.setOwnership(ownership); + enrolmentInfo.setStatus(status); + enrolmentInfo.setOwner(owner); + + Device device = new Device(); + device.setType(type); + device.setDeviceIdentifier(identifier); + device.setName(name); + device.setDescription(description); + device.setGroupId(groupId); + device.setEnrolmentInfo(enrolmentInfo); + try { + return this.getServiceProvider().enrollDevice(device); + } catch (DeviceManagementException e) { + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + return false; + } finally { + this.endTenantFlow(); + } + } + + @Path("/device/tenantconfiguration") + @GET + @Consumes("application/json") + @Produces("application/json") + public TenantConfiguration getConfiguration(){ + try { + return this.getServiceProvider().getConfiguration(); + } catch (DeviceManagementException e) { + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + return null; + } finally { + this.endTenantFlow(); + } + } + + @Path("/device/tenantconfiguration") + @POST + @Consumes("application/json") + @Produces("application/json") + public boolean saveConfiguration(@FormParam("tenantConfiguration") TenantConfiguration tenantConfiguration){ + try { + return this.getServiceProvider().saveConfiguration(tenantConfiguration); + } catch (DeviceManagementException e) { + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + return false; + } finally { + this.endTenantFlow(); + } + } + + @Path("/device/type/{type}/identifier/{identifier}") + @DELETE + @Consumes("application/json") + @Produces("application/json") + public boolean disenrollDevice(@PathParam("type") String type, @PathParam("identifier") String identifier){ + DeviceIdentifier deviceIdentifier = new DeviceIdentifier(); + deviceIdentifier.setType(type); + deviceIdentifier.setId(identifier); + try { + return this.getServiceProvider().disenrollDevice(deviceIdentifier); + } catch (DeviceManagementException e) { + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + return false; + } finally { + this.endTenantFlow(); + } + } + + @Path("/device/type/{type}/identifier/{identifier}/enrolled") + @GET + @Consumes("application/json") + @Produces("application/json") + public boolean isEnrolled(@PathParam("type") String type, @PathParam("identifier") String identifier){ + DeviceIdentifier deviceIdentifier = new DeviceIdentifier(); + deviceIdentifier.setType(type); + deviceIdentifier.setId(identifier); + try { + return this.getServiceProvider().isEnrolled(deviceIdentifier); + } catch (DeviceManagementException e) { + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + return false; + } finally { + this.endTenantFlow(); + } + } + @Path("/device/type/{type}/identifier/{identifier}/active") + @GET + @Consumes("application/json") + @Produces("application/json") + public boolean isActive(@PathParam("type") String type, @PathParam("identifier") String identifier){ + DeviceIdentifier deviceIdentifier = new DeviceIdentifier(); + deviceIdentifier.setType(type); + deviceIdentifier.setId(identifier); + try { + return this.getServiceProvider().isActive(deviceIdentifier); + } catch (DeviceManagementException e) { + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + return false; + } finally { + this.endTenantFlow(); + } + } + + @Path("/device/type/{type}/identifier/{identifier}/active") + @PUT + @Consumes("application/json") + @Produces("application/json") + public boolean setActive(@PathParam("type") String type, @PathParam("identifier") String identifier, + @FormParam("status") boolean status){ + DeviceIdentifier deviceIdentifier = new DeviceIdentifier(); + deviceIdentifier.setType(type); + deviceIdentifier.setId(identifier); + try { + return this.getServiceProvider().setActive(deviceIdentifier, status); + } catch (DeviceManagementException e) { + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + return false; + } finally { + this.endTenantFlow(); + } + } + + @Path("/device/type/{type}/identifier/{identifier}/ownership") + @PUT + @Consumes("application/json") + @Produces("application/json") + public boolean setOwnership(@PathParam("type") String type, @PathParam("identifier") String identifier, + @FormParam("ownership") String ownership){ + DeviceIdentifier deviceIdentifier = new DeviceIdentifier(); + deviceIdentifier.setType(type); + deviceIdentifier.setId(identifier); + try { + return this.getServiceProvider().setOwnership(deviceIdentifier, ownership); + } catch (DeviceManagementException e) { + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + return false; + } finally { + this.endTenantFlow(); + } + } + + @Path("/device/type/{type}/identifier/{identifier}/status") + @PUT + @Consumes("application/json") + @Produces("application/json") + public boolean setStatus(@PathParam("type") String type, @PathParam("identifier") String identifier, + @FormParam("owner") String owner, @FormParam("status") EnrolmentInfo.Status status){ + DeviceIdentifier deviceIdentifier = new DeviceIdentifier(); + deviceIdentifier.setType(type); + deviceIdentifier.setId(identifier); + try { + return this.getServiceProvider().setStatus(deviceIdentifier, owner, status); + } catch (DeviceManagementException e) { + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + return false; + } finally { + this.endTenantFlow(); + } + } + + @Path("/device/type/{type}/identifier/{identifier}/sensor/{sensorName}") + @POST + @Consumes("application/json") + @Produces("application/json") + public boolean setSensorValue(@PathParam("type") String type, @PathParam("identifier") String deviceId, + @PathParam("sensorName") String sensorName, + @HeaderParam("sensorValue") String sensorValue){ + + try { + return SensorDataManager.getInstance().setSensorRecord(deviceId, sensorName, sensorValue, Calendar + .getInstance().getTimeInMillis()); + } finally { + this.endTenantFlow(); + } + } + + @Path("/device/type/{type}/identifier/{identifier}/sensor/{sensorName}") + @GET + @Consumes("application/json") + @Produces("application/json") + public SensorRecord getSensorValue(@PathParam("type") String type, @PathParam("identifier") String deviceId, + @PathParam("sensorName") String sensorName, @HeaderParam("defaultValue") String defaultValue){ + + try { + return SensorDataManager.getInstance().getSensorRecord(deviceId, sensorName); + } catch (DeviceControllerException e) { + log.error("Error on reading sensor value: " + e.getMessage()); + if(defaultValue != null){ + return new SensorRecord(defaultValue, Calendar.getInstance().getTimeInMillis()); + }else{ + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + return null; + } + } finally { + this.endTenantFlow(); + } + } + +} diff --git a/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.api/src/main/java/org/wso2/carbon/device/mgt/iot/api/GroupManagerService.java b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.api/src/main/java/org/wso2/carbon/device/mgt/iot/api/GroupManagerService.java new file mode 100644 index 0000000000..4964103868 --- /dev/null +++ b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.api/src/main/java/org/wso2/carbon/device/mgt/iot/api/GroupManagerService.java @@ -0,0 +1,584 @@ +/* + * Copyright (c) 2016, 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.wso2.carbon.device.mgt.iot.api; + +import org.apache.commons.httpclient.HttpStatus; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.context.CarbonContext; +import org.wso2.carbon.context.PrivilegedCarbonContext; +import org.wso2.carbon.device.mgt.common.Device; +import org.wso2.carbon.device.mgt.common.DeviceIdentifier; +import org.wso2.carbon.device.mgt.common.PaginationRequest; +import org.wso2.carbon.device.mgt.common.PaginationResult; +import org.wso2.carbon.device.mgt.group.common.DeviceGroup; +import org.wso2.carbon.device.mgt.group.common.GroupManagementException; +import org.wso2.carbon.device.mgt.group.common.GroupUser; +import org.wso2.carbon.device.mgt.group.core.providers.GroupManagementServiceProvider; +import org.wso2.carbon.device.mgt.iot.util.ResponsePayload; + +import javax.jws.WebService; +import javax.servlet.http.HttpServletResponse; +import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; +import javax.ws.rs.FormParam; +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.util.Date; +import java.util.List; + +@WebService +public class GroupManagerService { + + private static final String DEFAULT_ADMIN_ROLE = "admin"; + private static final String DEFAULT_OPERATOR_ROLE = "invoke-device-operations"; + private static final String DEFAULT_STATS_MONITOR_ROLE = "view-statistics"; + private static final String DEFAULT_VIEW_POLICIES = "view-policies"; + private static final String DEFAULT_MANAGE_POLICIES = "mange-policies"; + private static final String DEFAULT_VIEW_EVENTS = "view-events"; + private static final String[] DEFAULT_ADMIN_PERMISSIONS = {"/permission/device-mgt/admin/groups", + "/permission/device-mgt/user/groups"}; + private static final String[] DEFAULT_OPERATOR_PERMISSIONS = {"/permission/device-mgt/user/groups/device_operation"}; + private static final String[] DEFAULT_STATS_MONITOR_PERMISSIONS = {"/permission/device-mgt/user/groups/device_monitor"}; + private static final String[] DEFAULT_MANAGE_POLICIES_PERMISSIONS = {"/permission/device-mgt/user/groups/device_policies/add"}; + private static final String[] DEFAULT_VIEW_POLICIES_PERMISSIONS = {"/permission/device-mgt/user/groups/device_policies/view"}; + private static final String[] DEFAULT_VIEW_EVENTS_PERMISSIONS = {"/permission/device-mgt/user/groups/device_events"}; + + private static Log log = LogFactory.getLog(GroupManagerService.class); + + @Context //injected response proxy supporting multiple threads + private HttpServletResponse response; + private PrivilegedCarbonContext ctx; + + private GroupManagementServiceProvider getServiceProvider() { + String tenantDomain = CarbonContext.getThreadLocalCarbonContext().getTenantDomain(); + String username = CarbonContext.getThreadLocalCarbonContext().getUsername(); + PrivilegedCarbonContext.startTenantFlow(); + ctx = PrivilegedCarbonContext.getThreadLocalCarbonContext(); + ctx.setTenantDomain(tenantDomain, true); + if (log.isDebugEnabled()) { + log.debug("Getting thread local carbon context for tenant domain: " + tenantDomain); + } + return (GroupManagementServiceProvider) ctx.getOSGiService(GroupManagementServiceProvider.class, null); + } + + private void endTenantFlow() { + PrivilegedCarbonContext.endTenantFlow(); + ctx = null; + if (log.isDebugEnabled()) { + log.debug("Tenant flow ended"); + } + } + + @Path("/group") + @POST + @Consumes("application/json") + @Produces("application/json") + public Response createGroup(@FormParam("name") String name, + @FormParam("username") String username, + @FormParam("description") String description) { + DeviceGroup group = new DeviceGroup(); + group.setName(name); + group.setDescription(description); + group.setOwner(username); + group.setDateOfCreation(new Date().getTime()); + group.setDateOfLastUpdate(new Date().getTime()); + boolean isAdded = false; + try { + GroupManagementServiceProvider groupManagementService = this.getServiceProvider(); + int groupId = groupManagementService.createGroup(group, DEFAULT_ADMIN_ROLE, DEFAULT_ADMIN_PERMISSIONS); + if (groupId == -2) { + ResponsePayload responsePayload = new ResponsePayload(); + responsePayload.setStatusCode(HttpStatus.SC_CONFLICT); + responsePayload.setMessageFromServer("Group name is already exists."); + responsePayload.setResponseContent("CONFLICT"); + return Response.status(HttpStatus.SC_CONFLICT).entity(responsePayload).build(); + } else { + isAdded = (groupId > 0) && groupManagementService.addGroupSharingRole(username, groupId, + DEFAULT_OPERATOR_ROLE, + DEFAULT_OPERATOR_PERMISSIONS); + groupManagementService.addGroupSharingRole(username, groupId, DEFAULT_STATS_MONITOR_ROLE, + DEFAULT_STATS_MONITOR_PERMISSIONS); + groupManagementService.addGroupSharingRole(username, groupId, DEFAULT_VIEW_POLICIES, + DEFAULT_VIEW_POLICIES_PERMISSIONS); + groupManagementService.addGroupSharingRole(username, groupId, DEFAULT_MANAGE_POLICIES, + DEFAULT_MANAGE_POLICIES_PERMISSIONS); + groupManagementService.addGroupSharingRole(username, groupId, DEFAULT_VIEW_EVENTS, + DEFAULT_VIEW_EVENTS_PERMISSIONS); + ResponsePayload responsePayload = new ResponsePayload(); + responsePayload.setStatusCode(HttpStatus.SC_OK); + return Response.status(HttpStatus.SC_OK).entity(responsePayload).build(); + } + } catch (GroupManagementException e) { + return Response.status(HttpStatus.SC_INTERNAL_SERVER_ERROR).entity(e.getMessage()).build(); + } finally { + this.endTenantFlow(); + } + } + + @Path("/group/id/{groupId}") + @PUT + @Consumes("application/json") + @Produces("application/json") + public boolean updateGroup(@PathParam("groupId") int groupId, @FormParam("name") String name, + @FormParam("username") String username, + @FormParam("description") String description) { + if (!isAuthorized(username, groupId, "/permission/device-mgt/admin/groups/modify")) { + response.setStatus(Response.Status.FORBIDDEN.getStatusCode()); + return false; + } + try { + GroupManagementServiceProvider groupManagementService = this.getServiceProvider(); + DeviceGroup group = groupManagementService.getGroup(groupId); + group.setName(name); + group.setDescription(description); + group.setOwner(username); + group.setDateOfLastUpdate(new Date().getTime()); + response.setStatus(Response.Status.OK.getStatusCode()); + groupManagementService.updateGroup(group); + return true; + } catch (GroupManagementException e) { + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + log.error(e.getErrorMessage(), e); + return false; + } finally { + this.endTenantFlow(); + } + } + + @Path("/group/id/{groupId}") + @DELETE + @Consumes("application/json") + @Produces("application/json") + public boolean deleteGroup(@PathParam("groupId") int groupId, + @QueryParam("username") String username) { + if (!isAuthorized(username, groupId, "/permission/device-mgt/admin/groups/delete")) { + response.setStatus(Response.Status.FORBIDDEN.getStatusCode()); + return false; + } + boolean isDeleted = false; + try { + response.setStatus(Response.Status.OK.getStatusCode()); + isDeleted = this.getServiceProvider().deleteGroup(groupId); + } catch (GroupManagementException e) { + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + log.error(e.getErrorMessage(), e); + } finally { + this.endTenantFlow(); + } + return isDeleted; + } + + @Path("/group/id/{groupId}") + @GET + @Consumes("application/json") + @Produces("application/json") + public DeviceGroup getGroup(@PathParam("groupId") int groupId, + @FormParam("username") String username) { + DeviceGroup deviceGroup = null; + try { + response.setStatus(Response.Status.OK.getStatusCode()); + deviceGroup = this.getServiceProvider().getGroup(groupId); + } catch (GroupManagementException e) { + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + log.error(e.getErrorMessage(), e); + } finally { + this.endTenantFlow(); + } + return deviceGroup; + } + + @Path("/group/name/{groupName}") + @GET + @Consumes("application/json") + @Produces("application/json") + public DeviceGroup[] findGroups(@PathParam("groupName") String groupName, + @FormParam("username") String username) { + DeviceGroup[] deviceGroups = null; + try { + List groups = this.getServiceProvider().findGroups(groupName, username); + deviceGroups = new DeviceGroup[groups.size()]; + response.setStatus(Response.Status.OK.getStatusCode()); + groups.toArray(deviceGroups); + } catch (GroupManagementException e) { + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + log.error(e.getErrorMessage(), e); + } finally { + this.endTenantFlow(); + } + return deviceGroups; + } + + @Path("/group/user/{username}/all") + @GET + @Consumes("application/json") + @Produces("application/json") + public DeviceGroup[] getGroups(@PathParam("username") String username, + @QueryParam("permission") String permission) { + DeviceGroup[] deviceGroups = null; + try { + GroupManagementServiceProvider groupManagementService = this.getServiceProvider(); + List groups; + if (permission != null) { + groups = groupManagementService.getGroups(username, permission); + } else { + groups = groupManagementService.getGroups(username); + } + deviceGroups = new DeviceGroup[groups.size()]; + response.setStatus(Response.Status.OK.getStatusCode()); + groups.toArray(deviceGroups); + } catch (GroupManagementException e) { + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + log.error(e.getErrorMessage(), e); + } finally { + this.endTenantFlow(); + } + return deviceGroups; + } + + @Path("/group/user/{username}/all/count") + @GET + @Consumes("application/json") + @Produces("application/json") + public int getGroupCount(@PathParam("username") String username) { + int count = -1; + try { + response.setStatus(Response.Status.OK.getStatusCode()); + count = this.getServiceProvider().getGroupCount(username); + } catch (GroupManagementException e) { + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + log.error(e.getErrorMessage(), e); + } finally { + this.endTenantFlow(); + } + return count; + } + + @Path("/group/id/{groupId}/share") + @POST + @Consumes("application/json") + @Produces("application/json") + public boolean shareGroup(@FormParam("username") String username, + @FormParam("shareUser") String shareUser, + @PathParam("groupId") int groupId, + @FormParam("role") String sharingRole) { + if (!isAuthorized(username, groupId, "/permission/device-mgt/admin/groups/share")) { + response.setStatus(Response.Status.FORBIDDEN.getStatusCode()); + return false; + } + boolean isShared = false; + try { + response.setStatus(Response.Status.OK.getStatusCode()); + isShared = this.getServiceProvider().shareGroup(shareUser, groupId, sharingRole); + } catch (GroupManagementException e) { + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + log.error(e.getErrorMessage(), e); + } finally { + this.endTenantFlow(); + } + return isShared; + } + + @Path("/group/id/{groupId}/unshare") + @POST + @Consumes("application/json") + @Produces("application/json") + public boolean unShareGroup(@FormParam("username") String username, + @FormParam("unShareUser") String unShareUser, + @PathParam("groupId") int groupId, + @FormParam("role") String sharingRole) { + if (!isAuthorized(username, groupId, "/permission/device-mgt/admin/groups/share")) { + response.setStatus(Response.Status.FORBIDDEN.getStatusCode()); + return false; + } + boolean isUnShared = false; + try { + response.setStatus(Response.Status.OK.getStatusCode()); + isUnShared = this.getServiceProvider().unshareGroup(unShareUser, groupId, sharingRole); + } catch (GroupManagementException e) { + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + log.error(e.getErrorMessage(), e); + } finally { + this.endTenantFlow(); + } + return isUnShared; + } + + @Path("/group/id/{groupId}/role") + @POST + @Consumes("application/json") + @Produces("application/json") + public boolean addSharing(@FormParam("username") String username, + @PathParam("groupId") int groupId, + @FormParam("role") String roleName, + @FormParam("permissions") String[] permissions) { + if (!isAuthorized(username, groupId, "/permission/device-mgt/admin/groups/share")) { + response.setStatus(Response.Status.FORBIDDEN.getStatusCode()); + return false; + } + boolean isAdded = false; + try { + response.setStatus(Response.Status.OK.getStatusCode()); + isAdded = this.getServiceProvider().addGroupSharingRole(username, groupId, roleName, permissions); + } catch (GroupManagementException e) { + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + log.error(e.getErrorMessage(), e); + } finally { + this.endTenantFlow(); + } + return isAdded; + } + + @Path("/group/id/{groupId}/role/{role}") + @DELETE + @Consumes("application/json") + @Produces("application/json") + public boolean removeSharing(@QueryParam("username") String username, + @PathParam("groupId") int groupId, + @PathParam("role") String roleName) { + if (!isAuthorized(username, groupId, "/permission/device-mgt/admin/groups/share")) { + response.setStatus(Response.Status.FORBIDDEN.getStatusCode()); + } + boolean isRemoved = false; + try { + response.setStatus(Response.Status.OK.getStatusCode()); + isRemoved = this.getServiceProvider().removeGroupSharingRole(groupId, roleName); + } catch (GroupManagementException e) { + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + log.error(e.getErrorMessage(), e); + } finally { + this.endTenantFlow(); + } + return isRemoved; + } + + @Path("/group/id/{groupId}/role/all") + @GET + @Consumes("application/json") + @Produces("application/json") + public String[] getRoles(@PathParam("groupId") int groupId) { + String[] rolesArray = null; + try { + List roles = this.getServiceProvider().getRoles(groupId); + rolesArray = new String[roles.size()]; + response.setStatus(Response.Status.OK.getStatusCode()); + roles.toArray(rolesArray); + } catch (GroupManagementException e) { + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + log.error(e.getErrorMessage(), e); + } finally { + this.endTenantFlow(); + } + return rolesArray; + } + + @Path("/group/id/{groupId}/{user}/role/all") + @GET + @Consumes("application/json") + @Produces("application/json") + public String[] getRoles(@PathParam("user") String user, @PathParam("groupId") int groupId) { + String[] rolesArray = null; + try { + List roles = this.getServiceProvider().getRoles(user, groupId); + rolesArray = new String[roles.size()]; + response.setStatus(Response.Status.OK.getStatusCode()); + roles.toArray(rolesArray); + } catch (GroupManagementException e) { + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + log.error(e.getErrorMessage(), e); + } finally { + this.endTenantFlow(); + } + return rolesArray; + } + + @Path("/group/id/{groupId}/user/all") + @GET + @Consumes("application/json") + @Produces("application/json") + public GroupUser[] getUsers(@PathParam("groupId") int groupId) { + GroupUser[] usersArray = null; + try { + List users = this.getServiceProvider().getUsers(groupId); + usersArray = new GroupUser[users.size()]; + response.setStatus(Response.Status.OK.getStatusCode()); + users.toArray(usersArray); + } catch (GroupManagementException e) { + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + log.error(e.getErrorMessage(), e); + } finally { + this.endTenantFlow(); + } + return usersArray; + } + + @Path("/group/id/{groupId}/device/all") + @GET + @Consumes("application/json") + @Produces("application/json") + public Device[] getDevices(@PathParam("groupId") int groupId) { + Device[] deviceArray = null; + try { + List devices = this.getServiceProvider().getDevices(groupId); + deviceArray = new Device[devices.size()]; + response.setStatus(Response.Status.OK.getStatusCode()); + devices.toArray(deviceArray); + } catch (GroupManagementException e) { + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + log.error(e.getErrorMessage(), e); + } finally { + this.endTenantFlow(); + } + return deviceArray; + } + + @Path("/group/id/{groupId}/device/count") + @GET + @Consumes("application/json") + @Produces("application/json") + public int getDeviceCount(@PathParam("groupId") int groupId) { + try { + return this.getServiceProvider().getDeviceCount(groupId); + } catch (GroupManagementException e) { + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + log.error(e.getErrorMessage(), e); + return -1; + } finally { + this.endTenantFlow(); + } + } + + @Path("/group/id/{groupId}/device") + @GET + @Consumes("application/json") + @Produces("application/json") + public PaginationResult getDevices(@PathParam("groupId") int groupId, + @QueryParam("index") int index, + @QueryParam("limit") int limit) { + try { + PaginationRequest request = new PaginationRequest(index, limit); + return this.getServiceProvider().getDevices(groupId, request); + } catch (GroupManagementException e) { + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + log.error(e.getErrorMessage(), e); + return null; + } finally { + this.endTenantFlow(); + } + } + + @Path("/group/id/{groupId}/device/assign") + @PUT + @Consumes("application/json") + @Produces("application/json") + public boolean addDevice(@PathParam("groupId") int groupId, + @FormParam("deviceId") String deviceId, + @FormParam("deviceType") String deviceType, + @FormParam("username") String username) { + if (!isAuthorized(username, groupId, "/permission/device-mgt/admin/groups/add_devices")) { + response.setStatus(Response.Status.FORBIDDEN.getStatusCode()); + return false; + } + boolean isAdded = false; + try { + DeviceIdentifier deviceIdentifier = new DeviceIdentifier(deviceId, deviceType); + response.setStatus(Response.Status.OK.getStatusCode()); + isAdded = this.getServiceProvider().addDevice(deviceIdentifier, groupId); + } catch (GroupManagementException e) { + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + log.error(e.getErrorMessage(), e); + } finally { + this.endTenantFlow(); + } + return isAdded; + } + + @Path("/group/id/{groupId}/device/assign") + @DELETE + @Consumes("application/json") + @Produces("application/json") + public boolean removeDevice(@PathParam("groupId") int groupId, + @FormParam("deviceId") String deviceId, + @FormParam("deviceType") String deviceType, + @FormParam("username") String username) { + if (!isAuthorized(username, groupId, "/permission/device-mgt/admin/groups/remove_devices")) { + response.setStatus(Response.Status.FORBIDDEN.getStatusCode()); + return false; + } + boolean isRemoved = false; + try { + DeviceIdentifier deviceIdentifier = new DeviceIdentifier(deviceId, deviceType); + response.setStatus(Response.Status.OK.getStatusCode()); + isRemoved = this.getServiceProvider().removeDevice(deviceIdentifier, groupId); + } catch (GroupManagementException e) { + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + log.error(e.getErrorMessage(), e); + } finally { + this.endTenantFlow(); + } + return isRemoved; + } + + @Path("/group/id/{groupId}/user/{username}/permissions") + @GET + @Consumes("application/json") + @Produces("application/json") + public String[] getPermissions(@PathParam("username") String username, + @PathParam("groupId") int groupId) { + String[] permissions = null; + try { + response.setStatus(Response.Status.OK.getStatusCode()); + permissions = this.getServiceProvider().getPermissions(username, groupId); + } catch (GroupManagementException e) { + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + log.error(e.getErrorMessage(), e); + } finally { + this.endTenantFlow(); + } + return permissions; + } + + @Path("/group/id/{groupId}/user/{username}/authorized") + @GET + @Consumes("application/json") + @Produces("application/json") + public boolean isAuthorized(@PathParam("username") String username, + @PathParam("groupId") int groupId, + @QueryParam("permission") String permission) { + boolean isAuthorized = false; + try { + response.setStatus(Response.Status.OK.getStatusCode()); + isAuthorized = this.getServiceProvider().isAuthorized(username, groupId, permission); + } catch (GroupManagementException e) { + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + log.error(e.getErrorMessage(), e); + } finally { + this.endTenantFlow(); + } + return isAuthorized; + } + +} diff --git a/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.api/src/main/java/org/wso2/carbon/device/mgt/iot/api/PolicyManagementService.java b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.api/src/main/java/org/wso2/carbon/device/mgt/iot/api/PolicyManagementService.java new file mode 100644 index 0000000000..702ea89eda --- /dev/null +++ b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.api/src/main/java/org/wso2/carbon/device/mgt/iot/api/PolicyManagementService.java @@ -0,0 +1,507 @@ +/* + * 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.wso2.carbon.device.mgt.iot.api; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.context.CarbonContext; +import org.wso2.carbon.context.PrivilegedCarbonContext; +import org.wso2.carbon.device.mgt.common.DeviceIdentifier; +import org.wso2.carbon.device.mgt.common.DeviceManagementException; +import org.wso2.carbon.policy.mgt.common.Policy; +import org.wso2.carbon.policy.mgt.common.PolicyAdministratorPoint; +import org.wso2.carbon.policy.mgt.common.PolicyManagementException; +import org.wso2.carbon.policy.mgt.common.PolicyMonitoringTaskException; +import org.wso2.carbon.policy.mgt.common.monitor.ComplianceData; +import org.wso2.carbon.policy.mgt.common.monitor.PolicyComplianceException; +import org.wso2.carbon.policy.mgt.core.PolicyManagerService; +import org.wso2.carbon.policy.mgt.core.task.TaskScheduleService; + +import javax.jws.WebService; +import javax.servlet.http.HttpServletResponse; +import javax.ws.rs.*; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.Response; +import java.util.List; + +@WebService +public class PolicyManagementService { + + private static Log log = LogFactory.getLog(PolicyManagementService.class); + + @Context //injected response proxy supporting multiple thread + private HttpServletResponse response; + + private PrivilegedCarbonContext ctx; + + private PolicyManagerService getPolicyServiceProvider() throws DeviceManagementException { + String tenantDomain = CarbonContext.getThreadLocalCarbonContext().getTenantDomain(); + PrivilegedCarbonContext.startTenantFlow(); + ctx = PrivilegedCarbonContext.getThreadLocalCarbonContext(); + ctx.setTenantDomain(tenantDomain, true); + + if (log.isDebugEnabled()) { + log.debug("Getting thread local carbon context for tenant domain: " + tenantDomain); + } + + PolicyManagerService policyManagerService = (PolicyManagerService) ctx.getOSGiService( + PolicyManagerService.class, null); + + if (policyManagerService == null) { + String msg = "Policy Management service not initialized"; + log.error(msg); + throw new DeviceManagementException(msg); + } + + return policyManagerService; + } + + private void endTenantFlow() { + PrivilegedCarbonContext.endTenantFlow(); + ctx = null; + if (log.isDebugEnabled()) { + log.debug("Tenant flow ended"); + } + } + + @POST + @Path("/inactive-policy") + @Produces("application/json") + public boolean addPolicy(Policy policy) { + + try { + PolicyManagerService policyManagerService = getPolicyServiceProvider(); + PolicyAdministratorPoint pap = policyManagerService.getPAP(); + pap.addPolicy(policy); + response.setStatus(Response.Status.CREATED.getStatusCode()); + if (log.isDebugEnabled()) { + log.debug("Policy has been added successfully."); + } + return true; + } catch (PolicyManagementException e) { + String error = "Policy Management related exception."; + log.error(error, e); + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + return false; + } catch (DeviceManagementException e) { + String error = "Error occurred while invoking Policy Management Service."; + log.error(error, e); + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + return false; + } finally { + this.endTenantFlow(); + } + } + + @POST + @Path("/active-policy") + @Produces("application/json") + public boolean addActivePolicy(Policy policy) { + + policy.setActive(true); + + try { + PolicyManagerService policyManagerService = getPolicyServiceProvider(); + PolicyAdministratorPoint pap = policyManagerService.getPAP(); + pap.addPolicy(policy); + response.setStatus(Response.Status.CREATED.getStatusCode()); + if (log.isDebugEnabled()) { + log.debug("Policy has been added successfully."); + } + return true; + } catch (PolicyManagementException e) { + String error = "Policy Management related exception."; + log.error(error, e); + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + return false; + } catch (DeviceManagementException e) { + String error = "Error occurred while invoking Policy Management Service."; + log.error(error, e); + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + return false; + } finally { + this.endTenantFlow(); + } + } + + @GET + @Produces("application/json") + public Policy[] getAllPolicies() { + try { + PolicyManagerService policyManagerService = getPolicyServiceProvider(); + PolicyAdministratorPoint policyAdministratorPoint = policyManagerService.getPAP(); + List policies = policyAdministratorPoint.getPolicies(); + return policyAdministratorPoint.getPolicies().toArray(new Policy[policies.size()]); + } catch (PolicyManagementException e) { + String error = "Policy Management related exception"; + log.error(error, e); + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + return null; + } catch (DeviceManagementException e) { + String error = "Error occurred while invoking Policy Management Service."; + log.error(error, e); + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + return null; + } finally { + this.endTenantFlow(); + } + } + + @GET + @Produces("application/json") + @Path("/{id}") + public Policy getPolicy(@PathParam("id") int policyId) { + try { + PolicyManagerService policyManagerService = getPolicyServiceProvider(); + PolicyAdministratorPoint policyAdministratorPoint = policyManagerService.getPAP(); + Policy policy = policyAdministratorPoint.getPolicy(policyId); + if (policy != null) { + if (log.isDebugEnabled()) { + log.debug("Sending policy for ID " + policyId); + } + return policy; + } else { + log.error("Policy for ID " + policyId + " not found."); + response.setStatus(Response.Status.NOT_FOUND.getStatusCode()); + return null; + } + } catch (PolicyManagementException e) { + String error = "Policy Management related exception"; + log.error(error, e); + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + return null; + } catch (DeviceManagementException e) { + String error = "Error occurred while invoking Policy Management Service."; + log.error(error, e); + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + return null; + } finally { + this.endTenantFlow(); + } + } + + @GET + @Path("/count") + public int getPolicyCount() { + try { + PolicyManagerService policyManagerService = getPolicyServiceProvider(); + PolicyAdministratorPoint policyAdministratorPoint = policyManagerService.getPAP(); + return policyAdministratorPoint.getPolicyCount(); + } catch (PolicyManagementException e) { + String error = "Policy Management related exception"; + log.error(error, e); + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + return -1; + } catch (DeviceManagementException e) { + String error = "Error occurred while invoking Policy Management Service."; + log.error(error, e); + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + return -1; + } finally { + this.endTenantFlow(); + } + } + + @PUT + @Path("/{id}") + @Produces("application/json") + public boolean updatePolicy(Policy policy, @PathParam("id") int policyId) { + try { + PolicyManagerService policyManagerService = getPolicyServiceProvider(); + PolicyAdministratorPoint pap = policyManagerService.getPAP(); + Policy previousPolicy = pap.getPolicy(policyId); + policy.setProfile(pap.getProfile(previousPolicy.getProfileId())); + policy.setPolicyName(previousPolicy.getPolicyName()); + pap.updatePolicy(policy); + if (log.isDebugEnabled()) { + log.debug("Policy with ID " + policyId + " has been updated successfully."); + } + return true; + } catch (PolicyManagementException e) { + String error = "Policy Management related exception"; + log.error(error, e); + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + return false; + } catch (DeviceManagementException e) { + String error = "Error occurred while invoking Policy Management Service."; + log.error(error, e); + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + return false; + } finally { + this.endTenantFlow(); + } + } + + @PUT + @Path("/priorities") + @Consumes("application/json") + @Produces("application/json") + public boolean updatePolicyPriorities(List priorityUpdatedPolicies) { + try { + PolicyManagerService policyManagerService = getPolicyServiceProvider(); + PolicyAdministratorPoint pap = policyManagerService.getPAP(); + boolean policiesUpdated = pap.updatePolicyPriorities(priorityUpdatedPolicies); + if (policiesUpdated) { + if (log.isDebugEnabled()) { + log.debug("Policy Priorities successfully updated."); + } + return true; + } else { + if (log.isDebugEnabled()) { + log.debug("Policy priorities did not update. Bad Request."); + } + response.setStatus(Response.Status.BAD_REQUEST.getStatusCode()); + return false; + } + } catch (PolicyManagementException e) { + String error = "Exception in updating policy priorities."; + log.error(error, e); + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + return false; + } catch (DeviceManagementException e) { + String error = "Error occurred while invoking Policy Management Service."; + log.error(error, e); + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + return false; + } finally { + this.endTenantFlow(); + } + } + + @DELETE + @Path("/{id}") + @Produces("application/json") + public boolean deletePolicy(@PathParam("id") int policyId) { + try { + PolicyManagerService policyManagerService = getPolicyServiceProvider(); + PolicyAdministratorPoint pap = policyManagerService.getPAP(); + Policy policy = pap.getPolicy(policyId); + boolean policyDeleted = pap.deletePolicy(policy); + if (policyDeleted) { + if (log.isDebugEnabled()) { + log.debug("Policy by id:" + policyId + " has been successfully deleted."); + } + return true; + } else { + if (log.isDebugEnabled()) { + log.debug("Policy by id:" + policyId + " does not exist."); + } + response.setStatus(Response.Status.NOT_FOUND.getStatusCode()); + return false; + } + } catch (PolicyManagementException e) { + String error = "Exception in deleting policy by id:" + policyId; + log.error(error, e); + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + return false; + } catch (DeviceManagementException e) { + String error = "Error occurred while invoking Policy Management Service."; + log.error(error, e); + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + return false; + } finally { + this.endTenantFlow(); + } + } + + @PUT + @Produces("application/json") + @Path("/activate/{id}") + public boolean activatePolicy(@PathParam("id") int policyId) { + try { + PolicyManagerService policyManagerService = getPolicyServiceProvider(); + PolicyAdministratorPoint pap = policyManagerService.getPAP(); + pap.activatePolicy(policyId); + if (log.isDebugEnabled()) { + log.debug("Policy by id:" + policyId + " has been successfully activated."); + } + return true; + } catch (PolicyManagementException e) { + String error = "Exception in activating policy by id:" + policyId; + log.error(error, e); + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + return false; + } catch (DeviceManagementException e) { + String error = "Error occurred while invoking Policy Management Service."; + log.error(error, e); + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + return false; + } finally { + this.endTenantFlow(); + } + } + + @PUT + @Produces("application/json") + @Path("/inactivate/{id}") + public boolean inactivatePolicy(@PathParam("id") int policyId) { + + try { + PolicyManagerService policyManagerService = getPolicyServiceProvider(); + PolicyAdministratorPoint pap = policyManagerService.getPAP(); + pap.inactivatePolicy(policyId); + if (log.isDebugEnabled()) { + log.debug("Policy by id:" + policyId + " has been successfully inactivated."); + } + return true; + } catch (PolicyManagementException e) { + String error = "Exception in inactivating policy by id:" + policyId; + log.error(error, e); + return false; + } catch (DeviceManagementException e) { + String error = "Error occurred while invoking Policy Management Service."; + log.error(error, e); + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + return false; + } finally { + this.endTenantFlow(); + } + } + + @PUT + @Produces("application/json") + @Path("/apply-changes") + public boolean applyChanges() { + + try { + PolicyManagerService policyManagerService = getPolicyServiceProvider(); + PolicyAdministratorPoint pap = policyManagerService.getPAP(); + pap.publishChanges(); + if (log.isDebugEnabled()) { + log.debug("Changes have been successfully updated."); + } + return true; + } catch (PolicyManagementException e) { + String error = "Exception in applying changes."; + log.error(error, e); + return false; + } catch (DeviceManagementException e) { + String error = "Error occurred while invoking Policy Management Service."; + log.error(error, e); + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + return false; + } finally { + this.endTenantFlow(); + } + } + + @GET + @Path("/start-task/{milliseconds}") + public boolean startTaskService(@PathParam("milliseconds") int monitoringFrequency) { + + try { + PolicyManagerService policyManagerService = getPolicyServiceProvider(); + TaskScheduleService taskScheduleService = policyManagerService.getTaskScheduleService(); + taskScheduleService.startTask(monitoringFrequency); + if (log.isDebugEnabled()) { + log.debug("Policy monitoring service started successfully."); + } + return true; + } catch (PolicyMonitoringTaskException e) { + String error = "Policy Management related exception."; + log.error(error, e); + return false; + } catch (DeviceManagementException e) { + String error = "Error occurred while invoking Policy Management Service."; + log.error(error, e); + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + return false; + } finally { + this.endTenantFlow(); + } + } + + @GET + @Path("/update-task/{milliseconds}") + public boolean updateTaskService(@PathParam("milliseconds") int monitoringFrequency) { + + try { + PolicyManagerService policyManagerService = getPolicyServiceProvider(); + TaskScheduleService taskScheduleService = policyManagerService.getTaskScheduleService(); + taskScheduleService.updateTask(monitoringFrequency); + if (log.isDebugEnabled()) { + log.debug("Policy monitoring service updated successfully."); + } + return true; + } catch (PolicyMonitoringTaskException e) { + String error = "Policy Management related exception."; + log.error(error, e); + return false; + } catch (DeviceManagementException e) { + String error = "Error occurred while invoking Policy Management Service."; + log.error(error, e); + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + return false; + } finally { + this.endTenantFlow(); + } + } + + @GET + @Path("/stop-task") + public boolean stopTaskService() { + + try { + PolicyManagerService policyManagerService = getPolicyServiceProvider(); + TaskScheduleService taskScheduleService = policyManagerService.getTaskScheduleService(); + taskScheduleService.stopTask(); + if (log.isDebugEnabled()) { + log.debug("Policy monitoring service stopped successfully."); + } + return true; + } catch (PolicyMonitoringTaskException e) { + String error = "Policy Management related exception."; + log.error(error, e); + return false; + } catch (DeviceManagementException e) { + String error = "Error occurred while invoking Policy Management Service."; + log.error(error, e); + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + return false; + } finally { + this.endTenantFlow(); + } + } + + @GET + @Path("/{type}/{id}") + public ComplianceData getComplianceDataOfDevice(@PathParam("id") String deviceId, + @PathParam("type") String deviceType) { + try { + PolicyManagerService policyManagerService = getPolicyServiceProvider(); + DeviceIdentifier deviceIdentifier = new DeviceIdentifier(); + deviceIdentifier.setType(deviceType); + deviceIdentifier.setId(deviceId); + return policyManagerService.getDeviceCompliance(deviceIdentifier); + } catch (PolicyComplianceException e) { + String error = "Error occurred while getting the compliance data."; + log.error(error, e); + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + return null; + } catch (DeviceManagementException e) { + String error = "Error occurred while invoking Policy Management Service."; + log.error(error, e); + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + return null; + } finally { + this.endTenantFlow(); + } + } + +} diff --git a/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.api/src/main/java/org/wso2/carbon/device/mgt/iot/api/StatsManagerService.java b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.api/src/main/java/org/wso2/carbon/device/mgt/iot/api/StatsManagerService.java new file mode 100644 index 0000000000..4c696654ae --- /dev/null +++ b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.api/src/main/java/org/wso2/carbon/device/mgt/iot/api/StatsManagerService.java @@ -0,0 +1,103 @@ +/* + * 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.wso2.carbon.device.mgt.iot.api; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.context.PrivilegedCarbonContext; +import org.wso2.carbon.device.mgt.analytics.common.AnalyticsDataRecord; +import org.wso2.carbon.device.mgt.analytics.exception.DeviceManagementAnalyticsException; +import org.wso2.carbon.device.mgt.analytics.service.DeviceAnalyticsService; +import javax.jws.WebService; +import javax.servlet.http.HttpServletResponse; +import javax.ws.rs.Consumes; +import javax.ws.rs.GET; +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.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +@WebService public class StatsManagerService { + + private static Log log = LogFactory.getLog(StatsManagerService.class); + + @Context //injected response proxy supporting multiple thread + private HttpServletResponse response; + //TODO THIS NEEDS TO BE REMOVED. + @Path("/stats/device/type/{type}/identifier/{identifier}") + @GET + @Consumes("application/json") + @Produces("application/json") + public DeviceUsageDTO[] getDeviceStats(@PathParam("type") String type, @PathParam("identifier") String identifier, + @QueryParam("table") String table, @QueryParam("column") String column, @QueryParam("username") String user, + @QueryParam("from") long from, @QueryParam("to") long to) { + + String fromDate = String.valueOf(from); + String toDate = String.valueOf(to); + + List deviceUsageDTOs = new ArrayList<>(); + PrivilegedCarbonContext.startTenantFlow(); + PrivilegedCarbonContext ctx = PrivilegedCarbonContext.getThreadLocalCarbonContext(); + ctx.setTenantDomain("carbon.super", true); + DeviceAnalyticsService deviceAnalyticsService = (DeviceAnalyticsService) ctx + .getOSGiService(DeviceAnalyticsService.class, null); + String query = "owner:" + user + " AND deviceId:" + identifier + " AND deviceType:" + type + + " AND time : [" + fromDate + " TO " + toDate + "]"; + try { + List records = deviceAnalyticsService.getAllEventsForDevice(table, query); + + Collections.sort(records, new Comparator() { + @Override + public int compare(AnalyticsDataRecord o1, AnalyticsDataRecord o2) { + long t1 = (Long) o1.getValue("time"); + long t2 = (Long) o2.getValue("time"); + if (t1 < t2) { + return -1; + } else if (t1 > t2) { + return 1; + } else { + return 0; + } + } + }); + + for (AnalyticsDataRecord record : records) { + DeviceUsageDTO deviceUsageDTO = new DeviceUsageDTO(); + deviceUsageDTO.setTime("" + (long)record.getValue("time")); + deviceUsageDTO.setValue("" + (float) record.getValue(column.toLowerCase())); + deviceUsageDTOs.add(deviceUsageDTO); + } + return deviceUsageDTOs.toArray(new DeviceUsageDTO[deviceUsageDTOs.size()]); + } catch (DeviceManagementAnalyticsException e) { + String errorMsg= "Error on retrieving stats on table " + table + " with query " + query; + log.error(errorMsg); + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + return deviceUsageDTOs.toArray(new DeviceUsageDTO[deviceUsageDTOs.size()]); + } finally { + PrivilegedCarbonContext.endTenantFlow(); + } + } + +} diff --git a/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.api/src/main/java/org/wso2/carbon/device/mgt/iot/util/ResponsePayload.java b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.api/src/main/java/org/wso2/carbon/device/mgt/iot/util/ResponsePayload.java new file mode 100644 index 0000000000..c37d1d3a0e --- /dev/null +++ b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.api/src/main/java/org/wso2/carbon/device/mgt/iot/util/ResponsePayload.java @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2016, 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.wso2.carbon.device.mgt.iot.util; + +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; + +@XmlRootElement +public class ResponsePayload { + + private int statusCode; + private String messageFromServer; + private Object responseContent; + + public static ResponsePayloadBuilder statusCode(int statusCode) { + ResponsePayload message = new ResponsePayload(); + return message.getBuilder().statusCode(statusCode); + } + + public static ResponsePayloadBuilder messageFromServer( + String messageFromServer) { + ResponsePayload message = new ResponsePayload(); + return message.getBuilder().messageFromServer(messageFromServer); + } + + public static ResponsePayloadBuilder responseContent(String responseContent) { + ResponsePayload message = new ResponsePayload(); + return message.getBuilder().responseContent(responseContent); + } + + @XmlElement + public int getStatusCode() { + return statusCode; + } + + public void setStatusCode(int statusCode) { + this.statusCode = statusCode; + } + + @XmlElement + public String getMessageFromServer() { + return messageFromServer; + } + + public void setMessageFromServer(String messageFromServer) { + this.messageFromServer = messageFromServer; + } + + @XmlElement + public Object getResponseContent() { + return responseContent; + } + + public void setResponseContent(Object responseContent) { + this.responseContent = responseContent; + } + + private ResponsePayloadBuilder getBuilder() { + return new ResponsePayloadBuilder(); + } + + public class ResponsePayloadBuilder { + + private int statusCode; + private String messageFromServer; + private Object responseContent; + + public ResponsePayloadBuilder statusCode(int statusCode) { + this.statusCode = statusCode; + return this; + } + + public ResponsePayloadBuilder messageFromServer(String messageFromServer) { + this.messageFromServer = messageFromServer; + return this; + } + + public ResponsePayloadBuilder responseContent(String responseContent) { + this.responseContent = responseContent; + return this; + } + + public ResponsePayload build() { + ResponsePayload payload = new ResponsePayload(); + payload.setStatusCode(statusCode); + payload.setMessageFromServer(messageFromServer); + payload.setResponseContent(responseContent); + return payload; + } + } + +} diff --git a/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.api/src/main/webapp/META-INF/permissions.xml b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.api/src/main/webapp/META-INF/permissions.xml new file mode 100644 index 0000000000..aa291109e3 --- /dev/null +++ b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.api/src/main/webapp/META-INF/permissions.xml @@ -0,0 +1,113 @@ + + + + + + + + + Get Device of user + /login + /device_manager/device/user/{username}/all + GET + + + + + Get ungrouped devices of user + /login + /device_manager/device/user/{username}/ungrouped + GET + + + + + Get Device of user + /login + /device_manager/device_manager/device/user/{username}/ungrouped + GET + + + + + Get count of all the devices + /login + /device_manager/device/user/{username}/all/count + GET + + + + + Get specific device + /login + /device_manager/device/type/{type}/identifier/{identifier} + GET + + + + + Get the device type + /login + /device_manager/device/type/all + GET + + + + + Get all devices + /login + /device_manager/device/all + GET + + + + + Get the device by name + /login + /device_manager/device/name/{name}/all + GET + + + + + Update device enrollment info + /login + /device_manager/device/type/{type}/identifier/{identifier}/status + GET + + + + + Update device enrollment info + /login + /device_manager/device/type/{type}/identifier/{identifier}/status + GET + + + + \ No newline at end of file diff --git a/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.api/src/main/webapp/META-INF/webapp-classloading.xml b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.api/src/main/webapp/META-INF/webapp-classloading.xml new file mode 100644 index 0000000000..fa44619195 --- /dev/null +++ b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.api/src/main/webapp/META-INF/webapp-classloading.xml @@ -0,0 +1,33 @@ + + + + + + + + + false + + + CXF,Carbon + diff --git a/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.api/src/main/webapp/WEB-INF/cxf-servlet.xml b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.api/src/main/webapp/WEB-INF/cxf-servlet.xml new file mode 100644 index 0000000000..1112d721e8 --- /dev/null +++ b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.api/src/main/webapp/WEB-INF/cxf-servlet.xml @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.api/src/main/webapp/WEB-INF/web.xml b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.api/src/main/webapp/WEB-INF/web.xml new file mode 100755 index 0000000000..4335265197 --- /dev/null +++ b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.api/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,27 @@ + + + WSO2 IoT Server + WSO2 IoT Server + + + CXFServlet + org.apache.cxf.transport.servlet.CXFServlet + 1 + + + CXFServlet + /* + + + isAdminService + false + + + doAuthentication + true + + diff --git a/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/pom.xml b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/pom.xml new file mode 100644 index 0000000000..2d8b711020 --- /dev/null +++ b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/pom.xml @@ -0,0 +1,62 @@ + + + + + + + + iot-base-plugin + org.wso2.carbon.devicemgt-plugins + 2.1.0-SNAPSHOT + ../pom.xml + + + 4.0.0 + org.wso2.carbon.device.mgt.iot.ui + WSO2 Carbon - IoT Server UI + pom + + + + + maven-assembly-plugin + 2.5.5 + + ${project.artifactId}-${carbon.device.mgt.version} + false + + src/assembly/src.xml + + + + + create-archive + package + + single + + + + + + + + \ No newline at end of file diff --git a/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/assembly/src.xml b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/assembly/src.xml new file mode 100644 index 0000000000..2797034e07 --- /dev/null +++ b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/assembly/src.xml @@ -0,0 +1,36 @@ + + + + src + + zip + + false + ${basedir}/src + + + ${basedir}/src/main/resources/jaggeryapps/devicemgt + / + true + + + \ No newline at end of file diff --git a/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/conf/app-conf.json b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/conf/app-conf.json new file mode 100644 index 0000000000..6d4d99825d --- /dev/null +++ b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/conf/app-conf.json @@ -0,0 +1,35 @@ +{ + "appName": "IoT Server", + "cachingEnabled": true, + "debuggingEnabled": false, + "permissionRoot": "/", + "loginPage": "cdmf.page.sign-in", + "adminServicesUrl": "https://${server.ip}:${server.https_port}/admin/services/", + "authModule": { + "enabled": true, + "login": { + "onSuccess": { + "script": "/app/modules/login.js", + "page": "cdmf.page.dashboard" + }, + "onFail": { + "script": "/app/modules/login.js", + "page": "cdmf.page.sign-in" + } + }, + "logout": { + "onSuccess": { + "page": "cdmf.page.sign-in" + }, + "onFail": { + "page": "cdmf.page.dashboard" + } + }, + "sso": { + "enabled": false + } + }, + "errorPages": { + "default": "uuf.page.error" + } +} \ No newline at end of file diff --git a/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/pages/iot.page.register/public/js/validate-register.js b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/pages/iot.page.register/public/js/validate-register.js new file mode 100644 index 0000000000..ea67fcc832 --- /dev/null +++ b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/pages/iot.page.register/public/js/validate-register.js @@ -0,0 +1,163 @@ +/* + * Copyright (c) 2016, 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. + */ + +/** + * Checks if provided input is valid against RegEx input. + * + * @param regExp Regular expression + * @param inputString Input string to check + * @returns {boolean} Returns true if input matches RegEx + */ +function inputIsValid(regExp, inputString) { + regExp = new RegExp(regExp); + return regExp.test(inputString); +} + +/** + * Checks if an email address has the valid format or not. + * + * @param email Email address + * @returns {boolean} true if email has the valid format, otherwise false. + */ +function emailIsValid(email) { + var regExp = /^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/; + return regExp.test(email); +} + +$(document).ready(function(){ + + /** + * Following click function would execute + * when a user clicks on "Add User" button + * on Add User page in WSO2 Devicemgt Console. + */ + $("button#add-user-btn").click(function () { + + var usernameInput = $("input#user_name"); + var firstnameInput = $("input#first_name"); + var lastnameInput = $("input#last_name"); + var emailInput = $("input#email"); + var passwordInput = $("input#password"); + var passwordConfirmationInput = $("input#password_confirmation"); + + var username = usernameInput.val().trim(); + var firstname = firstnameInput.val(); + var lastname = lastnameInput.val(); + var emailAddress = emailInput.val(); + var password = passwordInput.val(); + var passwordConfirmation = passwordConfirmationInput.val(); + var errorMsgWrapper = "#user-create-error-msg"; + var errorMsg = "#user-create-error-msg span"; + + if (!firstname) { + $(errorMsg).text("Firstname is a required field. It cannot be empty."); + $(errorMsgWrapper).removeClass("hidden"); + } else if (!inputIsValid(firstnameInput.data("regex"), firstname)) { + $(errorMsg).text(firstnameInput.data("errormsg")); + $(errorMsgWrapper).removeClass("hidden"); + } else if (!lastname) { + $(errorMsg).text("Lastname is a required field. It cannot be empty."); + $(errorMsgWrapper).removeClass("hidden"); + } else if (!inputIsValid(lastnameInput.data("regex"), lastname)) { + $(errorMsg).text(lastnameInput.data("errormsg")); + $(errorMsgWrapper).removeClass("hidden"); + } else if (!username) { + $(errorMsg).text("Username is a required field. It cannot be empty."); + $(errorMsgWrapper).removeClass("hidden"); + } else if (!inputIsValid(usernameInput.data("regex"), username)) { + $(errorMsg).text(usernameInput.data("errormsg")); + $(errorMsgWrapper).removeClass("hidden"); + } else if (!emailAddress) { + $(errorMsg).text("Email is a required field. It cannot be empty."); + $(errorMsgWrapper).removeClass("hidden"); + } else if (!emailIsValid(emailAddress)) { + $(errorMsg).text(emailInput.data("errormsg")); + $(errorMsgWrapper).removeClass("hidden"); + } else if (!password) { + $(errorMsg).text("Password is a required field. It cannot be empty."); + $(errorMsgWrapper).removeClass("hidden"); + } else if (password.length < 6) { + $(errorMsg).text("Password is a required field. It cannot be empty."); + $(errorMsgWrapper).removeClass("hidden"); + } else if (password != passwordConfirmation) { + $(errorMsg).text("Please enter the same password for confirmation."); + $(errorMsgWrapper).removeClass("hidden"); + } else { + $(errorMsgWrapper).addClass("hidden"); + $("#add-user-btn").prop('disabled', true); + + var addUserFormData = {}; + addUserFormData.username = username; + addUserFormData.firstname = firstname; + addUserFormData.lastname = lastname; + addUserFormData.emailAddress = emailAddress; + addUserFormData.password = $("input#password").val(); + addUserFormData.userRoles = null; + + var context = $(".form-login-box").data("context"); + var addUserAPI = context + "/api/user/register"; + + $.ajax({ + type: 'POST', + url: addUserAPI, + contentType: 'application/json', + data: JSON.stringify(addUserFormData), + success: function (data) { + $("#add-user-btn").prop('disabled', false); + if (data == 200) { + $('.wr-validation-summary strong').html( + " Successfully Submitted."); + $('.wr-validation-summary').removeClass("alert-danger"); + $('.wr-validation-summary').addClass("alert-success"); + } else if (data == 201) { + $('.wr-validation-summary strong').html( + " User created succssfully. You will be " + + "redirected to login page."); + $('.wr-validation-summary').removeClass("alert-danger"); + $('.wr-validation-summary').addClass("alert-success"); + $("#add-user-btn").prop('disabled', true); + setTimeout(function () { + window.location = context + "/login"; + }, 2000); + } else if (data == 400) { + $('.wr-validation-summary strong').html( + " Exception at backend."); + $('.wr-validation-summary').removeClass("alert-danger"); + $('.wr-validation-summary').addClass("alert-warning"); + } else if (data == 403) { + $('.wr-validation-summary strong').html("Action not permitted."); + } else if (data == 409) { + $('.wr-validation-summary strong').html( + " User name already exists."); + $('.wr-validation-summary').removeClass("alert-default"); + $('.wr-validation-summary').addClass("alert-success"); + } + $('.wr-validation-summary').removeClass("hidden"); + $('#password').val(''); + $('#password_confirmation').val(''); + }, + error: function (err) { + $("#add-user-btn").prop('disabled', false); + $('.wr-validation-summary strong').html( + " An unexpected error occurred."); + $('.wr-validation-summary').removeClass("hidden"); + } + }); + } + }); +}); diff --git a/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/pages/iot.page.register/register.hbs b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/pages/iot.page.register/register.hbs new file mode 100644 index 0000000000..e68e3ef1da --- /dev/null +++ b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/pages/iot.page.register/register.hbs @@ -0,0 +1,75 @@ +{{unit "cdmf.unit.ui.title" pageTitle="Register"}} + +{{#zone "content"}} + {{unit "uuf.unit.lib.form-validation"}} + + +{{/zone}} + +{{#zone "bottomJs"}} + {{js "js/validate-register.js"}} +{{/zone}} diff --git a/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/pages/iot.page.register/register.js b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/pages/iot.page.register/register.js new file mode 100644 index 0000000000..d0bdca27b1 --- /dev/null +++ b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/pages/iot.page.register/register.js @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2016, 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. + */ + +/** + * Returns the dynamic state to be populated by add-user page. + * + * @param context Object that gets updated with the dynamic state of this page to be presented + * @returns {*} A context object that returns the dynamic state of this page to be presented + */ +function onRequest(context) { + var devicemgtProps = require('/app/conf/devicemgt-props.js').config(); + var page = {}; + page["usernameJSRegEx"] = devicemgtProps.userValidationConfig.usernameJSRegEx; + page["usernameHelpText"] = devicemgtProps.userValidationConfig.usernameHelpMsg; + page["usernameRegExViolationErrorMsg"] = devicemgtProps.userValidationConfig.usernameRegExViolationErrorMsg; + page["firstnameJSRegEx"] = devicemgtProps.userValidationConfig.firstnameJSRegEx; + page["firstnameRegExViolationErrorMsg"] = devicemgtProps.userValidationConfig.firstnameRegExViolationErrorMsg; + page["lastnameJSRegEx"] = devicemgtProps.userValidationConfig.lastnameJSRegEx; + page["lastnameRegExViolationErrorMsg"] = devicemgtProps.userValidationConfig.lastnameRegExViolationErrorMsg; + page["emailJSRegEx"] = devicemgtProps.userValidationConfig.emailJSRegEx; + page["emailRegExViolationErrorMsg"] = devicemgtProps.userValidationConfig.emailRegExViolationErrorMsg; + return page; +} \ No newline at end of file diff --git a/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/pages/iot.page.register/register.json b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/pages/iot.page.register/register.json new file mode 100644 index 0000000000..58346ba14a --- /dev/null +++ b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/pages/iot.page.register/register.json @@ -0,0 +1,6 @@ +{ + "version": "1.0.0", + "uri": "/register", + "isAnonymous": true, + "layout": "uuf.layout.sign-in" +} \ No newline at end of file diff --git a/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/pages/iot.page.sign-in/sign-in.hbs b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/pages/iot.page.sign-in/sign-in.hbs new file mode 100644 index 0000000000..4ab4ff871b --- /dev/null +++ b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/pages/iot.page.sign-in/sign-in.hbs @@ -0,0 +1,5 @@ +{{#zone "signInForm-below"}} + +{{/zone}} diff --git a/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/pages/iot.page.sign-in/sign-in.json b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/pages/iot.page.sign-in/sign-in.json new file mode 100644 index 0000000000..dea3950ba9 --- /dev/null +++ b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/pages/iot.page.sign-in/sign-in.json @@ -0,0 +1,4 @@ +{ +"version": "1.0.0", +"extends": "cdmf.page.sign-in" +} \ No newline at end of file diff --git a/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.device.operation-bar/operation-bar.hbs b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.device.operation-bar/operation-bar.hbs new file mode 100644 index 0000000000..bdeac19679 --- /dev/null +++ b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.device.operation-bar/operation-bar.hbs @@ -0,0 +1,75 @@ +{{#if control_operations}} +
      + + {{#each control_operations}} + + {{#if icon}} + + {{else}} + + {{/if}} + {{name}} + + +
      +
      +
      +
      +

      + + + + + {{name}} +
      +

      +

      + {{description}} +
      +

      + +
      + {{#each params}} + +
      + {{/each}} + + + +
      +
      +
      +
      +
      + {{/each}} +
      +{{else}} +
      +

      + Operations Loading Failed!

      +
      +{{/if}} + +{{#zone "bottomJs"}} + {{js "js/operation-bar.js"}} +{{/zone}} diff --git a/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.device.operation-bar/operation-bar.js b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.device.operation-bar/operation-bar.js new file mode 100644 index 0000000000..6a90314eac --- /dev/null +++ b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.device.operation-bar/operation-bar.js @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2016, 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("operation.js"); + var operationModule = require("/app/modules/operation.js").operationModule; + var device = context.unit.params.device; + var controlOperations; + try { + controlOperations = operationModule.getControlOperations(device.type); + }catch(e){ + log.error("Control operation loading failed."); + controlOperations = null; + } + return {"control_operations": controlOperations, "device": device}; +} \ No newline at end of file diff --git a/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.device.operation-bar/operation-bar.json b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.device.operation-bar/operation-bar.json new file mode 100644 index 0000000000..688e939808 --- /dev/null +++ b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.device.operation-bar/operation-bar.json @@ -0,0 +1,3 @@ +{ + "version": "1.0.0" +} \ No newline at end of file diff --git a/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.device.operation-bar/public/js/operation-bar.js b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.device.operation-bar/public/js/operation-bar.js new file mode 100644 index 0000000000..efaa79aca7 --- /dev/null +++ b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.device.operation-bar/public/js/operation-bar.js @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2016, 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. + */ + +/* + * On operation click function. + * @param selection: Selected operation + */ +function operationSelect (selection) { + $(modalPopupContent).addClass("operation-data"); + $(modalPopupContent).html($(" .operation[data-operation-code=" + selection + "]").html()); + $(modalPopupContent).data("operation-code", selection); + showPopup(); +} + +$(document).on('submit', 'form', function (e) { + e.preventDefault(); + var postOperationRequest = $.ajax({ + url: $(this).attr("action") + '&' + $(this).serialize(), + method: "post" + }); + + var btnSubmit = $('#btnSend', this); + btnSubmit.addClass('hidden'); + + var lblSending = $('#lblSending', this); + lblSending.removeClass('hidden'); + + var lblSent = $('#lblSent', this); + postOperationRequest.done(function (data) { + lblSending.addClass('hidden'); + lblSent.removeClass('hidden'); + setTimeout(function () { + hidePopup(); + }, 3000); + }); + + postOperationRequest.fail(function (jqXHR, textStatus) { + lblSending.addClass('hidden'); + lblSent.addClass('hidden'); + }); +}); \ No newline at end of file diff --git a/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.device.stats/public/css/graph.css b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.device.stats/public/css/graph.css new file mode 100644 index 0000000000..7d0c9bba1d --- /dev/null +++ b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.device.stats/public/css/graph.css @@ -0,0 +1,471 @@ +/* + * Copyright (c) 2016, 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. + */ + +/* graph */ + +.rickshaw_graph { + position: relative; +} + +.rickshaw_graph svg { + display: block; + overflow: hidden; +} + +/* ticks */ + +.rickshaw_graph .x_tick { + position: absolute; + top: 0; + bottom: 0; + width: 0; + border-left: 1px dotted rgba(0, 0, 0, 0.2); + pointer-events: none; +} + +.rickshaw_graph .x_tick .title { + position: absolute; + font-size: 12px; + font-family: Arial, sans-serif; + opacity: 0.5; + white-space: nowrap; + margin-left: 3px; + bottom: -20px; + height: auto; + border-bottom: none; +} + +/* annotations */ + +.rickshaw_annotation_timeline { + height: 1px; + border-top: 1px solid #e0e0e0; + margin-top: 10px; + position: relative; +} + +.rickshaw_annotation_timeline .annotation { + position: absolute; + height: 6px; + width: 6px; + margin-left: -2px; + top: -3px; + border-radius: 5px; + background-color: rgba(0, 0, 0, 0.25); +} + +.rickshaw_graph .annotation_line { + position: absolute; + top: 0; + bottom: -6px; + width: 0; + border-left: 2px solid rgba(0, 0, 0, 0.3); + display: none; +} + +.rickshaw_graph .annotation_line.active { + display: block; +} + +.rickshaw_graph .annotation_range { + background: rgba(0, 0, 0, 0.1); + display: none; + position: absolute; + top: 0; + bottom: -6px; +} + +.rickshaw_graph .annotation_range.active { + display: block; +} + +.rickshaw_graph .annotation_range.active.offscreen { + display: none; +} + +.rickshaw_annotation_timeline .annotation .content { + background: white; + color: black; + opacity: 0.9; + box-shadow: 0 0 2px rgba(0, 0, 0, 0.8); + border-radius: 3px; + position: relative; + z-index: 20; + font-size: 12px; + padding: 6px 8px 8px; + top: 18px; + left: -11px; + width: 160px; + display: none; + cursor: pointer; +} + +.rickshaw_annotation_timeline .annotation .content:before { + content: "\25b2"; + position: absolute; + top: -11px; + color: white; + text-shadow: 0 -1px 1px rgba(0, 0, 0, 0.8); +} + +.rickshaw_annotation_timeline .annotation.active, +.rickshaw_annotation_timeline .annotation:hover { + background-color: rgba(0, 0, 0, 0.8); + cursor: none; +} + +.rickshaw_annotation_timeline .annotation .content:hover { + z-index: 50; +} + +.rickshaw_annotation_timeline .annotation.active .content { + display: block; +} + +.rickshaw_annotation_timeline .annotation:hover .content { + display: block; + z-index: 50; +} + +.rickshaw_graph .y_axis, +.rickshaw_graph .x_axis_d3 { + fill: none; +} + +.rickshaw_graph .y_ticks .tick line, +.rickshaw_graph .x_ticks_d3 .tick { + stroke: rgba(0, 0, 0, 0.16); + stroke-width: 2px; + shape-rendering: crisp-edges; + pointer-events: none; +} + +.rickshaw_graph .y_grid .tick, +.rickshaw_graph .x_grid_d3 .tick { + z-index: -1; + stroke: rgba(0, 0, 0, 0.20); + stroke-width: 1px; + stroke-dasharray: 1 1; +} + +.rickshaw_graph .y_grid .tick[data-y-value="0"] { + stroke-dasharray: 1 0; +} + +.rickshaw_graph .y_grid path, +.rickshaw_graph .x_grid_d3 path { + fill: none; + stroke: none; +} + +.rickshaw_graph .y_ticks path, +.rickshaw_graph .x_ticks_d3 path { + fill: none; + stroke: #808080; +} + +.rickshaw_graph .y_ticks text, +.rickshaw_graph .x_ticks_d3 text { + opacity: 0.5; + font-size: 12px; + pointer-events: none; +} + +.rickshaw_graph .x_tick.glow .title, +.rickshaw_graph .y_ticks.glow text { + fill: black; + color: black; + text-shadow: -1px 1px 0 rgba(255, 255, 255, 0.1), + 1px -1px 0 rgba(255, 255, 255, 0.1), + 1px 1px 0 rgba(255, 255, 255, 0.1), + 0 1px 0 rgba(255, 255, 255, 0.1), + 0 -1px 0 rgba(255, 255, 255, 0.1), + 1px 0 0 rgba(255, 255, 255, 0.1), + -1px 0 0 rgba(255, 255, 255, 0.1), + -1px -1px 0 rgba(255, 255, 255, 0.1); +} + +.rickshaw_graph .x_tick.inverse .title, +.rickshaw_graph .y_ticks.inverse text { + fill: white; + color: white; + text-shadow: -1px 1px 0 rgba(0, 0, 0, 0.8), + 1px -1px 0 rgba(0, 0, 0, 0.8), + 1px 1px 0 rgba(0, 0, 0, 0.8), + 0 1px 0 rgba(0, 0, 0, 0.8), + 0 -1px 0 rgba(0, 0, 0, 0.8), + 1px 0 0 rgba(0, 0, 0, 0.8), + -1px 0 0 rgba(0, 0, 0, 0.8), + -1px -1px 0 rgba(0, 0, 0, 0.8); +} + +.custom_rickshaw_graph { + position: relative; + left: 40px; +} + +.custom_y_axis { + position: absolute; + width: 40px; + margin-top: -20px; +} + +.custom_slider { + left: 40px; +} + +.custom_x_axis { + position: relative; + left: 40px; + height: 30px; + width: 97%; + top: 20px; + text-align: right; +} + +.chartWrapper { + padding-top: 50px; +} + +/*detail*/ + +.rickshaw_graph .detail { + pointer-events: none; + position: absolute; + top: 0; + z-index: 2; + background: rgba(0, 0, 0, 0.1); + bottom: 0; + width: 1px; + transition: opacity 0.25s linear; + -moz-transition: opacity 0.25s linear; + -o-transition: opacity 0.25s linear; + -webkit-transition: opacity 0.25s linear; +} + +.rickshaw_graph .detail.inactive { + opacity: 0; +} + +.rickshaw_graph .detail .item.active { + opacity: 1; +} + +.rickshaw_graph .detail .x_label { + font-family: Arial, sans-serif; + border-radius: 3px; + padding: 6px; + opacity: 0.5; + border: 1px solid #e0e0e0; + font-size: 12px; + position: absolute; + background: white; + white-space: nowrap; +} + +.rickshaw_graph .detail .x_label.left { + left: 0; +} + +.rickshaw_graph .detail .x_label.right { + right: 0; +} + +.rickshaw_graph .detail .item { + position: absolute; + z-index: 2; + border-radius: 3px; + padding: 0.25em; + font-size: 12px; + font-family: Arial, sans-serif; + opacity: 0; + background: rgba(0, 0, 0, 0.4); + color: white; + border: 1px solid rgba(0, 0, 0, 0.4); + margin-left: 1em; + margin-right: 1em; + margin-top: -1em; + white-space: nowrap; +} + +.rickshaw_graph .detail .item.left { + left: 0; +} + +.rickshaw_graph .detail .item.right { + right: 0; +} + +.rickshaw_graph .detail .item.active { + opacity: 1; + background: rgba(0, 0, 0, 0.8); +} + +.rickshaw_graph .detail .item:after { + position: absolute; + display: block; + width: 0; + height: 0; + + content: ""; + + border: 5px solid transparent; +} + +.rickshaw_graph .detail .item.left:after { + top: 1em; + left: -5px; + margin-top: -5px; + border-right-color: rgba(0, 0, 0, 0.8); + border-left-width: 0; +} + +.rickshaw_graph .detail .item.right:after { + top: 1em; + right: -5px; + margin-top: -5px; + border-left-color: rgba(0, 0, 0, 0.8); + border-right-width: 0; +} + +.rickshaw_graph .detail .dot { + width: 4px; + height: 4px; + margin-left: -3px; + margin-top: -3.5px; + border-radius: 5px; + position: absolute; + box-shadow: 0 0 2px rgba(0, 0, 0, 0.6); + box-sizing: content-box; + -moz-box-sizing: content-box; + background: white; + border-width: 2px; + border-style: solid; + display: none; + background-clip: padding-box; +} + +.rickshaw_graph .detail .dot.active { + display: block; +} + +/*legend*/ +.rickshaw_legend { + font-family: Arial; + font-size: 12px; + color: white; + background: #404040; + display: inline-block; + padding: 12px 5px; + border-radius: 2px; + position: relative; + float: right; +} + +.rickshaw_legend:hover { + z-index: 10; +} + +.rickshaw_legend .swatch { + width: 10px; + height: 10px; + border: 1px solid rgba(0, 0, 0, 0.2); +} + +.rickshaw_legend .line { + clear: both; + line-height: 140%; + padding-right: 15px; +} + +.rickshaw_legend .line .swatch { + display: inline-block; + margin-right: 3px; + border-radius: 2px; +} + +.rickshaw_legend .label { + margin: 0; + white-space: nowrap; + display: inline; + font-size: inherit; + background-color: transparent; + color: inherit; + font-weight: normal; + line-height: normal; + padding: 0; + text-shadow: none; +} + +.rickshaw_legend .action:hover { + opacity: 0.6; +} + +.rickshaw_legend .action { + margin-right: 0.2em; + opacity: 0.2; + cursor: pointer; + font-size: 14px; +} + +.rickshaw_legend .line.disabled { + opacity: 0.4; +} + +.rickshaw_legend ul { + list-style-type: none; + padding: 0; + margin: 2px; + cursor: pointer; +} + +.rickshaw_legend li { + padding: 0 0 0 2px; + min-width: 80px; + white-space: nowrap; +} + +.rickshaw_legend li:hover { + background: rgba(255, 255, 255, 0.08); + border-radius: 3px; +} + +.rickshaw_legend li:active { + background: rgba(255, 255, 255, 0.2); + border-radius: 3px; +} + +.legend { + display: inline-block; + position: relative; + left: 8px; +} + +.legend_container { + float: right; + padding-right: 10px; + width: 0; + z-index: 1; + position: relative; + opacity: 0.7; +} + +.spaced { + margin-top: 20px !important; +} + diff --git a/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.device.stats/public/images/map-marker-1.png b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.device.stats/public/images/map-marker-1.png new file mode 100644 index 0000000000..82ae21a32b Binary files /dev/null and b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.device.stats/public/images/map-marker-1.png differ diff --git a/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.device.stats/public/images/map-marker-2.png b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.device.stats/public/images/map-marker-2.png new file mode 100644 index 0000000000..83948f1313 Binary files /dev/null and b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.device.stats/public/images/map-marker-2.png differ diff --git a/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.device.stats/public/js/d3.min.js b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.device.stats/public/js/d3.min.js new file mode 100644 index 0000000000..71f9715eec --- /dev/null +++ b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.device.stats/public/js/d3.min.js @@ -0,0 +1,9488 @@ +/* + * Copyright (c) 2016, 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() { + var d3 = { + version: "3.5.2" + }; + if (!Date.now) Date.now = function() { + return +new Date(); + }; + var d3_arraySlice = [].slice, d3_array = function(list) { + return d3_arraySlice.call(list); + }; + var d3_document = document, d3_documentElement = d3_document.documentElement, d3_window = window; + try { + d3_array(d3_documentElement.childNodes)[0].nodeType; + } catch (e) { + d3_array = function(list) { + var i = list.length, array = new Array(i); + while (i--) array[i] = list[i]; + return array; + }; + } + try { + d3_document.createElement("div").style.setProperty("opacity", 0, ""); + } catch (error) { + var d3_element_prototype = d3_window.Element.prototype, d3_element_setAttribute = d3_element_prototype.setAttribute, d3_element_setAttributeNS = d3_element_prototype.setAttributeNS, d3_style_prototype = d3_window.CSSStyleDeclaration.prototype, d3_style_setProperty = d3_style_prototype.setProperty; + d3_element_prototype.setAttribute = function(name, value) { + d3_element_setAttribute.call(this, name, value + ""); + }; + d3_element_prototype.setAttributeNS = function(space, local, value) { + d3_element_setAttributeNS.call(this, space, local, value + ""); + }; + d3_style_prototype.setProperty = function(name, value, priority) { + d3_style_setProperty.call(this, name, value + "", priority); + }; + } + d3.ascending = d3_ascending; + function d3_ascending(a, b) { + return a < b ? -1 : a > b ? 1 : a >= b ? 0 : NaN; + } + d3.descending = function(a, b) { + return b < a ? -1 : b > a ? 1 : b >= a ? 0 : NaN; + }; + d3.min = function(array, f) { + var i = -1, n = array.length, a, b; + if (arguments.length === 1) { + while (++i < n) if ((b = array[i]) != null && b >= b) { + a = b; + break; + } + while (++i < n) if ((b = array[i]) != null && a > b) a = b; + } else { + while (++i < n) if ((b = f.call(array, array[i], i)) != null && b >= b) { + a = b; + break; + } + while (++i < n) if ((b = f.call(array, array[i], i)) != null && a > b) a = b; + } + return a; + }; + d3.max = function(array, f) { + var i = -1, n = array.length, a, b; + if (arguments.length === 1) { + while (++i < n) if ((b = array[i]) != null && b >= b) { + a = b; + break; + } + while (++i < n) if ((b = array[i]) != null && b > a) a = b; + } else { + while (++i < n) if ((b = f.call(array, array[i], i)) != null && b >= b) { + a = b; + break; + } + while (++i < n) if ((b = f.call(array, array[i], i)) != null && b > a) a = b; + } + return a; + }; + d3.extent = function(array, f) { + var i = -1, n = array.length, a, b, c; + if (arguments.length === 1) { + while (++i < n) if ((b = array[i]) != null && b >= b) { + a = c = b; + break; + } + while (++i < n) if ((b = array[i]) != null) { + if (a > b) a = b; + if (c < b) c = b; + } + } else { + while (++i < n) if ((b = f.call(array, array[i], i)) != null && b >= b) { + a = c = b; + break; + } + while (++i < n) if ((b = f.call(array, array[i], i)) != null) { + if (a > b) a = b; + if (c < b) c = b; + } + } + return [ a, c ]; + }; + function d3_number(x) { + return x === null ? NaN : +x; + } + function d3_numeric(x) { + return !isNaN(x); + } + d3.sum = function(array, f) { + var s = 0, n = array.length, a, i = -1; + if (arguments.length === 1) { + while (++i < n) if (d3_numeric(a = +array[i])) s += a; + } else { + while (++i < n) if (d3_numeric(a = +f.call(array, array[i], i))) s += a; + } + return s; + }; + d3.mean = function(array, f) { + var s = 0, n = array.length, a, i = -1, j = n; + if (arguments.length === 1) { + while (++i < n) if (d3_numeric(a = d3_number(array[i]))) s += a; else --j; + } else { + while (++i < n) if (d3_numeric(a = d3_number(f.call(array, array[i], i)))) s += a; else --j; + } + if (j) return s / j; + }; + d3.quantile = function(values, p) { + var H = (values.length - 1) * p + 1, h = Math.floor(H), v = +values[h - 1], e = H - h; + return e ? v + e * (values[h] - v) : v; + }; + d3.median = function(array, f) { + var numbers = [], n = array.length, a, i = -1; + if (arguments.length === 1) { + while (++i < n) if (d3_numeric(a = d3_number(array[i]))) numbers.push(a); + } else { + while (++i < n) if (d3_numeric(a = d3_number(f.call(array, array[i], i)))) numbers.push(a); + } + if (numbers.length) return d3.quantile(numbers.sort(d3_ascending), .5); + }; + d3.variance = function(array, f) { + var n = array.length, m = 0, a, d, s = 0, i = -1, j = 0; + if (arguments.length === 1) { + while (++i < n) { + if (d3_numeric(a = d3_number(array[i]))) { + d = a - m; + m += d / ++j; + s += d * (a - m); + } + } + } else { + while (++i < n) { + if (d3_numeric(a = d3_number(f.call(array, array[i], i)))) { + d = a - m; + m += d / ++j; + s += d * (a - m); + } + } + } + if (j > 1) return s / (j - 1); + }; + d3.deviation = function() { + var v = d3.variance.apply(this, arguments); + return v ? Math.sqrt(v) : v; + }; + function d3_bisector(compare) { + return { + left: function(a, x, lo, hi) { + if (arguments.length < 3) lo = 0; + if (arguments.length < 4) hi = a.length; + while (lo < hi) { + var mid = lo + hi >>> 1; + if (compare(a[mid], x) < 0) lo = mid + 1; else hi = mid; + } + return lo; + }, + right: function(a, x, lo, hi) { + if (arguments.length < 3) lo = 0; + if (arguments.length < 4) hi = a.length; + while (lo < hi) { + var mid = lo + hi >>> 1; + if (compare(a[mid], x) > 0) hi = mid; else lo = mid + 1; + } + return lo; + } + }; + } + var d3_bisect = d3_bisector(d3_ascending); + d3.bisectLeft = d3_bisect.left; + d3.bisect = d3.bisectRight = d3_bisect.right; + d3.bisector = function(f) { + return d3_bisector(f.length === 1 ? function(d, x) { + return d3_ascending(f(d), x); + } : f); + }; + d3.shuffle = function(array, i0, i1) { + if ((m = arguments.length) < 3) { + i1 = array.length; + if (m < 2) i0 = 0; + } + var m = i1 - i0, t, i; + while (m) { + i = Math.random() * m-- | 0; + t = array[m + i0], array[m + i0] = array[i + i0], array[i + i0] = t; + } + return array; + }; + d3.permute = function(array, indexes) { + var i = indexes.length, permutes = new Array(i); + while (i--) permutes[i] = array[indexes[i]]; + return permutes; + }; + d3.pairs = function(array) { + var i = 0, n = array.length - 1, p0, p1 = array[0], pairs = new Array(n < 0 ? 0 : n); + while (i < n) pairs[i] = [ p0 = p1, p1 = array[++i] ]; + return pairs; + }; + d3.zip = function() { + if (!(n = arguments.length)) return []; + for (var i = -1, m = d3.min(arguments, d3_zipLength), zips = new Array(m); ++i < m; ) { + for (var j = -1, n, zip = zips[i] = new Array(n); ++j < n; ) { + zip[j] = arguments[j][i]; + } + } + return zips; + }; + function d3_zipLength(d) { + return d.length; + } + d3.transpose = function(matrix) { + return d3.zip.apply(d3, matrix); + }; + d3.keys = function(map) { + var keys = []; + for (var key in map) keys.push(key); + return keys; + }; + d3.values = function(map) { + var values = []; + for (var key in map) values.push(map[key]); + return values; + }; + d3.entries = function(map) { + var entries = []; + for (var key in map) entries.push({ + key: key, + value: map[key] + }); + return entries; + }; + d3.merge = function(arrays) { + var n = arrays.length, m, i = -1, j = 0, merged, array; + while (++i < n) j += arrays[i].length; + merged = new Array(j); + while (--n >= 0) { + array = arrays[n]; + m = array.length; + while (--m >= 0) { + merged[--j] = array[m]; + } + } + return merged; + }; + var abs = Math.abs; + d3.range = function(start, stop, step) { + if (arguments.length < 3) { + step = 1; + if (arguments.length < 2) { + stop = start; + start = 0; + } + } + if ((stop - start) / step === Infinity) throw new Error("infinite range"); + var range = [], k = d3_range_integerScale(abs(step)), i = -1, j; + start *= k, stop *= k, step *= k; + if (step < 0) while ((j = start + step * ++i) > stop) range.push(j / k); else while ((j = start + step * ++i) < stop) range.push(j / k); + return range; + }; + function d3_range_integerScale(x) { + var k = 1; + while (x * k % 1) k *= 10; + return k; + } + function d3_class(ctor, properties) { + for (var key in properties) { + Object.defineProperty(ctor.prototype, key, { + value: properties[key], + enumerable: false + }); + } + } + d3.map = function(object, f) { + var map = new d3_Map(); + if (object instanceof d3_Map) { + object.forEach(function(key, value) { + map.set(key, value); + }); + } else if (Array.isArray(object)) { + var i = -1, n = object.length, o; + if (arguments.length === 1) while (++i < n) map.set(i, object[i]); else while (++i < n) map.set(f.call(object, o = object[i], i), o); + } else { + for (var key in object) map.set(key, object[key]); + } + return map; + }; + function d3_Map() { + this._ = Object.create(null); + } + var d3_map_proto = "__proto__", d3_map_zero = "\x00"; + d3_class(d3_Map, { + has: d3_map_has, + get: function(key) { + return this._[d3_map_escape(key)]; + }, + set: function(key, value) { + return this._[d3_map_escape(key)] = value; + }, + remove: d3_map_remove, + keys: d3_map_keys, + values: function() { + var values = []; + for (var key in this._) values.push(this._[key]); + return values; + }, + entries: function() { + var entries = []; + for (var key in this._) entries.push({ + key: d3_map_unescape(key), + value: this._[key] + }); + return entries; + }, + size: d3_map_size, + empty: d3_map_empty, + forEach: function(f) { + for (var key in this._) f.call(this, d3_map_unescape(key), this._[key]); + } + }); + function d3_map_escape(key) { + return (key += "") === d3_map_proto || key[0] === d3_map_zero ? d3_map_zero + key : key; + } + function d3_map_unescape(key) { + return (key += "")[0] === d3_map_zero ? key.slice(1) : key; + } + function d3_map_has(key) { + return d3_map_escape(key) in this._; + } + function d3_map_remove(key) { + return (key = d3_map_escape(key)) in this._ && delete this._[key]; + } + function d3_map_keys() { + var keys = []; + for (var key in this._) keys.push(d3_map_unescape(key)); + return keys; + } + function d3_map_size() { + var size = 0; + for (var key in this._) ++size; + return size; + } + function d3_map_empty() { + for (var key in this._) return false; + return true; + } + d3.nest = function() { + var nest = {}, keys = [], sortKeys = [], sortValues, rollup; + function map(mapType, array, depth) { + if (depth >= keys.length) return rollup ? rollup.call(nest, array) : sortValues ? array.sort(sortValues) : array; + var i = -1, n = array.length, key = keys[depth++], keyValue, object, setter, valuesByKey = new d3_Map(), values; + while (++i < n) { + if (values = valuesByKey.get(keyValue = key(object = array[i]))) { + values.push(object); + } else { + valuesByKey.set(keyValue, [ object ]); + } + } + if (mapType) { + object = mapType(); + setter = function(keyValue, values) { + object.set(keyValue, map(mapType, values, depth)); + }; + } else { + object = {}; + setter = function(keyValue, values) { + object[keyValue] = map(mapType, values, depth); + }; + } + valuesByKey.forEach(setter); + return object; + } + function entries(map, depth) { + if (depth >= keys.length) return map; + var array = [], sortKey = sortKeys[depth++]; + map.forEach(function(key, keyMap) { + array.push({ + key: key, + values: entries(keyMap, depth) + }); + }); + return sortKey ? array.sort(function(a, b) { + return sortKey(a.key, b.key); + }) : array; + } + nest.map = function(array, mapType) { + return map(mapType, array, 0); + }; + nest.entries = function(array) { + return entries(map(d3.map, array, 0), 0); + }; + nest.key = function(d) { + keys.push(d); + return nest; + }; + nest.sortKeys = function(order) { + sortKeys[keys.length - 1] = order; + return nest; + }; + nest.sortValues = function(order) { + sortValues = order; + return nest; + }; + nest.rollup = function(f) { + rollup = f; + return nest; + }; + return nest; + }; + d3.set = function(array) { + var set = new d3_Set(); + if (array) for (var i = 0, n = array.length; i < n; ++i) set.add(array[i]); + return set; + }; + function d3_Set() { + this._ = Object.create(null); + } + d3_class(d3_Set, { + has: d3_map_has, + add: function(key) { + this._[d3_map_escape(key += "")] = true; + return key; + }, + remove: d3_map_remove, + values: d3_map_keys, + size: d3_map_size, + empty: d3_map_empty, + forEach: function(f) { + for (var key in this._) f.call(this, d3_map_unescape(key)); + } + }); + d3.behavior = {}; + d3.rebind = function(target, source) { + var i = 1, n = arguments.length, method; + while (++i < n) target[method = arguments[i]] = d3_rebind(target, source, source[method]); + return target; + }; + function d3_rebind(target, source, method) { + return function() { + var value = method.apply(source, arguments); + return value === source ? target : value; + }; + } + function d3_vendorSymbol(object, name) { + if (name in object) return name; + name = name.charAt(0).toUpperCase() + name.slice(1); + for (var i = 0, n = d3_vendorPrefixes.length; i < n; ++i) { + var prefixName = d3_vendorPrefixes[i] + name; + if (prefixName in object) return prefixName; + } + } + var d3_vendorPrefixes = [ "webkit", "ms", "moz", "Moz", "o", "O" ]; + function d3_noop() {} + d3.dispatch = function() { + var dispatch = new d3_dispatch(), i = -1, n = arguments.length; + while (++i < n) dispatch[arguments[i]] = d3_dispatch_event(dispatch); + return dispatch; + }; + function d3_dispatch() {} + d3_dispatch.prototype.on = function(type, listener) { + var i = type.indexOf("."), name = ""; + if (i >= 0) { + name = type.slice(i + 1); + type = type.slice(0, i); + } + if (type) return arguments.length < 2 ? this[type].on(name) : this[type].on(name, listener); + if (arguments.length === 2) { + if (listener == null) for (type in this) { + if (this.hasOwnProperty(type)) this[type].on(name, null); + } + return this; + } + }; + function d3_dispatch_event(dispatch) { + var listeners = [], listenerByName = new d3_Map(); + function event() { + var z = listeners, i = -1, n = z.length, l; + while (++i < n) if (l = z[i].on) l.apply(this, arguments); + return dispatch; + } + event.on = function(name, listener) { + var l = listenerByName.get(name), i; + if (arguments.length < 2) return l && l.on; + if (l) { + l.on = null; + listeners = listeners.slice(0, i = listeners.indexOf(l)).concat(listeners.slice(i + 1)); + listenerByName.remove(name); + } + if (listener) listeners.push(listenerByName.set(name, { + on: listener + })); + return dispatch; + }; + return event; + } + d3.event = null; + function d3_eventPreventDefault() { + d3.event.preventDefault(); + } + function d3_eventSource() { + var e = d3.event, s; + while (s = e.sourceEvent) e = s; + return e; + } + function d3_eventDispatch(target) { + var dispatch = new d3_dispatch(), i = 0, n = arguments.length; + while (++i < n) dispatch[arguments[i]] = d3_dispatch_event(dispatch); + dispatch.of = function(thiz, argumentz) { + return function(e1) { + try { + var e0 = e1.sourceEvent = d3.event; + e1.target = target; + d3.event = e1; + dispatch[e1.type].apply(thiz, argumentz); + } finally { + d3.event = e0; + } + }; + }; + return dispatch; + } + d3.requote = function(s) { + return s.replace(d3_requote_re, "\\$&"); + }; + var d3_requote_re = /[\\\^\$\*\+\?\|\[\]\(\)\.\{\}]/g; + var d3_subclass = {}.__proto__ ? function(object, prototype) { + object.__proto__ = prototype; + } : function(object, prototype) { + for (var property in prototype) object[property] = prototype[property]; + }; + function d3_selection(groups) { + d3_subclass(groups, d3_selectionPrototype); + return groups; + } + var d3_select = function(s, n) { + return n.querySelector(s); + }, d3_selectAll = function(s, n) { + return n.querySelectorAll(s); + }, d3_selectMatcher = d3_documentElement.matches || d3_documentElement[d3_vendorSymbol(d3_documentElement, "matchesSelector")], d3_selectMatches = function(n, s) { + return d3_selectMatcher.call(n, s); + }; + if (typeof Sizzle === "function") { + d3_select = function(s, n) { + return Sizzle(s, n)[0] || null; + }; + d3_selectAll = Sizzle; + d3_selectMatches = Sizzle.matchesSelector; + } + d3.selection = function() { + return d3_selectionRoot; + }; + var d3_selectionPrototype = d3.selection.prototype = []; + d3_selectionPrototype.select = function(selector) { + var subgroups = [], subgroup, subnode, group, node; + selector = d3_selection_selector(selector); + for (var j = -1, m = this.length; ++j < m; ) { + subgroups.push(subgroup = []); + subgroup.parentNode = (group = this[j]).parentNode; + for (var i = -1, n = group.length; ++i < n; ) { + if (node = group[i]) { + subgroup.push(subnode = selector.call(node, node.__data__, i, j)); + if (subnode && "__data__" in node) subnode.__data__ = node.__data__; + } else { + subgroup.push(null); + } + } + } + return d3_selection(subgroups); + }; + function d3_selection_selector(selector) { + return typeof selector === "function" ? selector : function() { + return d3_select(selector, this); + }; + } + d3_selectionPrototype.selectAll = function(selector) { + var subgroups = [], subgroup, node; + selector = d3_selection_selectorAll(selector); + for (var j = -1, m = this.length; ++j < m; ) { + for (var group = this[j], i = -1, n = group.length; ++i < n; ) { + if (node = group[i]) { + subgroups.push(subgroup = d3_array(selector.call(node, node.__data__, i, j))); + subgroup.parentNode = node; + } + } + } + return d3_selection(subgroups); + }; + function d3_selection_selectorAll(selector) { + return typeof selector === "function" ? selector : function() { + return d3_selectAll(selector, this); + }; + } + var d3_nsPrefix = { + svg: "http://www.w3.org/2000/svg", + xhtml: "http://www.w3.org/1999/xhtml", + xlink: "http://www.w3.org/1999/xlink", + xml: "http://www.w3.org/XML/1998/namespace", + xmlns: "http://www.w3.org/2000/xmlns/" + }; + d3.ns = { + prefix: d3_nsPrefix, + qualify: function(name) { + var i = name.indexOf(":"), prefix = name; + if (i >= 0) { + prefix = name.slice(0, i); + name = name.slice(i + 1); + } + return d3_nsPrefix.hasOwnProperty(prefix) ? { + space: d3_nsPrefix[prefix], + local: name + } : name; + } + }; + d3_selectionPrototype.attr = function(name, value) { + if (arguments.length < 2) { + if (typeof name === "string") { + var node = this.node(); + name = d3.ns.qualify(name); + return name.local ? node.getAttributeNS(name.space, name.local) : node.getAttribute(name); + } + for (value in name) this.each(d3_selection_attr(value, name[value])); + return this; + } + return this.each(d3_selection_attr(name, value)); + }; + function d3_selection_attr(name, value) { + name = d3.ns.qualify(name); + function attrNull() { + this.removeAttribute(name); + } + function attrNullNS() { + this.removeAttributeNS(name.space, name.local); + } + function attrConstant() { + this.setAttribute(name, value); + } + function attrConstantNS() { + this.setAttributeNS(name.space, name.local, value); + } + function attrFunction() { + var x = value.apply(this, arguments); + if (x == null) this.removeAttribute(name); else this.setAttribute(name, x); + } + function attrFunctionNS() { + var x = value.apply(this, arguments); + if (x == null) this.removeAttributeNS(name.space, name.local); else this.setAttributeNS(name.space, name.local, x); + } + return value == null ? name.local ? attrNullNS : attrNull : typeof value === "function" ? name.local ? attrFunctionNS : attrFunction : name.local ? attrConstantNS : attrConstant; + } + function d3_collapse(s) { + return s.trim().replace(/\s+/g, " "); + } + d3_selectionPrototype.classed = function(name, value) { + if (arguments.length < 2) { + if (typeof name === "string") { + var node = this.node(), n = (name = d3_selection_classes(name)).length, i = -1; + if (value = node.classList) { + while (++i < n) if (!value.contains(name[i])) return false; + } else { + value = node.getAttribute("class"); + while (++i < n) if (!d3_selection_classedRe(name[i]).test(value)) return false; + } + return true; + } + for (value in name) this.each(d3_selection_classed(value, name[value])); + return this; + } + return this.each(d3_selection_classed(name, value)); + }; + function d3_selection_classedRe(name) { + return new RegExp("(?:^|\\s+)" + d3.requote(name) + "(?:\\s+|$)", "g"); + } + function d3_selection_classes(name) { + return (name + "").trim().split(/^|\s+/); + } + function d3_selection_classed(name, value) { + name = d3_selection_classes(name).map(d3_selection_classedName); + var n = name.length; + function classedConstant() { + var i = -1; + while (++i < n) name[i](this, value); + } + function classedFunction() { + var i = -1, x = value.apply(this, arguments); + while (++i < n) name[i](this, x); + } + return typeof value === "function" ? classedFunction : classedConstant; + } + function d3_selection_classedName(name) { + var re = d3_selection_classedRe(name); + return function(node, value) { + if (c = node.classList) return value ? c.add(name) : c.remove(name); + var c = node.getAttribute("class") || ""; + if (value) { + re.lastIndex = 0; + if (!re.test(c)) node.setAttribute("class", d3_collapse(c + " " + name)); + } else { + node.setAttribute("class", d3_collapse(c.replace(re, " "))); + } + }; + } + d3_selectionPrototype.style = function(name, value, priority) { + var n = arguments.length; + if (n < 3) { + if (typeof name !== "string") { + if (n < 2) value = ""; + for (priority in name) this.each(d3_selection_style(priority, name[priority], value)); + return this; + } + if (n < 2) return d3_window.getComputedStyle(this.node(), null).getPropertyValue(name); + priority = ""; + } + return this.each(d3_selection_style(name, value, priority)); + }; + function d3_selection_style(name, value, priority) { + function styleNull() { + this.style.removeProperty(name); + } + function styleConstant() { + this.style.setProperty(name, value, priority); + } + function styleFunction() { + var x = value.apply(this, arguments); + if (x == null) this.style.removeProperty(name); else this.style.setProperty(name, x, priority); + } + return value == null ? styleNull : typeof value === "function" ? styleFunction : styleConstant; + } + d3_selectionPrototype.property = function(name, value) { + if (arguments.length < 2) { + if (typeof name === "string") return this.node()[name]; + for (value in name) this.each(d3_selection_property(value, name[value])); + return this; + } + return this.each(d3_selection_property(name, value)); + }; + function d3_selection_property(name, value) { + function propertyNull() { + delete this[name]; + } + function propertyConstant() { + this[name] = value; + } + function propertyFunction() { + var x = value.apply(this, arguments); + if (x == null) delete this[name]; else this[name] = x; + } + return value == null ? propertyNull : typeof value === "function" ? propertyFunction : propertyConstant; + } + d3_selectionPrototype.text = function(value) { + return arguments.length ? this.each(typeof value === "function" ? function() { + var v = value.apply(this, arguments); + this.textContent = v == null ? "" : v; + } : value == null ? function() { + this.textContent = ""; + } : function() { + this.textContent = value; + }) : this.node().textContent; + }; + d3_selectionPrototype.html = function(value) { + return arguments.length ? this.each(typeof value === "function" ? function() { + var v = value.apply(this, arguments); + this.innerHTML = v == null ? "" : v; + } : value == null ? function() { + this.innerHTML = ""; + } : function() { + this.innerHTML = value; + }) : this.node().innerHTML; + }; + d3_selectionPrototype.append = function(name) { + name = d3_selection_creator(name); + return this.select(function() { + return this.appendChild(name.apply(this, arguments)); + }); + }; + function d3_selection_creator(name) { + return typeof name === "function" ? name : (name = d3.ns.qualify(name)).local ? function() { + return this.ownerDocument.createElementNS(name.space, name.local); + } : function() { + return this.ownerDocument.createElementNS(this.namespaceURI, name); + }; + } + d3_selectionPrototype.insert = function(name, before) { + name = d3_selection_creator(name); + before = d3_selection_selector(before); + return this.select(function() { + return this.insertBefore(name.apply(this, arguments), before.apply(this, arguments) || null); + }); + }; + d3_selectionPrototype.remove = function() { + return this.each(d3_selectionRemove); + }; + function d3_selectionRemove() { + var parent = this.parentNode; + if (parent) parent.removeChild(this); + } + d3_selectionPrototype.data = function(value, key) { + var i = -1, n = this.length, group, node; + if (!arguments.length) { + value = new Array(n = (group = this[0]).length); + while (++i < n) { + if (node = group[i]) { + value[i] = node.__data__; + } + } + return value; + } + function bind(group, groupData) { + var i, n = group.length, m = groupData.length, n0 = Math.min(n, m), updateNodes = new Array(m), enterNodes = new Array(m), exitNodes = new Array(n), node, nodeData; + if (key) { + var nodeByKeyValue = new d3_Map(), keyValues = new Array(n), keyValue; + for (i = -1; ++i < n; ) { + if (nodeByKeyValue.has(keyValue = key.call(node = group[i], node.__data__, i))) { + exitNodes[i] = node; + } else { + nodeByKeyValue.set(keyValue, node); + } + keyValues[i] = keyValue; + } + for (i = -1; ++i < m; ) { + if (!(node = nodeByKeyValue.get(keyValue = key.call(groupData, nodeData = groupData[i], i)))) { + enterNodes[i] = d3_selection_dataNode(nodeData); + } else if (node !== true) { + updateNodes[i] = node; + node.__data__ = nodeData; + } + nodeByKeyValue.set(keyValue, true); + } + for (i = -1; ++i < n; ) { + if (nodeByKeyValue.get(keyValues[i]) !== true) { + exitNodes[i] = group[i]; + } + } + } else { + for (i = -1; ++i < n0; ) { + node = group[i]; + nodeData = groupData[i]; + if (node) { + node.__data__ = nodeData; + updateNodes[i] = node; + } else { + enterNodes[i] = d3_selection_dataNode(nodeData); + } + } + for (;i < m; ++i) { + enterNodes[i] = d3_selection_dataNode(groupData[i]); + } + for (;i < n; ++i) { + exitNodes[i] = group[i]; + } + } + enterNodes.update = updateNodes; + enterNodes.parentNode = updateNodes.parentNode = exitNodes.parentNode = group.parentNode; + enter.push(enterNodes); + update.push(updateNodes); + exit.push(exitNodes); + } + var enter = d3_selection_enter([]), update = d3_selection([]), exit = d3_selection([]); + if (typeof value === "function") { + while (++i < n) { + bind(group = this[i], value.call(group, group.parentNode.__data__, i)); + } + } else { + while (++i < n) { + bind(group = this[i], value); + } + } + update.enter = function() { + return enter; + }; + update.exit = function() { + return exit; + }; + return update; + }; + function d3_selection_dataNode(data) { + return { + __data__: data + }; + } + d3_selectionPrototype.datum = function(value) { + return arguments.length ? this.property("__data__", value) : this.property("__data__"); + }; + d3_selectionPrototype.filter = function(filter) { + var subgroups = [], subgroup, group, node; + if (typeof filter !== "function") filter = d3_selection_filter(filter); + for (var j = 0, m = this.length; j < m; j++) { + subgroups.push(subgroup = []); + subgroup.parentNode = (group = this[j]).parentNode; + for (var i = 0, n = group.length; i < n; i++) { + if ((node = group[i]) && filter.call(node, node.__data__, i, j)) { + subgroup.push(node); + } + } + } + return d3_selection(subgroups); + }; + function d3_selection_filter(selector) { + return function() { + return d3_selectMatches(this, selector); + }; + } + d3_selectionPrototype.order = function() { + for (var j = -1, m = this.length; ++j < m; ) { + for (var group = this[j], i = group.length - 1, next = group[i], node; --i >= 0; ) { + if (node = group[i]) { + if (next && next !== node.nextSibling) next.parentNode.insertBefore(node, next); + next = node; + } + } + } + return this; + }; + d3_selectionPrototype.sort = function(comparator) { + comparator = d3_selection_sortComparator.apply(this, arguments); + for (var j = -1, m = this.length; ++j < m; ) this[j].sort(comparator); + return this.order(); + }; + function d3_selection_sortComparator(comparator) { + if (!arguments.length) comparator = d3_ascending; + return function(a, b) { + return a && b ? comparator(a.__data__, b.__data__) : !a - !b; + }; + } + d3_selectionPrototype.each = function(callback) { + return d3_selection_each(this, function(node, i, j) { + callback.call(node, node.__data__, i, j); + }); + }; + function d3_selection_each(groups, callback) { + for (var j = 0, m = groups.length; j < m; j++) { + for (var group = groups[j], i = 0, n = group.length, node; i < n; i++) { + if (node = group[i]) callback(node, i, j); + } + } + return groups; + } + d3_selectionPrototype.call = function(callback) { + var args = d3_array(arguments); + callback.apply(args[0] = this, args); + return this; + }; + d3_selectionPrototype.empty = function() { + return !this.node(); + }; + d3_selectionPrototype.node = function() { + for (var j = 0, m = this.length; j < m; j++) { + for (var group = this[j], i = 0, n = group.length; i < n; i++) { + var node = group[i]; + if (node) return node; + } + } + return null; + }; + d3_selectionPrototype.size = function() { + var n = 0; + d3_selection_each(this, function() { + ++n; + }); + return n; + }; + function d3_selection_enter(selection) { + d3_subclass(selection, d3_selection_enterPrototype); + return selection; + } + var d3_selection_enterPrototype = []; + d3.selection.enter = d3_selection_enter; + d3.selection.enter.prototype = d3_selection_enterPrototype; + d3_selection_enterPrototype.append = d3_selectionPrototype.append; + d3_selection_enterPrototype.empty = d3_selectionPrototype.empty; + d3_selection_enterPrototype.node = d3_selectionPrototype.node; + d3_selection_enterPrototype.call = d3_selectionPrototype.call; + d3_selection_enterPrototype.size = d3_selectionPrototype.size; + d3_selection_enterPrototype.select = function(selector) { + var subgroups = [], subgroup, subnode, upgroup, group, node; + for (var j = -1, m = this.length; ++j < m; ) { + upgroup = (group = this[j]).update; + subgroups.push(subgroup = []); + subgroup.parentNode = group.parentNode; + for (var i = -1, n = group.length; ++i < n; ) { + if (node = group[i]) { + subgroup.push(upgroup[i] = subnode = selector.call(group.parentNode, node.__data__, i, j)); + subnode.__data__ = node.__data__; + } else { + subgroup.push(null); + } + } + } + return d3_selection(subgroups); + }; + d3_selection_enterPrototype.insert = function(name, before) { + if (arguments.length < 2) before = d3_selection_enterInsertBefore(this); + return d3_selectionPrototype.insert.call(this, name, before); + }; + function d3_selection_enterInsertBefore(enter) { + var i0, j0; + return function(d, i, j) { + var group = enter[j].update, n = group.length, node; + if (j != j0) j0 = j, i0 = 0; + if (i >= i0) i0 = i + 1; + while (!(node = group[i0]) && ++i0 < n) ; + return node; + }; + } + d3.select = function(node) { + var group = [ typeof node === "string" ? d3_select(node, d3_document) : node ]; + group.parentNode = d3_documentElement; + return d3_selection([ group ]); + }; + d3.selectAll = function(nodes) { + var group = d3_array(typeof nodes === "string" ? d3_selectAll(nodes, d3_document) : nodes); + group.parentNode = d3_documentElement; + return d3_selection([ group ]); + }; + var d3_selectionRoot = d3.select(d3_documentElement); + d3_selectionPrototype.on = function(type, listener, capture) { + var n = arguments.length; + if (n < 3) { + if (typeof type !== "string") { + if (n < 2) listener = false; + for (capture in type) this.each(d3_selection_on(capture, type[capture], listener)); + return this; + } + if (n < 2) return (n = this.node()["__on" + type]) && n._; + capture = false; + } + return this.each(d3_selection_on(type, listener, capture)); + }; + function d3_selection_on(type, listener, capture) { + var name = "__on" + type, i = type.indexOf("."), wrap = d3_selection_onListener; + if (i > 0) type = type.slice(0, i); + var filter = d3_selection_onFilters.get(type); + if (filter) type = filter, wrap = d3_selection_onFilter; + function onRemove() { + var l = this[name]; + if (l) { + this.removeEventListener(type, l, l.$); + delete this[name]; + } + } + function onAdd() { + var l = wrap(listener, d3_array(arguments)); + onRemove.call(this); + this.addEventListener(type, this[name] = l, l.$ = capture); + l._ = listener; + } + function removeAll() { + var re = new RegExp("^__on([^.]+)" + d3.requote(type) + "$"), match; + for (var name in this) { + if (match = name.match(re)) { + var l = this[name]; + this.removeEventListener(match[1], l, l.$); + delete this[name]; + } + } + } + return i ? listener ? onAdd : onRemove : listener ? d3_noop : removeAll; + } + var d3_selection_onFilters = d3.map({ + mouseenter: "mouseover", + mouseleave: "mouseout" + }); + d3_selection_onFilters.forEach(function(k) { + if ("on" + k in d3_document) d3_selection_onFilters.remove(k); + }); + function d3_selection_onListener(listener, argumentz) { + return function(e) { + var o = d3.event; + d3.event = e; + argumentz[0] = this.__data__; + try { + listener.apply(this, argumentz); + } finally { + d3.event = o; + } + }; + } + function d3_selection_onFilter(listener, argumentz) { + var l = d3_selection_onListener(listener, argumentz); + return function(e) { + var target = this, related = e.relatedTarget; + if (!related || related !== target && !(related.compareDocumentPosition(target) & 8)) { + l.call(target, e); + } + }; + } + var d3_event_dragSelect = "onselectstart" in d3_document ? null : d3_vendorSymbol(d3_documentElement.style, "userSelect"), d3_event_dragId = 0; + function d3_event_dragSuppress() { + var name = ".dragsuppress-" + ++d3_event_dragId, click = "click" + name, w = d3.select(d3_window).on("touchmove" + name, d3_eventPreventDefault).on("dragstart" + name, d3_eventPreventDefault).on("selectstart" + name, d3_eventPreventDefault); + if (d3_event_dragSelect) { + var style = d3_documentElement.style, select = style[d3_event_dragSelect]; + style[d3_event_dragSelect] = "none"; + } + return function(suppressClick) { + w.on(name, null); + if (d3_event_dragSelect) style[d3_event_dragSelect] = select; + if (suppressClick) { + var off = function() { + w.on(click, null); + }; + w.on(click, function() { + d3_eventPreventDefault(); + off(); + }, true); + setTimeout(off, 0); + } + }; + } + d3.mouse = function(container) { + return d3_mousePoint(container, d3_eventSource()); + }; + var d3_mouse_bug44083 = /WebKit/.test(d3_window.navigator.userAgent) ? -1 : 0; + function d3_mousePoint(container, e) { + if (e.changedTouches) e = e.changedTouches[0]; + var svg = container.ownerSVGElement || container; + if (svg.createSVGPoint) { + var point = svg.createSVGPoint(); + if (d3_mouse_bug44083 < 0 && (d3_window.scrollX || d3_window.scrollY)) { + svg = d3.select("body").append("svg").style({ + position: "absolute", + top: 0, + left: 0, + margin: 0, + padding: 0, + border: "none" + }, "important"); + var ctm = svg[0][0].getScreenCTM(); + d3_mouse_bug44083 = !(ctm.f || ctm.e); + svg.remove(); + } + if (d3_mouse_bug44083) point.x = e.pageX, point.y = e.pageY; else point.x = e.clientX, + point.y = e.clientY; + point = point.matrixTransform(container.getScreenCTM().inverse()); + return [ point.x, point.y ]; + } + var rect = container.getBoundingClientRect(); + return [ e.clientX - rect.left - container.clientLeft, e.clientY - rect.top - container.clientTop ]; + } + d3.touch = function(container, touches, identifier) { + if (arguments.length < 3) identifier = touches, touches = d3_eventSource().changedTouches; + if (touches) for (var i = 0, n = touches.length, touch; i < n; ++i) { + if ((touch = touches[i]).identifier === identifier) { + return d3_mousePoint(container, touch); + } + } + }; + d3.behavior.drag = function() { + var event = d3_eventDispatch(drag, "drag", "dragstart", "dragend"), origin = null, mousedown = dragstart(d3_noop, d3.mouse, d3_behavior_dragMouseSubject, "mousemove", "mouseup"), touchstart = dragstart(d3_behavior_dragTouchId, d3.touch, d3_behavior_dragTouchSubject, "touchmove", "touchend"); + function drag() { + this.on("mousedown.drag", mousedown).on("touchstart.drag", touchstart); + } + function dragstart(id, position, subject, move, end) { + return function() { + var that = this, target = d3.event.target, parent = that.parentNode, dispatch = event.of(that, arguments), dragged = 0, dragId = id(), dragName = ".drag" + (dragId == null ? "" : "-" + dragId), dragOffset, dragSubject = d3.select(subject()).on(move + dragName, moved).on(end + dragName, ended), dragRestore = d3_event_dragSuppress(), position0 = position(parent, dragId); + if (origin) { + dragOffset = origin.apply(that, arguments); + dragOffset = [ dragOffset.x - position0[0], dragOffset.y - position0[1] ]; + } else { + dragOffset = [ 0, 0 ]; + } + dispatch({ + type: "dragstart" + }); + function moved() { + var position1 = position(parent, dragId), dx, dy; + if (!position1) return; + dx = position1[0] - position0[0]; + dy = position1[1] - position0[1]; + dragged |= dx | dy; + position0 = position1; + dispatch({ + type: "drag", + x: position1[0] + dragOffset[0], + y: position1[1] + dragOffset[1], + dx: dx, + dy: dy + }); + } + function ended() { + if (!position(parent, dragId)) return; + dragSubject.on(move + dragName, null).on(end + dragName, null); + dragRestore(dragged && d3.event.target === target); + dispatch({ + type: "dragend" + }); + } + }; + } + drag.origin = function(x) { + if (!arguments.length) return origin; + origin = x; + return drag; + }; + return d3.rebind(drag, event, "on"); + }; + function d3_behavior_dragTouchId() { + return d3.event.changedTouches[0].identifier; + } + function d3_behavior_dragTouchSubject() { + return d3.event.target; + } + function d3_behavior_dragMouseSubject() { + return d3_window; + } + d3.touches = function(container, touches) { + if (arguments.length < 2) touches = d3_eventSource().touches; + return touches ? d3_array(touches).map(function(touch) { + var point = d3_mousePoint(container, touch); + point.identifier = touch.identifier; + return point; + }) : []; + }; + var ε = 1e-6, ε2 = ε * ε, π = Math.PI, τ = 2 * π, τε = τ - ε, halfπ = π / 2, d3_radians = π / 180, d3_degrees = 180 / π; + function d3_sgn(x) { + return x > 0 ? 1 : x < 0 ? -1 : 0; + } + function d3_cross2d(a, b, c) { + return (b[0] - a[0]) * (c[1] - a[1]) - (b[1] - a[1]) * (c[0] - a[0]); + } + function d3_acos(x) { + return x > 1 ? 0 : x < -1 ? π : Math.acos(x); + } + function d3_asin(x) { + return x > 1 ? halfπ : x < -1 ? -halfπ : Math.asin(x); + } + function d3_sinh(x) { + return ((x = Math.exp(x)) - 1 / x) / 2; + } + function d3_cosh(x) { + return ((x = Math.exp(x)) + 1 / x) / 2; + } + function d3_tanh(x) { + return ((x = Math.exp(2 * x)) - 1) / (x + 1); + } + function d3_haversin(x) { + return (x = Math.sin(x / 2)) * x; + } + var ρ = Math.SQRT2, ρ2 = 2, ρ4 = 4; + d3.interpolateZoom = function(p0, p1) { + var ux0 = p0[0], uy0 = p0[1], w0 = p0[2], ux1 = p1[0], uy1 = p1[1], w1 = p1[2]; + var dx = ux1 - ux0, dy = uy1 - uy0, d2 = dx * dx + dy * dy, d1 = Math.sqrt(d2), b0 = (w1 * w1 - w0 * w0 + ρ4 * d2) / (2 * w0 * ρ2 * d1), b1 = (w1 * w1 - w0 * w0 - ρ4 * d2) / (2 * w1 * ρ2 * d1), r0 = Math.log(Math.sqrt(b0 * b0 + 1) - b0), r1 = Math.log(Math.sqrt(b1 * b1 + 1) - b1), dr = r1 - r0, S = (dr || Math.log(w1 / w0)) / ρ; + function interpolate(t) { + var s = t * S; + if (dr) { + var coshr0 = d3_cosh(r0), u = w0 / (ρ2 * d1) * (coshr0 * d3_tanh(ρ * s + r0) - d3_sinh(r0)); + return [ ux0 + u * dx, uy0 + u * dy, w0 * coshr0 / d3_cosh(ρ * s + r0) ]; + } + return [ ux0 + t * dx, uy0 + t * dy, w0 * Math.exp(ρ * s) ]; + } + interpolate.duration = S * 1e3; + return interpolate; + }; + d3.behavior.zoom = function() { + var view = { + x: 0, + y: 0, + k: 1 + }, translate0, center0, center, size = [ 960, 500 ], scaleExtent = d3_behavior_zoomInfinity, duration = 250, zooming = 0, mousedown = "mousedown.zoom", mousemove = "mousemove.zoom", mouseup = "mouseup.zoom", mousewheelTimer, touchstart = "touchstart.zoom", touchtime, event = d3_eventDispatch(zoom, "zoomstart", "zoom", "zoomend"), x0, x1, y0, y1; + function zoom(g) { + g.on(mousedown, mousedowned).on(d3_behavior_zoomWheel + ".zoom", mousewheeled).on("dblclick.zoom", dblclicked).on(touchstart, touchstarted); + } + zoom.event = function(g) { + g.each(function() { + var dispatch = event.of(this, arguments), view1 = view; + if (d3_transitionInheritId) { + d3.select(this).transition().each("start.zoom", function() { + view = this.__chart__ || { + x: 0, + y: 0, + k: 1 + }; + zoomstarted(dispatch); + }).tween("zoom:zoom", function() { + var dx = size[0], dy = size[1], cx = center0 ? center0[0] : dx / 2, cy = center0 ? center0[1] : dy / 2, i = d3.interpolateZoom([ (cx - view.x) / view.k, (cy - view.y) / view.k, dx / view.k ], [ (cx - view1.x) / view1.k, (cy - view1.y) / view1.k, dx / view1.k ]); + return function(t) { + var l = i(t), k = dx / l[2]; + this.__chart__ = view = { + x: cx - l[0] * k, + y: cy - l[1] * k, + k: k + }; + zoomed(dispatch); + }; + }).each("interrupt.zoom", function() { + zoomended(dispatch); + }).each("end.zoom", function() { + zoomended(dispatch); + }); + } else { + this.__chart__ = view; + zoomstarted(dispatch); + zoomed(dispatch); + zoomended(dispatch); + } + }); + }; + zoom.translate = function(_) { + if (!arguments.length) return [ view.x, view.y ]; + view = { + x: +_[0], + y: +_[1], + k: view.k + }; + rescale(); + return zoom; + }; + zoom.scale = function(_) { + if (!arguments.length) return view.k; + view = { + x: view.x, + y: view.y, + k: +_ + }; + rescale(); + return zoom; + }; + zoom.scaleExtent = function(_) { + if (!arguments.length) return scaleExtent; + scaleExtent = _ == null ? d3_behavior_zoomInfinity : [ +_[0], +_[1] ]; + return zoom; + }; + zoom.center = function(_) { + if (!arguments.length) return center; + center = _ && [ +_[0], +_[1] ]; + return zoom; + }; + zoom.size = function(_) { + if (!arguments.length) return size; + size = _ && [ +_[0], +_[1] ]; + return zoom; + }; + zoom.duration = function(_) { + if (!arguments.length) return duration; + duration = +_; + return zoom; + }; + zoom.x = function(z) { + if (!arguments.length) return x1; + x1 = z; + x0 = z.copy(); + view = { + x: 0, + y: 0, + k: 1 + }; + return zoom; + }; + zoom.y = function(z) { + if (!arguments.length) return y1; + y1 = z; + y0 = z.copy(); + view = { + x: 0, + y: 0, + k: 1 + }; + return zoom; + }; + function location(p) { + return [ (p[0] - view.x) / view.k, (p[1] - view.y) / view.k ]; + } + function point(l) { + return [ l[0] * view.k + view.x, l[1] * view.k + view.y ]; + } + function scaleTo(s) { + view.k = Math.max(scaleExtent[0], Math.min(scaleExtent[1], s)); + } + function translateTo(p, l) { + l = point(l); + view.x += p[0] - l[0]; + view.y += p[1] - l[1]; + } + function zoomTo(that, p, l, k) { + that.__chart__ = { + x: view.x, + y: view.y, + k: view.k + }; + scaleTo(Math.pow(2, k)); + translateTo(center0 = p, l); + that = d3.select(that); + if (duration > 0) that = that.transition().duration(duration); + that.call(zoom.event); + } + function rescale() { + if (x1) x1.domain(x0.range().map(function(x) { + return (x - view.x) / view.k; + }).map(x0.invert)); + if (y1) y1.domain(y0.range().map(function(y) { + return (y - view.y) / view.k; + }).map(y0.invert)); + } + function zoomstarted(dispatch) { + if (!zooming++) dispatch({ + type: "zoomstart" + }); + } + function zoomed(dispatch) { + rescale(); + dispatch({ + type: "zoom", + scale: view.k, + translate: [ view.x, view.y ] + }); + } + function zoomended(dispatch) { + if (!--zooming) dispatch({ + type: "zoomend" + }); + center0 = null; + } + function mousedowned() { + var that = this, target = d3.event.target, dispatch = event.of(that, arguments), dragged = 0, subject = d3.select(d3_window).on(mousemove, moved).on(mouseup, ended), location0 = location(d3.mouse(that)), dragRestore = d3_event_dragSuppress(); + d3_selection_interrupt.call(that); + zoomstarted(dispatch); + function moved() { + dragged = 1; + translateTo(d3.mouse(that), location0); + zoomed(dispatch); + } + function ended() { + subject.on(mousemove, null).on(mouseup, null); + dragRestore(dragged && d3.event.target === target); + zoomended(dispatch); + } + } + function touchstarted() { + var that = this, dispatch = event.of(that, arguments), locations0 = {}, distance0 = 0, scale0, zoomName = ".zoom-" + d3.event.changedTouches[0].identifier, touchmove = "touchmove" + zoomName, touchend = "touchend" + zoomName, targets = [], subject = d3.select(that), dragRestore = d3_event_dragSuppress(); + started(); + zoomstarted(dispatch); + subject.on(mousedown, null).on(touchstart, started); + function relocate() { + var touches = d3.touches(that); + scale0 = view.k; + touches.forEach(function(t) { + if (t.identifier in locations0) locations0[t.identifier] = location(t); + }); + return touches; + } + function started() { + var target = d3.event.target; + d3.select(target).on(touchmove, moved).on(touchend, ended); + targets.push(target); + var changed = d3.event.changedTouches; + for (var i = 0, n = changed.length; i < n; ++i) { + locations0[changed[i].identifier] = null; + } + var touches = relocate(), now = Date.now(); + if (touches.length === 1) { + if (now - touchtime < 500) { + var p = touches[0]; + zoomTo(that, p, locations0[p.identifier], Math.floor(Math.log(view.k) / Math.LN2) + 1); + d3_eventPreventDefault(); + } + touchtime = now; + } else if (touches.length > 1) { + var p = touches[0], q = touches[1], dx = p[0] - q[0], dy = p[1] - q[1]; + distance0 = dx * dx + dy * dy; + } + } + function moved() { + var touches = d3.touches(that), p0, l0, p1, l1; + d3_selection_interrupt.call(that); + for (var i = 0, n = touches.length; i < n; ++i, l1 = null) { + p1 = touches[i]; + if (l1 = locations0[p1.identifier]) { + if (l0) break; + p0 = p1, l0 = l1; + } + } + if (l1) { + var distance1 = (distance1 = p1[0] - p0[0]) * distance1 + (distance1 = p1[1] - p0[1]) * distance1, scale1 = distance0 && Math.sqrt(distance1 / distance0); + p0 = [ (p0[0] + p1[0]) / 2, (p0[1] + p1[1]) / 2 ]; + l0 = [ (l0[0] + l1[0]) / 2, (l0[1] + l1[1]) / 2 ]; + scaleTo(scale1 * scale0); + } + touchtime = null; + translateTo(p0, l0); + zoomed(dispatch); + } + function ended() { + if (d3.event.touches.length) { + var changed = d3.event.changedTouches; + for (var i = 0, n = changed.length; i < n; ++i) { + delete locations0[changed[i].identifier]; + } + for (var identifier in locations0) { + return void relocate(); + } + } + d3.selectAll(targets).on(zoomName, null); + subject.on(mousedown, mousedowned).on(touchstart, touchstarted); + dragRestore(); + zoomended(dispatch); + } + } + function mousewheeled() { + var dispatch = event.of(this, arguments); + if (mousewheelTimer) clearTimeout(mousewheelTimer); else translate0 = location(center0 = center || d3.mouse(this)), + d3_selection_interrupt.call(this), zoomstarted(dispatch); + mousewheelTimer = setTimeout(function() { + mousewheelTimer = null; + zoomended(dispatch); + }, 50); + d3_eventPreventDefault(); + scaleTo(Math.pow(2, d3_behavior_zoomDelta() * .002) * view.k); + translateTo(center0, translate0); + zoomed(dispatch); + } + function dblclicked() { + var p = d3.mouse(this), k = Math.log(view.k) / Math.LN2; + zoomTo(this, p, location(p), d3.event.shiftKey ? Math.ceil(k) - 1 : Math.floor(k) + 1); + } + return d3.rebind(zoom, event, "on"); + }; + var d3_behavior_zoomInfinity = [ 0, Infinity ]; + var d3_behavior_zoomDelta, d3_behavior_zoomWheel = "onwheel" in d3_document ? (d3_behavior_zoomDelta = function() { + return -d3.event.deltaY * (d3.event.deltaMode ? 120 : 1); + }, "wheel") : "onmousewheel" in d3_document ? (d3_behavior_zoomDelta = function() { + return d3.event.wheelDelta; + }, "mousewheel") : (d3_behavior_zoomDelta = function() { + return -d3.event.detail; + }, "MozMousePixelScroll"); + d3.color = d3_color; + function d3_color() {} + d3_color.prototype.toString = function() { + return this.rgb() + ""; + }; + d3.hsl = d3_hsl; + function d3_hsl(h, s, l) { + return this instanceof d3_hsl ? void (this.h = +h, this.s = +s, this.l = +l) : arguments.length < 2 ? h instanceof d3_hsl ? new d3_hsl(h.h, h.s, h.l) : d3_rgb_parse("" + h, d3_rgb_hsl, d3_hsl) : new d3_hsl(h, s, l); + } + var d3_hslPrototype = d3_hsl.prototype = new d3_color(); + d3_hslPrototype.brighter = function(k) { + k = Math.pow(.7, arguments.length ? k : 1); + return new d3_hsl(this.h, this.s, this.l / k); + }; + d3_hslPrototype.darker = function(k) { + k = Math.pow(.7, arguments.length ? k : 1); + return new d3_hsl(this.h, this.s, k * this.l); + }; + d3_hslPrototype.rgb = function() { + return d3_hsl_rgb(this.h, this.s, this.l); + }; + function d3_hsl_rgb(h, s, l) { + var m1, m2; + h = isNaN(h) ? 0 : (h %= 360) < 0 ? h + 360 : h; + s = isNaN(s) ? 0 : s < 0 ? 0 : s > 1 ? 1 : s; + l = l < 0 ? 0 : l > 1 ? 1 : l; + m2 = l <= .5 ? l * (1 + s) : l + s - l * s; + m1 = 2 * l - m2; + function v(h) { + if (h > 360) h -= 360; else if (h < 0) h += 360; + if (h < 60) return m1 + (m2 - m1) * h / 60; + if (h < 180) return m2; + if (h < 240) return m1 + (m2 - m1) * (240 - h) / 60; + return m1; + } + function vv(h) { + return Math.round(v(h) * 255); + } + return new d3_rgb(vv(h + 120), vv(h), vv(h - 120)); + } + d3.hcl = d3_hcl; + function d3_hcl(h, c, l) { + return this instanceof d3_hcl ? void (this.h = +h, this.c = +c, this.l = +l) : arguments.length < 2 ? h instanceof d3_hcl ? new d3_hcl(h.h, h.c, h.l) : h instanceof d3_lab ? d3_lab_hcl(h.l, h.a, h.b) : d3_lab_hcl((h = d3_rgb_lab((h = d3.rgb(h)).r, h.g, h.b)).l, h.a, h.b) : new d3_hcl(h, c, l); + } + var d3_hclPrototype = d3_hcl.prototype = new d3_color(); + d3_hclPrototype.brighter = function(k) { + return new d3_hcl(this.h, this.c, Math.min(100, this.l + d3_lab_K * (arguments.length ? k : 1))); + }; + d3_hclPrototype.darker = function(k) { + return new d3_hcl(this.h, this.c, Math.max(0, this.l - d3_lab_K * (arguments.length ? k : 1))); + }; + d3_hclPrototype.rgb = function() { + return d3_hcl_lab(this.h, this.c, this.l).rgb(); + }; + function d3_hcl_lab(h, c, l) { + if (isNaN(h)) h = 0; + if (isNaN(c)) c = 0; + return new d3_lab(l, Math.cos(h *= d3_radians) * c, Math.sin(h) * c); + } + d3.lab = d3_lab; + function d3_lab(l, a, b) { + return this instanceof d3_lab ? void (this.l = +l, this.a = +a, this.b = +b) : arguments.length < 2 ? l instanceof d3_lab ? new d3_lab(l.l, l.a, l.b) : l instanceof d3_hcl ? d3_hcl_lab(l.h, l.c, l.l) : d3_rgb_lab((l = d3_rgb(l)).r, l.g, l.b) : new d3_lab(l, a, b); + } + var d3_lab_K = 18; + var d3_lab_X = .95047, d3_lab_Y = 1, d3_lab_Z = 1.08883; + var d3_labPrototype = d3_lab.prototype = new d3_color(); + d3_labPrototype.brighter = function(k) { + return new d3_lab(Math.min(100, this.l + d3_lab_K * (arguments.length ? k : 1)), this.a, this.b); + }; + d3_labPrototype.darker = function(k) { + return new d3_lab(Math.max(0, this.l - d3_lab_K * (arguments.length ? k : 1)), this.a, this.b); + }; + d3_labPrototype.rgb = function() { + return d3_lab_rgb(this.l, this.a, this.b); + }; + function d3_lab_rgb(l, a, b) { + var y = (l + 16) / 116, x = y + a / 500, z = y - b / 200; + x = d3_lab_xyz(x) * d3_lab_X; + y = d3_lab_xyz(y) * d3_lab_Y; + z = d3_lab_xyz(z) * d3_lab_Z; + return new d3_rgb(d3_xyz_rgb(3.2404542 * x - 1.5371385 * y - .4985314 * z), d3_xyz_rgb(-.969266 * x + 1.8760108 * y + .041556 * z), d3_xyz_rgb(.0556434 * x - .2040259 * y + 1.0572252 * z)); + } + function d3_lab_hcl(l, a, b) { + return l > 0 ? new d3_hcl(Math.atan2(b, a) * d3_degrees, Math.sqrt(a * a + b * b), l) : new d3_hcl(NaN, NaN, l); + } + function d3_lab_xyz(x) { + return x > .206893034 ? x * x * x : (x - 4 / 29) / 7.787037; + } + function d3_xyz_lab(x) { + return x > .008856 ? Math.pow(x, 1 / 3) : 7.787037 * x + 4 / 29; + } + function d3_xyz_rgb(r) { + return Math.round(255 * (r <= .00304 ? 12.92 * r : 1.055 * Math.pow(r, 1 / 2.4) - .055)); + } + d3.rgb = d3_rgb; + function d3_rgb(r, g, b) { + return this instanceof d3_rgb ? void (this.r = ~~r, this.g = ~~g, this.b = ~~b) : arguments.length < 2 ? r instanceof d3_rgb ? new d3_rgb(r.r, r.g, r.b) : d3_rgb_parse("" + r, d3_rgb, d3_hsl_rgb) : new d3_rgb(r, g, b); + } + function d3_rgbNumber(value) { + return new d3_rgb(value >> 16, value >> 8 & 255, value & 255); + } + function d3_rgbString(value) { + return d3_rgbNumber(value) + ""; + } + var d3_rgbPrototype = d3_rgb.prototype = new d3_color(); + d3_rgbPrototype.brighter = function(k) { + k = Math.pow(.7, arguments.length ? k : 1); + var r = this.r, g = this.g, b = this.b, i = 30; + if (!r && !g && !b) return new d3_rgb(i, i, i); + if (r && r < i) r = i; + if (g && g < i) g = i; + if (b && b < i) b = i; + return new d3_rgb(Math.min(255, r / k), Math.min(255, g / k), Math.min(255, b / k)); + }; + d3_rgbPrototype.darker = function(k) { + k = Math.pow(.7, arguments.length ? k : 1); + return new d3_rgb(k * this.r, k * this.g, k * this.b); + }; + d3_rgbPrototype.hsl = function() { + return d3_rgb_hsl(this.r, this.g, this.b); + }; + d3_rgbPrototype.toString = function() { + return "#" + d3_rgb_hex(this.r) + d3_rgb_hex(this.g) + d3_rgb_hex(this.b); + }; + function d3_rgb_hex(v) { + return v < 16 ? "0" + Math.max(0, v).toString(16) : Math.min(255, v).toString(16); + } + function d3_rgb_parse(format, rgb, hsl) { + var r = 0, g = 0, b = 0, m1, m2, color; + m1 = /([a-z]+)\((.*)\)/i.exec(format); + if (m1) { + m2 = m1[2].split(","); + switch (m1[1]) { + case "hsl": + { + return hsl(parseFloat(m2[0]), parseFloat(m2[1]) / 100, parseFloat(m2[2]) / 100); + } + + case "rgb": + { + return rgb(d3_rgb_parseNumber(m2[0]), d3_rgb_parseNumber(m2[1]), d3_rgb_parseNumber(m2[2])); + } + } + } + if (color = d3_rgb_names.get(format)) return rgb(color.r, color.g, color.b); + if (format != null && format.charAt(0) === "#" && !isNaN(color = parseInt(format.slice(1), 16))) { + if (format.length === 4) { + r = (color & 3840) >> 4; + r = r >> 4 | r; + g = color & 240; + g = g >> 4 | g; + b = color & 15; + b = b << 4 | b; + } else if (format.length === 7) { + r = (color & 16711680) >> 16; + g = (color & 65280) >> 8; + b = color & 255; + } + } + return rgb(r, g, b); + } + function d3_rgb_hsl(r, g, b) { + var min = Math.min(r /= 255, g /= 255, b /= 255), max = Math.max(r, g, b), d = max - min, h, s, l = (max + min) / 2; + if (d) { + s = l < .5 ? d / (max + min) : d / (2 - max - min); + if (r == max) h = (g - b) / d + (g < b ? 6 : 0); else if (g == max) h = (b - r) / d + 2; else h = (r - g) / d + 4; + h *= 60; + } else { + h = NaN; + s = l > 0 && l < 1 ? 0 : h; + } + return new d3_hsl(h, s, l); + } + function d3_rgb_lab(r, g, b) { + r = d3_rgb_xyz(r); + g = d3_rgb_xyz(g); + b = d3_rgb_xyz(b); + var x = d3_xyz_lab((.4124564 * r + .3575761 * g + .1804375 * b) / d3_lab_X), y = d3_xyz_lab((.2126729 * r + .7151522 * g + .072175 * b) / d3_lab_Y), z = d3_xyz_lab((.0193339 * r + .119192 * g + .9503041 * b) / d3_lab_Z); + return d3_lab(116 * y - 16, 500 * (x - y), 200 * (y - z)); + } + function d3_rgb_xyz(r) { + return (r /= 255) <= .04045 ? r / 12.92 : Math.pow((r + .055) / 1.055, 2.4); + } + function d3_rgb_parseNumber(c) { + var f = parseFloat(c); + return c.charAt(c.length - 1) === "%" ? Math.round(f * 2.55) : f; + } + var d3_rgb_names = d3.map({ + aliceblue: 15792383, + antiquewhite: 16444375, + aqua: 65535, + aquamarine: 8388564, + azure: 15794175, + beige: 16119260, + bisque: 16770244, + black: 0, + blanchedalmond: 16772045, + blue: 255, + blueviolet: 9055202, + brown: 10824234, + burlywood: 14596231, + cadetblue: 6266528, + chartreuse: 8388352, + chocolate: 13789470, + coral: 16744272, + cornflowerblue: 6591981, + cornsilk: 16775388, + crimson: 14423100, + cyan: 65535, + darkblue: 139, + darkcyan: 35723, + darkgoldenrod: 12092939, + darkgray: 11119017, + darkgreen: 25600, + darkgrey: 11119017, + darkkhaki: 12433259, + darkmagenta: 9109643, + darkolivegreen: 5597999, + darkorange: 16747520, + darkorchid: 10040012, + darkred: 9109504, + darksalmon: 15308410, + darkseagreen: 9419919, + darkslateblue: 4734347, + darkslategray: 3100495, + darkslategrey: 3100495, + darkturquoise: 52945, + darkviolet: 9699539, + deeppink: 16716947, + deepskyblue: 49151, + dimgray: 6908265, + dimgrey: 6908265, + dodgerblue: 2003199, + firebrick: 11674146, + floralwhite: 16775920, + forestgreen: 2263842, + fuchsia: 16711935, + gainsboro: 14474460, + ghostwhite: 16316671, + gold: 16766720, + goldenrod: 14329120, + gray: 8421504, + green: 32768, + greenyellow: 11403055, + grey: 8421504, + honeydew: 15794160, + hotpink: 16738740, + indianred: 13458524, + indigo: 4915330, + ivory: 16777200, + khaki: 15787660, + lavender: 15132410, + lavenderblush: 16773365, + lawngreen: 8190976, + lemonchiffon: 16775885, + lightblue: 11393254, + lightcoral: 15761536, + lightcyan: 14745599, + lightgoldenrodyellow: 16448210, + lightgray: 13882323, + lightgreen: 9498256, + lightgrey: 13882323, + lightpink: 16758465, + lightsalmon: 16752762, + lightseagreen: 2142890, + lightskyblue: 8900346, + lightslategray: 7833753, + lightslategrey: 7833753, + lightsteelblue: 11584734, + lightyellow: 16777184, + lime: 65280, + limegreen: 3329330, + linen: 16445670, + magenta: 16711935, + maroon: 8388608, + mediumaquamarine: 6737322, + mediumblue: 205, + mediumorchid: 12211667, + mediumpurple: 9662683, + mediumseagreen: 3978097, + mediumslateblue: 8087790, + mediumspringgreen: 64154, + mediumturquoise: 4772300, + mediumvioletred: 13047173, + midnightblue: 1644912, + mintcream: 16121850, + mistyrose: 16770273, + moccasin: 16770229, + navajowhite: 16768685, + navy: 128, + oldlace: 16643558, + olive: 8421376, + olivedrab: 7048739, + orange: 16753920, + orangered: 16729344, + orchid: 14315734, + palegoldenrod: 15657130, + palegreen: 10025880, + paleturquoise: 11529966, + palevioletred: 14381203, + papayawhip: 16773077, + peachpuff: 16767673, + peru: 13468991, + pink: 16761035, + plum: 14524637, + powderblue: 11591910, + purple: 8388736, + red: 16711680, + rosybrown: 12357519, + royalblue: 4286945, + saddlebrown: 9127187, + salmon: 16416882, + sandybrown: 16032864, + seagreen: 3050327, + seashell: 16774638, + sienna: 10506797, + silver: 12632256, + skyblue: 8900331, + slateblue: 6970061, + slategray: 7372944, + slategrey: 7372944, + snow: 16775930, + springgreen: 65407, + steelblue: 4620980, + tan: 13808780, + teal: 32896, + thistle: 14204888, + tomato: 16737095, + turquoise: 4251856, + violet: 15631086, + wheat: 16113331, + white: 16777215, + whitesmoke: 16119285, + yellow: 16776960, + yellowgreen: 10145074 + }); + d3_rgb_names.forEach(function(key, value) { + d3_rgb_names.set(key, d3_rgbNumber(value)); + }); + function d3_functor(v) { + return typeof v === "function" ? v : function() { + return v; + }; + } + d3.functor = d3_functor; + function d3_identity(d) { + return d; + } + d3.xhr = d3_xhrType(d3_identity); + function d3_xhrType(response) { + return function(url, mimeType, callback) { + if (arguments.length === 2 && typeof mimeType === "function") callback = mimeType, + mimeType = null; + return d3_xhr(url, mimeType, response, callback); + }; + } + function d3_xhr(url, mimeType, response, callback) { + var xhr = {}, dispatch = d3.dispatch("beforesend", "progress", "load", "error"), headers = {}, request = new XMLHttpRequest(), responseType = null; + if (d3_window.XDomainRequest && !("withCredentials" in request) && /^(http(s)?:)?\/\//.test(url)) request = new XDomainRequest(); + "onload" in request ? request.onload = request.onerror = respond : request.onreadystatechange = function() { + request.readyState > 3 && respond(); + }; + function respond() { + var status = request.status, result; + if (!status && d3_xhrHasResponse(request) || status >= 200 && status < 300 || status === 304) { + try { + result = response.call(xhr, request); + } catch (e) { + dispatch.error.call(xhr, e); + return; + } + dispatch.load.call(xhr, result); + } else { + dispatch.error.call(xhr, request); + } + } + request.onprogress = function(event) { + var o = d3.event; + d3.event = event; + try { + dispatch.progress.call(xhr, request); + } finally { + d3.event = o; + } + }; + xhr.header = function(name, value) { + name = (name + "").toLowerCase(); + if (arguments.length < 2) return headers[name]; + if (value == null) delete headers[name]; else headers[name] = value + ""; + return xhr; + }; + xhr.mimeType = function(value) { + if (!arguments.length) return mimeType; + mimeType = value == null ? null : value + ""; + return xhr; + }; + xhr.responseType = function(value) { + if (!arguments.length) return responseType; + responseType = value; + return xhr; + }; + xhr.response = function(value) { + response = value; + return xhr; + }; + [ "get", "post" ].forEach(function(method) { + xhr[method] = function() { + return xhr.send.apply(xhr, [ method ].concat(d3_array(arguments))); + }; + }); + xhr.send = function(method, data, callback) { + if (arguments.length === 2 && typeof data === "function") callback = data, data = null; + request.open(method, url, true); + if (mimeType != null && !("accept" in headers)) headers["accept"] = mimeType + ",*/*"; + if (request.setRequestHeader) for (var name in headers) request.setRequestHeader(name, headers[name]); + if (mimeType != null && request.overrideMimeType) request.overrideMimeType(mimeType); + if (responseType != null) request.responseType = responseType; + if (callback != null) xhr.on("error", callback).on("load", function(request) { + callback(null, request); + }); + dispatch.beforesend.call(xhr, request); + request.send(data == null ? null : data); + return xhr; + }; + xhr.abort = function() { + request.abort(); + return xhr; + }; + d3.rebind(xhr, dispatch, "on"); + return callback == null ? xhr : xhr.get(d3_xhr_fixCallback(callback)); + } + function d3_xhr_fixCallback(callback) { + return callback.length === 1 ? function(error, request) { + callback(error == null ? request : null); + } : callback; + } + function d3_xhrHasResponse(request) { + var type = request.responseType; + return type && type !== "text" ? request.response : request.responseText; + } + d3.dsv = function(delimiter, mimeType) { + var reFormat = new RegExp('["' + delimiter + "\n]"), delimiterCode = delimiter.charCodeAt(0); + function dsv(url, row, callback) { + if (arguments.length < 3) callback = row, row = null; + var xhr = d3_xhr(url, mimeType, row == null ? response : typedResponse(row), callback); + xhr.row = function(_) { + return arguments.length ? xhr.response((row = _) == null ? response : typedResponse(_)) : row; + }; + return xhr; + } + function response(request) { + return dsv.parse(request.responseText); + } + function typedResponse(f) { + return function(request) { + return dsv.parse(request.responseText, f); + }; + } + dsv.parse = function(text, f) { + var o; + return dsv.parseRows(text, function(row, i) { + if (o) return o(row, i - 1); + var a = new Function("d", "return {" + row.map(function(name, i) { + return JSON.stringify(name) + ": d[" + i + "]"; + }).join(",") + "}"); + o = f ? function(row, i) { + return f(a(row), i); + } : a; + }); + }; + dsv.parseRows = function(text, f) { + var EOL = {}, EOF = {}, rows = [], N = text.length, I = 0, n = 0, t, eol; + function token() { + if (I >= N) return EOF; + if (eol) return eol = false, EOL; + var j = I; + if (text.charCodeAt(j) === 34) { + var i = j; + while (i++ < N) { + if (text.charCodeAt(i) === 34) { + if (text.charCodeAt(i + 1) !== 34) break; + ++i; + } + } + I = i + 2; + var c = text.charCodeAt(i + 1); + if (c === 13) { + eol = true; + if (text.charCodeAt(i + 2) === 10) ++I; + } else if (c === 10) { + eol = true; + } + return text.slice(j + 1, i).replace(/""/g, '"'); + } + while (I < N) { + var c = text.charCodeAt(I++), k = 1; + if (c === 10) eol = true; else if (c === 13) { + eol = true; + if (text.charCodeAt(I) === 10) ++I, ++k; + } else if (c !== delimiterCode) continue; + return text.slice(j, I - k); + } + return text.slice(j); + } + while ((t = token()) !== EOF) { + var a = []; + while (t !== EOL && t !== EOF) { + a.push(t); + t = token(); + } + if (f && (a = f(a, n++)) == null) continue; + rows.push(a); + } + return rows; + }; + dsv.format = function(rows) { + if (Array.isArray(rows[0])) return dsv.formatRows(rows); + var fieldSet = new d3_Set(), fields = []; + rows.forEach(function(row) { + for (var field in row) { + if (!fieldSet.has(field)) { + fields.push(fieldSet.add(field)); + } + } + }); + return [ fields.map(formatValue).join(delimiter) ].concat(rows.map(function(row) { + return fields.map(function(field) { + return formatValue(row[field]); + }).join(delimiter); + })).join("\n"); + }; + dsv.formatRows = function(rows) { + return rows.map(formatRow).join("\n"); + }; + function formatRow(row) { + return row.map(formatValue).join(delimiter); + } + function formatValue(text) { + return reFormat.test(text) ? '"' + text.replace(/\"/g, '""') + '"' : text; + } + return dsv; + }; + d3.csv = d3.dsv(",", "text/csv"); + d3.tsv = d3.dsv(" ", "text/tab-separated-values"); + var d3_timer_queueHead, d3_timer_queueTail, d3_timer_interval, d3_timer_timeout, d3_timer_active, d3_timer_frame = d3_window[d3_vendorSymbol(d3_window, "requestAnimationFrame")] || function(callback) { + setTimeout(callback, 17); + }; + d3.timer = function(callback, delay, then) { + var n = arguments.length; + if (n < 2) delay = 0; + if (n < 3) then = Date.now(); + var time = then + delay, timer = { + c: callback, + t: time, + f: false, + n: null + }; + if (d3_timer_queueTail) d3_timer_queueTail.n = timer; else d3_timer_queueHead = timer; + d3_timer_queueTail = timer; + if (!d3_timer_interval) { + d3_timer_timeout = clearTimeout(d3_timer_timeout); + d3_timer_interval = 1; + d3_timer_frame(d3_timer_step); + } + }; + function d3_timer_step() { + var now = d3_timer_mark(), delay = d3_timer_sweep() - now; + if (delay > 24) { + if (isFinite(delay)) { + clearTimeout(d3_timer_timeout); + d3_timer_timeout = setTimeout(d3_timer_step, delay); + } + d3_timer_interval = 0; + } else { + d3_timer_interval = 1; + d3_timer_frame(d3_timer_step); + } + } + d3.timer.flush = function() { + d3_timer_mark(); + d3_timer_sweep(); + }; + function d3_timer_mark() { + var now = Date.now(); + d3_timer_active = d3_timer_queueHead; + while (d3_timer_active) { + if (now >= d3_timer_active.t) d3_timer_active.f = d3_timer_active.c(now - d3_timer_active.t); + d3_timer_active = d3_timer_active.n; + } + return now; + } + function d3_timer_sweep() { + var t0, t1 = d3_timer_queueHead, time = Infinity; + while (t1) { + if (t1.f) { + t1 = t0 ? t0.n = t1.n : d3_timer_queueHead = t1.n; + } else { + if (t1.t < time) time = t1.t; + t1 = (t0 = t1).n; + } + } + d3_timer_queueTail = t0; + return time; + } + function d3_format_precision(x, p) { + return p - (x ? Math.ceil(Math.log(x) / Math.LN10) : 1); + } + d3.round = function(x, n) { + return n ? Math.round(x * (n = Math.pow(10, n))) / n : Math.round(x); + }; + var d3_formatPrefixes = [ "y", "z", "a", "f", "p", "n", "µ", "m", "", "k", "M", "G", "T", "P", "E", "Z", "Y" ].map(d3_formatPrefix); + d3.formatPrefix = function(value, precision) { + var i = 0; + if (value) { + if (value < 0) value *= -1; + if (precision) value = d3.round(value, d3_format_precision(value, precision)); + i = 1 + Math.floor(1e-12 + Math.log(value) / Math.LN10); + i = Math.max(-24, Math.min(24, Math.floor((i - 1) / 3) * 3)); + } + return d3_formatPrefixes[8 + i / 3]; + }; + function d3_formatPrefix(d, i) { + var k = Math.pow(10, abs(8 - i) * 3); + return { + scale: i > 8 ? function(d) { + return d / k; + } : function(d) { + return d * k; + }, + symbol: d + }; + } + function d3_locale_numberFormat(locale) { + var locale_decimal = locale.decimal, locale_thousands = locale.thousands, locale_grouping = locale.grouping, locale_currency = locale.currency, formatGroup = locale_grouping && locale_thousands ? function(value, width) { + var i = value.length, t = [], j = 0, g = locale_grouping[0], length = 0; + while (i > 0 && g > 0) { + if (length + g + 1 > width) g = Math.max(1, width - length); + t.push(value.substring(i -= g, i + g)); + if ((length += g + 1) > width) break; + g = locale_grouping[j = (j + 1) % locale_grouping.length]; + } + return t.reverse().join(locale_thousands); + } : d3_identity; + return function(specifier) { + var match = d3_format_re.exec(specifier), fill = match[1] || " ", align = match[2] || ">", sign = match[3] || "-", symbol = match[4] || "", zfill = match[5], width = +match[6], comma = match[7], precision = match[8], type = match[9], scale = 1, prefix = "", suffix = "", integer = false, exponent = true; + if (precision) precision = +precision.substring(1); + if (zfill || fill === "0" && align === "=") { + zfill = fill = "0"; + align = "="; + } + switch (type) { + case "n": + comma = true; + type = "g"; + break; + + case "%": + scale = 100; + suffix = "%"; + type = "f"; + break; + + case "p": + scale = 100; + suffix = "%"; + type = "r"; + break; + + case "b": + case "o": + case "x": + case "X": + if (symbol === "#") prefix = "0" + type.toLowerCase(); + + case "c": + exponent = false; + + case "d": + integer = true; + precision = 0; + break; + + case "s": + scale = -1; + type = "r"; + break; + } + if (symbol === "$") prefix = locale_currency[0], suffix = locale_currency[1]; + if (type == "r" && !precision) type = "g"; + if (precision != null) { + if (type == "g") precision = Math.max(1, Math.min(21, precision)); else if (type == "e" || type == "f") precision = Math.max(0, Math.min(20, precision)); + } + type = d3_format_types.get(type) || d3_format_typeDefault; + var zcomma = zfill && comma; + return function(value) { + var fullSuffix = suffix; + if (integer && value % 1) return ""; + var negative = value < 0 || value === 0 && 1 / value < 0 ? (value = -value, "-") : sign === "-" ? "" : sign; + if (scale < 0) { + var unit = d3.formatPrefix(value, precision); + value = unit.scale(value); + fullSuffix = unit.symbol + suffix; + } else { + value *= scale; + } + value = type(value, precision); + var i = value.lastIndexOf("."), before, after; + if (i < 0) { + var j = exponent ? value.lastIndexOf("e") : -1; + if (j < 0) before = value, after = ""; else before = value.substring(0, j), after = value.substring(j); + } else { + before = value.substring(0, i); + after = locale_decimal + value.substring(i + 1); + } + if (!zfill && comma) before = formatGroup(before, Infinity); + var length = prefix.length + before.length + after.length + (zcomma ? 0 : negative.length), padding = length < width ? new Array(length = width - length + 1).join(fill) : ""; + if (zcomma) before = formatGroup(padding + before, padding.length ? width - after.length : Infinity); + negative += prefix; + value = before + after; + return (align === "<" ? negative + value + padding : align === ">" ? padding + negative + value : align === "^" ? padding.substring(0, length >>= 1) + negative + value + padding.substring(length) : negative + (zcomma ? value : padding + value)) + fullSuffix; + }; + }; + } + var d3_format_re = /(?:([^{])?([<>=^]))?([+\- ])?([$#])?(0)?(\d+)?(,)?(\.-?\d+)?([a-z%])?/i; + var d3_format_types = d3.map({ + b: function(x) { + return x.toString(2); + }, + c: function(x) { + return String.fromCharCode(x); + }, + o: function(x) { + return x.toString(8); + }, + x: function(x) { + return x.toString(16); + }, + X: function(x) { + return x.toString(16).toUpperCase(); + }, + g: function(x, p) { + return x.toPrecision(p); + }, + e: function(x, p) { + return x.toExponential(p); + }, + f: function(x, p) { + return x.toFixed(p); + }, + r: function(x, p) { + return (x = d3.round(x, d3_format_precision(x, p))).toFixed(Math.max(0, Math.min(20, d3_format_precision(x * (1 + 1e-15), p)))); + } + }); + function d3_format_typeDefault(x) { + return x + ""; + } + var d3_time = d3.time = {}, d3_date = Date; + function d3_date_utc() { + this._ = new Date(arguments.length > 1 ? Date.UTC.apply(this, arguments) : arguments[0]); + } + d3_date_utc.prototype = { + getDate: function() { + return this._.getUTCDate(); + }, + getDay: function() { + return this._.getUTCDay(); + }, + getFullYear: function() { + return this._.getUTCFullYear(); + }, + getHours: function() { + return this._.getUTCHours(); + }, + getMilliseconds: function() { + return this._.getUTCMilliseconds(); + }, + getMinutes: function() { + return this._.getUTCMinutes(); + }, + getMonth: function() { + return this._.getUTCMonth(); + }, + getSeconds: function() { + return this._.getUTCSeconds(); + }, + getTime: function() { + return this._.getTime(); + }, + getTimezoneOffset: function() { + return 0; + }, + valueOf: function() { + return this._.valueOf(); + }, + setDate: function() { + d3_time_prototype.setUTCDate.apply(this._, arguments); + }, + setDay: function() { + d3_time_prototype.setUTCDay.apply(this._, arguments); + }, + setFullYear: function() { + d3_time_prototype.setUTCFullYear.apply(this._, arguments); + }, + setHours: function() { + d3_time_prototype.setUTCHours.apply(this._, arguments); + }, + setMilliseconds: function() { + d3_time_prototype.setUTCMilliseconds.apply(this._, arguments); + }, + setMinutes: function() { + d3_time_prototype.setUTCMinutes.apply(this._, arguments); + }, + setMonth: function() { + d3_time_prototype.setUTCMonth.apply(this._, arguments); + }, + setSeconds: function() { + d3_time_prototype.setUTCSeconds.apply(this._, arguments); + }, + setTime: function() { + d3_time_prototype.setTime.apply(this._, arguments); + } + }; + var d3_time_prototype = Date.prototype; + function d3_time_interval(local, step, number) { + function round(date) { + var d0 = local(date), d1 = offset(d0, 1); + return date - d0 < d1 - date ? d0 : d1; + } + function ceil(date) { + step(date = local(new d3_date(date - 1)), 1); + return date; + } + function offset(date, k) { + step(date = new d3_date(+date), k); + return date; + } + function range(t0, t1, dt) { + var time = ceil(t0), times = []; + if (dt > 1) { + while (time < t1) { + if (!(number(time) % dt)) times.push(new Date(+time)); + step(time, 1); + } + } else { + while (time < t1) times.push(new Date(+time)), step(time, 1); + } + return times; + } + function range_utc(t0, t1, dt) { + try { + d3_date = d3_date_utc; + var utc = new d3_date_utc(); + utc._ = t0; + return range(utc, t1, dt); + } finally { + d3_date = Date; + } + } + local.floor = local; + local.round = round; + local.ceil = ceil; + local.offset = offset; + local.range = range; + var utc = local.utc = d3_time_interval_utc(local); + utc.floor = utc; + utc.round = d3_time_interval_utc(round); + utc.ceil = d3_time_interval_utc(ceil); + utc.offset = d3_time_interval_utc(offset); + utc.range = range_utc; + return local; + } + function d3_time_interval_utc(method) { + return function(date, k) { + try { + d3_date = d3_date_utc; + var utc = new d3_date_utc(); + utc._ = date; + return method(utc, k)._; + } finally { + d3_date = Date; + } + }; + } + d3_time.year = d3_time_interval(function(date) { + date = d3_time.day(date); + date.setMonth(0, 1); + return date; + }, function(date, offset) { + date.setFullYear(date.getFullYear() + offset); + }, function(date) { + return date.getFullYear(); + }); + d3_time.years = d3_time.year.range; + d3_time.years.utc = d3_time.year.utc.range; + d3_time.day = d3_time_interval(function(date) { + var day = new d3_date(2e3, 0); + day.setFullYear(date.getFullYear(), date.getMonth(), date.getDate()); + return day; + }, function(date, offset) { + date.setDate(date.getDate() + offset); + }, function(date) { + return date.getDate() - 1; + }); + d3_time.days = d3_time.day.range; + d3_time.days.utc = d3_time.day.utc.range; + d3_time.dayOfYear = function(date) { + var year = d3_time.year(date); + return Math.floor((date - year - (date.getTimezoneOffset() - year.getTimezoneOffset()) * 6e4) / 864e5); + }; + [ "sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday" ].forEach(function(day, i) { + i = 7 - i; + var interval = d3_time[day] = d3_time_interval(function(date) { + (date = d3_time.day(date)).setDate(date.getDate() - (date.getDay() + i) % 7); + return date; + }, function(date, offset) { + date.setDate(date.getDate() + Math.floor(offset) * 7); + }, function(date) { + var day = d3_time.year(date).getDay(); + return Math.floor((d3_time.dayOfYear(date) + (day + i) % 7) / 7) - (day !== i); + }); + d3_time[day + "s"] = interval.range; + d3_time[day + "s"].utc = interval.utc.range; + d3_time[day + "OfYear"] = function(date) { + var day = d3_time.year(date).getDay(); + return Math.floor((d3_time.dayOfYear(date) + (day + i) % 7) / 7); + }; + }); + d3_time.week = d3_time.sunday; + d3_time.weeks = d3_time.sunday.range; + d3_time.weeks.utc = d3_time.sunday.utc.range; + d3_time.weekOfYear = d3_time.sundayOfYear; + function d3_locale_timeFormat(locale) { + var locale_dateTime = locale.dateTime, locale_date = locale.date, locale_time = locale.time, locale_periods = locale.periods, locale_days = locale.days, locale_shortDays = locale.shortDays, locale_months = locale.months, locale_shortMonths = locale.shortMonths; + function d3_time_format(template) { + var n = template.length; + function format(date) { + var string = [], i = -1, j = 0, c, p, f; + while (++i < n) { + if (template.charCodeAt(i) === 37) { + string.push(template.slice(j, i)); + if ((p = d3_time_formatPads[c = template.charAt(++i)]) != null) c = template.charAt(++i); + if (f = d3_time_formats[c]) c = f(date, p == null ? c === "e" ? " " : "0" : p); + string.push(c); + j = i + 1; + } + } + string.push(template.slice(j, i)); + return string.join(""); + } + format.parse = function(string) { + var d = { + y: 1900, + m: 0, + d: 1, + H: 0, + M: 0, + S: 0, + L: 0, + Z: null + }, i = d3_time_parse(d, template, string, 0); + if (i != string.length) return null; + if ("p" in d) d.H = d.H % 12 + d.p * 12; + var localZ = d.Z != null && d3_date !== d3_date_utc, date = new (localZ ? d3_date_utc : d3_date)(); + if ("j" in d) date.setFullYear(d.y, 0, d.j); else if ("w" in d && ("W" in d || "U" in d)) { + date.setFullYear(d.y, 0, 1); + date.setFullYear(d.y, 0, "W" in d ? (d.w + 6) % 7 + d.W * 7 - (date.getDay() + 5) % 7 : d.w + d.U * 7 - (date.getDay() + 6) % 7); + } else date.setFullYear(d.y, d.m, d.d); + date.setHours(d.H + (d.Z / 100 | 0), d.M + d.Z % 100, d.S, d.L); + return localZ ? date._ : date; + }; + format.toString = function() { + return template; + }; + return format; + } + function d3_time_parse(date, template, string, j) { + var c, p, t, i = 0, n = template.length, m = string.length; + while (i < n) { + if (j >= m) return -1; + c = template.charCodeAt(i++); + if (c === 37) { + t = template.charAt(i++); + p = d3_time_parsers[t in d3_time_formatPads ? template.charAt(i++) : t]; + if (!p || (j = p(date, string, j)) < 0) return -1; + } else if (c != string.charCodeAt(j++)) { + return -1; + } + } + return j; + } + d3_time_format.utc = function(template) { + var local = d3_time_format(template); + function format(date) { + try { + d3_date = d3_date_utc; + var utc = new d3_date(); + utc._ = date; + return local(utc); + } finally { + d3_date = Date; + } + } + format.parse = function(string) { + try { + d3_date = d3_date_utc; + var date = local.parse(string); + return date && date._; + } finally { + d3_date = Date; + } + }; + format.toString = local.toString; + return format; + }; + d3_time_format.multi = d3_time_format.utc.multi = d3_time_formatMulti; + var d3_time_periodLookup = d3.map(), d3_time_dayRe = d3_time_formatRe(locale_days), d3_time_dayLookup = d3_time_formatLookup(locale_days), d3_time_dayAbbrevRe = d3_time_formatRe(locale_shortDays), d3_time_dayAbbrevLookup = d3_time_formatLookup(locale_shortDays), d3_time_monthRe = d3_time_formatRe(locale_months), d3_time_monthLookup = d3_time_formatLookup(locale_months), d3_time_monthAbbrevRe = d3_time_formatRe(locale_shortMonths), d3_time_monthAbbrevLookup = d3_time_formatLookup(locale_shortMonths); + locale_periods.forEach(function(p, i) { + d3_time_periodLookup.set(p.toLowerCase(), i); + }); + var d3_time_formats = { + a: function(d) { + return locale_shortDays[d.getDay()]; + }, + A: function(d) { + return locale_days[d.getDay()]; + }, + b: function(d) { + return locale_shortMonths[d.getMonth()]; + }, + B: function(d) { + return locale_months[d.getMonth()]; + }, + c: d3_time_format(locale_dateTime), + d: function(d, p) { + return d3_time_formatPad(d.getDate(), p, 2); + }, + e: function(d, p) { + return d3_time_formatPad(d.getDate(), p, 2); + }, + H: function(d, p) { + return d3_time_formatPad(d.getHours(), p, 2); + }, + I: function(d, p) { + return d3_time_formatPad(d.getHours() % 12 || 12, p, 2); + }, + j: function(d, p) { + return d3_time_formatPad(1 + d3_time.dayOfYear(d), p, 3); + }, + L: function(d, p) { + return d3_time_formatPad(d.getMilliseconds(), p, 3); + }, + m: function(d, p) { + return d3_time_formatPad(d.getMonth() + 1, p, 2); + }, + M: function(d, p) { + return d3_time_formatPad(d.getMinutes(), p, 2); + }, + p: function(d) { + return locale_periods[+(d.getHours() >= 12)]; + }, + S: function(d, p) { + return d3_time_formatPad(d.getSeconds(), p, 2); + }, + U: function(d, p) { + return d3_time_formatPad(d3_time.sundayOfYear(d), p, 2); + }, + w: function(d) { + return d.getDay(); + }, + W: function(d, p) { + return d3_time_formatPad(d3_time.mondayOfYear(d), p, 2); + }, + x: d3_time_format(locale_date), + X: d3_time_format(locale_time), + y: function(d, p) { + return d3_time_formatPad(d.getFullYear() % 100, p, 2); + }, + Y: function(d, p) { + return d3_time_formatPad(d.getFullYear() % 1e4, p, 4); + }, + Z: d3_time_zone, + "%": function() { + return "%"; + } + }; + var d3_time_parsers = { + a: d3_time_parseWeekdayAbbrev, + A: d3_time_parseWeekday, + b: d3_time_parseMonthAbbrev, + B: d3_time_parseMonth, + c: d3_time_parseLocaleFull, + d: d3_time_parseDay, + e: d3_time_parseDay, + H: d3_time_parseHour24, + I: d3_time_parseHour24, + j: d3_time_parseDayOfYear, + L: d3_time_parseMilliseconds, + m: d3_time_parseMonthNumber, + M: d3_time_parseMinutes, + p: d3_time_parseAmPm, + S: d3_time_parseSeconds, + U: d3_time_parseWeekNumberSunday, + w: d3_time_parseWeekdayNumber, + W: d3_time_parseWeekNumberMonday, + x: d3_time_parseLocaleDate, + X: d3_time_parseLocaleTime, + y: d3_time_parseYear, + Y: d3_time_parseFullYear, + Z: d3_time_parseZone, + "%": d3_time_parseLiteralPercent + }; + function d3_time_parseWeekdayAbbrev(date, string, i) { + d3_time_dayAbbrevRe.lastIndex = 0; + var n = d3_time_dayAbbrevRe.exec(string.slice(i)); + return n ? (date.w = d3_time_dayAbbrevLookup.get(n[0].toLowerCase()), i + n[0].length) : -1; + } + function d3_time_parseWeekday(date, string, i) { + d3_time_dayRe.lastIndex = 0; + var n = d3_time_dayRe.exec(string.slice(i)); + return n ? (date.w = d3_time_dayLookup.get(n[0].toLowerCase()), i + n[0].length) : -1; + } + function d3_time_parseMonthAbbrev(date, string, i) { + d3_time_monthAbbrevRe.lastIndex = 0; + var n = d3_time_monthAbbrevRe.exec(string.slice(i)); + return n ? (date.m = d3_time_monthAbbrevLookup.get(n[0].toLowerCase()), i + n[0].length) : -1; + } + function d3_time_parseMonth(date, string, i) { + d3_time_monthRe.lastIndex = 0; + var n = d3_time_monthRe.exec(string.slice(i)); + return n ? (date.m = d3_time_monthLookup.get(n[0].toLowerCase()), i + n[0].length) : -1; + } + function d3_time_parseLocaleFull(date, string, i) { + return d3_time_parse(date, d3_time_formats.c.toString(), string, i); + } + function d3_time_parseLocaleDate(date, string, i) { + return d3_time_parse(date, d3_time_formats.x.toString(), string, i); + } + function d3_time_parseLocaleTime(date, string, i) { + return d3_time_parse(date, d3_time_formats.X.toString(), string, i); + } + function d3_time_parseAmPm(date, string, i) { + var n = d3_time_periodLookup.get(string.slice(i, i += 2).toLowerCase()); + return n == null ? -1 : (date.p = n, i); + } + return d3_time_format; + } + var d3_time_formatPads = { + "-": "", + _: " ", + "0": "0" + }, d3_time_numberRe = /^\s*\d+/, d3_time_percentRe = /^%/; + function d3_time_formatPad(value, fill, width) { + var sign = value < 0 ? "-" : "", string = (sign ? -value : value) + "", length = string.length; + return sign + (length < width ? new Array(width - length + 1).join(fill) + string : string); + } + function d3_time_formatRe(names) { + return new RegExp("^(?:" + names.map(d3.requote).join("|") + ")", "i"); + } + function d3_time_formatLookup(names) { + var map = new d3_Map(), i = -1, n = names.length; + while (++i < n) map.set(names[i].toLowerCase(), i); + return map; + } + function d3_time_parseWeekdayNumber(date, string, i) { + d3_time_numberRe.lastIndex = 0; + var n = d3_time_numberRe.exec(string.slice(i, i + 1)); + return n ? (date.w = +n[0], i + n[0].length) : -1; + } + function d3_time_parseWeekNumberSunday(date, string, i) { + d3_time_numberRe.lastIndex = 0; + var n = d3_time_numberRe.exec(string.slice(i)); + return n ? (date.U = +n[0], i + n[0].length) : -1; + } + function d3_time_parseWeekNumberMonday(date, string, i) { + d3_time_numberRe.lastIndex = 0; + var n = d3_time_numberRe.exec(string.slice(i)); + return n ? (date.W = +n[0], i + n[0].length) : -1; + } + function d3_time_parseFullYear(date, string, i) { + d3_time_numberRe.lastIndex = 0; + var n = d3_time_numberRe.exec(string.slice(i, i + 4)); + return n ? (date.y = +n[0], i + n[0].length) : -1; + } + function d3_time_parseYear(date, string, i) { + d3_time_numberRe.lastIndex = 0; + var n = d3_time_numberRe.exec(string.slice(i, i + 2)); + return n ? (date.y = d3_time_expandYear(+n[0]), i + n[0].length) : -1; + } + function d3_time_parseZone(date, string, i) { + return /^[+-]\d{4}$/.test(string = string.slice(i, i + 5)) ? (date.Z = -string, + i + 5) : -1; + } + function d3_time_expandYear(d) { + return d + (d > 68 ? 1900 : 2e3); + } + function d3_time_parseMonthNumber(date, string, i) { + d3_time_numberRe.lastIndex = 0; + var n = d3_time_numberRe.exec(string.slice(i, i + 2)); + return n ? (date.m = n[0] - 1, i + n[0].length) : -1; + } + function d3_time_parseDay(date, string, i) { + d3_time_numberRe.lastIndex = 0; + var n = d3_time_numberRe.exec(string.slice(i, i + 2)); + return n ? (date.d = +n[0], i + n[0].length) : -1; + } + function d3_time_parseDayOfYear(date, string, i) { + d3_time_numberRe.lastIndex = 0; + var n = d3_time_numberRe.exec(string.slice(i, i + 3)); + return n ? (date.j = +n[0], i + n[0].length) : -1; + } + function d3_time_parseHour24(date, string, i) { + d3_time_numberRe.lastIndex = 0; + var n = d3_time_numberRe.exec(string.slice(i, i + 2)); + return n ? (date.H = +n[0], i + n[0].length) : -1; + } + function d3_time_parseMinutes(date, string, i) { + d3_time_numberRe.lastIndex = 0; + var n = d3_time_numberRe.exec(string.slice(i, i + 2)); + return n ? (date.M = +n[0], i + n[0].length) : -1; + } + function d3_time_parseSeconds(date, string, i) { + d3_time_numberRe.lastIndex = 0; + var n = d3_time_numberRe.exec(string.slice(i, i + 2)); + return n ? (date.S = +n[0], i + n[0].length) : -1; + } + function d3_time_parseMilliseconds(date, string, i) { + d3_time_numberRe.lastIndex = 0; + var n = d3_time_numberRe.exec(string.slice(i, i + 3)); + return n ? (date.L = +n[0], i + n[0].length) : -1; + } + function d3_time_zone(d) { + var z = d.getTimezoneOffset(), zs = z > 0 ? "-" : "+", zh = abs(z) / 60 | 0, zm = abs(z) % 60; + return zs + d3_time_formatPad(zh, "0", 2) + d3_time_formatPad(zm, "0", 2); + } + function d3_time_parseLiteralPercent(date, string, i) { + d3_time_percentRe.lastIndex = 0; + var n = d3_time_percentRe.exec(string.slice(i, i + 1)); + return n ? i + n[0].length : -1; + } + function d3_time_formatMulti(formats) { + var n = formats.length, i = -1; + while (++i < n) formats[i][0] = this(formats[i][0]); + return function(date) { + var i = 0, f = formats[i]; + while (!f[1](date)) f = formats[++i]; + return f[0](date); + }; + } + d3.locale = function(locale) { + return { + numberFormat: d3_locale_numberFormat(locale), + timeFormat: d3_locale_timeFormat(locale) + }; + }; + var d3_locale_enUS = d3.locale({ + decimal: ".", + thousands: ",", + grouping: [ 3 ], + currency: [ "$", "" ], + dateTime: "%a %b %e %X %Y", + date: "%m/%d/%Y", + time: "%H:%M:%S", + periods: [ "AM", "PM" ], + days: [ "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" ], + shortDays: [ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" ], + months: [ "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" ], + shortMonths: [ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" ] + }); + d3.format = d3_locale_enUS.numberFormat; + d3.geo = {}; + function d3_adder() {} + d3_adder.prototype = { + s: 0, + t: 0, + add: function(y) { + d3_adderSum(y, this.t, d3_adderTemp); + d3_adderSum(d3_adderTemp.s, this.s, this); + if (this.s) this.t += d3_adderTemp.t; else this.s = d3_adderTemp.t; + }, + reset: function() { + this.s = this.t = 0; + }, + valueOf: function() { + return this.s; + } + }; + var d3_adderTemp = new d3_adder(); + function d3_adderSum(a, b, o) { + var x = o.s = a + b, bv = x - a, av = x - bv; + o.t = a - av + (b - bv); + } + d3.geo.stream = function(object, listener) { + if (object && d3_geo_streamObjectType.hasOwnProperty(object.type)) { + d3_geo_streamObjectType[object.type](object, listener); + } else { + d3_geo_streamGeometry(object, listener); + } + }; + function d3_geo_streamGeometry(geometry, listener) { + if (geometry && d3_geo_streamGeometryType.hasOwnProperty(geometry.type)) { + d3_geo_streamGeometryType[geometry.type](geometry, listener); + } + } + var d3_geo_streamObjectType = { + Feature: function(feature, listener) { + d3_geo_streamGeometry(feature.geometry, listener); + }, + FeatureCollection: function(object, listener) { + var features = object.features, i = -1, n = features.length; + while (++i < n) d3_geo_streamGeometry(features[i].geometry, listener); + } + }; + var d3_geo_streamGeometryType = { + Sphere: function(object, listener) { + listener.sphere(); + }, + Point: function(object, listener) { + object = object.coordinates; + listener.point(object[0], object[1], object[2]); + }, + MultiPoint: function(object, listener) { + var coordinates = object.coordinates, i = -1, n = coordinates.length; + while (++i < n) object = coordinates[i], listener.point(object[0], object[1], object[2]); + }, + LineString: function(object, listener) { + d3_geo_streamLine(object.coordinates, listener, 0); + }, + MultiLineString: function(object, listener) { + var coordinates = object.coordinates, i = -1, n = coordinates.length; + while (++i < n) d3_geo_streamLine(coordinates[i], listener, 0); + }, + Polygon: function(object, listener) { + d3_geo_streamPolygon(object.coordinates, listener); + }, + MultiPolygon: function(object, listener) { + var coordinates = object.coordinates, i = -1, n = coordinates.length; + while (++i < n) d3_geo_streamPolygon(coordinates[i], listener); + }, + GeometryCollection: function(object, listener) { + var geometries = object.geometries, i = -1, n = geometries.length; + while (++i < n) d3_geo_streamGeometry(geometries[i], listener); + } + }; + function d3_geo_streamLine(coordinates, listener, closed) { + var i = -1, n = coordinates.length - closed, coordinate; + listener.lineStart(); + while (++i < n) coordinate = coordinates[i], listener.point(coordinate[0], coordinate[1], coordinate[2]); + listener.lineEnd(); + } + function d3_geo_streamPolygon(coordinates, listener) { + var i = -1, n = coordinates.length; + listener.polygonStart(); + while (++i < n) d3_geo_streamLine(coordinates[i], listener, 1); + listener.polygonEnd(); + } + d3.geo.area = function(object) { + d3_geo_areaSum = 0; + d3.geo.stream(object, d3_geo_area); + return d3_geo_areaSum; + }; + var d3_geo_areaSum, d3_geo_areaRingSum = new d3_adder(); + var d3_geo_area = { + sphere: function() { + d3_geo_areaSum += 4 * π; + }, + point: d3_noop, + lineStart: d3_noop, + lineEnd: d3_noop, + polygonStart: function() { + d3_geo_areaRingSum.reset(); + d3_geo_area.lineStart = d3_geo_areaRingStart; + }, + polygonEnd: function() { + var area = 2 * d3_geo_areaRingSum; + d3_geo_areaSum += area < 0 ? 4 * π + area : area; + d3_geo_area.lineStart = d3_geo_area.lineEnd = d3_geo_area.point = d3_noop; + } + }; + function d3_geo_areaRingStart() { + var λ00, φ00, λ0, cosφ0, sinφ0; + d3_geo_area.point = function(λ, φ) { + d3_geo_area.point = nextPoint; + λ0 = (λ00 = λ) * d3_radians, cosφ0 = Math.cos(φ = (φ00 = φ) * d3_radians / 2 + π / 4), + sinφ0 = Math.sin(φ); + }; + function nextPoint(λ, φ) { + λ *= d3_radians; + φ = φ * d3_radians / 2 + π / 4; + var dλ = λ - λ0, sdλ = dλ >= 0 ? 1 : -1, adλ = sdλ * dλ, cosφ = Math.cos(φ), sinφ = Math.sin(φ), k = sinφ0 * sinφ, u = cosφ0 * cosφ + k * Math.cos(adλ), v = k * sdλ * Math.sin(adλ); + d3_geo_areaRingSum.add(Math.atan2(v, u)); + λ0 = λ, cosφ0 = cosφ, sinφ0 = sinφ; + } + d3_geo_area.lineEnd = function() { + nextPoint(λ00, φ00); + }; + } + function d3_geo_cartesian(spherical) { + var λ = spherical[0], φ = spherical[1], cosφ = Math.cos(φ); + return [ cosφ * Math.cos(λ), cosφ * Math.sin(λ), Math.sin(φ) ]; + } + function d3_geo_cartesianDot(a, b) { + return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]; + } + function d3_geo_cartesianCross(a, b) { + return [ a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0] ]; + } + function d3_geo_cartesianAdd(a, b) { + a[0] += b[0]; + a[1] += b[1]; + a[2] += b[2]; + } + function d3_geo_cartesianScale(vector, k) { + return [ vector[0] * k, vector[1] * k, vector[2] * k ]; + } + function d3_geo_cartesianNormalize(d) { + var l = Math.sqrt(d[0] * d[0] + d[1] * d[1] + d[2] * d[2]); + d[0] /= l; + d[1] /= l; + d[2] /= l; + } + function d3_geo_spherical(cartesian) { + return [ Math.atan2(cartesian[1], cartesian[0]), d3_asin(cartesian[2]) ]; + } + function d3_geo_sphericalEqual(a, b) { + return abs(a[0] - b[0]) < ε && abs(a[1] - b[1]) < ε; + } + d3.geo.bounds = function() { + var λ0, φ0, λ1, φ1, λ_, λ__, φ__, p0, dλSum, ranges, range; + var bound = { + point: point, + lineStart: lineStart, + lineEnd: lineEnd, + polygonStart: function() { + bound.point = ringPoint; + bound.lineStart = ringStart; + bound.lineEnd = ringEnd; + dλSum = 0; + d3_geo_area.polygonStart(); + }, + polygonEnd: function() { + d3_geo_area.polygonEnd(); + bound.point = point; + bound.lineStart = lineStart; + bound.lineEnd = lineEnd; + if (d3_geo_areaRingSum < 0) λ0 = -(λ1 = 180), φ0 = -(φ1 = 90); else if (dλSum > ε) φ1 = 90; else if (dλSum < -ε) φ0 = -90; + range[0] = λ0, range[1] = λ1; + } + }; + function point(λ, φ) { + ranges.push(range = [ λ0 = λ, λ1 = λ ]); + if (φ < φ0) φ0 = φ; + if (φ > φ1) φ1 = φ; + } + function linePoint(λ, φ) { + var p = d3_geo_cartesian([ λ * d3_radians, φ * d3_radians ]); + if (p0) { + var normal = d3_geo_cartesianCross(p0, p), equatorial = [ normal[1], -normal[0], 0 ], inflection = d3_geo_cartesianCross(equatorial, normal); + d3_geo_cartesianNormalize(inflection); + inflection = d3_geo_spherical(inflection); + var dλ = λ - λ_, s = dλ > 0 ? 1 : -1, λi = inflection[0] * d3_degrees * s, antimeridian = abs(dλ) > 180; + if (antimeridian ^ (s * λ_ < λi && λi < s * λ)) { + var φi = inflection[1] * d3_degrees; + if (φi > φ1) φ1 = φi; + } else if (λi = (λi + 360) % 360 - 180, antimeridian ^ (s * λ_ < λi && λi < s * λ)) { + var φi = -inflection[1] * d3_degrees; + if (φi < φ0) φ0 = φi; + } else { + if (φ < φ0) φ0 = φ; + if (φ > φ1) φ1 = φ; + } + if (antimeridian) { + if (λ < λ_) { + if (angle(λ0, λ) > angle(λ0, λ1)) λ1 = λ; + } else { + if (angle(λ, λ1) > angle(λ0, λ1)) λ0 = λ; + } + } else { + if (λ1 >= λ0) { + if (λ < λ0) λ0 = λ; + if (λ > λ1) λ1 = λ; + } else { + if (λ > λ_) { + if (angle(λ0, λ) > angle(λ0, λ1)) λ1 = λ; + } else { + if (angle(λ, λ1) > angle(λ0, λ1)) λ0 = λ; + } + } + } + } else { + point(λ, φ); + } + p0 = p, λ_ = λ; + } + function lineStart() { + bound.point = linePoint; + } + function lineEnd() { + range[0] = λ0, range[1] = λ1; + bound.point = point; + p0 = null; + } + function ringPoint(λ, φ) { + if (p0) { + var dλ = λ - λ_; + dλSum += abs(dλ) > 180 ? dλ + (dλ > 0 ? 360 : -360) : dλ; + } else λ__ = λ, φ__ = φ; + d3_geo_area.point(λ, φ); + linePoint(λ, φ); + } + function ringStart() { + d3_geo_area.lineStart(); + } + function ringEnd() { + ringPoint(λ__, φ__); + d3_geo_area.lineEnd(); + if (abs(dλSum) > ε) λ0 = -(λ1 = 180); + range[0] = λ0, range[1] = λ1; + p0 = null; + } + function angle(λ0, λ1) { + return (λ1 -= λ0) < 0 ? λ1 + 360 : λ1; + } + function compareRanges(a, b) { + return a[0] - b[0]; + } + function withinRange(x, range) { + return range[0] <= range[1] ? range[0] <= x && x <= range[1] : x < range[0] || range[1] < x; + } + return function(feature) { + φ1 = λ1 = -(λ0 = φ0 = Infinity); + ranges = []; + d3.geo.stream(feature, bound); + var n = ranges.length; + if (n) { + ranges.sort(compareRanges); + for (var i = 1, a = ranges[0], b, merged = [ a ]; i < n; ++i) { + b = ranges[i]; + if (withinRange(b[0], a) || withinRange(b[1], a)) { + if (angle(a[0], b[1]) > angle(a[0], a[1])) a[1] = b[1]; + if (angle(b[0], a[1]) > angle(a[0], a[1])) a[0] = b[0]; + } else { + merged.push(a = b); + } + } + var best = -Infinity, dλ; + for (var n = merged.length - 1, i = 0, a = merged[n], b; i <= n; a = b, ++i) { + b = merged[i]; + if ((dλ = angle(a[1], b[0])) > best) best = dλ, λ0 = b[0], λ1 = a[1]; + } + } + ranges = range = null; + return λ0 === Infinity || φ0 === Infinity ? [ [ NaN, NaN ], [ NaN, NaN ] ] : [ [ λ0, φ0 ], [ λ1, φ1 ] ]; + }; + }(); + d3.geo.centroid = function(object) { + d3_geo_centroidW0 = d3_geo_centroidW1 = d3_geo_centroidX0 = d3_geo_centroidY0 = d3_geo_centroidZ0 = d3_geo_centroidX1 = d3_geo_centroidY1 = d3_geo_centroidZ1 = d3_geo_centroidX2 = d3_geo_centroidY2 = d3_geo_centroidZ2 = 0; + d3.geo.stream(object, d3_geo_centroid); + var x = d3_geo_centroidX2, y = d3_geo_centroidY2, z = d3_geo_centroidZ2, m = x * x + y * y + z * z; + if (m < ε2) { + x = d3_geo_centroidX1, y = d3_geo_centroidY1, z = d3_geo_centroidZ1; + if (d3_geo_centroidW1 < ε) x = d3_geo_centroidX0, y = d3_geo_centroidY0, z = d3_geo_centroidZ0; + m = x * x + y * y + z * z; + if (m < ε2) return [ NaN, NaN ]; + } + return [ Math.atan2(y, x) * d3_degrees, d3_asin(z / Math.sqrt(m)) * d3_degrees ]; + }; + var d3_geo_centroidW0, d3_geo_centroidW1, d3_geo_centroidX0, d3_geo_centroidY0, d3_geo_centroidZ0, d3_geo_centroidX1, d3_geo_centroidY1, d3_geo_centroidZ1, d3_geo_centroidX2, d3_geo_centroidY2, d3_geo_centroidZ2; + var d3_geo_centroid = { + sphere: d3_noop, + point: d3_geo_centroidPoint, + lineStart: d3_geo_centroidLineStart, + lineEnd: d3_geo_centroidLineEnd, + polygonStart: function() { + d3_geo_centroid.lineStart = d3_geo_centroidRingStart; + }, + polygonEnd: function() { + d3_geo_centroid.lineStart = d3_geo_centroidLineStart; + } + }; + function d3_geo_centroidPoint(λ, φ) { + λ *= d3_radians; + var cosφ = Math.cos(φ *= d3_radians); + d3_geo_centroidPointXYZ(cosφ * Math.cos(λ), cosφ * Math.sin(λ), Math.sin(φ)); + } + function d3_geo_centroidPointXYZ(x, y, z) { + ++d3_geo_centroidW0; + d3_geo_centroidX0 += (x - d3_geo_centroidX0) / d3_geo_centroidW0; + d3_geo_centroidY0 += (y - d3_geo_centroidY0) / d3_geo_centroidW0; + d3_geo_centroidZ0 += (z - d3_geo_centroidZ0) / d3_geo_centroidW0; + } + function d3_geo_centroidLineStart() { + var x0, y0, z0; + d3_geo_centroid.point = function(λ, φ) { + λ *= d3_radians; + var cosφ = Math.cos(φ *= d3_radians); + x0 = cosφ * Math.cos(λ); + y0 = cosφ * Math.sin(λ); + z0 = Math.sin(φ); + d3_geo_centroid.point = nextPoint; + d3_geo_centroidPointXYZ(x0, y0, z0); + }; + function nextPoint(λ, φ) { + λ *= d3_radians; + var cosφ = Math.cos(φ *= d3_radians), x = cosφ * Math.cos(λ), y = cosφ * Math.sin(λ), z = Math.sin(φ), w = Math.atan2(Math.sqrt((w = y0 * z - z0 * y) * w + (w = z0 * x - x0 * z) * w + (w = x0 * y - y0 * x) * w), x0 * x + y0 * y + z0 * z); + d3_geo_centroidW1 += w; + d3_geo_centroidX1 += w * (x0 + (x0 = x)); + d3_geo_centroidY1 += w * (y0 + (y0 = y)); + d3_geo_centroidZ1 += w * (z0 + (z0 = z)); + d3_geo_centroidPointXYZ(x0, y0, z0); + } + } + function d3_geo_centroidLineEnd() { + d3_geo_centroid.point = d3_geo_centroidPoint; + } + function d3_geo_centroidRingStart() { + var λ00, φ00, x0, y0, z0; + d3_geo_centroid.point = function(λ, φ) { + λ00 = λ, φ00 = φ; + d3_geo_centroid.point = nextPoint; + λ *= d3_radians; + var cosφ = Math.cos(φ *= d3_radians); + x0 = cosφ * Math.cos(λ); + y0 = cosφ * Math.sin(λ); + z0 = Math.sin(φ); + d3_geo_centroidPointXYZ(x0, y0, z0); + }; + d3_geo_centroid.lineEnd = function() { + nextPoint(λ00, φ00); + d3_geo_centroid.lineEnd = d3_geo_centroidLineEnd; + d3_geo_centroid.point = d3_geo_centroidPoint; + }; + function nextPoint(λ, φ) { + λ *= d3_radians; + var cosφ = Math.cos(φ *= d3_radians), x = cosφ * Math.cos(λ), y = cosφ * Math.sin(λ), z = Math.sin(φ), cx = y0 * z - z0 * y, cy = z0 * x - x0 * z, cz = x0 * y - y0 * x, m = Math.sqrt(cx * cx + cy * cy + cz * cz), u = x0 * x + y0 * y + z0 * z, v = m && -d3_acos(u) / m, w = Math.atan2(m, u); + d3_geo_centroidX2 += v * cx; + d3_geo_centroidY2 += v * cy; + d3_geo_centroidZ2 += v * cz; + d3_geo_centroidW1 += w; + d3_geo_centroidX1 += w * (x0 + (x0 = x)); + d3_geo_centroidY1 += w * (y0 + (y0 = y)); + d3_geo_centroidZ1 += w * (z0 + (z0 = z)); + d3_geo_centroidPointXYZ(x0, y0, z0); + } + } + function d3_geo_compose(a, b) { + function compose(x, y) { + return x = a(x, y), b(x[0], x[1]); + } + if (a.invert && b.invert) compose.invert = function(x, y) { + return x = b.invert(x, y), x && a.invert(x[0], x[1]); + }; + return compose; + } + function d3_true() { + return true; + } + function d3_geo_clipPolygon(segments, compare, clipStartInside, interpolate, listener) { + var subject = [], clip = []; + segments.forEach(function(segment) { + if ((n = segment.length - 1) <= 0) return; + var n, p0 = segment[0], p1 = segment[n]; + if (d3_geo_sphericalEqual(p0, p1)) { + listener.lineStart(); + for (var i = 0; i < n; ++i) listener.point((p0 = segment[i])[0], p0[1]); + listener.lineEnd(); + return; + } + var a = new d3_geo_clipPolygonIntersection(p0, segment, null, true), b = new d3_geo_clipPolygonIntersection(p0, null, a, false); + a.o = b; + subject.push(a); + clip.push(b); + a = new d3_geo_clipPolygonIntersection(p1, segment, null, false); + b = new d3_geo_clipPolygonIntersection(p1, null, a, true); + a.o = b; + subject.push(a); + clip.push(b); + }); + clip.sort(compare); + d3_geo_clipPolygonLinkCircular(subject); + d3_geo_clipPolygonLinkCircular(clip); + if (!subject.length) return; + for (var i = 0, entry = clipStartInside, n = clip.length; i < n; ++i) { + clip[i].e = entry = !entry; + } + var start = subject[0], points, point; + while (1) { + var current = start, isSubject = true; + while (current.v) if ((current = current.n) === start) return; + points = current.z; + listener.lineStart(); + do { + current.v = current.o.v = true; + if (current.e) { + if (isSubject) { + for (var i = 0, n = points.length; i < n; ++i) listener.point((point = points[i])[0], point[1]); + } else { + interpolate(current.x, current.n.x, 1, listener); + } + current = current.n; + } else { + if (isSubject) { + points = current.p.z; + for (var i = points.length - 1; i >= 0; --i) listener.point((point = points[i])[0], point[1]); + } else { + interpolate(current.x, current.p.x, -1, listener); + } + current = current.p; + } + current = current.o; + points = current.z; + isSubject = !isSubject; + } while (!current.v); + listener.lineEnd(); + } + } + function d3_geo_clipPolygonLinkCircular(array) { + if (!(n = array.length)) return; + var n, i = 0, a = array[0], b; + while (++i < n) { + a.n = b = array[i]; + b.p = a; + a = b; + } + a.n = b = array[0]; + b.p = a; + } + function d3_geo_clipPolygonIntersection(point, points, other, entry) { + this.x = point; + this.z = points; + this.o = other; + this.e = entry; + this.v = false; + this.n = this.p = null; + } + function d3_geo_clip(pointVisible, clipLine, interpolate, clipStart) { + return function(rotate, listener) { + var line = clipLine(listener), rotatedClipStart = rotate.invert(clipStart[0], clipStart[1]); + var clip = { + point: point, + lineStart: lineStart, + lineEnd: lineEnd, + polygonStart: function() { + clip.point = pointRing; + clip.lineStart = ringStart; + clip.lineEnd = ringEnd; + segments = []; + polygon = []; + }, + polygonEnd: function() { + clip.point = point; + clip.lineStart = lineStart; + clip.lineEnd = lineEnd; + segments = d3.merge(segments); + var clipStartInside = d3_geo_pointInPolygon(rotatedClipStart, polygon); + if (segments.length) { + if (!polygonStarted) listener.polygonStart(), polygonStarted = true; + d3_geo_clipPolygon(segments, d3_geo_clipSort, clipStartInside, interpolate, listener); + } else if (clipStartInside) { + if (!polygonStarted) listener.polygonStart(), polygonStarted = true; + listener.lineStart(); + interpolate(null, null, 1, listener); + listener.lineEnd(); + } + if (polygonStarted) listener.polygonEnd(), polygonStarted = false; + segments = polygon = null; + }, + sphere: function() { + listener.polygonStart(); + listener.lineStart(); + interpolate(null, null, 1, listener); + listener.lineEnd(); + listener.polygonEnd(); + } + }; + function point(λ, φ) { + var point = rotate(λ, φ); + if (pointVisible(λ = point[0], φ = point[1])) listener.point(λ, φ); + } + function pointLine(λ, φ) { + var point = rotate(λ, φ); + line.point(point[0], point[1]); + } + function lineStart() { + clip.point = pointLine; + line.lineStart(); + } + function lineEnd() { + clip.point = point; + line.lineEnd(); + } + var segments; + var buffer = d3_geo_clipBufferListener(), ringListener = clipLine(buffer), polygonStarted = false, polygon, ring; + function pointRing(λ, φ) { + ring.push([ λ, φ ]); + var point = rotate(λ, φ); + ringListener.point(point[0], point[1]); + } + function ringStart() { + ringListener.lineStart(); + ring = []; + } + function ringEnd() { + pointRing(ring[0][0], ring[0][1]); + ringListener.lineEnd(); + var clean = ringListener.clean(), ringSegments = buffer.buffer(), segment, n = ringSegments.length; + ring.pop(); + polygon.push(ring); + ring = null; + if (!n) return; + if (clean & 1) { + segment = ringSegments[0]; + var n = segment.length - 1, i = -1, point; + if (n > 0) { + if (!polygonStarted) listener.polygonStart(), polygonStarted = true; + listener.lineStart(); + while (++i < n) listener.point((point = segment[i])[0], point[1]); + listener.lineEnd(); + } + return; + } + if (n > 1 && clean & 2) ringSegments.push(ringSegments.pop().concat(ringSegments.shift())); + segments.push(ringSegments.filter(d3_geo_clipSegmentLength1)); + } + return clip; + }; + } + function d3_geo_clipSegmentLength1(segment) { + return segment.length > 1; + } + function d3_geo_clipBufferListener() { + var lines = [], line; + return { + lineStart: function() { + lines.push(line = []); + }, + point: function(λ, φ) { + line.push([ λ, φ ]); + }, + lineEnd: d3_noop, + buffer: function() { + var buffer = lines; + lines = []; + line = null; + return buffer; + }, + rejoin: function() { + if (lines.length > 1) lines.push(lines.pop().concat(lines.shift())); + } + }; + } + function d3_geo_clipSort(a, b) { + return ((a = a.x)[0] < 0 ? a[1] - halfπ - ε : halfπ - a[1]) - ((b = b.x)[0] < 0 ? b[1] - halfπ - ε : halfπ - b[1]); + } + var d3_geo_clipAntimeridian = d3_geo_clip(d3_true, d3_geo_clipAntimeridianLine, d3_geo_clipAntimeridianInterpolate, [ -π, -π / 2 ]); + function d3_geo_clipAntimeridianLine(listener) { + var λ0 = NaN, φ0 = NaN, sλ0 = NaN, clean; + return { + lineStart: function() { + listener.lineStart(); + clean = 1; + }, + point: function(λ1, φ1) { + var sλ1 = λ1 > 0 ? π : -π, dλ = abs(λ1 - λ0); + if (abs(dλ - π) < ε) { + listener.point(λ0, φ0 = (φ0 + φ1) / 2 > 0 ? halfπ : -halfπ); + listener.point(sλ0, φ0); + listener.lineEnd(); + listener.lineStart(); + listener.point(sλ1, φ0); + listener.point(λ1, φ0); + clean = 0; + } else if (sλ0 !== sλ1 && dλ >= π) { + if (abs(λ0 - sλ0) < ε) λ0 -= sλ0 * ε; + if (abs(λ1 - sλ1) < ε) λ1 -= sλ1 * ε; + φ0 = d3_geo_clipAntimeridianIntersect(λ0, φ0, λ1, φ1); + listener.point(sλ0, φ0); + listener.lineEnd(); + listener.lineStart(); + listener.point(sλ1, φ0); + clean = 0; + } + listener.point(λ0 = λ1, φ0 = φ1); + sλ0 = sλ1; + }, + lineEnd: function() { + listener.lineEnd(); + λ0 = φ0 = NaN; + }, + clean: function() { + return 2 - clean; + } + }; + } + function d3_geo_clipAntimeridianIntersect(λ0, φ0, λ1, φ1) { + var cosφ0, cosφ1, sinλ0_λ1 = Math.sin(λ0 - λ1); + return abs(sinλ0_λ1) > ε ? Math.atan((Math.sin(φ0) * (cosφ1 = Math.cos(φ1)) * Math.sin(λ1) - Math.sin(φ1) * (cosφ0 = Math.cos(φ0)) * Math.sin(λ0)) / (cosφ0 * cosφ1 * sinλ0_λ1)) : (φ0 + φ1) / 2; + } + function d3_geo_clipAntimeridianInterpolate(from, to, direction, listener) { + var φ; + if (from == null) { + φ = direction * halfπ; + listener.point(-π, φ); + listener.point(0, φ); + listener.point(π, φ); + listener.point(π, 0); + listener.point(π, -φ); + listener.point(0, -φ); + listener.point(-π, -φ); + listener.point(-π, 0); + listener.point(-π, φ); + } else if (abs(from[0] - to[0]) > ε) { + var s = from[0] < to[0] ? π : -π; + φ = direction * s / 2; + listener.point(-s, φ); + listener.point(0, φ); + listener.point(s, φ); + } else { + listener.point(to[0], to[1]); + } + } + function d3_geo_pointInPolygon(point, polygon) { + var meridian = point[0], parallel = point[1], meridianNormal = [ Math.sin(meridian), -Math.cos(meridian), 0 ], polarAngle = 0, winding = 0; + d3_geo_areaRingSum.reset(); + for (var i = 0, n = polygon.length; i < n; ++i) { + var ring = polygon[i], m = ring.length; + if (!m) continue; + var point0 = ring[0], λ0 = point0[0], φ0 = point0[1] / 2 + π / 4, sinφ0 = Math.sin(φ0), cosφ0 = Math.cos(φ0), j = 1; + while (true) { + if (j === m) j = 0; + point = ring[j]; + var λ = point[0], φ = point[1] / 2 + π / 4, sinφ = Math.sin(φ), cosφ = Math.cos(φ), dλ = λ - λ0, sdλ = dλ >= 0 ? 1 : -1, adλ = sdλ * dλ, antimeridian = adλ > π, k = sinφ0 * sinφ; + d3_geo_areaRingSum.add(Math.atan2(k * sdλ * Math.sin(adλ), cosφ0 * cosφ + k * Math.cos(adλ))); + polarAngle += antimeridian ? dλ + sdλ * τ : dλ; + if (antimeridian ^ λ0 >= meridian ^ λ >= meridian) { + var arc = d3_geo_cartesianCross(d3_geo_cartesian(point0), d3_geo_cartesian(point)); + d3_geo_cartesianNormalize(arc); + var intersection = d3_geo_cartesianCross(meridianNormal, arc); + d3_geo_cartesianNormalize(intersection); + var φarc = (antimeridian ^ dλ >= 0 ? -1 : 1) * d3_asin(intersection[2]); + if (parallel > φarc || parallel === φarc && (arc[0] || arc[1])) { + winding += antimeridian ^ dλ >= 0 ? 1 : -1; + } + } + if (!j++) break; + λ0 = λ, sinφ0 = sinφ, cosφ0 = cosφ, point0 = point; + } + } + return (polarAngle < -ε || polarAngle < ε && d3_geo_areaRingSum < 0) ^ winding & 1; + } + function d3_geo_clipCircle(radius) { + var cr = Math.cos(radius), smallRadius = cr > 0, notHemisphere = abs(cr) > ε, interpolate = d3_geo_circleInterpolate(radius, 6 * d3_radians); + return d3_geo_clip(visible, clipLine, interpolate, smallRadius ? [ 0, -radius ] : [ -π, radius - π ]); + function visible(λ, φ) { + return Math.cos(λ) * Math.cos(φ) > cr; + } + function clipLine(listener) { + var point0, c0, v0, v00, clean; + return { + lineStart: function() { + v00 = v0 = false; + clean = 1; + }, + point: function(λ, φ) { + var point1 = [ λ, φ ], point2, v = visible(λ, φ), c = smallRadius ? v ? 0 : code(λ, φ) : v ? code(λ + (λ < 0 ? π : -π), φ) : 0; + if (!point0 && (v00 = v0 = v)) listener.lineStart(); + if (v !== v0) { + point2 = intersect(point0, point1); + if (d3_geo_sphericalEqual(point0, point2) || d3_geo_sphericalEqual(point1, point2)) { + point1[0] += ε; + point1[1] += ε; + v = visible(point1[0], point1[1]); + } + } + if (v !== v0) { + clean = 0; + if (v) { + listener.lineStart(); + point2 = intersect(point1, point0); + listener.point(point2[0], point2[1]); + } else { + point2 = intersect(point0, point1); + listener.point(point2[0], point2[1]); + listener.lineEnd(); + } + point0 = point2; + } else if (notHemisphere && point0 && smallRadius ^ v) { + var t; + if (!(c & c0) && (t = intersect(point1, point0, true))) { + clean = 0; + if (smallRadius) { + listener.lineStart(); + listener.point(t[0][0], t[0][1]); + listener.point(t[1][0], t[1][1]); + listener.lineEnd(); + } else { + listener.point(t[1][0], t[1][1]); + listener.lineEnd(); + listener.lineStart(); + listener.point(t[0][0], t[0][1]); + } + } + } + if (v && (!point0 || !d3_geo_sphericalEqual(point0, point1))) { + listener.point(point1[0], point1[1]); + } + point0 = point1, v0 = v, c0 = c; + }, + lineEnd: function() { + if (v0) listener.lineEnd(); + point0 = null; + }, + clean: function() { + return clean | (v00 && v0) << 1; + } + }; + } + function intersect(a, b, two) { + var pa = d3_geo_cartesian(a), pb = d3_geo_cartesian(b); + var n1 = [ 1, 0, 0 ], n2 = d3_geo_cartesianCross(pa, pb), n2n2 = d3_geo_cartesianDot(n2, n2), n1n2 = n2[0], determinant = n2n2 - n1n2 * n1n2; + if (!determinant) return !two && a; + var c1 = cr * n2n2 / determinant, c2 = -cr * n1n2 / determinant, n1xn2 = d3_geo_cartesianCross(n1, n2), A = d3_geo_cartesianScale(n1, c1), B = d3_geo_cartesianScale(n2, c2); + d3_geo_cartesianAdd(A, B); + var u = n1xn2, w = d3_geo_cartesianDot(A, u), uu = d3_geo_cartesianDot(u, u), t2 = w * w - uu * (d3_geo_cartesianDot(A, A) - 1); + if (t2 < 0) return; + var t = Math.sqrt(t2), q = d3_geo_cartesianScale(u, (-w - t) / uu); + d3_geo_cartesianAdd(q, A); + q = d3_geo_spherical(q); + if (!two) return q; + var λ0 = a[0], λ1 = b[0], φ0 = a[1], φ1 = b[1], z; + if (λ1 < λ0) z = λ0, λ0 = λ1, λ1 = z; + var δλ = λ1 - λ0, polar = abs(δλ - π) < ε, meridian = polar || δλ < ε; + if (!polar && φ1 < φ0) z = φ0, φ0 = φ1, φ1 = z; + if (meridian ? polar ? φ0 + φ1 > 0 ^ q[1] < (abs(q[0] - λ0) < ε ? φ0 : φ1) : φ0 <= q[1] && q[1] <= φ1 : δλ > π ^ (λ0 <= q[0] && q[0] <= λ1)) { + var q1 = d3_geo_cartesianScale(u, (-w + t) / uu); + d3_geo_cartesianAdd(q1, A); + return [ q, d3_geo_spherical(q1) ]; + } + } + function code(λ, φ) { + var r = smallRadius ? radius : π - radius, code = 0; + if (λ < -r) code |= 1; else if (λ > r) code |= 2; + if (φ < -r) code |= 4; else if (φ > r) code |= 8; + return code; + } + } + function d3_geom_clipLine(x0, y0, x1, y1) { + return function(line) { + var a = line.a, b = line.b, ax = a.x, ay = a.y, bx = b.x, by = b.y, t0 = 0, t1 = 1, dx = bx - ax, dy = by - ay, r; + r = x0 - ax; + if (!dx && r > 0) return; + r /= dx; + if (dx < 0) { + if (r < t0) return; + if (r < t1) t1 = r; + } else if (dx > 0) { + if (r > t1) return; + if (r > t0) t0 = r; + } + r = x1 - ax; + if (!dx && r < 0) return; + r /= dx; + if (dx < 0) { + if (r > t1) return; + if (r > t0) t0 = r; + } else if (dx > 0) { + if (r < t0) return; + if (r < t1) t1 = r; + } + r = y0 - ay; + if (!dy && r > 0) return; + r /= dy; + if (dy < 0) { + if (r < t0) return; + if (r < t1) t1 = r; + } else if (dy > 0) { + if (r > t1) return; + if (r > t0) t0 = r; + } + r = y1 - ay; + if (!dy && r < 0) return; + r /= dy; + if (dy < 0) { + if (r > t1) return; + if (r > t0) t0 = r; + } else if (dy > 0) { + if (r < t0) return; + if (r < t1) t1 = r; + } + if (t0 > 0) line.a = { + x: ax + t0 * dx, + y: ay + t0 * dy + }; + if (t1 < 1) line.b = { + x: ax + t1 * dx, + y: ay + t1 * dy + }; + return line; + }; + } + var d3_geo_clipExtentMAX = 1e9; + d3.geo.clipExtent = function() { + var x0, y0, x1, y1, stream, clip, clipExtent = { + stream: function(output) { + if (stream) stream.valid = false; + stream = clip(output); + stream.valid = true; + return stream; + }, + extent: function(_) { + if (!arguments.length) return [ [ x0, y0 ], [ x1, y1 ] ]; + clip = d3_geo_clipExtent(x0 = +_[0][0], y0 = +_[0][1], x1 = +_[1][0], y1 = +_[1][1]); + if (stream) stream.valid = false, stream = null; + return clipExtent; + } + }; + return clipExtent.extent([ [ 0, 0 ], [ 960, 500 ] ]); + }; + function d3_geo_clipExtent(x0, y0, x1, y1) { + return function(listener) { + var listener_ = listener, bufferListener = d3_geo_clipBufferListener(), clipLine = d3_geom_clipLine(x0, y0, x1, y1), segments, polygon, ring; + var clip = { + point: point, + lineStart: lineStart, + lineEnd: lineEnd, + polygonStart: function() { + listener = bufferListener; + segments = []; + polygon = []; + clean = true; + }, + polygonEnd: function() { + listener = listener_; + segments = d3.merge(segments); + var clipStartInside = insidePolygon([ x0, y1 ]), inside = clean && clipStartInside, visible = segments.length; + if (inside || visible) { + listener.polygonStart(); + if (inside) { + listener.lineStart(); + interpolate(null, null, 1, listener); + listener.lineEnd(); + } + if (visible) { + d3_geo_clipPolygon(segments, compare, clipStartInside, interpolate, listener); + } + listener.polygonEnd(); + } + segments = polygon = ring = null; + } + }; + function insidePolygon(p) { + var wn = 0, n = polygon.length, y = p[1]; + for (var i = 0; i < n; ++i) { + for (var j = 1, v = polygon[i], m = v.length, a = v[0], b; j < m; ++j) { + b = v[j]; + if (a[1] <= y) { + if (b[1] > y && d3_cross2d(a, b, p) > 0) ++wn; + } else { + if (b[1] <= y && d3_cross2d(a, b, p) < 0) --wn; + } + a = b; + } + } + return wn !== 0; + } + function interpolate(from, to, direction, listener) { + var a = 0, a1 = 0; + if (from == null || (a = corner(from, direction)) !== (a1 = corner(to, direction)) || comparePoints(from, to) < 0 ^ direction > 0) { + do { + listener.point(a === 0 || a === 3 ? x0 : x1, a > 1 ? y1 : y0); + } while ((a = (a + direction + 4) % 4) !== a1); + } else { + listener.point(to[0], to[1]); + } + } + function pointVisible(x, y) { + return x0 <= x && x <= x1 && y0 <= y && y <= y1; + } + function point(x, y) { + if (pointVisible(x, y)) listener.point(x, y); + } + var x__, y__, v__, x_, y_, v_, first, clean; + function lineStart() { + clip.point = linePoint; + if (polygon) polygon.push(ring = []); + first = true; + v_ = false; + x_ = y_ = NaN; + } + function lineEnd() { + if (segments) { + linePoint(x__, y__); + if (v__ && v_) bufferListener.rejoin(); + segments.push(bufferListener.buffer()); + } + clip.point = point; + if (v_) listener.lineEnd(); + } + function linePoint(x, y) { + x = Math.max(-d3_geo_clipExtentMAX, Math.min(d3_geo_clipExtentMAX, x)); + y = Math.max(-d3_geo_clipExtentMAX, Math.min(d3_geo_clipExtentMAX, y)); + var v = pointVisible(x, y); + if (polygon) ring.push([ x, y ]); + if (first) { + x__ = x, y__ = y, v__ = v; + first = false; + if (v) { + listener.lineStart(); + listener.point(x, y); + } + } else { + if (v && v_) listener.point(x, y); else { + var l = { + a: { + x: x_, + y: y_ + }, + b: { + x: x, + y: y + } + }; + if (clipLine(l)) { + if (!v_) { + listener.lineStart(); + listener.point(l.a.x, l.a.y); + } + listener.point(l.b.x, l.b.y); + if (!v) listener.lineEnd(); + clean = false; + } else if (v) { + listener.lineStart(); + listener.point(x, y); + clean = false; + } + } + } + x_ = x, y_ = y, v_ = v; + } + return clip; + }; + function corner(p, direction) { + return abs(p[0] - x0) < ε ? direction > 0 ? 0 : 3 : abs(p[0] - x1) < ε ? direction > 0 ? 2 : 1 : abs(p[1] - y0) < ε ? direction > 0 ? 1 : 0 : direction > 0 ? 3 : 2; + } + function compare(a, b) { + return comparePoints(a.x, b.x); + } + function comparePoints(a, b) { + var ca = corner(a, 1), cb = corner(b, 1); + return ca !== cb ? ca - cb : ca === 0 ? b[1] - a[1] : ca === 1 ? a[0] - b[0] : ca === 2 ? a[1] - b[1] : b[0] - a[0]; + } + } + function d3_geo_conic(projectAt) { + var φ0 = 0, φ1 = π / 3, m = d3_geo_projectionMutator(projectAt), p = m(φ0, φ1); + p.parallels = function(_) { + if (!arguments.length) return [ φ0 / π * 180, φ1 / π * 180 ]; + return m(φ0 = _[0] * π / 180, φ1 = _[1] * π / 180); + }; + return p; + } + function d3_geo_conicEqualArea(φ0, φ1) { + var sinφ0 = Math.sin(φ0), n = (sinφ0 + Math.sin(φ1)) / 2, C = 1 + sinφ0 * (2 * n - sinφ0), ρ0 = Math.sqrt(C) / n; + function forward(λ, φ) { + var ρ = Math.sqrt(C - 2 * n * Math.sin(φ)) / n; + return [ ρ * Math.sin(λ *= n), ρ0 - ρ * Math.cos(λ) ]; + } + forward.invert = function(x, y) { + var ρ0_y = ρ0 - y; + return [ Math.atan2(x, ρ0_y) / n, d3_asin((C - (x * x + ρ0_y * ρ0_y) * n * n) / (2 * n)) ]; + }; + return forward; + } + (d3.geo.conicEqualArea = function() { + return d3_geo_conic(d3_geo_conicEqualArea); + }).raw = d3_geo_conicEqualArea; + d3.geo.albers = function() { + return d3.geo.conicEqualArea().rotate([ 96, 0 ]).center([ -.6, 38.7 ]).parallels([ 29.5, 45.5 ]).scale(1070); + }; + d3.geo.albersUsa = function() { + var lower48 = d3.geo.albers(); + var alaska = d3.geo.conicEqualArea().rotate([ 154, 0 ]).center([ -2, 58.5 ]).parallels([ 55, 65 ]); + var hawaii = d3.geo.conicEqualArea().rotate([ 157, 0 ]).center([ -3, 19.9 ]).parallels([ 8, 18 ]); + var point, pointStream = { + point: function(x, y) { + point = [ x, y ]; + } + }, lower48Point, alaskaPoint, hawaiiPoint; + function albersUsa(coordinates) { + var x = coordinates[0], y = coordinates[1]; + point = null; + (lower48Point(x, y), point) || (alaskaPoint(x, y), point) || hawaiiPoint(x, y); + return point; + } + albersUsa.invert = function(coordinates) { + var k = lower48.scale(), t = lower48.translate(), x = (coordinates[0] - t[0]) / k, y = (coordinates[1] - t[1]) / k; + return (y >= .12 && y < .234 && x >= -.425 && x < -.214 ? alaska : y >= .166 && y < .234 && x >= -.214 && x < -.115 ? hawaii : lower48).invert(coordinates); + }; + albersUsa.stream = function(stream) { + var lower48Stream = lower48.stream(stream), alaskaStream = alaska.stream(stream), hawaiiStream = hawaii.stream(stream); + return { + point: function(x, y) { + lower48Stream.point(x, y); + alaskaStream.point(x, y); + hawaiiStream.point(x, y); + }, + sphere: function() { + lower48Stream.sphere(); + alaskaStream.sphere(); + hawaiiStream.sphere(); + }, + lineStart: function() { + lower48Stream.lineStart(); + alaskaStream.lineStart(); + hawaiiStream.lineStart(); + }, + lineEnd: function() { + lower48Stream.lineEnd(); + alaskaStream.lineEnd(); + hawaiiStream.lineEnd(); + }, + polygonStart: function() { + lower48Stream.polygonStart(); + alaskaStream.polygonStart(); + hawaiiStream.polygonStart(); + }, + polygonEnd: function() { + lower48Stream.polygonEnd(); + alaskaStream.polygonEnd(); + hawaiiStream.polygonEnd(); + } + }; + }; + albersUsa.precision = function(_) { + if (!arguments.length) return lower48.precision(); + lower48.precision(_); + alaska.precision(_); + hawaii.precision(_); + return albersUsa; + }; + albersUsa.scale = function(_) { + if (!arguments.length) return lower48.scale(); + lower48.scale(_); + alaska.scale(_ * .35); + hawaii.scale(_); + return albersUsa.translate(lower48.translate()); + }; + albersUsa.translate = function(_) { + if (!arguments.length) return lower48.translate(); + var k = lower48.scale(), x = +_[0], y = +_[1]; + lower48Point = lower48.translate(_).clipExtent([ [ x - .455 * k, y - .238 * k ], [ x + .455 * k, y + .238 * k ] ]).stream(pointStream).point; + alaskaPoint = alaska.translate([ x - .307 * k, y + .201 * k ]).clipExtent([ [ x - .425 * k + ε, y + .12 * k + ε ], [ x - .214 * k - ε, y + .234 * k - ε ] ]).stream(pointStream).point; + hawaiiPoint = hawaii.translate([ x - .205 * k, y + .212 * k ]).clipExtent([ [ x - .214 * k + ε, y + .166 * k + ε ], [ x - .115 * k - ε, y + .234 * k - ε ] ]).stream(pointStream).point; + return albersUsa; + }; + return albersUsa.scale(1070); + }; + var d3_geo_pathAreaSum, d3_geo_pathAreaPolygon, d3_geo_pathArea = { + point: d3_noop, + lineStart: d3_noop, + lineEnd: d3_noop, + polygonStart: function() { + d3_geo_pathAreaPolygon = 0; + d3_geo_pathArea.lineStart = d3_geo_pathAreaRingStart; + }, + polygonEnd: function() { + d3_geo_pathArea.lineStart = d3_geo_pathArea.lineEnd = d3_geo_pathArea.point = d3_noop; + d3_geo_pathAreaSum += abs(d3_geo_pathAreaPolygon / 2); + } + }; + function d3_geo_pathAreaRingStart() { + var x00, y00, x0, y0; + d3_geo_pathArea.point = function(x, y) { + d3_geo_pathArea.point = nextPoint; + x00 = x0 = x, y00 = y0 = y; + }; + function nextPoint(x, y) { + d3_geo_pathAreaPolygon += y0 * x - x0 * y; + x0 = x, y0 = y; + } + d3_geo_pathArea.lineEnd = function() { + nextPoint(x00, y00); + }; + } + var d3_geo_pathBoundsX0, d3_geo_pathBoundsY0, d3_geo_pathBoundsX1, d3_geo_pathBoundsY1; + var d3_geo_pathBounds = { + point: d3_geo_pathBoundsPoint, + lineStart: d3_noop, + lineEnd: d3_noop, + polygonStart: d3_noop, + polygonEnd: d3_noop + }; + function d3_geo_pathBoundsPoint(x, y) { + if (x < d3_geo_pathBoundsX0) d3_geo_pathBoundsX0 = x; + if (x > d3_geo_pathBoundsX1) d3_geo_pathBoundsX1 = x; + if (y < d3_geo_pathBoundsY0) d3_geo_pathBoundsY0 = y; + if (y > d3_geo_pathBoundsY1) d3_geo_pathBoundsY1 = y; + } + function d3_geo_pathBuffer() { + var pointCircle = d3_geo_pathBufferCircle(4.5), buffer = []; + var stream = { + point: point, + lineStart: function() { + stream.point = pointLineStart; + }, + lineEnd: lineEnd, + polygonStart: function() { + stream.lineEnd = lineEndPolygon; + }, + polygonEnd: function() { + stream.lineEnd = lineEnd; + stream.point = point; + }, + pointRadius: function(_) { + pointCircle = d3_geo_pathBufferCircle(_); + return stream; + }, + result: function() { + if (buffer.length) { + var result = buffer.join(""); + buffer = []; + return result; + } + } + }; + function point(x, y) { + buffer.push("M", x, ",", y, pointCircle); + } + function pointLineStart(x, y) { + buffer.push("M", x, ",", y); + stream.point = pointLine; + } + function pointLine(x, y) { + buffer.push("L", x, ",", y); + } + function lineEnd() { + stream.point = point; + } + function lineEndPolygon() { + buffer.push("Z"); + } + return stream; + } + function d3_geo_pathBufferCircle(radius) { + return "m0," + radius + "a" + radius + "," + radius + " 0 1,1 0," + -2 * radius + "a" + radius + "," + radius + " 0 1,1 0," + 2 * radius + "z"; + } + var d3_geo_pathCentroid = { + point: d3_geo_pathCentroidPoint, + lineStart: d3_geo_pathCentroidLineStart, + lineEnd: d3_geo_pathCentroidLineEnd, + polygonStart: function() { + d3_geo_pathCentroid.lineStart = d3_geo_pathCentroidRingStart; + }, + polygonEnd: function() { + d3_geo_pathCentroid.point = d3_geo_pathCentroidPoint; + d3_geo_pathCentroid.lineStart = d3_geo_pathCentroidLineStart; + d3_geo_pathCentroid.lineEnd = d3_geo_pathCentroidLineEnd; + } + }; + function d3_geo_pathCentroidPoint(x, y) { + d3_geo_centroidX0 += x; + d3_geo_centroidY0 += y; + ++d3_geo_centroidZ0; + } + function d3_geo_pathCentroidLineStart() { + var x0, y0; + d3_geo_pathCentroid.point = function(x, y) { + d3_geo_pathCentroid.point = nextPoint; + d3_geo_pathCentroidPoint(x0 = x, y0 = y); + }; + function nextPoint(x, y) { + var dx = x - x0, dy = y - y0, z = Math.sqrt(dx * dx + dy * dy); + d3_geo_centroidX1 += z * (x0 + x) / 2; + d3_geo_centroidY1 += z * (y0 + y) / 2; + d3_geo_centroidZ1 += z; + d3_geo_pathCentroidPoint(x0 = x, y0 = y); + } + } + function d3_geo_pathCentroidLineEnd() { + d3_geo_pathCentroid.point = d3_geo_pathCentroidPoint; + } + function d3_geo_pathCentroidRingStart() { + var x00, y00, x0, y0; + d3_geo_pathCentroid.point = function(x, y) { + d3_geo_pathCentroid.point = nextPoint; + d3_geo_pathCentroidPoint(x00 = x0 = x, y00 = y0 = y); + }; + function nextPoint(x, y) { + var dx = x - x0, dy = y - y0, z = Math.sqrt(dx * dx + dy * dy); + d3_geo_centroidX1 += z * (x0 + x) / 2; + d3_geo_centroidY1 += z * (y0 + y) / 2; + d3_geo_centroidZ1 += z; + z = y0 * x - x0 * y; + d3_geo_centroidX2 += z * (x0 + x); + d3_geo_centroidY2 += z * (y0 + y); + d3_geo_centroidZ2 += z * 3; + d3_geo_pathCentroidPoint(x0 = x, y0 = y); + } + d3_geo_pathCentroid.lineEnd = function() { + nextPoint(x00, y00); + }; + } + function d3_geo_pathContext(context) { + var pointRadius = 4.5; + var stream = { + point: point, + lineStart: function() { + stream.point = pointLineStart; + }, + lineEnd: lineEnd, + polygonStart: function() { + stream.lineEnd = lineEndPolygon; + }, + polygonEnd: function() { + stream.lineEnd = lineEnd; + stream.point = point; + }, + pointRadius: function(_) { + pointRadius = _; + return stream; + }, + result: d3_noop + }; + function point(x, y) { + context.moveTo(x + pointRadius, y); + context.arc(x, y, pointRadius, 0, τ); + } + function pointLineStart(x, y) { + context.moveTo(x, y); + stream.point = pointLine; + } + function pointLine(x, y) { + context.lineTo(x, y); + } + function lineEnd() { + stream.point = point; + } + function lineEndPolygon() { + context.closePath(); + } + return stream; + } + function d3_geo_resample(project) { + var δ2 = .5, cosMinDistance = Math.cos(30 * d3_radians), maxDepth = 16; + function resample(stream) { + return (maxDepth ? resampleRecursive : resampleNone)(stream); + } + function resampleNone(stream) { + return d3_geo_transformPoint(stream, function(x, y) { + x = project(x, y); + stream.point(x[0], x[1]); + }); + } + function resampleRecursive(stream) { + var λ00, φ00, x00, y00, a00, b00, c00, λ0, x0, y0, a0, b0, c0; + var resample = { + point: point, + lineStart: lineStart, + lineEnd: lineEnd, + polygonStart: function() { + stream.polygonStart(); + resample.lineStart = ringStart; + }, + polygonEnd: function() { + stream.polygonEnd(); + resample.lineStart = lineStart; + } + }; + function point(x, y) { + x = project(x, y); + stream.point(x[0], x[1]); + } + function lineStart() { + x0 = NaN; + resample.point = linePoint; + stream.lineStart(); + } + function linePoint(λ, φ) { + var c = d3_geo_cartesian([ λ, φ ]), p = project(λ, φ); + resampleLineTo(x0, y0, λ0, a0, b0, c0, x0 = p[0], y0 = p[1], λ0 = λ, a0 = c[0], b0 = c[1], c0 = c[2], maxDepth, stream); + stream.point(x0, y0); + } + function lineEnd() { + resample.point = point; + stream.lineEnd(); + } + function ringStart() { + lineStart(); + resample.point = ringPoint; + resample.lineEnd = ringEnd; + } + function ringPoint(λ, φ) { + linePoint(λ00 = λ, φ00 = φ), x00 = x0, y00 = y0, a00 = a0, b00 = b0, c00 = c0; + resample.point = linePoint; + } + function ringEnd() { + resampleLineTo(x0, y0, λ0, a0, b0, c0, x00, y00, λ00, a00, b00, c00, maxDepth, stream); + resample.lineEnd = lineEnd; + lineEnd(); + } + return resample; + } + function resampleLineTo(x0, y0, λ0, a0, b0, c0, x1, y1, λ1, a1, b1, c1, depth, stream) { + var dx = x1 - x0, dy = y1 - y0, d2 = dx * dx + dy * dy; + if (d2 > 4 * δ2 && depth--) { + var a = a0 + a1, b = b0 + b1, c = c0 + c1, m = Math.sqrt(a * a + b * b + c * c), φ2 = Math.asin(c /= m), λ2 = abs(abs(c) - 1) < ε || abs(λ0 - λ1) < ε ? (λ0 + λ1) / 2 : Math.atan2(b, a), p = project(λ2, φ2), x2 = p[0], y2 = p[1], dx2 = x2 - x0, dy2 = y2 - y0, dz = dy * dx2 - dx * dy2; + if (dz * dz / d2 > δ2 || abs((dx * dx2 + dy * dy2) / d2 - .5) > .3 || a0 * a1 + b0 * b1 + c0 * c1 < cosMinDistance) { + resampleLineTo(x0, y0, λ0, a0, b0, c0, x2, y2, λ2, a /= m, b /= m, c, depth, stream); + stream.point(x2, y2); + resampleLineTo(x2, y2, λ2, a, b, c, x1, y1, λ1, a1, b1, c1, depth, stream); + } + } + } + resample.precision = function(_) { + if (!arguments.length) return Math.sqrt(δ2); + maxDepth = (δ2 = _ * _) > 0 && 16; + return resample; + }; + return resample; + } + d3.geo.path = function() { + var pointRadius = 4.5, projection, context, projectStream, contextStream, cacheStream; + function path(object) { + if (object) { + if (typeof pointRadius === "function") contextStream.pointRadius(+pointRadius.apply(this, arguments)); + if (!cacheStream || !cacheStream.valid) cacheStream = projectStream(contextStream); + d3.geo.stream(object, cacheStream); + } + return contextStream.result(); + } + path.area = function(object) { + d3_geo_pathAreaSum = 0; + d3.geo.stream(object, projectStream(d3_geo_pathArea)); + return d3_geo_pathAreaSum; + }; + path.centroid = function(object) { + d3_geo_centroidX0 = d3_geo_centroidY0 = d3_geo_centroidZ0 = d3_geo_centroidX1 = d3_geo_centroidY1 = d3_geo_centroidZ1 = d3_geo_centroidX2 = d3_geo_centroidY2 = d3_geo_centroidZ2 = 0; + d3.geo.stream(object, projectStream(d3_geo_pathCentroid)); + return d3_geo_centroidZ2 ? [ d3_geo_centroidX2 / d3_geo_centroidZ2, d3_geo_centroidY2 / d3_geo_centroidZ2 ] : d3_geo_centroidZ1 ? [ d3_geo_centroidX1 / d3_geo_centroidZ1, d3_geo_centroidY1 / d3_geo_centroidZ1 ] : d3_geo_centroidZ0 ? [ d3_geo_centroidX0 / d3_geo_centroidZ0, d3_geo_centroidY0 / d3_geo_centroidZ0 ] : [ NaN, NaN ]; + }; + path.bounds = function(object) { + d3_geo_pathBoundsX1 = d3_geo_pathBoundsY1 = -(d3_geo_pathBoundsX0 = d3_geo_pathBoundsY0 = Infinity); + d3.geo.stream(object, projectStream(d3_geo_pathBounds)); + return [ [ d3_geo_pathBoundsX0, d3_geo_pathBoundsY0 ], [ d3_geo_pathBoundsX1, d3_geo_pathBoundsY1 ] ]; + }; + path.projection = function(_) { + if (!arguments.length) return projection; + projectStream = (projection = _) ? _.stream || d3_geo_pathProjectStream(_) : d3_identity; + return reset(); + }; + path.context = function(_) { + if (!arguments.length) return context; + contextStream = (context = _) == null ? new d3_geo_pathBuffer() : new d3_geo_pathContext(_); + if (typeof pointRadius !== "function") contextStream.pointRadius(pointRadius); + return reset(); + }; + path.pointRadius = function(_) { + if (!arguments.length) return pointRadius; + pointRadius = typeof _ === "function" ? _ : (contextStream.pointRadius(+_), +_); + return path; + }; + function reset() { + cacheStream = null; + return path; + } + return path.projection(d3.geo.albersUsa()).context(null); + }; + function d3_geo_pathProjectStream(project) { + var resample = d3_geo_resample(function(x, y) { + return project([ x * d3_degrees, y * d3_degrees ]); + }); + return function(stream) { + return d3_geo_projectionRadians(resample(stream)); + }; + } + d3.geo.transform = function(methods) { + return { + stream: function(stream) { + var transform = new d3_geo_transform(stream); + for (var k in methods) transform[k] = methods[k]; + return transform; + } + }; + }; + function d3_geo_transform(stream) { + this.stream = stream; + } + d3_geo_transform.prototype = { + point: function(x, y) { + this.stream.point(x, y); + }, + sphere: function() { + this.stream.sphere(); + }, + lineStart: function() { + this.stream.lineStart(); + }, + lineEnd: function() { + this.stream.lineEnd(); + }, + polygonStart: function() { + this.stream.polygonStart(); + }, + polygonEnd: function() { + this.stream.polygonEnd(); + } + }; + function d3_geo_transformPoint(stream, point) { + return { + point: point, + sphere: function() { + stream.sphere(); + }, + lineStart: function() { + stream.lineStart(); + }, + lineEnd: function() { + stream.lineEnd(); + }, + polygonStart: function() { + stream.polygonStart(); + }, + polygonEnd: function() { + stream.polygonEnd(); + } + }; + } + d3.geo.projection = d3_geo_projection; + d3.geo.projectionMutator = d3_geo_projectionMutator; + function d3_geo_projection(project) { + return d3_geo_projectionMutator(function() { + return project; + })(); + } + function d3_geo_projectionMutator(projectAt) { + var project, rotate, projectRotate, projectResample = d3_geo_resample(function(x, y) { + x = project(x, y); + return [ x[0] * k + δx, δy - x[1] * k ]; + }), k = 150, x = 480, y = 250, λ = 0, φ = 0, δλ = 0, δφ = 0, δγ = 0, δx, δy, preclip = d3_geo_clipAntimeridian, postclip = d3_identity, clipAngle = null, clipExtent = null, stream; + function projection(point) { + point = projectRotate(point[0] * d3_radians, point[1] * d3_radians); + return [ point[0] * k + δx, δy - point[1] * k ]; + } + function invert(point) { + point = projectRotate.invert((point[0] - δx) / k, (δy - point[1]) / k); + return point && [ point[0] * d3_degrees, point[1] * d3_degrees ]; + } + projection.stream = function(output) { + if (stream) stream.valid = false; + stream = d3_geo_projectionRadians(preclip(rotate, projectResample(postclip(output)))); + stream.valid = true; + return stream; + }; + projection.clipAngle = function(_) { + if (!arguments.length) return clipAngle; + preclip = _ == null ? (clipAngle = _, d3_geo_clipAntimeridian) : d3_geo_clipCircle((clipAngle = +_) * d3_radians); + return invalidate(); + }; + projection.clipExtent = function(_) { + if (!arguments.length) return clipExtent; + clipExtent = _; + postclip = _ ? d3_geo_clipExtent(_[0][0], _[0][1], _[1][0], _[1][1]) : d3_identity; + return invalidate(); + }; + projection.scale = function(_) { + if (!arguments.length) return k; + k = +_; + return reset(); + }; + projection.translate = function(_) { + if (!arguments.length) return [ x, y ]; + x = +_[0]; + y = +_[1]; + return reset(); + }; + projection.center = function(_) { + if (!arguments.length) return [ λ * d3_degrees, φ * d3_degrees ]; + λ = _[0] % 360 * d3_radians; + φ = _[1] % 360 * d3_radians; + return reset(); + }; + projection.rotate = function(_) { + if (!arguments.length) return [ δλ * d3_degrees, δφ * d3_degrees, δγ * d3_degrees ]; + δλ = _[0] % 360 * d3_radians; + δφ = _[1] % 360 * d3_radians; + δγ = _.length > 2 ? _[2] % 360 * d3_radians : 0; + return reset(); + }; + d3.rebind(projection, projectResample, "precision"); + function reset() { + projectRotate = d3_geo_compose(rotate = d3_geo_rotation(δλ, δφ, δγ), project); + var center = project(λ, φ); + δx = x - center[0] * k; + δy = y + center[1] * k; + return invalidate(); + } + function invalidate() { + if (stream) stream.valid = false, stream = null; + return projection; + } + return function() { + project = projectAt.apply(this, arguments); + projection.invert = project.invert && invert; + return reset(); + }; + } + function d3_geo_projectionRadians(stream) { + return d3_geo_transformPoint(stream, function(x, y) { + stream.point(x * d3_radians, y * d3_radians); + }); + } + function d3_geo_equirectangular(λ, φ) { + return [ λ, φ ]; + } + (d3.geo.equirectangular = function() { + return d3_geo_projection(d3_geo_equirectangular); + }).raw = d3_geo_equirectangular.invert = d3_geo_equirectangular; + d3.geo.rotation = function(rotate) { + rotate = d3_geo_rotation(rotate[0] % 360 * d3_radians, rotate[1] * d3_radians, rotate.length > 2 ? rotate[2] * d3_radians : 0); + function forward(coordinates) { + coordinates = rotate(coordinates[0] * d3_radians, coordinates[1] * d3_radians); + return coordinates[0] *= d3_degrees, coordinates[1] *= d3_degrees, coordinates; + } + forward.invert = function(coordinates) { + coordinates = rotate.invert(coordinates[0] * d3_radians, coordinates[1] * d3_radians); + return coordinates[0] *= d3_degrees, coordinates[1] *= d3_degrees, coordinates; + }; + return forward; + }; + function d3_geo_identityRotation(λ, φ) { + return [ λ > π ? λ - τ : λ < -π ? λ + τ : λ, φ ]; + } + d3_geo_identityRotation.invert = d3_geo_equirectangular; + function d3_geo_rotation(δλ, δφ, δγ) { + return δλ ? δφ || δγ ? d3_geo_compose(d3_geo_rotationλ(δλ), d3_geo_rotationφγ(δφ, δγ)) : d3_geo_rotationλ(δλ) : δφ || δγ ? d3_geo_rotationφγ(δφ, δγ) : d3_geo_identityRotation; + } + function d3_geo_forwardRotationλ(δλ) { + return function(λ, φ) { + return λ += δλ, [ λ > π ? λ - τ : λ < -π ? λ + τ : λ, φ ]; + }; + } + function d3_geo_rotationλ(δλ) { + var rotation = d3_geo_forwardRotationλ(δλ); + rotation.invert = d3_geo_forwardRotationλ(-δλ); + return rotation; + } + function d3_geo_rotationφγ(δφ, δγ) { + var cosδφ = Math.cos(δφ), sinδφ = Math.sin(δφ), cosδγ = Math.cos(δγ), sinδγ = Math.sin(δγ); + function rotation(λ, φ) { + var cosφ = Math.cos(φ), x = Math.cos(λ) * cosφ, y = Math.sin(λ) * cosφ, z = Math.sin(φ), k = z * cosδφ + x * sinδφ; + return [ Math.atan2(y * cosδγ - k * sinδγ, x * cosδφ - z * sinδφ), d3_asin(k * cosδγ + y * sinδγ) ]; + } + rotation.invert = function(λ, φ) { + var cosφ = Math.cos(φ), x = Math.cos(λ) * cosφ, y = Math.sin(λ) * cosφ, z = Math.sin(φ), k = z * cosδγ - y * sinδγ; + return [ Math.atan2(y * cosδγ + z * sinδγ, x * cosδφ + k * sinδφ), d3_asin(k * cosδφ - x * sinδφ) ]; + }; + return rotation; + } + d3.geo.circle = function() { + var origin = [ 0, 0 ], angle, precision = 6, interpolate; + function circle() { + var center = typeof origin === "function" ? origin.apply(this, arguments) : origin, rotate = d3_geo_rotation(-center[0] * d3_radians, -center[1] * d3_radians, 0).invert, ring = []; + interpolate(null, null, 1, { + point: function(x, y) { + ring.push(x = rotate(x, y)); + x[0] *= d3_degrees, x[1] *= d3_degrees; + } + }); + return { + type: "Polygon", + coordinates: [ ring ] + }; + } + circle.origin = function(x) { + if (!arguments.length) return origin; + origin = x; + return circle; + }; + circle.angle = function(x) { + if (!arguments.length) return angle; + interpolate = d3_geo_circleInterpolate((angle = +x) * d3_radians, precision * d3_radians); + return circle; + }; + circle.precision = function(_) { + if (!arguments.length) return precision; + interpolate = d3_geo_circleInterpolate(angle * d3_radians, (precision = +_) * d3_radians); + return circle; + }; + return circle.angle(90); + }; + function d3_geo_circleInterpolate(radius, precision) { + var cr = Math.cos(radius), sr = Math.sin(radius); + return function(from, to, direction, listener) { + var step = direction * precision; + if (from != null) { + from = d3_geo_circleAngle(cr, from); + to = d3_geo_circleAngle(cr, to); + if (direction > 0 ? from < to : from > to) from += direction * τ; + } else { + from = radius + direction * τ; + to = radius - .5 * step; + } + for (var point, t = from; direction > 0 ? t > to : t < to; t -= step) { + listener.point((point = d3_geo_spherical([ cr, -sr * Math.cos(t), -sr * Math.sin(t) ]))[0], point[1]); + } + }; + } + function d3_geo_circleAngle(cr, point) { + var a = d3_geo_cartesian(point); + a[0] -= cr; + d3_geo_cartesianNormalize(a); + var angle = d3_acos(-a[1]); + return ((-a[2] < 0 ? -angle : angle) + 2 * Math.PI - ε) % (2 * Math.PI); + } + d3.geo.distance = function(a, b) { + var Δλ = (b[0] - a[0]) * d3_radians, φ0 = a[1] * d3_radians, φ1 = b[1] * d3_radians, sinΔλ = Math.sin(Δλ), cosΔλ = Math.cos(Δλ), sinφ0 = Math.sin(φ0), cosφ0 = Math.cos(φ0), sinφ1 = Math.sin(φ1), cosφ1 = Math.cos(φ1), t; + return Math.atan2(Math.sqrt((t = cosφ1 * sinΔλ) * t + (t = cosφ0 * sinφ1 - sinφ0 * cosφ1 * cosΔλ) * t), sinφ0 * sinφ1 + cosφ0 * cosφ1 * cosΔλ); + }; + d3.geo.graticule = function() { + var x1, x0, X1, X0, y1, y0, Y1, Y0, dx = 10, dy = dx, DX = 90, DY = 360, x, y, X, Y, precision = 2.5; + function graticule() { + return { + type: "MultiLineString", + coordinates: lines() + }; + } + function lines() { + return d3.range(Math.ceil(X0 / DX) * DX, X1, DX).map(X).concat(d3.range(Math.ceil(Y0 / DY) * DY, Y1, DY).map(Y)).concat(d3.range(Math.ceil(x0 / dx) * dx, x1, dx).filter(function(x) { + return abs(x % DX) > ε; + }).map(x)).concat(d3.range(Math.ceil(y0 / dy) * dy, y1, dy).filter(function(y) { + return abs(y % DY) > ε; + }).map(y)); + } + graticule.lines = function() { + return lines().map(function(coordinates) { + return { + type: "LineString", + coordinates: coordinates + }; + }); + }; + graticule.outline = function() { + return { + type: "Polygon", + coordinates: [ X(X0).concat(Y(Y1).slice(1), X(X1).reverse().slice(1), Y(Y0).reverse().slice(1)) ] + }; + }; + graticule.extent = function(_) { + if (!arguments.length) return graticule.minorExtent(); + return graticule.majorExtent(_).minorExtent(_); + }; + graticule.majorExtent = function(_) { + if (!arguments.length) return [ [ X0, Y0 ], [ X1, Y1 ] ]; + X0 = +_[0][0], X1 = +_[1][0]; + Y0 = +_[0][1], Y1 = +_[1][1]; + if (X0 > X1) _ = X0, X0 = X1, X1 = _; + if (Y0 > Y1) _ = Y0, Y0 = Y1, Y1 = _; + return graticule.precision(precision); + }; + graticule.minorExtent = function(_) { + if (!arguments.length) return [ [ x0, y0 ], [ x1, y1 ] ]; + x0 = +_[0][0], x1 = +_[1][0]; + y0 = +_[0][1], y1 = +_[1][1]; + if (x0 > x1) _ = x0, x0 = x1, x1 = _; + if (y0 > y1) _ = y0, y0 = y1, y1 = _; + return graticule.precision(precision); + }; + graticule.step = function(_) { + if (!arguments.length) return graticule.minorStep(); + return graticule.majorStep(_).minorStep(_); + }; + graticule.majorStep = function(_) { + if (!arguments.length) return [ DX, DY ]; + DX = +_[0], DY = +_[1]; + return graticule; + }; + graticule.minorStep = function(_) { + if (!arguments.length) return [ dx, dy ]; + dx = +_[0], dy = +_[1]; + return graticule; + }; + graticule.precision = function(_) { + if (!arguments.length) return precision; + precision = +_; + x = d3_geo_graticuleX(y0, y1, 90); + y = d3_geo_graticuleY(x0, x1, precision); + X = d3_geo_graticuleX(Y0, Y1, 90); + Y = d3_geo_graticuleY(X0, X1, precision); + return graticule; + }; + return graticule.majorExtent([ [ -180, -90 + ε ], [ 180, 90 - ε ] ]).minorExtent([ [ -180, -80 - ε ], [ 180, 80 + ε ] ]); + }; + function d3_geo_graticuleX(y0, y1, dy) { + var y = d3.range(y0, y1 - ε, dy).concat(y1); + return function(x) { + return y.map(function(y) { + return [ x, y ]; + }); + }; + } + function d3_geo_graticuleY(x0, x1, dx) { + var x = d3.range(x0, x1 - ε, dx).concat(x1); + return function(y) { + return x.map(function(x) { + return [ x, y ]; + }); + }; + } + function d3_source(d) { + return d.source; + } + function d3_target(d) { + return d.target; + } + d3.geo.greatArc = function() { + var source = d3_source, source_, target = d3_target, target_; + function greatArc() { + return { + type: "LineString", + coordinates: [ source_ || source.apply(this, arguments), target_ || target.apply(this, arguments) ] + }; + } + greatArc.distance = function() { + return d3.geo.distance(source_ || source.apply(this, arguments), target_ || target.apply(this, arguments)); + }; + greatArc.source = function(_) { + if (!arguments.length) return source; + source = _, source_ = typeof _ === "function" ? null : _; + return greatArc; + }; + greatArc.target = function(_) { + if (!arguments.length) return target; + target = _, target_ = typeof _ === "function" ? null : _; + return greatArc; + }; + greatArc.precision = function() { + return arguments.length ? greatArc : 0; + }; + return greatArc; + }; + d3.geo.interpolate = function(source, target) { + return d3_geo_interpolate(source[0] * d3_radians, source[1] * d3_radians, target[0] * d3_radians, target[1] * d3_radians); + }; + function d3_geo_interpolate(x0, y0, x1, y1) { + var cy0 = Math.cos(y0), sy0 = Math.sin(y0), cy1 = Math.cos(y1), sy1 = Math.sin(y1), kx0 = cy0 * Math.cos(x0), ky0 = cy0 * Math.sin(x0), kx1 = cy1 * Math.cos(x1), ky1 = cy1 * Math.sin(x1), d = 2 * Math.asin(Math.sqrt(d3_haversin(y1 - y0) + cy0 * cy1 * d3_haversin(x1 - x0))), k = 1 / Math.sin(d); + var interpolate = d ? function(t) { + var B = Math.sin(t *= d) * k, A = Math.sin(d - t) * k, x = A * kx0 + B * kx1, y = A * ky0 + B * ky1, z = A * sy0 + B * sy1; + return [ Math.atan2(y, x) * d3_degrees, Math.atan2(z, Math.sqrt(x * x + y * y)) * d3_degrees ]; + } : function() { + return [ x0 * d3_degrees, y0 * d3_degrees ]; + }; + interpolate.distance = d; + return interpolate; + } + d3.geo.length = function(object) { + d3_geo_lengthSum = 0; + d3.geo.stream(object, d3_geo_length); + return d3_geo_lengthSum; + }; + var d3_geo_lengthSum; + var d3_geo_length = { + sphere: d3_noop, + point: d3_noop, + lineStart: d3_geo_lengthLineStart, + lineEnd: d3_noop, + polygonStart: d3_noop, + polygonEnd: d3_noop + }; + function d3_geo_lengthLineStart() { + var λ0, sinφ0, cosφ0; + d3_geo_length.point = function(λ, φ) { + λ0 = λ * d3_radians, sinφ0 = Math.sin(φ *= d3_radians), cosφ0 = Math.cos(φ); + d3_geo_length.point = nextPoint; + }; + d3_geo_length.lineEnd = function() { + d3_geo_length.point = d3_geo_length.lineEnd = d3_noop; + }; + function nextPoint(λ, φ) { + var sinφ = Math.sin(φ *= d3_radians), cosφ = Math.cos(φ), t = abs((λ *= d3_radians) - λ0), cosΔλ = Math.cos(t); + d3_geo_lengthSum += Math.atan2(Math.sqrt((t = cosφ * Math.sin(t)) * t + (t = cosφ0 * sinφ - sinφ0 * cosφ * cosΔλ) * t), sinφ0 * sinφ + cosφ0 * cosφ * cosΔλ); + λ0 = λ, sinφ0 = sinφ, cosφ0 = cosφ; + } + } + function d3_geo_azimuthal(scale, angle) { + function azimuthal(λ, φ) { + var cosλ = Math.cos(λ), cosφ = Math.cos(φ), k = scale(cosλ * cosφ); + return [ k * cosφ * Math.sin(λ), k * Math.sin(φ) ]; + } + azimuthal.invert = function(x, y) { + var ρ = Math.sqrt(x * x + y * y), c = angle(ρ), sinc = Math.sin(c), cosc = Math.cos(c); + return [ Math.atan2(x * sinc, ρ * cosc), Math.asin(ρ && y * sinc / ρ) ]; + }; + return azimuthal; + } + var d3_geo_azimuthalEqualArea = d3_geo_azimuthal(function(cosλcosφ) { + return Math.sqrt(2 / (1 + cosλcosφ)); + }, function(ρ) { + return 2 * Math.asin(ρ / 2); + }); + (d3.geo.azimuthalEqualArea = function() { + return d3_geo_projection(d3_geo_azimuthalEqualArea); + }).raw = d3_geo_azimuthalEqualArea; + var d3_geo_azimuthalEquidistant = d3_geo_azimuthal(function(cosλcosφ) { + var c = Math.acos(cosλcosφ); + return c && c / Math.sin(c); + }, d3_identity); + (d3.geo.azimuthalEquidistant = function() { + return d3_geo_projection(d3_geo_azimuthalEquidistant); + }).raw = d3_geo_azimuthalEquidistant; + function d3_geo_conicConformal(φ0, φ1) { + var cosφ0 = Math.cos(φ0), t = function(φ) { + return Math.tan(π / 4 + φ / 2); + }, n = φ0 === φ1 ? Math.sin(φ0) : Math.log(cosφ0 / Math.cos(φ1)) / Math.log(t(φ1) / t(φ0)), F = cosφ0 * Math.pow(t(φ0), n) / n; + if (!n) return d3_geo_mercator; + function forward(λ, φ) { + if (F > 0) { + if (φ < -halfπ + ε) φ = -halfπ + ε; + } else { + if (φ > halfπ - ε) φ = halfπ - ε; + } + var ρ = F / Math.pow(t(φ), n); + return [ ρ * Math.sin(n * λ), F - ρ * Math.cos(n * λ) ]; + } + forward.invert = function(x, y) { + var ρ0_y = F - y, ρ = d3_sgn(n) * Math.sqrt(x * x + ρ0_y * ρ0_y); + return [ Math.atan2(x, ρ0_y) / n, 2 * Math.atan(Math.pow(F / ρ, 1 / n)) - halfπ ]; + }; + return forward; + } + (d3.geo.conicConformal = function() { + return d3_geo_conic(d3_geo_conicConformal); + }).raw = d3_geo_conicConformal; + function d3_geo_conicEquidistant(φ0, φ1) { + var cosφ0 = Math.cos(φ0), n = φ0 === φ1 ? Math.sin(φ0) : (cosφ0 - Math.cos(φ1)) / (φ1 - φ0), G = cosφ0 / n + φ0; + if (abs(n) < ε) return d3_geo_equirectangular; + function forward(λ, φ) { + var ρ = G - φ; + return [ ρ * Math.sin(n * λ), G - ρ * Math.cos(n * λ) ]; + } + forward.invert = function(x, y) { + var ρ0_y = G - y; + return [ Math.atan2(x, ρ0_y) / n, G - d3_sgn(n) * Math.sqrt(x * x + ρ0_y * ρ0_y) ]; + }; + return forward; + } + (d3.geo.conicEquidistant = function() { + return d3_geo_conic(d3_geo_conicEquidistant); + }).raw = d3_geo_conicEquidistant; + var d3_geo_gnomonic = d3_geo_azimuthal(function(cosλcosφ) { + return 1 / cosλcosφ; + }, Math.atan); + (d3.geo.gnomonic = function() { + return d3_geo_projection(d3_geo_gnomonic); + }).raw = d3_geo_gnomonic; + function d3_geo_mercator(λ, φ) { + return [ λ, Math.log(Math.tan(π / 4 + φ / 2)) ]; + } + d3_geo_mercator.invert = function(x, y) { + return [ x, 2 * Math.atan(Math.exp(y)) - halfπ ]; + }; + function d3_geo_mercatorProjection(project) { + var m = d3_geo_projection(project), scale = m.scale, translate = m.translate, clipExtent = m.clipExtent, clipAuto; + m.scale = function() { + var v = scale.apply(m, arguments); + return v === m ? clipAuto ? m.clipExtent(null) : m : v; + }; + m.translate = function() { + var v = translate.apply(m, arguments); + return v === m ? clipAuto ? m.clipExtent(null) : m : v; + }; + m.clipExtent = function(_) { + var v = clipExtent.apply(m, arguments); + if (v === m) { + if (clipAuto = _ == null) { + var k = π * scale(), t = translate(); + clipExtent([ [ t[0] - k, t[1] - k ], [ t[0] + k, t[1] + k ] ]); + } + } else if (clipAuto) { + v = null; + } + return v; + }; + return m.clipExtent(null); + } + (d3.geo.mercator = function() { + return d3_geo_mercatorProjection(d3_geo_mercator); + }).raw = d3_geo_mercator; + var d3_geo_orthographic = d3_geo_azimuthal(function() { + return 1; + }, Math.asin); + (d3.geo.orthographic = function() { + return d3_geo_projection(d3_geo_orthographic); + }).raw = d3_geo_orthographic; + var d3_geo_stereographic = d3_geo_azimuthal(function(cosλcosφ) { + return 1 / (1 + cosλcosφ); + }, function(ρ) { + return 2 * Math.atan(ρ); + }); + (d3.geo.stereographic = function() { + return d3_geo_projection(d3_geo_stereographic); + }).raw = d3_geo_stereographic; + function d3_geo_transverseMercator(λ, φ) { + return [ Math.log(Math.tan(π / 4 + φ / 2)), -λ ]; + } + d3_geo_transverseMercator.invert = function(x, y) { + return [ -y, 2 * Math.atan(Math.exp(x)) - halfπ ]; + }; + (d3.geo.transverseMercator = function() { + var projection = d3_geo_mercatorProjection(d3_geo_transverseMercator), center = projection.center, rotate = projection.rotate; + projection.center = function(_) { + return _ ? center([ -_[1], _[0] ]) : (_ = center(), [ _[1], -_[0] ]); + }; + projection.rotate = function(_) { + return _ ? rotate([ _[0], _[1], _.length > 2 ? _[2] + 90 : 90 ]) : (_ = rotate(), + [ _[0], _[1], _[2] - 90 ]); + }; + return rotate([ 0, 0, 90 ]); + }).raw = d3_geo_transverseMercator; + d3.geom = {}; + function d3_geom_pointX(d) { + return d[0]; + } + function d3_geom_pointY(d) { + return d[1]; + } + d3.geom.hull = function(vertices) { + var x = d3_geom_pointX, y = d3_geom_pointY; + if (arguments.length) return hull(vertices); + function hull(data) { + if (data.length < 3) return []; + var fx = d3_functor(x), fy = d3_functor(y), i, n = data.length, points = [], flippedPoints = []; + for (i = 0; i < n; i++) { + points.push([ +fx.call(this, data[i], i), +fy.call(this, data[i], i), i ]); + } + points.sort(d3_geom_hullOrder); + for (i = 0; i < n; i++) flippedPoints.push([ points[i][0], -points[i][1] ]); + var upper = d3_geom_hullUpper(points), lower = d3_geom_hullUpper(flippedPoints); + var skipLeft = lower[0] === upper[0], skipRight = lower[lower.length - 1] === upper[upper.length - 1], polygon = []; + for (i = upper.length - 1; i >= 0; --i) polygon.push(data[points[upper[i]][2]]); + for (i = +skipLeft; i < lower.length - skipRight; ++i) polygon.push(data[points[lower[i]][2]]); + return polygon; + } + hull.x = function(_) { + return arguments.length ? (x = _, hull) : x; + }; + hull.y = function(_) { + return arguments.length ? (y = _, hull) : y; + }; + return hull; + }; + function d3_geom_hullUpper(points) { + var n = points.length, hull = [ 0, 1 ], hs = 2; + for (var i = 2; i < n; i++) { + while (hs > 1 && d3_cross2d(points[hull[hs - 2]], points[hull[hs - 1]], points[i]) <= 0) --hs; + hull[hs++] = i; + } + return hull.slice(0, hs); + } + function d3_geom_hullOrder(a, b) { + return a[0] - b[0] || a[1] - b[1]; + } + d3.geom.polygon = function(coordinates) { + d3_subclass(coordinates, d3_geom_polygonPrototype); + return coordinates; + }; + var d3_geom_polygonPrototype = d3.geom.polygon.prototype = []; + d3_geom_polygonPrototype.area = function() { + var i = -1, n = this.length, a, b = this[n - 1], area = 0; + while (++i < n) { + a = b; + b = this[i]; + area += a[1] * b[0] - a[0] * b[1]; + } + return area * .5; + }; + d3_geom_polygonPrototype.centroid = function(k) { + var i = -1, n = this.length, x = 0, y = 0, a, b = this[n - 1], c; + if (!arguments.length) k = -1 / (6 * this.area()); + while (++i < n) { + a = b; + b = this[i]; + c = a[0] * b[1] - b[0] * a[1]; + x += (a[0] + b[0]) * c; + y += (a[1] + b[1]) * c; + } + return [ x * k, y * k ]; + }; + d3_geom_polygonPrototype.clip = function(subject) { + var input, closed = d3_geom_polygonClosed(subject), i = -1, n = this.length - d3_geom_polygonClosed(this), j, m, a = this[n - 1], b, c, d; + while (++i < n) { + input = subject.slice(); + subject.length = 0; + b = this[i]; + c = input[(m = input.length - closed) - 1]; + j = -1; + while (++j < m) { + d = input[j]; + if (d3_geom_polygonInside(d, a, b)) { + if (!d3_geom_polygonInside(c, a, b)) { + subject.push(d3_geom_polygonIntersect(c, d, a, b)); + } + subject.push(d); + } else if (d3_geom_polygonInside(c, a, b)) { + subject.push(d3_geom_polygonIntersect(c, d, a, b)); + } + c = d; + } + if (closed) subject.push(subject[0]); + a = b; + } + return subject; + }; + function d3_geom_polygonInside(p, a, b) { + return (b[0] - a[0]) * (p[1] - a[1]) < (b[1] - a[1]) * (p[0] - a[0]); + } + function d3_geom_polygonIntersect(c, d, a, b) { + var x1 = c[0], x3 = a[0], x21 = d[0] - x1, x43 = b[0] - x3, y1 = c[1], y3 = a[1], y21 = d[1] - y1, y43 = b[1] - y3, ua = (x43 * (y1 - y3) - y43 * (x1 - x3)) / (y43 * x21 - x43 * y21); + return [ x1 + ua * x21, y1 + ua * y21 ]; + } + function d3_geom_polygonClosed(coordinates) { + var a = coordinates[0], b = coordinates[coordinates.length - 1]; + return !(a[0] - b[0] || a[1] - b[1]); + } + var d3_geom_voronoiEdges, d3_geom_voronoiCells, d3_geom_voronoiBeaches, d3_geom_voronoiBeachPool = [], d3_geom_voronoiFirstCircle, d3_geom_voronoiCircles, d3_geom_voronoiCirclePool = []; + function d3_geom_voronoiBeach() { + d3_geom_voronoiRedBlackNode(this); + this.edge = this.site = this.circle = null; + } + function d3_geom_voronoiCreateBeach(site) { + var beach = d3_geom_voronoiBeachPool.pop() || new d3_geom_voronoiBeach(); + beach.site = site; + return beach; + } + function d3_geom_voronoiDetachBeach(beach) { + d3_geom_voronoiDetachCircle(beach); + d3_geom_voronoiBeaches.remove(beach); + d3_geom_voronoiBeachPool.push(beach); + d3_geom_voronoiRedBlackNode(beach); + } + function d3_geom_voronoiRemoveBeach(beach) { + var circle = beach.circle, x = circle.x, y = circle.cy, vertex = { + x: x, + y: y + }, previous = beach.P, next = beach.N, disappearing = [ beach ]; + d3_geom_voronoiDetachBeach(beach); + var lArc = previous; + while (lArc.circle && abs(x - lArc.circle.x) < ε && abs(y - lArc.circle.cy) < ε) { + previous = lArc.P; + disappearing.unshift(lArc); + d3_geom_voronoiDetachBeach(lArc); + lArc = previous; + } + disappearing.unshift(lArc); + d3_geom_voronoiDetachCircle(lArc); + var rArc = next; + while (rArc.circle && abs(x - rArc.circle.x) < ε && abs(y - rArc.circle.cy) < ε) { + next = rArc.N; + disappearing.push(rArc); + d3_geom_voronoiDetachBeach(rArc); + rArc = next; + } + disappearing.push(rArc); + d3_geom_voronoiDetachCircle(rArc); + var nArcs = disappearing.length, iArc; + for (iArc = 1; iArc < nArcs; ++iArc) { + rArc = disappearing[iArc]; + lArc = disappearing[iArc - 1]; + d3_geom_voronoiSetEdgeEnd(rArc.edge, lArc.site, rArc.site, vertex); + } + lArc = disappearing[0]; + rArc = disappearing[nArcs - 1]; + rArc.edge = d3_geom_voronoiCreateEdge(lArc.site, rArc.site, null, vertex); + d3_geom_voronoiAttachCircle(lArc); + d3_geom_voronoiAttachCircle(rArc); + } + function d3_geom_voronoiAddBeach(site) { + var x = site.x, directrix = site.y, lArc, rArc, dxl, dxr, node = d3_geom_voronoiBeaches._; + while (node) { + dxl = d3_geom_voronoiLeftBreakPoint(node, directrix) - x; + if (dxl > ε) node = node.L; else { + dxr = x - d3_geom_voronoiRightBreakPoint(node, directrix); + if (dxr > ε) { + if (!node.R) { + lArc = node; + break; + } + node = node.R; + } else { + if (dxl > -ε) { + lArc = node.P; + rArc = node; + } else if (dxr > -ε) { + lArc = node; + rArc = node.N; + } else { + lArc = rArc = node; + } + break; + } + } + } + var newArc = d3_geom_voronoiCreateBeach(site); + d3_geom_voronoiBeaches.insert(lArc, newArc); + if (!lArc && !rArc) return; + if (lArc === rArc) { + d3_geom_voronoiDetachCircle(lArc); + rArc = d3_geom_voronoiCreateBeach(lArc.site); + d3_geom_voronoiBeaches.insert(newArc, rArc); + newArc.edge = rArc.edge = d3_geom_voronoiCreateEdge(lArc.site, newArc.site); + d3_geom_voronoiAttachCircle(lArc); + d3_geom_voronoiAttachCircle(rArc); + return; + } + if (!rArc) { + newArc.edge = d3_geom_voronoiCreateEdge(lArc.site, newArc.site); + return; + } + d3_geom_voronoiDetachCircle(lArc); + d3_geom_voronoiDetachCircle(rArc); + var lSite = lArc.site, ax = lSite.x, ay = lSite.y, bx = site.x - ax, by = site.y - ay, rSite = rArc.site, cx = rSite.x - ax, cy = rSite.y - ay, d = 2 * (bx * cy - by * cx), hb = bx * bx + by * by, hc = cx * cx + cy * cy, vertex = { + x: (cy * hb - by * hc) / d + ax, + y: (bx * hc - cx * hb) / d + ay + }; + d3_geom_voronoiSetEdgeEnd(rArc.edge, lSite, rSite, vertex); + newArc.edge = d3_geom_voronoiCreateEdge(lSite, site, null, vertex); + rArc.edge = d3_geom_voronoiCreateEdge(site, rSite, null, vertex); + d3_geom_voronoiAttachCircle(lArc); + d3_geom_voronoiAttachCircle(rArc); + } + function d3_geom_voronoiLeftBreakPoint(arc, directrix) { + var site = arc.site, rfocx = site.x, rfocy = site.y, pby2 = rfocy - directrix; + if (!pby2) return rfocx; + var lArc = arc.P; + if (!lArc) return -Infinity; + site = lArc.site; + var lfocx = site.x, lfocy = site.y, plby2 = lfocy - directrix; + if (!plby2) return lfocx; + var hl = lfocx - rfocx, aby2 = 1 / pby2 - 1 / plby2, b = hl / plby2; + if (aby2) return (-b + Math.sqrt(b * b - 2 * aby2 * (hl * hl / (-2 * plby2) - lfocy + plby2 / 2 + rfocy - pby2 / 2))) / aby2 + rfocx; + return (rfocx + lfocx) / 2; + } + function d3_geom_voronoiRightBreakPoint(arc, directrix) { + var rArc = arc.N; + if (rArc) return d3_geom_voronoiLeftBreakPoint(rArc, directrix); + var site = arc.site; + return site.y === directrix ? site.x : Infinity; + } + function d3_geom_voronoiCell(site) { + this.site = site; + this.edges = []; + } + d3_geom_voronoiCell.prototype.prepare = function() { + var halfEdges = this.edges, iHalfEdge = halfEdges.length, edge; + while (iHalfEdge--) { + edge = halfEdges[iHalfEdge].edge; + if (!edge.b || !edge.a) halfEdges.splice(iHalfEdge, 1); + } + halfEdges.sort(d3_geom_voronoiHalfEdgeOrder); + return halfEdges.length; + }; + function d3_geom_voronoiCloseCells(extent) { + var x0 = extent[0][0], x1 = extent[1][0], y0 = extent[0][1], y1 = extent[1][1], x2, y2, x3, y3, cells = d3_geom_voronoiCells, iCell = cells.length, cell, iHalfEdge, halfEdges, nHalfEdges, start, end; + while (iCell--) { + cell = cells[iCell]; + if (!cell || !cell.prepare()) continue; + halfEdges = cell.edges; + nHalfEdges = halfEdges.length; + iHalfEdge = 0; + while (iHalfEdge < nHalfEdges) { + end = halfEdges[iHalfEdge].end(), x3 = end.x, y3 = end.y; + start = halfEdges[++iHalfEdge % nHalfEdges].start(), x2 = start.x, y2 = start.y; + if (abs(x3 - x2) > ε || abs(y3 - y2) > ε) { + halfEdges.splice(iHalfEdge, 0, new d3_geom_voronoiHalfEdge(d3_geom_voronoiCreateBorderEdge(cell.site, end, abs(x3 - x0) < ε && y1 - y3 > ε ? { + x: x0, + y: abs(x2 - x0) < ε ? y2 : y1 + } : abs(y3 - y1) < ε && x1 - x3 > ε ? { + x: abs(y2 - y1) < ε ? x2 : x1, + y: y1 + } : abs(x3 - x1) < ε && y3 - y0 > ε ? { + x: x1, + y: abs(x2 - x1) < ε ? y2 : y0 + } : abs(y3 - y0) < ε && x3 - x0 > ε ? { + x: abs(y2 - y0) < ε ? x2 : x0, + y: y0 + } : null), cell.site, null)); + ++nHalfEdges; + } + } + } + } + function d3_geom_voronoiHalfEdgeOrder(a, b) { + return b.angle - a.angle; + } + function d3_geom_voronoiCircle() { + d3_geom_voronoiRedBlackNode(this); + this.x = this.y = this.arc = this.site = this.cy = null; + } + function d3_geom_voronoiAttachCircle(arc) { + var lArc = arc.P, rArc = arc.N; + if (!lArc || !rArc) return; + var lSite = lArc.site, cSite = arc.site, rSite = rArc.site; + if (lSite === rSite) return; + var bx = cSite.x, by = cSite.y, ax = lSite.x - bx, ay = lSite.y - by, cx = rSite.x - bx, cy = rSite.y - by; + var d = 2 * (ax * cy - ay * cx); + if (d >= -ε2) return; + var ha = ax * ax + ay * ay, hc = cx * cx + cy * cy, x = (cy * ha - ay * hc) / d, y = (ax * hc - cx * ha) / d, cy = y + by; + var circle = d3_geom_voronoiCirclePool.pop() || new d3_geom_voronoiCircle(); + circle.arc = arc; + circle.site = cSite; + circle.x = x + bx; + circle.y = cy + Math.sqrt(x * x + y * y); + circle.cy = cy; + arc.circle = circle; + var before = null, node = d3_geom_voronoiCircles._; + while (node) { + if (circle.y < node.y || circle.y === node.y && circle.x <= node.x) { + if (node.L) node = node.L; else { + before = node.P; + break; + } + } else { + if (node.R) node = node.R; else { + before = node; + break; + } + } + } + d3_geom_voronoiCircles.insert(before, circle); + if (!before) d3_geom_voronoiFirstCircle = circle; + } + function d3_geom_voronoiDetachCircle(arc) { + var circle = arc.circle; + if (circle) { + if (!circle.P) d3_geom_voronoiFirstCircle = circle.N; + d3_geom_voronoiCircles.remove(circle); + d3_geom_voronoiCirclePool.push(circle); + d3_geom_voronoiRedBlackNode(circle); + arc.circle = null; + } + } + function d3_geom_voronoiClipEdges(extent) { + var edges = d3_geom_voronoiEdges, clip = d3_geom_clipLine(extent[0][0], extent[0][1], extent[1][0], extent[1][1]), i = edges.length, e; + while (i--) { + e = edges[i]; + if (!d3_geom_voronoiConnectEdge(e, extent) || !clip(e) || abs(e.a.x - e.b.x) < ε && abs(e.a.y - e.b.y) < ε) { + e.a = e.b = null; + edges.splice(i, 1); + } + } + } + function d3_geom_voronoiConnectEdge(edge, extent) { + var vb = edge.b; + if (vb) return true; + var va = edge.a, x0 = extent[0][0], x1 = extent[1][0], y0 = extent[0][1], y1 = extent[1][1], lSite = edge.l, rSite = edge.r, lx = lSite.x, ly = lSite.y, rx = rSite.x, ry = rSite.y, fx = (lx + rx) / 2, fy = (ly + ry) / 2, fm, fb; + if (ry === ly) { + if (fx < x0 || fx >= x1) return; + if (lx > rx) { + if (!va) va = { + x: fx, + y: y0 + }; else if (va.y >= y1) return; + vb = { + x: fx, + y: y1 + }; + } else { + if (!va) va = { + x: fx, + y: y1 + }; else if (va.y < y0) return; + vb = { + x: fx, + y: y0 + }; + } + } else { + fm = (lx - rx) / (ry - ly); + fb = fy - fm * fx; + if (fm < -1 || fm > 1) { + if (lx > rx) { + if (!va) va = { + x: (y0 - fb) / fm, + y: y0 + }; else if (va.y >= y1) return; + vb = { + x: (y1 - fb) / fm, + y: y1 + }; + } else { + if (!va) va = { + x: (y1 - fb) / fm, + y: y1 + }; else if (va.y < y0) return; + vb = { + x: (y0 - fb) / fm, + y: y0 + }; + } + } else { + if (ly < ry) { + if (!va) va = { + x: x0, + y: fm * x0 + fb + }; else if (va.x >= x1) return; + vb = { + x: x1, + y: fm * x1 + fb + }; + } else { + if (!va) va = { + x: x1, + y: fm * x1 + fb + }; else if (va.x < x0) return; + vb = { + x: x0, + y: fm * x0 + fb + }; + } + } + } + edge.a = va; + edge.b = vb; + return true; + } + function d3_geom_voronoiEdge(lSite, rSite) { + this.l = lSite; + this.r = rSite; + this.a = this.b = null; + } + function d3_geom_voronoiCreateEdge(lSite, rSite, va, vb) { + var edge = new d3_geom_voronoiEdge(lSite, rSite); + d3_geom_voronoiEdges.push(edge); + if (va) d3_geom_voronoiSetEdgeEnd(edge, lSite, rSite, va); + if (vb) d3_geom_voronoiSetEdgeEnd(edge, rSite, lSite, vb); + d3_geom_voronoiCells[lSite.i].edges.push(new d3_geom_voronoiHalfEdge(edge, lSite, rSite)); + d3_geom_voronoiCells[rSite.i].edges.push(new d3_geom_voronoiHalfEdge(edge, rSite, lSite)); + return edge; + } + function d3_geom_voronoiCreateBorderEdge(lSite, va, vb) { + var edge = new d3_geom_voronoiEdge(lSite, null); + edge.a = va; + edge.b = vb; + d3_geom_voronoiEdges.push(edge); + return edge; + } + function d3_geom_voronoiSetEdgeEnd(edge, lSite, rSite, vertex) { + if (!edge.a && !edge.b) { + edge.a = vertex; + edge.l = lSite; + edge.r = rSite; + } else if (edge.l === rSite) { + edge.b = vertex; + } else { + edge.a = vertex; + } + } + function d3_geom_voronoiHalfEdge(edge, lSite, rSite) { + var va = edge.a, vb = edge.b; + this.edge = edge; + this.site = lSite; + this.angle = rSite ? Math.atan2(rSite.y - lSite.y, rSite.x - lSite.x) : edge.l === lSite ? Math.atan2(vb.x - va.x, va.y - vb.y) : Math.atan2(va.x - vb.x, vb.y - va.y); + } + d3_geom_voronoiHalfEdge.prototype = { + start: function() { + return this.edge.l === this.site ? this.edge.a : this.edge.b; + }, + end: function() { + return this.edge.l === this.site ? this.edge.b : this.edge.a; + } + }; + function d3_geom_voronoiRedBlackTree() { + this._ = null; + } + function d3_geom_voronoiRedBlackNode(node) { + node.U = node.C = node.L = node.R = node.P = node.N = null; + } + d3_geom_voronoiRedBlackTree.prototype = { + insert: function(after, node) { + var parent, grandpa, uncle; + if (after) { + node.P = after; + node.N = after.N; + if (after.N) after.N.P = node; + after.N = node; + if (after.R) { + after = after.R; + while (after.L) after = after.L; + after.L = node; + } else { + after.R = node; + } + parent = after; + } else if (this._) { + after = d3_geom_voronoiRedBlackFirst(this._); + node.P = null; + node.N = after; + after.P = after.L = node; + parent = after; + } else { + node.P = node.N = null; + this._ = node; + parent = null; + } + node.L = node.R = null; + node.U = parent; + node.C = true; + after = node; + while (parent && parent.C) { + grandpa = parent.U; + if (parent === grandpa.L) { + uncle = grandpa.R; + if (uncle && uncle.C) { + parent.C = uncle.C = false; + grandpa.C = true; + after = grandpa; + } else { + if (after === parent.R) { + d3_geom_voronoiRedBlackRotateLeft(this, parent); + after = parent; + parent = after.U; + } + parent.C = false; + grandpa.C = true; + d3_geom_voronoiRedBlackRotateRight(this, grandpa); + } + } else { + uncle = grandpa.L; + if (uncle && uncle.C) { + parent.C = uncle.C = false; + grandpa.C = true; + after = grandpa; + } else { + if (after === parent.L) { + d3_geom_voronoiRedBlackRotateRight(this, parent); + after = parent; + parent = after.U; + } + parent.C = false; + grandpa.C = true; + d3_geom_voronoiRedBlackRotateLeft(this, grandpa); + } + } + parent = after.U; + } + this._.C = false; + }, + remove: function(node) { + if (node.N) node.N.P = node.P; + if (node.P) node.P.N = node.N; + node.N = node.P = null; + var parent = node.U, sibling, left = node.L, right = node.R, next, red; + if (!left) next = right; else if (!right) next = left; else next = d3_geom_voronoiRedBlackFirst(right); + if (parent) { + if (parent.L === node) parent.L = next; else parent.R = next; + } else { + this._ = next; + } + if (left && right) { + red = next.C; + next.C = node.C; + next.L = left; + left.U = next; + if (next !== right) { + parent = next.U; + next.U = node.U; + node = next.R; + parent.L = node; + next.R = right; + right.U = next; + } else { + next.U = parent; + parent = next; + node = next.R; + } + } else { + red = node.C; + node = next; + } + if (node) node.U = parent; + if (red) return; + if (node && node.C) { + node.C = false; + return; + } + do { + if (node === this._) break; + if (node === parent.L) { + sibling = parent.R; + if (sibling.C) { + sibling.C = false; + parent.C = true; + d3_geom_voronoiRedBlackRotateLeft(this, parent); + sibling = parent.R; + } + if (sibling.L && sibling.L.C || sibling.R && sibling.R.C) { + if (!sibling.R || !sibling.R.C) { + sibling.L.C = false; + sibling.C = true; + d3_geom_voronoiRedBlackRotateRight(this, sibling); + sibling = parent.R; + } + sibling.C = parent.C; + parent.C = sibling.R.C = false; + d3_geom_voronoiRedBlackRotateLeft(this, parent); + node = this._; + break; + } + } else { + sibling = parent.L; + if (sibling.C) { + sibling.C = false; + parent.C = true; + d3_geom_voronoiRedBlackRotateRight(this, parent); + sibling = parent.L; + } + if (sibling.L && sibling.L.C || sibling.R && sibling.R.C) { + if (!sibling.L || !sibling.L.C) { + sibling.R.C = false; + sibling.C = true; + d3_geom_voronoiRedBlackRotateLeft(this, sibling); + sibling = parent.L; + } + sibling.C = parent.C; + parent.C = sibling.L.C = false; + d3_geom_voronoiRedBlackRotateRight(this, parent); + node = this._; + break; + } + } + sibling.C = true; + node = parent; + parent = parent.U; + } while (!node.C); + if (node) node.C = false; + } + }; + function d3_geom_voronoiRedBlackRotateLeft(tree, node) { + var p = node, q = node.R, parent = p.U; + if (parent) { + if (parent.L === p) parent.L = q; else parent.R = q; + } else { + tree._ = q; + } + q.U = parent; + p.U = q; + p.R = q.L; + if (p.R) p.R.U = p; + q.L = p; + } + function d3_geom_voronoiRedBlackRotateRight(tree, node) { + var p = node, q = node.L, parent = p.U; + if (parent) { + if (parent.L === p) parent.L = q; else parent.R = q; + } else { + tree._ = q; + } + q.U = parent; + p.U = q; + p.L = q.R; + if (p.L) p.L.U = p; + q.R = p; + } + function d3_geom_voronoiRedBlackFirst(node) { + while (node.L) node = node.L; + return node; + } + function d3_geom_voronoi(sites, bbox) { + var site = sites.sort(d3_geom_voronoiVertexOrder).pop(), x0, y0, circle; + d3_geom_voronoiEdges = []; + d3_geom_voronoiCells = new Array(sites.length); + d3_geom_voronoiBeaches = new d3_geom_voronoiRedBlackTree(); + d3_geom_voronoiCircles = new d3_geom_voronoiRedBlackTree(); + while (true) { + circle = d3_geom_voronoiFirstCircle; + if (site && (!circle || site.y < circle.y || site.y === circle.y && site.x < circle.x)) { + if (site.x !== x0 || site.y !== y0) { + d3_geom_voronoiCells[site.i] = new d3_geom_voronoiCell(site); + d3_geom_voronoiAddBeach(site); + x0 = site.x, y0 = site.y; + } + site = sites.pop(); + } else if (circle) { + d3_geom_voronoiRemoveBeach(circle.arc); + } else { + break; + } + } + if (bbox) d3_geom_voronoiClipEdges(bbox), d3_geom_voronoiCloseCells(bbox); + var diagram = { + cells: d3_geom_voronoiCells, + edges: d3_geom_voronoiEdges + }; + d3_geom_voronoiBeaches = d3_geom_voronoiCircles = d3_geom_voronoiEdges = d3_geom_voronoiCells = null; + return diagram; + } + function d3_geom_voronoiVertexOrder(a, b) { + return b.y - a.y || b.x - a.x; + } + d3.geom.voronoi = function(points) { + var x = d3_geom_pointX, y = d3_geom_pointY, fx = x, fy = y, clipExtent = d3_geom_voronoiClipExtent; + if (points) return voronoi(points); + function voronoi(data) { + var polygons = new Array(data.length), x0 = clipExtent[0][0], y0 = clipExtent[0][1], x1 = clipExtent[1][0], y1 = clipExtent[1][1]; + d3_geom_voronoi(sites(data), clipExtent).cells.forEach(function(cell, i) { + var edges = cell.edges, site = cell.site, polygon = polygons[i] = edges.length ? edges.map(function(e) { + var s = e.start(); + return [ s.x, s.y ]; + }) : site.x >= x0 && site.x <= x1 && site.y >= y0 && site.y <= y1 ? [ [ x0, y1 ], [ x1, y1 ], [ x1, y0 ], [ x0, y0 ] ] : []; + polygon.point = data[i]; + }); + return polygons; + } + function sites(data) { + return data.map(function(d, i) { + return { + x: Math.round(fx(d, i) / ε) * ε, + y: Math.round(fy(d, i) / ε) * ε, + i: i + }; + }); + } + voronoi.links = function(data) { + return d3_geom_voronoi(sites(data)).edges.filter(function(edge) { + return edge.l && edge.r; + }).map(function(edge) { + return { + source: data[edge.l.i], + target: data[edge.r.i] + }; + }); + }; + voronoi.triangles = function(data) { + var triangles = []; + d3_geom_voronoi(sites(data)).cells.forEach(function(cell, i) { + var site = cell.site, edges = cell.edges.sort(d3_geom_voronoiHalfEdgeOrder), j = -1, m = edges.length, e0, s0, e1 = edges[m - 1].edge, s1 = e1.l === site ? e1.r : e1.l; + while (++j < m) { + e0 = e1; + s0 = s1; + e1 = edges[j].edge; + s1 = e1.l === site ? e1.r : e1.l; + if (i < s0.i && i < s1.i && d3_geom_voronoiTriangleArea(site, s0, s1) < 0) { + triangles.push([ data[i], data[s0.i], data[s1.i] ]); + } + } + }); + return triangles; + }; + voronoi.x = function(_) { + return arguments.length ? (fx = d3_functor(x = _), voronoi) : x; + }; + voronoi.y = function(_) { + return arguments.length ? (fy = d3_functor(y = _), voronoi) : y; + }; + voronoi.clipExtent = function(_) { + if (!arguments.length) return clipExtent === d3_geom_voronoiClipExtent ? null : clipExtent; + clipExtent = _ == null ? d3_geom_voronoiClipExtent : _; + return voronoi; + }; + voronoi.size = function(_) { + if (!arguments.length) return clipExtent === d3_geom_voronoiClipExtent ? null : clipExtent && clipExtent[1]; + return voronoi.clipExtent(_ && [ [ 0, 0 ], _ ]); + }; + return voronoi; + }; + var d3_geom_voronoiClipExtent = [ [ -1e6, -1e6 ], [ 1e6, 1e6 ] ]; + function d3_geom_voronoiTriangleArea(a, b, c) { + return (a.x - c.x) * (b.y - a.y) - (a.x - b.x) * (c.y - a.y); + } + d3.geom.delaunay = function(vertices) { + return d3.geom.voronoi().triangles(vertices); + }; + d3.geom.quadtree = function(points, x1, y1, x2, y2) { + var x = d3_geom_pointX, y = d3_geom_pointY, compat; + if (compat = arguments.length) { + x = d3_geom_quadtreeCompatX; + y = d3_geom_quadtreeCompatY; + if (compat === 3) { + y2 = y1; + x2 = x1; + y1 = x1 = 0; + } + return quadtree(points); + } + function quadtree(data) { + var d, fx = d3_functor(x), fy = d3_functor(y), xs, ys, i, n, x1_, y1_, x2_, y2_; + if (x1 != null) { + x1_ = x1, y1_ = y1, x2_ = x2, y2_ = y2; + } else { + x2_ = y2_ = -(x1_ = y1_ = Infinity); + xs = [], ys = []; + n = data.length; + if (compat) for (i = 0; i < n; ++i) { + d = data[i]; + if (d.x < x1_) x1_ = d.x; + if (d.y < y1_) y1_ = d.y; + if (d.x > x2_) x2_ = d.x; + if (d.y > y2_) y2_ = d.y; + xs.push(d.x); + ys.push(d.y); + } else for (i = 0; i < n; ++i) { + var x_ = +fx(d = data[i], i), y_ = +fy(d, i); + if (x_ < x1_) x1_ = x_; + if (y_ < y1_) y1_ = y_; + if (x_ > x2_) x2_ = x_; + if (y_ > y2_) y2_ = y_; + xs.push(x_); + ys.push(y_); + } + } + var dx = x2_ - x1_, dy = y2_ - y1_; + if (dx > dy) y2_ = y1_ + dx; else x2_ = x1_ + dy; + function insert(n, d, x, y, x1, y1, x2, y2) { + if (isNaN(x) || isNaN(y)) return; + if (n.leaf) { + var nx = n.x, ny = n.y; + if (nx != null) { + if (abs(nx - x) + abs(ny - y) < .01) { + insertChild(n, d, x, y, x1, y1, x2, y2); + } else { + var nPoint = n.point; + n.x = n.y = n.point = null; + insertChild(n, nPoint, nx, ny, x1, y1, x2, y2); + insertChild(n, d, x, y, x1, y1, x2, y2); + } + } else { + n.x = x, n.y = y, n.point = d; + } + } else { + insertChild(n, d, x, y, x1, y1, x2, y2); + } + } + function insertChild(n, d, x, y, x1, y1, x2, y2) { + var xm = (x1 + x2) * .5, ym = (y1 + y2) * .5, right = x >= xm, below = y >= ym, i = below << 1 | right; + n.leaf = false; + n = n.nodes[i] || (n.nodes[i] = d3_geom_quadtreeNode()); + if (right) x1 = xm; else x2 = xm; + if (below) y1 = ym; else y2 = ym; + insert(n, d, x, y, x1, y1, x2, y2); + } + var root = d3_geom_quadtreeNode(); + root.add = function(d) { + insert(root, d, +fx(d, ++i), +fy(d, i), x1_, y1_, x2_, y2_); + }; + root.visit = function(f) { + d3_geom_quadtreeVisit(f, root, x1_, y1_, x2_, y2_); + }; + root.find = function(point) { + return d3_geom_quadtreeFind(root, point[0], point[1], x1_, y1_, x2_, y2_); + }; + i = -1; + if (x1 == null) { + while (++i < n) { + insert(root, data[i], xs[i], ys[i], x1_, y1_, x2_, y2_); + } + --i; + } else data.forEach(root.add); + xs = ys = data = d = null; + return root; + } + quadtree.x = function(_) { + return arguments.length ? (x = _, quadtree) : x; + }; + quadtree.y = function(_) { + return arguments.length ? (y = _, quadtree) : y; + }; + quadtree.extent = function(_) { + if (!arguments.length) return x1 == null ? null : [ [ x1, y1 ], [ x2, y2 ] ]; + if (_ == null) x1 = y1 = x2 = y2 = null; else x1 = +_[0][0], y1 = +_[0][1], x2 = +_[1][0], + y2 = +_[1][1]; + return quadtree; + }; + quadtree.size = function(_) { + if (!arguments.length) return x1 == null ? null : [ x2 - x1, y2 - y1 ]; + if (_ == null) x1 = y1 = x2 = y2 = null; else x1 = y1 = 0, x2 = +_[0], y2 = +_[1]; + return quadtree; + }; + return quadtree; + }; + function d3_geom_quadtreeCompatX(d) { + return d.x; + } + function d3_geom_quadtreeCompatY(d) { + return d.y; + } + function d3_geom_quadtreeNode() { + return { + leaf: true, + nodes: [], + point: null, + x: null, + y: null + }; + } + function d3_geom_quadtreeVisit(f, node, x1, y1, x2, y2) { + if (!f(node, x1, y1, x2, y2)) { + var sx = (x1 + x2) * .5, sy = (y1 + y2) * .5, children = node.nodes; + if (children[0]) d3_geom_quadtreeVisit(f, children[0], x1, y1, sx, sy); + if (children[1]) d3_geom_quadtreeVisit(f, children[1], sx, y1, x2, sy); + if (children[2]) d3_geom_quadtreeVisit(f, children[2], x1, sy, sx, y2); + if (children[3]) d3_geom_quadtreeVisit(f, children[3], sx, sy, x2, y2); + } + } + function d3_geom_quadtreeFind(root, x, y, x0, y0, x3, y3) { + var minDistance2 = Infinity, closestPoint; + (function find(node, x1, y1, x2, y2) { + if (x1 > x3 || y1 > y3 || x2 < x0 || y2 < y0) return; + if (point = node.point) { + var point, dx = x - point[0], dy = y - point[1], distance2 = dx * dx + dy * dy; + if (distance2 < minDistance2) { + var distance = Math.sqrt(minDistance2 = distance2); + x0 = x - distance, y0 = y - distance; + x3 = x + distance, y3 = y + distance; + closestPoint = point; + } + } + var children = node.nodes, xm = (x1 + x2) * .5, ym = (y1 + y2) * .5, right = x >= xm, below = y >= ym; + for (var i = below << 1 | right, j = i + 4; i < j; ++i) { + if (node = children[i & 3]) switch (i & 3) { + case 0: + find(node, x1, y1, xm, ym); + break; + + case 1: + find(node, xm, y1, x2, ym); + break; + + case 2: + find(node, x1, ym, xm, y2); + break; + + case 3: + find(node, xm, ym, x2, y2); + break; + } + } + })(root, x0, y0, x3, y3); + return closestPoint; + } + d3.interpolateRgb = d3_interpolateRgb; + function d3_interpolateRgb(a, b) { + a = d3.rgb(a); + b = d3.rgb(b); + var ar = a.r, ag = a.g, ab = a.b, br = b.r - ar, bg = b.g - ag, bb = b.b - ab; + return function(t) { + return "#" + d3_rgb_hex(Math.round(ar + br * t)) + d3_rgb_hex(Math.round(ag + bg * t)) + d3_rgb_hex(Math.round(ab + bb * t)); + }; + } + d3.interpolateObject = d3_interpolateObject; + function d3_interpolateObject(a, b) { + var i = {}, c = {}, k; + for (k in a) { + if (k in b) { + i[k] = d3_interpolate(a[k], b[k]); + } else { + c[k] = a[k]; + } + } + for (k in b) { + if (!(k in a)) { + c[k] = b[k]; + } + } + return function(t) { + for (k in i) c[k] = i[k](t); + return c; + }; + } + d3.interpolateNumber = d3_interpolateNumber; + function d3_interpolateNumber(a, b) { + a = +a, b = +b; + return function(t) { + return a * (1 - t) + b * t; + }; + } + d3.interpolateString = d3_interpolateString; + function d3_interpolateString(a, b) { + var bi = d3_interpolate_numberA.lastIndex = d3_interpolate_numberB.lastIndex = 0, am, bm, bs, i = -1, s = [], q = []; + a = a + "", b = b + ""; + while ((am = d3_interpolate_numberA.exec(a)) && (bm = d3_interpolate_numberB.exec(b))) { + if ((bs = bm.index) > bi) { + bs = b.slice(bi, bs); + if (s[i]) s[i] += bs; else s[++i] = bs; + } + if ((am = am[0]) === (bm = bm[0])) { + if (s[i]) s[i] += bm; else s[++i] = bm; + } else { + s[++i] = null; + q.push({ + i: i, + x: d3_interpolateNumber(am, bm) + }); + } + bi = d3_interpolate_numberB.lastIndex; + } + if (bi < b.length) { + bs = b.slice(bi); + if (s[i]) s[i] += bs; else s[++i] = bs; + } + return s.length < 2 ? q[0] ? (b = q[0].x, function(t) { + return b(t) + ""; + }) : function() { + return b; + } : (b = q.length, function(t) { + for (var i = 0, o; i < b; ++i) s[(o = q[i]).i] = o.x(t); + return s.join(""); + }); + } + var d3_interpolate_numberA = /[-+]?(?:\d+\.?\d*|\.?\d+)(?:[eE][-+]?\d+)?/g, d3_interpolate_numberB = new RegExp(d3_interpolate_numberA.source, "g"); + d3.interpolate = d3_interpolate; + function d3_interpolate(a, b) { + var i = d3.interpolators.length, f; + while (--i >= 0 && !(f = d3.interpolators[i](a, b))) ; + return f; + } + d3.interpolators = [ function(a, b) { + var t = typeof b; + return (t === "string" ? d3_rgb_names.has(b) || /^(#|rgb\(|hsl\()/.test(b) ? d3_interpolateRgb : d3_interpolateString : b instanceof d3_color ? d3_interpolateRgb : Array.isArray(b) ? d3_interpolateArray : t === "object" && isNaN(b) ? d3_interpolateObject : d3_interpolateNumber)(a, b); + } ]; + d3.interpolateArray = d3_interpolateArray; + function d3_interpolateArray(a, b) { + var x = [], c = [], na = a.length, nb = b.length, n0 = Math.min(a.length, b.length), i; + for (i = 0; i < n0; ++i) x.push(d3_interpolate(a[i], b[i])); + for (;i < na; ++i) c[i] = a[i]; + for (;i < nb; ++i) c[i] = b[i]; + return function(t) { + for (i = 0; i < n0; ++i) c[i] = x[i](t); + return c; + }; + } + var d3_ease_default = function() { + return d3_identity; + }; + var d3_ease = d3.map({ + linear: d3_ease_default, + poly: d3_ease_poly, + quad: function() { + return d3_ease_quad; + }, + cubic: function() { + return d3_ease_cubic; + }, + sin: function() { + return d3_ease_sin; + }, + exp: function() { + return d3_ease_exp; + }, + circle: function() { + return d3_ease_circle; + }, + elastic: d3_ease_elastic, + back: d3_ease_back, + bounce: function() { + return d3_ease_bounce; + } + }); + var d3_ease_mode = d3.map({ + "in": d3_identity, + out: d3_ease_reverse, + "in-out": d3_ease_reflect, + "out-in": function(f) { + return d3_ease_reflect(d3_ease_reverse(f)); + } + }); + d3.ease = function(name) { + var i = name.indexOf("-"), t = i >= 0 ? name.slice(0, i) : name, m = i >= 0 ? name.slice(i + 1) : "in"; + t = d3_ease.get(t) || d3_ease_default; + m = d3_ease_mode.get(m) || d3_identity; + return d3_ease_clamp(m(t.apply(null, d3_arraySlice.call(arguments, 1)))); + }; + function d3_ease_clamp(f) { + return function(t) { + return t <= 0 ? 0 : t >= 1 ? 1 : f(t); + }; + } + function d3_ease_reverse(f) { + return function(t) { + return 1 - f(1 - t); + }; + } + function d3_ease_reflect(f) { + return function(t) { + return .5 * (t < .5 ? f(2 * t) : 2 - f(2 - 2 * t)); + }; + } + function d3_ease_quad(t) { + return t * t; + } + function d3_ease_cubic(t) { + return t * t * t; + } + function d3_ease_cubicInOut(t) { + if (t <= 0) return 0; + if (t >= 1) return 1; + var t2 = t * t, t3 = t2 * t; + return 4 * (t < .5 ? t3 : 3 * (t - t2) + t3 - .75); + } + function d3_ease_poly(e) { + return function(t) { + return Math.pow(t, e); + }; + } + function d3_ease_sin(t) { + return 1 - Math.cos(t * halfπ); + } + function d3_ease_exp(t) { + return Math.pow(2, 10 * (t - 1)); + } + function d3_ease_circle(t) { + return 1 - Math.sqrt(1 - t * t); + } + function d3_ease_elastic(a, p) { + var s; + if (arguments.length < 2) p = .45; + if (arguments.length) s = p / τ * Math.asin(1 / a); else a = 1, s = p / 4; + return function(t) { + return 1 + a * Math.pow(2, -10 * t) * Math.sin((t - s) * τ / p); + }; + } + function d3_ease_back(s) { + if (!s) s = 1.70158; + return function(t) { + return t * t * ((s + 1) * t - s); + }; + } + function d3_ease_bounce(t) { + return t < 1 / 2.75 ? 7.5625 * t * t : t < 2 / 2.75 ? 7.5625 * (t -= 1.5 / 2.75) * t + .75 : t < 2.5 / 2.75 ? 7.5625 * (t -= 2.25 / 2.75) * t + .9375 : 7.5625 * (t -= 2.625 / 2.75) * t + .984375; + } + d3.interpolateHcl = d3_interpolateHcl; + function d3_interpolateHcl(a, b) { + a = d3.hcl(a); + b = d3.hcl(b); + var ah = a.h, ac = a.c, al = a.l, bh = b.h - ah, bc = b.c - ac, bl = b.l - al; + if (isNaN(bc)) bc = 0, ac = isNaN(ac) ? b.c : ac; + if (isNaN(bh)) bh = 0, ah = isNaN(ah) ? b.h : ah; else if (bh > 180) bh -= 360; else if (bh < -180) bh += 360; + return function(t) { + return d3_hcl_lab(ah + bh * t, ac + bc * t, al + bl * t) + ""; + }; + } + d3.interpolateHsl = d3_interpolateHsl; + function d3_interpolateHsl(a, b) { + a = d3.hsl(a); + b = d3.hsl(b); + var ah = a.h, as = a.s, al = a.l, bh = b.h - ah, bs = b.s - as, bl = b.l - al; + if (isNaN(bs)) bs = 0, as = isNaN(as) ? b.s : as; + if (isNaN(bh)) bh = 0, ah = isNaN(ah) ? b.h : ah; else if (bh > 180) bh -= 360; else if (bh < -180) bh += 360; + return function(t) { + return d3_hsl_rgb(ah + bh * t, as + bs * t, al + bl * t) + ""; + }; + } + d3.interpolateLab = d3_interpolateLab; + function d3_interpolateLab(a, b) { + a = d3.lab(a); + b = d3.lab(b); + var al = a.l, aa = a.a, ab = a.b, bl = b.l - al, ba = b.a - aa, bb = b.b - ab; + return function(t) { + return d3_lab_rgb(al + bl * t, aa + ba * t, ab + bb * t) + ""; + }; + } + d3.interpolateRound = d3_interpolateRound; + function d3_interpolateRound(a, b) { + b -= a; + return function(t) { + return Math.round(a + b * t); + }; + } + d3.transform = function(string) { + var g = d3_document.createElementNS(d3.ns.prefix.svg, "g"); + return (d3.transform = function(string) { + if (string != null) { + g.setAttribute("transform", string); + var t = g.transform.baseVal.consolidate(); + } + return new d3_transform(t ? t.matrix : d3_transformIdentity); + })(string); + }; + function d3_transform(m) { + var r0 = [ m.a, m.b ], r1 = [ m.c, m.d ], kx = d3_transformNormalize(r0), kz = d3_transformDot(r0, r1), ky = d3_transformNormalize(d3_transformCombine(r1, r0, -kz)) || 0; + if (r0[0] * r1[1] < r1[0] * r0[1]) { + r0[0] *= -1; + r0[1] *= -1; + kx *= -1; + kz *= -1; + } + this.rotate = (kx ? Math.atan2(r0[1], r0[0]) : Math.atan2(-r1[0], r1[1])) * d3_degrees; + this.translate = [ m.e, m.f ]; + this.scale = [ kx, ky ]; + this.skew = ky ? Math.atan2(kz, ky) * d3_degrees : 0; + } + d3_transform.prototype.toString = function() { + return "translate(" + this.translate + ")rotate(" + this.rotate + ")skewX(" + this.skew + ")scale(" + this.scale + ")"; + }; + function d3_transformDot(a, b) { + return a[0] * b[0] + a[1] * b[1]; + } + function d3_transformNormalize(a) { + var k = Math.sqrt(d3_transformDot(a, a)); + if (k) { + a[0] /= k; + a[1] /= k; + } + return k; + } + function d3_transformCombine(a, b, k) { + a[0] += k * b[0]; + a[1] += k * b[1]; + return a; + } + var d3_transformIdentity = { + a: 1, + b: 0, + c: 0, + d: 1, + e: 0, + f: 0 + }; + d3.interpolateTransform = d3_interpolateTransform; + function d3_interpolateTransform(a, b) { + var s = [], q = [], n, A = d3.transform(a), B = d3.transform(b), ta = A.translate, tb = B.translate, ra = A.rotate, rb = B.rotate, wa = A.skew, wb = B.skew, ka = A.scale, kb = B.scale; + if (ta[0] != tb[0] || ta[1] != tb[1]) { + s.push("translate(", null, ",", null, ")"); + q.push({ + i: 1, + x: d3_interpolateNumber(ta[0], tb[0]) + }, { + i: 3, + x: d3_interpolateNumber(ta[1], tb[1]) + }); + } else if (tb[0] || tb[1]) { + s.push("translate(" + tb + ")"); + } else { + s.push(""); + } + if (ra != rb) { + if (ra - rb > 180) rb += 360; else if (rb - ra > 180) ra += 360; + q.push({ + i: s.push(s.pop() + "rotate(", null, ")") - 2, + x: d3_interpolateNumber(ra, rb) + }); + } else if (rb) { + s.push(s.pop() + "rotate(" + rb + ")"); + } + if (wa != wb) { + q.push({ + i: s.push(s.pop() + "skewX(", null, ")") - 2, + x: d3_interpolateNumber(wa, wb) + }); + } else if (wb) { + s.push(s.pop() + "skewX(" + wb + ")"); + } + if (ka[0] != kb[0] || ka[1] != kb[1]) { + n = s.push(s.pop() + "scale(", null, ",", null, ")"); + q.push({ + i: n - 4, + x: d3_interpolateNumber(ka[0], kb[0]) + }, { + i: n - 2, + x: d3_interpolateNumber(ka[1], kb[1]) + }); + } else if (kb[0] != 1 || kb[1] != 1) { + s.push(s.pop() + "scale(" + kb + ")"); + } + n = q.length; + return function(t) { + var i = -1, o; + while (++i < n) s[(o = q[i]).i] = o.x(t); + return s.join(""); + }; + } + function d3_uninterpolateNumber(a, b) { + b = (b -= a = +a) || 1 / b; + return function(x) { + return (x - a) / b; + }; + } + function d3_uninterpolateClamp(a, b) { + b = (b -= a = +a) || 1 / b; + return function(x) { + return Math.max(0, Math.min(1, (x - a) / b)); + }; + } + d3.layout = {}; + d3.layout.bundle = function() { + return function(links) { + var paths = [], i = -1, n = links.length; + while (++i < n) paths.push(d3_layout_bundlePath(links[i])); + return paths; + }; + }; + function d3_layout_bundlePath(link) { + var start = link.source, end = link.target, lca = d3_layout_bundleLeastCommonAncestor(start, end), points = [ start ]; + while (start !== lca) { + start = start.parent; + points.push(start); + } + var k = points.length; + while (end !== lca) { + points.splice(k, 0, end); + end = end.parent; + } + return points; + } + function d3_layout_bundleAncestors(node) { + var ancestors = [], parent = node.parent; + while (parent != null) { + ancestors.push(node); + node = parent; + parent = parent.parent; + } + ancestors.push(node); + return ancestors; + } + function d3_layout_bundleLeastCommonAncestor(a, b) { + if (a === b) return a; + var aNodes = d3_layout_bundleAncestors(a), bNodes = d3_layout_bundleAncestors(b), aNode = aNodes.pop(), bNode = bNodes.pop(), sharedNode = null; + while (aNode === bNode) { + sharedNode = aNode; + aNode = aNodes.pop(); + bNode = bNodes.pop(); + } + return sharedNode; + } + d3.layout.chord = function() { + var chord = {}, chords, groups, matrix, n, padding = 0, sortGroups, sortSubgroups, sortChords; + function relayout() { + var subgroups = {}, groupSums = [], groupIndex = d3.range(n), subgroupIndex = [], k, x, x0, i, j; + chords = []; + groups = []; + k = 0, i = -1; + while (++i < n) { + x = 0, j = -1; + while (++j < n) { + x += matrix[i][j]; + } + groupSums.push(x); + subgroupIndex.push(d3.range(n)); + k += x; + } + if (sortGroups) { + groupIndex.sort(function(a, b) { + return sortGroups(groupSums[a], groupSums[b]); + }); + } + if (sortSubgroups) { + subgroupIndex.forEach(function(d, i) { + d.sort(function(a, b) { + return sortSubgroups(matrix[i][a], matrix[i][b]); + }); + }); + } + k = (τ - padding * n) / k; + x = 0, i = -1; + while (++i < n) { + x0 = x, j = -1; + while (++j < n) { + var di = groupIndex[i], dj = subgroupIndex[di][j], v = matrix[di][dj], a0 = x, a1 = x += v * k; + subgroups[di + "-" + dj] = { + index: di, + subindex: dj, + startAngle: a0, + endAngle: a1, + value: v + }; + } + groups[di] = { + index: di, + startAngle: x0, + endAngle: x, + value: (x - x0) / k + }; + x += padding; + } + i = -1; + while (++i < n) { + j = i - 1; + while (++j < n) { + var source = subgroups[i + "-" + j], target = subgroups[j + "-" + i]; + if (source.value || target.value) { + chords.push(source.value < target.value ? { + source: target, + target: source + } : { + source: source, + target: target + }); + } + } + } + if (sortChords) resort(); + } + function resort() { + chords.sort(function(a, b) { + return sortChords((a.source.value + a.target.value) / 2, (b.source.value + b.target.value) / 2); + }); + } + chord.matrix = function(x) { + if (!arguments.length) return matrix; + n = (matrix = x) && matrix.length; + chords = groups = null; + return chord; + }; + chord.padding = function(x) { + if (!arguments.length) return padding; + padding = x; + chords = groups = null; + return chord; + }; + chord.sortGroups = function(x) { + if (!arguments.length) return sortGroups; + sortGroups = x; + chords = groups = null; + return chord; + }; + chord.sortSubgroups = function(x) { + if (!arguments.length) return sortSubgroups; + sortSubgroups = x; + chords = null; + return chord; + }; + chord.sortChords = function(x) { + if (!arguments.length) return sortChords; + sortChords = x; + if (chords) resort(); + return chord; + }; + chord.chords = function() { + if (!chords) relayout(); + return chords; + }; + chord.groups = function() { + if (!groups) relayout(); + return groups; + }; + return chord; + }; + d3.layout.force = function() { + var force = {}, event = d3.dispatch("start", "tick", "end"), size = [ 1, 1 ], drag, alpha, friction = .9, linkDistance = d3_layout_forceLinkDistance, linkStrength = d3_layout_forceLinkStrength, charge = -30, chargeDistance2 = d3_layout_forceChargeDistance2, gravity = .1, theta2 = .64, nodes = [], links = [], distances, strengths, charges; + function repulse(node) { + return function(quad, x1, _, x2) { + if (quad.point !== node) { + var dx = quad.cx - node.x, dy = quad.cy - node.y, dw = x2 - x1, dn = dx * dx + dy * dy; + if (dw * dw / theta2 < dn) { + if (dn < chargeDistance2) { + var k = quad.charge / dn; + node.px -= dx * k; + node.py -= dy * k; + } + return true; + } + if (quad.point && dn && dn < chargeDistance2) { + var k = quad.pointCharge / dn; + node.px -= dx * k; + node.py -= dy * k; + } + } + return !quad.charge; + }; + } + force.tick = function() { + if ((alpha *= .99) < .005) { + event.end({ + type: "end", + alpha: alpha = 0 + }); + return true; + } + var n = nodes.length, m = links.length, q, i, o, s, t, l, k, x, y; + for (i = 0; i < m; ++i) { + o = links[i]; + s = o.source; + t = o.target; + x = t.x - s.x; + y = t.y - s.y; + if (l = x * x + y * y) { + l = alpha * strengths[i] * ((l = Math.sqrt(l)) - distances[i]) / l; + x *= l; + y *= l; + t.x -= x * (k = s.weight / (t.weight + s.weight)); + t.y -= y * k; + s.x += x * (k = 1 - k); + s.y += y * k; + } + } + if (k = alpha * gravity) { + x = size[0] / 2; + y = size[1] / 2; + i = -1; + if (k) while (++i < n) { + o = nodes[i]; + o.x += (x - o.x) * k; + o.y += (y - o.y) * k; + } + } + if (charge) { + d3_layout_forceAccumulate(q = d3.geom.quadtree(nodes), alpha, charges); + i = -1; + while (++i < n) { + if (!(o = nodes[i]).fixed) { + q.visit(repulse(o)); + } + } + } + i = -1; + while (++i < n) { + o = nodes[i]; + if (o.fixed) { + o.x = o.px; + o.y = o.py; + } else { + o.x -= (o.px - (o.px = o.x)) * friction; + o.y -= (o.py - (o.py = o.y)) * friction; + } + } + event.tick({ + type: "tick", + alpha: alpha + }); + }; + force.nodes = function(x) { + if (!arguments.length) return nodes; + nodes = x; + return force; + }; + force.links = function(x) { + if (!arguments.length) return links; + links = x; + return force; + }; + force.size = function(x) { + if (!arguments.length) return size; + size = x; + return force; + }; + force.linkDistance = function(x) { + if (!arguments.length) return linkDistance; + linkDistance = typeof x === "function" ? x : +x; + return force; + }; + force.distance = force.linkDistance; + force.linkStrength = function(x) { + if (!arguments.length) return linkStrength; + linkStrength = typeof x === "function" ? x : +x; + return force; + }; + force.friction = function(x) { + if (!arguments.length) return friction; + friction = +x; + return force; + }; + force.charge = function(x) { + if (!arguments.length) return charge; + charge = typeof x === "function" ? x : +x; + return force; + }; + force.chargeDistance = function(x) { + if (!arguments.length) return Math.sqrt(chargeDistance2); + chargeDistance2 = x * x; + return force; + }; + force.gravity = function(x) { + if (!arguments.length) return gravity; + gravity = +x; + return force; + }; + force.theta = function(x) { + if (!arguments.length) return Math.sqrt(theta2); + theta2 = x * x; + return force; + }; + force.alpha = function(x) { + if (!arguments.length) return alpha; + x = +x; + if (alpha) { + if (x > 0) alpha = x; else alpha = 0; + } else if (x > 0) { + event.start({ + type: "start", + alpha: alpha = x + }); + d3.timer(force.tick); + } + return force; + }; + force.start = function() { + var i, n = nodes.length, m = links.length, w = size[0], h = size[1], neighbors, o; + for (i = 0; i < n; ++i) { + (o = nodes[i]).index = i; + o.weight = 0; + } + for (i = 0; i < m; ++i) { + o = links[i]; + if (typeof o.source == "number") o.source = nodes[o.source]; + if (typeof o.target == "number") o.target = nodes[o.target]; + ++o.source.weight; + ++o.target.weight; + } + for (i = 0; i < n; ++i) { + o = nodes[i]; + if (isNaN(o.x)) o.x = position("x", w); + if (isNaN(o.y)) o.y = position("y", h); + if (isNaN(o.px)) o.px = o.x; + if (isNaN(o.py)) o.py = o.y; + } + distances = []; + if (typeof linkDistance === "function") for (i = 0; i < m; ++i) distances[i] = +linkDistance.call(this, links[i], i); else for (i = 0; i < m; ++i) distances[i] = linkDistance; + strengths = []; + if (typeof linkStrength === "function") for (i = 0; i < m; ++i) strengths[i] = +linkStrength.call(this, links[i], i); else for (i = 0; i < m; ++i) strengths[i] = linkStrength; + charges = []; + if (typeof charge === "function") for (i = 0; i < n; ++i) charges[i] = +charge.call(this, nodes[i], i); else for (i = 0; i < n; ++i) charges[i] = charge; + function position(dimension, size) { + if (!neighbors) { + neighbors = new Array(n); + for (j = 0; j < n; ++j) { + neighbors[j] = []; + } + for (j = 0; j < m; ++j) { + var o = links[j]; + neighbors[o.source.index].push(o.target); + neighbors[o.target.index].push(o.source); + } + } + var candidates = neighbors[i], j = -1, m = candidates.length, x; + while (++j < m) if (!isNaN(x = candidates[j][dimension])) return x; + return Math.random() * size; + } + return force.resume(); + }; + force.resume = function() { + return force.alpha(.1); + }; + force.stop = function() { + return force.alpha(0); + }; + force.drag = function() { + if (!drag) drag = d3.behavior.drag().origin(d3_identity).on("dragstart.force", d3_layout_forceDragstart).on("drag.force", dragmove).on("dragend.force", d3_layout_forceDragend); + if (!arguments.length) return drag; + this.on("mouseover.force", d3_layout_forceMouseover).on("mouseout.force", d3_layout_forceMouseout).call(drag); + }; + function dragmove(d) { + d.px = d3.event.x, d.py = d3.event.y; + force.resume(); + } + return d3.rebind(force, event, "on"); + }; + function d3_layout_forceDragstart(d) { + d.fixed |= 2; + } + function d3_layout_forceDragend(d) { + d.fixed &= ~6; + } + function d3_layout_forceMouseover(d) { + d.fixed |= 4; + d.px = d.x, d.py = d.y; + } + function d3_layout_forceMouseout(d) { + d.fixed &= ~4; + } + function d3_layout_forceAccumulate(quad, alpha, charges) { + var cx = 0, cy = 0; + quad.charge = 0; + if (!quad.leaf) { + var nodes = quad.nodes, n = nodes.length, i = -1, c; + while (++i < n) { + c = nodes[i]; + if (c == null) continue; + d3_layout_forceAccumulate(c, alpha, charges); + quad.charge += c.charge; + cx += c.charge * c.cx; + cy += c.charge * c.cy; + } + } + if (quad.point) { + if (!quad.leaf) { + quad.point.x += Math.random() - .5; + quad.point.y += Math.random() - .5; + } + var k = alpha * charges[quad.point.index]; + quad.charge += quad.pointCharge = k; + cx += k * quad.point.x; + cy += k * quad.point.y; + } + quad.cx = cx / quad.charge; + quad.cy = cy / quad.charge; + } + var d3_layout_forceLinkDistance = 20, d3_layout_forceLinkStrength = 1, d3_layout_forceChargeDistance2 = Infinity; + d3.layout.hierarchy = function() { + var sort = d3_layout_hierarchySort, children = d3_layout_hierarchyChildren, value = d3_layout_hierarchyValue; + function hierarchy(root) { + var stack = [ root ], nodes = [], node; + root.depth = 0; + while ((node = stack.pop()) != null) { + nodes.push(node); + if ((childs = children.call(hierarchy, node, node.depth)) && (n = childs.length)) { + var n, childs, child; + while (--n >= 0) { + stack.push(child = childs[n]); + child.parent = node; + child.depth = node.depth + 1; + } + if (value) node.value = 0; + node.children = childs; + } else { + if (value) node.value = +value.call(hierarchy, node, node.depth) || 0; + delete node.children; + } + } + d3_layout_hierarchyVisitAfter(root, function(node) { + var childs, parent; + if (sort && (childs = node.children)) childs.sort(sort); + if (value && (parent = node.parent)) parent.value += node.value; + }); + return nodes; + } + hierarchy.sort = function(x) { + if (!arguments.length) return sort; + sort = x; + return hierarchy; + }; + hierarchy.children = function(x) { + if (!arguments.length) return children; + children = x; + return hierarchy; + }; + hierarchy.value = function(x) { + if (!arguments.length) return value; + value = x; + return hierarchy; + }; + hierarchy.revalue = function(root) { + if (value) { + d3_layout_hierarchyVisitBefore(root, function(node) { + if (node.children) node.value = 0; + }); + d3_layout_hierarchyVisitAfter(root, function(node) { + var parent; + if (!node.children) node.value = +value.call(hierarchy, node, node.depth) || 0; + if (parent = node.parent) parent.value += node.value; + }); + } + return root; + }; + return hierarchy; + }; + function d3_layout_hierarchyRebind(object, hierarchy) { + d3.rebind(object, hierarchy, "sort", "children", "value"); + object.nodes = object; + object.links = d3_layout_hierarchyLinks; + return object; + } + function d3_layout_hierarchyVisitBefore(node, callback) { + var nodes = [ node ]; + while ((node = nodes.pop()) != null) { + callback(node); + if ((children = node.children) && (n = children.length)) { + var n, children; + while (--n >= 0) nodes.push(children[n]); + } + } + } + function d3_layout_hierarchyVisitAfter(node, callback) { + var nodes = [ node ], nodes2 = []; + while ((node = nodes.pop()) != null) { + nodes2.push(node); + if ((children = node.children) && (n = children.length)) { + var i = -1, n, children; + while (++i < n) nodes.push(children[i]); + } + } + while ((node = nodes2.pop()) != null) { + callback(node); + } + } + function d3_layout_hierarchyChildren(d) { + return d.children; + } + function d3_layout_hierarchyValue(d) { + return d.value; + } + function d3_layout_hierarchySort(a, b) { + return b.value - a.value; + } + function d3_layout_hierarchyLinks(nodes) { + return d3.merge(nodes.map(function(parent) { + return (parent.children || []).map(function(child) { + return { + source: parent, + target: child + }; + }); + })); + } + d3.layout.partition = function() { + var hierarchy = d3.layout.hierarchy(), size = [ 1, 1 ]; + function position(node, x, dx, dy) { + var children = node.children; + node.x = x; + node.y = node.depth * dy; + node.dx = dx; + node.dy = dy; + if (children && (n = children.length)) { + var i = -1, n, c, d; + dx = node.value ? dx / node.value : 0; + while (++i < n) { + position(c = children[i], x, d = c.value * dx, dy); + x += d; + } + } + } + function depth(node) { + var children = node.children, d = 0; + if (children && (n = children.length)) { + var i = -1, n; + while (++i < n) d = Math.max(d, depth(children[i])); + } + return 1 + d; + } + function partition(d, i) { + var nodes = hierarchy.call(this, d, i); + position(nodes[0], 0, size[0], size[1] / depth(nodes[0])); + return nodes; + } + partition.size = function(x) { + if (!arguments.length) return size; + size = x; + return partition; + }; + return d3_layout_hierarchyRebind(partition, hierarchy); + }; + d3.layout.pie = function() { + var value = Number, sort = d3_layout_pieSortByValue, startAngle = 0, endAngle = τ, padAngle = 0; + function pie(data) { + var n = data.length, values = data.map(function(d, i) { + return +value.call(pie, d, i); + }), a = +(typeof startAngle === "function" ? startAngle.apply(this, arguments) : startAngle), da = (typeof endAngle === "function" ? endAngle.apply(this, arguments) : endAngle) - a, p = Math.min(Math.abs(da) / n, +(typeof padAngle === "function" ? padAngle.apply(this, arguments) : padAngle)), pa = p * (da < 0 ? -1 : 1), k = (da - n * pa) / d3.sum(values), index = d3.range(n), arcs = [], v; + if (sort != null) index.sort(sort === d3_layout_pieSortByValue ? function(i, j) { + return values[j] - values[i]; + } : function(i, j) { + return sort(data[i], data[j]); + }); + index.forEach(function(i) { + arcs[i] = { + data: data[i], + value: v = values[i], + startAngle: a, + endAngle: a += v * k + pa, + padAngle: p + }; + }); + return arcs; + } + pie.value = function(_) { + if (!arguments.length) return value; + value = _; + return pie; + }; + pie.sort = function(_) { + if (!arguments.length) return sort; + sort = _; + return pie; + }; + pie.startAngle = function(_) { + if (!arguments.length) return startAngle; + startAngle = _; + return pie; + }; + pie.endAngle = function(_) { + if (!arguments.length) return endAngle; + endAngle = _; + return pie; + }; + pie.padAngle = function(_) { + if (!arguments.length) return padAngle; + padAngle = _; + return pie; + }; + return pie; + }; + var d3_layout_pieSortByValue = {}; + d3.layout.stack = function() { + var values = d3_identity, order = d3_layout_stackOrderDefault, offset = d3_layout_stackOffsetZero, out = d3_layout_stackOut, x = d3_layout_stackX, y = d3_layout_stackY; + function stack(data, index) { + if (!(n = data.length)) return data; + var series = data.map(function(d, i) { + return values.call(stack, d, i); + }); + var points = series.map(function(d) { + return d.map(function(v, i) { + return [ x.call(stack, v, i), y.call(stack, v, i) ]; + }); + }); + var orders = order.call(stack, points, index); + series = d3.permute(series, orders); + points = d3.permute(points, orders); + var offsets = offset.call(stack, points, index); + var m = series[0].length, n, i, j, o; + for (j = 0; j < m; ++j) { + out.call(stack, series[0][j], o = offsets[j], points[0][j][1]); + for (i = 1; i < n; ++i) { + out.call(stack, series[i][j], o += points[i - 1][j][1], points[i][j][1]); + } + } + return data; + } + stack.values = function(x) { + if (!arguments.length) return values; + values = x; + return stack; + }; + stack.order = function(x) { + if (!arguments.length) return order; + order = typeof x === "function" ? x : d3_layout_stackOrders.get(x) || d3_layout_stackOrderDefault; + return stack; + }; + stack.offset = function(x) { + if (!arguments.length) return offset; + offset = typeof x === "function" ? x : d3_layout_stackOffsets.get(x) || d3_layout_stackOffsetZero; + return stack; + }; + stack.x = function(z) { + if (!arguments.length) return x; + x = z; + return stack; + }; + stack.y = function(z) { + if (!arguments.length) return y; + y = z; + return stack; + }; + stack.out = function(z) { + if (!arguments.length) return out; + out = z; + return stack; + }; + return stack; + }; + function d3_layout_stackX(d) { + return d.x; + } + function d3_layout_stackY(d) { + return d.y; + } + function d3_layout_stackOut(d, y0, y) { + d.y0 = y0; + d.y = y; + } + var d3_layout_stackOrders = d3.map({ + "inside-out": function(data) { + var n = data.length, i, j, max = data.map(d3_layout_stackMaxIndex), sums = data.map(d3_layout_stackReduceSum), index = d3.range(n).sort(function(a, b) { + return max[a] - max[b]; + }), top = 0, bottom = 0, tops = [], bottoms = []; + for (i = 0; i < n; ++i) { + j = index[i]; + if (top < bottom) { + top += sums[j]; + tops.push(j); + } else { + bottom += sums[j]; + bottoms.push(j); + } + } + return bottoms.reverse().concat(tops); + }, + reverse: function(data) { + return d3.range(data.length).reverse(); + }, + "default": d3_layout_stackOrderDefault + }); + var d3_layout_stackOffsets = d3.map({ + silhouette: function(data) { + var n = data.length, m = data[0].length, sums = [], max = 0, i, j, o, y0 = []; + for (j = 0; j < m; ++j) { + for (i = 0, o = 0; i < n; i++) o += data[i][j][1]; + if (o > max) max = o; + sums.push(o); + } + for (j = 0; j < m; ++j) { + y0[j] = (max - sums[j]) / 2; + } + return y0; + }, + wiggle: function(data) { + var n = data.length, x = data[0], m = x.length, i, j, k, s1, s2, s3, dx, o, o0, y0 = []; + y0[0] = o = o0 = 0; + for (j = 1; j < m; ++j) { + for (i = 0, s1 = 0; i < n; ++i) s1 += data[i][j][1]; + for (i = 0, s2 = 0, dx = x[j][0] - x[j - 1][0]; i < n; ++i) { + for (k = 0, s3 = (data[i][j][1] - data[i][j - 1][1]) / (2 * dx); k < i; ++k) { + s3 += (data[k][j][1] - data[k][j - 1][1]) / dx; + } + s2 += s3 * data[i][j][1]; + } + y0[j] = o -= s1 ? s2 / s1 * dx : 0; + if (o < o0) o0 = o; + } + for (j = 0; j < m; ++j) y0[j] -= o0; + return y0; + }, + expand: function(data) { + var n = data.length, m = data[0].length, k = 1 / n, i, j, o, y0 = []; + for (j = 0; j < m; ++j) { + for (i = 0, o = 0; i < n; i++) o += data[i][j][1]; + if (o) for (i = 0; i < n; i++) data[i][j][1] /= o; else for (i = 0; i < n; i++) data[i][j][1] = k; + } + for (j = 0; j < m; ++j) y0[j] = 0; + return y0; + }, + zero: d3_layout_stackOffsetZero + }); + function d3_layout_stackOrderDefault(data) { + return d3.range(data.length); + } + function d3_layout_stackOffsetZero(data) { + var j = -1, m = data[0].length, y0 = []; + while (++j < m) y0[j] = 0; + return y0; + } + function d3_layout_stackMaxIndex(array) { + var i = 1, j = 0, v = array[0][1], k, n = array.length; + for (;i < n; ++i) { + if ((k = array[i][1]) > v) { + j = i; + v = k; + } + } + return j; + } + function d3_layout_stackReduceSum(d) { + return d.reduce(d3_layout_stackSum, 0); + } + function d3_layout_stackSum(p, d) { + return p + d[1]; + } + d3.layout.histogram = function() { + var frequency = true, valuer = Number, ranger = d3_layout_histogramRange, binner = d3_layout_histogramBinSturges; + function histogram(data, i) { + var bins = [], values = data.map(valuer, this), range = ranger.call(this, values, i), thresholds = binner.call(this, range, values, i), bin, i = -1, n = values.length, m = thresholds.length - 1, k = frequency ? 1 : 1 / n, x; + while (++i < m) { + bin = bins[i] = []; + bin.dx = thresholds[i + 1] - (bin.x = thresholds[i]); + bin.y = 0; + } + if (m > 0) { + i = -1; + while (++i < n) { + x = values[i]; + if (x >= range[0] && x <= range[1]) { + bin = bins[d3.bisect(thresholds, x, 1, m) - 1]; + bin.y += k; + bin.push(data[i]); + } + } + } + return bins; + } + histogram.value = function(x) { + if (!arguments.length) return valuer; + valuer = x; + return histogram; + }; + histogram.range = function(x) { + if (!arguments.length) return ranger; + ranger = d3_functor(x); + return histogram; + }; + histogram.bins = function(x) { + if (!arguments.length) return binner; + binner = typeof x === "number" ? function(range) { + return d3_layout_histogramBinFixed(range, x); + } : d3_functor(x); + return histogram; + }; + histogram.frequency = function(x) { + if (!arguments.length) return frequency; + frequency = !!x; + return histogram; + }; + return histogram; + }; + function d3_layout_histogramBinSturges(range, values) { + return d3_layout_histogramBinFixed(range, Math.ceil(Math.log(values.length) / Math.LN2 + 1)); + } + function d3_layout_histogramBinFixed(range, n) { + var x = -1, b = +range[0], m = (range[1] - b) / n, f = []; + while (++x <= n) f[x] = m * x + b; + return f; + } + function d3_layout_histogramRange(values) { + return [ d3.min(values), d3.max(values) ]; + } + d3.layout.pack = function() { + var hierarchy = d3.layout.hierarchy().sort(d3_layout_packSort), padding = 0, size = [ 1, 1 ], radius; + function pack(d, i) { + var nodes = hierarchy.call(this, d, i), root = nodes[0], w = size[0], h = size[1], r = radius == null ? Math.sqrt : typeof radius === "function" ? radius : function() { + return radius; + }; + root.x = root.y = 0; + d3_layout_hierarchyVisitAfter(root, function(d) { + d.r = +r(d.value); + }); + d3_layout_hierarchyVisitAfter(root, d3_layout_packSiblings); + if (padding) { + var dr = padding * (radius ? 1 : Math.max(2 * root.r / w, 2 * root.r / h)) / 2; + d3_layout_hierarchyVisitAfter(root, function(d) { + d.r += dr; + }); + d3_layout_hierarchyVisitAfter(root, d3_layout_packSiblings); + d3_layout_hierarchyVisitAfter(root, function(d) { + d.r -= dr; + }); + } + d3_layout_packTransform(root, w / 2, h / 2, radius ? 1 : 1 / Math.max(2 * root.r / w, 2 * root.r / h)); + return nodes; + } + pack.size = function(_) { + if (!arguments.length) return size; + size = _; + return pack; + }; + pack.radius = function(_) { + if (!arguments.length) return radius; + radius = _ == null || typeof _ === "function" ? _ : +_; + return pack; + }; + pack.padding = function(_) { + if (!arguments.length) return padding; + padding = +_; + return pack; + }; + return d3_layout_hierarchyRebind(pack, hierarchy); + }; + function d3_layout_packSort(a, b) { + return a.value - b.value; + } + function d3_layout_packInsert(a, b) { + var c = a._pack_next; + a._pack_next = b; + b._pack_prev = a; + b._pack_next = c; + c._pack_prev = b; + } + function d3_layout_packSplice(a, b) { + a._pack_next = b; + b._pack_prev = a; + } + function d3_layout_packIntersects(a, b) { + var dx = b.x - a.x, dy = b.y - a.y, dr = a.r + b.r; + return .999 * dr * dr > dx * dx + dy * dy; + } + function d3_layout_packSiblings(node) { + if (!(nodes = node.children) || !(n = nodes.length)) return; + var nodes, xMin = Infinity, xMax = -Infinity, yMin = Infinity, yMax = -Infinity, a, b, c, i, j, k, n; + function bound(node) { + xMin = Math.min(node.x - node.r, xMin); + xMax = Math.max(node.x + node.r, xMax); + yMin = Math.min(node.y - node.r, yMin); + yMax = Math.max(node.y + node.r, yMax); + } + nodes.forEach(d3_layout_packLink); + a = nodes[0]; + a.x = -a.r; + a.y = 0; + bound(a); + if (n > 1) { + b = nodes[1]; + b.x = b.r; + b.y = 0; + bound(b); + if (n > 2) { + c = nodes[2]; + d3_layout_packPlace(a, b, c); + bound(c); + d3_layout_packInsert(a, c); + a._pack_prev = c; + d3_layout_packInsert(c, b); + b = a._pack_next; + for (i = 3; i < n; i++) { + d3_layout_packPlace(a, b, c = nodes[i]); + var isect = 0, s1 = 1, s2 = 1; + for (j = b._pack_next; j !== b; j = j._pack_next, s1++) { + if (d3_layout_packIntersects(j, c)) { + isect = 1; + break; + } + } + if (isect == 1) { + for (k = a._pack_prev; k !== j._pack_prev; k = k._pack_prev, s2++) { + if (d3_layout_packIntersects(k, c)) { + break; + } + } + } + if (isect) { + if (s1 < s2 || s1 == s2 && b.r < a.r) d3_layout_packSplice(a, b = j); else d3_layout_packSplice(a = k, b); + i--; + } else { + d3_layout_packInsert(a, c); + b = c; + bound(c); + } + } + } + } + var cx = (xMin + xMax) / 2, cy = (yMin + yMax) / 2, cr = 0; + for (i = 0; i < n; i++) { + c = nodes[i]; + c.x -= cx; + c.y -= cy; + cr = Math.max(cr, c.r + Math.sqrt(c.x * c.x + c.y * c.y)); + } + node.r = cr; + nodes.forEach(d3_layout_packUnlink); + } + function d3_layout_packLink(node) { + node._pack_next = node._pack_prev = node; + } + function d3_layout_packUnlink(node) { + delete node._pack_next; + delete node._pack_prev; + } + function d3_layout_packTransform(node, x, y, k) { + var children = node.children; + node.x = x += k * node.x; + node.y = y += k * node.y; + node.r *= k; + if (children) { + var i = -1, n = children.length; + while (++i < n) d3_layout_packTransform(children[i], x, y, k); + } + } + function d3_layout_packPlace(a, b, c) { + var db = a.r + c.r, dx = b.x - a.x, dy = b.y - a.y; + if (db && (dx || dy)) { + var da = b.r + c.r, dc = dx * dx + dy * dy; + da *= da; + db *= db; + var x = .5 + (db - da) / (2 * dc), y = Math.sqrt(Math.max(0, 2 * da * (db + dc) - (db -= dc) * db - da * da)) / (2 * dc); + c.x = a.x + x * dx + y * dy; + c.y = a.y + x * dy - y * dx; + } else { + c.x = a.x + db; + c.y = a.y; + } + } + d3.layout.tree = function() { + var hierarchy = d3.layout.hierarchy().sort(null).value(null), separation = d3_layout_treeSeparation, size = [ 1, 1 ], nodeSize = null; + function tree(d, i) { + var nodes = hierarchy.call(this, d, i), root0 = nodes[0], root1 = wrapTree(root0); + d3_layout_hierarchyVisitAfter(root1, firstWalk), root1.parent.m = -root1.z; + d3_layout_hierarchyVisitBefore(root1, secondWalk); + if (nodeSize) d3_layout_hierarchyVisitBefore(root0, sizeNode); else { + var left = root0, right = root0, bottom = root0; + d3_layout_hierarchyVisitBefore(root0, function(node) { + if (node.x < left.x) left = node; + if (node.x > right.x) right = node; + if (node.depth > bottom.depth) bottom = node; + }); + var tx = separation(left, right) / 2 - left.x, kx = size[0] / (right.x + separation(right, left) / 2 + tx), ky = size[1] / (bottom.depth || 1); + d3_layout_hierarchyVisitBefore(root0, function(node) { + node.x = (node.x + tx) * kx; + node.y = node.depth * ky; + }); + } + return nodes; + } + function wrapTree(root0) { + var root1 = { + A: null, + children: [ root0 ] + }, queue = [ root1 ], node1; + while ((node1 = queue.pop()) != null) { + for (var children = node1.children, child, i = 0, n = children.length; i < n; ++i) { + queue.push((children[i] = child = { + _: children[i], + parent: node1, + children: (child = children[i].children) && child.slice() || [], + A: null, + a: null, + z: 0, + m: 0, + c: 0, + s: 0, + t: null, + i: i + }).a = child); + } + } + return root1.children[0]; + } + function firstWalk(v) { + var children = v.children, siblings = v.parent.children, w = v.i ? siblings[v.i - 1] : null; + if (children.length) { + d3_layout_treeShift(v); + var midpoint = (children[0].z + children[children.length - 1].z) / 2; + if (w) { + v.z = w.z + separation(v._, w._); + v.m = v.z - midpoint; + } else { + v.z = midpoint; + } + } else if (w) { + v.z = w.z + separation(v._, w._); + } + v.parent.A = apportion(v, w, v.parent.A || siblings[0]); + } + function secondWalk(v) { + v._.x = v.z + v.parent.m; + v.m += v.parent.m; + } + function apportion(v, w, ancestor) { + if (w) { + var vip = v, vop = v, vim = w, vom = vip.parent.children[0], sip = vip.m, sop = vop.m, sim = vim.m, som = vom.m, shift; + while (vim = d3_layout_treeRight(vim), vip = d3_layout_treeLeft(vip), vim && vip) { + vom = d3_layout_treeLeft(vom); + vop = d3_layout_treeRight(vop); + vop.a = v; + shift = vim.z + sim - vip.z - sip + separation(vim._, vip._); + if (shift > 0) { + d3_layout_treeMove(d3_layout_treeAncestor(vim, v, ancestor), v, shift); + sip += shift; + sop += shift; + } + sim += vim.m; + sip += vip.m; + som += vom.m; + sop += vop.m; + } + if (vim && !d3_layout_treeRight(vop)) { + vop.t = vim; + vop.m += sim - sop; + } + if (vip && !d3_layout_treeLeft(vom)) { + vom.t = vip; + vom.m += sip - som; + ancestor = v; + } + } + return ancestor; + } + function sizeNode(node) { + node.x *= size[0]; + node.y = node.depth * size[1]; + } + tree.separation = function(x) { + if (!arguments.length) return separation; + separation = x; + return tree; + }; + tree.size = function(x) { + if (!arguments.length) return nodeSize ? null : size; + nodeSize = (size = x) == null ? sizeNode : null; + return tree; + }; + tree.nodeSize = function(x) { + if (!arguments.length) return nodeSize ? size : null; + nodeSize = (size = x) == null ? null : sizeNode; + return tree; + }; + return d3_layout_hierarchyRebind(tree, hierarchy); + }; + function d3_layout_treeSeparation(a, b) { + return a.parent == b.parent ? 1 : 2; + } + function d3_layout_treeLeft(v) { + var children = v.children; + return children.length ? children[0] : v.t; + } + function d3_layout_treeRight(v) { + var children = v.children, n; + return (n = children.length) ? children[n - 1] : v.t; + } + function d3_layout_treeMove(wm, wp, shift) { + var change = shift / (wp.i - wm.i); + wp.c -= change; + wp.s += shift; + wm.c += change; + wp.z += shift; + wp.m += shift; + } + function d3_layout_treeShift(v) { + var shift = 0, change = 0, children = v.children, i = children.length, w; + while (--i >= 0) { + w = children[i]; + w.z += shift; + w.m += shift; + shift += w.s + (change += w.c); + } + } + function d3_layout_treeAncestor(vim, v, ancestor) { + return vim.a.parent === v.parent ? vim.a : ancestor; + } + d3.layout.cluster = function() { + var hierarchy = d3.layout.hierarchy().sort(null).value(null), separation = d3_layout_treeSeparation, size = [ 1, 1 ], nodeSize = false; + function cluster(d, i) { + var nodes = hierarchy.call(this, d, i), root = nodes[0], previousNode, x = 0; + d3_layout_hierarchyVisitAfter(root, function(node) { + var children = node.children; + if (children && children.length) { + node.x = d3_layout_clusterX(children); + node.y = d3_layout_clusterY(children); + } else { + node.x = previousNode ? x += separation(node, previousNode) : 0; + node.y = 0; + previousNode = node; + } + }); + var left = d3_layout_clusterLeft(root), right = d3_layout_clusterRight(root), x0 = left.x - separation(left, right) / 2, x1 = right.x + separation(right, left) / 2; + d3_layout_hierarchyVisitAfter(root, nodeSize ? function(node) { + node.x = (node.x - root.x) * size[0]; + node.y = (root.y - node.y) * size[1]; + } : function(node) { + node.x = (node.x - x0) / (x1 - x0) * size[0]; + node.y = (1 - (root.y ? node.y / root.y : 1)) * size[1]; + }); + return nodes; + } + cluster.separation = function(x) { + if (!arguments.length) return separation; + separation = x; + return cluster; + }; + cluster.size = function(x) { + if (!arguments.length) return nodeSize ? null : size; + nodeSize = (size = x) == null; + return cluster; + }; + cluster.nodeSize = function(x) { + if (!arguments.length) return nodeSize ? size : null; + nodeSize = (size = x) != null; + return cluster; + }; + return d3_layout_hierarchyRebind(cluster, hierarchy); + }; + function d3_layout_clusterY(children) { + return 1 + d3.max(children, function(child) { + return child.y; + }); + } + function d3_layout_clusterX(children) { + return children.reduce(function(x, child) { + return x + child.x; + }, 0) / children.length; + } + function d3_layout_clusterLeft(node) { + var children = node.children; + return children && children.length ? d3_layout_clusterLeft(children[0]) : node; + } + function d3_layout_clusterRight(node) { + var children = node.children, n; + return children && (n = children.length) ? d3_layout_clusterRight(children[n - 1]) : node; + } + d3.layout.treemap = function() { + var hierarchy = d3.layout.hierarchy(), round = Math.round, size = [ 1, 1 ], padding = null, pad = d3_layout_treemapPadNull, sticky = false, stickies, mode = "squarify", ratio = .5 * (1 + Math.sqrt(5)); + function scale(children, k) { + var i = -1, n = children.length, child, area; + while (++i < n) { + area = (child = children[i]).value * (k < 0 ? 0 : k); + child.area = isNaN(area) || area <= 0 ? 0 : area; + } + } + function squarify(node) { + var children = node.children; + if (children && children.length) { + var rect = pad(node), row = [], remaining = children.slice(), child, best = Infinity, score, u = mode === "slice" ? rect.dx : mode === "dice" ? rect.dy : mode === "slice-dice" ? node.depth & 1 ? rect.dy : rect.dx : Math.min(rect.dx, rect.dy), n; + scale(remaining, rect.dx * rect.dy / node.value); + row.area = 0; + while ((n = remaining.length) > 0) { + row.push(child = remaining[n - 1]); + row.area += child.area; + if (mode !== "squarify" || (score = worst(row, u)) <= best) { + remaining.pop(); + best = score; + } else { + row.area -= row.pop().area; + position(row, u, rect, false); + u = Math.min(rect.dx, rect.dy); + row.length = row.area = 0; + best = Infinity; + } + } + if (row.length) { + position(row, u, rect, true); + row.length = row.area = 0; + } + children.forEach(squarify); + } + } + function stickify(node) { + var children = node.children; + if (children && children.length) { + var rect = pad(node), remaining = children.slice(), child, row = []; + scale(remaining, rect.dx * rect.dy / node.value); + row.area = 0; + while (child = remaining.pop()) { + row.push(child); + row.area += child.area; + if (child.z != null) { + position(row, child.z ? rect.dx : rect.dy, rect, !remaining.length); + row.length = row.area = 0; + } + } + children.forEach(stickify); + } + } + function worst(row, u) { + var s = row.area, r, rmax = 0, rmin = Infinity, i = -1, n = row.length; + while (++i < n) { + if (!(r = row[i].area)) continue; + if (r < rmin) rmin = r; + if (r > rmax) rmax = r; + } + s *= s; + u *= u; + return s ? Math.max(u * rmax * ratio / s, s / (u * rmin * ratio)) : Infinity; + } + function position(row, u, rect, flush) { + var i = -1, n = row.length, x = rect.x, y = rect.y, v = u ? round(row.area / u) : 0, o; + if (u == rect.dx) { + if (flush || v > rect.dy) v = rect.dy; + while (++i < n) { + o = row[i]; + o.x = x; + o.y = y; + o.dy = v; + x += o.dx = Math.min(rect.x + rect.dx - x, v ? round(o.area / v) : 0); + } + o.z = true; + o.dx += rect.x + rect.dx - x; + rect.y += v; + rect.dy -= v; + } else { + if (flush || v > rect.dx) v = rect.dx; + while (++i < n) { + o = row[i]; + o.x = x; + o.y = y; + o.dx = v; + y += o.dy = Math.min(rect.y + rect.dy - y, v ? round(o.area / v) : 0); + } + o.z = false; + o.dy += rect.y + rect.dy - y; + rect.x += v; + rect.dx -= v; + } + } + function treemap(d) { + var nodes = stickies || hierarchy(d), root = nodes[0]; + root.x = 0; + root.y = 0; + root.dx = size[0]; + root.dy = size[1]; + if (stickies) hierarchy.revalue(root); + scale([ root ], root.dx * root.dy / root.value); + (stickies ? stickify : squarify)(root); + if (sticky) stickies = nodes; + return nodes; + } + treemap.size = function(x) { + if (!arguments.length) return size; + size = x; + return treemap; + }; + treemap.padding = function(x) { + if (!arguments.length) return padding; + function padFunction(node) { + var p = x.call(treemap, node, node.depth); + return p == null ? d3_layout_treemapPadNull(node) : d3_layout_treemapPad(node, typeof p === "number" ? [ p, p, p, p ] : p); + } + function padConstant(node) { + return d3_layout_treemapPad(node, x); + } + var type; + pad = (padding = x) == null ? d3_layout_treemapPadNull : (type = typeof x) === "function" ? padFunction : type === "number" ? (x = [ x, x, x, x ], + padConstant) : padConstant; + return treemap; + }; + treemap.round = function(x) { + if (!arguments.length) return round != Number; + round = x ? Math.round : Number; + return treemap; + }; + treemap.sticky = function(x) { + if (!arguments.length) return sticky; + sticky = x; + stickies = null; + return treemap; + }; + treemap.ratio = function(x) { + if (!arguments.length) return ratio; + ratio = x; + return treemap; + }; + treemap.mode = function(x) { + if (!arguments.length) return mode; + mode = x + ""; + return treemap; + }; + return d3_layout_hierarchyRebind(treemap, hierarchy); + }; + function d3_layout_treemapPadNull(node) { + return { + x: node.x, + y: node.y, + dx: node.dx, + dy: node.dy + }; + } + function d3_layout_treemapPad(node, padding) { + var x = node.x + padding[3], y = node.y + padding[0], dx = node.dx - padding[1] - padding[3], dy = node.dy - padding[0] - padding[2]; + if (dx < 0) { + x += dx / 2; + dx = 0; + } + if (dy < 0) { + y += dy / 2; + dy = 0; + } + return { + x: x, + y: y, + dx: dx, + dy: dy + }; + } + d3.random = { + normal: function(µ, σ) { + var n = arguments.length; + if (n < 2) σ = 1; + if (n < 1) µ = 0; + return function() { + var x, y, r; + do { + x = Math.random() * 2 - 1; + y = Math.random() * 2 - 1; + r = x * x + y * y; + } while (!r || r > 1); + return µ + σ * x * Math.sqrt(-2 * Math.log(r) / r); + }; + }, + logNormal: function() { + var random = d3.random.normal.apply(d3, arguments); + return function() { + return Math.exp(random()); + }; + }, + bates: function(m) { + var random = d3.random.irwinHall(m); + return function() { + return random() / m; + }; + }, + irwinHall: function(m) { + return function() { + for (var s = 0, j = 0; j < m; j++) s += Math.random(); + return s; + }; + } + }; + d3.scale = {}; + function d3_scaleExtent(domain) { + var start = domain[0], stop = domain[domain.length - 1]; + return start < stop ? [ start, stop ] : [ stop, start ]; + } + function d3_scaleRange(scale) { + return scale.rangeExtent ? scale.rangeExtent() : d3_scaleExtent(scale.range()); + } + function d3_scale_bilinear(domain, range, uninterpolate, interpolate) { + var u = uninterpolate(domain[0], domain[1]), i = interpolate(range[0], range[1]); + return function(x) { + return i(u(x)); + }; + } + function d3_scale_nice(domain, nice) { + var i0 = 0, i1 = domain.length - 1, x0 = domain[i0], x1 = domain[i1], dx; + if (x1 < x0) { + dx = i0, i0 = i1, i1 = dx; + dx = x0, x0 = x1, x1 = dx; + } + domain[i0] = nice.floor(x0); + domain[i1] = nice.ceil(x1); + return domain; + } + function d3_scale_niceStep(step) { + return step ? { + floor: function(x) { + return Math.floor(x / step) * step; + }, + ceil: function(x) { + return Math.ceil(x / step) * step; + } + } : d3_scale_niceIdentity; + } + var d3_scale_niceIdentity = { + floor: d3_identity, + ceil: d3_identity + }; + function d3_scale_polylinear(domain, range, uninterpolate, interpolate) { + var u = [], i = [], j = 0, k = Math.min(domain.length, range.length) - 1; + if (domain[k] < domain[0]) { + domain = domain.slice().reverse(); + range = range.slice().reverse(); + } + while (++j <= k) { + u.push(uninterpolate(domain[j - 1], domain[j])); + i.push(interpolate(range[j - 1], range[j])); + } + return function(x) { + var j = d3.bisect(domain, x, 1, k) - 1; + return i[j](u[j](x)); + }; + } + d3.scale.linear = function() { + return d3_scale_linear([ 0, 1 ], [ 0, 1 ], d3_interpolate, false); + }; + function d3_scale_linear(domain, range, interpolate, clamp) { + var output, input; + function rescale() { + var linear = Math.min(domain.length, range.length) > 2 ? d3_scale_polylinear : d3_scale_bilinear, uninterpolate = clamp ? d3_uninterpolateClamp : d3_uninterpolateNumber; + output = linear(domain, range, uninterpolate, interpolate); + input = linear(range, domain, uninterpolate, d3_interpolate); + return scale; + } + function scale(x) { + return output(x); + } + scale.invert = function(y) { + return input(y); + }; + scale.domain = function(x) { + if (!arguments.length) return domain; + domain = x.map(Number); + return rescale(); + }; + scale.range = function(x) { + if (!arguments.length) return range; + range = x; + return rescale(); + }; + scale.rangeRound = function(x) { + return scale.range(x).interpolate(d3_interpolateRound); + }; + scale.clamp = function(x) { + if (!arguments.length) return clamp; + clamp = x; + return rescale(); + }; + scale.interpolate = function(x) { + if (!arguments.length) return interpolate; + interpolate = x; + return rescale(); + }; + scale.ticks = function(m) { + return d3_scale_linearTicks(domain, m); + }; + scale.tickFormat = function(m, format) { + return d3_scale_linearTickFormat(domain, m, format); + }; + scale.nice = function(m) { + d3_scale_linearNice(domain, m); + return rescale(); + }; + scale.copy = function() { + return d3_scale_linear(domain, range, interpolate, clamp); + }; + return rescale(); + } + function d3_scale_linearRebind(scale, linear) { + return d3.rebind(scale, linear, "range", "rangeRound", "interpolate", "clamp"); + } + function d3_scale_linearNice(domain, m) { + return d3_scale_nice(domain, d3_scale_niceStep(d3_scale_linearTickRange(domain, m)[2])); + } + function d3_scale_linearTickRange(domain, m) { + if (m == null) m = 10; + var extent = d3_scaleExtent(domain), span = extent[1] - extent[0], step = Math.pow(10, Math.floor(Math.log(span / m) / Math.LN10)), err = m / span * step; + if (err <= .15) step *= 10; else if (err <= .35) step *= 5; else if (err <= .75) step *= 2; + extent[0] = Math.ceil(extent[0] / step) * step; + extent[1] = Math.floor(extent[1] / step) * step + step * .5; + extent[2] = step; + return extent; + } + function d3_scale_linearTicks(domain, m) { + return d3.range.apply(d3, d3_scale_linearTickRange(domain, m)); + } + function d3_scale_linearTickFormat(domain, m, format) { + var range = d3_scale_linearTickRange(domain, m); + if (format) { + var match = d3_format_re.exec(format); + match.shift(); + if (match[8] === "s") { + var prefix = d3.formatPrefix(Math.max(abs(range[0]), abs(range[1]))); + if (!match[7]) match[7] = "." + d3_scale_linearPrecision(prefix.scale(range[2])); + match[8] = "f"; + format = d3.format(match.join("")); + return function(d) { + return format(prefix.scale(d)) + prefix.symbol; + }; + } + if (!match[7]) match[7] = "." + d3_scale_linearFormatPrecision(match[8], range); + format = match.join(""); + } else { + format = ",." + d3_scale_linearPrecision(range[2]) + "f"; + } + return d3.format(format); + } + var d3_scale_linearFormatSignificant = { + s: 1, + g: 1, + p: 1, + r: 1, + e: 1 + }; + function d3_scale_linearPrecision(value) { + return -Math.floor(Math.log(value) / Math.LN10 + .01); + } + function d3_scale_linearFormatPrecision(type, range) { + var p = d3_scale_linearPrecision(range[2]); + return type in d3_scale_linearFormatSignificant ? Math.abs(p - d3_scale_linearPrecision(Math.max(abs(range[0]), abs(range[1])))) + +(type !== "e") : p - (type === "%") * 2; + } + d3.scale.log = function() { + return d3_scale_log(d3.scale.linear().domain([ 0, 1 ]), 10, true, [ 1, 10 ]); + }; + function d3_scale_log(linear, base, positive, domain) { + function log(x) { + return (positive ? Math.log(x < 0 ? 0 : x) : -Math.log(x > 0 ? 0 : -x)) / Math.log(base); + } + function pow(x) { + return positive ? Math.pow(base, x) : -Math.pow(base, -x); + } + function scale(x) { + return linear(log(x)); + } + scale.invert = function(x) { + return pow(linear.invert(x)); + }; + scale.domain = function(x) { + if (!arguments.length) return domain; + positive = x[0] >= 0; + linear.domain((domain = x.map(Number)).map(log)); + return scale; + }; + scale.base = function(_) { + if (!arguments.length) return base; + base = +_; + linear.domain(domain.map(log)); + return scale; + }; + scale.nice = function() { + var niced = d3_scale_nice(domain.map(log), positive ? Math : d3_scale_logNiceNegative); + linear.domain(niced); + domain = niced.map(pow); + return scale; + }; + scale.ticks = function() { + var extent = d3_scaleExtent(domain), ticks = [], u = extent[0], v = extent[1], i = Math.floor(log(u)), j = Math.ceil(log(v)), n = base % 1 ? 2 : base; + if (isFinite(j - i)) { + if (positive) { + for (;i < j; i++) for (var k = 1; k < n; k++) ticks.push(pow(i) * k); + ticks.push(pow(i)); + } else { + ticks.push(pow(i)); + for (;i++ < j; ) for (var k = n - 1; k > 0; k--) ticks.push(pow(i) * k); + } + for (i = 0; ticks[i] < u; i++) {} + for (j = ticks.length; ticks[j - 1] > v; j--) {} + ticks = ticks.slice(i, j); + } + return ticks; + }; + scale.tickFormat = function(n, format) { + if (!arguments.length) return d3_scale_logFormat; + if (arguments.length < 2) format = d3_scale_logFormat; else if (typeof format !== "function") format = d3.format(format); + var k = Math.max(.1, n / scale.ticks().length), f = positive ? (e = 1e-12, Math.ceil) : (e = -1e-12, + Math.floor), e; + return function(d) { + return d / pow(f(log(d) + e)) <= k ? format(d) : ""; + }; + }; + scale.copy = function() { + return d3_scale_log(linear.copy(), base, positive, domain); + }; + return d3_scale_linearRebind(scale, linear); + } + var d3_scale_logFormat = d3.format(".0e"), d3_scale_logNiceNegative = { + floor: function(x) { + return -Math.ceil(-x); + }, + ceil: function(x) { + return -Math.floor(-x); + } + }; + d3.scale.pow = function() { + return d3_scale_pow(d3.scale.linear(), 1, [ 0, 1 ]); + }; + function d3_scale_pow(linear, exponent, domain) { + var powp = d3_scale_powPow(exponent), powb = d3_scale_powPow(1 / exponent); + function scale(x) { + return linear(powp(x)); + } + scale.invert = function(x) { + return powb(linear.invert(x)); + }; + scale.domain = function(x) { + if (!arguments.length) return domain; + linear.domain((domain = x.map(Number)).map(powp)); + return scale; + }; + scale.ticks = function(m) { + return d3_scale_linearTicks(domain, m); + }; + scale.tickFormat = function(m, format) { + return d3_scale_linearTickFormat(domain, m, format); + }; + scale.nice = function(m) { + return scale.domain(d3_scale_linearNice(domain, m)); + }; + scale.exponent = function(x) { + if (!arguments.length) return exponent; + powp = d3_scale_powPow(exponent = x); + powb = d3_scale_powPow(1 / exponent); + linear.domain(domain.map(powp)); + return scale; + }; + scale.copy = function() { + return d3_scale_pow(linear.copy(), exponent, domain); + }; + return d3_scale_linearRebind(scale, linear); + } + function d3_scale_powPow(e) { + return function(x) { + return x < 0 ? -Math.pow(-x, e) : Math.pow(x, e); + }; + } + d3.scale.sqrt = function() { + return d3.scale.pow().exponent(.5); + }; + d3.scale.ordinal = function() { + return d3_scale_ordinal([], { + t: "range", + a: [ [] ] + }); + }; + function d3_scale_ordinal(domain, ranger) { + var index, range, rangeBand; + function scale(x) { + return range[((index.get(x) || (ranger.t === "range" ? index.set(x, domain.push(x)) : NaN)) - 1) % range.length]; + } + function steps(start, step) { + return d3.range(domain.length).map(function(i) { + return start + step * i; + }); + } + scale.domain = function(x) { + if (!arguments.length) return domain; + domain = []; + index = new d3_Map(); + var i = -1, n = x.length, xi; + while (++i < n) if (!index.has(xi = x[i])) index.set(xi, domain.push(xi)); + return scale[ranger.t].apply(scale, ranger.a); + }; + scale.range = function(x) { + if (!arguments.length) return range; + range = x; + rangeBand = 0; + ranger = { + t: "range", + a: arguments + }; + return scale; + }; + scale.rangePoints = function(x, padding) { + if (arguments.length < 2) padding = 0; + var start = x[0], stop = x[1], step = domain.length < 2 ? (start = (start + stop) / 2, + 0) : (stop - start) / (domain.length - 1 + padding); + range = steps(start + step * padding / 2, step); + rangeBand = 0; + ranger = { + t: "rangePoints", + a: arguments + }; + return scale; + }; + scale.rangeRoundPoints = function(x, padding) { + if (arguments.length < 2) padding = 0; + var start = x[0], stop = x[1], step = domain.length < 2 ? (start = stop = Math.round((start + stop) / 2), + 0) : (stop - start) / (domain.length - 1 + padding) | 0; + range = steps(start + Math.round(step * padding / 2 + (stop - start - (domain.length - 1 + padding) * step) / 2), step); + rangeBand = 0; + ranger = { + t: "rangeRoundPoints", + a: arguments + }; + return scale; + }; + scale.rangeBands = function(x, padding, outerPadding) { + if (arguments.length < 2) padding = 0; + if (arguments.length < 3) outerPadding = padding; + var reverse = x[1] < x[0], start = x[reverse - 0], stop = x[1 - reverse], step = (stop - start) / (domain.length - padding + 2 * outerPadding); + range = steps(start + step * outerPadding, step); + if (reverse) range.reverse(); + rangeBand = step * (1 - padding); + ranger = { + t: "rangeBands", + a: arguments + }; + return scale; + }; + scale.rangeRoundBands = function(x, padding, outerPadding) { + if (arguments.length < 2) padding = 0; + if (arguments.length < 3) outerPadding = padding; + var reverse = x[1] < x[0], start = x[reverse - 0], stop = x[1 - reverse], step = Math.floor((stop - start) / (domain.length - padding + 2 * outerPadding)); + range = steps(start + Math.round((stop - start - (domain.length - padding) * step) / 2), step); + if (reverse) range.reverse(); + rangeBand = Math.round(step * (1 - padding)); + ranger = { + t: "rangeRoundBands", + a: arguments + }; + return scale; + }; + scale.rangeBand = function() { + return rangeBand; + }; + scale.rangeExtent = function() { + return d3_scaleExtent(ranger.a[0]); + }; + scale.copy = function() { + return d3_scale_ordinal(domain, ranger); + }; + return scale.domain(domain); + } + d3.scale.category10 = function() { + return d3.scale.ordinal().range(d3_category10); + }; + d3.scale.category20 = function() { + return d3.scale.ordinal().range(d3_category20); + }; + d3.scale.category20b = function() { + return d3.scale.ordinal().range(d3_category20b); + }; + d3.scale.category20c = function() { + return d3.scale.ordinal().range(d3_category20c); + }; + var d3_category10 = [ 2062260, 16744206, 2924588, 14034728, 9725885, 9197131, 14907330, 8355711, 12369186, 1556175 ].map(d3_rgbString); + var d3_category20 = [ 2062260, 11454440, 16744206, 16759672, 2924588, 10018698, 14034728, 16750742, 9725885, 12955861, 9197131, 12885140, 14907330, 16234194, 8355711, 13092807, 12369186, 14408589, 1556175, 10410725 ].map(d3_rgbString); + var d3_category20b = [ 3750777, 5395619, 7040719, 10264286, 6519097, 9216594, 11915115, 13556636, 9202993, 12426809, 15186514, 15190932, 8666169, 11356490, 14049643, 15177372, 8077683, 10834324, 13528509, 14589654 ].map(d3_rgbString); + var d3_category20c = [ 3244733, 7057110, 10406625, 13032431, 15095053, 16616764, 16625259, 16634018, 3253076, 7652470, 10607003, 13101504, 7695281, 10394312, 12369372, 14342891, 6513507, 9868950, 12434877, 14277081 ].map(d3_rgbString); + d3.scale.quantile = function() { + return d3_scale_quantile([], []); + }; + function d3_scale_quantile(domain, range) { + var thresholds; + function rescale() { + var k = 0, q = range.length; + thresholds = []; + while (++k < q) thresholds[k - 1] = d3.quantile(domain, k / q); + return scale; + } + function scale(x) { + if (!isNaN(x = +x)) return range[d3.bisect(thresholds, x)]; + } + scale.domain = function(x) { + if (!arguments.length) return domain; + domain = x.map(d3_number).filter(d3_numeric).sort(d3_ascending); + return rescale(); + }; + scale.range = function(x) { + if (!arguments.length) return range; + range = x; + return rescale(); + }; + scale.quantiles = function() { + return thresholds; + }; + scale.invertExtent = function(y) { + y = range.indexOf(y); + return y < 0 ? [ NaN, NaN ] : [ y > 0 ? thresholds[y - 1] : domain[0], y < thresholds.length ? thresholds[y] : domain[domain.length - 1] ]; + }; + scale.copy = function() { + return d3_scale_quantile(domain, range); + }; + return rescale(); + } + d3.scale.quantize = function() { + return d3_scale_quantize(0, 1, [ 0, 1 ]); + }; + function d3_scale_quantize(x0, x1, range) { + var kx, i; + function scale(x) { + return range[Math.max(0, Math.min(i, Math.floor(kx * (x - x0))))]; + } + function rescale() { + kx = range.length / (x1 - x0); + i = range.length - 1; + return scale; + } + scale.domain = function(x) { + if (!arguments.length) return [ x0, x1 ]; + x0 = +x[0]; + x1 = +x[x.length - 1]; + return rescale(); + }; + scale.range = function(x) { + if (!arguments.length) return range; + range = x; + return rescale(); + }; + scale.invertExtent = function(y) { + y = range.indexOf(y); + y = y < 0 ? NaN : y / kx + x0; + return [ y, y + 1 / kx ]; + }; + scale.copy = function() { + return d3_scale_quantize(x0, x1, range); + }; + return rescale(); + } + d3.scale.threshold = function() { + return d3_scale_threshold([ .5 ], [ 0, 1 ]); + }; + function d3_scale_threshold(domain, range) { + function scale(x) { + if (x <= x) return range[d3.bisect(domain, x)]; + } + scale.domain = function(_) { + if (!arguments.length) return domain; + domain = _; + return scale; + }; + scale.range = function(_) { + if (!arguments.length) return range; + range = _; + return scale; + }; + scale.invertExtent = function(y) { + y = range.indexOf(y); + return [ domain[y - 1], domain[y] ]; + }; + scale.copy = function() { + return d3_scale_threshold(domain, range); + }; + return scale; + } + d3.scale.identity = function() { + return d3_scale_identity([ 0, 1 ]); + }; + function d3_scale_identity(domain) { + function identity(x) { + return +x; + } + identity.invert = identity; + identity.domain = identity.range = function(x) { + if (!arguments.length) return domain; + domain = x.map(identity); + return identity; + }; + identity.ticks = function(m) { + return d3_scale_linearTicks(domain, m); + }; + identity.tickFormat = function(m, format) { + return d3_scale_linearTickFormat(domain, m, format); + }; + identity.copy = function() { + return d3_scale_identity(domain); + }; + return identity; + } + d3.svg = {}; + function d3_zero() { + return 0; + } + d3.svg.arc = function() { + var innerRadius = d3_svg_arcInnerRadius, outerRadius = d3_svg_arcOuterRadius, cornerRadius = d3_zero, padRadius = d3_svg_arcAuto, startAngle = d3_svg_arcStartAngle, endAngle = d3_svg_arcEndAngle, padAngle = d3_svg_arcPadAngle; + function arc() { + var r0 = Math.max(0, +innerRadius.apply(this, arguments)), r1 = Math.max(0, +outerRadius.apply(this, arguments)), a0 = startAngle.apply(this, arguments) - halfπ, a1 = endAngle.apply(this, arguments) - halfπ, da = Math.abs(a1 - a0), cw = a0 > a1 ? 0 : 1; + if (r1 < r0) rc = r1, r1 = r0, r0 = rc; + if (da >= τε) return circleSegment(r1, cw) + (r0 ? circleSegment(r0, 1 - cw) : "") + "Z"; + var rc, cr, rp, ap, p0 = 0, p1 = 0, x0, y0, x1, y1, x2, y2, x3, y3, path = []; + if (ap = (+padAngle.apply(this, arguments) || 0) / 2) { + rp = padRadius === d3_svg_arcAuto ? Math.sqrt(r0 * r0 + r1 * r1) : +padRadius.apply(this, arguments); + if (!cw) p1 *= -1; + if (r1) p1 = d3_asin(rp / r1 * Math.sin(ap)); + if (r0) p0 = d3_asin(rp / r0 * Math.sin(ap)); + } + if (r1) { + x0 = r1 * Math.cos(a0 + p1); + y0 = r1 * Math.sin(a0 + p1); + x1 = r1 * Math.cos(a1 - p1); + y1 = r1 * Math.sin(a1 - p1); + var l1 = Math.abs(a1 - a0 - 2 * p1) <= π ? 0 : 1; + if (p1 && d3_svg_arcSweep(x0, y0, x1, y1) === cw ^ l1) { + var h1 = (a0 + a1) / 2; + x0 = r1 * Math.cos(h1); + y0 = r1 * Math.sin(h1); + x1 = y1 = null; + } + } else { + x0 = y0 = 0; + } + if (r0) { + x2 = r0 * Math.cos(a1 - p0); + y2 = r0 * Math.sin(a1 - p0); + x3 = r0 * Math.cos(a0 + p0); + y3 = r0 * Math.sin(a0 + p0); + var l0 = Math.abs(a0 - a1 + 2 * p0) <= π ? 0 : 1; + if (p0 && d3_svg_arcSweep(x2, y2, x3, y3) === 1 - cw ^ l0) { + var h0 = (a0 + a1) / 2; + x2 = r0 * Math.cos(h0); + y2 = r0 * Math.sin(h0); + x3 = y3 = null; + } + } else { + x2 = y2 = 0; + } + if ((rc = Math.min(Math.abs(r1 - r0) / 2, +cornerRadius.apply(this, arguments))) > .001) { + cr = r0 < r1 ^ cw ? 0 : 1; + var oc = x3 == null ? [ x2, y2 ] : x1 == null ? [ x0, y0 ] : d3_geom_polygonIntersect([ x0, y0 ], [ x3, y3 ], [ x1, y1 ], [ x2, y2 ]), ax = x0 - oc[0], ay = y0 - oc[1], bx = x1 - oc[0], by = y1 - oc[1], kc = 1 / Math.sin(Math.acos((ax * bx + ay * by) / (Math.sqrt(ax * ax + ay * ay) * Math.sqrt(bx * bx + by * by))) / 2), lc = Math.sqrt(oc[0] * oc[0] + oc[1] * oc[1]); + if (x1 != null) { + var rc1 = Math.min(rc, (r1 - lc) / (kc + 1)), t30 = d3_svg_arcCornerTangents(x3 == null ? [ x2, y2 ] : [ x3, y3 ], [ x0, y0 ], r1, rc1, cw), t12 = d3_svg_arcCornerTangents([ x1, y1 ], [ x2, y2 ], r1, rc1, cw); + if (rc === rc1) { + path.push("M", t30[0], "A", rc1, ",", rc1, " 0 0,", cr, " ", t30[1], "A", r1, ",", r1, " 0 ", 1 - cw ^ d3_svg_arcSweep(t30[1][0], t30[1][1], t12[1][0], t12[1][1]), ",", cw, " ", t12[1], "A", rc1, ",", rc1, " 0 0,", cr, " ", t12[0]); + } else { + path.push("M", t30[0], "A", rc1, ",", rc1, " 0 1,", cr, " ", t12[0]); + } + } else { + path.push("M", x0, ",", y0); + } + if (x3 != null) { + var rc0 = Math.min(rc, (r0 - lc) / (kc - 1)), t03 = d3_svg_arcCornerTangents([ x0, y0 ], [ x3, y3 ], r0, -rc0, cw), t21 = d3_svg_arcCornerTangents([ x2, y2 ], x1 == null ? [ x0, y0 ] : [ x1, y1 ], r0, -rc0, cw); + if (rc === rc0) { + path.push("L", t21[0], "A", rc0, ",", rc0, " 0 0,", cr, " ", t21[1], "A", r0, ",", r0, " 0 ", cw ^ d3_svg_arcSweep(t21[1][0], t21[1][1], t03[1][0], t03[1][1]), ",", 1 - cw, " ", t03[1], "A", rc0, ",", rc0, " 0 0,", cr, " ", t03[0]); + } else { + path.push("L", t21[0], "A", rc0, ",", rc0, " 0 0,", cr, " ", t03[0]); + } + } else { + path.push("L", x2, ",", y2); + } + } else { + path.push("M", x0, ",", y0); + if (x1 != null) path.push("A", r1, ",", r1, " 0 ", l1, ",", cw, " ", x1, ",", y1); + path.push("L", x2, ",", y2); + if (x3 != null) path.push("A", r0, ",", r0, " 0 ", l0, ",", 1 - cw, " ", x3, ",", y3); + } + path.push("Z"); + return path.join(""); + } + function circleSegment(r1, cw) { + return "M0," + r1 + "A" + r1 + "," + r1 + " 0 1," + cw + " 0," + -r1 + "A" + r1 + "," + r1 + " 0 1," + cw + " 0," + r1; + } + arc.innerRadius = function(v) { + if (!arguments.length) return innerRadius; + innerRadius = d3_functor(v); + return arc; + }; + arc.outerRadius = function(v) { + if (!arguments.length) return outerRadius; + outerRadius = d3_functor(v); + return arc; + }; + arc.cornerRadius = function(v) { + if (!arguments.length) return cornerRadius; + cornerRadius = d3_functor(v); + return arc; + }; + arc.padRadius = function(v) { + if (!arguments.length) return padRadius; + padRadius = v == d3_svg_arcAuto ? d3_svg_arcAuto : d3_functor(v); + return arc; + }; + arc.startAngle = function(v) { + if (!arguments.length) return startAngle; + startAngle = d3_functor(v); + return arc; + }; + arc.endAngle = function(v) { + if (!arguments.length) return endAngle; + endAngle = d3_functor(v); + return arc; + }; + arc.padAngle = function(v) { + if (!arguments.length) return padAngle; + padAngle = d3_functor(v); + return arc; + }; + arc.centroid = function() { + var r = (+innerRadius.apply(this, arguments) + +outerRadius.apply(this, arguments)) / 2, a = (+startAngle.apply(this, arguments) + +endAngle.apply(this, arguments)) / 2 - halfπ; + return [ Math.cos(a) * r, Math.sin(a) * r ]; + }; + return arc; + }; + var d3_svg_arcAuto = "auto"; + function d3_svg_arcInnerRadius(d) { + return d.innerRadius; + } + function d3_svg_arcOuterRadius(d) { + return d.outerRadius; + } + function d3_svg_arcStartAngle(d) { + return d.startAngle; + } + function d3_svg_arcEndAngle(d) { + return d.endAngle; + } + function d3_svg_arcPadAngle(d) { + return d && d.padAngle; + } + function d3_svg_arcSweep(x0, y0, x1, y1) { + return (x0 - x1) * y0 - (y0 - y1) * x0 > 0 ? 0 : 1; + } + function d3_svg_arcCornerTangents(p0, p1, r1, rc, cw) { + var x01 = p0[0] - p1[0], y01 = p0[1] - p1[1], lo = (cw ? rc : -rc) / Math.sqrt(x01 * x01 + y01 * y01), ox = lo * y01, oy = -lo * x01, x1 = p0[0] + ox, y1 = p0[1] + oy, x2 = p1[0] + ox, y2 = p1[1] + oy, x3 = (x1 + x2) / 2, y3 = (y1 + y2) / 2, dx = x2 - x1, dy = y2 - y1, d2 = dx * dx + dy * dy, r = r1 - rc, D = x1 * y2 - x2 * y1, d = (dy < 0 ? -1 : 1) * Math.sqrt(r * r * d2 - D * D), cx0 = (D * dy - dx * d) / d2, cy0 = (-D * dx - dy * d) / d2, cx1 = (D * dy + dx * d) / d2, cy1 = (-D * dx + dy * d) / d2, dx0 = cx0 - x3, dy0 = cy0 - y3, dx1 = cx1 - x3, dy1 = cy1 - y3; + if (dx0 * dx0 + dy0 * dy0 > dx1 * dx1 + dy1 * dy1) cx0 = cx1, cy0 = cy1; + return [ [ cx0 - ox, cy0 - oy ], [ cx0 * r1 / r, cy0 * r1 / r ] ]; + } + function d3_svg_line(projection) { + var x = d3_geom_pointX, y = d3_geom_pointY, defined = d3_true, interpolate = d3_svg_lineLinear, interpolateKey = interpolate.key, tension = .7; + function line(data) { + var segments = [], points = [], i = -1, n = data.length, d, fx = d3_functor(x), fy = d3_functor(y); + function segment() { + segments.push("M", interpolate(projection(points), tension)); + } + while (++i < n) { + if (defined.call(this, d = data[i], i)) { + points.push([ +fx.call(this, d, i), +fy.call(this, d, i) ]); + } else if (points.length) { + segment(); + points = []; + } + } + if (points.length) segment(); + return segments.length ? segments.join("") : null; + } + line.x = function(_) { + if (!arguments.length) return x; + x = _; + return line; + }; + line.y = function(_) { + if (!arguments.length) return y; + y = _; + return line; + }; + line.defined = function(_) { + if (!arguments.length) return defined; + defined = _; + return line; + }; + line.interpolate = function(_) { + if (!arguments.length) return interpolateKey; + if (typeof _ === "function") interpolateKey = interpolate = _; else interpolateKey = (interpolate = d3_svg_lineInterpolators.get(_) || d3_svg_lineLinear).key; + return line; + }; + line.tension = function(_) { + if (!arguments.length) return tension; + tension = _; + return line; + }; + return line; + } + d3.svg.line = function() { + return d3_svg_line(d3_identity); + }; + var d3_svg_lineInterpolators = d3.map({ + linear: d3_svg_lineLinear, + "linear-closed": d3_svg_lineLinearClosed, + step: d3_svg_lineStep, + "step-before": d3_svg_lineStepBefore, + "step-after": d3_svg_lineStepAfter, + basis: d3_svg_lineBasis, + "basis-open": d3_svg_lineBasisOpen, + "basis-closed": d3_svg_lineBasisClosed, + bundle: d3_svg_lineBundle, + cardinal: d3_svg_lineCardinal, + "cardinal-open": d3_svg_lineCardinalOpen, + "cardinal-closed": d3_svg_lineCardinalClosed, + monotone: d3_svg_lineMonotone + }); + d3_svg_lineInterpolators.forEach(function(key, value) { + value.key = key; + value.closed = /-closed$/.test(key); + }); + function d3_svg_lineLinear(points) { + return points.join("L"); + } + function d3_svg_lineLinearClosed(points) { + return d3_svg_lineLinear(points) + "Z"; + } + function d3_svg_lineStep(points) { + var i = 0, n = points.length, p = points[0], path = [ p[0], ",", p[1] ]; + while (++i < n) path.push("H", (p[0] + (p = points[i])[0]) / 2, "V", p[1]); + if (n > 1) path.push("H", p[0]); + return path.join(""); + } + function d3_svg_lineStepBefore(points) { + var i = 0, n = points.length, p = points[0], path = [ p[0], ",", p[1] ]; + while (++i < n) path.push("V", (p = points[i])[1], "H", p[0]); + return path.join(""); + } + function d3_svg_lineStepAfter(points) { + var i = 0, n = points.length, p = points[0], path = [ p[0], ",", p[1] ]; + while (++i < n) path.push("H", (p = points[i])[0], "V", p[1]); + return path.join(""); + } + function d3_svg_lineCardinalOpen(points, tension) { + return points.length < 4 ? d3_svg_lineLinear(points) : points[1] + d3_svg_lineHermite(points.slice(1, -1), d3_svg_lineCardinalTangents(points, tension)); + } + function d3_svg_lineCardinalClosed(points, tension) { + return points.length < 3 ? d3_svg_lineLinear(points) : points[0] + d3_svg_lineHermite((points.push(points[0]), + points), d3_svg_lineCardinalTangents([ points[points.length - 2] ].concat(points, [ points[1] ]), tension)); + } + function d3_svg_lineCardinal(points, tension) { + return points.length < 3 ? d3_svg_lineLinear(points) : points[0] + d3_svg_lineHermite(points, d3_svg_lineCardinalTangents(points, tension)); + } + function d3_svg_lineHermite(points, tangents) { + if (tangents.length < 1 || points.length != tangents.length && points.length != tangents.length + 2) { + return d3_svg_lineLinear(points); + } + var quad = points.length != tangents.length, path = "", p0 = points[0], p = points[1], t0 = tangents[0], t = t0, pi = 1; + if (quad) { + path += "Q" + (p[0] - t0[0] * 2 / 3) + "," + (p[1] - t0[1] * 2 / 3) + "," + p[0] + "," + p[1]; + p0 = points[1]; + pi = 2; + } + if (tangents.length > 1) { + t = tangents[1]; + p = points[pi]; + pi++; + path += "C" + (p0[0] + t0[0]) + "," + (p0[1] + t0[1]) + "," + (p[0] - t[0]) + "," + (p[1] - t[1]) + "," + p[0] + "," + p[1]; + for (var i = 2; i < tangents.length; i++, pi++) { + p = points[pi]; + t = tangents[i]; + path += "S" + (p[0] - t[0]) + "," + (p[1] - t[1]) + "," + p[0] + "," + p[1]; + } + } + if (quad) { + var lp = points[pi]; + path += "Q" + (p[0] + t[0] * 2 / 3) + "," + (p[1] + t[1] * 2 / 3) + "," + lp[0] + "," + lp[1]; + } + return path; + } + function d3_svg_lineCardinalTangents(points, tension) { + var tangents = [], a = (1 - tension) / 2, p0, p1 = points[0], p2 = points[1], i = 1, n = points.length; + while (++i < n) { + p0 = p1; + p1 = p2; + p2 = points[i]; + tangents.push([ a * (p2[0] - p0[0]), a * (p2[1] - p0[1]) ]); + } + return tangents; + } + function d3_svg_lineBasis(points) { + if (points.length < 3) return d3_svg_lineLinear(points); + var i = 1, n = points.length, pi = points[0], x0 = pi[0], y0 = pi[1], px = [ x0, x0, x0, (pi = points[1])[0] ], py = [ y0, y0, y0, pi[1] ], path = [ x0, ",", y0, "L", d3_svg_lineDot4(d3_svg_lineBasisBezier3, px), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier3, py) ]; + points.push(points[n - 1]); + while (++i <= n) { + pi = points[i]; + px.shift(); + px.push(pi[0]); + py.shift(); + py.push(pi[1]); + d3_svg_lineBasisBezier(path, px, py); + } + points.pop(); + path.push("L", pi); + return path.join(""); + } + function d3_svg_lineBasisOpen(points) { + if (points.length < 4) return d3_svg_lineLinear(points); + var path = [], i = -1, n = points.length, pi, px = [ 0 ], py = [ 0 ]; + while (++i < 3) { + pi = points[i]; + px.push(pi[0]); + py.push(pi[1]); + } + path.push(d3_svg_lineDot4(d3_svg_lineBasisBezier3, px) + "," + d3_svg_lineDot4(d3_svg_lineBasisBezier3, py)); + --i; + while (++i < n) { + pi = points[i]; + px.shift(); + px.push(pi[0]); + py.shift(); + py.push(pi[1]); + d3_svg_lineBasisBezier(path, px, py); + } + return path.join(""); + } + function d3_svg_lineBasisClosed(points) { + var path, i = -1, n = points.length, m = n + 4, pi, px = [], py = []; + while (++i < 4) { + pi = points[i % n]; + px.push(pi[0]); + py.push(pi[1]); + } + path = [ d3_svg_lineDot4(d3_svg_lineBasisBezier3, px), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier3, py) ]; + --i; + while (++i < m) { + pi = points[i % n]; + px.shift(); + px.push(pi[0]); + py.shift(); + py.push(pi[1]); + d3_svg_lineBasisBezier(path, px, py); + } + return path.join(""); + } + function d3_svg_lineBundle(points, tension) { + var n = points.length - 1; + if (n) { + var x0 = points[0][0], y0 = points[0][1], dx = points[n][0] - x0, dy = points[n][1] - y0, i = -1, p, t; + while (++i <= n) { + p = points[i]; + t = i / n; + p[0] = tension * p[0] + (1 - tension) * (x0 + t * dx); + p[1] = tension * p[1] + (1 - tension) * (y0 + t * dy); + } + } + return d3_svg_lineBasis(points); + } + function d3_svg_lineDot4(a, b) { + return a[0] * b[0] + a[1] * b[1] + a[2] * b[2] + a[3] * b[3]; + } + var d3_svg_lineBasisBezier1 = [ 0, 2 / 3, 1 / 3, 0 ], d3_svg_lineBasisBezier2 = [ 0, 1 / 3, 2 / 3, 0 ], d3_svg_lineBasisBezier3 = [ 0, 1 / 6, 2 / 3, 1 / 6 ]; + function d3_svg_lineBasisBezier(path, x, y) { + path.push("C", d3_svg_lineDot4(d3_svg_lineBasisBezier1, x), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier1, y), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier2, x), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier2, y), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier3, x), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier3, y)); + } + function d3_svg_lineSlope(p0, p1) { + return (p1[1] - p0[1]) / (p1[0] - p0[0]); + } + function d3_svg_lineFiniteDifferences(points) { + var i = 0, j = points.length - 1, m = [], p0 = points[0], p1 = points[1], d = m[0] = d3_svg_lineSlope(p0, p1); + while (++i < j) { + m[i] = (d + (d = d3_svg_lineSlope(p0 = p1, p1 = points[i + 1]))) / 2; + } + m[i] = d; + return m; + } + function d3_svg_lineMonotoneTangents(points) { + var tangents = [], d, a, b, s, m = d3_svg_lineFiniteDifferences(points), i = -1, j = points.length - 1; + while (++i < j) { + d = d3_svg_lineSlope(points[i], points[i + 1]); + if (abs(d) < ε) { + m[i] = m[i + 1] = 0; + } else { + a = m[i] / d; + b = m[i + 1] / d; + s = a * a + b * b; + if (s > 9) { + s = d * 3 / Math.sqrt(s); + m[i] = s * a; + m[i + 1] = s * b; + } + } + } + i = -1; + while (++i <= j) { + s = (points[Math.min(j, i + 1)][0] - points[Math.max(0, i - 1)][0]) / (6 * (1 + m[i] * m[i])); + tangents.push([ s || 0, m[i] * s || 0 ]); + } + return tangents; + } + function d3_svg_lineMonotone(points) { + return points.length < 3 ? d3_svg_lineLinear(points) : points[0] + d3_svg_lineHermite(points, d3_svg_lineMonotoneTangents(points)); + } + d3.svg.line.radial = function() { + var line = d3_svg_line(d3_svg_lineRadial); + line.radius = line.x, delete line.x; + line.angle = line.y, delete line.y; + return line; + }; + function d3_svg_lineRadial(points) { + var point, i = -1, n = points.length, r, a; + while (++i < n) { + point = points[i]; + r = point[0]; + a = point[1] - halfπ; + point[0] = r * Math.cos(a); + point[1] = r * Math.sin(a); + } + return points; + } + function d3_svg_area(projection) { + var x0 = d3_geom_pointX, x1 = d3_geom_pointX, y0 = 0, y1 = d3_geom_pointY, defined = d3_true, interpolate = d3_svg_lineLinear, interpolateKey = interpolate.key, interpolateReverse = interpolate, L = "L", tension = .7; + function area(data) { + var segments = [], points0 = [], points1 = [], i = -1, n = data.length, d, fx0 = d3_functor(x0), fy0 = d3_functor(y0), fx1 = x0 === x1 ? function() { + return x; + } : d3_functor(x1), fy1 = y0 === y1 ? function() { + return y; + } : d3_functor(y1), x, y; + function segment() { + segments.push("M", interpolate(projection(points1), tension), L, interpolateReverse(projection(points0.reverse()), tension), "Z"); + } + while (++i < n) { + if (defined.call(this, d = data[i], i)) { + points0.push([ x = +fx0.call(this, d, i), y = +fy0.call(this, d, i) ]); + points1.push([ +fx1.call(this, d, i), +fy1.call(this, d, i) ]); + } else if (points0.length) { + segment(); + points0 = []; + points1 = []; + } + } + if (points0.length) segment(); + return segments.length ? segments.join("") : null; + } + area.x = function(_) { + if (!arguments.length) return x1; + x0 = x1 = _; + return area; + }; + area.x0 = function(_) { + if (!arguments.length) return x0; + x0 = _; + return area; + }; + area.x1 = function(_) { + if (!arguments.length) return x1; + x1 = _; + return area; + }; + area.y = function(_) { + if (!arguments.length) return y1; + y0 = y1 = _; + return area; + }; + area.y0 = function(_) { + if (!arguments.length) return y0; + y0 = _; + return area; + }; + area.y1 = function(_) { + if (!arguments.length) return y1; + y1 = _; + return area; + }; + area.defined = function(_) { + if (!arguments.length) return defined; + defined = _; + return area; + }; + area.interpolate = function(_) { + if (!arguments.length) return interpolateKey; + if (typeof _ === "function") interpolateKey = interpolate = _; else interpolateKey = (interpolate = d3_svg_lineInterpolators.get(_) || d3_svg_lineLinear).key; + interpolateReverse = interpolate.reverse || interpolate; + L = interpolate.closed ? "M" : "L"; + return area; + }; + area.tension = function(_) { + if (!arguments.length) return tension; + tension = _; + return area; + }; + return area; + } + d3_svg_lineStepBefore.reverse = d3_svg_lineStepAfter; + d3_svg_lineStepAfter.reverse = d3_svg_lineStepBefore; + d3.svg.area = function() { + return d3_svg_area(d3_identity); + }; + d3.svg.area.radial = function() { + var area = d3_svg_area(d3_svg_lineRadial); + area.radius = area.x, delete area.x; + area.innerRadius = area.x0, delete area.x0; + area.outerRadius = area.x1, delete area.x1; + area.angle = area.y, delete area.y; + area.startAngle = area.y0, delete area.y0; + area.endAngle = area.y1, delete area.y1; + return area; + }; + d3.svg.chord = function() { + var source = d3_source, target = d3_target, radius = d3_svg_chordRadius, startAngle = d3_svg_arcStartAngle, endAngle = d3_svg_arcEndAngle; + function chord(d, i) { + var s = subgroup(this, source, d, i), t = subgroup(this, target, d, i); + return "M" + s.p0 + arc(s.r, s.p1, s.a1 - s.a0) + (equals(s, t) ? curve(s.r, s.p1, s.r, s.p0) : curve(s.r, s.p1, t.r, t.p0) + arc(t.r, t.p1, t.a1 - t.a0) + curve(t.r, t.p1, s.r, s.p0)) + "Z"; + } + function subgroup(self, f, d, i) { + var subgroup = f.call(self, d, i), r = radius.call(self, subgroup, i), a0 = startAngle.call(self, subgroup, i) - halfπ, a1 = endAngle.call(self, subgroup, i) - halfπ; + return { + r: r, + a0: a0, + a1: a1, + p0: [ r * Math.cos(a0), r * Math.sin(a0) ], + p1: [ r * Math.cos(a1), r * Math.sin(a1) ] + }; + } + function equals(a, b) { + return a.a0 == b.a0 && a.a1 == b.a1; + } + function arc(r, p, a) { + return "A" + r + "," + r + " 0 " + +(a > π) + ",1 " + p; + } + function curve(r0, p0, r1, p1) { + return "Q 0,0 " + p1; + } + chord.radius = function(v) { + if (!arguments.length) return radius; + radius = d3_functor(v); + return chord; + }; + chord.source = function(v) { + if (!arguments.length) return source; + source = d3_functor(v); + return chord; + }; + chord.target = function(v) { + if (!arguments.length) return target; + target = d3_functor(v); + return chord; + }; + chord.startAngle = function(v) { + if (!arguments.length) return startAngle; + startAngle = d3_functor(v); + return chord; + }; + chord.endAngle = function(v) { + if (!arguments.length) return endAngle; + endAngle = d3_functor(v); + return chord; + }; + return chord; + }; + function d3_svg_chordRadius(d) { + return d.radius; + } + d3.svg.diagonal = function() { + var source = d3_source, target = d3_target, projection = d3_svg_diagonalProjection; + function diagonal(d, i) { + var p0 = source.call(this, d, i), p3 = target.call(this, d, i), m = (p0.y + p3.y) / 2, p = [ p0, { + x: p0.x, + y: m + }, { + x: p3.x, + y: m + }, p3 ]; + p = p.map(projection); + return "M" + p[0] + "C" + p[1] + " " + p[2] + " " + p[3]; + } + diagonal.source = function(x) { + if (!arguments.length) return source; + source = d3_functor(x); + return diagonal; + }; + diagonal.target = function(x) { + if (!arguments.length) return target; + target = d3_functor(x); + return diagonal; + }; + diagonal.projection = function(x) { + if (!arguments.length) return projection; + projection = x; + return diagonal; + }; + return diagonal; + }; + function d3_svg_diagonalProjection(d) { + return [ d.x, d.y ]; + } + d3.svg.diagonal.radial = function() { + var diagonal = d3.svg.diagonal(), projection = d3_svg_diagonalProjection, projection_ = diagonal.projection; + diagonal.projection = function(x) { + return arguments.length ? projection_(d3_svg_diagonalRadialProjection(projection = x)) : projection; + }; + return diagonal; + }; + function d3_svg_diagonalRadialProjection(projection) { + return function() { + var d = projection.apply(this, arguments), r = d[0], a = d[1] - halfπ; + return [ r * Math.cos(a), r * Math.sin(a) ]; + }; + } + d3.svg.symbol = function() { + var type = d3_svg_symbolType, size = d3_svg_symbolSize; + function symbol(d, i) { + return (d3_svg_symbols.get(type.call(this, d, i)) || d3_svg_symbolCircle)(size.call(this, d, i)); + } + symbol.type = function(x) { + if (!arguments.length) return type; + type = d3_functor(x); + return symbol; + }; + symbol.size = function(x) { + if (!arguments.length) return size; + size = d3_functor(x); + return symbol; + }; + return symbol; + }; + function d3_svg_symbolSize() { + return 64; + } + function d3_svg_symbolType() { + return "circle"; + } + function d3_svg_symbolCircle(size) { + var r = Math.sqrt(size / π); + return "M0," + r + "A" + r + "," + r + " 0 1,1 0," + -r + "A" + r + "," + r + " 0 1,1 0," + r + "Z"; + } + var d3_svg_symbols = d3.map({ + circle: d3_svg_symbolCircle, + cross: function(size) { + var r = Math.sqrt(size / 5) / 2; + return "M" + -3 * r + "," + -r + "H" + -r + "V" + -3 * r + "H" + r + "V" + -r + "H" + 3 * r + "V" + r + "H" + r + "V" + 3 * r + "H" + -r + "V" + r + "H" + -3 * r + "Z"; + }, + diamond: function(size) { + var ry = Math.sqrt(size / (2 * d3_svg_symbolTan30)), rx = ry * d3_svg_symbolTan30; + return "M0," + -ry + "L" + rx + ",0" + " 0," + ry + " " + -rx + ",0" + "Z"; + }, + square: function(size) { + var r = Math.sqrt(size) / 2; + return "M" + -r + "," + -r + "L" + r + "," + -r + " " + r + "," + r + " " + -r + "," + r + "Z"; + }, + "triangle-down": function(size) { + var rx = Math.sqrt(size / d3_svg_symbolSqrt3), ry = rx * d3_svg_symbolSqrt3 / 2; + return "M0," + ry + "L" + rx + "," + -ry + " " + -rx + "," + -ry + "Z"; + }, + "triangle-up": function(size) { + var rx = Math.sqrt(size / d3_svg_symbolSqrt3), ry = rx * d3_svg_symbolSqrt3 / 2; + return "M0," + -ry + "L" + rx + "," + ry + " " + -rx + "," + ry + "Z"; + } + }); + d3.svg.symbolTypes = d3_svg_symbols.keys(); + var d3_svg_symbolSqrt3 = Math.sqrt(3), d3_svg_symbolTan30 = Math.tan(30 * d3_radians); + d3_selectionPrototype.transition = function(name) { + var id = d3_transitionInheritId || ++d3_transitionId, ns = d3_transitionNamespace(name), subgroups = [], subgroup, node, transition = d3_transitionInherit || { + time: Date.now(), + ease: d3_ease_cubicInOut, + delay: 0, + duration: 250 + }; + for (var j = -1, m = this.length; ++j < m; ) { + subgroups.push(subgroup = []); + for (var group = this[j], i = -1, n = group.length; ++i < n; ) { + if (node = group[i]) d3_transitionNode(node, i, ns, id, transition); + subgroup.push(node); + } + } + return d3_transition(subgroups, ns, id); + }; + d3_selectionPrototype.interrupt = function(name) { + return this.each(name == null ? d3_selection_interrupt : d3_selection_interruptNS(d3_transitionNamespace(name))); + }; + var d3_selection_interrupt = d3_selection_interruptNS(d3_transitionNamespace()); + function d3_selection_interruptNS(ns) { + return function() { + var lock, active; + if ((lock = this[ns]) && (active = lock[lock.active])) { + if (--lock.count) { + delete lock[lock.active]; + lock.active += .5; + } else { + delete this[ns]; + } + active.event && active.event.interrupt.call(this, this.__data__, active.index); + } + }; + } + function d3_transition(groups, ns, id) { + d3_subclass(groups, d3_transitionPrototype); + groups.namespace = ns; + groups.id = id; + return groups; + } + var d3_transitionPrototype = [], d3_transitionId = 0, d3_transitionInheritId, d3_transitionInherit; + d3_transitionPrototype.call = d3_selectionPrototype.call; + d3_transitionPrototype.empty = d3_selectionPrototype.empty; + d3_transitionPrototype.node = d3_selectionPrototype.node; + d3_transitionPrototype.size = d3_selectionPrototype.size; + d3.transition = function(selection, name) { + return selection && selection.transition ? d3_transitionInheritId ? selection.transition(name) : selection : d3_selectionRoot.transition(selection); + }; + d3.transition.prototype = d3_transitionPrototype; + d3_transitionPrototype.select = function(selector) { + var id = this.id, ns = this.namespace, subgroups = [], subgroup, subnode, node; + selector = d3_selection_selector(selector); + for (var j = -1, m = this.length; ++j < m; ) { + subgroups.push(subgroup = []); + for (var group = this[j], i = -1, n = group.length; ++i < n; ) { + if ((node = group[i]) && (subnode = selector.call(node, node.__data__, i, j))) { + if ("__data__" in node) subnode.__data__ = node.__data__; + d3_transitionNode(subnode, i, ns, id, node[ns][id]); + subgroup.push(subnode); + } else { + subgroup.push(null); + } + } + } + return d3_transition(subgroups, ns, id); + }; + d3_transitionPrototype.selectAll = function(selector) { + var id = this.id, ns = this.namespace, subgroups = [], subgroup, subnodes, node, subnode, transition; + selector = d3_selection_selectorAll(selector); + for (var j = -1, m = this.length; ++j < m; ) { + for (var group = this[j], i = -1, n = group.length; ++i < n; ) { + if (node = group[i]) { + transition = node[ns][id]; + subnodes = selector.call(node, node.__data__, i, j); + subgroups.push(subgroup = []); + for (var k = -1, o = subnodes.length; ++k < o; ) { + if (subnode = subnodes[k]) d3_transitionNode(subnode, k, ns, id, transition); + subgroup.push(subnode); + } + } + } + } + return d3_transition(subgroups, ns, id); + }; + d3_transitionPrototype.filter = function(filter) { + var subgroups = [], subgroup, group, node; + if (typeof filter !== "function") filter = d3_selection_filter(filter); + for (var j = 0, m = this.length; j < m; j++) { + subgroups.push(subgroup = []); + for (var group = this[j], i = 0, n = group.length; i < n; i++) { + if ((node = group[i]) && filter.call(node, node.__data__, i, j)) { + subgroup.push(node); + } + } + } + return d3_transition(subgroups, this.namespace, this.id); + }; + d3_transitionPrototype.tween = function(name, tween) { + var id = this.id, ns = this.namespace; + if (arguments.length < 2) return this.node()[ns][id].tween.get(name); + return d3_selection_each(this, tween == null ? function(node) { + node[ns][id].tween.remove(name); + } : function(node) { + node[ns][id].tween.set(name, tween); + }); + }; + function d3_transition_tween(groups, name, value, tween) { + var id = groups.id, ns = groups.namespace; + return d3_selection_each(groups, typeof value === "function" ? function(node, i, j) { + node[ns][id].tween.set(name, tween(value.call(node, node.__data__, i, j))); + } : (value = tween(value), function(node) { + node[ns][id].tween.set(name, value); + })); + } + d3_transitionPrototype.attr = function(nameNS, value) { + if (arguments.length < 2) { + for (value in nameNS) this.attr(value, nameNS[value]); + return this; + } + var interpolate = nameNS == "transform" ? d3_interpolateTransform : d3_interpolate, name = d3.ns.qualify(nameNS); + function attrNull() { + this.removeAttribute(name); + } + function attrNullNS() { + this.removeAttributeNS(name.space, name.local); + } + function attrTween(b) { + return b == null ? attrNull : (b += "", function() { + var a = this.getAttribute(name), i; + return a !== b && (i = interpolate(a, b), function(t) { + this.setAttribute(name, i(t)); + }); + }); + } + function attrTweenNS(b) { + return b == null ? attrNullNS : (b += "", function() { + var a = this.getAttributeNS(name.space, name.local), i; + return a !== b && (i = interpolate(a, b), function(t) { + this.setAttributeNS(name.space, name.local, i(t)); + }); + }); + } + return d3_transition_tween(this, "attr." + nameNS, value, name.local ? attrTweenNS : attrTween); + }; + d3_transitionPrototype.attrTween = function(nameNS, tween) { + var name = d3.ns.qualify(nameNS); + function attrTween(d, i) { + var f = tween.call(this, d, i, this.getAttribute(name)); + return f && function(t) { + this.setAttribute(name, f(t)); + }; + } + function attrTweenNS(d, i) { + var f = tween.call(this, d, i, this.getAttributeNS(name.space, name.local)); + return f && function(t) { + this.setAttributeNS(name.space, name.local, f(t)); + }; + } + return this.tween("attr." + nameNS, name.local ? attrTweenNS : attrTween); + }; + d3_transitionPrototype.style = function(name, value, priority) { + var n = arguments.length; + if (n < 3) { + if (typeof name !== "string") { + if (n < 2) value = ""; + for (priority in name) this.style(priority, name[priority], value); + return this; + } + priority = ""; + } + function styleNull() { + this.style.removeProperty(name); + } + function styleString(b) { + return b == null ? styleNull : (b += "", function() { + var a = d3_window.getComputedStyle(this, null).getPropertyValue(name), i; + return a !== b && (i = d3_interpolate(a, b), function(t) { + this.style.setProperty(name, i(t), priority); + }); + }); + } + return d3_transition_tween(this, "style." + name, value, styleString); + }; + d3_transitionPrototype.styleTween = function(name, tween, priority) { + if (arguments.length < 3) priority = ""; + function styleTween(d, i) { + var f = tween.call(this, d, i, d3_window.getComputedStyle(this, null).getPropertyValue(name)); + return f && function(t) { + this.style.setProperty(name, f(t), priority); + }; + } + return this.tween("style." + name, styleTween); + }; + d3_transitionPrototype.text = function(value) { + return d3_transition_tween(this, "text", value, d3_transition_text); + }; + function d3_transition_text(b) { + if (b == null) b = ""; + return function() { + this.textContent = b; + }; + } + d3_transitionPrototype.remove = function() { + var ns = this.namespace; + return this.each("end.transition", function() { + var p; + if (this[ns].count < 2 && (p = this.parentNode)) p.removeChild(this); + }); + }; + d3_transitionPrototype.ease = function(value) { + var id = this.id, ns = this.namespace; + if (arguments.length < 1) return this.node()[ns][id].ease; + if (typeof value !== "function") value = d3.ease.apply(d3, arguments); + return d3_selection_each(this, function(node) { + node[ns][id].ease = value; + }); + }; + d3_transitionPrototype.delay = function(value) { + var id = this.id, ns = this.namespace; + if (arguments.length < 1) return this.node()[ns][id].delay; + return d3_selection_each(this, typeof value === "function" ? function(node, i, j) { + node[ns][id].delay = +value.call(node, node.__data__, i, j); + } : (value = +value, function(node) { + node[ns][id].delay = value; + })); + }; + d3_transitionPrototype.duration = function(value) { + var id = this.id, ns = this.namespace; + if (arguments.length < 1) return this.node()[ns][id].duration; + return d3_selection_each(this, typeof value === "function" ? function(node, i, j) { + node[ns][id].duration = Math.max(1, value.call(node, node.__data__, i, j)); + } : (value = Math.max(1, value), function(node) { + node[ns][id].duration = value; + })); + }; + d3_transitionPrototype.each = function(type, listener) { + var id = this.id, ns = this.namespace; + if (arguments.length < 2) { + var inherit = d3_transitionInherit, inheritId = d3_transitionInheritId; + try { + d3_transitionInheritId = id; + d3_selection_each(this, function(node, i, j) { + d3_transitionInherit = node[ns][id]; + type.call(node, node.__data__, i, j); + }); + } finally { + d3_transitionInherit = inherit; + d3_transitionInheritId = inheritId; + } + } else { + d3_selection_each(this, function(node) { + var transition = node[ns][id]; + (transition.event || (transition.event = d3.dispatch("start", "end", "interrupt"))).on(type, listener); + }); + } + return this; + }; + d3_transitionPrototype.transition = function() { + var id0 = this.id, id1 = ++d3_transitionId, ns = this.namespace, subgroups = [], subgroup, group, node, transition; + for (var j = 0, m = this.length; j < m; j++) { + subgroups.push(subgroup = []); + for (var group = this[j], i = 0, n = group.length; i < n; i++) { + if (node = group[i]) { + transition = node[ns][id0]; + d3_transitionNode(node, i, ns, id1, { + time: transition.time, + ease: transition.ease, + delay: transition.delay + transition.duration, + duration: transition.duration + }); + } + subgroup.push(node); + } + } + return d3_transition(subgroups, ns, id1); + }; + function d3_transitionNamespace(name) { + return name == null ? "__transition__" : "__transition_" + name + "__"; + } + function d3_transitionNode(node, i, ns, id, inherit) { + var lock = node[ns] || (node[ns] = { + active: 0, + count: 0 + }), transition = lock[id]; + if (!transition) { + var time = inherit.time; + transition = lock[id] = { + tween: new d3_Map(), + time: time, + delay: inherit.delay, + duration: inherit.duration, + ease: inherit.ease, + index: i + }; + inherit = null; + ++lock.count; + d3.timer(function(elapsed) { + var delay = transition.delay, duration, ease, timer = d3_timer_active, tweened = []; + timer.t = delay + time; + if (delay <= elapsed) return start(elapsed - delay); + timer.c = start; + function start(elapsed) { + if (lock.active > id) return stop(); + var active = lock[lock.active]; + if (active) { + --lock.count; + delete lock[lock.active]; + active.event && active.event.interrupt.call(node, node.__data__, active.index); + } + lock.active = id; + transition.event && transition.event.start.call(node, node.__data__, i); + transition.tween.forEach(function(key, value) { + if (value = value.call(node, node.__data__, i)) { + tweened.push(value); + } + }); + ease = transition.ease; + duration = transition.duration; + d3.timer(function() { + timer.c = tick(elapsed || 1) ? d3_true : tick; + return 1; + }, 0, time); + } + function tick(elapsed) { + if (lock.active !== id) return 1; + var t = elapsed / duration, e = ease(t), n = tweened.length; + while (n > 0) { + tweened[--n].call(node, e); + } + if (t >= 1) { + transition.event && transition.event.end.call(node, node.__data__, i); + return stop(); + } + } + function stop() { + if (--lock.count) delete lock[id]; else delete node[ns]; + return 1; + } + }, 0, time); + } + } + d3.svg.axis = function() { + var scale = d3.scale.linear(), orient = d3_svg_axisDefaultOrient, innerTickSize = 6, outerTickSize = 6, tickPadding = 3, tickArguments_ = [ 10 ], tickValues = null, tickFormat_; + function axis(g) { + g.each(function() { + var g = d3.select(this); + var scale0 = this.__chart__ || scale, scale1 = this.__chart__ = scale.copy(); + var ticks = tickValues == null ? scale1.ticks ? scale1.ticks.apply(scale1, tickArguments_) : scale1.domain() : tickValues, tickFormat = tickFormat_ == null ? scale1.tickFormat ? scale1.tickFormat.apply(scale1, tickArguments_) : d3_identity : tickFormat_, tick = g.selectAll(".tick").data(ticks, scale1), tickEnter = tick.enter().insert("g", ".domain").attr("class", "tick").style("opacity", ε), tickExit = d3.transition(tick.exit()).style("opacity", ε).remove(), tickUpdate = d3.transition(tick.order()).style("opacity", 1), tickSpacing = Math.max(innerTickSize, 0) + tickPadding, tickTransform; + var range = d3_scaleRange(scale1), path = g.selectAll(".domain").data([ 0 ]), pathUpdate = (path.enter().append("path").attr("class", "domain"), + d3.transition(path)); + tickEnter.append("line"); + tickEnter.append("text"); + var lineEnter = tickEnter.select("line"), lineUpdate = tickUpdate.select("line"), text = tick.select("text").text(tickFormat), textEnter = tickEnter.select("text"), textUpdate = tickUpdate.select("text"), sign = orient === "top" || orient === "left" ? -1 : 1, x1, x2, y1, y2; + if (orient === "bottom" || orient === "top") { + tickTransform = d3_svg_axisX, x1 = "x", y1 = "y", x2 = "x2", y2 = "y2"; + text.attr("dy", sign < 0 ? "0em" : ".71em").style("text-anchor", "middle"); + pathUpdate.attr("d", "M" + range[0] + "," + sign * outerTickSize + "V0H" + range[1] + "V" + sign * outerTickSize); + } else { + tickTransform = d3_svg_axisY, x1 = "y", y1 = "x", x2 = "y2", y2 = "x2"; + text.attr("dy", ".32em").style("text-anchor", sign < 0 ? "end" : "start"); + pathUpdate.attr("d", "M" + sign * outerTickSize + "," + range[0] + "H0V" + range[1] + "H" + sign * outerTickSize); + } + lineEnter.attr(y2, sign * innerTickSize); + textEnter.attr(y1, sign * tickSpacing); + lineUpdate.attr(x2, 0).attr(y2, sign * innerTickSize); + textUpdate.attr(x1, 0).attr(y1, sign * tickSpacing); + if (scale1.rangeBand) { + var x = scale1, dx = x.rangeBand() / 2; + scale0 = scale1 = function(d) { + return x(d) + dx; + }; + } else if (scale0.rangeBand) { + scale0 = scale1; + } else { + tickExit.call(tickTransform, scale1, scale0); + } + tickEnter.call(tickTransform, scale0, scale1); + tickUpdate.call(tickTransform, scale1, scale1); + }); + } + axis.scale = function(x) { + if (!arguments.length) return scale; + scale = x; + return axis; + }; + axis.orient = function(x) { + if (!arguments.length) return orient; + orient = x in d3_svg_axisOrients ? x + "" : d3_svg_axisDefaultOrient; + return axis; + }; + axis.ticks = function() { + if (!arguments.length) return tickArguments_; + tickArguments_ = arguments; + return axis; + }; + axis.tickValues = function(x) { + if (!arguments.length) return tickValues; + tickValues = x; + return axis; + }; + axis.tickFormat = function(x) { + if (!arguments.length) return tickFormat_; + tickFormat_ = x; + return axis; + }; + axis.tickSize = function(x) { + var n = arguments.length; + if (!n) return innerTickSize; + innerTickSize = +x; + outerTickSize = +arguments[n - 1]; + return axis; + }; + axis.innerTickSize = function(x) { + if (!arguments.length) return innerTickSize; + innerTickSize = +x; + return axis; + }; + axis.outerTickSize = function(x) { + if (!arguments.length) return outerTickSize; + outerTickSize = +x; + return axis; + }; + axis.tickPadding = function(x) { + if (!arguments.length) return tickPadding; + tickPadding = +x; + return axis; + }; + axis.tickSubdivide = function() { + return arguments.length && axis; + }; + return axis; + }; + var d3_svg_axisDefaultOrient = "bottom", d3_svg_axisOrients = { + top: 1, + right: 1, + bottom: 1, + left: 1 + }; + function d3_svg_axisX(selection, x0, x1) { + selection.attr("transform", function(d) { + var v0 = x0(d); + return "translate(" + (isFinite(v0) ? v0 : x1(d)) + ",0)"; + }); + } + function d3_svg_axisY(selection, y0, y1) { + selection.attr("transform", function(d) { + var v0 = y0(d); + return "translate(0," + (isFinite(v0) ? v0 : y1(d)) + ")"; + }); + } + d3.svg.brush = function() { + var event = d3_eventDispatch(brush, "brushstart", "brush", "brushend"), x = null, y = null, xExtent = [ 0, 0 ], yExtent = [ 0, 0 ], xExtentDomain, yExtentDomain, xClamp = true, yClamp = true, resizes = d3_svg_brushResizes[0]; + function brush(g) { + g.each(function() { + var g = d3.select(this).style("pointer-events", "all").style("-webkit-tap-highlight-color", "rgba(0,0,0,0)").on("mousedown.brush", brushstart).on("touchstart.brush", brushstart); + var background = g.selectAll(".background").data([ 0 ]); + background.enter().append("rect").attr("class", "background").style("visibility", "hidden").style("cursor", "crosshair"); + g.selectAll(".extent").data([ 0 ]).enter().append("rect").attr("class", "extent").style("cursor", "move"); + var resize = g.selectAll(".resize").data(resizes, d3_identity); + resize.exit().remove(); + resize.enter().append("g").attr("class", function(d) { + return "resize " + d; + }).style("cursor", function(d) { + return d3_svg_brushCursor[d]; + }).append("rect").attr("x", function(d) { + return /[ew]$/.test(d) ? -3 : null; + }).attr("y", function(d) { + return /^[ns]/.test(d) ? -3 : null; + }).attr("width", 6).attr("height", 6).style("visibility", "hidden"); + resize.style("display", brush.empty() ? "none" : null); + var gUpdate = d3.transition(g), backgroundUpdate = d3.transition(background), range; + if (x) { + range = d3_scaleRange(x); + backgroundUpdate.attr("x", range[0]).attr("width", range[1] - range[0]); + redrawX(gUpdate); + } + if (y) { + range = d3_scaleRange(y); + backgroundUpdate.attr("y", range[0]).attr("height", range[1] - range[0]); + redrawY(gUpdate); + } + redraw(gUpdate); + }); + } + brush.event = function(g) { + g.each(function() { + var event_ = event.of(this, arguments), extent1 = { + x: xExtent, + y: yExtent, + i: xExtentDomain, + j: yExtentDomain + }, extent0 = this.__chart__ || extent1; + this.__chart__ = extent1; + if (d3_transitionInheritId) { + d3.select(this).transition().each("start.brush", function() { + xExtentDomain = extent0.i; + yExtentDomain = extent0.j; + xExtent = extent0.x; + yExtent = extent0.y; + event_({ + type: "brushstart" + }); + }).tween("brush:brush", function() { + var xi = d3_interpolateArray(xExtent, extent1.x), yi = d3_interpolateArray(yExtent, extent1.y); + xExtentDomain = yExtentDomain = null; + return function(t) { + xExtent = extent1.x = xi(t); + yExtent = extent1.y = yi(t); + event_({ + type: "brush", + mode: "resize" + }); + }; + }).each("end.brush", function() { + xExtentDomain = extent1.i; + yExtentDomain = extent1.j; + event_({ + type: "brush", + mode: "resize" + }); + event_({ + type: "brushend" + }); + }); + } else { + event_({ + type: "brushstart" + }); + event_({ + type: "brush", + mode: "resize" + }); + event_({ + type: "brushend" + }); + } + }); + }; + function redraw(g) { + g.selectAll(".resize").attr("transform", function(d) { + return "translate(" + xExtent[+/e$/.test(d)] + "," + yExtent[+/^s/.test(d)] + ")"; + }); + } + function redrawX(g) { + g.select(".extent").attr("x", xExtent[0]); + g.selectAll(".extent,.n>rect,.s>rect").attr("width", xExtent[1] - xExtent[0]); + } + function redrawY(g) { + g.select(".extent").attr("y", yExtent[0]); + g.selectAll(".extent,.e>rect,.w>rect").attr("height", yExtent[1] - yExtent[0]); + } + function brushstart() { + var target = this, eventTarget = d3.select(d3.event.target), event_ = event.of(target, arguments), g = d3.select(target), resizing = eventTarget.datum(), resizingX = !/^(n|s)$/.test(resizing) && x, resizingY = !/^(e|w)$/.test(resizing) && y, dragging = eventTarget.classed("extent"), dragRestore = d3_event_dragSuppress(), center, origin = d3.mouse(target), offset; + var w = d3.select(d3_window).on("keydown.brush", keydown).on("keyup.brush", keyup); + if (d3.event.changedTouches) { + w.on("touchmove.brush", brushmove).on("touchend.brush", brushend); + } else { + w.on("mousemove.brush", brushmove).on("mouseup.brush", brushend); + } + g.interrupt().selectAll("*").interrupt(); + if (dragging) { + origin[0] = xExtent[0] - origin[0]; + origin[1] = yExtent[0] - origin[1]; + } else if (resizing) { + var ex = +/w$/.test(resizing), ey = +/^n/.test(resizing); + offset = [ xExtent[1 - ex] - origin[0], yExtent[1 - ey] - origin[1] ]; + origin[0] = xExtent[ex]; + origin[1] = yExtent[ey]; + } else if (d3.event.altKey) center = origin.slice(); + g.style("pointer-events", "none").selectAll(".resize").style("display", null); + d3.select("body").style("cursor", eventTarget.style("cursor")); + event_({ + type: "brushstart" + }); + brushmove(); + function keydown() { + if (d3.event.keyCode == 32) { + if (!dragging) { + center = null; + origin[0] -= xExtent[1]; + origin[1] -= yExtent[1]; + dragging = 2; + } + d3_eventPreventDefault(); + } + } + function keyup() { + if (d3.event.keyCode == 32 && dragging == 2) { + origin[0] += xExtent[1]; + origin[1] += yExtent[1]; + dragging = 0; + d3_eventPreventDefault(); + } + } + function brushmove() { + var point = d3.mouse(target), moved = false; + if (offset) { + point[0] += offset[0]; + point[1] += offset[1]; + } + if (!dragging) { + if (d3.event.altKey) { + if (!center) center = [ (xExtent[0] + xExtent[1]) / 2, (yExtent[0] + yExtent[1]) / 2 ]; + origin[0] = xExtent[+(point[0] < center[0])]; + origin[1] = yExtent[+(point[1] < center[1])]; + } else center = null; + } + if (resizingX && move1(point, x, 0)) { + redrawX(g); + moved = true; + } + if (resizingY && move1(point, y, 1)) { + redrawY(g); + moved = true; + } + if (moved) { + redraw(g); + event_({ + type: "brush", + mode: dragging ? "move" : "resize" + }); + } + } + function move1(point, scale, i) { + var range = d3_scaleRange(scale), r0 = range[0], r1 = range[1], position = origin[i], extent = i ? yExtent : xExtent, size = extent[1] - extent[0], min, max; + if (dragging) { + r0 -= position; + r1 -= size + position; + } + min = (i ? yClamp : xClamp) ? Math.max(r0, Math.min(r1, point[i])) : point[i]; + if (dragging) { + max = (min += position) + size; + } else { + if (center) position = Math.max(r0, Math.min(r1, 2 * center[i] - min)); + if (position < min) { + max = min; + min = position; + } else { + max = position; + } + } + if (extent[0] != min || extent[1] != max) { + if (i) yExtentDomain = null; else xExtentDomain = null; + extent[0] = min; + extent[1] = max; + return true; + } + } + function brushend() { + brushmove(); + g.style("pointer-events", "all").selectAll(".resize").style("display", brush.empty() ? "none" : null); + d3.select("body").style("cursor", null); + w.on("mousemove.brush", null).on("mouseup.brush", null).on("touchmove.brush", null).on("touchend.brush", null).on("keydown.brush", null).on("keyup.brush", null); + dragRestore(); + event_({ + type: "brushend" + }); + } + } + brush.x = function(z) { + if (!arguments.length) return x; + x = z; + resizes = d3_svg_brushResizes[!x << 1 | !y]; + return brush; + }; + brush.y = function(z) { + if (!arguments.length) return y; + y = z; + resizes = d3_svg_brushResizes[!x << 1 | !y]; + return brush; + }; + brush.clamp = function(z) { + if (!arguments.length) return x && y ? [ xClamp, yClamp ] : x ? xClamp : y ? yClamp : null; + if (x && y) xClamp = !!z[0], yClamp = !!z[1]; else if (x) xClamp = !!z; else if (y) yClamp = !!z; + return brush; + }; + brush.extent = function(z) { + var x0, x1, y0, y1, t; + if (!arguments.length) { + if (x) { + if (xExtentDomain) { + x0 = xExtentDomain[0], x1 = xExtentDomain[1]; + } else { + x0 = xExtent[0], x1 = xExtent[1]; + if (x.invert) x0 = x.invert(x0), x1 = x.invert(x1); + if (x1 < x0) t = x0, x0 = x1, x1 = t; + } + } + if (y) { + if (yExtentDomain) { + y0 = yExtentDomain[0], y1 = yExtentDomain[1]; + } else { + y0 = yExtent[0], y1 = yExtent[1]; + if (y.invert) y0 = y.invert(y0), y1 = y.invert(y1); + if (y1 < y0) t = y0, y0 = y1, y1 = t; + } + } + return x && y ? [ [ x0, y0 ], [ x1, y1 ] ] : x ? [ x0, x1 ] : y && [ y0, y1 ]; + } + if (x) { + x0 = z[0], x1 = z[1]; + if (y) x0 = x0[0], x1 = x1[0]; + xExtentDomain = [ x0, x1 ]; + if (x.invert) x0 = x(x0), x1 = x(x1); + if (x1 < x0) t = x0, x0 = x1, x1 = t; + if (x0 != xExtent[0] || x1 != xExtent[1]) xExtent = [ x0, x1 ]; + } + if (y) { + y0 = z[0], y1 = z[1]; + if (x) y0 = y0[1], y1 = y1[1]; + yExtentDomain = [ y0, y1 ]; + if (y.invert) y0 = y(y0), y1 = y(y1); + if (y1 < y0) t = y0, y0 = y1, y1 = t; + if (y0 != yExtent[0] || y1 != yExtent[1]) yExtent = [ y0, y1 ]; + } + return brush; + }; + brush.clear = function() { + if (!brush.empty()) { + xExtent = [ 0, 0 ], yExtent = [ 0, 0 ]; + xExtentDomain = yExtentDomain = null; + } + return brush; + }; + brush.empty = function() { + return !!x && xExtent[0] == xExtent[1] || !!y && yExtent[0] == yExtent[1]; + }; + return d3.rebind(brush, event, "on"); + }; + var d3_svg_brushCursor = { + n: "ns-resize", + e: "ew-resize", + s: "ns-resize", + w: "ew-resize", + nw: "nwse-resize", + ne: "nesw-resize", + se: "nwse-resize", + sw: "nesw-resize" + }; + var d3_svg_brushResizes = [ [ "n", "e", "s", "w", "nw", "ne", "se", "sw" ], [ "e", "w" ], [ "n", "s" ], [] ]; + var d3_time_format = d3_time.format = d3_locale_enUS.timeFormat; + var d3_time_formatUtc = d3_time_format.utc; + var d3_time_formatIso = d3_time_formatUtc("%Y-%m-%dT%H:%M:%S.%LZ"); + d3_time_format.iso = Date.prototype.toISOString && +new Date("2000-01-01T00:00:00.000Z") ? d3_time_formatIsoNative : d3_time_formatIso; + function d3_time_formatIsoNative(date) { + return date.toISOString(); + } + d3_time_formatIsoNative.parse = function(string) { + var date = new Date(string); + return isNaN(date) ? null : date; + }; + d3_time_formatIsoNative.toString = d3_time_formatIso.toString; + d3_time.second = d3_time_interval(function(date) { + return new d3_date(Math.floor(date / 1e3) * 1e3); + }, function(date, offset) { + date.setTime(date.getTime() + Math.floor(offset) * 1e3); + }, function(date) { + return date.getSeconds(); + }); + d3_time.seconds = d3_time.second.range; + d3_time.seconds.utc = d3_time.second.utc.range; + d3_time.minute = d3_time_interval(function(date) { + return new d3_date(Math.floor(date / 6e4) * 6e4); + }, function(date, offset) { + date.setTime(date.getTime() + Math.floor(offset) * 6e4); + }, function(date) { + return date.getMinutes(); + }); + d3_time.minutes = d3_time.minute.range; + d3_time.minutes.utc = d3_time.minute.utc.range; + d3_time.hour = d3_time_interval(function(date) { + var timezone = date.getTimezoneOffset() / 60; + return new d3_date((Math.floor(date / 36e5 - timezone) + timezone) * 36e5); + }, function(date, offset) { + date.setTime(date.getTime() + Math.floor(offset) * 36e5); + }, function(date) { + return date.getHours(); + }); + d3_time.hours = d3_time.hour.range; + d3_time.hours.utc = d3_time.hour.utc.range; + d3_time.month = d3_time_interval(function(date) { + date = d3_time.day(date); + date.setDate(1); + return date; + }, function(date, offset) { + date.setMonth(date.getMonth() + offset); + }, function(date) { + return date.getMonth(); + }); + d3_time.months = d3_time.month.range; + d3_time.months.utc = d3_time.month.utc.range; + function d3_time_scale(linear, methods, format) { + function scale(x) { + return linear(x); + } + scale.invert = function(x) { + return d3_time_scaleDate(linear.invert(x)); + }; + scale.domain = function(x) { + if (!arguments.length) return linear.domain().map(d3_time_scaleDate); + linear.domain(x); + return scale; + }; + function tickMethod(extent, count) { + var span = extent[1] - extent[0], target = span / count, i = d3.bisect(d3_time_scaleSteps, target); + return i == d3_time_scaleSteps.length ? [ methods.year, d3_scale_linearTickRange(extent.map(function(d) { + return d / 31536e6; + }), count)[2] ] : !i ? [ d3_time_scaleMilliseconds, d3_scale_linearTickRange(extent, count)[2] ] : methods[target / d3_time_scaleSteps[i - 1] < d3_time_scaleSteps[i] / target ? i - 1 : i]; + } + scale.nice = function(interval, skip) { + var domain = scale.domain(), extent = d3_scaleExtent(domain), method = interval == null ? tickMethod(extent, 10) : typeof interval === "number" && tickMethod(extent, interval); + if (method) interval = method[0], skip = method[1]; + function skipped(date) { + return !isNaN(date) && !interval.range(date, d3_time_scaleDate(+date + 1), skip).length; + } + return scale.domain(d3_scale_nice(domain, skip > 1 ? { + floor: function(date) { + while (skipped(date = interval.floor(date))) date = d3_time_scaleDate(date - 1); + return date; + }, + ceil: function(date) { + while (skipped(date = interval.ceil(date))) date = d3_time_scaleDate(+date + 1); + return date; + } + } : interval)); + }; + scale.ticks = function(interval, skip) { + var extent = d3_scaleExtent(scale.domain()), method = interval == null ? tickMethod(extent, 10) : typeof interval === "number" ? tickMethod(extent, interval) : !interval.range && [ { + range: interval + }, skip ]; + if (method) interval = method[0], skip = method[1]; + return interval.range(extent[0], d3_time_scaleDate(+extent[1] + 1), skip < 1 ? 1 : skip); + }; + scale.tickFormat = function() { + return format; + }; + scale.copy = function() { + return d3_time_scale(linear.copy(), methods, format); + }; + return d3_scale_linearRebind(scale, linear); + } + function d3_time_scaleDate(t) { + return new Date(t); + } + var d3_time_scaleSteps = [ 1e3, 5e3, 15e3, 3e4, 6e4, 3e5, 9e5, 18e5, 36e5, 108e5, 216e5, 432e5, 864e5, 1728e5, 6048e5, 2592e6, 7776e6, 31536e6 ]; + var d3_time_scaleLocalMethods = [ [ d3_time.second, 1 ], [ d3_time.second, 5 ], [ d3_time.second, 15 ], [ d3_time.second, 30 ], [ d3_time.minute, 1 ], [ d3_time.minute, 5 ], [ d3_time.minute, 15 ], [ d3_time.minute, 30 ], [ d3_time.hour, 1 ], [ d3_time.hour, 3 ], [ d3_time.hour, 6 ], [ d3_time.hour, 12 ], [ d3_time.day, 1 ], [ d3_time.day, 2 ], [ d3_time.week, 1 ], [ d3_time.month, 1 ], [ d3_time.month, 3 ], [ d3_time.year, 1 ] ]; + var d3_time_scaleLocalFormat = d3_time_format.multi([ [ ".%L", function(d) { + return d.getMilliseconds(); + } ], [ ":%S", function(d) { + return d.getSeconds(); + } ], [ "%I:%M", function(d) { + return d.getMinutes(); + } ], [ "%I %p", function(d) { + return d.getHours(); + } ], [ "%a %d", function(d) { + return d.getDay() && d.getDate() != 1; + } ], [ "%b %d", function(d) { + return d.getDate() != 1; + } ], [ "%B", function(d) { + return d.getMonth(); + } ], [ "%Y", d3_true ] ]); + var d3_time_scaleMilliseconds = { + range: function(start, stop, step) { + return d3.range(Math.ceil(start / step) * step, +stop, step).map(d3_time_scaleDate); + }, + floor: d3_identity, + ceil: d3_identity + }; + d3_time_scaleLocalMethods.year = d3_time.year; + d3_time.scale = function() { + return d3_time_scale(d3.scale.linear(), d3_time_scaleLocalMethods, d3_time_scaleLocalFormat); + }; + var d3_time_scaleUtcMethods = d3_time_scaleLocalMethods.map(function(m) { + return [ m[0].utc, m[1] ]; + }); + var d3_time_scaleUtcFormat = d3_time_formatUtc.multi([ [ ".%L", function(d) { + return d.getUTCMilliseconds(); + } ], [ ":%S", function(d) { + return d.getUTCSeconds(); + } ], [ "%I:%M", function(d) { + return d.getUTCMinutes(); + } ], [ "%I %p", function(d) { + return d.getUTCHours(); + } ], [ "%a %d", function(d) { + return d.getUTCDay() && d.getUTCDate() != 1; + } ], [ "%b %d", function(d) { + return d.getUTCDate() != 1; + } ], [ "%B", function(d) { + return d.getUTCMonth(); + } ], [ "%Y", d3_true ] ]); + d3_time_scaleUtcMethods.year = d3_time.year.utc; + d3_time.scale.utc = function() { + return d3_time_scale(d3.scale.linear(), d3_time_scaleUtcMethods, d3_time_scaleUtcFormat); + }; + d3.text = d3_xhrType(function(request) { + return request.responseText; + }); + d3.json = function(url, callback) { + return d3_xhr(url, "application/json", d3_json, callback); + }; + function d3_json(request) { + return JSON.parse(request.responseText); + } + d3.html = function(url, callback) { + return d3_xhr(url, "text/html", d3_html, callback); + }; + function d3_html(request) { + var range = d3_document.createRange(); + range.selectNode(d3_document.body); + return range.createContextualFragment(request.responseText); + } + d3.xml = d3_xhrType(function(request) { + return request.responseXML; + }); + if (typeof define === "function" && define.amd) define(d3); else if (typeof module === "object" && module.exports) module.exports = d3; + this.d3 = d3; +}(); \ No newline at end of file diff --git a/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.device.stats/public/js/device-stats.js b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.device.stats/public/js/device-stats.js new file mode 100644 index 0000000000..5104ab1762 --- /dev/null +++ b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.device.stats/public/js/device-stats.js @@ -0,0 +1,247 @@ +/* + * Copyright (c) 2016, 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. + */ + +var xAxis; + +var deviceType = $("#details").data("devicetype"); +var deviceId = $(".device-id").data("deviceid"); +var monitorOperations = $("#details").data("monitor"); +var appContext = $("#details").data("appcontext"); + +var marker1 = appContext + "/public/iot.unit.device.stats/images/map-marker-1.png"; +var marker2 = appContext + "/public/iot.unit.device.stats/images/map-marker-2.png"; + +var map, mapPoints = [], mapPaths = [], mapMarkers = []; +var palette = new Rickshaw.Color.Palette({scheme: "classic9"}); + +function initMap() { + if ($('#map').length) { + map = new google.maps.Map(document.getElementById("map"), { + center: {lat: 6.9344, lng: 79.8428}, + zoom: 12 + }); + } +} + +function formatDates() { + $(".formatDate").each(function () { + var timeStamp = $(this).html(); + $(this).html(getDateString(timeStamp)); + }); +} + +function getDateString(timeStamp) { + var monthNames = [ + "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec" + ]; + + var date = new Date(parseInt(timeStamp)); + var day = date.getDate(); + var monthIndex = date.getMonth() + 1; + if (monthIndex < 10) { + monthIndex = "0" + monthIndex; + } + + var year = date.getFullYear(); + var hours = date.getHours(); + var amPm = hours < 12 ? "AM" : "PM"; + + if (hours > 12) { + hours -= 12; + } + if (hours == 0) { + hours = 12; + } + + return day + '-' + monthNames[monthIndex - 1] + '-' + year + ' ' + hours + ':' + date.getMinutes() + amPm; +} + +$(window).on("resize", function () { + location.reload(false); +}); + +$(document).ready(function () { + formatDates(); + updateGraphs(); +}); + +function updateGraphs() { + var tv = 5000; + var graphs = {}; + for (var op in monitorOperations) { + var opName = monitorOperations[op].name; + if (opName == "gps") { + $("#map").removeClass("hidden"); + } else { + var xLabel = "", yLabel = ""; + if (monitorOperations[op].ui_unit) { + var graph_data = monitorOperations[op].ui_unit.data; + for (var d in graph_data) { + if (graph_data[d].hasOwnProperty("column")) { + if (graph_data[d]["column"]["ui-mapping"] == 'x-axis') { + xLabel = graph_data[d]["column"]["label"]; + } else if (graph_data[d]["column"]["ui-mapping"] == 'y-axis') { + yLabel = graph_data[d]["column"]["label"]; + } + } + } + } + var graphHtml = '
      ' + + '
      ' + yLabel + '
      ' + + '
      ' + + '
      ' + + '
      ' + + '
      ' + + '
      ' + + '
      ' + xLabel + '
      ' + + '
      '; + $("#div-chart").append(graphHtml); + + var graph = new Rickshaw.Graph({ + element: document.getElementById("chart-" + opName), + width: $("#chartWrapper").width() - 50, + height: 300, + renderer: "line", + padding: {top: 0.2, left: 0.0, right: 0.0, bottom: 0.2}, + series: new Rickshaw.Series.FixedDuration([{name: monitorOperations[op].name}], undefined, { + timeInterval: 10000, + maxDataPoints: 20, + color: palette.color(), + timeBase: new Date().getTime() / 1000 + }) + }); + + graph.render(); + + xAxis = new Rickshaw.Graph.Axis.Time({ + graph: graph + }); + + xAxis.render(); + + new Rickshaw.Graph.Axis.Y({ + graph: graph, + orientation: 'left', + height: 300, + tickFormat: Rickshaw.Fixtures.Number.formatKMBT, + element: document.getElementById('y_axis-' + opName) + }); + + new Rickshaw.Graph.Legend({ + graph: graph, + element: document.getElementById('legend-' + opName) + }); + + new Rickshaw.Graph.HoverDetail({ + graph: graph, + formatter: function (series, x, y) { + var date = '' + moment(x * 1000).format('Do MMM YYYY h:mm:ss a') + ''; + var swatch = ''; + return swatch + series.name + ": " + parseInt(y) + '
      ' + date; + } + }); + + graphs[opName] = graph; + } + } + + setInterval(function () { + + var getStatsRequest = $.ajax({ + url: appContext + "/api/operations/" + deviceType + "/stats?deviceId=" + + deviceId, + method: "get" + }); + + getStatsRequest.done(function (data) { + var stats = data.data; + var lastUpdate = -1; + for (var s in stats) { + var val = stats[s]; + if (!val) { + continue; + } + if (val.time > lastUpdate) { + lastUpdate = val.time; + } + delete val["time"]; + if (val.map) { + mapPoints.push(val.map); + var marker = new google.maps.Marker({ + position: val.map, + map: map, + icon: marker1, + title: "Seen at " + getDateString(lastUpdate) + }); + marker.setMap(map); + map.panTo(val.map); + mapMarkers.push(marker); + + if (mapPoints.length > 1) { + var l = mapPoints.length; + var path = new google.maps.Polyline({ + path: [mapPoints[l - 1], mapPoints[l - 2]], + geodesic: true, + strokeColor: "#FF0000", + strokeOpacity: 1.0, + strokeWeight: 2 + }); + + path.setMap(map); + mapPaths.push(path); + + mapMarkers[l - 2].setIcon(marker2); + } + + if (mapPoints.length >= 10) { + mapMarkers[0].setMap(null); + mapMarkers.splice(0, 1); + + mapPaths[0].setMap(null); + mapPaths.splice(0, 1); + + mapPoints.splice(0, 1); + } + } else { + var graphVals = {}; + for (var key in val) { + graphVals[key] = val[key]; + graphs[key].series.addData(graphVals); + graphs[key].render(); + } + } + } + + if (lastUpdate == -1) { + $('#last_seen').text("Not seen recently"); + } + + var timeDiff = new Date().getTime() - lastUpdate; + if (timeDiff < tv * 2) { + $('#last_seen').text("Last seen: A while ago"); + } else if (timeDiff < 60 * 1000) { + $('#last_seen').text("Last seen: Less than a minute ago"); + } else if (timeDiff < 60 * 60 * 1000) { + $('#last_seen').text("Last seen: " + Math.round(timeDiff / (60 * 1000)) + + " minutes ago"); + } else { + $('#last_seen').text("Last seen: " + getDateString(lastUpdate)); + } + }); + }, tv); +} \ No newline at end of file diff --git a/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.device.stats/public/js/moment.min.js b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.device.stats/public/js/moment.min.js new file mode 100644 index 0000000000..78e5aaadc7 --- /dev/null +++ b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.device.stats/public/js/moment.min.js @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2016, 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. + */ + +//! moment.js +//! version : 2.10.2 +//! authors : Tim Wood, Iskren Chernev, Moment.js contributors +//! license : MIT +//! momentjs.com +!function(a,b){"object"==typeof exports&&"undefined"!=typeof module?module.exports=b():"function"==typeof define&&define.amd?define(b):a.moment=b()}(this,function(){"use strict";function a(){return Ac.apply(null,arguments)}function b(a){Ac=a}function c(){return{empty:!1,unusedTokens:[],unusedInput:[],overflow:-2,charsLeftOver:0,nullInput:!1,invalidMonth:null,invalidFormat:!1,userInvalidated:!1,iso:!1}}function d(a){return"[object Array]"===Object.prototype.toString.call(a)}function e(a){return"[object Date]"===Object.prototype.toString.call(a)||a instanceof Date}function f(a,b){var c,d=[];for(c=0;c0)for(c in Cc)d=Cc[c],e=b[d],"undefined"!=typeof e&&(a[d]=e);return a}function m(b){l(this,b),this._d=new Date(+b._d),Dc===!1&&(Dc=!0,a.updateOffset(this),Dc=!1)}function n(a){return a instanceof m||null!=a&&g(a,"_isAMomentObject")}function o(a){var b=+a,c=0;return 0!==b&&isFinite(b)&&(c=b>=0?Math.floor(b):Math.ceil(b)),c}function p(a,b,c){var d,e=Math.min(a.length,b.length),f=Math.abs(a.length-b.length),g=0;for(d=0;e>d;d++)(c&&a[d]!==b[d]||!c&&o(a[d])!==o(b[d]))&&g++;return g+f}function q(){}function r(a){return a?a.toLowerCase().replace("_","-"):a}function s(a){for(var b,c,d,e,f=0;f0;){if(d=t(e.slice(0,b).join("-")))return d;if(c&&c.length>=b&&p(e,c,!0)>=b-1)break;b--}f++}return null}function t(a){var b=null;if(!Ec[a]&&"undefined"!=typeof module&&module&&module.exports)try{b=Bc._abbr,require("./locale/"+a),u(b)}catch(c){}return Ec[a]}function u(a,b){var c;return a&&(c="undefined"==typeof b?w(a):v(a,b),c&&(Bc=c)),Bc._abbr}function v(a,b){return null!==b?(b.abbr=a,Ec[a]||(Ec[a]=new q),Ec[a].set(b),u(a),Ec[a]):(delete Ec[a],null)}function w(a){var b;if(a&&a._locale&&a._locale._abbr&&(a=a._locale._abbr),!a)return Bc;if(!d(a)){if(b=t(a))return b;a=[a]}return s(a)}function x(a,b){var c=a.toLowerCase();Fc[c]=Fc[c+"s"]=Fc[b]=a}function y(a){return"string"==typeof a?Fc[a]||Fc[a.toLowerCase()]:void 0}function z(a){var b,c,d={};for(c in a)g(a,c)&&(b=y(c),b&&(d[b]=a[c]));return d}function A(b,c){return function(d){return null!=d?(C(this,b,d),a.updateOffset(this,c),this):B(this,b)}}function B(a,b){return a._d["get"+(a._isUTC?"UTC":"")+b]()}function C(a,b,c){return a._d["set"+(a._isUTC?"UTC":"")+b](c)}function D(a,b){var c;if("object"==typeof a)for(c in a)this.set(c,a[c]);else if(a=y(a),"function"==typeof this[a])return this[a](b);return this}function E(a,b,c){for(var d=""+Math.abs(a),e=a>=0;d.lengthb;b++)d[b]=Jc[d[b]]?Jc[d[b]]:G(d[b]);return function(e){var f="";for(b=0;c>b;b++)f+=d[b]instanceof Function?d[b].call(e,a):d[b];return f}}function I(a,b){return a.isValid()?(b=J(b,a.localeData()),Ic[b]||(Ic[b]=H(b)),Ic[b](a)):a.localeData().invalidDate()}function J(a,b){function c(a){return b.longDateFormat(a)||a}var d=5;for(Hc.lastIndex=0;d>=0&&Hc.test(a);)a=a.replace(Hc,c),Hc.lastIndex=0,d-=1;return a}function K(a,b,c){Yc[a]="function"==typeof b?b:function(a){return a&&c?c:b}}function L(a,b){return g(Yc,a)?Yc[a](b._strict,b._locale):new RegExp(M(a))}function M(a){return a.replace("\\","").replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g,function(a,b,c,d,e){return b||c||d||e}).replace(/[-\/\\^$*+?.()|[\]{}]/g,"\\$&")}function N(a,b){var c,d=b;for("string"==typeof a&&(a=[a]),"number"==typeof b&&(d=function(a,c){c[b]=o(a)}),c=0;cd;d++){if(e=i([2e3,d]),c&&!this._longMonthsParse[d]&&(this._longMonthsParse[d]=new RegExp("^"+this.months(e,"").replace(".","")+"$","i"),this._shortMonthsParse[d]=new RegExp("^"+this.monthsShort(e,"").replace(".","")+"$","i")),c||this._monthsParse[d]||(f="^"+this.months(e,"")+"|^"+this.monthsShort(e,""),this._monthsParse[d]=new RegExp(f.replace(".",""),"i")),c&&"MMMM"===b&&this._longMonthsParse[d].test(a))return d;if(c&&"MMM"===b&&this._shortMonthsParse[d].test(a))return d;if(!c&&this._monthsParse[d].test(a))return d}}function U(a,b){var c;return"string"==typeof b&&(b=a.localeData().monthsParse(b),"number"!=typeof b)?a:(c=Math.min(a.date(),Q(a.year(),b)),a._d["set"+(a._isUTC?"UTC":"")+"Month"](b,c),a)}function V(b){return null!=b?(U(this,b),a.updateOffset(this,!0),this):B(this,"Month")}function W(){return Q(this.year(),this.month())}function X(a){var b,c=a._a;return c&&-2===a._pf.overflow&&(b=c[_c]<0||c[_c]>11?_c:c[ad]<1||c[ad]>Q(c[$c],c[_c])?ad:c[bd]<0||c[bd]>24||24===c[bd]&&(0!==c[cd]||0!==c[dd]||0!==c[ed])?bd:c[cd]<0||c[cd]>59?cd:c[dd]<0||c[dd]>59?dd:c[ed]<0||c[ed]>999?ed:-1,a._pf._overflowDayOfYear&&($c>b||b>ad)&&(b=ad),a._pf.overflow=b),a}function Y(b){a.suppressDeprecationWarnings===!1&&"undefined"!=typeof console&&console.warn&&console.warn("Deprecation warning: "+b)}function Z(a,b){var c=!0;return h(function(){return c&&(Y(a),c=!1),b.apply(this,arguments)},b)}function $(a,b){hd[a]||(Y(b),hd[a]=!0)}function _(a){var b,c,d=a._i,e=id.exec(d);if(e){for(a._pf.iso=!0,b=0,c=jd.length;c>b;b++)if(jd[b][1].exec(d)){a._f=jd[b][0]+(e[6]||" ");break}for(b=0,c=kd.length;c>b;b++)if(kd[b][1].exec(d)){a._f+=kd[b][0];break}d.match(Vc)&&(a._f+="Z"),sa(a)}else a._isValid=!1}function aa(b){var c=ld.exec(b._i);return null!==c?void(b._d=new Date(+c[1])):(_(b),void(b._isValid===!1&&(delete b._isValid,a.createFromInputFallback(b))))}function ba(a,b,c,d,e,f,g){var h=new Date(a,b,c,d,e,f,g);return 1970>a&&h.setFullYear(a),h}function ca(a){var b=new Date(Date.UTC.apply(null,arguments));return 1970>a&&b.setUTCFullYear(a),b}function da(a){return ea(a)?366:365}function ea(a){return a%4===0&&a%100!==0||a%400===0}function fa(){return ea(this.year())}function ga(a,b,c){var d,e=c-b,f=c-a.day();return f>e&&(f-=7),e-7>f&&(f+=7),d=za(a).add(f,"d"),{week:Math.ceil(d.dayOfYear()/7),year:d.year()}}function ha(a){return ga(a,this._week.dow,this._week.doy).week}function ia(){return this._week.dow}function ja(){return this._week.doy}function ka(a){var b=this.localeData().week(this);return null==a?b:this.add(7*(a-b),"d")}function la(a){var b=ga(this,1,4).week;return null==a?b:this.add(7*(a-b),"d")}function ma(a,b,c,d,e){var f,g,h=ca(a,0,1).getUTCDay();return h=0===h?7:h,c=null!=c?c:e,f=e-h+(h>d?7:0)-(e>h?7:0),g=7*(b-1)+(c-e)+f+1,{year:g>0?a:a-1,dayOfYear:g>0?g:da(a-1)+g}}function na(a){var b=Math.round((this.clone().startOf("day")-this.clone().startOf("year"))/864e5)+1;return null==a?b:this.add(a-b,"d")}function oa(a,b,c){return null!=a?a:null!=b?b:c}function pa(a){var b=new Date;return a._useUTC?[b.getUTCFullYear(),b.getUTCMonth(),b.getUTCDate()]:[b.getFullYear(),b.getMonth(),b.getDate()]}function qa(a){var b,c,d,e,f=[];if(!a._d){for(d=pa(a),a._w&&null==a._a[ad]&&null==a._a[_c]&&ra(a),a._dayOfYear&&(e=oa(a._a[$c],d[$c]),a._dayOfYear>da(e)&&(a._pf._overflowDayOfYear=!0),c=ca(e,0,a._dayOfYear),a._a[_c]=c.getUTCMonth(),a._a[ad]=c.getUTCDate()),b=0;3>b&&null==a._a[b];++b)a._a[b]=f[b]=d[b];for(;7>b;b++)a._a[b]=f[b]=null==a._a[b]?2===b?1:0:a._a[b];24===a._a[bd]&&0===a._a[cd]&&0===a._a[dd]&&0===a._a[ed]&&(a._nextDay=!0,a._a[bd]=0),a._d=(a._useUTC?ca:ba).apply(null,f),null!=a._tzm&&a._d.setUTCMinutes(a._d.getUTCMinutes()-a._tzm),a._nextDay&&(a._a[bd]=24)}}function ra(a){var b,c,d,e,f,g,h;b=a._w,null!=b.GG||null!=b.W||null!=b.E?(f=1,g=4,c=oa(b.GG,a._a[$c],ga(za(),1,4).year),d=oa(b.W,1),e=oa(b.E,1)):(f=a._locale._week.dow,g=a._locale._week.doy,c=oa(b.gg,a._a[$c],ga(za(),f,g).year),d=oa(b.w,1),null!=b.d?(e=b.d,f>e&&++d):e=null!=b.e?b.e+f:f),h=ma(c,d,e,g,f),a._a[$c]=h.year,a._dayOfYear=h.dayOfYear}function sa(b){if(b._f===a.ISO_8601)return void _(b);b._a=[],b._pf.empty=!0;var c,d,e,f,g,h=""+b._i,i=h.length,j=0;for(e=J(b._f,b._locale).match(Gc)||[],c=0;c0&&b._pf.unusedInput.push(g),h=h.slice(h.indexOf(d)+d.length),j+=d.length),Jc[f]?(d?b._pf.empty=!1:b._pf.unusedTokens.push(f),P(f,d,b)):b._strict&&!d&&b._pf.unusedTokens.push(f);b._pf.charsLeftOver=i-j,h.length>0&&b._pf.unusedInput.push(h),b._pf.bigHour===!0&&b._a[bd]<=12&&(b._pf.bigHour=void 0),b._a[bd]=ta(b._locale,b._a[bd],b._meridiem),qa(b),X(b)}function ta(a,b,c){var d;return null==c?b:null!=a.meridiemHour?a.meridiemHour(b,c):null!=a.isPM?(d=a.isPM(c),d&&12>b&&(b+=12),d||12!==b||(b=0),b):b}function ua(a){var b,d,e,f,g;if(0===a._f.length)return a._pf.invalidFormat=!0,void(a._d=new Date(0/0));for(f=0;fg)&&(e=g,d=b));h(a,d||b)}function va(a){if(!a._d){var b=z(a._i);a._a=[b.year,b.month,b.day||b.date,b.hour,b.minute,b.second,b.millisecond],qa(a)}}function wa(a){var b,c=a._i,e=a._f;return a._locale=a._locale||w(a._l),null===c||void 0===e&&""===c?k({nullInput:!0}):("string"==typeof c&&(a._i=c=a._locale.preparse(c)),n(c)?new m(X(c)):(d(e)?ua(a):e?sa(a):xa(a),b=new m(X(a)),b._nextDay&&(b.add(1,"d"),b._nextDay=void 0),b))}function xa(b){var c=b._i;void 0===c?b._d=new Date:e(c)?b._d=new Date(+c):"string"==typeof c?aa(b):d(c)?(b._a=f(c.slice(0),function(a){return parseInt(a,10)}),qa(b)):"object"==typeof c?va(b):"number"==typeof c?b._d=new Date(c):a.createFromInputFallback(b)}function ya(a,b,d,e,f){var g={};return"boolean"==typeof d&&(e=d,d=void 0),g._isAMomentObject=!0,g._useUTC=g._isUTC=f,g._l=d,g._i=a,g._f=b,g._strict=e,g._pf=c(),wa(g)}function za(a,b,c,d){return ya(a,b,c,d,!1)}function Aa(a,b){var c,e;if(1===b.length&&d(b[0])&&(b=b[0]),!b.length)return za();for(c=b[0],e=1;ea&&(a=-a,c="-"),c+E(~~(a/60),2)+b+E(~~a%60,2)})}function Ga(a){var b=(a||"").match(Vc)||[],c=b[b.length-1]||[],d=(c+"").match(qd)||["-",0,0],e=+(60*d[1])+o(d[2]);return"+"===d[0]?e:-e}function Ha(b,c){var d,f;return c._isUTC?(d=c.clone(),f=(n(b)||e(b)?+b:+za(b))-+d,d._d.setTime(+d._d+f),a.updateOffset(d,!1),d):za(b).local();return c._isUTC?za(b).zone(c._offset||0):za(b).local()}function Ia(a){return 15*-Math.round(a._d.getTimezoneOffset()/15)}function Ja(b,c){var d,e=this._offset||0;return null!=b?("string"==typeof b&&(b=Ga(b)),Math.abs(b)<16&&(b=60*b),!this._isUTC&&c&&(d=Ia(this)),this._offset=b,this._isUTC=!0,null!=d&&this.add(d,"m"),e!==b&&(!c||this._changeInProgress?Za(this,Ua(b-e,"m"),1,!1):this._changeInProgress||(this._changeInProgress=!0,a.updateOffset(this,!0),this._changeInProgress=null)),this):this._isUTC?e:Ia(this)}function Ka(a,b){return null!=a?("string"!=typeof a&&(a=-a),this.utcOffset(a,b),this):-this.utcOffset()}function La(a){return this.utcOffset(0,a)}function Ma(a){return this._isUTC&&(this.utcOffset(0,a),this._isUTC=!1,a&&this.subtract(Ia(this),"m")),this}function Na(){return this._tzm?this.utcOffset(this._tzm):"string"==typeof this._i&&this.utcOffset(Ga(this._i)),this}function Oa(a){return a=a?za(a).utcOffset():0,(this.utcOffset()-a)%60===0}function Pa(){return this.utcOffset()>this.clone().month(0).utcOffset()||this.utcOffset()>this.clone().month(5).utcOffset()}function Qa(){if(this._a){var a=this._isUTC?i(this._a):za(this._a);return this.isValid()&&p(this._a,a.toArray())>0}return!1}function Ra(){return!this._isUTC}function Sa(){return this._isUTC}function Ta(){return this._isUTC&&0===this._offset}function Ua(a,b){var c,d,e,f=a,h=null;return Ea(a)?f={ms:a._milliseconds,d:a._days,M:a._months}:"number"==typeof a?(f={},b?f[b]=a:f.milliseconds=a):(h=rd.exec(a))?(c="-"===h[1]?-1:1,f={y:0,d:o(h[ad])*c,h:o(h[bd])*c,m:o(h[cd])*c,s:o(h[dd])*c,ms:o(h[ed])*c}):(h=sd.exec(a))?(c="-"===h[1]?-1:1,f={y:Va(h[2],c),M:Va(h[3],c),d:Va(h[4],c),h:Va(h[5],c),m:Va(h[6],c),s:Va(h[7],c),w:Va(h[8],c)}):null==f?f={}:"object"==typeof f&&("from"in f||"to"in f)&&(e=Xa(za(f.from),za(f.to)),f={},f.ms=e.milliseconds,f.M=e.months),d=new Da(f),Ea(a)&&g(a,"_locale")&&(d._locale=a._locale),d}function Va(a,b){var c=a&&parseFloat(a.replace(",","."));return(isNaN(c)?0:c)*b}function Wa(a,b){var c={milliseconds:0,months:0};return c.months=b.month()-a.month()+12*(b.year()-a.year()),a.clone().add(c.months,"M").isAfter(b)&&--c.months,c.milliseconds=+b-+a.clone().add(c.months,"M"),c}function Xa(a,b){var c;return b=Ha(b,a),a.isBefore(b)?c=Wa(a,b):(c=Wa(b,a),c.milliseconds=-c.milliseconds,c.months=-c.months),c}function Ya(a,b){return function(c,d){var e,f;return null===d||isNaN(+d)||($(b,"moment()."+b+"(period, number) is deprecated. Please use moment()."+b+"(number, period)."),f=c,c=d,d=f),c="string"==typeof c?+c:c,e=Ua(c,d),Za(this,e,a),this}}function Za(b,c,d,e){var f=c._milliseconds,g=c._days,h=c._months;e=null==e?!0:e,f&&b._d.setTime(+b._d+f*d),g&&C(b,"Date",B(b,"Date")+g*d),h&&U(b,B(b,"Month")+h*d),e&&a.updateOffset(b,g||h)}function $a(a){var b=a||za(),c=Ha(b,this).startOf("day"),d=this.diff(c,"days",!0),e=-6>d?"sameElse":-1>d?"lastWeek":0>d?"lastDay":1>d?"sameDay":2>d?"nextDay":7>d?"nextWeek":"sameElse";return this.format(this.localeData().calendar(e,this,za(b)))}function _a(){return new m(this)}function ab(a,b){var c;return b=y("undefined"!=typeof b?b:"millisecond"),"millisecond"===b?(a=n(a)?a:za(a),+this>+a):(c=n(a)?+a:+za(a),c<+this.clone().startOf(b))}function bb(a,b){var c;return b=y("undefined"!=typeof b?b:"millisecond"),"millisecond"===b?(a=n(a)?a:za(a),+a>+this):(c=n(a)?+a:+za(a),+this.clone().endOf(b)a?Math.ceil(a):Math.floor(a)}function fb(a,b,c){var d,e,f=Ha(a,this),g=6e4*(f.utcOffset()-this.utcOffset());return b=y(b),"year"===b||"month"===b||"quarter"===b?(e=gb(this,f),"quarter"===b?e/=3:"year"===b&&(e/=12)):(d=this-f,e="second"===b?d/1e3:"minute"===b?d/6e4:"hour"===b?d/36e5:"day"===b?(d-g)/864e5:"week"===b?(d-g)/6048e5:d),c?e:eb(e)}function gb(a,b){var c,d,e=12*(b.year()-a.year())+(b.month()-a.month()),f=a.clone().add(e,"months");return 0>b-f?(c=a.clone().add(e-1,"months"),d=(b-f)/(f-c)):(c=a.clone().add(e+1,"months"),d=(b-f)/(c-f)),-(e+d)}function hb(){return this.clone().locale("en").format("ddd MMM DD YYYY HH:mm:ss [GMT]ZZ")}function ib(){var a=this.clone().utc();return 0b;b++)if(this._weekdaysParse[b]||(c=za([2e3,1]).day(b),d="^"+this.weekdays(c,"")+"|^"+this.weekdaysShort(c,"")+"|^"+this.weekdaysMin(c,""),this._weekdaysParse[b]=new RegExp(d.replace(".",""),"i")),this._weekdaysParse[b].test(a))return b}function Jb(a){var b=this._isUTC?this._d.getUTCDay():this._d.getDay();return null!=a?(a=Eb(a,this.localeData()),this.add(a-b,"d")):b}function Kb(a){var b=(this.day()+7-this.localeData()._week.dow)%7;return null==a?b:this.add(a-b,"d")}function Lb(a){return null==a?this.day()||7:this.day(this.day()%7?a:a-7)}function Mb(a,b){F(a,0,0,function(){return this.localeData().meridiem(this.hours(),this.minutes(),b)})}function Nb(a,b){return b._meridiemParse}function Ob(a){return"p"===(a+"").toLowerCase().charAt(0)}function Pb(a,b,c){return a>11?c?"pm":"PM":c?"am":"AM"}function Qb(a){F(0,[a,3],0,"millisecond")}function Rb(){return this._isUTC?"UTC":""}function Sb(){return this._isUTC?"Coordinated Universal Time":""}function Tb(a){return za(1e3*a)}function Ub(){return za.apply(null,arguments).parseZone()}function Vb(a,b,c){var d=this._calendar[a];return"function"==typeof d?d.call(b,c):d}function Wb(a){var b=this._longDateFormat[a];return!b&&this._longDateFormat[a.toUpperCase()]&&(b=this._longDateFormat[a.toUpperCase()].replace(/MMMM|MM|DD|dddd/g,function(a){return a.slice(1)}),this._longDateFormat[a]=b),b}function Xb(){return this._invalidDate}function Yb(a){return this._ordinal.replace("%d",a)}function Zb(a){return a}function $b(a,b,c,d){var e=this._relativeTime[c];return"function"==typeof e?e(a,b,c,d):e.replace(/%d/i,a)}function _b(a,b){var c=this._relativeTime[a>0?"future":"past"];return"function"==typeof c?c(b):c.replace(/%s/i,b)}function ac(a){var b,c;for(c in a)b=a[c],"function"==typeof b?this[c]=b:this["_"+c]=b;this._ordinalParseLenient=new RegExp(this._ordinalParse.source+"|"+/\d{1,2}/.source)}function bc(a,b,c,d){var e=w(),f=i().set(d,b);return e[c](f,a)}function cc(a,b,c,d,e){if("number"==typeof a&&(b=a,a=void 0),a=a||"",null!=b)return bc(a,b,c,e);var f,g=[];for(f=0;d>f;f++)g[f]=bc(a,f,c,e);return g}function dc(a,b){return cc(a,b,"months",12,"month")}function ec(a,b){return cc(a,b,"monthsShort",12,"month")}function fc(a,b){return cc(a,b,"weekdays",7,"day")}function gc(a,b){return cc(a,b,"weekdaysShort",7,"day")}function hc(a,b){return cc(a,b,"weekdaysMin",7,"day")}function ic(){var a=this._data;return this._milliseconds=Od(this._milliseconds),this._days=Od(this._days),this._months=Od(this._months),a.milliseconds=Od(a.milliseconds),a.seconds=Od(a.seconds),a.minutes=Od(a.minutes),a.hours=Od(a.hours),a.months=Od(a.months),a.years=Od(a.years),this}function jc(a,b,c,d){var e=Ua(b,c);return a._milliseconds+=d*e._milliseconds,a._days+=d*e._days,a._months+=d*e._months,a._bubble()}function kc(a,b){return jc(this,a,b,1)}function lc(a,b){return jc(this,a,b,-1)}function mc(){var a,b,c,d=this._milliseconds,e=this._days,f=this._months,g=this._data,h=0;return g.milliseconds=d%1e3,a=eb(d/1e3),g.seconds=a%60,b=eb(a/60),g.minutes=b%60,c=eb(b/60),g.hours=c%24,e+=eb(c/24),h=eb(nc(e)),e-=eb(oc(h)),f+=eb(e/30),e%=30,h+=eb(f/12),f%=12,g.days=e,g.months=f,g.years=h,this}function nc(a){return 400*a/146097}function oc(a){return 146097*a/400}function pc(a){var b,c,d=this._milliseconds;if(a=y(a),"month"===a||"year"===a)return b=this._days+d/864e5,c=this._months+12*nc(b),"month"===a?c:c/12;switch(b=this._days+Math.round(oc(this._months/12)),a){case"week":return b/7+d/6048e5;case"day":return b+d/864e5;case"hour":return 24*b+d/36e5;case"minute":return 24*b*60+d/6e4;case"second":return 24*b*60*60+d/1e3;case"millisecond":return Math.floor(24*b*60*60*1e3)+d;default:throw new Error("Unknown unit "+a)}}function qc(){return this._milliseconds+864e5*this._days+this._months%12*2592e6+31536e6*o(this._months/12)}function rc(a){return function(){return this.as(a)}}function sc(a){return a=y(a),this[a+"s"]()}function tc(a){return function(){return this._data[a]}}function uc(){return eb(this.days()/7)}function vc(a,b,c,d,e){return e.relativeTime(b||1,!!c,a,d)}function wc(a,b,c){var d=Ua(a).abs(),e=ce(d.as("s")),f=ce(d.as("m")),g=ce(d.as("h")),h=ce(d.as("d")),i=ce(d.as("M")),j=ce(d.as("y")),k=e0,k[4]=c,vc.apply(null,k)}function xc(a,b){return void 0===de[a]?!1:void 0===b?de[a]:(de[a]=b,!0)}function yc(a){var b=this.localeData(),c=wc(this,!a,b);return a&&(c=b.pastFuture(+this,c)),b.postformat(c)}function zc(){var a=ee(this.years()),b=ee(this.months()),c=ee(this.days()),d=ee(this.hours()),e=ee(this.minutes()),f=ee(this.seconds()+this.milliseconds()/1e3),g=this.asSeconds();return g?(0>g?"-":"")+"P"+(a?a+"Y":"")+(b?b+"M":"")+(c?c+"D":"")+(d||e||f?"T":"")+(d?d+"H":"")+(e?e+"M":"")+(f?f+"S":""):"P0D"}var Ac,Bc,Cc=a.momentProperties=[],Dc=!1,Ec={},Fc={},Gc=/(\[[^\[]*\])|(\\)?(Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Q|YYYYYY|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|mm?|ss?|S{1,4}|x|X|zz?|ZZ?|.)/g,Hc=/(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g,Ic={},Jc={},Kc=/\d/,Lc=/\d\d/,Mc=/\d{3}/,Nc=/\d{4}/,Oc=/[+-]?\d{6}/,Pc=/\d\d?/,Qc=/\d{1,3}/,Rc=/\d{1,4}/,Sc=/[+-]?\d{1,6}/,Tc=/\d+/,Uc=/[+-]?\d+/,Vc=/Z|[+-]\d\d:?\d\d/gi,Wc=/[+-]?\d+(\.\d{1,3})?/,Xc=/[0-9]*['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06FF\/]+(\s*?[\u0600-\u06FF]+){1,2}/i,Yc={},Zc={},$c=0,_c=1,ad=2,bd=3,cd=4,dd=5,ed=6;F("M",["MM",2],"Mo",function(){return this.month()+1}),F("MMM",0,0,function(a){return this.localeData().monthsShort(this,a)}),F("MMMM",0,0,function(a){return this.localeData().months(this,a)}),x("month","M"),K("M",Pc),K("MM",Pc,Lc),K("MMM",Xc),K("MMMM",Xc),N(["M","MM"],function(a,b){b[_c]=o(a)-1}),N(["MMM","MMMM"],function(a,b,c,d){var e=c._locale.monthsParse(a,d,c._strict);null!=e?b[_c]=e:c._pf.invalidMonth=a});var fd="January_February_March_April_May_June_July_August_September_October_November_December".split("_"),gd="Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"),hd={};a.suppressDeprecationWarnings=!1;var id=/^\s*(?:[+-]\d{6}|\d{4})-(?:(\d\d-\d\d)|(W\d\d$)|(W\d\d-\d)|(\d\d\d))((T| )(\d\d(:\d\d(:\d\d(\.\d+)?)?)?)?([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/,jd=[["YYYYYY-MM-DD",/[+-]\d{6}-\d{2}-\d{2}/],["YYYY-MM-DD",/\d{4}-\d{2}-\d{2}/],["GGGG-[W]WW-E",/\d{4}-W\d{2}-\d/],["GGGG-[W]WW",/\d{4}-W\d{2}/],["YYYY-DDD",/\d{4}-\d{3}/]],kd=[["HH:mm:ss.SSSS",/(T| )\d\d:\d\d:\d\d\.\d+/],["HH:mm:ss",/(T| )\d\d:\d\d:\d\d/],["HH:mm",/(T| )\d\d:\d\d/],["HH",/(T| )\d\d/]],ld=/^\/?Date\((\-?\d+)/i;a.createFromInputFallback=Z("moment construction falls back to js Date. This is discouraged and will be removed in upcoming major release. Please refer to https://github.com/moment/moment/issues/1407 for more info.",function(a){a._d=new Date(a._i+(a._useUTC?" UTC":""))}),F(0,["YY",2],0,function(){return this.year()%100}),F(0,["YYYY",4],0,"year"),F(0,["YYYYY",5],0,"year"),F(0,["YYYYYY",6,!0],0,"year"),x("year","y"),K("Y",Uc),K("YY",Pc,Lc),K("YYYY",Rc,Nc),K("YYYYY",Sc,Oc),K("YYYYYY",Sc,Oc),N(["YYYY","YYYYY","YYYYYY"],$c),N("YY",function(b,c){c[$c]=a.parseTwoDigitYear(b)}),a.parseTwoDigitYear=function(a){return o(a)+(o(a)>68?1900:2e3)};var md=A("FullYear",!1);F("w",["ww",2],"wo","week"),F("W",["WW",2],"Wo","isoWeek"),x("week","w"),x("isoWeek","W"),K("w",Pc),K("ww",Pc,Lc),K("W",Pc),K("WW",Pc,Lc),O(["w","ww","W","WW"],function(a,b,c,d){b[d.substr(0,1)]=o(a)});var nd={dow:0,doy:6};F("DDD",["DDDD",3],"DDDo","dayOfYear"),x("dayOfYear","DDD"),K("DDD",Qc),K("DDDD",Mc),N(["DDD","DDDD"],function(a,b,c){c._dayOfYear=o(a)}),a.ISO_8601=function(){};var od=Z("moment().min is deprecated, use moment.min instead. https://github.com/moment/moment/issues/1548",function(){var a=za.apply(null,arguments);return this>a?this:a}),pd=Z("moment().max is deprecated, use moment.max instead. https://github.com/moment/moment/issues/1548",function(){var a=za.apply(null,arguments);return a>this?this:a});Fa("Z",":"),Fa("ZZ",""),K("Z",Vc),K("ZZ",Vc),N(["Z","ZZ"],function(a,b,c){c._useUTC=!0,c._tzm=Ga(a)});var qd=/([\+\-]|\d\d)/gi;a.updateOffset=function(){};var rd=/(\-)?(?:(\d*)\.)?(\d+)\:(\d+)(?:\:(\d+)\.?(\d{3})?)?/,sd=/^(-)?P(?:(?:([0-9,.]*)Y)?(?:([0-9,.]*)M)?(?:([0-9,.]*)D)?(?:T(?:([0-9,.]*)H)?(?:([0-9,.]*)M)?(?:([0-9,.]*)S)?)?|([0-9,.]*)W)$/;Ua.fn=Da.prototype;var td=Ya(1,"add"),ud=Ya(-1,"subtract");a.defaultFormat="YYYY-MM-DDTHH:mm:ssZ";var vd=Z("moment().lang() is deprecated. Instead, use moment().localeData() to get the language configuration. Use moment().locale() to change languages.",function(a){return void 0===a?this.localeData():this.locale(a)});F(0,["gg",2],0,function(){return this.weekYear()%100}),F(0,["GG",2],0,function(){return this.isoWeekYear()%100}),xb("gggg","weekYear"),xb("ggggg","weekYear"),xb("GGGG","isoWeekYear"),xb("GGGGG","isoWeekYear"),x("weekYear","gg"),x("isoWeekYear","GG"),K("G",Uc),K("g",Uc),K("GG",Pc,Lc),K("gg",Pc,Lc),K("GGGG",Rc,Nc),K("gggg",Rc,Nc),K("GGGGG",Sc,Oc),K("ggggg",Sc,Oc),O(["gggg","ggggg","GGGG","GGGGG"],function(a,b,c,d){b[d.substr(0,2)]=o(a)}),O(["gg","GG"],function(b,c,d,e){c[e]=a.parseTwoDigitYear(b)}),F("Q",0,0,"quarter"),x("quarter","Q"),K("Q",Kc),N("Q",function(a,b){b[_c]=3*(o(a)-1)}),F("D",["DD",2],"Do","date"),x("date","D"),K("D",Pc),K("DD",Pc,Lc),K("Do",function(a,b){return a?b._ordinalParse:b._ordinalParseLenient}),N(["D","DD"],ad),N("Do",function(a,b){b[ad]=o(a.match(Pc)[0],10)});var wd=A("Date",!0);F("d",0,"do","day"),F("dd",0,0,function(a){return this.localeData().weekdaysMin(this,a)}),F("ddd",0,0,function(a){return this.localeData().weekdaysShort(this,a)}),F("dddd",0,0,function(a){return this.localeData().weekdays(this,a)}),F("e",0,0,"weekday"),F("E",0,0,"isoWeekday"),x("day","d"),x("weekday","e"),x("isoWeekday","E"),K("d",Pc),K("e",Pc),K("E",Pc),K("dd",Xc),K("ddd",Xc),K("dddd",Xc),O(["dd","ddd","dddd"],function(a,b,c){var d=c._locale.weekdaysParse(a);null!=d?b.d=d:c._pf.invalidWeekday=a}),O(["d","e","E"],function(a,b,c,d){b[d]=o(a)});var xd="Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),yd="Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),zd="Su_Mo_Tu_We_Th_Fr_Sa".split("_");F("H",["HH",2],0,"hour"),F("h",["hh",2],0,function(){return this.hours()%12||12}),Mb("a",!0),Mb("A",!1),x("hour","h"),K("a",Nb),K("A",Nb),K("H",Pc),K("h",Pc),K("HH",Pc,Lc),K("hh",Pc,Lc),N(["H","HH"],bd),N(["a","A"],function(a,b,c){c._isPm=c._locale.isPM(a),c._meridiem=a}),N(["h","hh"],function(a,b,c){b[bd]=o(a),c._pf.bigHour=!0});var Ad=/[ap]\.?m?\.?/i,Bd=A("Hours",!0);F("m",["mm",2],0,"minute"),x("minute","m"),K("m",Pc),K("mm",Pc,Lc),N(["m","mm"],cd);var Cd=A("Minutes",!1);F("s",["ss",2],0,"second"),x("second","s"),K("s",Pc),K("ss",Pc,Lc),N(["s","ss"],dd);var Dd=A("Seconds",!1);F("S",0,0,function(){return~~(this.millisecond()/100)}),F(0,["SS",2],0,function(){return~~(this.millisecond()/10)}),Qb("SSS"),Qb("SSSS"),x("millisecond","ms"),K("S",Qc,Kc),K("SS",Qc,Lc),K("SSS",Qc,Mc),K("SSSS",Tc),N(["S","SS","SSS","SSSS"],function(a,b){b[ed]=o(1e3*("0."+a))});var Ed=A("Milliseconds",!1);F("z",0,0,"zoneAbbr"),F("zz",0,0,"zoneName");var Fd=m.prototype;Fd.add=td,Fd.calendar=$a,Fd.clone=_a,Fd.diff=fb,Fd.endOf=pb,Fd.format=jb,Fd.from=kb,Fd.fromNow=lb,Fd.get=D,Fd.invalidAt=wb,Fd.isAfter=ab,Fd.isBefore=bb,Fd.isBetween=cb,Fd.isSame=db,Fd.isValid=ub,Fd.lang=vd,Fd.locale=mb,Fd.localeData=nb,Fd.max=pd,Fd.min=od,Fd.parsingFlags=vb,Fd.set=D,Fd.startOf=ob,Fd.subtract=ud,Fd.toArray=tb,Fd.toDate=sb,Fd.toISOString=ib,Fd.toJSON=ib,Fd.toString=hb,Fd.unix=rb,Fd.valueOf=qb,Fd.year=md,Fd.isLeapYear=fa,Fd.weekYear=zb,Fd.isoWeekYear=Ab,Fd.quarter=Fd.quarters=Db,Fd.month=V,Fd.daysInMonth=W,Fd.week=Fd.weeks=ka,Fd.isoWeek=Fd.isoWeeks=la,Fd.weeksInYear=Cb,Fd.isoWeeksInYear=Bb,Fd.date=wd,Fd.day=Fd.days=Jb,Fd.weekday=Kb,Fd.isoWeekday=Lb,Fd.dayOfYear=na,Fd.hour=Fd.hours=Bd,Fd.minute=Fd.minutes=Cd,Fd.second=Fd.seconds=Dd,Fd.millisecond=Fd.milliseconds=Ed,Fd.utcOffset=Ja,Fd.utc=La,Fd.local=Ma,Fd.parseZone=Na,Fd.hasAlignedHourOffset=Oa,Fd.isDST=Pa,Fd.isDSTShifted=Qa,Fd.isLocal=Ra,Fd.isUtcOffset=Sa,Fd.isUtc=Ta,Fd.isUTC=Ta,Fd.zoneAbbr=Rb,Fd.zoneName=Sb,Fd.dates=Z("dates accessor is deprecated. Use date instead.",wd),Fd.months=Z("months accessor is deprecated. Use month instead",V),Fd.years=Z("years accessor is deprecated. Use year instead",md),Fd.zone=Z("moment().zone is deprecated, use moment().utcOffset instead. https://github.com/moment/moment/issues/1779",Ka);var Gd=Fd,Hd={sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},Id={LTS:"h:mm:ss A",LT:"h:mm A",L:"MM/DD/YYYY",LL:"MMMM D, YYYY",LLL:"MMMM D, YYYY LT",LLLL:"dddd, MMMM D, YYYY LT"},Jd="Invalid date",Kd="%d",Ld=/\d{1,2}/,Md={future:"in %s",past:"%s ago",s:"a few seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},Nd=q.prototype;Nd._calendar=Hd,Nd.calendar=Vb,Nd._longDateFormat=Id,Nd.longDateFormat=Wb,Nd._invalidDate=Jd,Nd.invalidDate=Xb,Nd._ordinal=Kd,Nd.ordinal=Yb,Nd._ordinalParse=Ld, +Nd.preparse=Zb,Nd.postformat=Zb,Nd._relativeTime=Md,Nd.relativeTime=$b,Nd.pastFuture=_b,Nd.set=ac,Nd.months=R,Nd._months=fd,Nd.monthsShort=S,Nd._monthsShort=gd,Nd.monthsParse=T,Nd.week=ha,Nd._week=nd,Nd.firstDayOfYear=ja,Nd.firstDayOfWeek=ia,Nd.weekdays=Fb,Nd._weekdays=xd,Nd.weekdaysMin=Hb,Nd._weekdaysMin=zd,Nd.weekdaysShort=Gb,Nd._weekdaysShort=yd,Nd.weekdaysParse=Ib,Nd.isPM=Ob,Nd._meridiemParse=Ad,Nd.meridiem=Pb,u("en",{ordinalParse:/\d{1,2}(th|st|nd|rd)/,ordinal:function(a){var b=a%10,c=1===o(a%100/10)?"th":1===b?"st":2===b?"nd":3===b?"rd":"th";return a+c}}),a.lang=Z("moment.lang is deprecated. Use moment.locale instead.",u),a.langData=Z("moment.langData is deprecated. Use moment.localeData instead.",w);var Od=Math.abs,Pd=rc("ms"),Qd=rc("s"),Rd=rc("m"),Sd=rc("h"),Td=rc("d"),Ud=rc("w"),Vd=rc("M"),Wd=rc("y"),Xd=tc("milliseconds"),Yd=tc("seconds"),Zd=tc("minutes"),$d=tc("hours"),_d=tc("days"),ae=tc("months"),be=tc("years"),ce=Math.round,de={s:45,m:45,h:22,d:26,M:11},ee=Math.abs,fe=Da.prototype;fe.abs=ic,fe.add=kc,fe.subtract=lc,fe.as=pc,fe.asMilliseconds=Pd,fe.asSeconds=Qd,fe.asMinutes=Rd,fe.asHours=Sd,fe.asDays=Td,fe.asWeeks=Ud,fe.asMonths=Vd,fe.asYears=Wd,fe.valueOf=qc,fe._bubble=mc,fe.get=sc,fe.milliseconds=Xd,fe.seconds=Yd,fe.minutes=Zd,fe.hours=$d,fe.days=_d,fe.weeks=uc,fe.months=ae,fe.years=be,fe.humanize=yc,fe.toISOString=zc,fe.toString=zc,fe.toJSON=zc,fe.locale=mb,fe.localeData=nb,fe.toIsoString=Z("toIsoString() is deprecated. Please use toISOString() instead (notice the capitals)",zc),fe.lang=vd,F("X",0,0,"unix"),F("x",0,0,"valueOf"),K("x",Uc),K("X",Wc),N("X",function(a,b,c){c._d=new Date(1e3*parseFloat(a,10))}),N("x",function(a,b,c){c._d=new Date(o(a))}),a.version="2.10.2",b(za),a.fn=Gd,a.min=Ba,a.max=Ca,a.utc=i,a.unix=Tb,a.months=dc,a.isDate=e,a.locale=u,a.invalid=k,a.duration=Ua,a.isMoment=n,a.weekdays=fc,a.parseZone=Ub,a.localeData=w,a.isDuration=Ea,a.monthsShort=ec,a.weekdaysMin=hc,a.defineLocale=v,a.weekdaysShort=gc,a.normalizeUnits=y,a.relativeTimeThreshold=xc;var ge=a;return ge}); \ No newline at end of file diff --git a/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.device.stats/public/js/rickshaw.min.js b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.device.stats/public/js/rickshaw.min.js new file mode 100644 index 0000000000..be060e0b87 --- /dev/null +++ b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.device.stats/public/js/rickshaw.min.js @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2016, 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(root,factory){if(typeof define==="function"&&define.amd){define(["d3.min"],function(d3){return root.Rickshaw=factory(d3)})}else if(typeof exports==="object"){module.exports=factory(require("d3"))}else{root.Rickshaw=factory(d3)}})(this,function(d3){var Rickshaw={namespace:function(namespace,obj){var parts=namespace.split(".");var parent=Rickshaw;for(var i=1,length=parts.length;i0){var x=s.data[0].x;var y=s.data[0].y;if(typeof x!="number"||typeof y!="number"&&y!==null){throw"x and y properties of points should be numbers instead of "+typeof x+" and "+typeof y}}if(s.data.length>=3){if(s.data[2].xthis.window.xMax)isInRange=false;return isInRange}return true};this.onUpdate=function(callback){this.updateCallbacks.push(callback)};this.onConfigure=function(callback){this.configureCallbacks.push(callback)};this.registerRenderer=function(renderer){this._renderers=this._renderers||{};this._renderers[renderer.name]=renderer};this.configure=function(args){this.config=this.config||{};if(args.width||args.height){this.setSize(args)}Rickshaw.keys(this.defaults).forEach(function(k){this.config[k]=k in args?args[k]:k in this?this[k]:this.defaults[k]},this);Rickshaw.keys(this.config).forEach(function(k){this[k]=this.config[k]},this);if("stack"in args)args.unstack=!args.stack;var renderer=args.renderer||this.renderer&&this.renderer.name||"stack";this.setRenderer(renderer,args);this.configureCallbacks.forEach(function(callback){callback(args)})};this.setRenderer=function(r,args){if(typeof r=="function"){this.renderer=new r({graph:self});this.registerRenderer(this.renderer)}else{if(!this._renderers[r]){throw"couldn't find renderer "+r}this.renderer=this._renderers[r]}if(typeof args=="object"){this.renderer.configure(args)}};this.setSize=function(args){args=args||{};if(typeof window!==undefined){var style=window.getComputedStyle(this.element,null);var elementWidth=parseInt(style.getPropertyValue("width"),10);var elementHeight=parseInt(style.getPropertyValue("height"),10)}this.width=args.width||elementWidth||400;this.height=args.height||elementHeight||250;this.vis&&this.vis.attr("width",this.width).attr("height",this.height)};this.initialize(args)};Rickshaw.namespace("Rickshaw.Fixtures.Color");Rickshaw.Fixtures.Color=function(){this.schemes={};this.schemes.spectrum14=["#ecb796","#dc8f70","#b2a470","#92875a","#716c49","#d2ed82","#bbe468","#a1d05d","#e7cbe6","#d8aad6","#a888c2","#9dc2d3","#649eb9","#387aa3"].reverse();this.schemes.spectrum2000=["#57306f","#514c76","#646583","#738394","#6b9c7d","#84b665","#a7ca50","#bfe746","#e2f528","#fff726","#ecdd00","#d4b11d","#de8800","#de4800","#c91515","#9a0000","#7b0429","#580839","#31082b"];this.schemes.spectrum2001=["#2f243f","#3c2c55","#4a3768","#565270","#6b6b7c","#72957f","#86ad6e","#a1bc5e","#b8d954","#d3e04e","#ccad2a","#cc8412","#c1521d","#ad3821","#8a1010","#681717","#531e1e","#3d1818","#320a1b"];this.schemes.classic9=["#423d4f","#4a6860","#848f39","#a2b73c","#ddcb53","#c5a32f","#7d5836","#963b20","#7c2626","#491d37","#2f254a"].reverse();this.schemes.httpStatus={503:"#ea5029",502:"#d23f14",500:"#bf3613",410:"#efacea",409:"#e291dc",403:"#f457e8",408:"#e121d2",401:"#b92dae",405:"#f47ceb",404:"#a82a9f",400:"#b263c6",301:"#6fa024",302:"#87c32b",307:"#a0d84c",304:"#28b55c",200:"#1a4f74",206:"#27839f",201:"#52adc9",202:"#7c979f",203:"#a5b8bd",204:"#c1cdd1"};this.schemes.colorwheel=["#b5b6a9","#858772","#785f43","#96557e","#4682b4","#65b9ac","#73c03a","#cb513a"].reverse();this.schemes.cool=["#5e9d2f","#73c03a","#4682b4","#7bc3b8","#a9884e","#c1b266","#a47493","#c09fb5"];this.schemes.munin=["#00cc00","#0066b3","#ff8000","#ffcc00","#330099","#990099","#ccff00","#ff0000","#808080","#008f00","#00487d","#b35a00","#b38f00","#6b006b","#8fb300","#b30000","#bebebe","#80ff80","#80c9ff","#ffc080","#ffe680","#aa80ff","#ee00cc","#ff8080","#666600","#ffbfff","#00ffcc","#cc6699","#999900"]};Rickshaw.namespace("Rickshaw.Fixtures.RandomData");Rickshaw.Fixtures.RandomData=function(timeInterval){var addData;timeInterval=timeInterval||1;var lastRandomValue=200;var timeBase=Math.floor((new Date).getTime()/1e3);this.addData=function(data){var randomValue=Math.random()*100+15+lastRandomValue;var index=data[0].length;var counter=1;data.forEach(function(series){var randomVariance=Math.random()*20;var v=randomValue/25+counter++ +(Math.cos(index*counter*11/960)+2)*15+(Math.cos(index/7)+2)*7+(Math.cos(index/17)+2)*1;series.push({x:index*timeInterval+timeBase,y:v+randomVariance})});lastRandomValue=randomValue*.85};this.removeData=function(data){data.forEach(function(series){series.shift()});timeBase+=timeInterval}};Rickshaw.namespace("Rickshaw.Fixtures.Time");Rickshaw.Fixtures.Time=function(){var self=this;this.months=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];this.units=[{name:"decade",seconds:86400*365.25*10,formatter:function(d){return parseInt(d.getUTCFullYear()/10,10)*10}},{name:"year",seconds:86400*365.25,formatter:function(d){return d.getUTCFullYear()}},{name:"month",seconds:86400*30.5,formatter:function(d){return self.months[d.getUTCMonth()]}},{name:"week",seconds:86400*7,formatter:function(d){return self.formatDate(d)}},{name:"day",seconds:86400,formatter:function(d){return d.getUTCDate()}},{name:"6 hour",seconds:3600*6,formatter:function(d){return self.formatTime(d)}},{name:"hour",seconds:3600,formatter:function(d){return self.formatTime(d)}},{name:"15 minute",seconds:60*15,formatter:function(d){return self.formatTime(d)}},{name:"minute",seconds:60,formatter:function(d){return d.getUTCMinutes()}},{name:"15 second",seconds:15,formatter:function(d){return d.getUTCSeconds()+"s"}},{name:"second",seconds:1,formatter:function(d){return d.getUTCSeconds()+"s"}},{name:"decisecond",seconds:1/10,formatter:function(d){return d.getUTCMilliseconds()+"ms"}},{name:"centisecond",seconds:1/100,formatter:function(d){return d.getUTCMilliseconds()+"ms"}}];this.unit=function(unitName){return this.units.filter(function(unit){return unitName==unit.name}).shift()};this.formatDate=function(d){return d3.time.format("%b %e")(d)};this.formatTime=function(d){return d.toUTCString().match(/(\d+:\d+):/)[1]};this.ceil=function(time,unit){var date,floor,year;if(unit.name=="month"){date=new Date(time*1e3);floor=Date.UTC(date.getUTCFullYear(),date.getUTCMonth())/1e3;if(floor==time)return time;year=date.getUTCFullYear();var month=date.getUTCMonth();if(month==11){month=0;year=year+1}else{month+=1}return Date.UTC(year,month)/1e3}if(unit.name=="year"){date=new Date(time*1e3);floor=Date.UTC(date.getUTCFullYear(),0)/1e3;if(floor==time)return time;year=date.getUTCFullYear()+1;return Date.UTC(year,0)/1e3}return Math.ceil(time/unit.seconds)*unit.seconds}};Rickshaw.namespace("Rickshaw.Fixtures.Time.Local");Rickshaw.Fixtures.Time.Local=function(){var self=this;this.months=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];this.units=[{name:"decade",seconds:86400*365.25*10,formatter:function(d){return parseInt(d.getFullYear()/10,10)*10}},{name:"year",seconds:86400*365.25,formatter:function(d){return d.getFullYear()}},{name:"month",seconds:86400*30.5,formatter:function(d){return self.months[d.getMonth()]}},{name:"week",seconds:86400*7,formatter:function(d){return self.formatDate(d)}},{name:"day",seconds:86400,formatter:function(d){return d.getDate()}},{name:"6 hour",seconds:3600*6,formatter:function(d){return self.formatTime(d)}},{name:"hour",seconds:3600,formatter:function(d){return self.formatTime(d)}},{name:"15 minute",seconds:60*15,formatter:function(d){return self.formatTime(d)}},{name:"minute",seconds:60,formatter:function(d){return d.getMinutes()}},{name:"15 second",seconds:15,formatter:function(d){return d.getSeconds()+"s"}},{name:"second",seconds:1,formatter:function(d){return d.getSeconds()+"s"}},{name:"decisecond",seconds:1/10,formatter:function(d){return d.getMilliseconds()+"ms"}},{name:"centisecond",seconds:1/100,formatter:function(d){return d.getMilliseconds()+"ms"}}];this.unit=function(unitName){return this.units.filter(function(unit){return unitName==unit.name}).shift()};this.formatDate=function(d){return d3.time.format("%b %e")(d)};this.formatTime=function(d){return d.toString().match(/(\d+:\d+):/)[1]};this.ceil=function(time,unit){var date,floor,year;if(unit.name=="day"){var nearFuture=new Date((time+unit.seconds-1)*1e3);var rounded=new Date(0);rounded.setMilliseconds(0);rounded.setSeconds(0);rounded.setMinutes(0);rounded.setHours(0);rounded.setDate(nearFuture.getDate());rounded.setMonth(nearFuture.getMonth());rounded.setFullYear(nearFuture.getFullYear());return rounded.getTime()/1e3}if(unit.name=="month"){date=new Date(time*1e3);floor=new Date(date.getFullYear(),date.getMonth()).getTime()/1e3;if(floor==time)return time;year=date.getFullYear();var month=date.getMonth();if(month==11){month=0;year=year+1}else{month+=1}return new Date(year,month).getTime()/1e3}if(unit.name=="year"){date=new Date(time*1e3);floor=new Date(date.getUTCFullYear(),0).getTime()/1e3;if(floor==time)return time;year=date.getFullYear()+1;return new Date(year,0).getTime()/1e3}return Math.ceil(time/unit.seconds)*unit.seconds}};Rickshaw.namespace("Rickshaw.Fixtures.Number");Rickshaw.Fixtures.Number.formatKMBT=function(y){var abs_y=Math.abs(y);if(abs_y>=1e12){return y/1e12+"T"}else if(abs_y>=1e9){return y/1e9+"B"}else if(abs_y>=1e6){return y/1e6+"M"}else if(abs_y>=1e3){return y/1e3+"K"}else if(abs_y<1&&y>0){return y.toFixed(2)}else if(abs_y===0){return""}else{return y}};Rickshaw.Fixtures.Number.formatBase1024KMGTP=function(y){var abs_y=Math.abs(y);if(abs_y>=0x4000000000000){return y/0x4000000000000+"P"}else if(abs_y>=1099511627776){return y/1099511627776+"T"}else if(abs_y>=1073741824){return y/1073741824+"G"}else if(abs_y>=1048576){return y/1048576+"M"}else if(abs_y>=1024){return y/1024+"K"}else if(abs_y<1&&y>0){return y.toFixed(2)}else if(abs_y===0){return""}else{return y}};Rickshaw.namespace("Rickshaw.Color.Palette");Rickshaw.Color.Palette=function(args){var color=new Rickshaw.Fixtures.Color;args=args||{};this.schemes={};this.scheme=color.schemes[args.scheme]||args.scheme||color.schemes.colorwheel;this.runningIndex=0;this.generatorIndex=0;if(args.interpolatedStopCount){var schemeCount=this.scheme.length-1;var i,j,scheme=[];for(i=0;iself.graph.x.range()[1]){if(annotation.element){annotation.line.classList.add("offscreen");annotation.element.style.display="none"}annotation.boxes.forEach(function(box){if(box.rangeElement)box.rangeElement.classList.add("offscreen")});return}if(!annotation.element){var element=annotation.element=document.createElement("div");element.classList.add("annotation");this.elements.timeline.appendChild(element);element.addEventListener("click",function(e){element.classList.toggle("active");annotation.line.classList.toggle("active");annotation.boxes.forEach(function(box){if(box.rangeElement)box.rangeElement.classList.toggle("active")})},false)}annotation.element.style.left=left+"px";annotation.element.style.display="block";annotation.boxes.forEach(function(box){var element=box.element;if(!element){element=box.element=document.createElement("div");element.classList.add("content");element.innerHTML=box.content;annotation.element.appendChild(element);annotation.line=document.createElement("div");annotation.line.classList.add("annotation_line");self.graph.element.appendChild(annotation.line);if(box.end){box.rangeElement=document.createElement("div");box.rangeElement.classList.add("annotation_range");self.graph.element.appendChild(box.rangeElement)}}if(box.end){var annotationRangeStart=left;var annotationRangeEnd=Math.min(self.graph.x(box.end),self.graph.x.range()[1]);if(annotationRangeStart>annotationRangeEnd){annotationRangeEnd=left;annotationRangeStart=Math.max(self.graph.x(box.end),self.graph.x.range()[0])}var annotationRangeWidth=annotationRangeEnd-annotationRangeStart;box.rangeElement.style.left=annotationRangeStart+"px";box.rangeElement.style.width=annotationRangeWidth+"px";box.rangeElement.classList.remove("offscreen")}annotation.line.classList.remove("offscreen");annotation.line.style.left=left+"px"})},this)};this.graph.onUpdate(function(){self.update()})};Rickshaw.namespace("Rickshaw.Graph.Axis.Time");Rickshaw.Graph.Axis.Time=function(args){var self=this;this.graph=args.graph;this.elements=[];this.ticksTreatment=args.ticksTreatment||"plain";this.fixedTimeUnit=args.timeUnit;var time=args.timeFixture||new Rickshaw.Fixtures.Time;this.appropriateTimeUnit=function(){var unit;var units=time.units;var domain=this.graph.x.domain();var rangeSeconds=domain[1]-domain[0];units.forEach(function(u){if(Math.floor(rangeSeconds/u.seconds)>=2){unit=unit||u}});return unit||time.units[time.units.length-1]};this.tickOffsets=function(){var domain=this.graph.x.domain();var unit=this.fixedTimeUnit||this.appropriateTimeUnit();var count=Math.ceil((domain[1]-domain[0])/unit.seconds);var runningTick=domain[0];var offsets=[];for(var i=0;iself.graph.x.range()[1])return;var element=document.createElement("div");element.style.left=self.graph.x(o.value)+"px";element.classList.add("x_tick");element.classList.add(self.ticksTreatment);var title=document.createElement("div");title.classList.add("title");title.innerHTML=o.unit.formatter(new Date(o.value*1e3));element.appendChild(title);self.graph.element.appendChild(element);self.elements.push(element)})};this.graph.onUpdate(function(){self.render()})};Rickshaw.namespace("Rickshaw.Graph.Axis.X");Rickshaw.Graph.Axis.X=function(args){var self=this;var berthRate=.1;this.initialize=function(args){this.graph=args.graph;this.orientation=args.orientation||"top";this.pixelsPerTick=args.pixelsPerTick||75;if(args.ticks)this.staticTicks=args.ticks;if(args.tickValues)this.tickValues=args.tickValues;this.tickSize=args.tickSize||4;this.ticksTreatment=args.ticksTreatment||"plain";if(args.element){this.element=args.element;this._discoverSize(args.element,args);this.vis=d3.select(args.element).append("svg:svg").attr("height",this.height).attr("width",this.width).attr("class","rickshaw_graph x_axis_d3");this.element=this.vis[0][0];this.element.style.position="relative";this.setSize({width:args.width,height:args.height})}else{this.vis=this.graph.vis}this.graph.onUpdate(function(){self.render()})};this.setSize=function(args){args=args||{};if(!this.element)return;this._discoverSize(this.element.parentNode,args);this.vis.attr("height",this.height).attr("width",this.width*(1+berthRate));var berth=Math.floor(this.width*berthRate/2);this.element.style.left=-1*berth+"px"};this.render=function(){if(this._renderWidth!==undefined&&this.graph.width!==this._renderWidth)this.setSize({auto:true});var axis=d3.svg.axis().scale(this.graph.x).orient(this.orientation);axis.tickFormat(args.tickFormat||function(x){return x});if(this.tickValues)axis.tickValues(this.tickValues);this.ticks=this.staticTicks||Math.floor(this.graph.width/this.pixelsPerTick);var berth=Math.floor(this.width*berthRate/2)||0;var transform;if(this.orientation=="top"){var yOffset=this.height||this.graph.height;transform="translate("+berth+","+yOffset+")"}else{transform="translate("+berth+", 0)"}if(this.element){this.vis.selectAll("*").remove()}this.vis.append("svg:g").attr("class",["x_ticks_d3",this.ticksTreatment].join(" ")).attr("transform",transform).call(axis.ticks(this.ticks).tickSubdivide(0).tickSize(this.tickSize));var gridSize=(this.orientation=="bottom"?1:-1)*this.graph.height;this.graph.vis.append("svg:g").attr("class","x_grid_d3").call(axis.ticks(this.ticks).tickSubdivide(0).tickSize(gridSize)).selectAll("text").each(function(){this.parentNode.setAttribute("data-x-value",this.textContent)});this._renderHeight=this.graph.height};this._discoverSize=function(element,args){if(typeof window!=="undefined"){var style=window.getComputedStyle(element,null);var elementHeight=parseInt(style.getPropertyValue("height"),10);if(!args.auto){var elementWidth=parseInt(style.getPropertyValue("width"),10)}}this.width=(args.width||elementWidth||this.graph.width)*(1+berthRate);this.height=args.height||elementHeight||40};this.initialize(args)};Rickshaw.namespace("Rickshaw.Graph.Axis.Y");Rickshaw.Graph.Axis.Y=Rickshaw.Class.create({initialize:function(args){this.graph=args.graph;this.orientation=args.orientation||"right";this.pixelsPerTick=args.pixelsPerTick||75;if(args.ticks)this.staticTicks=args.ticks;if(args.tickValues)this.tickValues=args.tickValues;this.tickSize=args.tickSize||4;this.ticksTreatment=args.ticksTreatment||"plain";this.tickFormat=args.tickFormat||function(y){return y};this.berthRate=.1;if(args.element){this.element=args.element;this.vis=d3.select(args.element).append("svg:svg").attr("class","rickshaw_graph y_axis");this.element=this.vis[0][0];this.element.style.position="relative";this.setSize({width:args.width,height:args.height})}else{this.vis=this.graph.vis}var self=this;this.graph.onUpdate(function(){self.render()})},setSize:function(args){args=args||{};if(!this.element)return;if(typeof window!=="undefined"){var style=window.getComputedStyle(this.element.parentNode,null);var elementWidth=parseInt(style.getPropertyValue("width"),10);if(!args.auto){var elementHeight=parseInt(style.getPropertyValue("height"),10)}}this.width=args.width||elementWidth||this.graph.width*this.berthRate;this.height=args.height||elementHeight||this.graph.height;this.vis.attr("width",this.width).attr("height",this.height*(1+this.berthRate));var berth=this.height*this.berthRate;if(this.orientation=="left"){this.element.style.top=-1*berth+"px"}},render:function(){if(this._renderHeight!==undefined&&this.graph.height!==this._renderHeight)this.setSize({auto:true});this.ticks=this.staticTicks||Math.floor(this.graph.height/this.pixelsPerTick);var axis=this._drawAxis(this.graph.y);this._drawGrid(axis);this._renderHeight=this.graph.height},_drawAxis:function(scale){var axis=d3.svg.axis().scale(scale).orient(this.orientation);axis.tickFormat(this.tickFormat);if(this.tickValues)axis.tickValues(this.tickValues);if(this.orientation=="left"){var berth=this.height*this.berthRate;var transform="translate("+this.width+", "+berth+")"}if(this.element){this.vis.selectAll("*").remove()}this.vis.append("svg:g").attr("class",["y_ticks",this.ticksTreatment].join(" ")).attr("transform",transform).call(axis.ticks(this.ticks).tickSubdivide(0).tickSize(this.tickSize));return axis},_drawGrid:function(axis){var gridSize=(this.orientation=="right"?1:-1)*this.graph.width;this.graph.vis.append("svg:g").attr("class","y_grid").call(axis.ticks(this.ticks).tickSubdivide(0).tickSize(gridSize)).selectAll("text").each(function(){this.parentNode.setAttribute("data-y-value",this.textContent) +})}});Rickshaw.namespace("Rickshaw.Graph.Axis.Y.Scaled");Rickshaw.Graph.Axis.Y.Scaled=Rickshaw.Class.create(Rickshaw.Graph.Axis.Y,{initialize:function($super,args){if(typeof args.scale==="undefined"){throw new Error("Scaled requires scale")}this.scale=args.scale;if(typeof args.grid==="undefined"){this.grid=true}else{this.grid=args.grid}$super(args)},_drawAxis:function($super,scale){var domain=this.scale.domain();var renderDomain=this.graph.renderer.domain().y;var extents=[Math.min.apply(Math,domain),Math.max.apply(Math,domain)];var extentMap=d3.scale.linear().domain([0,1]).range(extents);var adjExtents=[extentMap(renderDomain[0]),extentMap(renderDomain[1])];var adjustment=d3.scale.linear().domain(extents).range(adjExtents);var adjustedScale=this.scale.copy().domain(domain.map(adjustment)).range(scale.range());return $super(adjustedScale)},_drawGrid:function($super,axis){if(this.grid){$super(axis)}}});Rickshaw.namespace("Rickshaw.Graph.Behavior.Series.Highlight");Rickshaw.Graph.Behavior.Series.Highlight=function(args){this.graph=args.graph;this.legend=args.legend;var self=this;var colorSafe={};var activeLine=null;var disabledColor=args.disabledColor||function(seriesColor){return d3.interpolateRgb(seriesColor,d3.rgb("#d8d8d8"))(.8).toString()};this.addHighlightEvents=function(l){l.element.addEventListener("mouseover",function(e){if(activeLine)return;else activeLine=l;self.legend.lines.forEach(function(line){if(l===line){if(self.graph.renderer.unstack&&(line.series.renderer?line.series.renderer.unstack:true)){var seriesIndex=self.graph.series.indexOf(line.series);line.originalIndex=seriesIndex;var series=self.graph.series.splice(seriesIndex,1)[0];self.graph.series.push(series)}return}colorSafe[line.series.name]=colorSafe[line.series.name]||line.series.color;line.series.color=disabledColor(line.series.color)});self.graph.update()},false);l.element.addEventListener("mouseout",function(e){if(!activeLine)return;else activeLine=null;self.legend.lines.forEach(function(line){if(l===line&&line.hasOwnProperty("originalIndex")){var series=self.graph.series.pop();self.graph.series.splice(line.originalIndex,0,series);delete line.originalIndex}if(colorSafe[line.series.name]){line.series.color=colorSafe[line.series.name]}});self.graph.update()},false)};if(this.legend){this.legend.lines.forEach(function(l){self.addHighlightEvents(l)})}};Rickshaw.namespace("Rickshaw.Graph.Behavior.Series.Order");Rickshaw.Graph.Behavior.Series.Order=function(args){this.graph=args.graph;this.legend=args.legend;var self=this;if(typeof window.jQuery=="undefined"){throw"couldn't find jQuery at window.jQuery"}if(typeof window.jQuery.ui=="undefined"){throw"couldn't find jQuery UI at window.jQuery.ui"}jQuery(function(){jQuery(self.legend.list).sortable({containment:"parent",tolerance:"pointer",update:function(event,ui){var series=[];jQuery(self.legend.list).find("li").each(function(index,item){if(!item.series)return;series.push(item.series)});for(var i=self.graph.series.length-1;i>=0;i--){self.graph.series[i]=series.shift()}self.graph.update()}});jQuery(self.legend.list).disableSelection()});this.graph.onUpdate(function(){var h=window.getComputedStyle(self.legend.element).height;self.legend.element.style.height=h})};Rickshaw.namespace("Rickshaw.Graph.Behavior.Series.Toggle");Rickshaw.Graph.Behavior.Series.Toggle=function(args){this.graph=args.graph;this.legend=args.legend;var self=this;this.addAnchor=function(line){var anchor=document.createElement("a");anchor.innerHTML="✔";anchor.classList.add("action");line.element.insertBefore(anchor,line.element.firstChild);anchor.onclick=function(e){if(line.series.disabled){line.series.enable();line.element.classList.remove("disabled")}else{if(this.graph.series.filter(function(s){return!s.disabled}).length<=1)return;line.series.disable();line.element.classList.add("disabled")}self.graph.update()}.bind(this);var label=line.element.getElementsByTagName("span")[0];label.onclick=function(e){var disableAllOtherLines=line.series.disabled;if(!disableAllOtherLines){for(var i=0;idomainX){dataIndex=Math.abs(domainX-data[i].x)0){alignables.forEach(function(el){el.classList.remove("left");el.classList.add("right")});var rightAlignError=this._calcLayoutError(alignables);if(rightAlignError>leftAlignError){alignables.forEach(function(el){el.classList.remove("right");el.classList.add("left")})}}if(typeof this.onRender=="function"){this.onRender(args)}},_calcLayoutError:function(alignables){var parentRect=this.element.parentNode.getBoundingClientRect();var error=0;var alignRight=alignables.forEach(function(el){var rect=el.getBoundingClientRect();if(!rect.width){return}if(rect.right>parentRect.right){error+=rect.right-parentRect.right}if(rect.left=self.previewWidth){frameAfterDrag[0]-=frameAfterDrag[1]-self.previewWidth;frameAfterDrag[1]=self.previewWidth}}self.graphs.forEach(function(graph){var domainScale=d3.scale.linear().interpolate(d3.interpolateNumber).domain([0,self.previewWidth]).range(graph.dataDomain());var windowAfterDrag=[domainScale(frameAfterDrag[0]),domainScale(frameAfterDrag[1])];self.slideCallbacks.forEach(function(callback){callback(graph,windowAfterDrag[0],windowAfterDrag[1])});if(frameAfterDrag[0]===0){windowAfterDrag[0]=undefined}if(frameAfterDrag[1]===self.previewWidth){windowAfterDrag[1]=undefined}graph.window.xMin=windowAfterDrag[0];graph.window.xMax=windowAfterDrag[1];graph.update()})}function onMousedown(){drag.target=d3.event.target;drag.start=self._getClientXFromEvent(d3.event,drag);self.frameBeforeDrag=self.currentFrame.slice();d3.event.preventDefault?d3.event.preventDefault():d3.event.returnValue=false;d3.select(document).on("mousemove.rickshaw_range_slider_preview",onMousemove);d3.select(document).on("mouseup.rickshaw_range_slider_preview",onMouseup);d3.select(document).on("touchmove.rickshaw_range_slider_preview",onMousemove);d3.select(document).on("touchend.rickshaw_range_slider_preview",onMouseup);d3.select(document).on("touchcancel.rickshaw_range_slider_preview",onMouseup)}function onMousedownLeftHandle(datum,index){drag.left=true;onMousedown()}function onMousedownRightHandle(datum,index){drag.right=true;onMousedown()}function onMousedownMiddleHandle(datum,index){drag.left=true;drag.right=true;drag.rigid=true;onMousedown()}function onMouseup(datum,index){d3.select(document).on("mousemove.rickshaw_range_slider_preview",null);d3.select(document).on("mouseup.rickshaw_range_slider_preview",null);d3.select(document).on("touchmove.rickshaw_range_slider_preview",null);d3.select(document).on("touchend.rickshaw_range_slider_preview",null);d3.select(document).on("touchcancel.rickshaw_range_slider_preview",null);delete self.frameBeforeDrag;drag.left=false;drag.right=false;drag.rigid=false}element.select("rect.left_handle").on("mousedown",onMousedownLeftHandle);element.select("rect.right_handle").on("mousedown",onMousedownRightHandle);element.select("rect.middle_handle").on("mousedown",onMousedownMiddleHandle);element.select("rect.left_handle").on("touchstart",onMousedownLeftHandle);element.select("rect.right_handle").on("touchstart",onMousedownRightHandle);element.select("rect.middle_handle").on("touchstart",onMousedownMiddleHandle)},_getClientXFromEvent:function(event,drag){switch(event.type){case"touchstart":case"touchmove":var touchList=event.changedTouches;var touch=null;for(var touchIndex=0;touchIndexyMax)yMax=y});if(!series.length)return;if(series[0].xxMax)xMax=series[series.length-1].x});xMin-=(xMax-xMin)*this.padding.left;xMax+=(xMax-xMin)*this.padding.right;yMin=this.graph.min==="auto"?yMin:this.graph.min||0;yMax=this.graph.max===undefined?yMax:this.graph.max;if(this.graph.min==="auto"||yMin<0){yMin-=(yMax-yMin)*this.padding.bottom}if(this.graph.max===undefined){yMax+=(yMax-yMin)*this.padding.top}return{x:[xMin,xMax],y:[yMin,yMax]}},render:function(args){args=args||{};var graph=this.graph;var series=args.series||graph.series;var vis=args.vis||graph.vis;vis.selectAll("*").remove();var data=series.filter(function(s){return!s.disabled}).map(function(s){return s.stack});var pathNodes=vis.selectAll("path.path").data(data).enter().append("svg:path").classed("path",true).attr("d",this.seriesPathFactory());if(this.stroke){var strokeNodes=vis.selectAll("path.stroke").data(data).enter().append("svg:path").classed("stroke",true).attr("d",this.seriesStrokeFactory())}var i=0;series.forEach(function(series){if(series.disabled)return;series.path=pathNodes[0][i];if(this.stroke)series.stroke=strokeNodes[0][i];this._styleSeries(series);i++},this)},_styleSeries:function(series){var fill=this.fill?series.color:"none";var stroke=this.stroke?series.color:"none";series.path.setAttribute("fill",fill);series.path.setAttribute("stroke",stroke);series.path.setAttribute("stroke-width",this.strokeWidth);if(series.className){d3.select(series.path).classed(series.className,true)}if(series.className&&this.stroke){d3.select(series.stroke).classed(series.className,true)}},configure:function(args){args=args||{};Rickshaw.keys(this.defaults()).forEach(function(key){if(!args.hasOwnProperty(key)){this[key]=this[key]||this.graph[key]||this.defaults()[key];return}if(typeof this.defaults()[key]=="object"){Rickshaw.keys(this.defaults()[key]).forEach(function(k){this[key][k]=args[key][k]!==undefined?args[key][k]:this[key][k]!==undefined?this[key][k]:this.defaults()[key][k]},this)}else{this[key]=args[key]!==undefined?args[key]:this[key]!==undefined?this[key]:this.graph[key]!==undefined?this.graph[key]:this.defaults()[key]}},this)},setStrokeWidth:function(strokeWidth){if(strokeWidth!==undefined){this.strokeWidth=strokeWidth}},setTension:function(tension){if(tension!==undefined){this.tension=tension}}});Rickshaw.namespace("Rickshaw.Graph.Renderer.Line");Rickshaw.Graph.Renderer.Line=Rickshaw.Class.create(Rickshaw.Graph.Renderer,{name:"line",defaults:function($super){return Rickshaw.extend($super(),{unstack:true,fill:false,stroke:true})},seriesPathFactory:function(){var graph=this.graph;var factory=d3.svg.line().x(function(d){return graph.x(d.x)}).y(function(d){return graph.y(d.y)}).interpolate(this.graph.interpolation).tension(this.tension);factory.defined&&factory.defined(function(d){return d.y!==null});return factory}});Rickshaw.namespace("Rickshaw.Graph.Renderer.Stack");Rickshaw.Graph.Renderer.Stack=Rickshaw.Class.create(Rickshaw.Graph.Renderer,{name:"stack",defaults:function($super){return Rickshaw.extend($super(),{fill:true,stroke:false,unstack:false})},seriesPathFactory:function(){var graph=this.graph;var factory=d3.svg.area().x(function(d){return graph.x(d.x)}).y0(function(d){return graph.y(d.y0)}).y1(function(d){return graph.y(d.y+d.y0)}).interpolate(this.graph.interpolation).tension(this.tension);factory.defined&&factory.defined(function(d){return d.y!==null});return factory}});Rickshaw.namespace("Rickshaw.Graph.Renderer.Bar");Rickshaw.Graph.Renderer.Bar=Rickshaw.Class.create(Rickshaw.Graph.Renderer,{name:"bar",defaults:function($super){var defaults=Rickshaw.extend($super(),{gapSize:.05,unstack:false});delete defaults.tension;return defaults},initialize:function($super,args){args=args||{};this.gapSize=args.gapSize||this.gapSize;$super(args)},domain:function($super){var domain=$super();var frequentInterval=this._frequentInterval(this.graph.stackedData.slice(-1).shift());domain.x[1]+=Number(frequentInterval.magnitude);return domain},barWidth:function(series){var frequentInterval=this._frequentInterval(series.stack);var barWidth=this.graph.x.magnitude(frequentInterval.magnitude)*(1-this.gapSize);return barWidth},render:function(args){args=args||{};var graph=this.graph;var series=args.series||graph.series;var vis=args.vis||graph.vis;vis.selectAll("*").remove();var barWidth=this.barWidth(series.active()[0]);var barXOffset=0;var activeSeriesCount=series.filter(function(s){return!s.disabled}).length;var seriesBarWidth=this.unstack?barWidth/activeSeriesCount:barWidth;var transform=function(d){var matrix=[1,0,0,d.y<0?-1:1,0,d.y<0?graph.y.magnitude(Math.abs(d.y))*2:0];return"matrix("+matrix.join(",")+")"};series.forEach(function(series){if(series.disabled)return;var barWidth=this.barWidth(series);var nodes=vis.selectAll("path").data(series.stack.filter(function(d){return d.y!==null})).enter().append("svg:rect").attr("x",function(d){return graph.x(d.x)+barXOffset}).attr("y",function(d){return graph.y(d.y0+Math.abs(d.y))*(d.y<0?-1:1)}).attr("width",seriesBarWidth).attr("height",function(d){return graph.y.magnitude(Math.abs(d.y))}).attr("transform",transform);Array.prototype.forEach.call(nodes[0],function(n){n.setAttribute("fill",series.color)});if(this.unstack)barXOffset+=seriesBarWidth},this)},_frequentInterval:function(data){var intervalCounts={};for(var i=0;i0){this[0].data.forEach(function(plot){item.data.push({x:plot.x,y:0})})}else if(item.data.length===0){item.data.push({x:this.timeBase-(this.timeInterval||0),y:0})}this.push(item);if(this.legend){this.legend.addLine(this.itemByName(item.name))}},addData:function(data,x){var index=this.getIndex();Rickshaw.keys(data).forEach(function(name){if(!this.itemByName(name)){this.addItem({name:name})}},this);this.forEach(function(item){item.data.push({x:x||(index*this.timeInterval||1)+this.timeBase,y:data[item.name]||0})},this)},getIndex:function(){return this[0]&&this[0].data&&this[0].data.length?this[0].data.length:0},itemByName:function(name){for(var i=0;i1;i--){this.currentSize+=1;this.currentIndex+=1;this.forEach(function(item){item.data.unshift({x:((i-1)*this.timeInterval||1)+this.timeBase,y:0,i:i})},this)}}},addData:function($super,data,x){$super(data,x);this.currentSize+=1;this.currentIndex+=1;if(this.maxDataPoints!==undefined){while(this.currentSize>this.maxDataPoints){this.dropData()}}},dropData:function(){this.forEach(function(item){item.data.splice(0,1)});this.currentSize-=1},getIndex:function(){return this.currentIndex}});return Rickshaw}); \ No newline at end of file diff --git a/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.device.stats/stats.hbs b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.device.stats/stats.hbs new file mode 100644 index 0000000000..ff0fb60237 --- /dev/null +++ b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.device.stats/stats.hbs @@ -0,0 +1,29 @@ +{{#if monitor_operations}} + {{#zone "topCss"}} + {{css "css/graph.css"}} + {{/zone}} + +
      +
      +
      +
      + + + + + View Device Analytics + + {{#zone "bottomJs"}} + {{js "js/d3.min.js"}} + {{js "js/rickshaw.min.js"}} + {{js "js/moment.min.js"}} + {{js "js/device-stats.js"}} + {{/zone}} +{{else}} +
      +

      + Stats Loading Failed!

      +
      +{{/if}} \ No newline at end of file diff --git a/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.device.stats/stats.js b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.device.stats/stats.js new file mode 100644 index 0000000000..e7f862207e --- /dev/null +++ b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.device.stats/stats.js @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2016, 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("stats.js"); + var operationModule = require("/app/modules/operation.js").operationModule; + var device = context.unit.params.device; + var monitor_operations; + try { + monitor_operations = JSON.stringify(operationModule.getMonitorOperations(device.type)); + } catch (e) { + log.error("Monitor operation loading failed."); + monitor_operations = null; + } + + return {"monitor_operations": monitor_operations, "device": device}; +} \ No newline at end of file diff --git a/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.device.stats/stats.json b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.device.stats/stats.json new file mode 100644 index 0000000000..688e939808 --- /dev/null +++ b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.device.stats/stats.json @@ -0,0 +1,3 @@ +{ + "version": "1.0.0" +} \ No newline at end of file diff --git a/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.platform.configuration/configuration.hbs b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.platform.configuration/configuration.hbs new file mode 100644 index 0000000000..94522b0bb8 --- /dev/null +++ b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.platform.configuration/configuration.hbs @@ -0,0 +1,492 @@ +
      +
      + +
      +
      +

      + Platform Configurations +

      +
      + General and Platform Specific Server Settings for the Tenant +
      +
      +
      +
      + + +
      + +
      +
      + +
      + +
      + + +
      +
      + +
      +
      + + +
      +
      + + + +
      +
      +
      + + +
      + + +
      + +
      +
      + + +
      +
      + +
      +
      + + +
      + +
      + + +
      +
      +
      + + +
      +
      + +
      +
      + +
      +
      +
      +
      + + + +
      +
      + + +
      + + +
      + + +
      + +
      + + +
      + +
      + + +
      + +
      + + +
      + +
      + + +
      +
      + + + +
      + +
      + + +
      +
      + + + +
      + +
      +
      + + +
      + +
      + + +
      + +
      + + +
      + +
      + + +
      +
      + + + +
      + +
      +
      + + +
      + +
      + + +
      + +
      + + +
      +
      + + +
      +
      + +
      +
      + +
      +
      +
      +
      + + + +
      +
      +
      + + +
      +
      + + +
      +
      +
      + + +
      +
      + +
      +
      + +
      +
      +
      +
      + +
      +
      +
      +
      + + + +
      +
      +{{#zone "bottomJs"}} + {{js "js/platform-configuration.js"}} +{{/zone}} diff --git a/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.platform.configuration/configuration.json b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.platform.configuration/configuration.json new file mode 100644 index 0000000000..be0496bf61 --- /dev/null +++ b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.platform.configuration/configuration.json @@ -0,0 +1,4 @@ +{ + "version" : "1.0.0", + "extends": "cdmf.unit.platform.configuration" +} \ No newline at end of file diff --git a/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.platform.configuration/public/js/platform-configuration.js b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.platform.configuration/public/js/platform-configuration.js new file mode 100644 index 0000000000..41123e12a8 --- /dev/null +++ b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.platform.configuration/public/js/platform-configuration.js @@ -0,0 +1,856 @@ +/* + * Copyright (c) 2016, 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. + */ + +/** + * Checks if provided input is valid against RegEx input. + * + * @param regExp Regular expression + * @param inputString Input string to check + * @returns {boolean} Returns true if input matches RegEx + */ +function inputIsValid(regExp, inputString) { + return regExp.test(inputString); +} + +/** + * Checks if an email address has the valid format or not. + * + * @param email Email address + * @returns {boolean} true if email has the valid format, otherwise false. + */ +function emailIsValid(email) { + var regExp = /^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/; + return regExp.test(email); +} + +var notifierTypeConstants = { + "LOCAL": "1", + "GCM": "2" +}; +// Constants to define platform types available +var platformTypeConstants = { + "ANDROID": "android", + "IOS": "ios", + "WINDOWS": "windows" +}; + +var responseCodes = { + "CREATED": "Created", + "SUCCESS": "201", + "INTERNAL_SERVER_ERROR": "Internal Server Error" +}; + +var configParams = { + "NOTIFIER_TYPE": "notifierType", + "NOTIFIER_FREQUENCY": "notifierFrequency", + "GCM_API_KEY": "gcmAPIKey", + "GCM_SENDER_ID": "gcmSenderId", + "ANDROID_EULA": "androidEula", + "IOS_EULA": "iosEula", + "CONFIG_COUNTRY": "configCountry", + "CONFIG_STATE": "configState", + "CONFIG_LOCALITY": "configLocality", + "CONFIG_ORGANIZATION": "configOrganization", + "CONFIG_ORGANIZATION_UNIT": "configOrganizationUnit", + "MDM_CERT_PASSWORD": "MDMCertPassword", + "MDM_CERT_TOPIC_ID": "MDMCertTopicID", + "APNS_CERT_PASSWORD": "APNSCertPassword", + "MDM_CERT": "MDMCert", + "MDM_CERT_NAME": "MDMCertName", + "APNS_CERT": "APNSCert", + "APNS_CERT_NAME": "APNSCertName", + "ORG_DISPLAY_NAME": "organizationDisplayName", + "GENERAL_EMAIL_HOST": "emailHost", + "GENERAL_EMAIL_PORT": "emailPort", + "GENERAL_EMAIL_USERNAME": "emailUsername", + "GENERAL_EMAIL_PASSWORD": "emailPassword", + "GENERAL_EMAIL_SENDER_ADDRESS": "emailSender", + "GENERAL_EMAIL_TEMPLATE": "emailTemplate", + "COMMON_NAME": "commonName", + "KEYSTORE_PASSWORD": "keystorePassword", + "PRIVATE_KEY_PASSWORD": "privateKeyPassword", + "BEFORE_EXPIRE": "beforeExpire", + "AFTER_EXPIRE": "afterExpire", + "WINDOWS_EULA": "windowsLicense" +}; + +$(document).ready(function () { + $("#gcm-inputs").hide(); + tinymce.init({ + selector: "textarea", + theme: "modern", + plugins: [ + "advlist autolink lists link image charmap print preview anchor", + "searchreplace visualblocks code fullscreen", + "insertdatetime image table contextmenu paste" + ], + toolbar: "undo redo | styleselect | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | link image" + }); + + var getAndroidConfigAPI = "/mdm-android-agent/configuration"; + var getGeneralConfigAPI = "/devicemgt_admin/configuration"; + var getIosConfigAPI = "/ios/configuration"; + var getWindowsConfigAPI = "/mdm-windows-agent/services/configuration"; + + /** + * Following requests would execute + * on page load event of platform configuration page in WSO2 EMM Console. + * Upon receiving the response, the parameters will be set to the fields, + * in case those configurations are already set. + */ + invokerUtil.get( + getAndroidConfigAPI, + + function (data) { + data = JSON.parse(data); + if (data != null && data.configuration != null) { + for (var i = 0; i < data.configuration.length; i++) { + var config = data.configuration[i]; + if(config.name == configParams["NOTIFIER_TYPE"]){ + $("#android-config-notifier").val(config.value); + if(config.value != notifierTypeConstants["GCM"] ) { + $("#gcm-inputs").hide(); + }else{ + $("#gcm-inputs").show(); + } + } else if(config.name == configParams["NOTIFIER_FREQUENCY"]){ + $("input#android-config-notifier-frequency").val(config.value); + } else if(config.name == configParams["GCM_API_KEY"]){ + $("input#android-config-gcm-api-key").val(config.value); + } else if(config.name == configParams["GCM_SENDER_ID"]){ + $("input#android-config-gcm-sender-id").val(config.value); + } else if(config.name == configParams["ANDROID_EULA"]){ + $("#android-eula").val(config.value); + } + } + } + + }, function () { + + } + ); + + invokerUtil.get( + getGeneralConfigAPI, + + function (data) { + data = JSON.parse(data); + if (data != null && data.configuration != null) { + for (var i = 0; i < data.configuration.length; i++) { + var config = data.configuration[i]; + if(config.name == configParams["NOTIFIER_FREQUENCY"]){ + $("input#monitoring-config-frequency").val(config.value); + } + /*if(config.name == configParams["GENERAL_EMAIL_HOST"]){ + $("input#email-config-host").val(config.value); + } else if(config.name == configParams["GENERAL_EMAIL_PORT"]){ + $("input#email-config-port").val(config.value); + } else if(config.name == configParams["GENERAL_EMAIL_USERNAME"]){ + $("input#email-config-username").val(config.value); + } else if(config.name == configParams["GENERAL_EMAIL_PASSWORD"]){ + $("input#email-config-password").val(config.value); + } else if(config.name == configParams["GENERAL_EMAIL_SENDER_ADDRESS"]){ + $("input#email-config-sender-email").val(config.value); + } else if(config.name == configParams["GENERAL_EMAIL_TEMPLATE"]){ + $("input#email-config-template").val(config.value); + }*/ + } + } + + }, function () { + + } + ); + + invokerUtil.get( + getIosConfigAPI, + + function (data) { + data = JSON.parse(data); + if (data != null && data.configuration != null) { + for (var i = 0; i < data.configuration.length; i++) { + var config = data.configuration[i]; + if(config.name == configParams["CONFIG_COUNTRY"]){ + $("input#ios-config-country").val(config.value); + } else if(config.name == configParams["CONFIG_STATE"]){ + $("input#ios-config-state").val(config.value); + } else if(config.name == configParams["CONFIG_LOCALITY"]){ + $("input#ios-config-locality").val(config.value); + } else if(config.name == configParams["CONFIG_ORGANIZATION"]){ + $("input#ios-config-organization").val(config.value); + } else if(config.name == configParams["CONFIG_ORGANIZATION_UNIT"]){ + $("input#ios-config-organization-unit").val(config.value); + } else if(config.name == configParams["MDM_CERT_PASSWORD"]){ + $("input#ios-config-mdm-certificate-password").val(config.value); + } else if(config.name == configParams["MDM_CERT_TOPIC_ID"]){ + $("input#ios-config-mdm-certificate-topic-id").val(config.value); + } else if(config.name == configParams["APNS_CERT_PASSWORD"]){ + $("input#ios-config-apns-certificate-password").val(config.value); + } else if(config.name == configParams["MDM_CERT_NAME"]){ + $("#mdm-cert-file-name").html(config.value); + } else if(config.name == configParams["APNS_CERT_NAME"]){ + $("#apns-cert-file-name").html(config.value); + } else if(config.name == configParams["ORG_DISPLAY_NAME"]){ + $("input#ios-org-display-name").val(config.value); + } else if(config.name == configParams["IOS_EULA"]){ + $("#ios-eula").val(config.value); + } + } + } + + }, function () { + + } + ); + + invokerUtil.get( + getWindowsConfigAPI, + + function (data) { + data = JSON.parse(data); + if (data != null && data.configuration != null) { + for (var i = 0; i < data.configuration.length; i++) { + var config = data.configuration[i]; + if(config.name == configParams["NOTIFIER_FREQUENCY"]) { + $("input#windows-config-notifier-frequency").val(config.value); + } else if(config.name == configParams["WINDOWS_EULA"]) { + $("#windows-eula").val(config.value); + } + } + } + + }, function () { + + } + ); + + + $("select.select2[multiple=multiple]").select2({ + tags : true + }); + + $("#android-config-notifier").change(function() { + var notifierType = $("#android-config-notifier").find("option:selected").attr("value"); + if(notifierType != notifierTypeConstants["GCM"] ) { + $("#gcm-inputs").hide(); + $("#local-inputs").show(); + }else{ + $("#local-inputs").hide(); + $("#gcm-inputs").show(); + } + }); + /** + * Following click function would execute + * when a user clicks on "Save" button + * on Android platform configuration page in WSO2 EMM Console. + */ + $("button#save-android-btn").click(function() { + var notifierType = $("#android-config-notifier").find("option:selected").attr("value"); + var notifierFrequency = $("input#android-config-notifier-frequency").val(); + var gcmAPIKey = $("input#android-config-gcm-api-key").val(); + var gcmSenderId = $("input#android-config-gcm-sender-id").val(); + var androidLicense = tinymce.get('android-eula').getContent(); + + var errorMsgWrapper = "#android-config-error-msg"; + var errorMsg = "#android-config-error-msg span"; + if (!notifierFrequency) { + $(errorMsg).text("Notifier frequency is a required field. It cannot be empty."); + $(errorMsgWrapper).removeClass("hidden"); + } else if (!$.isNumeric(notifierFrequency)) { + $(errorMsg).text("Provided notifier frequency is invalid. Please check."); + $(errorMsgWrapper).removeClass("hidden"); + } else if (notifierType == notifierTypeConstants["GCM"] && !gcmAPIKey) { + $(errorMsg).text("GCM API Key is a required field. It cannot be empty."); + $(errorMsgWrapper).removeClass("hidden"); + } else if (notifierType == notifierTypeConstants["GCM"] && !gcmSenderId) { + $(errorMsg).text("GCM Sender ID is a required field. It cannot be empty."); + $(errorMsgWrapper).removeClass("hidden"); + } else { + + var addConfigFormData = {}; + var configList = new Array(); + + var type = { + "name": configParams["NOTIFIER_TYPE"], + "value": notifierType, + "contentType": "text" + }; + + var frequency = { + "name": configParams["NOTIFIER_FREQUENCY"], + "value": notifierFrequency, + "contentType": "text" + }; + + var gcmKey = { + "name": configParams["GCM_API_KEY"], + "value": gcmAPIKey, + "contentType": "text" + }; + + var gcmId = { + "name": configParams["GCM_SENDER_ID"], + "value": gcmSenderId, + "contentType": "text" + }; + + var androidEula = { + "name": configParams["ANDROID_EULA"], + "value": androidLicense, + "contentType": "text" + }; + + configList.push(type); + configList.push(frequency); + configList.push(androidEula); + if (notifierType == notifierTypeConstants["GCM"]) { + configList.push(gcmKey); + configList.push(gcmId); + } + + addConfigFormData.type = platformTypeConstants["ANDROID"]; + addConfigFormData.configuration = configList; + + var addConfigAPI = "/mdm-android-agent/configuration"; + + invokerUtil.post( + addConfigAPI, + addConfigFormData, + function (data) { + data = JSON.parse(data); + if (data.responseCode == responseCodes["CREATED"]) { + $("#config-save-form").addClass("hidden"); + $("#record-created-msg").removeClass("hidden"); + } else if (data == 500) { + $(errorMsg).text("Exception occurred at backend."); + } else if (data == 403) { + $(errorMsg).text("Action was not permitted."); + } else { + $(errorMsg).text("An unexpected error occurred."); + } + + $(errorMsgWrapper).removeClass("hidden"); + }, function () { + $(errorMsg).text("An unexpected error occurred."); + $(errorMsgWrapper).removeClass("hidden"); + } + ); + } + }); + + /** + * Following click function would execute + * when a user clicks on "Save" button + * on General platform configuration page in WSO2 EMM Console. + */ + $("button#save-general-btn").click(function() { + var notifierFrequency = $("input#monitoring-config-frequency").val(); + /*var emailHost = $("input#email-config-host").val(); + var emailPort = $("input#email-config-port").val(); + var emailUsername = $("input#email-config-username").val(); + var emailPassword = $("input#email-config-password").val(); + var emailSenderAddress = $("input#email-config-sender-email").val(); + var emailTemplate = $("input#email-config-template").val();*/ + + var errorMsgWrapper = "#email-config-error-msg"; + var errorMsg = "#email-config-error-msg span"; + + if (!notifierFrequency) { + $(errorMsg).text("Monitoring frequency is a required field. It cannot be empty."); + $(errorMsgWrapper).removeClass("hidden"); + } else if (!$.isNumeric(notifierFrequency)) { + $(errorMsg).text("Provided monitoring frequency is invalid. It must be a number."); + $(errorMsgWrapper).removeClass("hidden"); + } else { + + var addConfigFormData = {}; + var configList = new Array(); + + var monitorFrequency = { + "name": configParams["NOTIFIER_FREQUENCY"], + "value": notifierFrequency, + "contentType": "text" + }; + + /*if (!emailHost) { + $(errorMsg).text("Email Host is a required field. It cannot be empty."); + //$(errorMsgWrapper).removeClass("hidden"); + } else if (!notifierFrequency) { + $(errorMsg).text("Monitoring frequency is a required field. It cannot be empty."); + $(errorMsgWrapper).removeClass("hidden"); + } else if (!$.isNumeric(notifierFrequency)) { + $(errorMsg).text("Provided monitoring frequency is invalid. It must be a number."); + $(errorMsgWrapper).removeClass("hidden"); + }else if (!emailPort) { + $(errorMsg).text("Email Port is a required field. It cannot be empty."); + //$(errorMsgWrapper).removeClass("hidden"); + } else if (!emailUsername) { + $(errorMsg).text("Username is a required field. It cannot be empty."); + //$(errorMsgWrapper).removeClass("hidden"); + } else if (!emailPassword) { + $(errorMsg).text("Password is a required field. It cannot be empty."); + //$(errorMsgWrapper).removeClass("hidden"); + } else if (!emailSenderAddress) { + $(errorMsg).text("Sender Email Address is a required field. It cannot be empty."); + //$(errorMsgWrapper).removeClass("hidden"); + } else if (!emailIsValid(emailSenderAddress)) { + $(errorMsg).text("Provided sender email is invalid. Please check."); + //$(errorMsgWrapper).removeClass("hidden"); + } */ + + /*var host = { + "name": configParams["GENERAL_EMAIL_HOST"], + "value": emailHost, + "contentType": "text" + }; + + var port = { + "name": configParams["GENERAL_EMAIL_PORT"], + "value": emailPort, + "contentType": "text" + }; + + var username = { + "name": configParams["GENERAL_EMAIL_USERNAME"], + "value": emailUsername, + "contentType": "text" + }; + + var password = { + "name": configParams["GENERAL_EMAIL_PASSWORD"], + "value": emailPassword, + "contentType": "text" + }; + + var sender = { + "name": configParams["GENERAL_EMAIL_SENDER_ADDRESS"], + "value": emailSenderAddress, + "contentType": "text" + }; + + var template = { + "name": configParams["GENERAL_EMAIL_TEMPLATE"], + "value": emailTemplate, + "contentType": "text" + };*/ + + configList.push(monitorFrequency); + /*configList.push(host); + configList.push(port); + configList.push(username); + configList.push(password); + configList.push(sender); + configList.push(template);*/ + + addConfigFormData.configuration = configList; + + var addConfigAPI = "/devicemgt_admin/configuration"; + + invokerUtil.post( + addConfigAPI, + addConfigFormData, + function (data) { + data = JSON.parse(data); + if (data.statusCode == responseCodes["SUCCESS"]) { + $("#config-save-form").addClass("hidden"); + $("#record-created-msg").removeClass("hidden"); + } else if (data == 500) { + $(errorMsg).text("Exception occurred at backend."); + } else if (data == 403) { + $(errorMsg).text("Action was not permitted."); + } else { + $(errorMsg).text("An unexpected error occurred."); + } + + $(errorMsgWrapper).removeClass("hidden"); + }, function () { + $(errorMsg).text("An unexpected error occurred."); + $(errorMsgWrapper).removeClass("hidden"); + } + ); + } + }); + + var errorMsgWrapper = "#ios-config-error-msg"; + var errorMsg = "#ios-config-error-msg span"; + var fileTypes = ['pfx']; + var notSupportedError = false; + + var base64MDMCert = ""; + var fileInputMDMCert = $('#ios-config-mdm-certificate'); + var fileNameMDMCert = ""; + var invalidFormatMDMCert = false; + + var base64APNSCert = ""; + var fileInputAPNSCert = $('#ios-config-apns-certificate'); + var fileNameAPNSCert = ""; + var invalidFormatAPNSCert = false; + + $(fileInputMDMCert).change(function() { + + if (!window.File || !window.FileReader || !window.FileList || !window.Blob) { + $(errorMsg).text("The File APIs are not fully supported in this browser."); + $(errorMsgWrapper).removeClass("hidden"); + notSupportedError = true; + return; + } + + var file = fileInputMDMCert[0].files[0]; + fileNameMDMCert = file.name; + var extension = file.name.split('.').pop().toLowerCase(), + isSuccess = fileTypes.indexOf(extension) > -1; + + if (isSuccess) { + var fileReader = new FileReader(); + fileReader.onload = function(event) { + base64MDMCert = event.target.result; + }; + fileReader.readAsDataURL(file); + invalidFormatMDMCert = false; + } else { + base64MDMCert = ""; + invalidFormatMDMCert = true; + } + }); + + $(fileInputAPNSCert).change(function() { + + if (!window.File || !window.FileReader || !window.FileList || !window.Blob) { + $(errorMsg).text("The File APIs are not fully supported in this browser."); + $(errorMsgWrapper).removeClass("hidden"); + notSupportedError = true; + return; + } + + var file = fileInputAPNSCert[0].files[0]; + fileNameAPNSCert = file.name; + var extension = file.name.split('.').pop().toLowerCase(), + isSuccess = fileTypes.indexOf(extension) > -1; + + if (isSuccess) { + var fileReader = new FileReader(); + fileReader.onload = function(event) { + base64APNSCert = event.target.result; + }; + fileReader.readAsDataURL(file); + invalidFormatAPNSCert = false; + } else { + base64MDMCert = ""; + invalidFormatAPNSCert = true; + } + }); + + $("button#save-ios-btn").click(function() { + + var configCountry = $("#ios-config-country").val(); + var configState = $("#ios-config-state").val(); + var configLocality = $("#ios-config-locality").val(); + var configOrganization = $("#ios-config-organization").val(); + var configOrganizationUnit = $("#ios-config-organization-unit").val(); + var MDMCertPassword = $("#ios-config-mdm-certificate-password").val(); + var MDMCertTopicID = $("#ios-config-mdm-certificate-topic-id").val(); + var APNSCertPassword = $("#ios-config-apns-certificate-password").val(); + var configOrgDisplayName = $("#ios-org-display-name").val(); + var iosLicense = tinymce.get('ios-eula').getContent(); + + if (!configCountry) { + $(errorMsg).text("SCEP country is a required field. It cannot be empty."); + $(errorMsgWrapper).removeClass("hidden"); + } else if (!configState) { + $(errorMsg).text("SCEP state is a required field. It cannot be empty."); + $(errorMsgWrapper).removeClass("hidden"); + } else if (!configLocality) { + $(errorMsg).text("SCEP locality is a required field. It cannot be empty."); + $(errorMsgWrapper).removeClass("hidden"); + } else if (!configOrganization) { + $(errorMsg).text("SCEP organization is a required field. It cannot be empty."); + $(errorMsgWrapper).removeClass("hidden"); + } else if (!configOrganizationUnit) { + $(errorMsg).text("SCEP organization unit is a required field. It cannot be empty."); + $(errorMsgWrapper).removeClass("hidden"); + } else if (!MDMCertPassword) { + $(errorMsg).text("MDM certificate password is a required field. It cannot be empty."); + $(errorMsgWrapper).removeClass("hidden"); + } else if (!MDMCertTopicID) { + $(errorMsg).text("MDM certificate topic ID is a required field. It cannot be empty."); + $(errorMsgWrapper).removeClass("hidden"); + } else if (!APNSCertPassword) { + $(errorMsg).text("APNS certificate password is a required field. It cannot be empty."); + $(errorMsgWrapper).removeClass("hidden"); + } else if(notSupportedError) { + $(errorMsg).text("The File APIs are not fully supported in this browser."); + $(errorMsgWrapper).removeClass("hidden"); + } else if (invalidFormatMDMCert) { + $(errorMsg).text("MDM certificate needs to be in pfx format."); + $(errorMsgWrapper).removeClass("hidden"); + } else if (base64MDMCert == '') { + $(errorMsg).text("MDM certificate is a required field. It cannot be empty."); + $(errorMsgWrapper).removeClass("hidden"); + } else if (invalidFormatAPNSCert) { + $(errorMsg).text("APNS certificate needs to be in pfx format."); + $(errorMsgWrapper).removeClass("hidden"); + } else if (base64APNSCert == '') { + $(errorMsg).text("APNS certificate is a required field. It cannot be empty."); + $(errorMsgWrapper).removeClass("hidden"); + } else if (!configOrgDisplayName) { + $(errorMsg).text("Organization display name is a required field. It cannot be empty."); + $(errorMsgWrapper).removeClass("hidden"); + } + + var addConfigFormData = {}; + var configList = new Array(); + + var configCountry = { + "name": configParams["CONFIG_COUNTRY"], + "value": configCountry, + "contentType": "text" + }; + + var configState = { + "name": configParams["CONFIG_STATE"], + "value": configState, + "contentType": "text" + }; + + var configLocality = { + "name": configParams["CONFIG_LOCALITY"], + "value": configLocality, + "contentType": "text" + }; + + var configOrganization = { + "name": configParams["CONFIG_ORGANIZATION"], + "value": configOrganization, + "contentType": "text" + }; + + var configOrganizationUnit = { + "name": configParams["CONFIG_ORGANIZATION_UNIT"], + "value": configOrganizationUnit, + "contentType": "text" + }; + + var MDMCertPassword = { + "name": configParams["MDM_CERT_PASSWORD"], + "value": MDMCertPassword, + "contentType": "text" + }; + + var MDMCertTopicID = { + "name": configParams["MDM_CERT_TOPIC_ID"], + "value": MDMCertTopicID, + "contentType": "text" + }; + + var APNSCertPassword = { + "name": configParams["APNS_CERT_PASSWORD"], + "value": APNSCertPassword, + "contentType": "text" + }; + + var paramBase64MDMCert = { + "name": configParams["MDM_CERT"], + "value": base64MDMCert, + "contentType": "text" + }; + + var MDMCertName = { + "name": configParams["MDM_CERT_NAME"], + "value": fileNameMDMCert, + "contentType": "text" + }; + + var paramBase64APNSCert = { + "name": configParams["APNS_CERT"], + "value": base64APNSCert, + "contentType": "text" + }; + + var APNSCertName = { + "name": configParams["APNS_CERT_NAME"], + "value": fileNameAPNSCert, + "contentType": "text" + }; + + var paramOrganizationDisplayName = { + "name": configParams["ORG_DISPLAY_NAME"], + "value": configOrgDisplayName, + "contentType": "text" + }; + + var iosEula = { + "name": configParams["IOS_EULA"], + "value": iosLicense, + "contentType": "text" + }; + + configList.push(configCountry); + configList.push(configState); + configList.push(configLocality); + configList.push(configOrganization); + configList.push(configOrganizationUnit); + configList.push(MDMCertPassword); + configList.push(MDMCertTopicID); + configList.push(APNSCertPassword); + configList.push(paramBase64MDMCert); + configList.push(MDMCertName); + configList.push(paramBase64APNSCert); + configList.push(APNSCertName); + configList.push(paramOrganizationDisplayName); + configList.push(iosEula); + + addConfigFormData.type = platformTypeConstants["IOS"]; + addConfigFormData.configuration = configList; + + var addConfigAPI = "/ios/configuration"; + + invokerUtil.post( + addConfigAPI, + addConfigFormData, + function (data) { + data = JSON.parse(data); + if (data.responseCode == responseCodes["CREATED"]) { + $("#config-save-form").addClass("hidden"); + $("#record-created-msg").removeClass("hidden"); + } else if (data == 500) { + $(errorMsg).text("Exception occurred at backend."); + } else if (data == 400) { + $(errorMsg).text("Configurations cannot be empty."); + } else { + $(errorMsg).text("An unexpected error occurred."); + } + + $(errorMsgWrapper).removeClass("hidden"); + }, function () { + $(errorMsg).text("An unexpected error occurred."); + $(errorMsgWrapper).removeClass("hidden"); + } + ); + + }); + + var errorMsgWrapper = "#windows-config-error-msg"; + var errorMsg = "#windows-config-error-msg span"; + var fileTypes = ['jks']; + var notSupportedError = false; + + var base64WindowsMDMCert = ""; + var fileInputWindowsMDMCert = $('#windows-config-mdm-certificate'); + var fileNameWindowsMDMCert = ""; + var invalidFormatWindowsMDMCert = false; + + $(fileInputWindowsMDMCert).change(function() { + + if (!window.File || !window.FileReader || !window.FileList || !window.Blob) { + $(errorMsg).text("The File APIs are not fully supported in this browser."); + $(errorMsgWrapper).removeClass("hidden"); + notSupportedError = true; + return; + } + + var file = fileInputWindowsMDMCert[0].files[0]; + fileNameWindowsMDMCert = file.name; + var extension = file.name.split('.').pop().toLowerCase(), + isSuccess = fileTypes.indexOf(extension) > -1; + + if (isSuccess) { + var fileReader = new FileReader(); + fileReader.onload = function(event) { + base64WindowsMDMCert = event.target.result; + }; + fileReader.readAsDataURL(file); + invalidFormatWindowsMDMCert = false; + } else { + base64MDMCert = ""; + invalidFormatWindowsMDMCert = true; + } + }); + + $("button#save-windows-btn").click(function() { + + var notifierFrequency = $("#windows-config-notifier-frequency").val(); + var windowsLicense = tinymce.get('windows-eula').getContent(); + + if (!notifierFrequency) { + $(errorMsg).text("Notifier Frequency is a required field. It cannot be empty."); + $(errorMsgWrapper).removeClass("hidden"); + } else if (!windowsLicense) { + $(errorMsg).text("License is a required field. It cannot be empty."); + $(errorMsgWrapper).removeClass("hidden"); + } else if(!$.isNumeric(notifierFrequency)){ + $(errorMsg).text("Provided Notifier frequency is invalid. It must be a number."); + $(errorMsgWrapper).removeClass("hidden"); + } + + var addConfigFormData = {}; + var configList = new Array(); + + var paramNotifierFrequency = { + "name": configParams["NOTIFIER_FREQUENCY"], + "value": notifierFrequency, + "contentType": "text" + }; + + var windowsEula = { + "name": configParams["WINDOWS_EULA"], + "value": windowsLicense, + "contentType": "text" + }; + + configList.push(paramNotifierFrequency); + configList.push(windowsEula); + + addConfigFormData.type = platformTypeConstants["WINDOWS"]; + addConfigFormData.configuration = configList; + + var addConfigAPI = "/mdm-windows-agent/services/configuration"; + + invokerUtil.post( + addConfigAPI, + addConfigFormData, + function (data) { + data = JSON.parse(data); + if (data.responseCode == responseCodes["CREATED"]) { + $("#config-save-form").addClass("hidden"); + $("#record-created-msg").removeClass("hidden"); + } else if (data == 500) { + $(errorMsg).text("Exception occurred at backend."); + } else if (data == 400) { + $(errorMsg).text("Configurations cannot be empty."); + } else { + $(errorMsg).text("An unexpected error occurred."); + } + + $(errorMsgWrapper).removeClass("hidden"); + }, function () { + $(errorMsg).text("An unexpected error occurred."); + $(errorMsgWrapper).removeClass("hidden"); + } + ); + + }); +}); + +// Start of HTML embedded invoke methods +var showAdvanceOperation = function (operation, button) { + $(button).addClass('selected'); + $(button).siblings().removeClass('selected'); + var hiddenOperation = ".wr-hidden-operations-content > div"; + $(hiddenOperation + '[data-operation="' + operation + '"]').show(); + $(hiddenOperation + '[data-operation="' + operation + '"]').siblings().hide(); +}; diff --git a/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.policy.edit/edit.hbs b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.policy.edit/edit.hbs new file mode 100644 index 0000000000..4418289187 --- /dev/null +++ b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.policy.edit/edit.hbs @@ -0,0 +1,246 @@ +{{#zone "topCss"}} + {{css "css/codemirror.css"}} +{{/zone}} +
      +
      + + + + + + + + + +
      +
      +

      EDIT POLICY

      +
      +
      +
      +
      +
      +

      Step 1: Edit current profile

      +
      +
      + +
      +
      +
      + +
      +
      +
      +
      +
      + +
      +
      +
      +
      + + + +
      +
      +{{#zone "bottomJs"}} + {{js "js/codemirror.js"}} + {{js "js/sql.js"}} + {{js "js/policy-edit.js"}} +{{/zone}} + diff --git a/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.policy.edit/edit.js b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.policy.edit/edit.js new file mode 100644 index 0000000000..36b130ca07 --- /dev/null +++ b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.policy.edit/edit.js @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2016, 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("policy-view-edit-unit backend js"); + log.debug("calling policy-view-edit-unit"); + var userModule = require("/app/modules/user.js").userModule; + context.roles = userModule.getRoles().content; + return context; +} \ No newline at end of file diff --git a/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.policy.edit/edit.json b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.policy.edit/edit.json new file mode 100644 index 0000000000..fd25901297 --- /dev/null +++ b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.policy.edit/edit.json @@ -0,0 +1,3 @@ +{ + "version" : "1.0.0" +} \ No newline at end of file diff --git a/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.policy.edit/public/css/codemirror.css b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.policy.edit/public/css/codemirror.css new file mode 100644 index 0000000000..e749a5211d --- /dev/null +++ b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.policy.edit/public/css/codemirror.css @@ -0,0 +1,342 @@ +/* + * Copyright (c) 2016, 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. + */ + +/* BASICS */ + +.CodeMirror { + /* Set height, width, borders, and global font properties here */ + font-family: monospace; + height: 300px; + color: black; +} + +/* PADDING */ + +.CodeMirror-lines { + padding: 4px 0; /* Vertical padding around content */ +} +.CodeMirror pre { + padding: 0 4px; /* Horizontal padding of content */ +} + +.CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler { + background-color: white; /* The little square between H and V scrollbars */ +} + +/* GUTTER */ + +.CodeMirror-gutters { + border-right: 1px solid #ddd; + background-color: #f7f7f7; + white-space: nowrap; +} +.CodeMirror-linenumbers {} +.CodeMirror-linenumber { + padding: 0 3px 0 5px; + min-width: 20px; + text-align: right; + color: #999; + white-space: nowrap; + left: -30px; +} + +.CodeMirror-guttermarker { color: black; } +.CodeMirror-guttermarker-subtle { color: #999; } + +/* CURSOR */ + +.CodeMirror div.CodeMirror-cursor { + border-left: 1px solid black; +} +/* Shown when moving in bi-directional text */ +.CodeMirror div.CodeMirror-secondarycursor { + border-left: 1px solid silver; +} +.CodeMirror.cm-fat-cursor div.CodeMirror-cursor { + width: auto; + border: 0; + background: #7e7; +} +.CodeMirror.cm-fat-cursor div.CodeMirror-cursors { + z-index: 1; +} + +.cm-animate-fat-cursor { + width: auto; + border: 0; + -webkit-animation: blink 1.06s steps(1) infinite; + -moz-animation: blink 1.06s steps(1) infinite; + animation: blink 1.06s steps(1) infinite; +} +@-moz-keyframes blink { + 0% { background: #7e7; } + 50% { background: none; } + 100% { background: #7e7; } +} +@-webkit-keyframes blink { + 0% { background: #7e7; } + 50% { background: none; } + 100% { background: #7e7; } +} +@keyframes blink { + 0% { background: #7e7; } + 50% { background: none; } + 100% { background: #7e7; } +} + +/* Can style cursor different in overwrite (non-insert) mode */ +div.CodeMirror-overwrite div.CodeMirror-cursor {} + +.cm-tab { display: inline-block; text-decoration: inherit; } + +.CodeMirror-ruler { + border-left: 1px solid #ccc; + position: absolute; +} + +/* DEFAULT THEME */ + +.cm-s-default .cm-keyword {color: #708;} +.cm-s-default .cm-atom {color: #219;} +.cm-s-default .cm-number {color: #164;} +.cm-s-default .cm-def {color: #00f;} +.cm-s-default .cm-variable, +.cm-s-default .cm-punctuation, +.cm-s-default .cm-property, +.cm-s-default .cm-operator {} +.cm-s-default .cm-variable-2 {color: #05a;} +.cm-s-default .cm-variable-3 {color: #085;} +.cm-s-default .cm-comment {color: #a50;} +.cm-s-default .cm-string {color: #a11;} +.cm-s-default .cm-string-2 {color: #f50;} +.cm-s-default .cm-meta {color: #555;} +.cm-s-default .cm-qualifier {color: #555;} +.cm-s-default .cm-builtin {color: #30a;} +.cm-s-default .cm-bracket {color: #997;} +.cm-s-default .cm-tag {color: #170;} +.cm-s-default .cm-attribute {color: #00c;} +.cm-s-default .cm-header {color: blue;} +.cm-s-default .cm-quote {color: #090;} +.cm-s-default .cm-hr {color: #999;} +.cm-s-default .cm-link {color: #00c;} + +.cm-negative {color: #d44;} +.cm-positive {color: #292;} +.cm-header, .cm-strong {font-weight: bold;} +.cm-em {font-style: italic;} +.cm-link {text-decoration: underline;} +.cm-strikethrough {text-decoration: line-through;} + +.cm-s-default .cm-error {color: #f00;} +.cm-invalidchar {color: #f00;} + +/* Default styles for common addons */ + +div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;} +div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;} +.CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); } +.CodeMirror-activeline-background {background: #e8f2ff;} + +/* STOP */ + +/* The rest of this file contains styles related to the mechanics of + the editor. You probably shouldn't touch them. */ + +.CodeMirror { + position: relative; + overflow: hidden; + background: white; +} + +.CodeMirror-scroll { + overflow: scroll !important; /* Things will break if this is overridden */ + /* 30px is the magic margin used to hide the element's real scrollbars */ + /* See overflow: hidden in .CodeMirror */ + margin-bottom: -30px; margin-right: -30px; + padding-bottom: 30px; + height: 100%; + outline: none; /* Prevent dragging from highlighting the element */ + position: relative; +} +.CodeMirror-sizer { + position: relative; + border-right: 30px solid transparent; +} + +/* The fake, visible scrollbars. Used to force redraw during scrolling + before actuall scrolling happens, thus preventing shaking and + flickering artifacts. */ +.CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler { + position: absolute; + z-index: 6; + display: none; +} +.CodeMirror-vscrollbar { + right: 0; top: 0; + overflow-x: hidden; + overflow-y: scroll; +} +.CodeMirror-hscrollbar { + bottom: 0; left: 0; + overflow-y: hidden; + overflow-x: scroll; +} +.CodeMirror-scrollbar-filler { + right: 0; bottom: 0; +} +.CodeMirror-gutter-filler { + left: 0; bottom: 0; +} + +.CodeMirror-gutters { + position: absolute; left: 0; top: 0; + z-index: 3; +} +.CodeMirror-gutter { + white-space: normal; + height: 100%; + display: inline-block; + /* Hack to make IE7 behave */ + *zoom:1; + *display:inline; +} +.CodeMirror-gutter-wrapper { + position: absolute; + z-index: 4; + height: 100%; +} +.CodeMirror-gutter-elt { + position: absolute; + cursor: default; + z-index: 4; + left: -30px; +} +.CodeMirror-gutter-wrapper { + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; +} + +.CodeMirror-lines { + cursor: text; + min-height: 1px; /* prevents collapsing before first draw */ +} +.CodeMirror pre { + /* Reset some styles that the rest of the page might have set */ + -moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0; + border-width: 0; + background: transparent; + font-family: inherit; + font-size: inherit; + margin: 0; + white-space: pre; + word-wrap: normal; + line-height: inherit; + color: inherit; + z-index: 2; + position: relative; + overflow: visible; + -webkit-tap-highlight-color: transparent; +} +.CodeMirror-wrap pre { + word-wrap: break-word; + white-space: pre-wrap; + word-break: normal; +} + +.CodeMirror-linebackground { + position: absolute; + left: 0; right: 0; top: 0; bottom: 0; + z-index: 0; +} + +.CodeMirror-linewidget { + position: relative; + z-index: 2; + overflow: auto; +} + +.CodeMirror-widget {} + +.CodeMirror-code { + outline: none; +} + +/* Force content-box sizing for the elements where we expect it */ +.CodeMirror-scroll, +.CodeMirror-sizer, +.CodeMirror-gutter, +.CodeMirror-gutters, +.CodeMirror-linenumber { + -moz-box-sizing: content-box; + box-sizing: content-box; +} + +.CodeMirror-measure { + position: absolute; + width: 100%; + height: 0; + overflow: hidden; + visibility: hidden; +} +.CodeMirror-measure pre { position: static; } + +.CodeMirror div.CodeMirror-cursor { + position: absolute; + border-right: none; + width: 0; +} + +div.CodeMirror-cursors { + visibility: hidden; + position: relative; + z-index: 3; +} +.CodeMirror-focused div.CodeMirror-cursors { + visibility: visible; +} + +.CodeMirror-selected { background: #d9d9d9; } +.CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; } +.CodeMirror-crosshair { cursor: crosshair; } +.CodeMirror ::selection { background: #d7d4f0; } +.CodeMirror ::-moz-selection { background: #d7d4f0; } + +.cm-searching { + background: #ffa; + background: rgba(255, 255, 0, .4); +} + +/* IE7 hack to prevent it from returning funny offsetTops on the spans */ +.CodeMirror span { *vertical-align: text-bottom; } + +/* Used to force a border model for a node */ +.cm-force-border { padding-right: .1px; } + +@media print { + /* Hide the cursor when printing */ + .CodeMirror div.CodeMirror-cursors { + visibility: hidden; + } +} + +/* See issue #2901 */ +.cm-tab-wrap-hack:after { content: ''; } + +/* Help users use markselection to safely style text background */ +span.CodeMirror-selectedtext { background: none; } \ No newline at end of file diff --git a/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.policy.edit/public/js/codemirror.js b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.policy.edit/public/js/codemirror.js new file mode 100644 index 0000000000..20f3f95ed9 --- /dev/null +++ b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.policy.edit/public/js/codemirror.js @@ -0,0 +1,8720 @@ +/* + * Copyright (c) 2016, 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. + */ + +// This is CodeMirror (http://codemirror.net), a code editor +// implemented in JavaScript on top of the browser's DOM. +// +// You can find some technical background for some of the code below +// at http://marijnhaverbeke.nl/blog/#cm-internals . + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + module.exports = mod(); + else if (typeof define == "function" && define.amd) // AMD + return define([], mod); + else // Plain browser env + this.CodeMirror = mod(); +})(function() { + "use strict"; + + // BROWSER SNIFFING + + // Kludges for bugs and behavior differences that can't be feature + // detected are enabled based on userAgent etc sniffing. + + var gecko = /gecko\/\d/i.test(navigator.userAgent); + var ie_upto10 = /MSIE \d/.test(navigator.userAgent); + var ie_11up = /Trident\/(?:[7-9]|\d{2,})\..*rv:(\d+)/.exec(navigator.userAgent); + var ie = ie_upto10 || ie_11up; + var ie_version = ie && (ie_upto10 ? document.documentMode || 6 : ie_11up[1]); + var webkit = /WebKit\//.test(navigator.userAgent); + var qtwebkit = webkit && /Qt\/\d+\.\d+/.test(navigator.userAgent); + var chrome = /Chrome\//.test(navigator.userAgent); + var presto = /Opera\//.test(navigator.userAgent); + var safari = /Apple Computer/.test(navigator.vendor); + var mac_geMountainLion = /Mac OS X 1\d\D([8-9]|\d\d)\D/.test(navigator.userAgent); + var phantom = /PhantomJS/.test(navigator.userAgent); + + var ios = /AppleWebKit/.test(navigator.userAgent) && /Mobile\/\w+/.test(navigator.userAgent); + // This is woefully incomplete. Suggestions for alternative methods welcome. + var mobile = ios || /Android|webOS|BlackBerry|Opera Mini|Opera Mobi|IEMobile/i.test(navigator.userAgent); + var mac = ios || /Mac/.test(navigator.platform); + var windows = /win/i.test(navigator.platform); + + var presto_version = presto && navigator.userAgent.match(/Version\/(\d*\.\d*)/); + if (presto_version) presto_version = Number(presto_version[1]); + if (presto_version && presto_version >= 15) { presto = false; webkit = true; } + // Some browsers use the wrong event properties to signal cmd/ctrl on OS X + var flipCtrlCmd = mac && (qtwebkit || presto && (presto_version == null || presto_version < 12.11)); + var captureRightClick = gecko || (ie && ie_version >= 9); + + // Optimize some code when these features are not used. + var sawReadOnlySpans = false, sawCollapsedSpans = false; + + // EDITOR CONSTRUCTOR + + // A CodeMirror instance represents an editor. This is the object + // that user code is usually dealing with. + + function CodeMirror(place, options) { + if (!(this instanceof CodeMirror)) return new CodeMirror(place, options); + + this.options = options = options ? copyObj(options) : {}; + // Determine effective options based on given values and defaults. + copyObj(defaults, options, false); + setGuttersForLineNumbers(options); + + var doc = options.value; + if (typeof doc == "string") doc = new Doc(doc, options.mode); + this.doc = doc; + + var input = new CodeMirror.inputStyles[options.inputStyle](this); + var display = this.display = new Display(place, doc, input); + display.wrapper.CodeMirror = this; + updateGutters(this); + themeChanged(this); + if (options.lineWrapping) + this.display.wrapper.className += " CodeMirror-wrap"; + if (options.autofocus && !mobile) display.input.focus(); + initScrollbars(this); + + this.state = { + keyMaps: [], // stores maps added by addKeyMap + overlays: [], // highlighting overlays, as added by addOverlay + modeGen: 0, // bumped when mode/overlay changes, used to invalidate highlighting info + overwrite: false, + delayingBlurEvent: false, + focused: false, + suppressEdits: false, // used to disable editing during key handlers when in readOnly mode + pasteIncoming: false, cutIncoming: false, // help recognize paste/cut edits in input.poll + draggingText: false, + highlight: new Delayed(), // stores highlight worker timeout + keySeq: null, // Unfinished key sequence + specialChars: null + }; + + var cm = this; + + // Override magic textarea content restore that IE sometimes does + // on our hidden textarea on reload + if (ie && ie_version < 11) setTimeout(function() { cm.display.input.reset(true); }, 20); + + registerEventHandlers(this); + ensureGlobalHandlers(); + + startOperation(this); + this.curOp.forceUpdate = true; + attachDoc(this, doc); + + if ((options.autofocus && !mobile) || cm.hasFocus()) + setTimeout(bind(onFocus, this), 20); + else + onBlur(this); + + for (var opt in optionHandlers) if (optionHandlers.hasOwnProperty(opt)) + optionHandlers[opt](this, options[opt], Init); + maybeUpdateLineNumberWidth(this); + if (options.finishInit) options.finishInit(this); + for (var i = 0; i < initHooks.length; ++i) initHooks[i](this); + endOperation(this); + // Suppress optimizelegibility in Webkit, since it breaks text + // measuring on line wrapping boundaries. + if (webkit && options.lineWrapping && + getComputedStyle(display.lineDiv).textRendering == "optimizelegibility") + display.lineDiv.style.textRendering = "auto"; + } + + // DISPLAY CONSTRUCTOR + + // The display handles the DOM integration, both for input reading + // and content drawing. It holds references to DOM nodes and + // display-related state. + + function Display(place, doc, input) { + var d = this; + this.input = input; + + // Covers bottom-right square when both scrollbars are present. + d.scrollbarFiller = elt("div", null, "CodeMirror-scrollbar-filler"); + d.scrollbarFiller.setAttribute("cm-not-content", "true"); + // Covers bottom of gutter when coverGutterNextToScrollbar is on + // and h scrollbar is present. + d.gutterFiller = elt("div", null, "CodeMirror-gutter-filler"); + d.gutterFiller.setAttribute("cm-not-content", "true"); + // Will contain the actual code, positioned to cover the viewport. + d.lineDiv = elt("div", null, "CodeMirror-code"); + // Elements are added to these to represent selection and cursors. + d.selectionDiv = elt("div", null, null, "position: relative; z-index: 1"); + d.cursorDiv = elt("div", null, "CodeMirror-cursors"); + // A visibility: hidden element used to find the size of things. + d.measure = elt("div", null, "CodeMirror-measure"); + // When lines outside of the viewport are measured, they are drawn in this. + d.lineMeasure = elt("div", null, "CodeMirror-measure"); + // Wraps everything that needs to exist inside the vertically-padded coordinate system + d.lineSpace = elt("div", [d.measure, d.lineMeasure, d.selectionDiv, d.cursorDiv, d.lineDiv], + null, "position: relative; outline: none"); + // Moved around its parent to cover visible view. + d.mover = elt("div", [elt("div", [d.lineSpace], "CodeMirror-lines")], null, "position: relative"); + // Set to the height of the document, allowing scrolling. + d.sizer = elt("div", [d.mover], "CodeMirror-sizer"); + d.sizerWidth = null; + // Behavior of elts with overflow: auto and padding is + // inconsistent across browsers. This is used to ensure the + // scrollable area is big enough. + d.heightForcer = elt("div", null, null, "position: absolute; height: " + scrollerGap + "px; width: 1px;"); + // Will contain the gutters, if any. + d.gutters = elt("div", null, "CodeMirror-gutters"); + d.lineGutter = null; + // Actual scrollable element. + d.scroller = elt("div", [d.sizer, d.heightForcer, d.gutters], "CodeMirror-scroll"); + d.scroller.setAttribute("tabIndex", "-1"); + // The element in which the editor lives. + d.wrapper = elt("div", [d.scrollbarFiller, d.gutterFiller, d.scroller], "CodeMirror"); + + // Work around IE7 z-index bug (not perfect, hence IE7 not really being supported) + if (ie && ie_version < 8) { d.gutters.style.zIndex = -1; d.scroller.style.paddingRight = 0; } + if (!webkit && !(gecko && mobile)) d.scroller.draggable = true; + + if (place) { + if (place.appendChild) place.appendChild(d.wrapper); + else place(d.wrapper); + } + + // Current rendered range (may be bigger than the view window). + d.viewFrom = d.viewTo = doc.first; + d.reportedViewFrom = d.reportedViewTo = doc.first; + // Information about the rendered lines. + d.view = []; + d.renderedView = null; + // Holds info about a single rendered line when it was rendered + // for measurement, while not in view. + d.externalMeasured = null; + // Empty space (in pixels) above the view + d.viewOffset = 0; + d.lastWrapHeight = d.lastWrapWidth = 0; + d.updateLineNumbers = null; + + d.nativeBarWidth = d.barHeight = d.barWidth = 0; + d.scrollbarsClipped = false; + + // Used to only resize the line number gutter when necessary (when + // the amount of lines crosses a boundary that makes its width change) + d.lineNumWidth = d.lineNumInnerWidth = d.lineNumChars = null; + // Set to true when a non-horizontal-scrolling line widget is + // added. As an optimization, line widget aligning is skipped when + // this is false. + d.alignWidgets = false; + + d.cachedCharWidth = d.cachedTextHeight = d.cachedPaddingH = null; + + // Tracks the maximum line length so that the horizontal scrollbar + // can be kept static when scrolling. + d.maxLine = null; + d.maxLineLength = 0; + d.maxLineChanged = false; + + // Used for measuring wheel scrolling granularity + d.wheelDX = d.wheelDY = d.wheelStartX = d.wheelStartY = null; + + // True when shift is held down. + d.shift = false; + + // Used to track whether anything happened since the context menu + // was opened. + d.selForContextMenu = null; + + d.activeTouch = null; + + input.init(d); + } + + // STATE UPDATES + + // Used to get the editor into a consistent state again when options change. + + function loadMode(cm) { + cm.doc.mode = CodeMirror.getMode(cm.options, cm.doc.modeOption); + resetModeState(cm); + } + + function resetModeState(cm) { + cm.doc.iter(function(line) { + if (line.stateAfter) line.stateAfter = null; + if (line.styles) line.styles = null; + }); + cm.doc.frontier = cm.doc.first; + startWorker(cm, 100); + cm.state.modeGen++; + if (cm.curOp) regChange(cm); + } + + function wrappingChanged(cm) { + if (cm.options.lineWrapping) { + addClass(cm.display.wrapper, "CodeMirror-wrap"); + cm.display.sizer.style.minWidth = ""; + cm.display.sizerWidth = null; + } else { + rmClass(cm.display.wrapper, "CodeMirror-wrap"); + findMaxLine(cm); + } + estimateLineHeights(cm); + regChange(cm); + clearCaches(cm); + setTimeout(function(){updateScrollbars(cm);}, 100); + } + + // Returns a function that estimates the height of a line, to use as + // first approximation until the line becomes visible (and is thus + // properly measurable). + function estimateHeight(cm) { + var th = textHeight(cm.display), wrapping = cm.options.lineWrapping; + var perLine = wrapping && Math.max(5, cm.display.scroller.clientWidth / charWidth(cm.display) - 3); + return function(line) { + if (lineIsHidden(cm.doc, line)) return 0; + + var widgetsHeight = 0; + if (line.widgets) for (var i = 0; i < line.widgets.length; i++) { + if (line.widgets[i].height) widgetsHeight += line.widgets[i].height; + } + + if (wrapping) + return widgetsHeight + (Math.ceil(line.text.length / perLine) || 1) * th; + else + return widgetsHeight + th; + }; + } + + function estimateLineHeights(cm) { + var doc = cm.doc, est = estimateHeight(cm); + doc.iter(function(line) { + var estHeight = est(line); + if (estHeight != line.height) updateLineHeight(line, estHeight); + }); + } + + function themeChanged(cm) { + cm.display.wrapper.className = cm.display.wrapper.className.replace(/\s*cm-s-\S+/g, "") + + cm.options.theme.replace(/(^|\s)\s*/g, " cm-s-"); + clearCaches(cm); + } + + function guttersChanged(cm) { + updateGutters(cm); + regChange(cm); + setTimeout(function(){alignHorizontally(cm);}, 20); + } + + // Rebuild the gutter elements, ensure the margin to the left of the + // code matches their width. + function updateGutters(cm) { + var gutters = cm.display.gutters, specs = cm.options.gutters; + removeChildren(gutters); + for (var i = 0; i < specs.length; ++i) { + var gutterClass = specs[i]; + var gElt = gutters.appendChild(elt("div", null, "CodeMirror-gutter " + gutterClass)); + if (gutterClass == "CodeMirror-linenumbers") { + cm.display.lineGutter = gElt; + gElt.style.width = (cm.display.lineNumWidth || 1) + "px"; + } + } + gutters.style.display = i ? "" : "none"; + updateGutterSpace(cm); + } + + function updateGutterSpace(cm) { + var width = cm.display.gutters.offsetWidth; + cm.display.sizer.style.marginLeft = width + "px"; + } + + // Compute the character length of a line, taking into account + // collapsed ranges (see markText) that might hide parts, and join + // other lines onto it. + function lineLength(line) { + if (line.height == 0) return 0; + var len = line.text.length, merged, cur = line; + while (merged = collapsedSpanAtStart(cur)) { + var found = merged.find(0, true); + cur = found.from.line; + len += found.from.ch - found.to.ch; + } + cur = line; + while (merged = collapsedSpanAtEnd(cur)) { + var found = merged.find(0, true); + len -= cur.text.length - found.from.ch; + cur = found.to.line; + len += cur.text.length - found.to.ch; + } + return len; + } + + // Find the longest line in the document. + function findMaxLine(cm) { + var d = cm.display, doc = cm.doc; + d.maxLine = getLine(doc, doc.first); + d.maxLineLength = lineLength(d.maxLine); + d.maxLineChanged = true; + doc.iter(function(line) { + var len = lineLength(line); + if (len > d.maxLineLength) { + d.maxLineLength = len; + d.maxLine = line; + } + }); + } + + // Make sure the gutters options contains the element + // "CodeMirror-linenumbers" when the lineNumbers option is true. + function setGuttersForLineNumbers(options) { + var found = indexOf(options.gutters, "CodeMirror-linenumbers"); + if (found == -1 && options.lineNumbers) { + options.gutters = options.gutters.concat(["CodeMirror-linenumbers"]); + } else if (found > -1 && !options.lineNumbers) { + options.gutters = options.gutters.slice(0); + options.gutters.splice(found, 1); + } + } + + // SCROLLBARS + + // Prepare DOM reads needed to update the scrollbars. Done in one + // shot to minimize update/measure roundtrips. + function measureForScrollbars(cm) { + var d = cm.display, gutterW = d.gutters.offsetWidth; + var docH = Math.round(cm.doc.height + paddingVert(cm.display)); + return { + clientHeight: d.scroller.clientHeight, + viewHeight: d.wrapper.clientHeight, + scrollWidth: d.scroller.scrollWidth, clientWidth: d.scroller.clientWidth, + viewWidth: d.wrapper.clientWidth, + barLeft: cm.options.fixedGutter ? gutterW : 0, + docHeight: docH, + scrollHeight: docH + scrollGap(cm) + d.barHeight, + nativeBarWidth: d.nativeBarWidth, + gutterWidth: gutterW + }; + } + + function NativeScrollbars(place, scroll, cm) { + this.cm = cm; + var vert = this.vert = elt("div", [elt("div", null, null, "min-width: 1px")], "CodeMirror-vscrollbar"); + var horiz = this.horiz = elt("div", [elt("div", null, null, "height: 100%; min-height: 1px")], "CodeMirror-hscrollbar"); + place(vert); place(horiz); + + on(vert, "scroll", function() { + if (vert.clientHeight) scroll(vert.scrollTop, "vertical"); + }); + on(horiz, "scroll", function() { + if (horiz.clientWidth) scroll(horiz.scrollLeft, "horizontal"); + }); + + this.checkedOverlay = false; + // Need to set a minimum width to see the scrollbar on IE7 (but must not set it on IE8). + if (ie && ie_version < 8) this.horiz.style.minHeight = this.vert.style.minWidth = "18px"; + } + + NativeScrollbars.prototype = copyObj({ + update: function(measure) { + var needsH = measure.scrollWidth > measure.clientWidth + 1; + var needsV = measure.scrollHeight > measure.clientHeight + 1; + var sWidth = measure.nativeBarWidth; + + if (needsV) { + this.vert.style.display = "block"; + this.vert.style.bottom = needsH ? sWidth + "px" : "0"; + var totalHeight = measure.viewHeight - (needsH ? sWidth : 0); + // A bug in IE8 can cause this value to be negative, so guard it. + this.vert.firstChild.style.height = + Math.max(0, measure.scrollHeight - measure.clientHeight + totalHeight) + "px"; + } else { + this.vert.style.display = ""; + this.vert.firstChild.style.height = "0"; + } + + if (needsH) { + this.horiz.style.display = "block"; + this.horiz.style.right = needsV ? sWidth + "px" : "0"; + this.horiz.style.left = measure.barLeft + "px"; + var totalWidth = measure.viewWidth - measure.barLeft - (needsV ? sWidth : 0); + this.horiz.firstChild.style.width = + (measure.scrollWidth - measure.clientWidth + totalWidth) + "px"; + } else { + this.horiz.style.display = ""; + this.horiz.firstChild.style.width = "0"; + } + + if (!this.checkedOverlay && measure.clientHeight > 0) { + if (sWidth == 0) this.overlayHack(); + this.checkedOverlay = true; + } + + return {right: needsV ? sWidth : 0, bottom: needsH ? sWidth : 0}; + }, + setScrollLeft: function(pos) { + if (this.horiz.scrollLeft != pos) this.horiz.scrollLeft = pos; + }, + setScrollTop: function(pos) { + if (this.vert.scrollTop != pos) this.vert.scrollTop = pos; + }, + overlayHack: function() { + var w = mac && !mac_geMountainLion ? "12px" : "18px"; + this.horiz.style.minHeight = this.vert.style.minWidth = w; + var self = this; + var barMouseDown = function(e) { + if (e_target(e) != self.vert && e_target(e) != self.horiz) + operation(self.cm, onMouseDown)(e); + }; + on(this.vert, "mousedown", barMouseDown); + on(this.horiz, "mousedown", barMouseDown); + }, + clear: function() { + var parent = this.horiz.parentNode; + parent.removeChild(this.horiz); + parent.removeChild(this.vert); + } + }, NativeScrollbars.prototype); + + function NullScrollbars() {} + + NullScrollbars.prototype = copyObj({ + update: function() { return {bottom: 0, right: 0}; }, + setScrollLeft: function() {}, + setScrollTop: function() {}, + clear: function() {} + }, NullScrollbars.prototype); + + CodeMirror.scrollbarModel = {"native": NativeScrollbars, "null": NullScrollbars}; + + function initScrollbars(cm) { + if (cm.display.scrollbars) { + cm.display.scrollbars.clear(); + if (cm.display.scrollbars.addClass) + rmClass(cm.display.wrapper, cm.display.scrollbars.addClass); + } + + cm.display.scrollbars = new CodeMirror.scrollbarModel[cm.options.scrollbarStyle](function(node) { + cm.display.wrapper.insertBefore(node, cm.display.scrollbarFiller); + // Prevent clicks in the scrollbars from killing focus + on(node, "mousedown", function() { + if (cm.state.focused) setTimeout(function() { cm.display.input.focus(); }, 0); + }); + node.setAttribute("cm-not-content", "true"); + }, function(pos, axis) { + if (axis == "horizontal") setScrollLeft(cm, pos); + else setScrollTop(cm, pos); + }, cm); + if (cm.display.scrollbars.addClass) + addClass(cm.display.wrapper, cm.display.scrollbars.addClass); + } + + function updateScrollbars(cm, measure) { + if (!measure) measure = measureForScrollbars(cm); + var startWidth = cm.display.barWidth, startHeight = cm.display.barHeight; + updateScrollbarsInner(cm, measure); + for (var i = 0; i < 4 && startWidth != cm.display.barWidth || startHeight != cm.display.barHeight; i++) { + if (startWidth != cm.display.barWidth && cm.options.lineWrapping) + updateHeightsInViewport(cm); + updateScrollbarsInner(cm, measureForScrollbars(cm)); + startWidth = cm.display.barWidth; startHeight = cm.display.barHeight; + } + } + + // Re-synchronize the fake scrollbars with the actual size of the + // content. + function updateScrollbarsInner(cm, measure) { + var d = cm.display; + var sizes = d.scrollbars.update(measure); + + d.sizer.style.paddingRight = (d.barWidth = sizes.right) + "px"; + d.sizer.style.paddingBottom = (d.barHeight = sizes.bottom) + "px"; + + if (sizes.right && sizes.bottom) { + d.scrollbarFiller.style.display = "block"; + d.scrollbarFiller.style.height = sizes.bottom + "px"; + d.scrollbarFiller.style.width = sizes.right + "px"; + } else d.scrollbarFiller.style.display = ""; + if (sizes.bottom && cm.options.coverGutterNextToScrollbar && cm.options.fixedGutter) { + d.gutterFiller.style.display = "block"; + d.gutterFiller.style.height = sizes.bottom + "px"; + d.gutterFiller.style.width = measure.gutterWidth + "px"; + } else d.gutterFiller.style.display = ""; + } + + // Compute the lines that are visible in a given viewport (defaults + // the the current scroll position). viewport may contain top, + // height, and ensure (see op.scrollToPos) properties. + function visibleLines(display, doc, viewport) { + var top = viewport && viewport.top != null ? Math.max(0, viewport.top) : display.scroller.scrollTop; + top = Math.floor(top - paddingTop(display)); + var bottom = viewport && viewport.bottom != null ? viewport.bottom : top + display.wrapper.clientHeight; + + var from = lineAtHeight(doc, top), to = lineAtHeight(doc, bottom); + // Ensure is a {from: {line, ch}, to: {line, ch}} object, and + // forces those lines into the viewport (if possible). + if (viewport && viewport.ensure) { + var ensureFrom = viewport.ensure.from.line, ensureTo = viewport.ensure.to.line; + if (ensureFrom < from) { + from = ensureFrom; + to = lineAtHeight(doc, heightAtLine(getLine(doc, ensureFrom)) + display.wrapper.clientHeight); + } else if (Math.min(ensureTo, doc.lastLine()) >= to) { + from = lineAtHeight(doc, heightAtLine(getLine(doc, ensureTo)) - display.wrapper.clientHeight); + to = ensureTo; + } + } + return {from: from, to: Math.max(to, from + 1)}; + } + + // LINE NUMBERS + + // Re-align line numbers and gutter marks to compensate for + // horizontal scrolling. + function alignHorizontally(cm) { + var display = cm.display, view = display.view; + if (!display.alignWidgets && (!display.gutters.firstChild || !cm.options.fixedGutter)) return; + var comp = compensateForHScroll(display) - display.scroller.scrollLeft + cm.doc.scrollLeft; + var gutterW = display.gutters.offsetWidth, left = comp + "px"; + for (var i = 0; i < view.length; i++) if (!view[i].hidden) { + if (cm.options.fixedGutter && view[i].gutter) + view[i].gutter.style.left = left; + var align = view[i].alignable; + if (align) for (var j = 0; j < align.length; j++) + align[j].style.left = left; + } + if (cm.options.fixedGutter) + display.gutters.style.left = (comp + gutterW) + "px"; + } + + // Used to ensure that the line number gutter is still the right + // size for the current document size. Returns true when an update + // is needed. + function maybeUpdateLineNumberWidth(cm) { + if (!cm.options.lineNumbers) return false; + var doc = cm.doc, last = lineNumberFor(cm.options, doc.first + doc.size - 1), display = cm.display; + if (last.length != display.lineNumChars) { + var test = display.measure.appendChild(elt("div", [elt("div", last)], + "CodeMirror-linenumber CodeMirror-gutter-elt")); + var innerW = test.firstChild.offsetWidth, padding = test.offsetWidth - innerW; + display.lineGutter.style.width = ""; + display.lineNumInnerWidth = Math.max(innerW, display.lineGutter.offsetWidth - padding) + 1; + display.lineNumWidth = display.lineNumInnerWidth + padding; + display.lineNumChars = display.lineNumInnerWidth ? last.length : -1; + display.lineGutter.style.width = display.lineNumWidth + "px"; + updateGutterSpace(cm); + return true; + } + return false; + } + + function lineNumberFor(options, i) { + return String(options.lineNumberFormatter(i + options.firstLineNumber)); + } + + // Computes display.scroller.scrollLeft + display.gutters.offsetWidth, + // but using getBoundingClientRect to get a sub-pixel-accurate + // result. + function compensateForHScroll(display) { + return display.scroller.getBoundingClientRect().left - display.sizer.getBoundingClientRect().left; + } + + // DISPLAY DRAWING + + function DisplayUpdate(cm, viewport, force) { + var display = cm.display; + + this.viewport = viewport; + // Store some values that we'll need later (but don't want to force a relayout for) + this.visible = visibleLines(display, cm.doc, viewport); + this.editorIsHidden = !display.wrapper.offsetWidth; + this.wrapperHeight = display.wrapper.clientHeight; + this.wrapperWidth = display.wrapper.clientWidth; + this.oldDisplayWidth = displayWidth(cm); + this.force = force; + this.dims = getDimensions(cm); + this.events = []; + } + + DisplayUpdate.prototype.signal = function(emitter, type) { + if (hasHandler(emitter, type)) + this.events.push(arguments); + }; + DisplayUpdate.prototype.finish = function() { + for (var i = 0; i < this.events.length; i++) + signal.apply(null, this.events[i]); + }; + + function maybeClipScrollbars(cm) { + var display = cm.display; + if (!display.scrollbarsClipped && display.scroller.offsetWidth) { + display.nativeBarWidth = display.scroller.offsetWidth - display.scroller.clientWidth; + display.heightForcer.style.height = scrollGap(cm) + "px"; + display.sizer.style.marginBottom = -display.nativeBarWidth + "px"; + display.sizer.style.borderRightWidth = scrollGap(cm) + "px"; + display.scrollbarsClipped = true; + } + } + + // Does the actual updating of the line display. Bails out + // (returning false) when there is nothing to be done and forced is + // false. + function updateDisplayIfNeeded(cm, update) { + var display = cm.display, doc = cm.doc; + + if (update.editorIsHidden) { + resetView(cm); + return false; + } + + // Bail out if the visible area is already rendered and nothing changed. + if (!update.force && + update.visible.from >= display.viewFrom && update.visible.to <= display.viewTo && + (display.updateLineNumbers == null || display.updateLineNumbers >= display.viewTo) && + display.renderedView == display.view && countDirtyView(cm) == 0) + return false; + + if (maybeUpdateLineNumberWidth(cm)) { + resetView(cm); + update.dims = getDimensions(cm); + } + + // Compute a suitable new viewport (from & to) + var end = doc.first + doc.size; + var from = Math.max(update.visible.from - cm.options.viewportMargin, doc.first); + var to = Math.min(end, update.visible.to + cm.options.viewportMargin); + if (display.viewFrom < from && from - display.viewFrom < 20) from = Math.max(doc.first, display.viewFrom); + if (display.viewTo > to && display.viewTo - to < 20) to = Math.min(end, display.viewTo); + if (sawCollapsedSpans) { + from = visualLineNo(cm.doc, from); + to = visualLineEndNo(cm.doc, to); + } + + var different = from != display.viewFrom || to != display.viewTo || + display.lastWrapHeight != update.wrapperHeight || display.lastWrapWidth != update.wrapperWidth; + adjustView(cm, from, to); + + display.viewOffset = heightAtLine(getLine(cm.doc, display.viewFrom)); + // Position the mover div to align with the current scroll position + cm.display.mover.style.top = display.viewOffset + "px"; + + var toUpdate = countDirtyView(cm); + if (!different && toUpdate == 0 && !update.force && display.renderedView == display.view && + (display.updateLineNumbers == null || display.updateLineNumbers >= display.viewTo)) + return false; + + // For big changes, we hide the enclosing element during the + // update, since that speeds up the operations on most browsers. + var focused = activeElt(); + if (toUpdate > 4) display.lineDiv.style.display = "none"; + patchDisplay(cm, display.updateLineNumbers, update.dims); + if (toUpdate > 4) display.lineDiv.style.display = ""; + display.renderedView = display.view; + // There might have been a widget with a focused element that got + // hidden or updated, if so re-focus it. + if (focused && activeElt() != focused && focused.offsetHeight) focused.focus(); + + // Prevent selection and cursors from interfering with the scroll + // width and height. + removeChildren(display.cursorDiv); + removeChildren(display.selectionDiv); + display.gutters.style.height = 0; + + if (different) { + display.lastWrapHeight = update.wrapperHeight; + display.lastWrapWidth = update.wrapperWidth; + startWorker(cm, 400); + } + + display.updateLineNumbers = null; + + return true; + } + + function postUpdateDisplay(cm, update) { + var force = update.force, viewport = update.viewport; + for (var first = true;; first = false) { + if (first && cm.options.lineWrapping && update.oldDisplayWidth != displayWidth(cm)) { + force = true; + } else { + force = false; + // Clip forced viewport to actual scrollable area. + if (viewport && viewport.top != null) + viewport = {top: Math.min(cm.doc.height + paddingVert(cm.display) - displayHeight(cm), viewport.top)}; + // Updated line heights might result in the drawn area not + // actually covering the viewport. Keep looping until it does. + update.visible = visibleLines(cm.display, cm.doc, viewport); + if (update.visible.from >= cm.display.viewFrom && update.visible.to <= cm.display.viewTo) + break; + } + if (!updateDisplayIfNeeded(cm, update)) break; + updateHeightsInViewport(cm); + var barMeasure = measureForScrollbars(cm); + updateSelection(cm); + setDocumentHeight(cm, barMeasure); + updateScrollbars(cm, barMeasure); + } + + update.signal(cm, "update", cm); + if (cm.display.viewFrom != cm.display.reportedViewFrom || cm.display.viewTo != cm.display.reportedViewTo) { + update.signal(cm, "viewportChange", cm, cm.display.viewFrom, cm.display.viewTo); + cm.display.reportedViewFrom = cm.display.viewFrom; cm.display.reportedViewTo = cm.display.viewTo; + } + } + + function updateDisplaySimple(cm, viewport) { + var update = new DisplayUpdate(cm, viewport); + if (updateDisplayIfNeeded(cm, update)) { + updateHeightsInViewport(cm); + postUpdateDisplay(cm, update); + var barMeasure = measureForScrollbars(cm); + updateSelection(cm); + setDocumentHeight(cm, barMeasure); + updateScrollbars(cm, barMeasure); + update.finish(); + } + } + + function setDocumentHeight(cm, measure) { + cm.display.sizer.style.minHeight = measure.docHeight + "px"; + var total = measure.docHeight + cm.display.barHeight; + cm.display.heightForcer.style.top = total + "px"; + cm.display.gutters.style.height = Math.max(total + scrollGap(cm), measure.clientHeight) + "px"; + } + + // Read the actual heights of the rendered lines, and update their + // stored heights to match. + function updateHeightsInViewport(cm) { + var display = cm.display; + var prevBottom = display.lineDiv.offsetTop; + for (var i = 0; i < display.view.length; i++) { + var cur = display.view[i], height; + if (cur.hidden) continue; + if (ie && ie_version < 8) { + var bot = cur.node.offsetTop + cur.node.offsetHeight; + height = bot - prevBottom; + prevBottom = bot; + } else { + var box = cur.node.getBoundingClientRect(); + height = box.bottom - box.top; + } + var diff = cur.line.height - height; + if (height < 2) height = textHeight(display); + if (diff > .001 || diff < -.001) { + updateLineHeight(cur.line, height); + updateWidgetHeight(cur.line); + if (cur.rest) for (var j = 0; j < cur.rest.length; j++) + updateWidgetHeight(cur.rest[j]); + } + } + } + + // Read and store the height of line widgets associated with the + // given line. + function updateWidgetHeight(line) { + if (line.widgets) for (var i = 0; i < line.widgets.length; ++i) + line.widgets[i].height = line.widgets[i].node.offsetHeight; + } + + // Do a bulk-read of the DOM positions and sizes needed to draw the + // view, so that we don't interleave reading and writing to the DOM. + function getDimensions(cm) { + var d = cm.display, left = {}, width = {}; + var gutterLeft = d.gutters.clientLeft; + for (var n = d.gutters.firstChild, i = 0; n; n = n.nextSibling, ++i) { + left[cm.options.gutters[i]] = n.offsetLeft + n.clientLeft + gutterLeft; + width[cm.options.gutters[i]] = n.clientWidth; + } + return {fixedPos: compensateForHScroll(d), + gutterTotalWidth: d.gutters.offsetWidth, + gutterLeft: left, + gutterWidth: width, + wrapperWidth: d.wrapper.clientWidth}; + } + + // Sync the actual display DOM structure with display.view, removing + // nodes for lines that are no longer in view, and creating the ones + // that are not there yet, and updating the ones that are out of + // date. + function patchDisplay(cm, updateNumbersFrom, dims) { + var display = cm.display, lineNumbers = cm.options.lineNumbers; + var container = display.lineDiv, cur = container.firstChild; + + function rm(node) { + var next = node.nextSibling; + // Works around a throw-scroll bug in OS X Webkit + if (webkit && mac && cm.display.currentWheelTarget == node) + node.style.display = "none"; + else + node.parentNode.removeChild(node); + return next; + } + + var view = display.view, lineN = display.viewFrom; + // Loop over the elements in the view, syncing cur (the DOM nodes + // in display.lineDiv) with the view as we go. + for (var i = 0; i < view.length; i++) { + var lineView = view[i]; + if (lineView.hidden) { + } else if (!lineView.node || lineView.node.parentNode != container) { // Not drawn yet + var node = buildLineElement(cm, lineView, lineN, dims); + container.insertBefore(node, cur); + } else { // Already drawn + while (cur != lineView.node) cur = rm(cur); + var updateNumber = lineNumbers && updateNumbersFrom != null && + updateNumbersFrom <= lineN && lineView.lineNumber; + if (lineView.changes) { + if (indexOf(lineView.changes, "gutter") > -1) updateNumber = false; + updateLineForChanges(cm, lineView, lineN, dims); + } + if (updateNumber) { + removeChildren(lineView.lineNumber); + lineView.lineNumber.appendChild(document.createTextNode(lineNumberFor(cm.options, lineN))); + } + cur = lineView.node.nextSibling; + } + lineN += lineView.size; + } + while (cur) cur = rm(cur); + } + + // When an aspect of a line changes, a string is added to + // lineView.changes. This updates the relevant part of the line's + // DOM structure. + function updateLineForChanges(cm, lineView, lineN, dims) { + for (var j = 0; j < lineView.changes.length; j++) { + var type = lineView.changes[j]; + if (type == "text") updateLineText(cm, lineView); + else if (type == "gutter") updateLineGutter(cm, lineView, lineN, dims); + else if (type == "class") updateLineClasses(lineView); + else if (type == "widget") updateLineWidgets(cm, lineView, dims); + } + lineView.changes = null; + } + + // Lines with gutter elements, widgets or a background class need to + // be wrapped, and have the extra elements added to the wrapper div + function ensureLineWrapped(lineView) { + if (lineView.node == lineView.text) { + lineView.node = elt("div", null, null, "position: relative"); + if (lineView.text.parentNode) + lineView.text.parentNode.replaceChild(lineView.node, lineView.text); + lineView.node.appendChild(lineView.text); + if (ie && ie_version < 8) lineView.node.style.zIndex = 2; + } + return lineView.node; + } + + function updateLineBackground(lineView) { + var cls = lineView.bgClass ? lineView.bgClass + " " + (lineView.line.bgClass || "") : lineView.line.bgClass; + if (cls) cls += " CodeMirror-linebackground"; + if (lineView.background) { + if (cls) lineView.background.className = cls; + else { lineView.background.parentNode.removeChild(lineView.background); lineView.background = null; } + } else if (cls) { + var wrap = ensureLineWrapped(lineView); + lineView.background = wrap.insertBefore(elt("div", null, cls), wrap.firstChild); + } + } + + // Wrapper around buildLineContent which will reuse the structure + // in display.externalMeasured when possible. + function getLineContent(cm, lineView) { + var ext = cm.display.externalMeasured; + if (ext && ext.line == lineView.line) { + cm.display.externalMeasured = null; + lineView.measure = ext.measure; + return ext.built; + } + return buildLineContent(cm, lineView); + } + + // Redraw the line's text. Interacts with the background and text + // classes because the mode may output tokens that influence these + // classes. + function updateLineText(cm, lineView) { + var cls = lineView.text.className; + var built = getLineContent(cm, lineView); + if (lineView.text == lineView.node) lineView.node = built.pre; + lineView.text.parentNode.replaceChild(built.pre, lineView.text); + lineView.text = built.pre; + if (built.bgClass != lineView.bgClass || built.textClass != lineView.textClass) { + lineView.bgClass = built.bgClass; + lineView.textClass = built.textClass; + updateLineClasses(lineView); + } else if (cls) { + lineView.text.className = cls; + } + } + + function updateLineClasses(lineView) { + updateLineBackground(lineView); + if (lineView.line.wrapClass) + ensureLineWrapped(lineView).className = lineView.line.wrapClass; + else if (lineView.node != lineView.text) + lineView.node.className = ""; + var textClass = lineView.textClass ? lineView.textClass + " " + (lineView.line.textClass || "") : lineView.line.textClass; + lineView.text.className = textClass || ""; + } + + function updateLineGutter(cm, lineView, lineN, dims) { + if (lineView.gutter) { + lineView.node.removeChild(lineView.gutter); + lineView.gutter = null; + } + var markers = lineView.line.gutterMarkers; + if (cm.options.lineNumbers || markers) { + var wrap = ensureLineWrapped(lineView); + var gutterWrap = lineView.gutter = elt("div", null, "CodeMirror-gutter-wrapper", "left: " + + (cm.options.fixedGutter ? dims.fixedPos : -dims.gutterTotalWidth) + + "px; width: " + dims.gutterTotalWidth + "px"); + cm.display.input.setUneditable(gutterWrap); + wrap.insertBefore(gutterWrap, lineView.text); + if (lineView.line.gutterClass) + gutterWrap.className += " " + lineView.line.gutterClass; + if (cm.options.lineNumbers && (!markers || !markers["CodeMirror-linenumbers"])) + lineView.lineNumber = gutterWrap.appendChild( + elt("div", lineNumberFor(cm.options, lineN), + "CodeMirror-linenumber CodeMirror-gutter-elt", + "left: " + dims.gutterLeft["CodeMirror-linenumbers"] + "px; width: " + + cm.display.lineNumInnerWidth + "px")); + if (markers) for (var k = 0; k < cm.options.gutters.length; ++k) { + var id = cm.options.gutters[k], found = markers.hasOwnProperty(id) && markers[id]; + if (found) + gutterWrap.appendChild(elt("div", [found], "CodeMirror-gutter-elt", "left: " + + dims.gutterLeft[id] + "px; width: " + dims.gutterWidth[id] + "px")); + } + } + } + + function updateLineWidgets(cm, lineView, dims) { + if (lineView.alignable) lineView.alignable = null; + for (var node = lineView.node.firstChild, next; node; node = next) { + var next = node.nextSibling; + if (node.className == "CodeMirror-linewidget") + lineView.node.removeChild(node); + } + insertLineWidgets(cm, lineView, dims); + } + + // Build a line's DOM representation from scratch + function buildLineElement(cm, lineView, lineN, dims) { + var built = getLineContent(cm, lineView); + lineView.text = lineView.node = built.pre; + if (built.bgClass) lineView.bgClass = built.bgClass; + if (built.textClass) lineView.textClass = built.textClass; + + updateLineClasses(lineView); + updateLineGutter(cm, lineView, lineN, dims); + insertLineWidgets(cm, lineView, dims); + return lineView.node; + } + + // A lineView may contain multiple logical lines (when merged by + // collapsed spans). The widgets for all of them need to be drawn. + function insertLineWidgets(cm, lineView, dims) { + insertLineWidgetsFor(cm, lineView.line, lineView, dims, true); + if (lineView.rest) for (var i = 0; i < lineView.rest.length; i++) + insertLineWidgetsFor(cm, lineView.rest[i], lineView, dims, false); + } + + function insertLineWidgetsFor(cm, line, lineView, dims, allowAbove) { + if (!line.widgets) return; + var wrap = ensureLineWrapped(lineView); + for (var i = 0, ws = line.widgets; i < ws.length; ++i) { + var widget = ws[i], node = elt("div", [widget.node], "CodeMirror-linewidget"); + if (!widget.handleMouseEvents) node.setAttribute("cm-ignore-events", "true"); + positionLineWidget(widget, node, lineView, dims); + cm.display.input.setUneditable(node); + if (allowAbove && widget.above) + wrap.insertBefore(node, lineView.gutter || lineView.text); + else + wrap.appendChild(node); + signalLater(widget, "redraw"); + } + } + + function positionLineWidget(widget, node, lineView, dims) { + if (widget.noHScroll) { + (lineView.alignable || (lineView.alignable = [])).push(node); + var width = dims.wrapperWidth; + node.style.left = dims.fixedPos + "px"; + if (!widget.coverGutter) { + width -= dims.gutterTotalWidth; + node.style.paddingLeft = dims.gutterTotalWidth + "px"; + } + node.style.width = width + "px"; + } + if (widget.coverGutter) { + node.style.zIndex = 5; + node.style.position = "relative"; + if (!widget.noHScroll) node.style.marginLeft = -dims.gutterTotalWidth + "px"; + } + } + + // POSITION OBJECT + + // A Pos instance represents a position within the text. + var Pos = CodeMirror.Pos = function(line, ch) { + if (!(this instanceof Pos)) return new Pos(line, ch); + this.line = line; this.ch = ch; + }; + + // Compare two positions, return 0 if they are the same, a negative + // number when a is less, and a positive number otherwise. + var cmp = CodeMirror.cmpPos = function(a, b) { return a.line - b.line || a.ch - b.ch; }; + + function copyPos(x) {return Pos(x.line, x.ch);} + function maxPos(a, b) { return cmp(a, b) < 0 ? b : a; } + function minPos(a, b) { return cmp(a, b) < 0 ? a : b; } + + // INPUT HANDLING + + function ensureFocus(cm) { + if (!cm.state.focused) { cm.display.input.focus(); onFocus(cm); } + } + + function isReadOnly(cm) { + return cm.options.readOnly || cm.doc.cantEdit; + } + + // This will be set to an array of strings when copying, so that, + // when pasting, we know what kind of selections the copied text + // was made out of. + var lastCopied = null; + + function applyTextInput(cm, inserted, deleted, sel) { + var doc = cm.doc; + cm.display.shift = false; + if (!sel) sel = doc.sel; + + var textLines = splitLines(inserted), multiPaste = null; + // When pasing N lines into N selections, insert one line per selection + if (cm.state.pasteIncoming && sel.ranges.length > 1) { + if (lastCopied && lastCopied.join("\n") == inserted) + multiPaste = sel.ranges.length % lastCopied.length == 0 && map(lastCopied, splitLines); + else if (textLines.length == sel.ranges.length) + multiPaste = map(textLines, function(l) { return [l]; }); + } + + // Normal behavior is to insert the new text into every selection + for (var i = sel.ranges.length - 1; i >= 0; i--) { + var range = sel.ranges[i]; + var from = range.from(), to = range.to(); + if (range.empty()) { + if (deleted && deleted > 0) // Handle deletion + from = Pos(from.line, from.ch - deleted); + else if (cm.state.overwrite && !cm.state.pasteIncoming) // Handle overwrite + to = Pos(to.line, Math.min(getLine(doc, to.line).text.length, to.ch + lst(textLines).length)); + } + var updateInput = cm.curOp.updateInput; + var changeEvent = {from: from, to: to, text: multiPaste ? multiPaste[i % multiPaste.length] : textLines, + origin: cm.state.pasteIncoming ? "paste" : cm.state.cutIncoming ? "cut" : "+input"}; + makeChange(cm.doc, changeEvent); + signalLater(cm, "inputRead", cm, changeEvent); + // When an 'electric' character is inserted, immediately trigger a reindent + if (inserted && !cm.state.pasteIncoming && cm.options.electricChars && + cm.options.smartIndent && range.head.ch < 100 && + (!i || sel.ranges[i - 1].head.line != range.head.line)) { + var mode = cm.getModeAt(range.head); + var end = changeEnd(changeEvent); + if (mode.electricChars) { + for (var j = 0; j < mode.electricChars.length; j++) + if (inserted.indexOf(mode.electricChars.charAt(j)) > -1) { + indentLine(cm, end.line, "smart"); + break; + } + } else if (mode.electricInput) { + if (mode.electricInput.test(getLine(doc, end.line).text.slice(0, end.ch))) + indentLine(cm, end.line, "smart"); + } + } + } + ensureCursorVisible(cm); + cm.curOp.updateInput = updateInput; + cm.curOp.typing = true; + cm.state.pasteIncoming = cm.state.cutIncoming = false; + } + + function copyableRanges(cm) { + var text = [], ranges = []; + for (var i = 0; i < cm.doc.sel.ranges.length; i++) { + var line = cm.doc.sel.ranges[i].head.line; + var lineRange = {anchor: Pos(line, 0), head: Pos(line + 1, 0)}; + ranges.push(lineRange); + text.push(cm.getRange(lineRange.anchor, lineRange.head)); + } + return {text: text, ranges: ranges}; + } + + function disableBrowserMagic(field) { + field.setAttribute("autocorrect", "off"); + field.setAttribute("autocapitalize", "off"); + field.setAttribute("spellcheck", "false"); + } + + // TEXTAREA INPUT STYLE + + function TextareaInput(cm) { + this.cm = cm; + // See input.poll and input.reset + this.prevInput = ""; + + // Flag that indicates whether we expect input to appear real soon + // now (after some event like 'keypress' or 'input') and are + // polling intensively. + this.pollingFast = false; + // Self-resetting timeout for the poller + this.polling = new Delayed(); + // Tracks when input.reset has punted to just putting a short + // string into the textarea instead of the full selection. + this.inaccurateSelection = false; + // Used to work around IE issue with selection being forgotten when focus moves away from textarea + this.hasSelection = false; + }; + + function hiddenTextarea() { + var te = elt("textarea", null, null, "position: absolute; padding: 0; width: 1px; height: 1em; outline: none"); + var div = elt("div", [te], null, "overflow: hidden; position: relative; width: 3px; height: 0px;"); + // The textarea is kept positioned near the cursor to prevent the + // fact that it'll be scrolled into view on input from scrolling + // our fake cursor out of view. On webkit, when wrap=off, paste is + // very slow. So make the area wide instead. + if (webkit) te.style.width = "1000px"; + else te.setAttribute("wrap", "off"); + // If border: 0; -- iOS fails to open keyboard (issue #1287) + if (ios) te.style.border = "1px solid black"; + disableBrowserMagic(te); + return div; + } + + TextareaInput.prototype = copyObj({ + init: function(display) { + var input = this, cm = this.cm; + + // Wraps and hides input textarea + var div = this.wrapper = hiddenTextarea(); + // The semihidden textarea that is focused when the editor is + // focused, and receives input. + var te = this.textarea = div.firstChild; + display.wrapper.insertBefore(div, display.wrapper.firstChild); + + // Needed to hide big blue blinking cursor on Mobile Safari (doesn't seem to work in iOS 8 anymore) + if (ios) te.style.width = "0px"; + + on(te, "input", function() { + if (ie && ie_version >= 9 && input.hasSelection) input.hasSelection = null; + input.poll(); + }); + + on(te, "paste", function() { + // Workaround for webkit bug https://bugs.webkit.org/show_bug.cgi?id=90206 + // Add a char to the end of textarea before paste occur so that + // selection doesn't span to the end of textarea. + if (webkit && !cm.state.fakedLastChar && !(new Date - cm.state.lastMiddleDown < 200)) { + var start = te.selectionStart, end = te.selectionEnd; + te.value += "$"; + // The selection end needs to be set before the start, otherwise there + // can be an intermediate non-empty selection between the two, which + // can override the middle-click paste buffer on linux and cause the + // wrong thing to get pasted. + te.selectionEnd = end; + te.selectionStart = start; + cm.state.fakedLastChar = true; + } + cm.state.pasteIncoming = true; + input.fastPoll(); + }); + + function prepareCopyCut(e) { + if (cm.somethingSelected()) { + lastCopied = cm.getSelections(); + if (input.inaccurateSelection) { + input.prevInput = ""; + input.inaccurateSelection = false; + te.value = lastCopied.join("\n"); + selectInput(te); + } + } else if (!cm.options.lineWiseCopyCut) { + return; + } else { + var ranges = copyableRanges(cm); + lastCopied = ranges.text; + if (e.type == "cut") { + cm.setSelections(ranges.ranges, null, sel_dontScroll); + } else { + input.prevInput = ""; + te.value = ranges.text.join("\n"); + selectInput(te); + } + } + if (e.type == "cut") cm.state.cutIncoming = true; + } + on(te, "cut", prepareCopyCut); + on(te, "copy", prepareCopyCut); + + on(display.scroller, "paste", function(e) { + if (eventInWidget(display, e)) return; + cm.state.pasteIncoming = true; + input.focus(); + }); + + // Prevent normal selection in the editor (we handle our own) + on(display.lineSpace, "selectstart", function(e) { + if (!eventInWidget(display, e)) e_preventDefault(e); + }); + }, + + prepareSelection: function() { + // Redraw the selection and/or cursor + var cm = this.cm, display = cm.display, doc = cm.doc; + var result = prepareSelection(cm); + + // Move the hidden textarea near the cursor to prevent scrolling artifacts + if (cm.options.moveInputWithCursor) { + var headPos = cursorCoords(cm, doc.sel.primary().head, "div"); + var wrapOff = display.wrapper.getBoundingClientRect(), lineOff = display.lineDiv.getBoundingClientRect(); + result.teTop = Math.max(0, Math.min(display.wrapper.clientHeight - 10, + headPos.top + lineOff.top - wrapOff.top)); + result.teLeft = Math.max(0, Math.min(display.wrapper.clientWidth - 10, + headPos.left + lineOff.left - wrapOff.left)); + } + + return result; + }, + + showSelection: function(drawn) { + var cm = this.cm, display = cm.display; + removeChildrenAndAdd(display.cursorDiv, drawn.cursors); + removeChildrenAndAdd(display.selectionDiv, drawn.selection); + if (drawn.teTop != null) { + this.wrapper.style.top = drawn.teTop + "px"; + this.wrapper.style.left = drawn.teLeft + "px"; + } + }, + + // Reset the input to correspond to the selection (or to be empty, + // when not typing and nothing is selected) + reset: function(typing) { + if (this.contextMenuPending) return; + var minimal, selected, cm = this.cm, doc = cm.doc; + if (cm.somethingSelected()) { + this.prevInput = ""; + var range = doc.sel.primary(); + minimal = hasCopyEvent && + (range.to().line - range.from().line > 100 || (selected = cm.getSelection()).length > 1000); + var content = minimal ? "-" : selected || cm.getSelection(); + this.textarea.value = content; + if (cm.state.focused) selectInput(this.textarea); + if (ie && ie_version >= 9) this.hasSelection = content; + } else if (!typing) { + this.prevInput = this.textarea.value = ""; + if (ie && ie_version >= 9) this.hasSelection = null; + } + this.inaccurateSelection = minimal; + }, + + getField: function() { return this.textarea; }, + + supportsTouch: function() { return false; }, + + focus: function() { + if (this.cm.options.readOnly != "nocursor" && (!mobile || activeElt() != this.textarea)) { + try { this.textarea.focus(); } + catch (e) {} // IE8 will throw if the textarea is display: none or not in DOM + } + }, + + blur: function() { this.textarea.blur(); }, + + resetPosition: function() { + this.wrapper.style.top = this.wrapper.style.left = 0; + }, + + receivedFocus: function() { this.slowPoll(); }, + + // Poll for input changes, using the normal rate of polling. This + // runs as long as the editor is focused. + slowPoll: function() { + var input = this; + if (input.pollingFast) return; + input.polling.set(this.cm.options.pollInterval, function() { + input.poll(); + if (input.cm.state.focused) input.slowPoll(); + }); + }, + + // When an event has just come in that is likely to add or change + // something in the input textarea, we poll faster, to ensure that + // the change appears on the screen quickly. + fastPoll: function() { + var missed = false, input = this; + input.pollingFast = true; + function p() { + var changed = input.poll(); + if (!changed && !missed) {missed = true; input.polling.set(60, p);} + else {input.pollingFast = false; input.slowPoll();} + } + input.polling.set(20, p); + }, + + // Read input from the textarea, and update the document to match. + // When something is selected, it is present in the textarea, and + // selected (unless it is huge, in which case a placeholder is + // used). When nothing is selected, the cursor sits after previously + // seen text (can be empty), which is stored in prevInput (we must + // not reset the textarea when typing, because that breaks IME). + poll: function() { + var cm = this.cm, input = this.textarea, prevInput = this.prevInput; + // Since this is called a *lot*, try to bail out as cheaply as + // possible when it is clear that nothing happened. hasSelection + // will be the case when there is a lot of text in the textarea, + // in which case reading its value would be expensive. + if (!cm.state.focused || (hasSelection(input) && !prevInput) || + isReadOnly(cm) || cm.options.disableInput || cm.state.keySeq) + return false; + // See paste handler for more on the fakedLastChar kludge + if (cm.state.pasteIncoming && cm.state.fakedLastChar) { + input.value = input.value.substring(0, input.value.length - 1); + cm.state.fakedLastChar = false; + } + var text = input.value; + // If nothing changed, bail. + if (text == prevInput && !cm.somethingSelected()) return false; + // Work around nonsensical selection resetting in IE9/10, and + // inexplicable appearance of private area unicode characters on + // some key combos in Mac (#2689). + if (ie && ie_version >= 9 && this.hasSelection === text || + mac && /[\uf700-\uf7ff]/.test(text)) { + cm.display.input.reset(); + return false; + } + + if (cm.doc.sel == cm.display.selForContextMenu) { + var first = text.charCodeAt(0); + if (first == 0x200b && !prevInput) prevInput = "\u200b"; + if (first == 0x21da) { this.reset(); return this.cm.execCommand("undo"); } + } + // Find the part of the input that is actually new + var same = 0, l = Math.min(prevInput.length, text.length); + while (same < l && prevInput.charCodeAt(same) == text.charCodeAt(same)) ++same; + + var self = this; + runInOp(cm, function() { + applyTextInput(cm, text.slice(same), prevInput.length - same); + + // Don't leave long text in the textarea, since it makes further polling slow + if (text.length > 1000 || text.indexOf("\n") > -1) input.value = self.prevInput = ""; + else self.prevInput = text; + }); + return true; + }, + + ensurePolled: function() { + if (this.pollingFast && this.poll()) this.pollingFast = false; + }, + + onKeyPress: function() { + if (ie && ie_version >= 9) this.hasSelection = null; + this.fastPoll(); + }, + + onContextMenu: function(e) { + var input = this, cm = input.cm, display = cm.display, te = input.textarea; + var pos = posFromMouse(cm, e), scrollPos = display.scroller.scrollTop; + if (!pos || presto) return; // Opera is difficult. + + // Reset the current text selection only if the click is done outside of the selection + // and 'resetSelectionOnContextMenu' option is true. + var reset = cm.options.resetSelectionOnContextMenu; + if (reset && cm.doc.sel.contains(pos) == -1) + operation(cm, setSelection)(cm.doc, simpleSelection(pos), sel_dontScroll); + + var oldCSS = te.style.cssText; + input.wrapper.style.position = "absolute"; + te.style.cssText = "position: fixed; width: 30px; height: 30px; top: " + (e.clientY - 5) + + "px; left: " + (e.clientX - 5) + "px; z-index: 1000; background: " + + (ie ? "rgba(255, 255, 255, .05)" : "transparent") + + "; outline: none; border-width: 0; outline: none; overflow: hidden; opacity: .05; filter: alpha(opacity=5);"; + if (webkit) var oldScrollY = window.scrollY; // Work around Chrome issue (#2712) + display.input.focus(); + if (webkit) window.scrollTo(null, oldScrollY); + display.input.reset(); + // Adds "Select all" to context menu in FF + if (!cm.somethingSelected()) te.value = input.prevInput = " "; + input.contextMenuPending = true; + display.selForContextMenu = cm.doc.sel; + clearTimeout(display.detectingSelectAll); + + // Select-all will be greyed out if there's nothing to select, so + // this adds a zero-width space so that we can later check whether + // it got selected. + function prepareSelectAllHack() { + if (te.selectionStart != null) { + var selected = cm.somethingSelected(); + var extval = "\u200b" + (selected ? te.value : ""); + te.value = "\u21da"; // Used to catch context-menu undo + te.value = extval; + input.prevInput = selected ? "" : "\u200b"; + te.selectionStart = 1; te.selectionEnd = extval.length; + // Re-set this, in case some other handler touched the + // selection in the meantime. + display.selForContextMenu = cm.doc.sel; + } + } + function rehide() { + input.contextMenuPending = false; + input.wrapper.style.position = "relative"; + te.style.cssText = oldCSS; + if (ie && ie_version < 9) display.scrollbars.setScrollTop(display.scroller.scrollTop = scrollPos); + + // Try to detect the user choosing select-all + if (te.selectionStart != null) { + if (!ie || (ie && ie_version < 9)) prepareSelectAllHack(); + var i = 0, poll = function() { + if (display.selForContextMenu == cm.doc.sel && te.selectionStart == 0 && + te.selectionEnd > 0 && input.prevInput == "\u200b") + operation(cm, commands.selectAll)(cm); + else if (i++ < 10) display.detectingSelectAll = setTimeout(poll, 500); + else display.input.reset(); + }; + display.detectingSelectAll = setTimeout(poll, 200); + } + } + + if (ie && ie_version >= 9) prepareSelectAllHack(); + if (captureRightClick) { + e_stop(e); + var mouseup = function() { + off(window, "mouseup", mouseup); + setTimeout(rehide, 20); + }; + on(window, "mouseup", mouseup); + } else { + setTimeout(rehide, 50); + } + }, + + setUneditable: nothing, + + needsContentAttribute: false + }, TextareaInput.prototype); + + // CONTENTEDITABLE INPUT STYLE + + function ContentEditableInput(cm) { + this.cm = cm; + this.lastAnchorNode = this.lastAnchorOffset = this.lastFocusNode = this.lastFocusOffset = null; + this.polling = new Delayed(); + this.gracePeriod = false; + } + + ContentEditableInput.prototype = copyObj({ + init: function(display) { + var input = this, cm = input.cm; + var div = input.div = display.lineDiv; + div.contentEditable = "true"; + disableBrowserMagic(div); + + on(div, "paste", function(e) { + var pasted = e.clipboardData && e.clipboardData.getData("text/plain"); + if (pasted) { + e.preventDefault(); + cm.replaceSelection(pasted, null, "paste"); + } + }); + + on(div, "compositionstart", function(e) { + var data = e.data; + input.composing = {sel: cm.doc.sel, data: data, startData: data}; + if (!data) return; + var prim = cm.doc.sel.primary(); + var line = cm.getLine(prim.head.line); + var found = line.indexOf(data, Math.max(0, prim.head.ch - data.length)); + if (found > -1 && found <= prim.head.ch) + input.composing.sel = simpleSelection(Pos(prim.head.line, found), + Pos(prim.head.line, found + data.length)); + }); + on(div, "compositionupdate", function(e) { + input.composing.data = e.data; + }); + on(div, "compositionend", function(e) { + var ours = input.composing; + if (!ours) return; + if (e.data != ours.startData && !/\u200b/.test(e.data)) + ours.data = e.data; + // Need a small delay to prevent other code (input event, + // selection polling) from doing damage when fired right after + // compositionend. + setTimeout(function() { + if (!ours.handled) + input.applyComposition(ours); + if (input.composing == ours) + input.composing = null; + }, 50); + }); + + on(div, "touchstart", function() { + input.forceCompositionEnd(); + }); + + on(div, "input", function() { + if (input.composing) return; + if (!input.pollContent()) + runInOp(input.cm, function() {regChange(cm);}); + }); + + function onCopyCut(e) { + if (cm.somethingSelected()) { + lastCopied = cm.getSelections(); + if (e.type == "cut") cm.replaceSelection("", null, "cut"); + } else if (!cm.options.lineWiseCopyCut) { + return; + } else { + var ranges = copyableRanges(cm); + lastCopied = ranges.text; + if (e.type == "cut") { + cm.operation(function() { + cm.setSelections(ranges.ranges, 0, sel_dontScroll); + cm.replaceSelection("", null, "cut"); + }); + } + } + // iOS exposes the clipboard API, but seems to discard content inserted into it + if (e.clipboardData && !ios) { + e.preventDefault(); + e.clipboardData.clearData(); + e.clipboardData.setData("text/plain", lastCopied.join("\n")); + } else { + // Old-fashioned briefly-focus-a-textarea hack + var kludge = hiddenTextarea(), te = kludge.firstChild; + cm.display.lineSpace.insertBefore(kludge, cm.display.lineSpace.firstChild); + te.value = lastCopied.join("\n"); + var hadFocus = document.activeElement; + selectInput(te); + setTimeout(function() { + cm.display.lineSpace.removeChild(kludge); + hadFocus.focus(); + }, 50); + } + } + on(div, "copy", onCopyCut); + on(div, "cut", onCopyCut); + }, + + prepareSelection: function() { + var result = prepareSelection(this.cm, false); + result.focus = this.cm.state.focused; + return result; + }, + + showSelection: function(info) { + if (!info || !this.cm.display.view.length) return; + if (info.focus) this.showPrimarySelection(); + this.showMultipleSelections(info); + }, + + showPrimarySelection: function() { + var sel = window.getSelection(), prim = this.cm.doc.sel.primary(); + var curAnchor = domToPos(this.cm, sel.anchorNode, sel.anchorOffset); + var curFocus = domToPos(this.cm, sel.focusNode, sel.focusOffset); + if (curAnchor && !curAnchor.bad && curFocus && !curFocus.bad && + cmp(minPos(curAnchor, curFocus), prim.from()) == 0 && + cmp(maxPos(curAnchor, curFocus), prim.to()) == 0) + return; + + var start = posToDOM(this.cm, prim.from()); + var end = posToDOM(this.cm, prim.to()); + if (!start && !end) return; + + var view = this.cm.display.view; + var old = sel.rangeCount && sel.getRangeAt(0); + if (!start) { + start = {node: view[0].measure.map[2], offset: 0}; + } else if (!end) { // FIXME dangerously hacky + var measure = view[view.length - 1].measure; + var map = measure.maps ? measure.maps[measure.maps.length - 1] : measure.map; + end = {node: map[map.length - 1], offset: map[map.length - 2] - map[map.length - 3]}; + } + + try { var rng = range(start.node, start.offset, end.offset, end.node); } + catch(e) {} // Our model of the DOM might be outdated, in which case the range we try to set can be impossible + if (rng) { + sel.removeAllRanges(); + sel.addRange(rng); + if (old && sel.anchorNode == null) sel.addRange(old); + else if (gecko) this.startGracePeriod(); + } + this.rememberSelection(); + }, + + startGracePeriod: function() { + var input = this; + clearTimeout(this.gracePeriod); + this.gracePeriod = setTimeout(function() { + input.gracePeriod = false; + if (input.selectionChanged()) + input.cm.operation(function() { input.cm.curOp.selectionChanged = true; }); + }, 20); + }, + + showMultipleSelections: function(info) { + removeChildrenAndAdd(this.cm.display.cursorDiv, info.cursors); + removeChildrenAndAdd(this.cm.display.selectionDiv, info.selection); + }, + + rememberSelection: function() { + var sel = window.getSelection(); + this.lastAnchorNode = sel.anchorNode; this.lastAnchorOffset = sel.anchorOffset; + this.lastFocusNode = sel.focusNode; this.lastFocusOffset = sel.focusOffset; + }, + + selectionInEditor: function() { + var sel = window.getSelection(); + if (!sel.rangeCount) return false; + var node = sel.getRangeAt(0).commonAncestorContainer; + return contains(this.div, node); + }, + + focus: function() { + if (this.cm.options.readOnly != "nocursor") this.div.focus(); + }, + blur: function() { this.div.blur(); }, + getField: function() { return this.div; }, + + supportsTouch: function() { return true; }, + + receivedFocus: function() { + var input = this; + if (this.selectionInEditor()) + this.pollSelection(); + else + runInOp(this.cm, function() { input.cm.curOp.selectionChanged = true; }); + + function poll() { + if (input.cm.state.focused) { + input.pollSelection(); + input.polling.set(input.cm.options.pollInterval, poll); + } + } + this.polling.set(this.cm.options.pollInterval, poll); + }, + + selectionChanged: function() { + var sel = window.getSelection(); + return sel.anchorNode != this.lastAnchorNode || sel.anchorOffset != this.lastAnchorOffset || + sel.focusNode != this.lastFocusNode || sel.focusOffset != this.lastFocusOffset; + }, + + pollSelection: function() { + if (!this.composing && !this.gracePeriod && this.selectionChanged()) { + var sel = window.getSelection(), cm = this.cm; + this.rememberSelection(); + var anchor = domToPos(cm, sel.anchorNode, sel.anchorOffset); + var head = domToPos(cm, sel.focusNode, sel.focusOffset); + if (anchor && head) runInOp(cm, function() { + setSelection(cm.doc, simpleSelection(anchor, head), sel_dontScroll); + if (anchor.bad || head.bad) cm.curOp.selectionChanged = true; + }); + } + }, + + pollContent: function() { + var cm = this.cm, display = cm.display, sel = cm.doc.sel.primary(); + var from = sel.from(), to = sel.to(); + if (from.line < display.viewFrom || to.line > display.viewTo - 1) return false; + + var fromIndex; + if (from.line == display.viewFrom || (fromIndex = findViewIndex(cm, from.line)) == 0) { + var fromLine = lineNo(display.view[0].line); + var fromNode = display.view[0].node; + } else { + var fromLine = lineNo(display.view[fromIndex].line); + var fromNode = display.view[fromIndex - 1].node.nextSibling; + } + var toIndex = findViewIndex(cm, to.line); + if (toIndex == display.view.length - 1) { + var toLine = display.viewTo - 1; + var toNode = display.view[toIndex].node; + } else { + var toLine = lineNo(display.view[toIndex + 1].line) - 1; + var toNode = display.view[toIndex + 1].node.previousSibling; + } + + var newText = splitLines(domTextBetween(cm, fromNode, toNode, fromLine, toLine)); + var oldText = getBetween(cm.doc, Pos(fromLine, 0), Pos(toLine, getLine(cm.doc, toLine).text.length)); + while (newText.length > 1 && oldText.length > 1) { + if (lst(newText) == lst(oldText)) { newText.pop(); oldText.pop(); toLine--; } + else if (newText[0] == oldText[0]) { newText.shift(); oldText.shift(); fromLine++; } + else break; + } + + var cutFront = 0, cutEnd = 0; + var newTop = newText[0], oldTop = oldText[0], maxCutFront = Math.min(newTop.length, oldTop.length); + while (cutFront < maxCutFront && newTop.charCodeAt(cutFront) == oldTop.charCodeAt(cutFront)) + ++cutFront; + var newBot = lst(newText), oldBot = lst(oldText); + var maxCutEnd = Math.min(newBot.length - (newText.length == 1 ? cutFront : 0), + oldBot.length - (oldText.length == 1 ? cutFront : 0)); + while (cutEnd < maxCutEnd && + newBot.charCodeAt(newBot.length - cutEnd - 1) == oldBot.charCodeAt(oldBot.length - cutEnd - 1)) + ++cutEnd; + + newText[newText.length - 1] = newBot.slice(0, newBot.length - cutEnd); + newText[0] = newText[0].slice(cutFront); + + var chFrom = Pos(fromLine, cutFront); + var chTo = Pos(toLine, oldText.length ? lst(oldText).length - cutEnd : 0); + if (newText.length > 1 || newText[0] || cmp(chFrom, chTo)) { + replaceRange(cm.doc, newText, chFrom, chTo, "+input"); + return true; + } + }, + + ensurePolled: function() { + this.forceCompositionEnd(); + }, + reset: function() { + this.forceCompositionEnd(); + }, + forceCompositionEnd: function() { + if (!this.composing || this.composing.handled) return; + this.applyComposition(this.composing); + this.composing.handled = true; + this.div.blur(); + this.div.focus(); + }, + applyComposition: function(composing) { + if (composing.data && composing.data != composing.startData) + operation(this.cm, applyTextInput)(this.cm, composing.data, 0, composing.sel); + }, + + setUneditable: function(node) { + node.setAttribute("contenteditable", "false"); + }, + + onKeyPress: function(e) { + e.preventDefault(); + operation(this.cm, applyTextInput)(this.cm, String.fromCharCode(e.charCode == null ? e.keyCode : e.charCode), 0); + }, + + onContextMenu: nothing, + resetPosition: nothing, + + needsContentAttribute: true + }, ContentEditableInput.prototype); + + function posToDOM(cm, pos) { + var view = findViewForLine(cm, pos.line); + if (!view || view.hidden) return null; + var line = getLine(cm.doc, pos.line); + var info = mapFromLineView(view, line, pos.line); + + var order = getOrder(line), side = "left"; + if (order) { + var partPos = getBidiPartAt(order, pos.ch); + side = partPos % 2 ? "right" : "left"; + } + var result = nodeAndOffsetInLineMap(info.map, pos.ch, "left"); + result.offset = result.collapse == "right" ? result.end : result.start; + return result; + } + + function badPos(pos, bad) { if (bad) pos.bad = true; return pos; } + + function domToPos(cm, node, offset) { + var lineNode; + if (node == cm.display.lineDiv) { + lineNode = cm.display.lineDiv.childNodes[offset]; + if (!lineNode) return badPos(cm.clipPos(Pos(cm.display.viewTo - 1)), true); + node = null; offset = 0; + } else { + for (lineNode = node;; lineNode = lineNode.parentNode) { + if (!lineNode || lineNode == cm.display.lineDiv) return null; + if (lineNode.parentNode && lineNode.parentNode == cm.display.lineDiv) break; + } + } + for (var i = 0; i < cm.display.view.length; i++) { + var lineView = cm.display.view[i]; + if (lineView.node == lineNode) + return locateNodeInLineView(lineView, node, offset); + } + } + + function locateNodeInLineView(lineView, node, offset) { + var wrapper = lineView.text.firstChild, bad = false; + if (!node || !contains(wrapper, node)) return badPos(Pos(lineNo(lineView.line), 0), true); + if (node == wrapper) { + bad = true; + node = wrapper.childNodes[offset]; + offset = 0; + if (!node) { + var line = lineView.rest ? lst(lineView.rest) : lineView.line; + return badPos(Pos(lineNo(line), line.text.length), bad); + } + } + + var textNode = node.nodeType == 3 ? node : null, topNode = node; + if (!textNode && node.childNodes.length == 1 && node.firstChild.nodeType == 3) { + textNode = node.firstChild; + if (offset) offset = textNode.nodeValue.length; + } + while (topNode.parentNode != wrapper) topNode = topNode.parentNode; + var measure = lineView.measure, maps = measure.maps; + + function find(textNode, topNode, offset) { + for (var i = -1; i < (maps ? maps.length : 0); i++) { + var map = i < 0 ? measure.map : maps[i]; + for (var j = 0; j < map.length; j += 3) { + var curNode = map[j + 2]; + if (curNode == textNode || curNode == topNode) { + var line = lineNo(i < 0 ? lineView.line : lineView.rest[i]); + var ch = map[j] + offset; + if (offset < 0 || curNode != textNode) ch = map[j + (offset ? 1 : 0)]; + return Pos(line, ch); + } + } + } + } + var found = find(textNode, topNode, offset); + if (found) return badPos(found, bad); + + // FIXME this is all really shaky. might handle the few cases it needs to handle, but likely to cause problems + for (var after = topNode.nextSibling, dist = textNode ? textNode.nodeValue.length - offset : 0; after; after = after.nextSibling) { + found = find(after, after.firstChild, 0); + if (found) + return badPos(Pos(found.line, found.ch - dist), bad); + else + dist += after.textContent.length; + } + for (var before = topNode.previousSibling, dist = offset; before; before = before.previousSibling) { + found = find(before, before.firstChild, -1); + if (found) + return badPos(Pos(found.line, found.ch + dist), bad); + else + dist += after.textContent.length; + } + } + + function domTextBetween(cm, from, to, fromLine, toLine) { + var text = "", closing = false; + function recognizeMarker(id) { return function(marker) { return marker.id == id; }; } + function walk(node) { + if (node.nodeType == 1) { + var cmText = node.getAttribute("cm-text"); + if (cmText != null) { + if (cmText == "") cmText = node.textContent.replace(/\u200b/g, ""); + text += cmText; + return; + } + var markerID = node.getAttribute("cm-marker"), range; + if (markerID) { + var found = cm.findMarks(Pos(fromLine, 0), Pos(toLine + 1, 0), recognizeMarker(+markerID)); + if (found.length && (range = found[0].find())) + text += getBetween(cm.doc, range.from, range.to).join("\n"); + return; + } + if (node.getAttribute("contenteditable") == "false") return; + for (var i = 0; i < node.childNodes.length; i++) + walk(node.childNodes[i]); + if (/^(pre|div|p)$/i.test(node.nodeName)) + closing = true; + } else if (node.nodeType == 3) { + var val = node.nodeValue; + if (!val) return; + if (closing) { + text += "\n"; + closing = false; + } + text += val; + } + } + for (;;) { + walk(from); + if (from == to) break; + from = from.nextSibling; + } + return text; + } + + CodeMirror.inputStyles = {"textarea": TextareaInput, "contenteditable": ContentEditableInput}; + + // SELECTION / CURSOR + + // Selection objects are immutable. A new one is created every time + // the selection changes. A selection is one or more non-overlapping + // (and non-touching) ranges, sorted, and an integer that indicates + // which one is the primary selection (the one that's scrolled into + // view, that getCursor returns, etc). + function Selection(ranges, primIndex) { + this.ranges = ranges; + this.primIndex = primIndex; + } + + Selection.prototype = { + primary: function() { return this.ranges[this.primIndex]; }, + equals: function(other) { + if (other == this) return true; + if (other.primIndex != this.primIndex || other.ranges.length != this.ranges.length) return false; + for (var i = 0; i < this.ranges.length; i++) { + var here = this.ranges[i], there = other.ranges[i]; + if (cmp(here.anchor, there.anchor) != 0 || cmp(here.head, there.head) != 0) return false; + } + return true; + }, + deepCopy: function() { + for (var out = [], i = 0; i < this.ranges.length; i++) + out[i] = new Range(copyPos(this.ranges[i].anchor), copyPos(this.ranges[i].head)); + return new Selection(out, this.primIndex); + }, + somethingSelected: function() { + for (var i = 0; i < this.ranges.length; i++) + if (!this.ranges[i].empty()) return true; + return false; + }, + contains: function(pos, end) { + if (!end) end = pos; + for (var i = 0; i < this.ranges.length; i++) { + var range = this.ranges[i]; + if (cmp(end, range.from()) >= 0 && cmp(pos, range.to()) <= 0) + return i; + } + return -1; + } + }; + + function Range(anchor, head) { + this.anchor = anchor; this.head = head; + } + + Range.prototype = { + from: function() { return minPos(this.anchor, this.head); }, + to: function() { return maxPos(this.anchor, this.head); }, + empty: function() { + return this.head.line == this.anchor.line && this.head.ch == this.anchor.ch; + } + }; + + // Take an unsorted, potentially overlapping set of ranges, and + // build a selection out of it. 'Consumes' ranges array (modifying + // it). + function normalizeSelection(ranges, primIndex) { + var prim = ranges[primIndex]; + ranges.sort(function(a, b) { return cmp(a.from(), b.from()); }); + primIndex = indexOf(ranges, prim); + for (var i = 1; i < ranges.length; i++) { + var cur = ranges[i], prev = ranges[i - 1]; + if (cmp(prev.to(), cur.from()) >= 0) { + var from = minPos(prev.from(), cur.from()), to = maxPos(prev.to(), cur.to()); + var inv = prev.empty() ? cur.from() == cur.head : prev.from() == prev.head; + if (i <= primIndex) --primIndex; + ranges.splice(--i, 2, new Range(inv ? to : from, inv ? from : to)); + } + } + return new Selection(ranges, primIndex); + } + + function simpleSelection(anchor, head) { + return new Selection([new Range(anchor, head || anchor)], 0); + } + + // Most of the external API clips given positions to make sure they + // actually exist within the document. + function clipLine(doc, n) {return Math.max(doc.first, Math.min(n, doc.first + doc.size - 1));} + function clipPos(doc, pos) { + if (pos.line < doc.first) return Pos(doc.first, 0); + var last = doc.first + doc.size - 1; + if (pos.line > last) return Pos(last, getLine(doc, last).text.length); + return clipToLen(pos, getLine(doc, pos.line).text.length); + } + function clipToLen(pos, linelen) { + var ch = pos.ch; + if (ch == null || ch > linelen) return Pos(pos.line, linelen); + else if (ch < 0) return Pos(pos.line, 0); + else return pos; + } + function isLine(doc, l) {return l >= doc.first && l < doc.first + doc.size;} + function clipPosArray(doc, array) { + for (var out = [], i = 0; i < array.length; i++) out[i] = clipPos(doc, array[i]); + return out; + } + + // SELECTION UPDATES + + // The 'scroll' parameter given to many of these indicated whether + // the new cursor position should be scrolled into view after + // modifying the selection. + + // If shift is held or the extend flag is set, extends a range to + // include a given position (and optionally a second position). + // Otherwise, simply returns the range between the given positions. + // Used for cursor motion and such. + function extendRange(doc, range, head, other) { + if (doc.cm && doc.cm.display.shift || doc.extend) { + var anchor = range.anchor; + if (other) { + var posBefore = cmp(head, anchor) < 0; + if (posBefore != (cmp(other, anchor) < 0)) { + anchor = head; + head = other; + } else if (posBefore != (cmp(head, other) < 0)) { + head = other; + } + } + return new Range(anchor, head); + } else { + return new Range(other || head, head); + } + } + + // Extend the primary selection range, discard the rest. + function extendSelection(doc, head, other, options) { + setSelection(doc, new Selection([extendRange(doc, doc.sel.primary(), head, other)], 0), options); + } + + // Extend all selections (pos is an array of selections with length + // equal the number of selections) + function extendSelections(doc, heads, options) { + for (var out = [], i = 0; i < doc.sel.ranges.length; i++) + out[i] = extendRange(doc, doc.sel.ranges[i], heads[i], null); + var newSel = normalizeSelection(out, doc.sel.primIndex); + setSelection(doc, newSel, options); + } + + // Updates a single range in the selection. + function replaceOneSelection(doc, i, range, options) { + var ranges = doc.sel.ranges.slice(0); + ranges[i] = range; + setSelection(doc, normalizeSelection(ranges, doc.sel.primIndex), options); + } + + // Reset the selection to a single range. + function setSimpleSelection(doc, anchor, head, options) { + setSelection(doc, simpleSelection(anchor, head), options); + } + + // Give beforeSelectionChange handlers a change to influence a + // selection update. + function filterSelectionChange(doc, sel) { + var obj = { + ranges: sel.ranges, + update: function(ranges) { + this.ranges = []; + for (var i = 0; i < ranges.length; i++) + this.ranges[i] = new Range(clipPos(doc, ranges[i].anchor), + clipPos(doc, ranges[i].head)); + } + }; + signal(doc, "beforeSelectionChange", doc, obj); + if (doc.cm) signal(doc.cm, "beforeSelectionChange", doc.cm, obj); + if (obj.ranges != sel.ranges) return normalizeSelection(obj.ranges, obj.ranges.length - 1); + else return sel; + } + + function setSelectionReplaceHistory(doc, sel, options) { + var done = doc.history.done, last = lst(done); + if (last && last.ranges) { + done[done.length - 1] = sel; + setSelectionNoUndo(doc, sel, options); + } else { + setSelection(doc, sel, options); + } + } + + // Set a new selection. + function setSelection(doc, sel, options) { + setSelectionNoUndo(doc, sel, options); + addSelectionToHistory(doc, doc.sel, doc.cm ? doc.cm.curOp.id : NaN, options); + } + + function setSelectionNoUndo(doc, sel, options) { + if (hasHandler(doc, "beforeSelectionChange") || doc.cm && hasHandler(doc.cm, "beforeSelectionChange")) + sel = filterSelectionChange(doc, sel); + + var bias = options && options.bias || + (cmp(sel.primary().head, doc.sel.primary().head) < 0 ? -1 : 1); + setSelectionInner(doc, skipAtomicInSelection(doc, sel, bias, true)); + + if (!(options && options.scroll === false) && doc.cm) + ensureCursorVisible(doc.cm); + } + + function setSelectionInner(doc, sel) { + if (sel.equals(doc.sel)) return; + + doc.sel = sel; + + if (doc.cm) { + doc.cm.curOp.updateInput = doc.cm.curOp.selectionChanged = true; + signalCursorActivity(doc.cm); + } + signalLater(doc, "cursorActivity", doc); + } + + // Verify that the selection does not partially select any atomic + // marked ranges. + function reCheckSelection(doc) { + setSelectionInner(doc, skipAtomicInSelection(doc, doc.sel, null, false), sel_dontScroll); + } + + // Return a selection that does not partially select any atomic + // ranges. + function skipAtomicInSelection(doc, sel, bias, mayClear) { + var out; + for (var i = 0; i < sel.ranges.length; i++) { + var range = sel.ranges[i]; + var newAnchor = skipAtomic(doc, range.anchor, bias, mayClear); + var newHead = skipAtomic(doc, range.head, bias, mayClear); + if (out || newAnchor != range.anchor || newHead != range.head) { + if (!out) out = sel.ranges.slice(0, i); + out[i] = new Range(newAnchor, newHead); + } + } + return out ? normalizeSelection(out, sel.primIndex) : sel; + } + + // Ensure a given position is not inside an atomic range. + function skipAtomic(doc, pos, bias, mayClear) { + var flipped = false, curPos = pos; + var dir = bias || 1; + doc.cantEdit = false; + search: for (;;) { + var line = getLine(doc, curPos.line); + if (line.markedSpans) { + for (var i = 0; i < line.markedSpans.length; ++i) { + var sp = line.markedSpans[i], m = sp.marker; + if ((sp.from == null || (m.inclusiveLeft ? sp.from <= curPos.ch : sp.from < curPos.ch)) && + (sp.to == null || (m.inclusiveRight ? sp.to >= curPos.ch : sp.to > curPos.ch))) { + if (mayClear) { + signal(m, "beforeCursorEnter"); + if (m.explicitlyCleared) { + if (!line.markedSpans) break; + else {--i; continue;} + } + } + if (!m.atomic) continue; + var newPos = m.find(dir < 0 ? -1 : 1); + if (cmp(newPos, curPos) == 0) { + newPos.ch += dir; + if (newPos.ch < 0) { + if (newPos.line > doc.first) newPos = clipPos(doc, Pos(newPos.line - 1)); + else newPos = null; + } else if (newPos.ch > line.text.length) { + if (newPos.line < doc.first + doc.size - 1) newPos = Pos(newPos.line + 1, 0); + else newPos = null; + } + if (!newPos) { + if (flipped) { + // Driven in a corner -- no valid cursor position found at all + // -- try again *with* clearing, if we didn't already + if (!mayClear) return skipAtomic(doc, pos, bias, true); + // Otherwise, turn off editing until further notice, and return the start of the doc + doc.cantEdit = true; + return Pos(doc.first, 0); + } + flipped = true; newPos = pos; dir = -dir; + } + } + curPos = newPos; + continue search; + } + } + } + return curPos; + } + } + + // SELECTION DRAWING + + function updateSelection(cm) { + cm.display.input.showSelection(cm.display.input.prepareSelection()); + } + + function prepareSelection(cm, primary) { + var doc = cm.doc, result = {}; + var curFragment = result.cursors = document.createDocumentFragment(); + var selFragment = result.selection = document.createDocumentFragment(); + + for (var i = 0; i < doc.sel.ranges.length; i++) { + if (primary === false && i == doc.sel.primIndex) continue; + var range = doc.sel.ranges[i]; + var collapsed = range.empty(); + if (collapsed || cm.options.showCursorWhenSelecting) + drawSelectionCursor(cm, range, curFragment); + if (!collapsed) + drawSelectionRange(cm, range, selFragment); + } + return result; + } + + // Draws a cursor for the given range + function drawSelectionCursor(cm, range, output) { + var pos = cursorCoords(cm, range.head, "div", null, null, !cm.options.singleCursorHeightPerLine); + + var cursor = output.appendChild(elt("div", "\u00a0", "CodeMirror-cursor")); + cursor.style.left = pos.left + "px"; + cursor.style.top = pos.top + "px"; + cursor.style.height = Math.max(0, pos.bottom - pos.top) * cm.options.cursorHeight + "px"; + + if (pos.other) { + // Secondary cursor, shown when on a 'jump' in bi-directional text + var otherCursor = output.appendChild(elt("div", "\u00a0", "CodeMirror-cursor CodeMirror-secondarycursor")); + otherCursor.style.display = ""; + otherCursor.style.left = pos.other.left + "px"; + otherCursor.style.top = pos.other.top + "px"; + otherCursor.style.height = (pos.other.bottom - pos.other.top) * .85 + "px"; + } + } + + // Draws the given range as a highlighted selection + function drawSelectionRange(cm, range, output) { + var display = cm.display, doc = cm.doc; + var fragment = document.createDocumentFragment(); + var padding = paddingH(cm.display), leftSide = padding.left; + var rightSide = Math.max(display.sizerWidth, displayWidth(cm) - display.sizer.offsetLeft) - padding.right; + + function add(left, top, width, bottom) { + if (top < 0) top = 0; + top = Math.round(top); + bottom = Math.round(bottom); + fragment.appendChild(elt("div", null, "CodeMirror-selected", "position: absolute; left: " + left + + "px; top: " + top + "px; width: " + (width == null ? rightSide - left : width) + + "px; height: " + (bottom - top) + "px")); + } + + function drawForLine(line, fromArg, toArg) { + var lineObj = getLine(doc, line); + var lineLen = lineObj.text.length; + var start, end; + function coords(ch, bias) { + return charCoords(cm, Pos(line, ch), "div", lineObj, bias); + } + + iterateBidiSections(getOrder(lineObj), fromArg || 0, toArg == null ? lineLen : toArg, function(from, to, dir) { + var leftPos = coords(from, "left"), rightPos, left, right; + if (from == to) { + rightPos = leftPos; + left = right = leftPos.left; + } else { + rightPos = coords(to - 1, "right"); + if (dir == "rtl") { var tmp = leftPos; leftPos = rightPos; rightPos = tmp; } + left = leftPos.left; + right = rightPos.right; + } + if (fromArg == null && from == 0) left = leftSide; + if (rightPos.top - leftPos.top > 3) { // Different lines, draw top part + add(left, leftPos.top, null, leftPos.bottom); + left = leftSide; + if (leftPos.bottom < rightPos.top) add(left, leftPos.bottom, null, rightPos.top); + } + if (toArg == null && to == lineLen) right = rightSide; + if (!start || leftPos.top < start.top || leftPos.top == start.top && leftPos.left < start.left) + start = leftPos; + if (!end || rightPos.bottom > end.bottom || rightPos.bottom == end.bottom && rightPos.right > end.right) + end = rightPos; + if (left < leftSide + 1) left = leftSide; + add(left, rightPos.top, right - left, rightPos.bottom); + }); + return {start: start, end: end}; + } + + var sFrom = range.from(), sTo = range.to(); + if (sFrom.line == sTo.line) { + drawForLine(sFrom.line, sFrom.ch, sTo.ch); + } else { + var fromLine = getLine(doc, sFrom.line), toLine = getLine(doc, sTo.line); + var singleVLine = visualLine(fromLine) == visualLine(toLine); + var leftEnd = drawForLine(sFrom.line, sFrom.ch, singleVLine ? fromLine.text.length + 1 : null).end; + var rightStart = drawForLine(sTo.line, singleVLine ? 0 : null, sTo.ch).start; + if (singleVLine) { + if (leftEnd.top < rightStart.top - 2) { + add(leftEnd.right, leftEnd.top, null, leftEnd.bottom); + add(leftSide, rightStart.top, rightStart.left, rightStart.bottom); + } else { + add(leftEnd.right, leftEnd.top, rightStart.left - leftEnd.right, leftEnd.bottom); + } + } + if (leftEnd.bottom < rightStart.top) + add(leftSide, leftEnd.bottom, null, rightStart.top); + } + + output.appendChild(fragment); + } + + // Cursor-blinking + function restartBlink(cm) { + if (!cm.state.focused) return; + var display = cm.display; + clearInterval(display.blinker); + var on = true; + display.cursorDiv.style.visibility = ""; + if (cm.options.cursorBlinkRate > 0) + display.blinker = setInterval(function() { + display.cursorDiv.style.visibility = (on = !on) ? "" : "hidden"; + }, cm.options.cursorBlinkRate); + else if (cm.options.cursorBlinkRate < 0) + display.cursorDiv.style.visibility = "hidden"; + } + + // HIGHLIGHT WORKER + + function startWorker(cm, time) { + if (cm.doc.mode.startState && cm.doc.frontier < cm.display.viewTo) + cm.state.highlight.set(time, bind(highlightWorker, cm)); + } + + function highlightWorker(cm) { + var doc = cm.doc; + if (doc.frontier < doc.first) doc.frontier = doc.first; + if (doc.frontier >= cm.display.viewTo) return; + var end = +new Date + cm.options.workTime; + var state = copyState(doc.mode, getStateBefore(cm, doc.frontier)); + var changedLines = []; + + doc.iter(doc.frontier, Math.min(doc.first + doc.size, cm.display.viewTo + 500), function(line) { + if (doc.frontier >= cm.display.viewFrom) { // Visible + var oldStyles = line.styles; + var highlighted = highlightLine(cm, line, state, true); + line.styles = highlighted.styles; + var oldCls = line.styleClasses, newCls = highlighted.classes; + if (newCls) line.styleClasses = newCls; + else if (oldCls) line.styleClasses = null; + var ischange = !oldStyles || oldStyles.length != line.styles.length || + oldCls != newCls && (!oldCls || !newCls || oldCls.bgClass != newCls.bgClass || oldCls.textClass != newCls.textClass); + for (var i = 0; !ischange && i < oldStyles.length; ++i) ischange = oldStyles[i] != line.styles[i]; + if (ischange) changedLines.push(doc.frontier); + line.stateAfter = copyState(doc.mode, state); + } else { + processLine(cm, line.text, state); + line.stateAfter = doc.frontier % 5 == 0 ? copyState(doc.mode, state) : null; + } + ++doc.frontier; + if (+new Date > end) { + startWorker(cm, cm.options.workDelay); + return true; + } + }); + if (changedLines.length) runInOp(cm, function() { + for (var i = 0; i < changedLines.length; i++) + regLineChange(cm, changedLines[i], "text"); + }); + } + + // Finds the line to start with when starting a parse. Tries to + // find a line with a stateAfter, so that it can start with a + // valid state. If that fails, it returns the line with the + // smallest indentation, which tends to need the least context to + // parse correctly. + function findStartLine(cm, n, precise) { + var minindent, minline, doc = cm.doc; + var lim = precise ? -1 : n - (cm.doc.mode.innerMode ? 1000 : 100); + for (var search = n; search > lim; --search) { + if (search <= doc.first) return doc.first; + var line = getLine(doc, search - 1); + if (line.stateAfter && (!precise || search <= doc.frontier)) return search; + var indented = countColumn(line.text, null, cm.options.tabSize); + if (minline == null || minindent > indented) { + minline = search - 1; + minindent = indented; + } + } + return minline; + } + + function getStateBefore(cm, n, precise) { + var doc = cm.doc, display = cm.display; + if (!doc.mode.startState) return true; + var pos = findStartLine(cm, n, precise), state = pos > doc.first && getLine(doc, pos-1).stateAfter; + if (!state) state = startState(doc.mode); + else state = copyState(doc.mode, state); + doc.iter(pos, n, function(line) { + processLine(cm, line.text, state); + var save = pos == n - 1 || pos % 5 == 0 || pos >= display.viewFrom && pos < display.viewTo; + line.stateAfter = save ? copyState(doc.mode, state) : null; + ++pos; + }); + if (precise) doc.frontier = pos; + return state; + } + + // POSITION MEASUREMENT + + function paddingTop(display) {return display.lineSpace.offsetTop;} + function paddingVert(display) {return display.mover.offsetHeight - display.lineSpace.offsetHeight;} + function paddingH(display) { + if (display.cachedPaddingH) return display.cachedPaddingH; + var e = removeChildrenAndAdd(display.measure, elt("pre", "x")); + var style = window.getComputedStyle ? window.getComputedStyle(e) : e.currentStyle; + var data = {left: parseInt(style.paddingLeft), right: parseInt(style.paddingRight)}; + if (!isNaN(data.left) && !isNaN(data.right)) display.cachedPaddingH = data; + return data; + } + + function scrollGap(cm) { return scrollerGap - cm.display.nativeBarWidth; } + function displayWidth(cm) { + return cm.display.scroller.clientWidth - scrollGap(cm) - cm.display.barWidth; + } + function displayHeight(cm) { + return cm.display.scroller.clientHeight - scrollGap(cm) - cm.display.barHeight; + } + + // Ensure the lineView.wrapping.heights array is populated. This is + // an array of bottom offsets for the lines that make up a drawn + // line. When lineWrapping is on, there might be more than one + // height. + function ensureLineHeights(cm, lineView, rect) { + var wrapping = cm.options.lineWrapping; + var curWidth = wrapping && displayWidth(cm); + if (!lineView.measure.heights || wrapping && lineView.measure.width != curWidth) { + var heights = lineView.measure.heights = []; + if (wrapping) { + lineView.measure.width = curWidth; + var rects = lineView.text.firstChild.getClientRects(); + for (var i = 0; i < rects.length - 1; i++) { + var cur = rects[i], next = rects[i + 1]; + if (Math.abs(cur.bottom - next.bottom) > 2) + heights.push((cur.bottom + next.top) / 2 - rect.top); + } + } + heights.push(rect.bottom - rect.top); + } + } + + // Find a line map (mapping character offsets to text nodes) and a + // measurement cache for the given line number. (A line view might + // contain multiple lines when collapsed ranges are present.) + function mapFromLineView(lineView, line, lineN) { + if (lineView.line == line) + return {map: lineView.measure.map, cache: lineView.measure.cache}; + for (var i = 0; i < lineView.rest.length; i++) + if (lineView.rest[i] == line) + return {map: lineView.measure.maps[i], cache: lineView.measure.caches[i]}; + for (var i = 0; i < lineView.rest.length; i++) + if (lineNo(lineView.rest[i]) > lineN) + return {map: lineView.measure.maps[i], cache: lineView.measure.caches[i], before: true}; + } + + // Render a line into the hidden node display.externalMeasured. Used + // when measurement is needed for a line that's not in the viewport. + function updateExternalMeasurement(cm, line) { + line = visualLine(line); + var lineN = lineNo(line); + var view = cm.display.externalMeasured = new LineView(cm.doc, line, lineN); + view.lineN = lineN; + var built = view.built = buildLineContent(cm, view); + view.text = built.pre; + removeChildrenAndAdd(cm.display.lineMeasure, built.pre); + return view; + } + + // Get a {top, bottom, left, right} box (in line-local coordinates) + // for a given character. + function measureChar(cm, line, ch, bias) { + return measureCharPrepared(cm, prepareMeasureForLine(cm, line), ch, bias); + } + + // Find a line view that corresponds to the given line number. + function findViewForLine(cm, lineN) { + if (lineN >= cm.display.viewFrom && lineN < cm.display.viewTo) + return cm.display.view[findViewIndex(cm, lineN)]; + var ext = cm.display.externalMeasured; + if (ext && lineN >= ext.lineN && lineN < ext.lineN + ext.size) + return ext; + } + + // Measurement can be split in two steps, the set-up work that + // applies to the whole line, and the measurement of the actual + // character. Functions like coordsChar, that need to do a lot of + // measurements in a row, can thus ensure that the set-up work is + // only done once. + function prepareMeasureForLine(cm, line) { + var lineN = lineNo(line); + var view = findViewForLine(cm, lineN); + if (view && !view.text) + view = null; + else if (view && view.changes) + updateLineForChanges(cm, view, lineN, getDimensions(cm)); + if (!view) + view = updateExternalMeasurement(cm, line); + + var info = mapFromLineView(view, line, lineN); + return { + line: line, view: view, rect: null, + map: info.map, cache: info.cache, before: info.before, + hasHeights: false + }; + } + + // Given a prepared measurement object, measures the position of an + // actual character (or fetches it from the cache). + function measureCharPrepared(cm, prepared, ch, bias, varHeight) { + if (prepared.before) ch = -1; + var key = ch + (bias || ""), found; + if (prepared.cache.hasOwnProperty(key)) { + found = prepared.cache[key]; + } else { + if (!prepared.rect) + prepared.rect = prepared.view.text.getBoundingClientRect(); + if (!prepared.hasHeights) { + ensureLineHeights(cm, prepared.view, prepared.rect); + prepared.hasHeights = true; + } + found = measureCharInner(cm, prepared, ch, bias); + if (!found.bogus) prepared.cache[key] = found; + } + return {left: found.left, right: found.right, + top: varHeight ? found.rtop : found.top, + bottom: varHeight ? found.rbottom : found.bottom}; + } + + var nullRect = {left: 0, right: 0, top: 0, bottom: 0}; + + function nodeAndOffsetInLineMap(map, ch, bias) { + var node, start, end, collapse; + // First, search the line map for the text node corresponding to, + // or closest to, the target character. + for (var i = 0; i < map.length; i += 3) { + var mStart = map[i], mEnd = map[i + 1]; + if (ch < mStart) { + start = 0; end = 1; + collapse = "left"; + } else if (ch < mEnd) { + start = ch - mStart; + end = start + 1; + } else if (i == map.length - 3 || ch == mEnd && map[i + 3] > ch) { + end = mEnd - mStart; + start = end - 1; + if (ch >= mEnd) collapse = "right"; + } + if (start != null) { + node = map[i + 2]; + if (mStart == mEnd && bias == (node.insertLeft ? "left" : "right")) + collapse = bias; + if (bias == "left" && start == 0) + while (i && map[i - 2] == map[i - 3] && map[i - 1].insertLeft) { + node = map[(i -= 3) + 2]; + collapse = "left"; + } + if (bias == "right" && start == mEnd - mStart) + while (i < map.length - 3 && map[i + 3] == map[i + 4] && !map[i + 5].insertLeft) { + node = map[(i += 3) + 2]; + collapse = "right"; + } + break; + } + } + return {node: node, start: start, end: end, collapse: collapse, coverStart: mStart, coverEnd: mEnd}; + } + + function measureCharInner(cm, prepared, ch, bias) { + var place = nodeAndOffsetInLineMap(prepared.map, ch, bias); + var node = place.node, start = place.start, end = place.end, collapse = place.collapse; + + var rect; + if (node.nodeType == 3) { // If it is a text node, use a range to retrieve the coordinates. + for (var i = 0; i < 4; i++) { // Retry a maximum of 4 times when nonsense rectangles are returned + while (start && isExtendingChar(prepared.line.text.charAt(place.coverStart + start))) --start; + while (place.coverStart + end < place.coverEnd && isExtendingChar(prepared.line.text.charAt(place.coverStart + end))) ++end; + if (ie && ie_version < 9 && start == 0 && end == place.coverEnd - place.coverStart) { + rect = node.parentNode.getBoundingClientRect(); + } else if (ie && cm.options.lineWrapping) { + var rects = range(node, start, end).getClientRects(); + if (rects.length) + rect = rects[bias == "right" ? rects.length - 1 : 0]; + else + rect = nullRect; + } else { + rect = range(node, start, end).getBoundingClientRect() || nullRect; + } + if (rect.left || rect.right || start == 0) break; + end = start; + start = start - 1; + collapse = "right"; + } + if (ie && ie_version < 11) rect = maybeUpdateRectForZooming(cm.display.measure, rect); + } else { // If it is a widget, simply get the box for the whole widget. + if (start > 0) collapse = bias = "right"; + var rects; + if (cm.options.lineWrapping && (rects = node.getClientRects()).length > 1) + rect = rects[bias == "right" ? rects.length - 1 : 0]; + else + rect = node.getBoundingClientRect(); + } + if (ie && ie_version < 9 && !start && (!rect || !rect.left && !rect.right)) { + var rSpan = node.parentNode.getClientRects()[0]; + if (rSpan) + rect = {left: rSpan.left, right: rSpan.left + charWidth(cm.display), top: rSpan.top, bottom: rSpan.bottom}; + else + rect = nullRect; + } + + var rtop = rect.top - prepared.rect.top, rbot = rect.bottom - prepared.rect.top; + var mid = (rtop + rbot) / 2; + var heights = prepared.view.measure.heights; + for (var i = 0; i < heights.length - 1; i++) + if (mid < heights[i]) break; + var top = i ? heights[i - 1] : 0, bot = heights[i]; + var result = {left: (collapse == "right" ? rect.right : rect.left) - prepared.rect.left, + right: (collapse == "left" ? rect.left : rect.right) - prepared.rect.left, + top: top, bottom: bot}; + if (!rect.left && !rect.right) result.bogus = true; + if (!cm.options.singleCursorHeightPerLine) { result.rtop = rtop; result.rbottom = rbot; } + + return result; + } + + // Work around problem with bounding client rects on ranges being + // returned incorrectly when zoomed on IE10 and below. + function maybeUpdateRectForZooming(measure, rect) { + if (!window.screen || screen.logicalXDPI == null || + screen.logicalXDPI == screen.deviceXDPI || !hasBadZoomedRects(measure)) + return rect; + var scaleX = screen.logicalXDPI / screen.deviceXDPI; + var scaleY = screen.logicalYDPI / screen.deviceYDPI; + return {left: rect.left * scaleX, right: rect.right * scaleX, + top: rect.top * scaleY, bottom: rect.bottom * scaleY}; + } + + function clearLineMeasurementCacheFor(lineView) { + if (lineView.measure) { + lineView.measure.cache = {}; + lineView.measure.heights = null; + if (lineView.rest) for (var i = 0; i < lineView.rest.length; i++) + lineView.measure.caches[i] = {}; + } + } + + function clearLineMeasurementCache(cm) { + cm.display.externalMeasure = null; + removeChildren(cm.display.lineMeasure); + for (var i = 0; i < cm.display.view.length; i++) + clearLineMeasurementCacheFor(cm.display.view[i]); + } + + function clearCaches(cm) { + clearLineMeasurementCache(cm); + cm.display.cachedCharWidth = cm.display.cachedTextHeight = cm.display.cachedPaddingH = null; + if (!cm.options.lineWrapping) cm.display.maxLineChanged = true; + cm.display.lineNumChars = null; + } + + function pageScrollX() { return window.pageXOffset || (document.documentElement || document.body).scrollLeft; } + function pageScrollY() { return window.pageYOffset || (document.documentElement || document.body).scrollTop; } + + // Converts a {top, bottom, left, right} box from line-local + // coordinates into another coordinate system. Context may be one of + // "line", "div" (display.lineDiv), "local"/null (editor), "window", + // or "page". + function intoCoordSystem(cm, lineObj, rect, context) { + if (lineObj.widgets) for (var i = 0; i < lineObj.widgets.length; ++i) if (lineObj.widgets[i].above) { + var size = widgetHeight(lineObj.widgets[i]); + rect.top += size; rect.bottom += size; + } + if (context == "line") return rect; + if (!context) context = "local"; + var yOff = heightAtLine(lineObj); + if (context == "local") yOff += paddingTop(cm.display); + else yOff -= cm.display.viewOffset; + if (context == "page" || context == "window") { + var lOff = cm.display.lineSpace.getBoundingClientRect(); + yOff += lOff.top + (context == "window" ? 0 : pageScrollY()); + var xOff = lOff.left + (context == "window" ? 0 : pageScrollX()); + rect.left += xOff; rect.right += xOff; + } + rect.top += yOff; rect.bottom += yOff; + return rect; + } + + // Coverts a box from "div" coords to another coordinate system. + // Context may be "window", "page", "div", or "local"/null. + function fromCoordSystem(cm, coords, context) { + if (context == "div") return coords; + var left = coords.left, top = coords.top; + // First move into "page" coordinate system + if (context == "page") { + left -= pageScrollX(); + top -= pageScrollY(); + } else if (context == "local" || !context) { + var localBox = cm.display.sizer.getBoundingClientRect(); + left += localBox.left; + top += localBox.top; + } + + var lineSpaceBox = cm.display.lineSpace.getBoundingClientRect(); + return {left: left - lineSpaceBox.left, top: top - lineSpaceBox.top}; + } + + function charCoords(cm, pos, context, lineObj, bias) { + if (!lineObj) lineObj = getLine(cm.doc, pos.line); + return intoCoordSystem(cm, lineObj, measureChar(cm, lineObj, pos.ch, bias), context); + } + + // Returns a box for a given cursor position, which may have an + // 'other' property containing the position of the secondary cursor + // on a bidi boundary. + function cursorCoords(cm, pos, context, lineObj, preparedMeasure, varHeight) { + lineObj = lineObj || getLine(cm.doc, pos.line); + if (!preparedMeasure) preparedMeasure = prepareMeasureForLine(cm, lineObj); + function get(ch, right) { + var m = measureCharPrepared(cm, preparedMeasure, ch, right ? "right" : "left", varHeight); + if (right) m.left = m.right; else m.right = m.left; + return intoCoordSystem(cm, lineObj, m, context); + } + function getBidi(ch, partPos) { + var part = order[partPos], right = part.level % 2; + if (ch == bidiLeft(part) && partPos && part.level < order[partPos - 1].level) { + part = order[--partPos]; + ch = bidiRight(part) - (part.level % 2 ? 0 : 1); + right = true; + } else if (ch == bidiRight(part) && partPos < order.length - 1 && part.level < order[partPos + 1].level) { + part = order[++partPos]; + ch = bidiLeft(part) - part.level % 2; + right = false; + } + if (right && ch == part.to && ch > part.from) return get(ch - 1); + return get(ch, right); + } + var order = getOrder(lineObj), ch = pos.ch; + if (!order) return get(ch); + var partPos = getBidiPartAt(order, ch); + var val = getBidi(ch, partPos); + if (bidiOther != null) val.other = getBidi(ch, bidiOther); + return val; + } + + // Used to cheaply estimate the coordinates for a position. Used for + // intermediate scroll updates. + function estimateCoords(cm, pos) { + var left = 0, pos = clipPos(cm.doc, pos); + if (!cm.options.lineWrapping) left = charWidth(cm.display) * pos.ch; + var lineObj = getLine(cm.doc, pos.line); + var top = heightAtLine(lineObj) + paddingTop(cm.display); + return {left: left, right: left, top: top, bottom: top + lineObj.height}; + } + + // Positions returned by coordsChar contain some extra information. + // xRel is the relative x position of the input coordinates compared + // to the found position (so xRel > 0 means the coordinates are to + // the right of the character position, for example). When outside + // is true, that means the coordinates lie outside the line's + // vertical range. + function PosWithInfo(line, ch, outside, xRel) { + var pos = Pos(line, ch); + pos.xRel = xRel; + if (outside) pos.outside = true; + return pos; + } + + // Compute the character position closest to the given coordinates. + // Input must be lineSpace-local ("div" coordinate system). + function coordsChar(cm, x, y) { + var doc = cm.doc; + y += cm.display.viewOffset; + if (y < 0) return PosWithInfo(doc.first, 0, true, -1); + var lineN = lineAtHeight(doc, y), last = doc.first + doc.size - 1; + if (lineN > last) + return PosWithInfo(doc.first + doc.size - 1, getLine(doc, last).text.length, true, 1); + if (x < 0) x = 0; + + var lineObj = getLine(doc, lineN); + for (;;) { + var found = coordsCharInner(cm, lineObj, lineN, x, y); + var merged = collapsedSpanAtEnd(lineObj); + var mergedPos = merged && merged.find(0, true); + if (merged && (found.ch > mergedPos.from.ch || found.ch == mergedPos.from.ch && found.xRel > 0)) + lineN = lineNo(lineObj = mergedPos.to.line); + else + return found; + } + } + + function coordsCharInner(cm, lineObj, lineNo, x, y) { + var innerOff = y - heightAtLine(lineObj); + var wrongLine = false, adjust = 2 * cm.display.wrapper.clientWidth; + var preparedMeasure = prepareMeasureForLine(cm, lineObj); + + function getX(ch) { + var sp = cursorCoords(cm, Pos(lineNo, ch), "line", lineObj, preparedMeasure); + wrongLine = true; + if (innerOff > sp.bottom) return sp.left - adjust; + else if (innerOff < sp.top) return sp.left + adjust; + else wrongLine = false; + return sp.left; + } + + var bidi = getOrder(lineObj), dist = lineObj.text.length; + var from = lineLeft(lineObj), to = lineRight(lineObj); + var fromX = getX(from), fromOutside = wrongLine, toX = getX(to), toOutside = wrongLine; + + if (x > toX) return PosWithInfo(lineNo, to, toOutside, 1); + // Do a binary search between these bounds. + for (;;) { + if (bidi ? to == from || to == moveVisually(lineObj, from, 1) : to - from <= 1) { + var ch = x < fromX || x - fromX <= toX - x ? from : to; + var xDiff = x - (ch == from ? fromX : toX); + while (isExtendingChar(lineObj.text.charAt(ch))) ++ch; + var pos = PosWithInfo(lineNo, ch, ch == from ? fromOutside : toOutside, + xDiff < -1 ? -1 : xDiff > 1 ? 1 : 0); + return pos; + } + var step = Math.ceil(dist / 2), middle = from + step; + if (bidi) { + middle = from; + for (var i = 0; i < step; ++i) middle = moveVisually(lineObj, middle, 1); + } + var middleX = getX(middle); + if (middleX > x) {to = middle; toX = middleX; if (toOutside = wrongLine) toX += 1000; dist = step;} + else {from = middle; fromX = middleX; fromOutside = wrongLine; dist -= step;} + } + } + + var measureText; + // Compute the default text height. + function textHeight(display) { + if (display.cachedTextHeight != null) return display.cachedTextHeight; + if (measureText == null) { + measureText = elt("pre"); + // Measure a bunch of lines, for browsers that compute + // fractional heights. + for (var i = 0; i < 49; ++i) { + measureText.appendChild(document.createTextNode("x")); + measureText.appendChild(elt("br")); + } + measureText.appendChild(document.createTextNode("x")); + } + removeChildrenAndAdd(display.measure, measureText); + var height = measureText.offsetHeight / 50; + if (height > 3) display.cachedTextHeight = height; + removeChildren(display.measure); + return height || 1; + } + + // Compute the default character width. + function charWidth(display) { + if (display.cachedCharWidth != null) return display.cachedCharWidth; + var anchor = elt("span", "xxxxxxxxxx"); + var pre = elt("pre", [anchor]); + removeChildrenAndAdd(display.measure, pre); + var rect = anchor.getBoundingClientRect(), width = (rect.right - rect.left) / 10; + if (width > 2) display.cachedCharWidth = width; + return width || 10; + } + + // OPERATIONS + + // Operations are used to wrap a series of changes to the editor + // state in such a way that each change won't have to update the + // cursor and display (which would be awkward, slow, and + // error-prone). Instead, display updates are batched and then all + // combined and executed at once. + + var operationGroup = null; + + var nextOpId = 0; + // Start a new operation. + function startOperation(cm) { + cm.curOp = { + cm: cm, + viewChanged: false, // Flag that indicates that lines might need to be redrawn + startHeight: cm.doc.height, // Used to detect need to update scrollbar + forceUpdate: false, // Used to force a redraw + updateInput: null, // Whether to reset the input textarea + typing: false, // Whether this reset should be careful to leave existing text (for compositing) + changeObjs: null, // Accumulated changes, for firing change events + cursorActivityHandlers: null, // Set of handlers to fire cursorActivity on + cursorActivityCalled: 0, // Tracks which cursorActivity handlers have been called already + selectionChanged: false, // Whether the selection needs to be redrawn + updateMaxLine: false, // Set when the widest line needs to be determined anew + scrollLeft: null, scrollTop: null, // Intermediate scroll position, not pushed to DOM yet + scrollToPos: null, // Used to scroll to a specific position + focus: false, + id: ++nextOpId // Unique ID + }; + if (operationGroup) { + operationGroup.ops.push(cm.curOp); + } else { + cm.curOp.ownsGroup = operationGroup = { + ops: [cm.curOp], + delayedCallbacks: [] + }; + } + } + + function fireCallbacksForOps(group) { + // Calls delayed callbacks and cursorActivity handlers until no + // new ones appear + var callbacks = group.delayedCallbacks, i = 0; + do { + for (; i < callbacks.length; i++) + callbacks[i](); + for (var j = 0; j < group.ops.length; j++) { + var op = group.ops[j]; + if (op.cursorActivityHandlers) + while (op.cursorActivityCalled < op.cursorActivityHandlers.length) + op.cursorActivityHandlers[op.cursorActivityCalled++](op.cm); + } + } while (i < callbacks.length); + } + + // Finish an operation, updating the display and signalling delayed events + function endOperation(cm) { + var op = cm.curOp, group = op.ownsGroup; + if (!group) return; + + try { fireCallbacksForOps(group); } + finally { + operationGroup = null; + for (var i = 0; i < group.ops.length; i++) + group.ops[i].cm.curOp = null; + endOperations(group); + } + } + + // The DOM updates done when an operation finishes are batched so + // that the minimum number of relayouts are required. + function endOperations(group) { + var ops = group.ops; + for (var i = 0; i < ops.length; i++) // Read DOM + endOperation_R1(ops[i]); + for (var i = 0; i < ops.length; i++) // Write DOM (maybe) + endOperation_W1(ops[i]); + for (var i = 0; i < ops.length; i++) // Read DOM + endOperation_R2(ops[i]); + for (var i = 0; i < ops.length; i++) // Write DOM (maybe) + endOperation_W2(ops[i]); + for (var i = 0; i < ops.length; i++) // Read DOM + endOperation_finish(ops[i]); + } + + function endOperation_R1(op) { + var cm = op.cm, display = cm.display; + maybeClipScrollbars(cm); + if (op.updateMaxLine) findMaxLine(cm); + + op.mustUpdate = op.viewChanged || op.forceUpdate || op.scrollTop != null || + op.scrollToPos && (op.scrollToPos.from.line < display.viewFrom || + op.scrollToPos.to.line >= display.viewTo) || + display.maxLineChanged && cm.options.lineWrapping; + op.update = op.mustUpdate && + new DisplayUpdate(cm, op.mustUpdate && {top: op.scrollTop, ensure: op.scrollToPos}, op.forceUpdate); + } + + function endOperation_W1(op) { + op.updatedDisplay = op.mustUpdate && updateDisplayIfNeeded(op.cm, op.update); + } + + function endOperation_R2(op) { + var cm = op.cm, display = cm.display; + if (op.updatedDisplay) updateHeightsInViewport(cm); + + op.barMeasure = measureForScrollbars(cm); + + // If the max line changed since it was last measured, measure it, + // and ensure the document's width matches it. + // updateDisplay_W2 will use these properties to do the actual resizing + if (display.maxLineChanged && !cm.options.lineWrapping) { + op.adjustWidthTo = measureChar(cm, display.maxLine, display.maxLine.text.length).left + 3; + cm.display.sizerWidth = op.adjustWidthTo; + op.barMeasure.scrollWidth = + Math.max(display.scroller.clientWidth, display.sizer.offsetLeft + op.adjustWidthTo + scrollGap(cm) + cm.display.barWidth); + op.maxScrollLeft = Math.max(0, display.sizer.offsetLeft + op.adjustWidthTo - displayWidth(cm)); + } + + if (op.updatedDisplay || op.selectionChanged) + op.preparedSelection = display.input.prepareSelection(); + } + + function endOperation_W2(op) { + var cm = op.cm; + + if (op.adjustWidthTo != null) { + cm.display.sizer.style.minWidth = op.adjustWidthTo + "px"; + if (op.maxScrollLeft < cm.doc.scrollLeft) + setScrollLeft(cm, Math.min(cm.display.scroller.scrollLeft, op.maxScrollLeft), true); + cm.display.maxLineChanged = false; + } + + if (op.preparedSelection) + cm.display.input.showSelection(op.preparedSelection); + if (op.updatedDisplay) + setDocumentHeight(cm, op.barMeasure); + if (op.updatedDisplay || op.startHeight != cm.doc.height) + updateScrollbars(cm, op.barMeasure); + + if (op.selectionChanged) restartBlink(cm); + + if (cm.state.focused && op.updateInput) + cm.display.input.reset(op.typing); + if (op.focus && op.focus == activeElt()) ensureFocus(op.cm); + } + + function endOperation_finish(op) { + var cm = op.cm, display = cm.display, doc = cm.doc; + + if (op.updatedDisplay) postUpdateDisplay(cm, op.update); + + // Abort mouse wheel delta measurement, when scrolling explicitly + if (display.wheelStartX != null && (op.scrollTop != null || op.scrollLeft != null || op.scrollToPos)) + display.wheelStartX = display.wheelStartY = null; + + // Propagate the scroll position to the actual DOM scroller + if (op.scrollTop != null && (display.scroller.scrollTop != op.scrollTop || op.forceScroll)) { + doc.scrollTop = Math.max(0, Math.min(display.scroller.scrollHeight - display.scroller.clientHeight, op.scrollTop)); + display.scrollbars.setScrollTop(doc.scrollTop); + display.scroller.scrollTop = doc.scrollTop; + } + if (op.scrollLeft != null && (display.scroller.scrollLeft != op.scrollLeft || op.forceScroll)) { + doc.scrollLeft = Math.max(0, Math.min(display.scroller.scrollWidth - displayWidth(cm), op.scrollLeft)); + display.scrollbars.setScrollLeft(doc.scrollLeft); + display.scroller.scrollLeft = doc.scrollLeft; + alignHorizontally(cm); + } + // If we need to scroll a specific position into view, do so. + if (op.scrollToPos) { + var coords = scrollPosIntoView(cm, clipPos(doc, op.scrollToPos.from), + clipPos(doc, op.scrollToPos.to), op.scrollToPos.margin); + if (op.scrollToPos.isCursor && cm.state.focused) maybeScrollWindow(cm, coords); + } + + // Fire events for markers that are hidden/unidden by editing or + // undoing + var hidden = op.maybeHiddenMarkers, unhidden = op.maybeUnhiddenMarkers; + if (hidden) for (var i = 0; i < hidden.length; ++i) + if (!hidden[i].lines.length) signal(hidden[i], "hide"); + if (unhidden) for (var i = 0; i < unhidden.length; ++i) + if (unhidden[i].lines.length) signal(unhidden[i], "unhide"); + + if (display.wrapper.offsetHeight) + doc.scrollTop = cm.display.scroller.scrollTop; + + // Fire change events, and delayed event handlers + if (op.changeObjs) + signal(cm, "changes", cm, op.changeObjs); + if (op.update) + op.update.finish(); + } + + // Run the given function in an operation + function runInOp(cm, f) { + if (cm.curOp) return f(); + startOperation(cm); + try { return f(); } + finally { endOperation(cm); } + } + // Wraps a function in an operation. Returns the wrapped function. + function operation(cm, f) { + return function() { + if (cm.curOp) return f.apply(cm, arguments); + startOperation(cm); + try { return f.apply(cm, arguments); } + finally { endOperation(cm); } + }; + } + // Used to add methods to editor and doc instances, wrapping them in + // operations. + function methodOp(f) { + return function() { + if (this.curOp) return f.apply(this, arguments); + startOperation(this); + try { return f.apply(this, arguments); } + finally { endOperation(this); } + }; + } + function docMethodOp(f) { + return function() { + var cm = this.cm; + if (!cm || cm.curOp) return f.apply(this, arguments); + startOperation(cm); + try { return f.apply(this, arguments); } + finally { endOperation(cm); } + }; + } + + // VIEW TRACKING + + // These objects are used to represent the visible (currently drawn) + // part of the document. A LineView may correspond to multiple + // logical lines, if those are connected by collapsed ranges. + function LineView(doc, line, lineN) { + // The starting line + this.line = line; + // Continuing lines, if any + this.rest = visualLineContinued(line); + // Number of logical lines in this visual line + this.size = this.rest ? lineNo(lst(this.rest)) - lineN + 1 : 1; + this.node = this.text = null; + this.hidden = lineIsHidden(doc, line); + } + + // Create a range of LineView objects for the given lines. + function buildViewArray(cm, from, to) { + var array = [], nextPos; + for (var pos = from; pos < to; pos = nextPos) { + var view = new LineView(cm.doc, getLine(cm.doc, pos), pos); + nextPos = pos + view.size; + array.push(view); + } + return array; + } + + // Updates the display.view data structure for a given change to the + // document. From and to are in pre-change coordinates. Lendiff is + // the amount of lines added or subtracted by the change. This is + // used for changes that span multiple lines, or change the way + // lines are divided into visual lines. regLineChange (below) + // registers single-line changes. + function regChange(cm, from, to, lendiff) { + if (from == null) from = cm.doc.first; + if (to == null) to = cm.doc.first + cm.doc.size; + if (!lendiff) lendiff = 0; + + var display = cm.display; + if (lendiff && to < display.viewTo && + (display.updateLineNumbers == null || display.updateLineNumbers > from)) + display.updateLineNumbers = from; + + cm.curOp.viewChanged = true; + + if (from >= display.viewTo) { // Change after + if (sawCollapsedSpans && visualLineNo(cm.doc, from) < display.viewTo) + resetView(cm); + } else if (to <= display.viewFrom) { // Change before + if (sawCollapsedSpans && visualLineEndNo(cm.doc, to + lendiff) > display.viewFrom) { + resetView(cm); + } else { + display.viewFrom += lendiff; + display.viewTo += lendiff; + } + } else if (from <= display.viewFrom && to >= display.viewTo) { // Full overlap + resetView(cm); + } else if (from <= display.viewFrom) { // Top overlap + var cut = viewCuttingPoint(cm, to, to + lendiff, 1); + if (cut) { + display.view = display.view.slice(cut.index); + display.viewFrom = cut.lineN; + display.viewTo += lendiff; + } else { + resetView(cm); + } + } else if (to >= display.viewTo) { // Bottom overlap + var cut = viewCuttingPoint(cm, from, from, -1); + if (cut) { + display.view = display.view.slice(0, cut.index); + display.viewTo = cut.lineN; + } else { + resetView(cm); + } + } else { // Gap in the middle + var cutTop = viewCuttingPoint(cm, from, from, -1); + var cutBot = viewCuttingPoint(cm, to, to + lendiff, 1); + if (cutTop && cutBot) { + display.view = display.view.slice(0, cutTop.index) + .concat(buildViewArray(cm, cutTop.lineN, cutBot.lineN)) + .concat(display.view.slice(cutBot.index)); + display.viewTo += lendiff; + } else { + resetView(cm); + } + } + + var ext = display.externalMeasured; + if (ext) { + if (to < ext.lineN) + ext.lineN += lendiff; + else if (from < ext.lineN + ext.size) + display.externalMeasured = null; + } + } + + // Register a change to a single line. Type must be one of "text", + // "gutter", "class", "widget" + function regLineChange(cm, line, type) { + cm.curOp.viewChanged = true; + var display = cm.display, ext = cm.display.externalMeasured; + if (ext && line >= ext.lineN && line < ext.lineN + ext.size) + display.externalMeasured = null; + + if (line < display.viewFrom || line >= display.viewTo) return; + var lineView = display.view[findViewIndex(cm, line)]; + if (lineView.node == null) return; + var arr = lineView.changes || (lineView.changes = []); + if (indexOf(arr, type) == -1) arr.push(type); + } + + // Clear the view. + function resetView(cm) { + cm.display.viewFrom = cm.display.viewTo = cm.doc.first; + cm.display.view = []; + cm.display.viewOffset = 0; + } + + // Find the view element corresponding to a given line. Return null + // when the line isn't visible. + function findViewIndex(cm, n) { + if (n >= cm.display.viewTo) return null; + n -= cm.display.viewFrom; + if (n < 0) return null; + var view = cm.display.view; + for (var i = 0; i < view.length; i++) { + n -= view[i].size; + if (n < 0) return i; + } + } + + function viewCuttingPoint(cm, oldN, newN, dir) { + var index = findViewIndex(cm, oldN), diff, view = cm.display.view; + if (!sawCollapsedSpans || newN == cm.doc.first + cm.doc.size) + return {index: index, lineN: newN}; + for (var i = 0, n = cm.display.viewFrom; i < index; i++) + n += view[i].size; + if (n != oldN) { + if (dir > 0) { + if (index == view.length - 1) return null; + diff = (n + view[index].size) - oldN; + index++; + } else { + diff = n - oldN; + } + oldN += diff; newN += diff; + } + while (visualLineNo(cm.doc, newN) != newN) { + if (index == (dir < 0 ? 0 : view.length - 1)) return null; + newN += dir * view[index - (dir < 0 ? 1 : 0)].size; + index += dir; + } + return {index: index, lineN: newN}; + } + + // Force the view to cover a given range, adding empty view element + // or clipping off existing ones as needed. + function adjustView(cm, from, to) { + var display = cm.display, view = display.view; + if (view.length == 0 || from >= display.viewTo || to <= display.viewFrom) { + display.view = buildViewArray(cm, from, to); + display.viewFrom = from; + } else { + if (display.viewFrom > from) + display.view = buildViewArray(cm, from, display.viewFrom).concat(display.view); + else if (display.viewFrom < from) + display.view = display.view.slice(findViewIndex(cm, from)); + display.viewFrom = from; + if (display.viewTo < to) + display.view = display.view.concat(buildViewArray(cm, display.viewTo, to)); + else if (display.viewTo > to) + display.view = display.view.slice(0, findViewIndex(cm, to)); + } + display.viewTo = to; + } + + // Count the number of lines in the view whose DOM representation is + // out of date (or nonexistent). + function countDirtyView(cm) { + var view = cm.display.view, dirty = 0; + for (var i = 0; i < view.length; i++) { + var lineView = view[i]; + if (!lineView.hidden && (!lineView.node || lineView.changes)) ++dirty; + } + return dirty; + } + + // EVENT HANDLERS + + // Attach the necessary event handlers when initializing the editor + function registerEventHandlers(cm) { + var d = cm.display; + on(d.scroller, "mousedown", operation(cm, onMouseDown)); + // Older IE's will not fire a second mousedown for a double click + if (ie && ie_version < 11) + on(d.scroller, "dblclick", operation(cm, function(e) { + if (signalDOMEvent(cm, e)) return; + var pos = posFromMouse(cm, e); + if (!pos || clickInGutter(cm, e) || eventInWidget(cm.display, e)) return; + e_preventDefault(e); + var word = cm.findWordAt(pos); + extendSelection(cm.doc, word.anchor, word.head); + })); + else + on(d.scroller, "dblclick", function(e) { signalDOMEvent(cm, e) || e_preventDefault(e); }); + // Some browsers fire contextmenu *after* opening the menu, at + // which point we can't mess with it anymore. Context menu is + // handled in onMouseDown for these browsers. + if (!captureRightClick) on(d.scroller, "contextmenu", function(e) {onContextMenu(cm, e);}); + + // Used to suppress mouse event handling when a touch happens + var touchFinished, prevTouch = {end: 0}; + function finishTouch() { + if (d.activeTouch) { + touchFinished = setTimeout(function() {d.activeTouch = null;}, 1000); + prevTouch = d.activeTouch; + prevTouch.end = +new Date; + } + }; + function isMouseLikeTouchEvent(e) { + if (e.touches.length != 1) return false; + var touch = e.touches[0]; + return touch.radiusX <= 1 && touch.radiusY <= 1; + } + function farAway(touch, other) { + if (other.left == null) return true; + var dx = other.left - touch.left, dy = other.top - touch.top; + return dx * dx + dy * dy > 20 * 20; + } + on(d.scroller, "touchstart", function(e) { + if (!isMouseLikeTouchEvent(e)) { + clearTimeout(touchFinished); + var now = +new Date; + d.activeTouch = {start: now, moved: false, + prev: now - prevTouch.end <= 300 ? prevTouch : null}; + if (e.touches.length == 1) { + d.activeTouch.left = e.touches[0].pageX; + d.activeTouch.top = e.touches[0].pageY; + } + } + }); + on(d.scroller, "touchmove", function() { + if (d.activeTouch) d.activeTouch.moved = true; + }); + on(d.scroller, "touchend", function(e) { + var touch = d.activeTouch; + if (touch && !eventInWidget(d, e) && touch.left != null && + !touch.moved && new Date - touch.start < 300) { + var pos = cm.coordsChar(d.activeTouch, "page"), range; + if (!touch.prev || farAway(touch, touch.prev)) // Single tap + range = new Range(pos, pos); + else if (!touch.prev.prev || farAway(touch, touch.prev.prev)) // Double tap + range = cm.findWordAt(pos); + else // Triple tap + range = new Range(Pos(pos.line, 0), clipPos(cm.doc, Pos(pos.line + 1, 0))); + cm.setSelection(range.anchor, range.head); + cm.focus(); + e_preventDefault(e); + } + finishTouch(); + }); + on(d.scroller, "touchcancel", finishTouch); + + // Sync scrolling between fake scrollbars and real scrollable + // area, ensure viewport is updated when scrolling. + on(d.scroller, "scroll", function() { + if (d.scroller.clientHeight) { + setScrollTop(cm, d.scroller.scrollTop); + setScrollLeft(cm, d.scroller.scrollLeft, true); + signal(cm, "scroll", cm); + } + }); + + // Listen to wheel events in order to try and update the viewport on time. + on(d.scroller, "mousewheel", function(e){onScrollWheel(cm, e);}); + on(d.scroller, "DOMMouseScroll", function(e){onScrollWheel(cm, e);}); + + // Prevent wrapper from ever scrolling + on(d.wrapper, "scroll", function() { d.wrapper.scrollTop = d.wrapper.scrollLeft = 0; }); + + d.dragFunctions = { + simple: function(e) {if (!signalDOMEvent(cm, e)) e_stop(e);}, + start: function(e){onDragStart(cm, e);}, + drop: operation(cm, onDrop) + }; + + var inp = d.input.getField(); + on(inp, "keyup", function(e) { onKeyUp.call(cm, e); }); + on(inp, "keydown", operation(cm, onKeyDown)); + on(inp, "keypress", operation(cm, onKeyPress)); + on(inp, "focus", bind(onFocus, cm)); + on(inp, "blur", bind(onBlur, cm)); + } + + function dragDropChanged(cm, value, old) { + var wasOn = old && old != CodeMirror.Init; + if (!value != !wasOn) { + var funcs = cm.display.dragFunctions; + var toggle = value ? on : off; + toggle(cm.display.scroller, "dragstart", funcs.start); + toggle(cm.display.scroller, "dragenter", funcs.simple); + toggle(cm.display.scroller, "dragover", funcs.simple); + toggle(cm.display.scroller, "drop", funcs.drop); + } + } + + // Called when the window resizes + function onResize(cm) { + var d = cm.display; + if (d.lastWrapHeight == d.wrapper.clientHeight && d.lastWrapWidth == d.wrapper.clientWidth) + return; + // Might be a text scaling operation, clear size caches. + d.cachedCharWidth = d.cachedTextHeight = d.cachedPaddingH = null; + d.scrollbarsClipped = false; + cm.setSize(); + } + + // MOUSE EVENTS + + // Return true when the given mouse event happened in a widget + function eventInWidget(display, e) { + for (var n = e_target(e); n != display.wrapper; n = n.parentNode) { + if (!n || (n.nodeType == 1 && n.getAttribute("cm-ignore-events") == "true") || + (n.parentNode == display.sizer && n != display.mover)) + return true; + } + } + + // Given a mouse event, find the corresponding position. If liberal + // is false, it checks whether a gutter or scrollbar was clicked, + // and returns null if it was. forRect is used by rectangular + // selections, and tries to estimate a character position even for + // coordinates beyond the right of the text. + function posFromMouse(cm, e, liberal, forRect) { + var display = cm.display; + if (!liberal && e_target(e).getAttribute("cm-not-content") == "true") return null; + + var x, y, space = display.lineSpace.getBoundingClientRect(); + // Fails unpredictably on IE[67] when mouse is dragged around quickly. + try { x = e.clientX - space.left; y = e.clientY - space.top; } + catch (e) { return null; } + var coords = coordsChar(cm, x, y), line; + if (forRect && coords.xRel == 1 && (line = getLine(cm.doc, coords.line).text).length == coords.ch) { + var colDiff = countColumn(line, line.length, cm.options.tabSize) - line.length; + coords = Pos(coords.line, Math.max(0, Math.round((x - paddingH(cm.display).left) / charWidth(cm.display)) - colDiff)); + } + return coords; + } + + // A mouse down can be a single click, double click, triple click, + // start of selection drag, start of text drag, new cursor + // (ctrl-click), rectangle drag (alt-drag), or xwin + // middle-click-paste. Or it might be a click on something we should + // not interfere with, such as a scrollbar or widget. + function onMouseDown(e) { + var cm = this, display = cm.display; + if (display.activeTouch && display.input.supportsTouch() || signalDOMEvent(cm, e)) return; + display.shift = e.shiftKey; + + if (eventInWidget(display, e)) { + if (!webkit) { + // Briefly turn off draggability, to allow widgets to do + // normal dragging things. + display.scroller.draggable = false; + setTimeout(function(){display.scroller.draggable = true;}, 100); + } + return; + } + if (clickInGutter(cm, e)) return; + var start = posFromMouse(cm, e); + window.focus(); + + switch (e_button(e)) { + case 1: + if (start) + leftButtonDown(cm, e, start); + else if (e_target(e) == display.scroller) + e_preventDefault(e); + break; + case 2: + if (webkit) cm.state.lastMiddleDown = +new Date; + if (start) extendSelection(cm.doc, start); + setTimeout(function() {display.input.focus();}, 20); + e_preventDefault(e); + break; + case 3: + if (captureRightClick) onContextMenu(cm, e); + else delayBlurEvent(cm); + break; + } + } + + var lastClick, lastDoubleClick; + function leftButtonDown(cm, e, start) { + if (ie) setTimeout(bind(ensureFocus, cm), 0); + else cm.curOp.focus = activeElt(); + + var now = +new Date, type; + if (lastDoubleClick && lastDoubleClick.time > now - 400 && cmp(lastDoubleClick.pos, start) == 0) { + type = "triple"; + } else if (lastClick && lastClick.time > now - 400 && cmp(lastClick.pos, start) == 0) { + type = "double"; + lastDoubleClick = {time: now, pos: start}; + } else { + type = "single"; + lastClick = {time: now, pos: start}; + } + + var sel = cm.doc.sel, modifier = mac ? e.metaKey : e.ctrlKey, contained; + if (cm.options.dragDrop && dragAndDrop && !isReadOnly(cm) && + type == "single" && (contained = sel.contains(start)) > -1 && + !sel.ranges[contained].empty()) + leftButtonStartDrag(cm, e, start, modifier); + else + leftButtonSelect(cm, e, start, type, modifier); + } + + // Start a text drag. When it ends, see if any dragging actually + // happen, and treat as a click if it didn't. + function leftButtonStartDrag(cm, e, start, modifier) { + var display = cm.display; + var dragEnd = operation(cm, function(e2) { + if (webkit) display.scroller.draggable = false; + cm.state.draggingText = false; + off(document, "mouseup", dragEnd); + off(display.scroller, "drop", dragEnd); + if (Math.abs(e.clientX - e2.clientX) + Math.abs(e.clientY - e2.clientY) < 10) { + e_preventDefault(e2); + if (!modifier) + extendSelection(cm.doc, start); + // Work around unexplainable focus problem in IE9 (#2127) and Chrome (#3081) + if (webkit || ie && ie_version == 9) + setTimeout(function() {document.body.focus(); display.input.focus();}, 20); + else + display.input.focus(); + } + }); + // Let the drag handler handle this. + if (webkit) display.scroller.draggable = true; + cm.state.draggingText = dragEnd; + // IE's approach to draggable + if (display.scroller.dragDrop) display.scroller.dragDrop(); + on(document, "mouseup", dragEnd); + on(display.scroller, "drop", dragEnd); + } + + // Normal selection, as opposed to text dragging. + function leftButtonSelect(cm, e, start, type, addNew) { + var display = cm.display, doc = cm.doc; + e_preventDefault(e); + + var ourRange, ourIndex, startSel = doc.sel, ranges = startSel.ranges; + if (addNew && !e.shiftKey) { + ourIndex = doc.sel.contains(start); + if (ourIndex > -1) + ourRange = ranges[ourIndex]; + else + ourRange = new Range(start, start); + } else { + ourRange = doc.sel.primary(); + ourIndex = doc.sel.primIndex; + } + + if (e.altKey) { + type = "rect"; + if (!addNew) ourRange = new Range(start, start); + start = posFromMouse(cm, e, true, true); + ourIndex = -1; + } else if (type == "double") { + var word = cm.findWordAt(start); + if (cm.display.shift || doc.extend) + ourRange = extendRange(doc, ourRange, word.anchor, word.head); + else + ourRange = word; + } else if (type == "triple") { + var line = new Range(Pos(start.line, 0), clipPos(doc, Pos(start.line + 1, 0))); + if (cm.display.shift || doc.extend) + ourRange = extendRange(doc, ourRange, line.anchor, line.head); + else + ourRange = line; + } else { + ourRange = extendRange(doc, ourRange, start); + } + + if (!addNew) { + ourIndex = 0; + setSelection(doc, new Selection([ourRange], 0), sel_mouse); + startSel = doc.sel; + } else if (ourIndex == -1) { + ourIndex = ranges.length; + setSelection(doc, normalizeSelection(ranges.concat([ourRange]), ourIndex), + {scroll: false, origin: "*mouse"}); + } else if (ranges.length > 1 && ranges[ourIndex].empty() && type == "single" && !e.shiftKey) { + setSelection(doc, normalizeSelection(ranges.slice(0, ourIndex).concat(ranges.slice(ourIndex + 1)), 0)); + startSel = doc.sel; + } else { + replaceOneSelection(doc, ourIndex, ourRange, sel_mouse); + } + + var lastPos = start; + function extendTo(pos) { + if (cmp(lastPos, pos) == 0) return; + lastPos = pos; + + if (type == "rect") { + var ranges = [], tabSize = cm.options.tabSize; + var startCol = countColumn(getLine(doc, start.line).text, start.ch, tabSize); + var posCol = countColumn(getLine(doc, pos.line).text, pos.ch, tabSize); + var left = Math.min(startCol, posCol), right = Math.max(startCol, posCol); + for (var line = Math.min(start.line, pos.line), end = Math.min(cm.lastLine(), Math.max(start.line, pos.line)); + line <= end; line++) { + var text = getLine(doc, line).text, leftPos = findColumn(text, left, tabSize); + if (left == right) + ranges.push(new Range(Pos(line, leftPos), Pos(line, leftPos))); + else if (text.length > leftPos) + ranges.push(new Range(Pos(line, leftPos), Pos(line, findColumn(text, right, tabSize)))); + } + if (!ranges.length) ranges.push(new Range(start, start)); + setSelection(doc, normalizeSelection(startSel.ranges.slice(0, ourIndex).concat(ranges), ourIndex), + {origin: "*mouse", scroll: false}); + cm.scrollIntoView(pos); + } else { + var oldRange = ourRange; + var anchor = oldRange.anchor, head = pos; + if (type != "single") { + if (type == "double") + var range = cm.findWordAt(pos); + else + var range = new Range(Pos(pos.line, 0), clipPos(doc, Pos(pos.line + 1, 0))); + if (cmp(range.anchor, anchor) > 0) { + head = range.head; + anchor = minPos(oldRange.from(), range.anchor); + } else { + head = range.anchor; + anchor = maxPos(oldRange.to(), range.head); + } + } + var ranges = startSel.ranges.slice(0); + ranges[ourIndex] = new Range(clipPos(doc, anchor), head); + setSelection(doc, normalizeSelection(ranges, ourIndex), sel_mouse); + } + } + + var editorSize = display.wrapper.getBoundingClientRect(); + // Used to ensure timeout re-tries don't fire when another extend + // happened in the meantime (clearTimeout isn't reliable -- at + // least on Chrome, the timeouts still happen even when cleared, + // if the clear happens after their scheduled firing time). + var counter = 0; + + function extend(e) { + var curCount = ++counter; + var cur = posFromMouse(cm, e, true, type == "rect"); + if (!cur) return; + if (cmp(cur, lastPos) != 0) { + cm.curOp.focus = activeElt(); + extendTo(cur); + var visible = visibleLines(display, doc); + if (cur.line >= visible.to || cur.line < visible.from) + setTimeout(operation(cm, function(){if (counter == curCount) extend(e);}), 150); + } else { + var outside = e.clientY < editorSize.top ? -20 : e.clientY > editorSize.bottom ? 20 : 0; + if (outside) setTimeout(operation(cm, function() { + if (counter != curCount) return; + display.scroller.scrollTop += outside; + extend(e); + }), 50); + } + } + + function done(e) { + counter = Infinity; + e_preventDefault(e); + display.input.focus(); + off(document, "mousemove", move); + off(document, "mouseup", up); + doc.history.lastSelOrigin = null; + } + + var move = operation(cm, function(e) { + if (!e_button(e)) done(e); + else extend(e); + }); + var up = operation(cm, done); + on(document, "mousemove", move); + on(document, "mouseup", up); + } + + // Determines whether an event happened in the gutter, and fires the + // handlers for the corresponding event. + function gutterEvent(cm, e, type, prevent, signalfn) { + try { var mX = e.clientX, mY = e.clientY; } + catch(e) { return false; } + if (mX >= Math.floor(cm.display.gutters.getBoundingClientRect().right)) return false; + if (prevent) e_preventDefault(e); + + var display = cm.display; + var lineBox = display.lineDiv.getBoundingClientRect(); + + if (mY > lineBox.bottom || !hasHandler(cm, type)) return e_defaultPrevented(e); + mY -= lineBox.top - display.viewOffset; + + for (var i = 0; i < cm.options.gutters.length; ++i) { + var g = display.gutters.childNodes[i]; + if (g && g.getBoundingClientRect().right >= mX) { + var line = lineAtHeight(cm.doc, mY); + var gutter = cm.options.gutters[i]; + signalfn(cm, type, cm, line, gutter, e); + return e_defaultPrevented(e); + } + } + } + + function clickInGutter(cm, e) { + return gutterEvent(cm, e, "gutterClick", true, signalLater); + } + + // Kludge to work around strange IE behavior where it'll sometimes + // re-fire a series of drag-related events right after the drop (#1551) + var lastDrop = 0; + + function onDrop(e) { + var cm = this; + if (signalDOMEvent(cm, e) || eventInWidget(cm.display, e)) + return; + e_preventDefault(e); + if (ie) lastDrop = +new Date; + var pos = posFromMouse(cm, e, true), files = e.dataTransfer.files; + if (!pos || isReadOnly(cm)) return; + // Might be a file drop, in which case we simply extract the text + // and insert it. + if (files && files.length && window.FileReader && window.File) { + var n = files.length, text = Array(n), read = 0; + var loadFile = function(file, i) { + var reader = new FileReader; + reader.onload = operation(cm, function() { + text[i] = reader.result; + if (++read == n) { + pos = clipPos(cm.doc, pos); + var change = {from: pos, to: pos, text: splitLines(text.join("\n")), origin: "paste"}; + makeChange(cm.doc, change); + setSelectionReplaceHistory(cm.doc, simpleSelection(pos, changeEnd(change))); + } + }); + reader.readAsText(file); + }; + for (var i = 0; i < n; ++i) loadFile(files[i], i); + } else { // Normal drop + // Don't do a replace if the drop happened inside of the selected text. + if (cm.state.draggingText && cm.doc.sel.contains(pos) > -1) { + cm.state.draggingText(e); + // Ensure the editor is re-focused + setTimeout(function() {cm.display.input.focus();}, 20); + return; + } + try { + var text = e.dataTransfer.getData("Text"); + if (text) { + if (cm.state.draggingText && !(mac ? e.altKey : e.ctrlKey)) + var selected = cm.listSelections(); + setSelectionNoUndo(cm.doc, simpleSelection(pos, pos)); + if (selected) for (var i = 0; i < selected.length; ++i) + replaceRange(cm.doc, "", selected[i].anchor, selected[i].head, "drag"); + cm.replaceSelection(text, "around", "paste"); + cm.display.input.focus(); + } + } + catch(e){} + } + } + + function onDragStart(cm, e) { + if (ie && (!cm.state.draggingText || +new Date - lastDrop < 100)) { e_stop(e); return; } + if (signalDOMEvent(cm, e) || eventInWidget(cm.display, e)) return; + + e.dataTransfer.setData("Text", cm.getSelection()); + + // Use dummy image instead of default browsers image. + // Recent Safari (~6.0.2) have a tendency to segfault when this happens, so we don't do it there. + if (e.dataTransfer.setDragImage && !safari) { + var img = elt("img", null, null, "position: fixed; left: 0; top: 0;"); + img.src = "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="; + if (presto) { + img.width = img.height = 1; + cm.display.wrapper.appendChild(img); + // Force a relayout, or Opera won't use our image for some obscure reason + img._top = img.offsetTop; + } + e.dataTransfer.setDragImage(img, 0, 0); + if (presto) img.parentNode.removeChild(img); + } + } + + // SCROLL EVENTS + + // Sync the scrollable area and scrollbars, ensure the viewport + // covers the visible area. + function setScrollTop(cm, val) { + if (Math.abs(cm.doc.scrollTop - val) < 2) return; + cm.doc.scrollTop = val; + if (!gecko) updateDisplaySimple(cm, {top: val}); + if (cm.display.scroller.scrollTop != val) cm.display.scroller.scrollTop = val; + cm.display.scrollbars.setScrollTop(val); + if (gecko) updateDisplaySimple(cm); + startWorker(cm, 100); + } + // Sync scroller and scrollbar, ensure the gutter elements are + // aligned. + function setScrollLeft(cm, val, isScroller) { + if (isScroller ? val == cm.doc.scrollLeft : Math.abs(cm.doc.scrollLeft - val) < 2) return; + val = Math.min(val, cm.display.scroller.scrollWidth - cm.display.scroller.clientWidth); + cm.doc.scrollLeft = val; + alignHorizontally(cm); + if (cm.display.scroller.scrollLeft != val) cm.display.scroller.scrollLeft = val; + cm.display.scrollbars.setScrollLeft(val); + } + + // Since the delta values reported on mouse wheel events are + // unstandardized between browsers and even browser versions, and + // generally horribly unpredictable, this code starts by measuring + // the scroll effect that the first few mouse wheel events have, + // and, from that, detects the way it can convert deltas to pixel + // offsets afterwards. + // + // The reason we want to know the amount a wheel event will scroll + // is that it gives us a chance to update the display before the + // actual scrolling happens, reducing flickering. + + var wheelSamples = 0, wheelPixelsPerUnit = null; + // Fill in a browser-detected starting value on browsers where we + // know one. These don't have to be accurate -- the result of them + // being wrong would just be a slight flicker on the first wheel + // scroll (if it is large enough). + if (ie) wheelPixelsPerUnit = -.53; + else if (gecko) wheelPixelsPerUnit = 15; + else if (chrome) wheelPixelsPerUnit = -.7; + else if (safari) wheelPixelsPerUnit = -1/3; + + var wheelEventDelta = function(e) { + var dx = e.wheelDeltaX, dy = e.wheelDeltaY; + if (dx == null && e.detail && e.axis == e.HORIZONTAL_AXIS) dx = e.detail; + if (dy == null && e.detail && e.axis == e.VERTICAL_AXIS) dy = e.detail; + else if (dy == null) dy = e.wheelDelta; + return {x: dx, y: dy}; + }; + CodeMirror.wheelEventPixels = function(e) { + var delta = wheelEventDelta(e); + delta.x *= wheelPixelsPerUnit; + delta.y *= wheelPixelsPerUnit; + return delta; + }; + + function onScrollWheel(cm, e) { + var delta = wheelEventDelta(e), dx = delta.x, dy = delta.y; + + var display = cm.display, scroll = display.scroller; + // Quit if there's nothing to scroll here + if (!(dx && scroll.scrollWidth > scroll.clientWidth || + dy && scroll.scrollHeight > scroll.clientHeight)) return; + + // Webkit browsers on OS X abort momentum scrolls when the target + // of the scroll event is removed from the scrollable element. + // This hack (see related code in patchDisplay) makes sure the + // element is kept around. + if (dy && mac && webkit) { + outer: for (var cur = e.target, view = display.view; cur != scroll; cur = cur.parentNode) { + for (var i = 0; i < view.length; i++) { + if (view[i].node == cur) { + cm.display.currentWheelTarget = cur; + break outer; + } + } + } + } + + // On some browsers, horizontal scrolling will cause redraws to + // happen before the gutter has been realigned, causing it to + // wriggle around in a most unseemly way. When we have an + // estimated pixels/delta value, we just handle horizontal + // scrolling entirely here. It'll be slightly off from native, but + // better than glitching out. + if (dx && !gecko && !presto && wheelPixelsPerUnit != null) { + if (dy) + setScrollTop(cm, Math.max(0, Math.min(scroll.scrollTop + dy * wheelPixelsPerUnit, scroll.scrollHeight - scroll.clientHeight))); + setScrollLeft(cm, Math.max(0, Math.min(scroll.scrollLeft + dx * wheelPixelsPerUnit, scroll.scrollWidth - scroll.clientWidth))); + e_preventDefault(e); + display.wheelStartX = null; // Abort measurement, if in progress + return; + } + + // 'Project' the visible viewport to cover the area that is being + // scrolled into view (if we know enough to estimate it). + if (dy && wheelPixelsPerUnit != null) { + var pixels = dy * wheelPixelsPerUnit; + var top = cm.doc.scrollTop, bot = top + display.wrapper.clientHeight; + if (pixels < 0) top = Math.max(0, top + pixels - 50); + else bot = Math.min(cm.doc.height, bot + pixels + 50); + updateDisplaySimple(cm, {top: top, bottom: bot}); + } + + if (wheelSamples < 20) { + if (display.wheelStartX == null) { + display.wheelStartX = scroll.scrollLeft; display.wheelStartY = scroll.scrollTop; + display.wheelDX = dx; display.wheelDY = dy; + setTimeout(function() { + if (display.wheelStartX == null) return; + var movedX = scroll.scrollLeft - display.wheelStartX; + var movedY = scroll.scrollTop - display.wheelStartY; + var sample = (movedY && display.wheelDY && movedY / display.wheelDY) || + (movedX && display.wheelDX && movedX / display.wheelDX); + display.wheelStartX = display.wheelStartY = null; + if (!sample) return; + wheelPixelsPerUnit = (wheelPixelsPerUnit * wheelSamples + sample) / (wheelSamples + 1); + ++wheelSamples; + }, 200); + } else { + display.wheelDX += dx; display.wheelDY += dy; + } + } + } + + // KEY EVENTS + + // Run a handler that was bound to a key. + function doHandleBinding(cm, bound, dropShift) { + if (typeof bound == "string") { + bound = commands[bound]; + if (!bound) return false; + } + // Ensure previous input has been read, so that the handler sees a + // consistent view of the document + cm.display.input.ensurePolled(); + var prevShift = cm.display.shift, done = false; + try { + if (isReadOnly(cm)) cm.state.suppressEdits = true; + if (dropShift) cm.display.shift = false; + done = bound(cm) != Pass; + } finally { + cm.display.shift = prevShift; + cm.state.suppressEdits = false; + } + return done; + } + + function lookupKeyForEditor(cm, name, handle) { + for (var i = 0; i < cm.state.keyMaps.length; i++) { + var result = lookupKey(name, cm.state.keyMaps[i], handle, cm); + if (result) return result; + } + return (cm.options.extraKeys && lookupKey(name, cm.options.extraKeys, handle, cm)) + || lookupKey(name, cm.options.keyMap, handle, cm); + } + + var stopSeq = new Delayed; + function dispatchKey(cm, name, e, handle) { + var seq = cm.state.keySeq; + if (seq) { + if (isModifierKey(name)) return "handled"; + stopSeq.set(50, function() { + if (cm.state.keySeq == seq) { + cm.state.keySeq = null; + cm.display.input.reset(); + } + }); + name = seq + " " + name; + } + var result = lookupKeyForEditor(cm, name, handle); + + if (result == "multi") + cm.state.keySeq = name; + if (result == "handled") + signalLater(cm, "keyHandled", cm, name, e); + + if (result == "handled" || result == "multi") { + e_preventDefault(e); + restartBlink(cm); + } + + if (seq && !result && /\'$/.test(name)) { + e_preventDefault(e); + return true; + } + return !!result; + } + + // Handle a key from the keydown event. + function handleKeyBinding(cm, e) { + var name = keyName(e, true); + if (!name) return false; + + if (e.shiftKey && !cm.state.keySeq) { + // First try to resolve full name (including 'Shift-'). Failing + // that, see if there is a cursor-motion command (starting with + // 'go') bound to the keyname without 'Shift-'. + return dispatchKey(cm, "Shift-" + name, e, function(b) {return doHandleBinding(cm, b, true);}) + || dispatchKey(cm, name, e, function(b) { + if (typeof b == "string" ? /^go[A-Z]/.test(b) : b.motion) + return doHandleBinding(cm, b); + }); + } else { + return dispatchKey(cm, name, e, function(b) { return doHandleBinding(cm, b); }); + } + } + + // Handle a key from the keypress event + function handleCharBinding(cm, e, ch) { + return dispatchKey(cm, "'" + ch + "'", e, + function(b) { return doHandleBinding(cm, b, true); }); + } + + var lastStoppedKey = null; + function onKeyDown(e) { + var cm = this; + cm.curOp.focus = activeElt(); + if (signalDOMEvent(cm, e)) return; + // IE does strange things with escape. + if (ie && ie_version < 11 && e.keyCode == 27) e.returnValue = false; + var code = e.keyCode; + cm.display.shift = code == 16 || e.shiftKey; + var handled = handleKeyBinding(cm, e); + if (presto) { + lastStoppedKey = handled ? code : null; + // Opera has no cut event... we try to at least catch the key combo + if (!handled && code == 88 && !hasCopyEvent && (mac ? e.metaKey : e.ctrlKey)) + cm.replaceSelection("", null, "cut"); + } + + // Turn mouse into crosshair when Alt is held on Mac. + if (code == 18 && !/\bCodeMirror-crosshair\b/.test(cm.display.lineDiv.className)) + showCrossHair(cm); + } + + function showCrossHair(cm) { + var lineDiv = cm.display.lineDiv; + addClass(lineDiv, "CodeMirror-crosshair"); + + function up(e) { + if (e.keyCode == 18 || !e.altKey) { + rmClass(lineDiv, "CodeMirror-crosshair"); + off(document, "keyup", up); + off(document, "mouseover", up); + } + } + on(document, "keyup", up); + on(document, "mouseover", up); + } + + function onKeyUp(e) { + if (e.keyCode == 16) this.doc.sel.shift = false; + signalDOMEvent(this, e); + } + + function onKeyPress(e) { + var cm = this; + if (eventInWidget(cm.display, e) || signalDOMEvent(cm, e) || e.ctrlKey && !e.altKey || mac && e.metaKey) return; + var keyCode = e.keyCode, charCode = e.charCode; + if (presto && keyCode == lastStoppedKey) {lastStoppedKey = null; e_preventDefault(e); return;} + if ((presto && (!e.which || e.which < 10)) && handleKeyBinding(cm, e)) return; + var ch = String.fromCharCode(charCode == null ? keyCode : charCode); + if (handleCharBinding(cm, e, ch)) return; + cm.display.input.onKeyPress(e); + } + + // FOCUS/BLUR EVENTS + + function delayBlurEvent(cm) { + cm.state.delayingBlurEvent = true; + setTimeout(function() { + if (cm.state.delayingBlurEvent) { + cm.state.delayingBlurEvent = false; + onBlur(cm); + } + }, 100); + } + + function onFocus(cm) { + if (cm.state.delayingBlurEvent) cm.state.delayingBlurEvent = false; + + if (cm.options.readOnly == "nocursor") return; + if (!cm.state.focused) { + signal(cm, "focus", cm); + cm.state.focused = true; + addClass(cm.display.wrapper, "CodeMirror-focused"); + // This test prevents this from firing when a context + // menu is closed (since the input reset would kill the + // select-all detection hack) + if (!cm.curOp && cm.display.selForContextMenu != cm.doc.sel) { + cm.display.input.reset(); + if (webkit) setTimeout(function() { cm.display.input.reset(true); }, 20); // Issue #1730 + } + cm.display.input.receivedFocus(); + } + restartBlink(cm); + } + function onBlur(cm) { + if (cm.state.delayingBlurEvent) return; + + if (cm.state.focused) { + signal(cm, "blur", cm); + cm.state.focused = false; + rmClass(cm.display.wrapper, "CodeMirror-focused"); + } + clearInterval(cm.display.blinker); + setTimeout(function() {if (!cm.state.focused) cm.display.shift = false;}, 150); + } + + // CONTEXT MENU HANDLING + + // To make the context menu work, we need to briefly unhide the + // textarea (making it as unobtrusive as possible) to let the + // right-click take effect on it. + function onContextMenu(cm, e) { + if (eventInWidget(cm.display, e) || contextMenuInGutter(cm, e)) return; + cm.display.input.onContextMenu(e); + } + + function contextMenuInGutter(cm, e) { + if (!hasHandler(cm, "gutterContextMenu")) return false; + return gutterEvent(cm, e, "gutterContextMenu", false, signal); + } + + // UPDATING + + // Compute the position of the end of a change (its 'to' property + // refers to the pre-change end). + var changeEnd = CodeMirror.changeEnd = function(change) { + if (!change.text) return change.to; + return Pos(change.from.line + change.text.length - 1, + lst(change.text).length + (change.text.length == 1 ? change.from.ch : 0)); + }; + + // Adjust a position to refer to the post-change position of the + // same text, or the end of the change if the change covers it. + function adjustForChange(pos, change) { + if (cmp(pos, change.from) < 0) return pos; + if (cmp(pos, change.to) <= 0) return changeEnd(change); + + var line = pos.line + change.text.length - (change.to.line - change.from.line) - 1, ch = pos.ch; + if (pos.line == change.to.line) ch += changeEnd(change).ch - change.to.ch; + return Pos(line, ch); + } + + function computeSelAfterChange(doc, change) { + var out = []; + for (var i = 0; i < doc.sel.ranges.length; i++) { + var range = doc.sel.ranges[i]; + out.push(new Range(adjustForChange(range.anchor, change), + adjustForChange(range.head, change))); + } + return normalizeSelection(out, doc.sel.primIndex); + } + + function offsetPos(pos, old, nw) { + if (pos.line == old.line) + return Pos(nw.line, pos.ch - old.ch + nw.ch); + else + return Pos(nw.line + (pos.line - old.line), pos.ch); + } + + // Used by replaceSelections to allow moving the selection to the + // start or around the replaced test. Hint may be "start" or "around". + function computeReplacedSel(doc, changes, hint) { + var out = []; + var oldPrev = Pos(doc.first, 0), newPrev = oldPrev; + for (var i = 0; i < changes.length; i++) { + var change = changes[i]; + var from = offsetPos(change.from, oldPrev, newPrev); + var to = offsetPos(changeEnd(change), oldPrev, newPrev); + oldPrev = change.to; + newPrev = to; + if (hint == "around") { + var range = doc.sel.ranges[i], inv = cmp(range.head, range.anchor) < 0; + out[i] = new Range(inv ? to : from, inv ? from : to); + } else { + out[i] = new Range(from, from); + } + } + return new Selection(out, doc.sel.primIndex); + } + + // Allow "beforeChange" event handlers to influence a change + function filterChange(doc, change, update) { + var obj = { + canceled: false, + from: change.from, + to: change.to, + text: change.text, + origin: change.origin, + cancel: function() { this.canceled = true; } + }; + if (update) obj.update = function(from, to, text, origin) { + if (from) this.from = clipPos(doc, from); + if (to) this.to = clipPos(doc, to); + if (text) this.text = text; + if (origin !== undefined) this.origin = origin; + }; + signal(doc, "beforeChange", doc, obj); + if (doc.cm) signal(doc.cm, "beforeChange", doc.cm, obj); + + if (obj.canceled) return null; + return {from: obj.from, to: obj.to, text: obj.text, origin: obj.origin}; + } + + // Apply a change to a document, and add it to the document's + // history, and propagating it to all linked documents. + function makeChange(doc, change, ignoreReadOnly) { + if (doc.cm) { + if (!doc.cm.curOp) return operation(doc.cm, makeChange)(doc, change, ignoreReadOnly); + if (doc.cm.state.suppressEdits) return; + } + + if (hasHandler(doc, "beforeChange") || doc.cm && hasHandler(doc.cm, "beforeChange")) { + change = filterChange(doc, change, true); + if (!change) return; + } + + // Possibly split or suppress the update based on the presence + // of read-only spans in its range. + var split = sawReadOnlySpans && !ignoreReadOnly && removeReadOnlyRanges(doc, change.from, change.to); + if (split) { + for (var i = split.length - 1; i >= 0; --i) + makeChangeInner(doc, {from: split[i].from, to: split[i].to, text: i ? [""] : change.text}); + } else { + makeChangeInner(doc, change); + } + } + + function makeChangeInner(doc, change) { + if (change.text.length == 1 && change.text[0] == "" && cmp(change.from, change.to) == 0) return; + var selAfter = computeSelAfterChange(doc, change); + addChangeToHistory(doc, change, selAfter, doc.cm ? doc.cm.curOp.id : NaN); + + makeChangeSingleDoc(doc, change, selAfter, stretchSpansOverChange(doc, change)); + var rebased = []; + + linkedDocs(doc, function(doc, sharedHist) { + if (!sharedHist && indexOf(rebased, doc.history) == -1) { + rebaseHist(doc.history, change); + rebased.push(doc.history); + } + makeChangeSingleDoc(doc, change, null, stretchSpansOverChange(doc, change)); + }); + } + + // Revert a change stored in a document's history. + function makeChangeFromHistory(doc, type, allowSelectionOnly) { + if (doc.cm && doc.cm.state.suppressEdits) return; + + var hist = doc.history, event, selAfter = doc.sel; + var source = type == "undo" ? hist.done : hist.undone, dest = type == "undo" ? hist.undone : hist.done; + + // Verify that there is a useable event (so that ctrl-z won't + // needlessly clear selection events) + for (var i = 0; i < source.length; i++) { + event = source[i]; + if (allowSelectionOnly ? event.ranges && !event.equals(doc.sel) : !event.ranges) + break; + } + if (i == source.length) return; + hist.lastOrigin = hist.lastSelOrigin = null; + + for (;;) { + event = source.pop(); + if (event.ranges) { + pushSelectionToHistory(event, dest); + if (allowSelectionOnly && !event.equals(doc.sel)) { + setSelection(doc, event, {clearRedo: false}); + return; + } + selAfter = event; + } + else break; + } + + // Build up a reverse change object to add to the opposite history + // stack (redo when undoing, and vice versa). + var antiChanges = []; + pushSelectionToHistory(selAfter, dest); + dest.push({changes: antiChanges, generation: hist.generation}); + hist.generation = event.generation || ++hist.maxGeneration; + + var filter = hasHandler(doc, "beforeChange") || doc.cm && hasHandler(doc.cm, "beforeChange"); + + for (var i = event.changes.length - 1; i >= 0; --i) { + var change = event.changes[i]; + change.origin = type; + if (filter && !filterChange(doc, change, false)) { + source.length = 0; + return; + } + + antiChanges.push(historyChangeFromChange(doc, change)); + + var after = i ? computeSelAfterChange(doc, change) : lst(source); + makeChangeSingleDoc(doc, change, after, mergeOldSpans(doc, change)); + if (!i && doc.cm) doc.cm.scrollIntoView({from: change.from, to: changeEnd(change)}); + var rebased = []; + + // Propagate to the linked documents + linkedDocs(doc, function(doc, sharedHist) { + if (!sharedHist && indexOf(rebased, doc.history) == -1) { + rebaseHist(doc.history, change); + rebased.push(doc.history); + } + makeChangeSingleDoc(doc, change, null, mergeOldSpans(doc, change)); + }); + } + } + + // Sub-views need their line numbers shifted when text is added + // above or below them in the parent document. + function shiftDoc(doc, distance) { + if (distance == 0) return; + doc.first += distance; + doc.sel = new Selection(map(doc.sel.ranges, function(range) { + return new Range(Pos(range.anchor.line + distance, range.anchor.ch), + Pos(range.head.line + distance, range.head.ch)); + }), doc.sel.primIndex); + if (doc.cm) { + regChange(doc.cm, doc.first, doc.first - distance, distance); + for (var d = doc.cm.display, l = d.viewFrom; l < d.viewTo; l++) + regLineChange(doc.cm, l, "gutter"); + } + } + + // More lower-level change function, handling only a single document + // (not linked ones). + function makeChangeSingleDoc(doc, change, selAfter, spans) { + if (doc.cm && !doc.cm.curOp) + return operation(doc.cm, makeChangeSingleDoc)(doc, change, selAfter, spans); + + if (change.to.line < doc.first) { + shiftDoc(doc, change.text.length - 1 - (change.to.line - change.from.line)); + return; + } + if (change.from.line > doc.lastLine()) return; + + // Clip the change to the size of this doc + if (change.from.line < doc.first) { + var shift = change.text.length - 1 - (doc.first - change.from.line); + shiftDoc(doc, shift); + change = {from: Pos(doc.first, 0), to: Pos(change.to.line + shift, change.to.ch), + text: [lst(change.text)], origin: change.origin}; + } + var last = doc.lastLine(); + if (change.to.line > last) { + change = {from: change.from, to: Pos(last, getLine(doc, last).text.length), + text: [change.text[0]], origin: change.origin}; + } + + change.removed = getBetween(doc, change.from, change.to); + + if (!selAfter) selAfter = computeSelAfterChange(doc, change); + if (doc.cm) makeChangeSingleDocInEditor(doc.cm, change, spans); + else updateDoc(doc, change, spans); + setSelectionNoUndo(doc, selAfter, sel_dontScroll); + } + + // Handle the interaction of a change to a document with the editor + // that this document is part of. + function makeChangeSingleDocInEditor(cm, change, spans) { + var doc = cm.doc, display = cm.display, from = change.from, to = change.to; + + var recomputeMaxLength = false, checkWidthStart = from.line; + if (!cm.options.lineWrapping) { + checkWidthStart = lineNo(visualLine(getLine(doc, from.line))); + doc.iter(checkWidthStart, to.line + 1, function(line) { + if (line == display.maxLine) { + recomputeMaxLength = true; + return true; + } + }); + } + + if (doc.sel.contains(change.from, change.to) > -1) + signalCursorActivity(cm); + + updateDoc(doc, change, spans, estimateHeight(cm)); + + if (!cm.options.lineWrapping) { + doc.iter(checkWidthStart, from.line + change.text.length, function(line) { + var len = lineLength(line); + if (len > display.maxLineLength) { + display.maxLine = line; + display.maxLineLength = len; + display.maxLineChanged = true; + recomputeMaxLength = false; + } + }); + if (recomputeMaxLength) cm.curOp.updateMaxLine = true; + } + + // Adjust frontier, schedule worker + doc.frontier = Math.min(doc.frontier, from.line); + startWorker(cm, 400); + + var lendiff = change.text.length - (to.line - from.line) - 1; + // Remember that these lines changed, for updating the display + if (change.full) + regChange(cm); + else if (from.line == to.line && change.text.length == 1 && !isWholeLineUpdate(cm.doc, change)) + regLineChange(cm, from.line, "text"); + else + regChange(cm, from.line, to.line + 1, lendiff); + + var changesHandler = hasHandler(cm, "changes"), changeHandler = hasHandler(cm, "change"); + if (changeHandler || changesHandler) { + var obj = { + from: from, to: to, + text: change.text, + removed: change.removed, + origin: change.origin + }; + if (changeHandler) signalLater(cm, "change", cm, obj); + if (changesHandler) (cm.curOp.changeObjs || (cm.curOp.changeObjs = [])).push(obj); + } + cm.display.selForContextMenu = null; + } + + function replaceRange(doc, code, from, to, origin) { + if (!to) to = from; + if (cmp(to, from) < 0) { var tmp = to; to = from; from = tmp; } + if (typeof code == "string") code = splitLines(code); + makeChange(doc, {from: from, to: to, text: code, origin: origin}); + } + + // SCROLLING THINGS INTO VIEW + + // If an editor sits on the top or bottom of the window, partially + // scrolled out of view, this ensures that the cursor is visible. + function maybeScrollWindow(cm, coords) { + if (signalDOMEvent(cm, "scrollCursorIntoView")) return; + + var display = cm.display, box = display.sizer.getBoundingClientRect(), doScroll = null; + if (coords.top + box.top < 0) doScroll = true; + else if (coords.bottom + box.top > (window.innerHeight || document.documentElement.clientHeight)) doScroll = false; + if (doScroll != null && !phantom) { + var scrollNode = elt("div", "\u200b", null, "position: absolute; top: " + + (coords.top - display.viewOffset - paddingTop(cm.display)) + "px; height: " + + (coords.bottom - coords.top + scrollGap(cm) + display.barHeight) + "px; left: " + + coords.left + "px; width: 2px;"); + cm.display.lineSpace.appendChild(scrollNode); + scrollNode.scrollIntoView(doScroll); + cm.display.lineSpace.removeChild(scrollNode); + } + } + + // Scroll a given position into view (immediately), verifying that + // it actually became visible (as line heights are accurately + // measured, the position of something may 'drift' during drawing). + function scrollPosIntoView(cm, pos, end, margin) { + if (margin == null) margin = 0; + for (var limit = 0; limit < 5; limit++) { + var changed = false, coords = cursorCoords(cm, pos); + var endCoords = !end || end == pos ? coords : cursorCoords(cm, end); + var scrollPos = calculateScrollPos(cm, Math.min(coords.left, endCoords.left), + Math.min(coords.top, endCoords.top) - margin, + Math.max(coords.left, endCoords.left), + Math.max(coords.bottom, endCoords.bottom) + margin); + var startTop = cm.doc.scrollTop, startLeft = cm.doc.scrollLeft; + if (scrollPos.scrollTop != null) { + setScrollTop(cm, scrollPos.scrollTop); + if (Math.abs(cm.doc.scrollTop - startTop) > 1) changed = true; + } + if (scrollPos.scrollLeft != null) { + setScrollLeft(cm, scrollPos.scrollLeft); + if (Math.abs(cm.doc.scrollLeft - startLeft) > 1) changed = true; + } + if (!changed) break; + } + return coords; + } + + // Scroll a given set of coordinates into view (immediately). + function scrollIntoView(cm, x1, y1, x2, y2) { + var scrollPos = calculateScrollPos(cm, x1, y1, x2, y2); + if (scrollPos.scrollTop != null) setScrollTop(cm, scrollPos.scrollTop); + if (scrollPos.scrollLeft != null) setScrollLeft(cm, scrollPos.scrollLeft); + } + + // Calculate a new scroll position needed to scroll the given + // rectangle into view. Returns an object with scrollTop and + // scrollLeft properties. When these are undefined, the + // vertical/horizontal position does not need to be adjusted. + function calculateScrollPos(cm, x1, y1, x2, y2) { + var display = cm.display, snapMargin = textHeight(cm.display); + if (y1 < 0) y1 = 0; + var screentop = cm.curOp && cm.curOp.scrollTop != null ? cm.curOp.scrollTop : display.scroller.scrollTop; + var screen = displayHeight(cm), result = {}; + if (y2 - y1 > screen) y2 = y1 + screen; + var docBottom = cm.doc.height + paddingVert(display); + var atTop = y1 < snapMargin, atBottom = y2 > docBottom - snapMargin; + if (y1 < screentop) { + result.scrollTop = atTop ? 0 : y1; + } else if (y2 > screentop + screen) { + var newTop = Math.min(y1, (atBottom ? docBottom : y2) - screen); + if (newTop != screentop) result.scrollTop = newTop; + } + + var screenleft = cm.curOp && cm.curOp.scrollLeft != null ? cm.curOp.scrollLeft : display.scroller.scrollLeft; + var screenw = displayWidth(cm) - (cm.options.fixedGutter ? display.gutters.offsetWidth : 0); + var tooWide = x2 - x1 > screenw; + if (tooWide) x2 = x1 + screenw; + if (x1 < 10) + result.scrollLeft = 0; + else if (x1 < screenleft) + result.scrollLeft = Math.max(0, x1 - (tooWide ? 0 : 10)); + else if (x2 > screenw + screenleft - 3) + result.scrollLeft = x2 + (tooWide ? 0 : 10) - screenw; + return result; + } + + // Store a relative adjustment to the scroll position in the current + // operation (to be applied when the operation finishes). + function addToScrollPos(cm, left, top) { + if (left != null || top != null) resolveScrollToPos(cm); + if (left != null) + cm.curOp.scrollLeft = (cm.curOp.scrollLeft == null ? cm.doc.scrollLeft : cm.curOp.scrollLeft) + left; + if (top != null) + cm.curOp.scrollTop = (cm.curOp.scrollTop == null ? cm.doc.scrollTop : cm.curOp.scrollTop) + top; + } + + // Make sure that at the end of the operation the current cursor is + // shown. + function ensureCursorVisible(cm) { + resolveScrollToPos(cm); + var cur = cm.getCursor(), from = cur, to = cur; + if (!cm.options.lineWrapping) { + from = cur.ch ? Pos(cur.line, cur.ch - 1) : cur; + to = Pos(cur.line, cur.ch + 1); + } + cm.curOp.scrollToPos = {from: from, to: to, margin: cm.options.cursorScrollMargin, isCursor: true}; + } + + // When an operation has its scrollToPos property set, and another + // scroll action is applied before the end of the operation, this + // 'simulates' scrolling that position into view in a cheap way, so + // that the effect of intermediate scroll commands is not ignored. + function resolveScrollToPos(cm) { + var range = cm.curOp.scrollToPos; + if (range) { + cm.curOp.scrollToPos = null; + var from = estimateCoords(cm, range.from), to = estimateCoords(cm, range.to); + var sPos = calculateScrollPos(cm, Math.min(from.left, to.left), + Math.min(from.top, to.top) - range.margin, + Math.max(from.right, to.right), + Math.max(from.bottom, to.bottom) + range.margin); + cm.scrollTo(sPos.scrollLeft, sPos.scrollTop); + } + } + + // API UTILITIES + + // Indent the given line. The how parameter can be "smart", + // "add"/null, "subtract", or "prev". When aggressive is false + // (typically set to true for forced single-line indents), empty + // lines are not indented, and places where the mode returns Pass + // are left alone. + function indentLine(cm, n, how, aggressive) { + var doc = cm.doc, state; + if (how == null) how = "add"; + if (how == "smart") { + // Fall back to "prev" when the mode doesn't have an indentation + // method. + if (!doc.mode.indent) how = "prev"; + else state = getStateBefore(cm, n); + } + + var tabSize = cm.options.tabSize; + var line = getLine(doc, n), curSpace = countColumn(line.text, null, tabSize); + if (line.stateAfter) line.stateAfter = null; + var curSpaceString = line.text.match(/^\s*/)[0], indentation; + if (!aggressive && !/\S/.test(line.text)) { + indentation = 0; + how = "not"; + } else if (how == "smart") { + indentation = doc.mode.indent(state, line.text.slice(curSpaceString.length), line.text); + if (indentation == Pass || indentation > 150) { + if (!aggressive) return; + how = "prev"; + } + } + if (how == "prev") { + if (n > doc.first) indentation = countColumn(getLine(doc, n-1).text, null, tabSize); + else indentation = 0; + } else if (how == "add") { + indentation = curSpace + cm.options.indentUnit; + } else if (how == "subtract") { + indentation = curSpace - cm.options.indentUnit; + } else if (typeof how == "number") { + indentation = curSpace + how; + } + indentation = Math.max(0, indentation); + + var indentString = "", pos = 0; + if (cm.options.indentWithTabs) + for (var i = Math.floor(indentation / tabSize); i; --i) {pos += tabSize; indentString += "\t";} + if (pos < indentation) indentString += spaceStr(indentation - pos); + + if (indentString != curSpaceString) { + replaceRange(doc, indentString, Pos(n, 0), Pos(n, curSpaceString.length), "+input"); + } else { + // Ensure that, if the cursor was in the whitespace at the start + // of the line, it is moved to the end of that space. + for (var i = 0; i < doc.sel.ranges.length; i++) { + var range = doc.sel.ranges[i]; + if (range.head.line == n && range.head.ch < curSpaceString.length) { + var pos = Pos(n, curSpaceString.length); + replaceOneSelection(doc, i, new Range(pos, pos)); + break; + } + } + } + line.stateAfter = null; + } + + // Utility for applying a change to a line by handle or number, + // returning the number and optionally registering the line as + // changed. + function changeLine(doc, handle, changeType, op) { + var no = handle, line = handle; + if (typeof handle == "number") line = getLine(doc, clipLine(doc, handle)); + else no = lineNo(handle); + if (no == null) return null; + if (op(line, no) && doc.cm) regLineChange(doc.cm, no, changeType); + return line; + } + + // Helper for deleting text near the selection(s), used to implement + // backspace, delete, and similar functionality. + function deleteNearSelection(cm, compute) { + var ranges = cm.doc.sel.ranges, kill = []; + // Build up a set of ranges to kill first, merging overlapping + // ranges. + for (var i = 0; i < ranges.length; i++) { + var toKill = compute(ranges[i]); + while (kill.length && cmp(toKill.from, lst(kill).to) <= 0) { + var replaced = kill.pop(); + if (cmp(replaced.from, toKill.from) < 0) { + toKill.from = replaced.from; + break; + } + } + kill.push(toKill); + } + // Next, remove those actual ranges. + runInOp(cm, function() { + for (var i = kill.length - 1; i >= 0; i--) + replaceRange(cm.doc, "", kill[i].from, kill[i].to, "+delete"); + ensureCursorVisible(cm); + }); + } + + // Used for horizontal relative motion. Dir is -1 or 1 (left or + // right), unit can be "char", "column" (like char, but doesn't + // cross line boundaries), "word" (across next word), or "group" (to + // the start of next group of word or non-word-non-whitespace + // chars). The visually param controls whether, in right-to-left + // text, direction 1 means to move towards the next index in the + // string, or towards the character to the right of the current + // position. The resulting position will have a hitSide=true + // property if it reached the end of the document. + function findPosH(doc, pos, dir, unit, visually) { + var line = pos.line, ch = pos.ch, origDir = dir; + var lineObj = getLine(doc, line); + var possible = true; + function findNextLine() { + var l = line + dir; + if (l < doc.first || l >= doc.first + doc.size) return (possible = false); + line = l; + return lineObj = getLine(doc, l); + } + function moveOnce(boundToLine) { + var next = (visually ? moveVisually : moveLogically)(lineObj, ch, dir, true); + if (next == null) { + if (!boundToLine && findNextLine()) { + if (visually) ch = (dir < 0 ? lineRight : lineLeft)(lineObj); + else ch = dir < 0 ? lineObj.text.length : 0; + } else return (possible = false); + } else ch = next; + return true; + } + + if (unit == "char") moveOnce(); + else if (unit == "column") moveOnce(true); + else if (unit == "word" || unit == "group") { + var sawType = null, group = unit == "group"; + var helper = doc.cm && doc.cm.getHelper(pos, "wordChars"); + for (var first = true;; first = false) { + if (dir < 0 && !moveOnce(!first)) break; + var cur = lineObj.text.charAt(ch) || "\n"; + var type = isWordChar(cur, helper) ? "w" + : group && cur == "\n" ? "n" + : !group || /\s/.test(cur) ? null + : "p"; + if (group && !first && !type) type = "s"; + if (sawType && sawType != type) { + if (dir < 0) {dir = 1; moveOnce();} + break; + } + + if (type) sawType = type; + if (dir > 0 && !moveOnce(!first)) break; + } + } + var result = skipAtomic(doc, Pos(line, ch), origDir, true); + if (!possible) result.hitSide = true; + return result; + } + + // For relative vertical movement. Dir may be -1 or 1. Unit can be + // "page" or "line". The resulting position will have a hitSide=true + // property if it reached the end of the document. + function findPosV(cm, pos, dir, unit) { + var doc = cm.doc, x = pos.left, y; + if (unit == "page") { + var pageSize = Math.min(cm.display.wrapper.clientHeight, window.innerHeight || document.documentElement.clientHeight); + y = pos.top + dir * (pageSize - (dir < 0 ? 1.5 : .5) * textHeight(cm.display)); + } else if (unit == "line") { + y = dir > 0 ? pos.bottom + 3 : pos.top - 3; + } + for (;;) { + var target = coordsChar(cm, x, y); + if (!target.outside) break; + if (dir < 0 ? y <= 0 : y >= doc.height) { target.hitSide = true; break; } + y += dir * 5; + } + return target; + } + + // EDITOR METHODS + + // The publicly visible API. Note that methodOp(f) means + // 'wrap f in an operation, performed on its `this` parameter'. + + // This is not the complete set of editor methods. Most of the + // methods defined on the Doc type are also injected into + // CodeMirror.prototype, for backwards compatibility and + // convenience. + + CodeMirror.prototype = { + constructor: CodeMirror, + focus: function(){window.focus(); this.display.input.focus();}, + + setOption: function(option, value) { + var options = this.options, old = options[option]; + if (options[option] == value && option != "mode") return; + options[option] = value; + if (optionHandlers.hasOwnProperty(option)) + operation(this, optionHandlers[option])(this, value, old); + }, + + getOption: function(option) {return this.options[option];}, + getDoc: function() {return this.doc;}, + + addKeyMap: function(map, bottom) { + this.state.keyMaps[bottom ? "push" : "unshift"](getKeyMap(map)); + }, + removeKeyMap: function(map) { + var maps = this.state.keyMaps; + for (var i = 0; i < maps.length; ++i) + if (maps[i] == map || maps[i].name == map) { + maps.splice(i, 1); + return true; + } + }, + + addOverlay: methodOp(function(spec, options) { + var mode = spec.token ? spec : CodeMirror.getMode(this.options, spec); + if (mode.startState) throw new Error("Overlays may not be stateful."); + this.state.overlays.push({mode: mode, modeSpec: spec, opaque: options && options.opaque}); + this.state.modeGen++; + regChange(this); + }), + removeOverlay: methodOp(function(spec) { + var overlays = this.state.overlays; + for (var i = 0; i < overlays.length; ++i) { + var cur = overlays[i].modeSpec; + if (cur == spec || typeof spec == "string" && cur.name == spec) { + overlays.splice(i, 1); + this.state.modeGen++; + regChange(this); + return; + } + } + }), + + indentLine: methodOp(function(n, dir, aggressive) { + if (typeof dir != "string" && typeof dir != "number") { + if (dir == null) dir = this.options.smartIndent ? "smart" : "prev"; + else dir = dir ? "add" : "subtract"; + } + if (isLine(this.doc, n)) indentLine(this, n, dir, aggressive); + }), + indentSelection: methodOp(function(how) { + var ranges = this.doc.sel.ranges, end = -1; + for (var i = 0; i < ranges.length; i++) { + var range = ranges[i]; + if (!range.empty()) { + var from = range.from(), to = range.to(); + var start = Math.max(end, from.line); + end = Math.min(this.lastLine(), to.line - (to.ch ? 0 : 1)) + 1; + for (var j = start; j < end; ++j) + indentLine(this, j, how); + var newRanges = this.doc.sel.ranges; + if (from.ch == 0 && ranges.length == newRanges.length && newRanges[i].from().ch > 0) + replaceOneSelection(this.doc, i, new Range(from, newRanges[i].to()), sel_dontScroll); + } else if (range.head.line > end) { + indentLine(this, range.head.line, how, true); + end = range.head.line; + if (i == this.doc.sel.primIndex) ensureCursorVisible(this); + } + } + }), + + // Fetch the parser token for a given character. Useful for hacks + // that want to inspect the mode state (say, for completion). + getTokenAt: function(pos, precise) { + return takeToken(this, pos, precise); + }, + + getLineTokens: function(line, precise) { + return takeToken(this, Pos(line), precise, true); + }, + + getTokenTypeAt: function(pos) { + pos = clipPos(this.doc, pos); + var styles = getLineStyles(this, getLine(this.doc, pos.line)); + var before = 0, after = (styles.length - 1) / 2, ch = pos.ch; + var type; + if (ch == 0) type = styles[2]; + else for (;;) { + var mid = (before + after) >> 1; + if ((mid ? styles[mid * 2 - 1] : 0) >= ch) after = mid; + else if (styles[mid * 2 + 1] < ch) before = mid + 1; + else { type = styles[mid * 2 + 2]; break; } + } + var cut = type ? type.indexOf("cm-overlay ") : -1; + return cut < 0 ? type : cut == 0 ? null : type.slice(0, cut - 1); + }, + + getModeAt: function(pos) { + var mode = this.doc.mode; + if (!mode.innerMode) return mode; + return CodeMirror.innerMode(mode, this.getTokenAt(pos).state).mode; + }, + + getHelper: function(pos, type) { + return this.getHelpers(pos, type)[0]; + }, + + getHelpers: function(pos, type) { + var found = []; + if (!helpers.hasOwnProperty(type)) return found; + var help = helpers[type], mode = this.getModeAt(pos); + if (typeof mode[type] == "string") { + if (help[mode[type]]) found.push(help[mode[type]]); + } else if (mode[type]) { + for (var i = 0; i < mode[type].length; i++) { + var val = help[mode[type][i]]; + if (val) found.push(val); + } + } else if (mode.helperType && help[mode.helperType]) { + found.push(help[mode.helperType]); + } else if (help[mode.name]) { + found.push(help[mode.name]); + } + for (var i = 0; i < help._global.length; i++) { + var cur = help._global[i]; + if (cur.pred(mode, this) && indexOf(found, cur.val) == -1) + found.push(cur.val); + } + return found; + }, + + getStateAfter: function(line, precise) { + var doc = this.doc; + line = clipLine(doc, line == null ? doc.first + doc.size - 1: line); + return getStateBefore(this, line + 1, precise); + }, + + cursorCoords: function(start, mode) { + var pos, range = this.doc.sel.primary(); + if (start == null) pos = range.head; + else if (typeof start == "object") pos = clipPos(this.doc, start); + else pos = start ? range.from() : range.to(); + return cursorCoords(this, pos, mode || "page"); + }, + + charCoords: function(pos, mode) { + return charCoords(this, clipPos(this.doc, pos), mode || "page"); + }, + + coordsChar: function(coords, mode) { + coords = fromCoordSystem(this, coords, mode || "page"); + return coordsChar(this, coords.left, coords.top); + }, + + lineAtHeight: function(height, mode) { + height = fromCoordSystem(this, {top: height, left: 0}, mode || "page").top; + return lineAtHeight(this.doc, height + this.display.viewOffset); + }, + heightAtLine: function(line, mode) { + var end = false, lineObj; + if (typeof line == "number") { + var last = this.doc.first + this.doc.size - 1; + if (line < this.doc.first) line = this.doc.first; + else if (line > last) { line = last; end = true; } + lineObj = getLine(this.doc, line); + } else { + lineObj = line; + } + return intoCoordSystem(this, lineObj, {top: 0, left: 0}, mode || "page").top + + (end ? this.doc.height - heightAtLine(lineObj) : 0); + }, + + defaultTextHeight: function() { return textHeight(this.display); }, + defaultCharWidth: function() { return charWidth(this.display); }, + + setGutterMarker: methodOp(function(line, gutterID, value) { + return changeLine(this.doc, line, "gutter", function(line) { + var markers = line.gutterMarkers || (line.gutterMarkers = {}); + markers[gutterID] = value; + if (!value && isEmpty(markers)) line.gutterMarkers = null; + return true; + }); + }), + + clearGutter: methodOp(function(gutterID) { + var cm = this, doc = cm.doc, i = doc.first; + doc.iter(function(line) { + if (line.gutterMarkers && line.gutterMarkers[gutterID]) { + line.gutterMarkers[gutterID] = null; + regLineChange(cm, i, "gutter"); + if (isEmpty(line.gutterMarkers)) line.gutterMarkers = null; + } + ++i; + }); + }), + + lineInfo: function(line) { + if (typeof line == "number") { + if (!isLine(this.doc, line)) return null; + var n = line; + line = getLine(this.doc, line); + if (!line) return null; + } else { + var n = lineNo(line); + if (n == null) return null; + } + return {line: n, handle: line, text: line.text, gutterMarkers: line.gutterMarkers, + textClass: line.textClass, bgClass: line.bgClass, wrapClass: line.wrapClass, + widgets: line.widgets}; + }, + + getViewport: function() { return {from: this.display.viewFrom, to: this.display.viewTo};}, + + addWidget: function(pos, node, scroll, vert, horiz) { + var display = this.display; + pos = cursorCoords(this, clipPos(this.doc, pos)); + var top = pos.bottom, left = pos.left; + node.style.position = "absolute"; + node.setAttribute("cm-ignore-events", "true"); + this.display.input.setUneditable(node); + display.sizer.appendChild(node); + if (vert == "over") { + top = pos.top; + } else if (vert == "above" || vert == "near") { + var vspace = Math.max(display.wrapper.clientHeight, this.doc.height), + hspace = Math.max(display.sizer.clientWidth, display.lineSpace.clientWidth); + // Default to positioning above (if specified and possible); otherwise default to positioning below + if ((vert == 'above' || pos.bottom + node.offsetHeight > vspace) && pos.top > node.offsetHeight) + top = pos.top - node.offsetHeight; + else if (pos.bottom + node.offsetHeight <= vspace) + top = pos.bottom; + if (left + node.offsetWidth > hspace) + left = hspace - node.offsetWidth; + } + node.style.top = top + "px"; + node.style.left = node.style.right = ""; + if (horiz == "right") { + left = display.sizer.clientWidth - node.offsetWidth; + node.style.right = "0px"; + } else { + if (horiz == "left") left = 0; + else if (horiz == "middle") left = (display.sizer.clientWidth - node.offsetWidth) / 2; + node.style.left = left + "px"; + } + if (scroll) + scrollIntoView(this, left, top, left + node.offsetWidth, top + node.offsetHeight); + }, + + triggerOnKeyDown: methodOp(onKeyDown), + triggerOnKeyPress: methodOp(onKeyPress), + triggerOnKeyUp: onKeyUp, + + execCommand: function(cmd) { + if (commands.hasOwnProperty(cmd)) + return commands[cmd](this); + }, + + findPosH: function(from, amount, unit, visually) { + var dir = 1; + if (amount < 0) { dir = -1; amount = -amount; } + for (var i = 0, cur = clipPos(this.doc, from); i < amount; ++i) { + cur = findPosH(this.doc, cur, dir, unit, visually); + if (cur.hitSide) break; + } + return cur; + }, + + moveH: methodOp(function(dir, unit) { + var cm = this; + cm.extendSelectionsBy(function(range) { + if (cm.display.shift || cm.doc.extend || range.empty()) + return findPosH(cm.doc, range.head, dir, unit, cm.options.rtlMoveVisually); + else + return dir < 0 ? range.from() : range.to(); + }, sel_move); + }), + + deleteH: methodOp(function(dir, unit) { + var sel = this.doc.sel, doc = this.doc; + if (sel.somethingSelected()) + doc.replaceSelection("", null, "+delete"); + else + deleteNearSelection(this, function(range) { + var other = findPosH(doc, range.head, dir, unit, false); + return dir < 0 ? {from: other, to: range.head} : {from: range.head, to: other}; + }); + }), + + findPosV: function(from, amount, unit, goalColumn) { + var dir = 1, x = goalColumn; + if (amount < 0) { dir = -1; amount = -amount; } + for (var i = 0, cur = clipPos(this.doc, from); i < amount; ++i) { + var coords = cursorCoords(this, cur, "div"); + if (x == null) x = coords.left; + else coords.left = x; + cur = findPosV(this, coords, dir, unit); + if (cur.hitSide) break; + } + return cur; + }, + + moveV: methodOp(function(dir, unit) { + var cm = this, doc = this.doc, goals = []; + var collapse = !cm.display.shift && !doc.extend && doc.sel.somethingSelected(); + doc.extendSelectionsBy(function(range) { + if (collapse) + return dir < 0 ? range.from() : range.to(); + var headPos = cursorCoords(cm, range.head, "div"); + if (range.goalColumn != null) headPos.left = range.goalColumn; + goals.push(headPos.left); + var pos = findPosV(cm, headPos, dir, unit); + if (unit == "page" && range == doc.sel.primary()) + addToScrollPos(cm, null, charCoords(cm, pos, "div").top - headPos.top); + return pos; + }, sel_move); + if (goals.length) for (var i = 0; i < doc.sel.ranges.length; i++) + doc.sel.ranges[i].goalColumn = goals[i]; + }), + + // Find the word at the given position (as returned by coordsChar). + findWordAt: function(pos) { + var doc = this.doc, line = getLine(doc, pos.line).text; + var start = pos.ch, end = pos.ch; + if (line) { + var helper = this.getHelper(pos, "wordChars"); + if ((pos.xRel < 0 || end == line.length) && start) --start; else ++end; + var startChar = line.charAt(start); + var check = isWordChar(startChar, helper) + ? function(ch) { return isWordChar(ch, helper); } + : /\s/.test(startChar) ? function(ch) {return /\s/.test(ch);} + : function(ch) {return !/\s/.test(ch) && !isWordChar(ch);}; + while (start > 0 && check(line.charAt(start - 1))) --start; + while (end < line.length && check(line.charAt(end))) ++end; + } + return new Range(Pos(pos.line, start), Pos(pos.line, end)); + }, + + toggleOverwrite: function(value) { + if (value != null && value == this.state.overwrite) return; + if (this.state.overwrite = !this.state.overwrite) + addClass(this.display.cursorDiv, "CodeMirror-overwrite"); + else + rmClass(this.display.cursorDiv, "CodeMirror-overwrite"); + + signal(this, "overwriteToggle", this, this.state.overwrite); + }, + hasFocus: function() { return this.display.input.getField() == activeElt(); }, + + scrollTo: methodOp(function(x, y) { + if (x != null || y != null) resolveScrollToPos(this); + if (x != null) this.curOp.scrollLeft = x; + if (y != null) this.curOp.scrollTop = y; + }), + getScrollInfo: function() { + var scroller = this.display.scroller; + return {left: scroller.scrollLeft, top: scroller.scrollTop, + height: scroller.scrollHeight - scrollGap(this) - this.display.barHeight, + width: scroller.scrollWidth - scrollGap(this) - this.display.barWidth, + clientHeight: displayHeight(this), clientWidth: displayWidth(this)}; + }, + + scrollIntoView: methodOp(function(range, margin) { + if (range == null) { + range = {from: this.doc.sel.primary().head, to: null}; + if (margin == null) margin = this.options.cursorScrollMargin; + } else if (typeof range == "number") { + range = {from: Pos(range, 0), to: null}; + } else if (range.from == null) { + range = {from: range, to: null}; + } + if (!range.to) range.to = range.from; + range.margin = margin || 0; + + if (range.from.line != null) { + resolveScrollToPos(this); + this.curOp.scrollToPos = range; + } else { + var sPos = calculateScrollPos(this, Math.min(range.from.left, range.to.left), + Math.min(range.from.top, range.to.top) - range.margin, + Math.max(range.from.right, range.to.right), + Math.max(range.from.bottom, range.to.bottom) + range.margin); + this.scrollTo(sPos.scrollLeft, sPos.scrollTop); + } + }), + + setSize: methodOp(function(width, height) { + var cm = this; + function interpret(val) { + return typeof val == "number" || /^\d+$/.test(String(val)) ? val + "px" : val; + } + if (width != null) cm.display.wrapper.style.width = interpret(width); + if (height != null) cm.display.wrapper.style.height = interpret(height); + if (cm.options.lineWrapping) clearLineMeasurementCache(this); + var lineNo = cm.display.viewFrom; + cm.doc.iter(lineNo, cm.display.viewTo, function(line) { + if (line.widgets) for (var i = 0; i < line.widgets.length; i++) + if (line.widgets[i].noHScroll) { regLineChange(cm, lineNo, "widget"); break; } + ++lineNo; + }); + cm.curOp.forceUpdate = true; + signal(cm, "refresh", this); + }), + + operation: function(f){return runInOp(this, f);}, + + refresh: methodOp(function() { + var oldHeight = this.display.cachedTextHeight; + regChange(this); + this.curOp.forceUpdate = true; + clearCaches(this); + this.scrollTo(this.doc.scrollLeft, this.doc.scrollTop); + updateGutterSpace(this); + if (oldHeight == null || Math.abs(oldHeight - textHeight(this.display)) > .5) + estimateLineHeights(this); + signal(this, "refresh", this); + }), + + swapDoc: methodOp(function(doc) { + var old = this.doc; + old.cm = null; + attachDoc(this, doc); + clearCaches(this); + this.display.input.reset(); + this.scrollTo(doc.scrollLeft, doc.scrollTop); + this.curOp.forceScroll = true; + signalLater(this, "swapDoc", this, old); + return old; + }), + + getInputField: function(){return this.display.input.getField();}, + getWrapperElement: function(){return this.display.wrapper;}, + getScrollerElement: function(){return this.display.scroller;}, + getGutterElement: function(){return this.display.gutters;} + }; + eventMixin(CodeMirror); + + // OPTION DEFAULTS + + // The default configuration options. + var defaults = CodeMirror.defaults = {}; + // Functions to run when options are changed. + var optionHandlers = CodeMirror.optionHandlers = {}; + + function option(name, deflt, handle, notOnInit) { + CodeMirror.defaults[name] = deflt; + if (handle) optionHandlers[name] = + notOnInit ? function(cm, val, old) {if (old != Init) handle(cm, val, old);} : handle; + } + + // Passed to option handlers when there is no old value. + var Init = CodeMirror.Init = {toString: function(){return "CodeMirror.Init";}}; + + // These two are, on init, called from the constructor because they + // have to be initialized before the editor can start at all. + option("value", "", function(cm, val) { + cm.setValue(val); + }, true); + option("mode", null, function(cm, val) { + cm.doc.modeOption = val; + loadMode(cm); + }, true); + + option("indentUnit", 2, loadMode, true); + option("indentWithTabs", false); + option("smartIndent", true); + option("tabSize", 4, function(cm) { + resetModeState(cm); + clearCaches(cm); + regChange(cm); + }, true); + option("specialChars", /[\t\u0000-\u0019\u00ad\u200b-\u200f\u2028\u2029\ufeff]/g, function(cm, val, old) { + cm.state.specialChars = new RegExp(val.source + (val.test("\t") ? "" : "|\t"), "g"); + if (old != CodeMirror.Init) cm.refresh(); + }); + option("specialCharPlaceholder", defaultSpecialCharPlaceholder, function(cm) {cm.refresh();}, true); + option("electricChars", true); + option("inputStyle", mobile ? "contenteditable" : "textarea", function() { + throw new Error("inputStyle can not (yet) be changed in a running editor"); // FIXME + }, true); + option("rtlMoveVisually", !windows); + option("wholeLineUpdateBefore", true); + + option("theme", "default", function(cm) { + themeChanged(cm); + guttersChanged(cm); + }, true); + option("keyMap", "default", function(cm, val, old) { + var next = getKeyMap(val); + var prev = old != CodeMirror.Init && getKeyMap(old); + if (prev && prev.detach) prev.detach(cm, next); + if (next.attach) next.attach(cm, prev || null); + }); + option("extraKeys", null); + + option("lineWrapping", false, wrappingChanged, true); + option("gutters", [], function(cm) { + setGuttersForLineNumbers(cm.options); + guttersChanged(cm); + }, true); + option("fixedGutter", true, function(cm, val) { + cm.display.gutters.style.left = val ? compensateForHScroll(cm.display) + "px" : "0"; + cm.refresh(); + }, true); + option("coverGutterNextToScrollbar", false, function(cm) {updateScrollbars(cm);}, true); + option("scrollbarStyle", "native", function(cm) { + initScrollbars(cm); + updateScrollbars(cm); + cm.display.scrollbars.setScrollTop(cm.doc.scrollTop); + cm.display.scrollbars.setScrollLeft(cm.doc.scrollLeft); + }, true); + option("lineNumbers", false, function(cm) { + setGuttersForLineNumbers(cm.options); + guttersChanged(cm); + }, true); + option("firstLineNumber", 1, guttersChanged, true); + option("lineNumberFormatter", function(integer) {return integer;}, guttersChanged, true); + option("showCursorWhenSelecting", false, updateSelection, true); + + option("resetSelectionOnContextMenu", true); + option("lineWiseCopyCut", true); + + option("readOnly", false, function(cm, val) { + if (val == "nocursor") { + onBlur(cm); + cm.display.input.blur(); + cm.display.disabled = true; + } else { + cm.display.disabled = false; + if (!val) cm.display.input.reset(); + } + }); + option("disableInput", false, function(cm, val) {if (!val) cm.display.input.reset();}, true); + option("dragDrop", true, dragDropChanged); + + option("cursorBlinkRate", 530); + option("cursorScrollMargin", 0); + option("cursorHeight", 1, updateSelection, true); + option("singleCursorHeightPerLine", true, updateSelection, true); + option("workTime", 100); + option("workDelay", 100); + option("flattenSpans", true, resetModeState, true); + option("addModeClass", false, resetModeState, true); + option("pollInterval", 100); + option("undoDepth", 200, function(cm, val){cm.doc.history.undoDepth = val;}); + option("historyEventDelay", 1250); + option("viewportMargin", 10, function(cm){cm.refresh();}, true); + option("maxHighlightLength", 10000, resetModeState, true); + option("moveInputWithCursor", true, function(cm, val) { + if (!val) cm.display.input.resetPosition(); + }); + + option("tabindex", null, function(cm, val) { + cm.display.input.getField().tabIndex = val || ""; + }); + option("autofocus", null); + + // MODE DEFINITION AND QUERYING + + // Known modes, by name and by MIME + var modes = CodeMirror.modes = {}, mimeModes = CodeMirror.mimeModes = {}; + + // Extra arguments are stored as the mode's dependencies, which is + // used by (legacy) mechanisms like loadmode.js to automatically + // load a mode. (Preferred mechanism is the require/define calls.) + CodeMirror.defineMode = function(name, mode) { + if (!CodeMirror.defaults.mode && name != "null") CodeMirror.defaults.mode = name; + if (arguments.length > 2) + mode.dependencies = Array.prototype.slice.call(arguments, 2); + modes[name] = mode; + }; + + CodeMirror.defineMIME = function(mime, spec) { + mimeModes[mime] = spec; + }; + + // Given a MIME type, a {name, ...options} config object, or a name + // string, return a mode config object. + CodeMirror.resolveMode = function(spec) { + if (typeof spec == "string" && mimeModes.hasOwnProperty(spec)) { + spec = mimeModes[spec]; + } else if (spec && typeof spec.name == "string" && mimeModes.hasOwnProperty(spec.name)) { + var found = mimeModes[spec.name]; + if (typeof found == "string") found = {name: found}; + spec = createObj(found, spec); + spec.name = found.name; + } else if (typeof spec == "string" && /^[\w\-]+\/[\w\-]+\+xml$/.test(spec)) { + return CodeMirror.resolveMode("application/xml"); + } + if (typeof spec == "string") return {name: spec}; + else return spec || {name: "null"}; + }; + + // Given a mode spec (anything that resolveMode accepts), find and + // initialize an actual mode object. + CodeMirror.getMode = function(options, spec) { + var spec = CodeMirror.resolveMode(spec); + var mfactory = modes[spec.name]; + if (!mfactory) return CodeMirror.getMode(options, "text/plain"); + var modeObj = mfactory(options, spec); + if (modeExtensions.hasOwnProperty(spec.name)) { + var exts = modeExtensions[spec.name]; + for (var prop in exts) { + if (!exts.hasOwnProperty(prop)) continue; + if (modeObj.hasOwnProperty(prop)) modeObj["_" + prop] = modeObj[prop]; + modeObj[prop] = exts[prop]; + } + } + modeObj.name = spec.name; + if (spec.helperType) modeObj.helperType = spec.helperType; + if (spec.modeProps) for (var prop in spec.modeProps) + modeObj[prop] = spec.modeProps[prop]; + + return modeObj; + }; + + // Minimal default mode. + CodeMirror.defineMode("null", function() { + return {token: function(stream) {stream.skipToEnd();}}; + }); + CodeMirror.defineMIME("text/plain", "null"); + + // This can be used to attach properties to mode objects from + // outside the actual mode definition. + var modeExtensions = CodeMirror.modeExtensions = {}; + CodeMirror.extendMode = function(mode, properties) { + var exts = modeExtensions.hasOwnProperty(mode) ? modeExtensions[mode] : (modeExtensions[mode] = {}); + copyObj(properties, exts); + }; + + // EXTENSIONS + + CodeMirror.defineExtension = function(name, func) { + CodeMirror.prototype[name] = func; + }; + CodeMirror.defineDocExtension = function(name, func) { + Doc.prototype[name] = func; + }; + CodeMirror.defineOption = option; + + var initHooks = []; + CodeMirror.defineInitHook = function(f) {initHooks.push(f);}; + + var helpers = CodeMirror.helpers = {}; + CodeMirror.registerHelper = function(type, name, value) { + if (!helpers.hasOwnProperty(type)) helpers[type] = CodeMirror[type] = {_global: []}; + helpers[type][name] = value; + }; + CodeMirror.registerGlobalHelper = function(type, name, predicate, value) { + CodeMirror.registerHelper(type, name, value); + helpers[type]._global.push({pred: predicate, val: value}); + }; + + // MODE STATE HANDLING + + // Utility functions for working with state. Exported because nested + // modes need to do this for their inner modes. + + var copyState = CodeMirror.copyState = function(mode, state) { + if (state === true) return state; + if (mode.copyState) return mode.copyState(state); + var nstate = {}; + for (var n in state) { + var val = state[n]; + if (val instanceof Array) val = val.concat([]); + nstate[n] = val; + } + return nstate; + }; + + var startState = CodeMirror.startState = function(mode, a1, a2) { + return mode.startState ? mode.startState(a1, a2) : true; + }; + + // Given a mode and a state (for that mode), find the inner mode and + // state at the position that the state refers to. + CodeMirror.innerMode = function(mode, state) { + while (mode.innerMode) { + var info = mode.innerMode(state); + if (!info || info.mode == mode) break; + state = info.state; + mode = info.mode; + } + return info || {mode: mode, state: state}; + }; + + // STANDARD COMMANDS + + // Commands are parameter-less actions that can be performed on an + // editor, mostly used for keybindings. + var commands = CodeMirror.commands = { + selectAll: function(cm) {cm.setSelection(Pos(cm.firstLine(), 0), Pos(cm.lastLine()), sel_dontScroll);}, + singleSelection: function(cm) { + cm.setSelection(cm.getCursor("anchor"), cm.getCursor("head"), sel_dontScroll); + }, + killLine: function(cm) { + deleteNearSelection(cm, function(range) { + if (range.empty()) { + var len = getLine(cm.doc, range.head.line).text.length; + if (range.head.ch == len && range.head.line < cm.lastLine()) + return {from: range.head, to: Pos(range.head.line + 1, 0)}; + else + return {from: range.head, to: Pos(range.head.line, len)}; + } else { + return {from: range.from(), to: range.to()}; + } + }); + }, + deleteLine: function(cm) { + deleteNearSelection(cm, function(range) { + return {from: Pos(range.from().line, 0), + to: clipPos(cm.doc, Pos(range.to().line + 1, 0))}; + }); + }, + delLineLeft: function(cm) { + deleteNearSelection(cm, function(range) { + return {from: Pos(range.from().line, 0), to: range.from()}; + }); + }, + delWrappedLineLeft: function(cm) { + deleteNearSelection(cm, function(range) { + var top = cm.charCoords(range.head, "div").top + 5; + var leftPos = cm.coordsChar({left: 0, top: top}, "div"); + return {from: leftPos, to: range.from()}; + }); + }, + delWrappedLineRight: function(cm) { + deleteNearSelection(cm, function(range) { + var top = cm.charCoords(range.head, "div").top + 5; + var rightPos = cm.coordsChar({left: cm.display.lineDiv.offsetWidth + 100, top: top}, "div"); + return {from: range.from(), to: rightPos }; + }); + }, + undo: function(cm) {cm.undo();}, + redo: function(cm) {cm.redo();}, + undoSelection: function(cm) {cm.undoSelection();}, + redoSelection: function(cm) {cm.redoSelection();}, + goDocStart: function(cm) {cm.extendSelection(Pos(cm.firstLine(), 0));}, + goDocEnd: function(cm) {cm.extendSelection(Pos(cm.lastLine()));}, + goLineStart: function(cm) { + cm.extendSelectionsBy(function(range) { return lineStart(cm, range.head.line); }, + {origin: "+move", bias: 1}); + }, + goLineStartSmart: function(cm) { + cm.extendSelectionsBy(function(range) { + return lineStartSmart(cm, range.head); + }, {origin: "+move", bias: 1}); + }, + goLineEnd: function(cm) { + cm.extendSelectionsBy(function(range) { return lineEnd(cm, range.head.line); }, + {origin: "+move", bias: -1}); + }, + goLineRight: function(cm) { + cm.extendSelectionsBy(function(range) { + var top = cm.charCoords(range.head, "div").top + 5; + return cm.coordsChar({left: cm.display.lineDiv.offsetWidth + 100, top: top}, "div"); + }, sel_move); + }, + goLineLeft: function(cm) { + cm.extendSelectionsBy(function(range) { + var top = cm.charCoords(range.head, "div").top + 5; + return cm.coordsChar({left: 0, top: top}, "div"); + }, sel_move); + }, + goLineLeftSmart: function(cm) { + cm.extendSelectionsBy(function(range) { + var top = cm.charCoords(range.head, "div").top + 5; + var pos = cm.coordsChar({left: 0, top: top}, "div"); + if (pos.ch < cm.getLine(pos.line).search(/\S/)) return lineStartSmart(cm, range.head); + return pos; + }, sel_move); + }, + goLineUp: function(cm) {cm.moveV(-1, "line");}, + goLineDown: function(cm) {cm.moveV(1, "line");}, + goPageUp: function(cm) {cm.moveV(-1, "page");}, + goPageDown: function(cm) {cm.moveV(1, "page");}, + goCharLeft: function(cm) {cm.moveH(-1, "char");}, + goCharRight: function(cm) {cm.moveH(1, "char");}, + goColumnLeft: function(cm) {cm.moveH(-1, "column");}, + goColumnRight: function(cm) {cm.moveH(1, "column");}, + goWordLeft: function(cm) {cm.moveH(-1, "word");}, + goGroupRight: function(cm) {cm.moveH(1, "group");}, + goGroupLeft: function(cm) {cm.moveH(-1, "group");}, + goWordRight: function(cm) {cm.moveH(1, "word");}, + delCharBefore: function(cm) {cm.deleteH(-1, "char");}, + delCharAfter: function(cm) {cm.deleteH(1, "char");}, + delWordBefore: function(cm) {cm.deleteH(-1, "word");}, + delWordAfter: function(cm) {cm.deleteH(1, "word");}, + delGroupBefore: function(cm) {cm.deleteH(-1, "group");}, + delGroupAfter: function(cm) {cm.deleteH(1, "group");}, + indentAuto: function(cm) {cm.indentSelection("smart");}, + indentMore: function(cm) {cm.indentSelection("add");}, + indentLess: function(cm) {cm.indentSelection("subtract");}, + insertTab: function(cm) {cm.replaceSelection("\t");}, + insertSoftTab: function(cm) { + var spaces = [], ranges = cm.listSelections(), tabSize = cm.options.tabSize; + for (var i = 0; i < ranges.length; i++) { + var pos = ranges[i].from(); + var col = countColumn(cm.getLine(pos.line), pos.ch, tabSize); + spaces.push(new Array(tabSize - col % tabSize + 1).join(" ")); + } + cm.replaceSelections(spaces); + }, + defaultTab: function(cm) { + if (cm.somethingSelected()) cm.indentSelection("add"); + else cm.execCommand("insertTab"); + }, + transposeChars: function(cm) { + runInOp(cm, function() { + var ranges = cm.listSelections(), newSel = []; + for (var i = 0; i < ranges.length; i++) { + var cur = ranges[i].head, line = getLine(cm.doc, cur.line).text; + if (line) { + if (cur.ch == line.length) cur = new Pos(cur.line, cur.ch - 1); + if (cur.ch > 0) { + cur = new Pos(cur.line, cur.ch + 1); + cm.replaceRange(line.charAt(cur.ch - 1) + line.charAt(cur.ch - 2), + Pos(cur.line, cur.ch - 2), cur, "+transpose"); + } else if (cur.line > cm.doc.first) { + var prev = getLine(cm.doc, cur.line - 1).text; + if (prev) + cm.replaceRange(line.charAt(0) + "\n" + prev.charAt(prev.length - 1), + Pos(cur.line - 1, prev.length - 1), Pos(cur.line, 1), "+transpose"); + } + } + newSel.push(new Range(cur, cur)); + } + cm.setSelections(newSel); + }); + }, + newlineAndIndent: function(cm) { + runInOp(cm, function() { + var len = cm.listSelections().length; + for (var i = 0; i < len; i++) { + var range = cm.listSelections()[i]; + cm.replaceRange("\n", range.anchor, range.head, "+input"); + cm.indentLine(range.from().line + 1, null, true); + ensureCursorVisible(cm); + } + }); + }, + toggleOverwrite: function(cm) {cm.toggleOverwrite();} + }; + + + // STANDARD KEYMAPS + + var keyMap = CodeMirror.keyMap = {}; + + keyMap.basic = { + "Left": "goCharLeft", "Right": "goCharRight", "Up": "goLineUp", "Down": "goLineDown", + "End": "goLineEnd", "Home": "goLineStartSmart", "PageUp": "goPageUp", "PageDown": "goPageDown", + "Delete": "delCharAfter", "Backspace": "delCharBefore", "Shift-Backspace": "delCharBefore", + "Tab": "defaultTab", "Shift-Tab": "indentAuto", + "Enter": "newlineAndIndent", "Insert": "toggleOverwrite", + "Esc": "singleSelection" + }; + // Note that the save and find-related commands aren't defined by + // default. User code or addons can define them. Unknown commands + // are simply ignored. + keyMap.pcDefault = { + "Ctrl-A": "selectAll", "Ctrl-D": "deleteLine", "Ctrl-Z": "undo", "Shift-Ctrl-Z": "redo", "Ctrl-Y": "redo", + "Ctrl-Home": "goDocStart", "Ctrl-End": "goDocEnd", "Ctrl-Up": "goLineUp", "Ctrl-Down": "goLineDown", + "Ctrl-Left": "goGroupLeft", "Ctrl-Right": "goGroupRight", "Alt-Left": "goLineStart", "Alt-Right": "goLineEnd", + "Ctrl-Backspace": "delGroupBefore", "Ctrl-Delete": "delGroupAfter", "Ctrl-S": "save", "Ctrl-F": "find", + "Ctrl-G": "findNext", "Shift-Ctrl-G": "findPrev", "Shift-Ctrl-F": "replace", "Shift-Ctrl-R": "replaceAll", + "Ctrl-[": "indentLess", "Ctrl-]": "indentMore", + "Ctrl-U": "undoSelection", "Shift-Ctrl-U": "redoSelection", "Alt-U": "redoSelection", + fallthrough: "basic" + }; + // Very basic readline/emacs-style bindings, which are standard on Mac. + keyMap.emacsy = { + "Ctrl-F": "goCharRight", "Ctrl-B": "goCharLeft", "Ctrl-P": "goLineUp", "Ctrl-N": "goLineDown", + "Alt-F": "goWordRight", "Alt-B": "goWordLeft", "Ctrl-A": "goLineStart", "Ctrl-E": "goLineEnd", + "Ctrl-V": "goPageDown", "Shift-Ctrl-V": "goPageUp", "Ctrl-D": "delCharAfter", "Ctrl-H": "delCharBefore", + "Alt-D": "delWordAfter", "Alt-Backspace": "delWordBefore", "Ctrl-K": "killLine", "Ctrl-T": "transposeChars" + }; + keyMap.macDefault = { + "Cmd-A": "selectAll", "Cmd-D": "deleteLine", "Cmd-Z": "undo", "Shift-Cmd-Z": "redo", "Cmd-Y": "redo", + "Cmd-Home": "goDocStart", "Cmd-Up": "goDocStart", "Cmd-End": "goDocEnd", "Cmd-Down": "goDocEnd", "Alt-Left": "goGroupLeft", + "Alt-Right": "goGroupRight", "Cmd-Left": "goLineLeft", "Cmd-Right": "goLineRight", "Alt-Backspace": "delGroupBefore", + "Ctrl-Alt-Backspace": "delGroupAfter", "Alt-Delete": "delGroupAfter", "Cmd-S": "save", "Cmd-F": "find", + "Cmd-G": "findNext", "Shift-Cmd-G": "findPrev", "Cmd-Alt-F": "replace", "Shift-Cmd-Alt-F": "replaceAll", + "Cmd-[": "indentLess", "Cmd-]": "indentMore", "Cmd-Backspace": "delWrappedLineLeft", "Cmd-Delete": "delWrappedLineRight", + "Cmd-U": "undoSelection", "Shift-Cmd-U": "redoSelection", "Ctrl-Up": "goDocStart", "Ctrl-Down": "goDocEnd", + fallthrough: ["basic", "emacsy"] + }; + keyMap["default"] = mac ? keyMap.macDefault : keyMap.pcDefault; + + // KEYMAP DISPATCH + + function normalizeKeyName(name) { + var parts = name.split(/-(?!$)/), name = parts[parts.length - 1]; + var alt, ctrl, shift, cmd; + for (var i = 0; i < parts.length - 1; i++) { + var mod = parts[i]; + if (/^(cmd|meta|m)$/i.test(mod)) cmd = true; + else if (/^a(lt)?$/i.test(mod)) alt = true; + else if (/^(c|ctrl|control)$/i.test(mod)) ctrl = true; + else if (/^s(hift)$/i.test(mod)) shift = true; + else throw new Error("Unrecognized modifier name: " + mod); + } + if (alt) name = "Alt-" + name; + if (ctrl) name = "Ctrl-" + name; + if (cmd) name = "Cmd-" + name; + if (shift) name = "Shift-" + name; + return name; + } + + // This is a kludge to keep keymaps mostly working as raw objects + // (backwards compatibility) while at the same time support features + // like normalization and multi-stroke key bindings. It compiles a + // new normalized keymap, and then updates the old object to reflect + // this. + CodeMirror.normalizeKeyMap = function(keymap) { + var copy = {}; + for (var keyname in keymap) if (keymap.hasOwnProperty(keyname)) { + var value = keymap[keyname]; + if (/^(name|fallthrough|(de|at)tach)$/.test(keyname)) continue; + if (value == "...") { delete keymap[keyname]; continue; } + + var keys = map(keyname.split(" "), normalizeKeyName); + for (var i = 0; i < keys.length; i++) { + var val, name; + if (i == keys.length - 1) { + name = keyname; + val = value; + } else { + name = keys.slice(0, i + 1).join(" "); + val = "..."; + } + var prev = copy[name]; + if (!prev) copy[name] = val; + else if (prev != val) throw new Error("Inconsistent bindings for " + name); + } + delete keymap[keyname]; + } + for (var prop in copy) keymap[prop] = copy[prop]; + return keymap; + }; + + var lookupKey = CodeMirror.lookupKey = function(key, map, handle, context) { + map = getKeyMap(map); + var found = map.call ? map.call(key, context) : map[key]; + if (found === false) return "nothing"; + if (found === "...") return "multi"; + if (found != null && handle(found)) return "handled"; + + if (map.fallthrough) { + if (Object.prototype.toString.call(map.fallthrough) != "[object Array]") + return lookupKey(key, map.fallthrough, handle, context); + for (var i = 0; i < map.fallthrough.length; i++) { + var result = lookupKey(key, map.fallthrough[i], handle, context); + if (result) return result; + } + } + }; + + // Modifier key presses don't count as 'real' key presses for the + // purpose of keymap fallthrough. + var isModifierKey = CodeMirror.isModifierKey = function(value) { + var name = typeof value == "string" ? value : keyNames[value.keyCode]; + return name == "Ctrl" || name == "Alt" || name == "Shift" || name == "Mod"; + }; + + // Look up the name of a key as indicated by an event object. + var keyName = CodeMirror.keyName = function(event, noShift) { + if (presto && event.keyCode == 34 && event["char"]) return false; + var base = keyNames[event.keyCode], name = base; + if (name == null || event.altGraphKey) return false; + if (event.altKey && base != "Alt") name = "Alt-" + name; + if ((flipCtrlCmd ? event.metaKey : event.ctrlKey) && base != "Ctrl") name = "Ctrl-" + name; + if ((flipCtrlCmd ? event.ctrlKey : event.metaKey) && base != "Cmd") name = "Cmd-" + name; + if (!noShift && event.shiftKey && base != "Shift") name = "Shift-" + name; + return name; + }; + + function getKeyMap(val) { + return typeof val == "string" ? keyMap[val] : val; + } + + // FROMTEXTAREA + + CodeMirror.fromTextArea = function(textarea, options) { + options = options ? copyObj(options) : {}; + options.value = textarea.value; + if (!options.tabindex && textarea.tabIndex) + options.tabindex = textarea.tabIndex; + if (!options.placeholder && textarea.placeholder) + options.placeholder = textarea.placeholder; + // Set autofocus to true if this textarea is focused, or if it has + // autofocus and no other element is focused. + if (options.autofocus == null) { + var hasFocus = activeElt(); + options.autofocus = hasFocus == textarea || + textarea.getAttribute("autofocus") != null && hasFocus == document.body; + } + + function save() {textarea.value = cm.getValue();} + if (textarea.form) { + on(textarea.form, "submit", save); + // Deplorable hack to make the submit method do the right thing. + if (!options.leaveSubmitMethodAlone) { + var form = textarea.form, realSubmit = form.submit; + try { + var wrappedSubmit = form.submit = function() { + save(); + form.submit = realSubmit; + form.submit(); + form.submit = wrappedSubmit; + }; + } catch(e) {} + } + } + + options.finishInit = function(cm) { + cm.save = save; + cm.getTextArea = function() { return textarea; }; + cm.toTextArea = function() { + cm.toTextArea = isNaN; // Prevent this from being ran twice + save(); + textarea.parentNode.removeChild(cm.getWrapperElement()); + textarea.style.display = ""; + if (textarea.form) { + off(textarea.form, "submit", save); + if (typeof textarea.form.submit == "function") + textarea.form.submit = realSubmit; + } + }; + }; + + textarea.style.display = "none"; + var cm = CodeMirror(function(node) { + textarea.parentNode.insertBefore(node, textarea.nextSibling); + }, options); + return cm; + }; + + // STRING STREAM + + // Fed to the mode parsers, provides helper functions to make + // parsers more succinct. + + var StringStream = CodeMirror.StringStream = function(string, tabSize) { + this.pos = this.start = 0; + this.string = string; + this.tabSize = tabSize || 8; + this.lastColumnPos = this.lastColumnValue = 0; + this.lineStart = 0; + }; + + StringStream.prototype = { + eol: function() {return this.pos >= this.string.length;}, + sol: function() {return this.pos == this.lineStart;}, + peek: function() {return this.string.charAt(this.pos) || undefined;}, + next: function() { + if (this.pos < this.string.length) + return this.string.charAt(this.pos++); + }, + eat: function(match) { + var ch = this.string.charAt(this.pos); + if (typeof match == "string") var ok = ch == match; + else var ok = ch && (match.test ? match.test(ch) : match(ch)); + if (ok) {++this.pos; return ch;} + }, + eatWhile: function(match) { + var start = this.pos; + while (this.eat(match)){} + return this.pos > start; + }, + eatSpace: function() { + var start = this.pos; + while (/[\s\u00a0]/.test(this.string.charAt(this.pos))) ++this.pos; + return this.pos > start; + }, + skipToEnd: function() {this.pos = this.string.length;}, + skipTo: function(ch) { + var found = this.string.indexOf(ch, this.pos); + if (found > -1) {this.pos = found; return true;} + }, + backUp: function(n) {this.pos -= n;}, + column: function() { + if (this.lastColumnPos < this.start) { + this.lastColumnValue = countColumn(this.string, this.start, this.tabSize, this.lastColumnPos, this.lastColumnValue); + this.lastColumnPos = this.start; + } + return this.lastColumnValue - (this.lineStart ? countColumn(this.string, this.lineStart, this.tabSize) : 0); + }, + indentation: function() { + return countColumn(this.string, null, this.tabSize) - + (this.lineStart ? countColumn(this.string, this.lineStart, this.tabSize) : 0); + }, + match: function(pattern, consume, caseInsensitive) { + if (typeof pattern == "string") { + var cased = function(str) {return caseInsensitive ? str.toLowerCase() : str;}; + var substr = this.string.substr(this.pos, pattern.length); + if (cased(substr) == cased(pattern)) { + if (consume !== false) this.pos += pattern.length; + return true; + } + } else { + var match = this.string.slice(this.pos).match(pattern); + if (match && match.index > 0) return null; + if (match && consume !== false) this.pos += match[0].length; + return match; + } + }, + current: function(){return this.string.slice(this.start, this.pos);}, + hideFirstChars: function(n, inner) { + this.lineStart += n; + try { return inner(); } + finally { this.lineStart -= n; } + } + }; + + // TEXTMARKERS + + // Created with markText and setBookmark methods. A TextMarker is a + // handle that can be used to clear or find a marked position in the + // document. Line objects hold arrays (markedSpans) containing + // {from, to, marker} object pointing to such marker objects, and + // indicating that such a marker is present on that line. Multiple + // lines may point to the same marker when it spans across lines. + // The spans will have null for their from/to properties when the + // marker continues beyond the start/end of the line. Markers have + // links back to the lines they currently touch. + + var nextMarkerId = 0; + + var TextMarker = CodeMirror.TextMarker = function(doc, type) { + this.lines = []; + this.type = type; + this.doc = doc; + this.id = ++nextMarkerId; + }; + eventMixin(TextMarker); + + // Clear the marker. + TextMarker.prototype.clear = function() { + if (this.explicitlyCleared) return; + var cm = this.doc.cm, withOp = cm && !cm.curOp; + if (withOp) startOperation(cm); + if (hasHandler(this, "clear")) { + var found = this.find(); + if (found) signalLater(this, "clear", found.from, found.to); + } + var min = null, max = null; + for (var i = 0; i < this.lines.length; ++i) { + var line = this.lines[i]; + var span = getMarkedSpanFor(line.markedSpans, this); + if (cm && !this.collapsed) regLineChange(cm, lineNo(line), "text"); + else if (cm) { + if (span.to != null) max = lineNo(line); + if (span.from != null) min = lineNo(line); + } + line.markedSpans = removeMarkedSpan(line.markedSpans, span); + if (span.from == null && this.collapsed && !lineIsHidden(this.doc, line) && cm) + updateLineHeight(line, textHeight(cm.display)); + } + if (cm && this.collapsed && !cm.options.lineWrapping) for (var i = 0; i < this.lines.length; ++i) { + var visual = visualLine(this.lines[i]), len = lineLength(visual); + if (len > cm.display.maxLineLength) { + cm.display.maxLine = visual; + cm.display.maxLineLength = len; + cm.display.maxLineChanged = true; + } + } + + if (min != null && cm && this.collapsed) regChange(cm, min, max + 1); + this.lines.length = 0; + this.explicitlyCleared = true; + if (this.atomic && this.doc.cantEdit) { + this.doc.cantEdit = false; + if (cm) reCheckSelection(cm.doc); + } + if (cm) signalLater(cm, "markerCleared", cm, this); + if (withOp) endOperation(cm); + if (this.parent) this.parent.clear(); + }; + + // Find the position of the marker in the document. Returns a {from, + // to} object by default. Side can be passed to get a specific side + // -- 0 (both), -1 (left), or 1 (right). When lineObj is true, the + // Pos objects returned contain a line object, rather than a line + // number (used to prevent looking up the same line twice). + TextMarker.prototype.find = function(side, lineObj) { + if (side == null && this.type == "bookmark") side = 1; + var from, to; + for (var i = 0; i < this.lines.length; ++i) { + var line = this.lines[i]; + var span = getMarkedSpanFor(line.markedSpans, this); + if (span.from != null) { + from = Pos(lineObj ? line : lineNo(line), span.from); + if (side == -1) return from; + } + if (span.to != null) { + to = Pos(lineObj ? line : lineNo(line), span.to); + if (side == 1) return to; + } + } + return from && {from: from, to: to}; + }; + + // Signals that the marker's widget changed, and surrounding layout + // should be recomputed. + TextMarker.prototype.changed = function() { + var pos = this.find(-1, true), widget = this, cm = this.doc.cm; + if (!pos || !cm) return; + runInOp(cm, function() { + var line = pos.line, lineN = lineNo(pos.line); + var view = findViewForLine(cm, lineN); + if (view) { + clearLineMeasurementCacheFor(view); + cm.curOp.selectionChanged = cm.curOp.forceUpdate = true; + } + cm.curOp.updateMaxLine = true; + if (!lineIsHidden(widget.doc, line) && widget.height != null) { + var oldHeight = widget.height; + widget.height = null; + var dHeight = widgetHeight(widget) - oldHeight; + if (dHeight) + updateLineHeight(line, line.height + dHeight); + } + }); + }; + + TextMarker.prototype.attachLine = function(line) { + if (!this.lines.length && this.doc.cm) { + var op = this.doc.cm.curOp; + if (!op.maybeHiddenMarkers || indexOf(op.maybeHiddenMarkers, this) == -1) + (op.maybeUnhiddenMarkers || (op.maybeUnhiddenMarkers = [])).push(this); + } + this.lines.push(line); + }; + TextMarker.prototype.detachLine = function(line) { + this.lines.splice(indexOf(this.lines, line), 1); + if (!this.lines.length && this.doc.cm) { + var op = this.doc.cm.curOp; + (op.maybeHiddenMarkers || (op.maybeHiddenMarkers = [])).push(this); + } + }; + + // Collapsed markers have unique ids, in order to be able to order + // them, which is needed for uniquely determining an outer marker + // when they overlap (they may nest, but not partially overlap). + var nextMarkerId = 0; + + // Create a marker, wire it up to the right lines, and + function markText(doc, from, to, options, type) { + // Shared markers (across linked documents) are handled separately + // (markTextShared will call out to this again, once per + // document). + if (options && options.shared) return markTextShared(doc, from, to, options, type); + // Ensure we are in an operation. + if (doc.cm && !doc.cm.curOp) return operation(doc.cm, markText)(doc, from, to, options, type); + + var marker = new TextMarker(doc, type), diff = cmp(from, to); + if (options) copyObj(options, marker, false); + // Don't connect empty markers unless clearWhenEmpty is false + if (diff > 0 || diff == 0 && marker.clearWhenEmpty !== false) + return marker; + if (marker.replacedWith) { + // Showing up as a widget implies collapsed (widget replaces text) + marker.collapsed = true; + marker.widgetNode = elt("span", [marker.replacedWith], "CodeMirror-widget"); + if (!options.handleMouseEvents) marker.widgetNode.setAttribute("cm-ignore-events", "true"); + if (options.insertLeft) marker.widgetNode.insertLeft = true; + } + if (marker.collapsed) { + if (conflictingCollapsedRange(doc, from.line, from, to, marker) || + from.line != to.line && conflictingCollapsedRange(doc, to.line, from, to, marker)) + throw new Error("Inserting collapsed marker partially overlapping an existing one"); + sawCollapsedSpans = true; + } + + if (marker.addToHistory) + addChangeToHistory(doc, {from: from, to: to, origin: "markText"}, doc.sel, NaN); + + var curLine = from.line, cm = doc.cm, updateMaxLine; + doc.iter(curLine, to.line + 1, function(line) { + if (cm && marker.collapsed && !cm.options.lineWrapping && visualLine(line) == cm.display.maxLine) + updateMaxLine = true; + if (marker.collapsed && curLine != from.line) updateLineHeight(line, 0); + addMarkedSpan(line, new MarkedSpan(marker, + curLine == from.line ? from.ch : null, + curLine == to.line ? to.ch : null)); + ++curLine; + }); + // lineIsHidden depends on the presence of the spans, so needs a second pass + if (marker.collapsed) doc.iter(from.line, to.line + 1, function(line) { + if (lineIsHidden(doc, line)) updateLineHeight(line, 0); + }); + + if (marker.clearOnEnter) on(marker, "beforeCursorEnter", function() { marker.clear(); }); + + if (marker.readOnly) { + sawReadOnlySpans = true; + if (doc.history.done.length || doc.history.undone.length) + doc.clearHistory(); + } + if (marker.collapsed) { + marker.id = ++nextMarkerId; + marker.atomic = true; + } + if (cm) { + // Sync editor state + if (updateMaxLine) cm.curOp.updateMaxLine = true; + if (marker.collapsed) + regChange(cm, from.line, to.line + 1); + else if (marker.className || marker.title || marker.startStyle || marker.endStyle || marker.css) + for (var i = from.line; i <= to.line; i++) regLineChange(cm, i, "text"); + if (marker.atomic) reCheckSelection(cm.doc); + signalLater(cm, "markerAdded", cm, marker); + } + return marker; + } + + // SHARED TEXTMARKERS + + // A shared marker spans multiple linked documents. It is + // implemented as a meta-marker-object controlling multiple normal + // markers. + var SharedTextMarker = CodeMirror.SharedTextMarker = function(markers, primary) { + this.markers = markers; + this.primary = primary; + for (var i = 0; i < markers.length; ++i) + markers[i].parent = this; + }; + eventMixin(SharedTextMarker); + + SharedTextMarker.prototype.clear = function() { + if (this.explicitlyCleared) return; + this.explicitlyCleared = true; + for (var i = 0; i < this.markers.length; ++i) + this.markers[i].clear(); + signalLater(this, "clear"); + }; + SharedTextMarker.prototype.find = function(side, lineObj) { + return this.primary.find(side, lineObj); + }; + + function markTextShared(doc, from, to, options, type) { + options = copyObj(options); + options.shared = false; + var markers = [markText(doc, from, to, options, type)], primary = markers[0]; + var widget = options.widgetNode; + linkedDocs(doc, function(doc) { + if (widget) options.widgetNode = widget.cloneNode(true); + markers.push(markText(doc, clipPos(doc, from), clipPos(doc, to), options, type)); + for (var i = 0; i < doc.linked.length; ++i) + if (doc.linked[i].isParent) return; + primary = lst(markers); + }); + return new SharedTextMarker(markers, primary); + } + + function findSharedMarkers(doc) { + return doc.findMarks(Pos(doc.first, 0), doc.clipPos(Pos(doc.lastLine())), + function(m) { return m.parent; }); + } + + function copySharedMarkers(doc, markers) { + for (var i = 0; i < markers.length; i++) { + var marker = markers[i], pos = marker.find(); + var mFrom = doc.clipPos(pos.from), mTo = doc.clipPos(pos.to); + if (cmp(mFrom, mTo)) { + var subMark = markText(doc, mFrom, mTo, marker.primary, marker.primary.type); + marker.markers.push(subMark); + subMark.parent = marker; + } + } + } + + function detachSharedMarkers(markers) { + for (var i = 0; i < markers.length; i++) { + var marker = markers[i], linked = [marker.primary.doc];; + linkedDocs(marker.primary.doc, function(d) { linked.push(d); }); + for (var j = 0; j < marker.markers.length; j++) { + var subMarker = marker.markers[j]; + if (indexOf(linked, subMarker.doc) == -1) { + subMarker.parent = null; + marker.markers.splice(j--, 1); + } + } + } + } + + // TEXTMARKER SPANS + + function MarkedSpan(marker, from, to) { + this.marker = marker; + this.from = from; this.to = to; + } + + // Search an array of spans for a span matching the given marker. + function getMarkedSpanFor(spans, marker) { + if (spans) for (var i = 0; i < spans.length; ++i) { + var span = spans[i]; + if (span.marker == marker) return span; + } + } + // Remove a span from an array, returning undefined if no spans are + // left (we don't store arrays for lines without spans). + function removeMarkedSpan(spans, span) { + for (var r, i = 0; i < spans.length; ++i) + if (spans[i] != span) (r || (r = [])).push(spans[i]); + return r; + } + // Add a span to a line. + function addMarkedSpan(line, span) { + line.markedSpans = line.markedSpans ? line.markedSpans.concat([span]) : [span]; + span.marker.attachLine(line); + } + + // Used for the algorithm that adjusts markers for a change in the + // document. These functions cut an array of spans at a given + // character position, returning an array of remaining chunks (or + // undefined if nothing remains). + function markedSpansBefore(old, startCh, isInsert) { + if (old) for (var i = 0, nw; i < old.length; ++i) { + var span = old[i], marker = span.marker; + var startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= startCh : span.from < startCh); + if (startsBefore || span.from == startCh && marker.type == "bookmark" && (!isInsert || !span.marker.insertLeft)) { + var endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= startCh : span.to > startCh); + (nw || (nw = [])).push(new MarkedSpan(marker, span.from, endsAfter ? null : span.to)); + } + } + return nw; + } + function markedSpansAfter(old, endCh, isInsert) { + if (old) for (var i = 0, nw; i < old.length; ++i) { + var span = old[i], marker = span.marker; + var endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= endCh : span.to > endCh); + if (endsAfter || span.from == endCh && marker.type == "bookmark" && (!isInsert || span.marker.insertLeft)) { + var startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= endCh : span.from < endCh); + (nw || (nw = [])).push(new MarkedSpan(marker, startsBefore ? null : span.from - endCh, + span.to == null ? null : span.to - endCh)); + } + } + return nw; + } + + // Given a change object, compute the new set of marker spans that + // cover the line in which the change took place. Removes spans + // entirely within the change, reconnects spans belonging to the + // same marker that appear on both sides of the change, and cuts off + // spans partially within the change. Returns an array of span + // arrays with one element for each line in (after) the change. + function stretchSpansOverChange(doc, change) { + if (change.full) return null; + var oldFirst = isLine(doc, change.from.line) && getLine(doc, change.from.line).markedSpans; + var oldLast = isLine(doc, change.to.line) && getLine(doc, change.to.line).markedSpans; + if (!oldFirst && !oldLast) return null; + + var startCh = change.from.ch, endCh = change.to.ch, isInsert = cmp(change.from, change.to) == 0; + // Get the spans that 'stick out' on both sides + var first = markedSpansBefore(oldFirst, startCh, isInsert); + var last = markedSpansAfter(oldLast, endCh, isInsert); + + // Next, merge those two ends + var sameLine = change.text.length == 1, offset = lst(change.text).length + (sameLine ? startCh : 0); + if (first) { + // Fix up .to properties of first + for (var i = 0; i < first.length; ++i) { + var span = first[i]; + if (span.to == null) { + var found = getMarkedSpanFor(last, span.marker); + if (!found) span.to = startCh; + else if (sameLine) span.to = found.to == null ? null : found.to + offset; + } + } + } + if (last) { + // Fix up .from in last (or move them into first in case of sameLine) + for (var i = 0; i < last.length; ++i) { + var span = last[i]; + if (span.to != null) span.to += offset; + if (span.from == null) { + var found = getMarkedSpanFor(first, span.marker); + if (!found) { + span.from = offset; + if (sameLine) (first || (first = [])).push(span); + } + } else { + span.from += offset; + if (sameLine) (first || (first = [])).push(span); + } + } + } + // Make sure we didn't create any zero-length spans + if (first) first = clearEmptySpans(first); + if (last && last != first) last = clearEmptySpans(last); + + var newMarkers = [first]; + if (!sameLine) { + // Fill gap with whole-line-spans + var gap = change.text.length - 2, gapMarkers; + if (gap > 0 && first) + for (var i = 0; i < first.length; ++i) + if (first[i].to == null) + (gapMarkers || (gapMarkers = [])).push(new MarkedSpan(first[i].marker, null, null)); + for (var i = 0; i < gap; ++i) + newMarkers.push(gapMarkers); + newMarkers.push(last); + } + return newMarkers; + } + + // Remove spans that are empty and don't have a clearWhenEmpty + // option of false. + function clearEmptySpans(spans) { + for (var i = 0; i < spans.length; ++i) { + var span = spans[i]; + if (span.from != null && span.from == span.to && span.marker.clearWhenEmpty !== false) + spans.splice(i--, 1); + } + if (!spans.length) return null; + return spans; + } + + // Used for un/re-doing changes from the history. Combines the + // result of computing the existing spans with the set of spans that + // existed in the history (so that deleting around a span and then + // undoing brings back the span). + function mergeOldSpans(doc, change) { + var old = getOldSpans(doc, change); + var stretched = stretchSpansOverChange(doc, change); + if (!old) return stretched; + if (!stretched) return old; + + for (var i = 0; i < old.length; ++i) { + var oldCur = old[i], stretchCur = stretched[i]; + if (oldCur && stretchCur) { + spans: for (var j = 0; j < stretchCur.length; ++j) { + var span = stretchCur[j]; + for (var k = 0; k < oldCur.length; ++k) + if (oldCur[k].marker == span.marker) continue spans; + oldCur.push(span); + } + } else if (stretchCur) { + old[i] = stretchCur; + } + } + return old; + } + + // Used to 'clip' out readOnly ranges when making a change. + function removeReadOnlyRanges(doc, from, to) { + var markers = null; + doc.iter(from.line, to.line + 1, function(line) { + if (line.markedSpans) for (var i = 0; i < line.markedSpans.length; ++i) { + var mark = line.markedSpans[i].marker; + if (mark.readOnly && (!markers || indexOf(markers, mark) == -1)) + (markers || (markers = [])).push(mark); + } + }); + if (!markers) return null; + var parts = [{from: from, to: to}]; + for (var i = 0; i < markers.length; ++i) { + var mk = markers[i], m = mk.find(0); + for (var j = 0; j < parts.length; ++j) { + var p = parts[j]; + if (cmp(p.to, m.from) < 0 || cmp(p.from, m.to) > 0) continue; + var newParts = [j, 1], dfrom = cmp(p.from, m.from), dto = cmp(p.to, m.to); + if (dfrom < 0 || !mk.inclusiveLeft && !dfrom) + newParts.push({from: p.from, to: m.from}); + if (dto > 0 || !mk.inclusiveRight && !dto) + newParts.push({from: m.to, to: p.to}); + parts.splice.apply(parts, newParts); + j += newParts.length - 1; + } + } + return parts; + } + + // Connect or disconnect spans from a line. + function detachMarkedSpans(line) { + var spans = line.markedSpans; + if (!spans) return; + for (var i = 0; i < spans.length; ++i) + spans[i].marker.detachLine(line); + line.markedSpans = null; + } + function attachMarkedSpans(line, spans) { + if (!spans) return; + for (var i = 0; i < spans.length; ++i) + spans[i].marker.attachLine(line); + line.markedSpans = spans; + } + + // Helpers used when computing which overlapping collapsed span + // counts as the larger one. + function extraLeft(marker) { return marker.inclusiveLeft ? -1 : 0; } + function extraRight(marker) { return marker.inclusiveRight ? 1 : 0; } + + // Returns a number indicating which of two overlapping collapsed + // spans is larger (and thus includes the other). Falls back to + // comparing ids when the spans cover exactly the same range. + function compareCollapsedMarkers(a, b) { + var lenDiff = a.lines.length - b.lines.length; + if (lenDiff != 0) return lenDiff; + var aPos = a.find(), bPos = b.find(); + var fromCmp = cmp(aPos.from, bPos.from) || extraLeft(a) - extraLeft(b); + if (fromCmp) return -fromCmp; + var toCmp = cmp(aPos.to, bPos.to) || extraRight(a) - extraRight(b); + if (toCmp) return toCmp; + return b.id - a.id; + } + + // Find out whether a line ends or starts in a collapsed span. If + // so, return the marker for that span. + function collapsedSpanAtSide(line, start) { + var sps = sawCollapsedSpans && line.markedSpans, found; + if (sps) for (var sp, i = 0; i < sps.length; ++i) { + sp = sps[i]; + if (sp.marker.collapsed && (start ? sp.from : sp.to) == null && + (!found || compareCollapsedMarkers(found, sp.marker) < 0)) + found = sp.marker; + } + return found; + } + function collapsedSpanAtStart(line) { return collapsedSpanAtSide(line, true); } + function collapsedSpanAtEnd(line) { return collapsedSpanAtSide(line, false); } + + // Test whether there exists a collapsed span that partially + // overlaps (covers the start or end, but not both) of a new span. + // Such overlap is not allowed. + function conflictingCollapsedRange(doc, lineNo, from, to, marker) { + var line = getLine(doc, lineNo); + var sps = sawCollapsedSpans && line.markedSpans; + if (sps) for (var i = 0; i < sps.length; ++i) { + var sp = sps[i]; + if (!sp.marker.collapsed) continue; + var found = sp.marker.find(0); + var fromCmp = cmp(found.from, from) || extraLeft(sp.marker) - extraLeft(marker); + var toCmp = cmp(found.to, to) || extraRight(sp.marker) - extraRight(marker); + if (fromCmp >= 0 && toCmp <= 0 || fromCmp <= 0 && toCmp >= 0) continue; + if (fromCmp <= 0 && (cmp(found.to, from) > 0 || (sp.marker.inclusiveRight && marker.inclusiveLeft)) || + fromCmp >= 0 && (cmp(found.from, to) < 0 || (sp.marker.inclusiveLeft && marker.inclusiveRight))) + return true; + } + } + + // A visual line is a line as drawn on the screen. Folding, for + // example, can cause multiple logical lines to appear on the same + // visual line. This finds the start of the visual line that the + // given line is part of (usually that is the line itself). + function visualLine(line) { + var merged; + while (merged = collapsedSpanAtStart(line)) + line = merged.find(-1, true).line; + return line; + } + + // Returns an array of logical lines that continue the visual line + // started by the argument, or undefined if there are no such lines. + function visualLineContinued(line) { + var merged, lines; + while (merged = collapsedSpanAtEnd(line)) { + line = merged.find(1, true).line; + (lines || (lines = [])).push(line); + } + return lines; + } + + // Get the line number of the start of the visual line that the + // given line number is part of. + function visualLineNo(doc, lineN) { + var line = getLine(doc, lineN), vis = visualLine(line); + if (line == vis) return lineN; + return lineNo(vis); + } + // Get the line number of the start of the next visual line after + // the given line. + function visualLineEndNo(doc, lineN) { + if (lineN > doc.lastLine()) return lineN; + var line = getLine(doc, lineN), merged; + if (!lineIsHidden(doc, line)) return lineN; + while (merged = collapsedSpanAtEnd(line)) + line = merged.find(1, true).line; + return lineNo(line) + 1; + } + + // Compute whether a line is hidden. Lines count as hidden when they + // are part of a visual line that starts with another line, or when + // they are entirely covered by collapsed, non-widget span. + function lineIsHidden(doc, line) { + var sps = sawCollapsedSpans && line.markedSpans; + if (sps) for (var sp, i = 0; i < sps.length; ++i) { + sp = sps[i]; + if (!sp.marker.collapsed) continue; + if (sp.from == null) return true; + if (sp.marker.widgetNode) continue; + if (sp.from == 0 && sp.marker.inclusiveLeft && lineIsHiddenInner(doc, line, sp)) + return true; + } + } + function lineIsHiddenInner(doc, line, span) { + if (span.to == null) { + var end = span.marker.find(1, true); + return lineIsHiddenInner(doc, end.line, getMarkedSpanFor(end.line.markedSpans, span.marker)); + } + if (span.marker.inclusiveRight && span.to == line.text.length) + return true; + for (var sp, i = 0; i < line.markedSpans.length; ++i) { + sp = line.markedSpans[i]; + if (sp.marker.collapsed && !sp.marker.widgetNode && sp.from == span.to && + (sp.to == null || sp.to != span.from) && + (sp.marker.inclusiveLeft || span.marker.inclusiveRight) && + lineIsHiddenInner(doc, line, sp)) return true; + } + } + + // LINE WIDGETS + + // Line widgets are block elements displayed above or below a line. + + var LineWidget = CodeMirror.LineWidget = function(doc, node, options) { + if (options) for (var opt in options) if (options.hasOwnProperty(opt)) + this[opt] = options[opt]; + this.doc = doc; + this.node = node; + }; + eventMixin(LineWidget); + + function adjustScrollWhenAboveVisible(cm, line, diff) { + if (heightAtLine(line) < ((cm.curOp && cm.curOp.scrollTop) || cm.doc.scrollTop)) + addToScrollPos(cm, null, diff); + } + + LineWidget.prototype.clear = function() { + var cm = this.doc.cm, ws = this.line.widgets, line = this.line, no = lineNo(line); + if (no == null || !ws) return; + for (var i = 0; i < ws.length; ++i) if (ws[i] == this) ws.splice(i--, 1); + if (!ws.length) line.widgets = null; + var height = widgetHeight(this); + updateLineHeight(line, Math.max(0, line.height - height)); + if (cm) runInOp(cm, function() { + adjustScrollWhenAboveVisible(cm, line, -height); + regLineChange(cm, no, "widget"); + }); + }; + LineWidget.prototype.changed = function() { + var oldH = this.height, cm = this.doc.cm, line = this.line; + this.height = null; + var diff = widgetHeight(this) - oldH; + if (!diff) return; + updateLineHeight(line, line.height + diff); + if (cm) runInOp(cm, function() { + cm.curOp.forceUpdate = true; + adjustScrollWhenAboveVisible(cm, line, diff); + }); + }; + + function widgetHeight(widget) { + if (widget.height != null) return widget.height; + var cm = widget.doc.cm; + if (!cm) return 0; + if (!contains(document.body, widget.node)) { + var parentStyle = "position: relative;"; + if (widget.coverGutter) + parentStyle += "margin-left: -" + cm.display.gutters.offsetWidth + "px;"; + if (widget.noHScroll) + parentStyle += "width: " + cm.display.wrapper.clientWidth + "px;"; + removeChildrenAndAdd(cm.display.measure, elt("div", [widget.node], null, parentStyle)); + } + return widget.height = widget.node.offsetHeight; + } + + function addLineWidget(doc, handle, node, options) { + var widget = new LineWidget(doc, node, options); + var cm = doc.cm; + if (cm && widget.noHScroll) cm.display.alignWidgets = true; + changeLine(doc, handle, "widget", function(line) { + var widgets = line.widgets || (line.widgets = []); + if (widget.insertAt == null) widgets.push(widget); + else widgets.splice(Math.min(widgets.length - 1, Math.max(0, widget.insertAt)), 0, widget); + widget.line = line; + if (cm && !lineIsHidden(doc, line)) { + var aboveVisible = heightAtLine(line) < doc.scrollTop; + updateLineHeight(line, line.height + widgetHeight(widget)); + if (aboveVisible) addToScrollPos(cm, null, widget.height); + cm.curOp.forceUpdate = true; + } + return true; + }); + return widget; + } + + // LINE DATA STRUCTURE + + // Line objects. These hold state related to a line, including + // highlighting info (the styles array). + var Line = CodeMirror.Line = function(text, markedSpans, estimateHeight) { + this.text = text; + attachMarkedSpans(this, markedSpans); + this.height = estimateHeight ? estimateHeight(this) : 1; + }; + eventMixin(Line); + Line.prototype.lineNo = function() { return lineNo(this); }; + + // Change the content (text, markers) of a line. Automatically + // invalidates cached information and tries to re-estimate the + // line's height. + function updateLine(line, text, markedSpans, estimateHeight) { + line.text = text; + if (line.stateAfter) line.stateAfter = null; + if (line.styles) line.styles = null; + if (line.order != null) line.order = null; + detachMarkedSpans(line); + attachMarkedSpans(line, markedSpans); + var estHeight = estimateHeight ? estimateHeight(line) : 1; + if (estHeight != line.height) updateLineHeight(line, estHeight); + } + + // Detach a line from the document tree and its markers. + function cleanUpLine(line) { + line.parent = null; + detachMarkedSpans(line); + } + + function extractLineClasses(type, output) { + if (type) for (;;) { + var lineClass = type.match(/(?:^|\s+)line-(background-)?(\S+)/); + if (!lineClass) break; + type = type.slice(0, lineClass.index) + type.slice(lineClass.index + lineClass[0].length); + var prop = lineClass[1] ? "bgClass" : "textClass"; + if (output[prop] == null) + output[prop] = lineClass[2]; + else if (!(new RegExp("(?:^|\s)" + lineClass[2] + "(?:$|\s)")).test(output[prop])) + output[prop] += " " + lineClass[2]; + } + return type; + } + + function callBlankLine(mode, state) { + if (mode.blankLine) return mode.blankLine(state); + if (!mode.innerMode) return; + var inner = CodeMirror.innerMode(mode, state); + if (inner.mode.blankLine) return inner.mode.blankLine(inner.state); + } + + function readToken(mode, stream, state, inner) { + for (var i = 0; i < 10; i++) { + if (inner) inner[0] = CodeMirror.innerMode(mode, state).mode; + var style = mode.token(stream, state); + if (stream.pos > stream.start) return style; + } + throw new Error("Mode " + mode.name + " failed to advance stream."); + } + + // Utility for getTokenAt and getLineTokens + function takeToken(cm, pos, precise, asArray) { + function getObj(copy) { + return {start: stream.start, end: stream.pos, + string: stream.current(), + type: style || null, + state: copy ? copyState(doc.mode, state) : state}; + } + + var doc = cm.doc, mode = doc.mode, style; + pos = clipPos(doc, pos); + var line = getLine(doc, pos.line), state = getStateBefore(cm, pos.line, precise); + var stream = new StringStream(line.text, cm.options.tabSize), tokens; + if (asArray) tokens = []; + while ((asArray || stream.pos < pos.ch) && !stream.eol()) { + stream.start = stream.pos; + style = readToken(mode, stream, state); + if (asArray) tokens.push(getObj(true)); + } + return asArray ? tokens : getObj(); + } + + // Run the given mode's parser over a line, calling f for each token. + function runMode(cm, text, mode, state, f, lineClasses, forceToEnd) { + var flattenSpans = mode.flattenSpans; + if (flattenSpans == null) flattenSpans = cm.options.flattenSpans; + var curStart = 0, curStyle = null; + var stream = new StringStream(text, cm.options.tabSize), style; + var inner = cm.options.addModeClass && [null]; + if (text == "") extractLineClasses(callBlankLine(mode, state), lineClasses); + while (!stream.eol()) { + if (stream.pos > cm.options.maxHighlightLength) { + flattenSpans = false; + if (forceToEnd) processLine(cm, text, state, stream.pos); + stream.pos = text.length; + style = null; + } else { + style = extractLineClasses(readToken(mode, stream, state, inner), lineClasses); + } + if (inner) { + var mName = inner[0].name; + if (mName) style = "m-" + (style ? mName + " " + style : mName); + } + if (!flattenSpans || curStyle != style) { + while (curStart < stream.start) { + curStart = Math.min(stream.start, curStart + 50000); + f(curStart, curStyle); + } + curStyle = style; + } + stream.start = stream.pos; + } + while (curStart < stream.pos) { + // Webkit seems to refuse to render text nodes longer than 57444 characters + var pos = Math.min(stream.pos, curStart + 50000); + f(pos, curStyle); + curStart = pos; + } + } + + // Compute a style array (an array starting with a mode generation + // -- for invalidation -- followed by pairs of end positions and + // style strings), which is used to highlight the tokens on the + // line. + function highlightLine(cm, line, state, forceToEnd) { + // A styles array always starts with a number identifying the + // mode/overlays that it is based on (for easy invalidation). + var st = [cm.state.modeGen], lineClasses = {}; + // Compute the base array of styles + runMode(cm, line.text, cm.doc.mode, state, function(end, style) { + st.push(end, style); + }, lineClasses, forceToEnd); + + // Run overlays, adjust style array. + for (var o = 0; o < cm.state.overlays.length; ++o) { + var overlay = cm.state.overlays[o], i = 1, at = 0; + runMode(cm, line.text, overlay.mode, true, function(end, style) { + var start = i; + // Ensure there's a token end at the current position, and that i points at it + while (at < end) { + var i_end = st[i]; + if (i_end > end) + st.splice(i, 1, end, st[i+1], i_end); + i += 2; + at = Math.min(end, i_end); + } + if (!style) return; + if (overlay.opaque) { + st.splice(start, i - start, end, "cm-overlay " + style); + i = start + 2; + } else { + for (; start < i; start += 2) { + var cur = st[start+1]; + st[start+1] = (cur ? cur + " " : "") + "cm-overlay " + style; + } + } + }, lineClasses); + } + + return {styles: st, classes: lineClasses.bgClass || lineClasses.textClass ? lineClasses : null}; + } + + function getLineStyles(cm, line, updateFrontier) { + if (!line.styles || line.styles[0] != cm.state.modeGen) { + var result = highlightLine(cm, line, line.stateAfter = getStateBefore(cm, lineNo(line))); + line.styles = result.styles; + if (result.classes) line.styleClasses = result.classes; + else if (line.styleClasses) line.styleClasses = null; + if (updateFrontier === cm.doc.frontier) cm.doc.frontier++; + } + return line.styles; + } + + // Lightweight form of highlight -- proceed over this line and + // update state, but don't save a style array. Used for lines that + // aren't currently visible. + function processLine(cm, text, state, startAt) { + var mode = cm.doc.mode; + var stream = new StringStream(text, cm.options.tabSize); + stream.start = stream.pos = startAt || 0; + if (text == "") callBlankLine(mode, state); + while (!stream.eol() && stream.pos <= cm.options.maxHighlightLength) { + readToken(mode, stream, state); + stream.start = stream.pos; + } + } + + // Convert a style as returned by a mode (either null, or a string + // containing one or more styles) to a CSS style. This is cached, + // and also looks for line-wide styles. + var styleToClassCache = {}, styleToClassCacheWithMode = {}; + function interpretTokenStyle(style, options) { + if (!style || /^\s*$/.test(style)) return null; + var cache = options.addModeClass ? styleToClassCacheWithMode : styleToClassCache; + return cache[style] || + (cache[style] = style.replace(/\S+/g, "cm-$&")); + } + + // Render the DOM representation of the text of a line. Also builds + // up a 'line map', which points at the DOM nodes that represent + // specific stretches of text, and is used by the measuring code. + // The returned object contains the DOM node, this map, and + // information about line-wide styles that were set by the mode. + function buildLineContent(cm, lineView) { + // The padding-right forces the element to have a 'border', which + // is needed on Webkit to be able to get line-level bounding + // rectangles for it (in measureChar). + var content = elt("span", null, null, webkit ? "padding-right: .1px" : null); + var builder = {pre: elt("pre", [content]), content: content, + col: 0, pos: 0, cm: cm, + splitSpaces: (ie || webkit) && cm.getOption("lineWrapping")}; + lineView.measure = {}; + + // Iterate over the logical lines that make up this visual line. + for (var i = 0; i <= (lineView.rest ? lineView.rest.length : 0); i++) { + var line = i ? lineView.rest[i - 1] : lineView.line, order; + builder.pos = 0; + builder.addToken = buildToken; + // Optionally wire in some hacks into the token-rendering + // algorithm, to deal with browser quirks. + if (hasBadBidiRects(cm.display.measure) && (order = getOrder(line))) + builder.addToken = buildTokenBadBidi(builder.addToken, order); + builder.map = []; + var allowFrontierUpdate = lineView != cm.display.externalMeasured && lineNo(line); + insertLineContent(line, builder, getLineStyles(cm, line, allowFrontierUpdate)); + if (line.styleClasses) { + if (line.styleClasses.bgClass) + builder.bgClass = joinClasses(line.styleClasses.bgClass, builder.bgClass || ""); + if (line.styleClasses.textClass) + builder.textClass = joinClasses(line.styleClasses.textClass, builder.textClass || ""); + } + + // Ensure at least a single node is present, for measuring. + if (builder.map.length == 0) + builder.map.push(0, 0, builder.content.appendChild(zeroWidthElement(cm.display.measure))); + + // Store the map and a cache object for the current logical line + if (i == 0) { + lineView.measure.map = builder.map; + lineView.measure.cache = {}; + } else { + (lineView.measure.maps || (lineView.measure.maps = [])).push(builder.map); + (lineView.measure.caches || (lineView.measure.caches = [])).push({}); + } + } + + // See issue #2901 + if (webkit && /\bcm-tab\b/.test(builder.content.lastChild.className)) + builder.content.className = "cm-tab-wrap-hack"; + + signal(cm, "renderLine", cm, lineView.line, builder.pre); + if (builder.pre.className) + builder.textClass = joinClasses(builder.pre.className, builder.textClass || ""); + + return builder; + } + + function defaultSpecialCharPlaceholder(ch) { + var token = elt("span", "\u2022", "cm-invalidchar"); + token.title = "\\u" + ch.charCodeAt(0).toString(16); + token.setAttribute("aria-label", token.title); + return token; + } + + // Build up the DOM representation for a single token, and add it to + // the line map. Takes care to render special characters separately. + function buildToken(builder, text, style, startStyle, endStyle, title, css) { + if (!text) return; + var displayText = builder.splitSpaces ? text.replace(/ {3,}/g, splitSpaces) : text; + var special = builder.cm.state.specialChars, mustWrap = false; + if (!special.test(text)) { + builder.col += text.length; + var content = document.createTextNode(displayText); + builder.map.push(builder.pos, builder.pos + text.length, content); + if (ie && ie_version < 9) mustWrap = true; + builder.pos += text.length; + } else { + var content = document.createDocumentFragment(), pos = 0; + while (true) { + special.lastIndex = pos; + var m = special.exec(text); + var skipped = m ? m.index - pos : text.length - pos; + if (skipped) { + var txt = document.createTextNode(displayText.slice(pos, pos + skipped)); + if (ie && ie_version < 9) content.appendChild(elt("span", [txt])); + else content.appendChild(txt); + builder.map.push(builder.pos, builder.pos + skipped, txt); + builder.col += skipped; + builder.pos += skipped; + } + if (!m) break; + pos += skipped + 1; + if (m[0] == "\t") { + var tabSize = builder.cm.options.tabSize, tabWidth = tabSize - builder.col % tabSize; + var txt = content.appendChild(elt("span", spaceStr(tabWidth), "cm-tab")); + txt.setAttribute("role", "presentation"); + txt.setAttribute("cm-text", "\t"); + builder.col += tabWidth; + } else { + var txt = builder.cm.options.specialCharPlaceholder(m[0]); + txt.setAttribute("cm-text", m[0]); + if (ie && ie_version < 9) content.appendChild(elt("span", [txt])); + else content.appendChild(txt); + builder.col += 1; + } + builder.map.push(builder.pos, builder.pos + 1, txt); + builder.pos++; + } + } + if (style || startStyle || endStyle || mustWrap || css) { + var fullStyle = style || ""; + if (startStyle) fullStyle += startStyle; + if (endStyle) fullStyle += endStyle; + var token = elt("span", [content], fullStyle, css); + if (title) token.title = title; + return builder.content.appendChild(token); + } + builder.content.appendChild(content); + } + + function splitSpaces(old) { + var out = " "; + for (var i = 0; i < old.length - 2; ++i) out += i % 2 ? " " : "\u00a0"; + out += " "; + return out; + } + + // Work around nonsense dimensions being reported for stretches of + // right-to-left text. + function buildTokenBadBidi(inner, order) { + return function(builder, text, style, startStyle, endStyle, title, css) { + style = style ? style + " cm-force-border" : "cm-force-border"; + var start = builder.pos, end = start + text.length; + for (;;) { + // Find the part that overlaps with the start of this text + for (var i = 0; i < order.length; i++) { + var part = order[i]; + if (part.to > start && part.from <= start) break; + } + if (part.to >= end) return inner(builder, text, style, startStyle, endStyle, title, css); + inner(builder, text.slice(0, part.to - start), style, startStyle, null, title, css); + startStyle = null; + text = text.slice(part.to - start); + start = part.to; + } + }; + } + + function buildCollapsedSpan(builder, size, marker, ignoreWidget) { + var widget = !ignoreWidget && marker.widgetNode; + if (widget) builder.map.push(builder.pos, builder.pos + size, widget); + if (!ignoreWidget && builder.cm.display.input.needsContentAttribute) { + if (!widget) + widget = builder.content.appendChild(document.createElement("span")); + widget.setAttribute("cm-marker", marker.id); + } + if (widget) { + builder.cm.display.input.setUneditable(widget); + builder.content.appendChild(widget); + } + builder.pos += size; + } + + // Outputs a number of spans to make up a line, taking highlighting + // and marked text into account. + function insertLineContent(line, builder, styles) { + var spans = line.markedSpans, allText = line.text, at = 0; + if (!spans) { + for (var i = 1; i < styles.length; i+=2) + builder.addToken(builder, allText.slice(at, at = styles[i]), interpretTokenStyle(styles[i+1], builder.cm.options)); + return; + } + + var len = allText.length, pos = 0, i = 1, text = "", style, css; + var nextChange = 0, spanStyle, spanEndStyle, spanStartStyle, title, collapsed; + for (;;) { + if (nextChange == pos) { // Update current marker set + spanStyle = spanEndStyle = spanStartStyle = title = css = ""; + collapsed = null; nextChange = Infinity; + var foundBookmarks = []; + for (var j = 0; j < spans.length; ++j) { + var sp = spans[j], m = sp.marker; + if (sp.from <= pos && (sp.to == null || sp.to > pos)) { + if (sp.to != null && nextChange > sp.to) { nextChange = sp.to; spanEndStyle = ""; } + if (m.className) spanStyle += " " + m.className; + if (m.css) css = m.css; + if (m.startStyle && sp.from == pos) spanStartStyle += " " + m.startStyle; + if (m.endStyle && sp.to == nextChange) spanEndStyle += " " + m.endStyle; + if (m.title && !title) title = m.title; + if (m.collapsed && (!collapsed || compareCollapsedMarkers(collapsed.marker, m) < 0)) + collapsed = sp; + } else if (sp.from > pos && nextChange > sp.from) { + nextChange = sp.from; + } + if (m.type == "bookmark" && sp.from == pos && m.widgetNode) foundBookmarks.push(m); + } + if (collapsed && (collapsed.from || 0) == pos) { + buildCollapsedSpan(builder, (collapsed.to == null ? len + 1 : collapsed.to) - pos, + collapsed.marker, collapsed.from == null); + if (collapsed.to == null) return; + } + if (!collapsed && foundBookmarks.length) for (var j = 0; j < foundBookmarks.length; ++j) + buildCollapsedSpan(builder, 0, foundBookmarks[j]); + } + if (pos >= len) break; + + var upto = Math.min(len, nextChange); + while (true) { + if (text) { + var end = pos + text.length; + if (!collapsed) { + var tokenText = end > upto ? text.slice(0, upto - pos) : text; + builder.addToken(builder, tokenText, style ? style + spanStyle : spanStyle, + spanStartStyle, pos + tokenText.length == nextChange ? spanEndStyle : "", title, css); + } + if (end >= upto) {text = text.slice(upto - pos); pos = upto; break;} + pos = end; + spanStartStyle = ""; + } + text = allText.slice(at, at = styles[i++]); + style = interpretTokenStyle(styles[i++], builder.cm.options); + } + } + } + + // DOCUMENT DATA STRUCTURE + + // By default, updates that start and end at the beginning of a line + // are treated specially, in order to make the association of line + // widgets and marker elements with the text behave more intuitive. + function isWholeLineUpdate(doc, change) { + return change.from.ch == 0 && change.to.ch == 0 && lst(change.text) == "" && + (!doc.cm || doc.cm.options.wholeLineUpdateBefore); + } + + // Perform a change on the document data structure. + function updateDoc(doc, change, markedSpans, estimateHeight) { + function spansFor(n) {return markedSpans ? markedSpans[n] : null;} + function update(line, text, spans) { + updateLine(line, text, spans, estimateHeight); + signalLater(line, "change", line, change); + } + function linesFor(start, end) { + for (var i = start, result = []; i < end; ++i) + result.push(new Line(text[i], spansFor(i), estimateHeight)); + return result; + } + + var from = change.from, to = change.to, text = change.text; + var firstLine = getLine(doc, from.line), lastLine = getLine(doc, to.line); + var lastText = lst(text), lastSpans = spansFor(text.length - 1), nlines = to.line - from.line; + + // Adjust the line structure + if (change.full) { + doc.insert(0, linesFor(0, text.length)); + doc.remove(text.length, doc.size - text.length); + } else if (isWholeLineUpdate(doc, change)) { + // This is a whole-line replace. Treated specially to make + // sure line objects move the way they are supposed to. + var added = linesFor(0, text.length - 1); + update(lastLine, lastLine.text, lastSpans); + if (nlines) doc.remove(from.line, nlines); + if (added.length) doc.insert(from.line, added); + } else if (firstLine == lastLine) { + if (text.length == 1) { + update(firstLine, firstLine.text.slice(0, from.ch) + lastText + firstLine.text.slice(to.ch), lastSpans); + } else { + var added = linesFor(1, text.length - 1); + added.push(new Line(lastText + firstLine.text.slice(to.ch), lastSpans, estimateHeight)); + update(firstLine, firstLine.text.slice(0, from.ch) + text[0], spansFor(0)); + doc.insert(from.line + 1, added); + } + } else if (text.length == 1) { + update(firstLine, firstLine.text.slice(0, from.ch) + text[0] + lastLine.text.slice(to.ch), spansFor(0)); + doc.remove(from.line + 1, nlines); + } else { + update(firstLine, firstLine.text.slice(0, from.ch) + text[0], spansFor(0)); + update(lastLine, lastText + lastLine.text.slice(to.ch), lastSpans); + var added = linesFor(1, text.length - 1); + if (nlines > 1) doc.remove(from.line + 1, nlines - 1); + doc.insert(from.line + 1, added); + } + + signalLater(doc, "change", doc, change); + } + + // The document is represented as a BTree consisting of leaves, with + // chunk of lines in them, and branches, with up to ten leaves or + // other branch nodes below them. The top node is always a branch + // node, and is the document object itself (meaning it has + // additional methods and properties). + // + // All nodes have parent links. The tree is used both to go from + // line numbers to line objects, and to go from objects to numbers. + // It also indexes by height, and is used to convert between height + // and line object, and to find the total height of the document. + // + // See also http://marijnhaverbeke.nl/blog/codemirror-line-tree.html + + function LeafChunk(lines) { + this.lines = lines; + this.parent = null; + for (var i = 0, height = 0; i < lines.length; ++i) { + lines[i].parent = this; + height += lines[i].height; + } + this.height = height; + } + + LeafChunk.prototype = { + chunkSize: function() { return this.lines.length; }, + // Remove the n lines at offset 'at'. + removeInner: function(at, n) { + for (var i = at, e = at + n; i < e; ++i) { + var line = this.lines[i]; + this.height -= line.height; + cleanUpLine(line); + signalLater(line, "delete"); + } + this.lines.splice(at, n); + }, + // Helper used to collapse a small branch into a single leaf. + collapse: function(lines) { + lines.push.apply(lines, this.lines); + }, + // Insert the given array of lines at offset 'at', count them as + // having the given height. + insertInner: function(at, lines, height) { + this.height += height; + this.lines = this.lines.slice(0, at).concat(lines).concat(this.lines.slice(at)); + for (var i = 0; i < lines.length; ++i) lines[i].parent = this; + }, + // Used to iterate over a part of the tree. + iterN: function(at, n, op) { + for (var e = at + n; at < e; ++at) + if (op(this.lines[at])) return true; + } + }; + + function BranchChunk(children) { + this.children = children; + var size = 0, height = 0; + for (var i = 0; i < children.length; ++i) { + var ch = children[i]; + size += ch.chunkSize(); height += ch.height; + ch.parent = this; + } + this.size = size; + this.height = height; + this.parent = null; + } + + BranchChunk.prototype = { + chunkSize: function() { return this.size; }, + removeInner: function(at, n) { + this.size -= n; + for (var i = 0; i < this.children.length; ++i) { + var child = this.children[i], sz = child.chunkSize(); + if (at < sz) { + var rm = Math.min(n, sz - at), oldHeight = child.height; + child.removeInner(at, rm); + this.height -= oldHeight - child.height; + if (sz == rm) { this.children.splice(i--, 1); child.parent = null; } + if ((n -= rm) == 0) break; + at = 0; + } else at -= sz; + } + // If the result is smaller than 25 lines, ensure that it is a + // single leaf node. + if (this.size - n < 25 && + (this.children.length > 1 || !(this.children[0] instanceof LeafChunk))) { + var lines = []; + this.collapse(lines); + this.children = [new LeafChunk(lines)]; + this.children[0].parent = this; + } + }, + collapse: function(lines) { + for (var i = 0; i < this.children.length; ++i) this.children[i].collapse(lines); + }, + insertInner: function(at, lines, height) { + this.size += lines.length; + this.height += height; + for (var i = 0; i < this.children.length; ++i) { + var child = this.children[i], sz = child.chunkSize(); + if (at <= sz) { + child.insertInner(at, lines, height); + if (child.lines && child.lines.length > 50) { + while (child.lines.length > 50) { + var spilled = child.lines.splice(child.lines.length - 25, 25); + var newleaf = new LeafChunk(spilled); + child.height -= newleaf.height; + this.children.splice(i + 1, 0, newleaf); + newleaf.parent = this; + } + this.maybeSpill(); + } + break; + } + at -= sz; + } + }, + // When a node has grown, check whether it should be split. + maybeSpill: function() { + if (this.children.length <= 10) return; + var me = this; + do { + var spilled = me.children.splice(me.children.length - 5, 5); + var sibling = new BranchChunk(spilled); + if (!me.parent) { // Become the parent node + var copy = new BranchChunk(me.children); + copy.parent = me; + me.children = [copy, sibling]; + me = copy; + } else { + me.size -= sibling.size; + me.height -= sibling.height; + var myIndex = indexOf(me.parent.children, me); + me.parent.children.splice(myIndex + 1, 0, sibling); + } + sibling.parent = me.parent; + } while (me.children.length > 10); + me.parent.maybeSpill(); + }, + iterN: function(at, n, op) { + for (var i = 0; i < this.children.length; ++i) { + var child = this.children[i], sz = child.chunkSize(); + if (at < sz) { + var used = Math.min(n, sz - at); + if (child.iterN(at, used, op)) return true; + if ((n -= used) == 0) break; + at = 0; + } else at -= sz; + } + } + }; + + var nextDocId = 0; + var Doc = CodeMirror.Doc = function(text, mode, firstLine) { + if (!(this instanceof Doc)) return new Doc(text, mode, firstLine); + if (firstLine == null) firstLine = 0; + + BranchChunk.call(this, [new LeafChunk([new Line("", null)])]); + this.first = firstLine; + this.scrollTop = this.scrollLeft = 0; + this.cantEdit = false; + this.cleanGeneration = 1; + this.frontier = firstLine; + var start = Pos(firstLine, 0); + this.sel = simpleSelection(start); + this.history = new History(null); + this.id = ++nextDocId; + this.modeOption = mode; + + if (typeof text == "string") text = splitLines(text); + updateDoc(this, {from: start, to: start, text: text}); + setSelection(this, simpleSelection(start), sel_dontScroll); + }; + + Doc.prototype = createObj(BranchChunk.prototype, { + constructor: Doc, + // Iterate over the document. Supports two forms -- with only one + // argument, it calls that for each line in the document. With + // three, it iterates over the range given by the first two (with + // the second being non-inclusive). + iter: function(from, to, op) { + if (op) this.iterN(from - this.first, to - from, op); + else this.iterN(this.first, this.first + this.size, from); + }, + + // Non-public interface for adding and removing lines. + insert: function(at, lines) { + var height = 0; + for (var i = 0; i < lines.length; ++i) height += lines[i].height; + this.insertInner(at - this.first, lines, height); + }, + remove: function(at, n) { this.removeInner(at - this.first, n); }, + + // From here, the methods are part of the public interface. Most + // are also available from CodeMirror (editor) instances. + + getValue: function(lineSep) { + var lines = getLines(this, this.first, this.first + this.size); + if (lineSep === false) return lines; + return lines.join(lineSep || "\n"); + }, + setValue: docMethodOp(function(code) { + var top = Pos(this.first, 0), last = this.first + this.size - 1; + makeChange(this, {from: top, to: Pos(last, getLine(this, last).text.length), + text: splitLines(code), origin: "setValue", full: true}, true); + setSelection(this, simpleSelection(top)); + }), + replaceRange: function(code, from, to, origin) { + from = clipPos(this, from); + to = to ? clipPos(this, to) : from; + replaceRange(this, code, from, to, origin); + }, + getRange: function(from, to, lineSep) { + var lines = getBetween(this, clipPos(this, from), clipPos(this, to)); + if (lineSep === false) return lines; + return lines.join(lineSep || "\n"); + }, + + getLine: function(line) {var l = this.getLineHandle(line); return l && l.text;}, + + getLineHandle: function(line) {if (isLine(this, line)) return getLine(this, line);}, + getLineNumber: function(line) {return lineNo(line);}, + + getLineHandleVisualStart: function(line) { + if (typeof line == "number") line = getLine(this, line); + return visualLine(line); + }, + + lineCount: function() {return this.size;}, + firstLine: function() {return this.first;}, + lastLine: function() {return this.first + this.size - 1;}, + + clipPos: function(pos) {return clipPos(this, pos);}, + + getCursor: function(start) { + var range = this.sel.primary(), pos; + if (start == null || start == "head") pos = range.head; + else if (start == "anchor") pos = range.anchor; + else if (start == "end" || start == "to" || start === false) pos = range.to(); + else pos = range.from(); + return pos; + }, + listSelections: function() { return this.sel.ranges; }, + somethingSelected: function() {return this.sel.somethingSelected();}, + + setCursor: docMethodOp(function(line, ch, options) { + setSimpleSelection(this, clipPos(this, typeof line == "number" ? Pos(line, ch || 0) : line), null, options); + }), + setSelection: docMethodOp(function(anchor, head, options) { + setSimpleSelection(this, clipPos(this, anchor), clipPos(this, head || anchor), options); + }), + extendSelection: docMethodOp(function(head, other, options) { + extendSelection(this, clipPos(this, head), other && clipPos(this, other), options); + }), + extendSelections: docMethodOp(function(heads, options) { + extendSelections(this, clipPosArray(this, heads, options)); + }), + extendSelectionsBy: docMethodOp(function(f, options) { + extendSelections(this, map(this.sel.ranges, f), options); + }), + setSelections: docMethodOp(function(ranges, primary, options) { + if (!ranges.length) return; + for (var i = 0, out = []; i < ranges.length; i++) + out[i] = new Range(clipPos(this, ranges[i].anchor), + clipPos(this, ranges[i].head)); + if (primary == null) primary = Math.min(ranges.length - 1, this.sel.primIndex); + setSelection(this, normalizeSelection(out, primary), options); + }), + addSelection: docMethodOp(function(anchor, head, options) { + var ranges = this.sel.ranges.slice(0); + ranges.push(new Range(clipPos(this, anchor), clipPos(this, head || anchor))); + setSelection(this, normalizeSelection(ranges, ranges.length - 1), options); + }), + + getSelection: function(lineSep) { + var ranges = this.sel.ranges, lines; + for (var i = 0; i < ranges.length; i++) { + var sel = getBetween(this, ranges[i].from(), ranges[i].to()); + lines = lines ? lines.concat(sel) : sel; + } + if (lineSep === false) return lines; + else return lines.join(lineSep || "\n"); + }, + getSelections: function(lineSep) { + var parts = [], ranges = this.sel.ranges; + for (var i = 0; i < ranges.length; i++) { + var sel = getBetween(this, ranges[i].from(), ranges[i].to()); + if (lineSep !== false) sel = sel.join(lineSep || "\n"); + parts[i] = sel; + } + return parts; + }, + replaceSelection: function(code, collapse, origin) { + var dup = []; + for (var i = 0; i < this.sel.ranges.length; i++) + dup[i] = code; + this.replaceSelections(dup, collapse, origin || "+input"); + }, + replaceSelections: docMethodOp(function(code, collapse, origin) { + var changes = [], sel = this.sel; + for (var i = 0; i < sel.ranges.length; i++) { + var range = sel.ranges[i]; + changes[i] = {from: range.from(), to: range.to(), text: splitLines(code[i]), origin: origin}; + } + var newSel = collapse && collapse != "end" && computeReplacedSel(this, changes, collapse); + for (var i = changes.length - 1; i >= 0; i--) + makeChange(this, changes[i]); + if (newSel) setSelectionReplaceHistory(this, newSel); + else if (this.cm) ensureCursorVisible(this.cm); + }), + undo: docMethodOp(function() {makeChangeFromHistory(this, "undo");}), + redo: docMethodOp(function() {makeChangeFromHistory(this, "redo");}), + undoSelection: docMethodOp(function() {makeChangeFromHistory(this, "undo", true);}), + redoSelection: docMethodOp(function() {makeChangeFromHistory(this, "redo", true);}), + + setExtending: function(val) {this.extend = val;}, + getExtending: function() {return this.extend;}, + + historySize: function() { + var hist = this.history, done = 0, undone = 0; + for (var i = 0; i < hist.done.length; i++) if (!hist.done[i].ranges) ++done; + for (var i = 0; i < hist.undone.length; i++) if (!hist.undone[i].ranges) ++undone; + return {undo: done, redo: undone}; + }, + clearHistory: function() {this.history = new History(this.history.maxGeneration);}, + + markClean: function() { + this.cleanGeneration = this.changeGeneration(true); + }, + changeGeneration: function(forceSplit) { + if (forceSplit) + this.history.lastOp = this.history.lastSelOp = this.history.lastOrigin = null; + return this.history.generation; + }, + isClean: function (gen) { + return this.history.generation == (gen || this.cleanGeneration); + }, + + getHistory: function() { + return {done: copyHistoryArray(this.history.done), + undone: copyHistoryArray(this.history.undone)}; + }, + setHistory: function(histData) { + var hist = this.history = new History(this.history.maxGeneration); + hist.done = copyHistoryArray(histData.done.slice(0), null, true); + hist.undone = copyHistoryArray(histData.undone.slice(0), null, true); + }, + + addLineClass: docMethodOp(function(handle, where, cls) { + return changeLine(this, handle, where == "gutter" ? "gutter" : "class", function(line) { + var prop = where == "text" ? "textClass" + : where == "background" ? "bgClass" + : where == "gutter" ? "gutterClass" : "wrapClass"; + if (!line[prop]) line[prop] = cls; + else if (classTest(cls).test(line[prop])) return false; + else line[prop] += " " + cls; + return true; + }); + }), + removeLineClass: docMethodOp(function(handle, where, cls) { + return changeLine(this, handle, where == "gutter" ? "gutter" : "class", function(line) { + var prop = where == "text" ? "textClass" + : where == "background" ? "bgClass" + : where == "gutter" ? "gutterClass" : "wrapClass"; + var cur = line[prop]; + if (!cur) return false; + else if (cls == null) line[prop] = null; + else { + var found = cur.match(classTest(cls)); + if (!found) return false; + var end = found.index + found[0].length; + line[prop] = cur.slice(0, found.index) + (!found.index || end == cur.length ? "" : " ") + cur.slice(end) || null; + } + return true; + }); + }), + + addLineWidget: docMethodOp(function(handle, node, options) { + return addLineWidget(this, handle, node, options); + }), + removeLineWidget: function(widget) { widget.clear(); }, + + markText: function(from, to, options) { + return markText(this, clipPos(this, from), clipPos(this, to), options, "range"); + }, + setBookmark: function(pos, options) { + var realOpts = {replacedWith: options && (options.nodeType == null ? options.widget : options), + insertLeft: options && options.insertLeft, + clearWhenEmpty: false, shared: options && options.shared, + handleMouseEvents: options && options.handleMouseEvents}; + pos = clipPos(this, pos); + return markText(this, pos, pos, realOpts, "bookmark"); + }, + findMarksAt: function(pos) { + pos = clipPos(this, pos); + var markers = [], spans = getLine(this, pos.line).markedSpans; + if (spans) for (var i = 0; i < spans.length; ++i) { + var span = spans[i]; + if ((span.from == null || span.from <= pos.ch) && + (span.to == null || span.to >= pos.ch)) + markers.push(span.marker.parent || span.marker); + } + return markers; + }, + findMarks: function(from, to, filter) { + from = clipPos(this, from); to = clipPos(this, to); + var found = [], lineNo = from.line; + this.iter(from.line, to.line + 1, function(line) { + var spans = line.markedSpans; + if (spans) for (var i = 0; i < spans.length; i++) { + var span = spans[i]; + if (!(lineNo == from.line && from.ch > span.to || + span.from == null && lineNo != from.line|| + lineNo == to.line && span.from > to.ch) && + (!filter || filter(span.marker))) + found.push(span.marker.parent || span.marker); + } + ++lineNo; + }); + return found; + }, + getAllMarks: function() { + var markers = []; + this.iter(function(line) { + var sps = line.markedSpans; + if (sps) for (var i = 0; i < sps.length; ++i) + if (sps[i].from != null) markers.push(sps[i].marker); + }); + return markers; + }, + + posFromIndex: function(off) { + var ch, lineNo = this.first; + this.iter(function(line) { + var sz = line.text.length + 1; + if (sz > off) { ch = off; return true; } + off -= sz; + ++lineNo; + }); + return clipPos(this, Pos(lineNo, ch)); + }, + indexFromPos: function (coords) { + coords = clipPos(this, coords); + var index = coords.ch; + if (coords.line < this.first || coords.ch < 0) return 0; + this.iter(this.first, coords.line, function (line) { + index += line.text.length + 1; + }); + return index; + }, + + copy: function(copyHistory) { + var doc = new Doc(getLines(this, this.first, this.first + this.size), this.modeOption, this.first); + doc.scrollTop = this.scrollTop; doc.scrollLeft = this.scrollLeft; + doc.sel = this.sel; + doc.extend = false; + if (copyHistory) { + doc.history.undoDepth = this.history.undoDepth; + doc.setHistory(this.getHistory()); + } + return doc; + }, + + linkedDoc: function(options) { + if (!options) options = {}; + var from = this.first, to = this.first + this.size; + if (options.from != null && options.from > from) from = options.from; + if (options.to != null && options.to < to) to = options.to; + var copy = new Doc(getLines(this, from, to), options.mode || this.modeOption, from); + if (options.sharedHist) copy.history = this.history; + (this.linked || (this.linked = [])).push({doc: copy, sharedHist: options.sharedHist}); + copy.linked = [{doc: this, isParent: true, sharedHist: options.sharedHist}]; + copySharedMarkers(copy, findSharedMarkers(this)); + return copy; + }, + unlinkDoc: function(other) { + if (other instanceof CodeMirror) other = other.doc; + if (this.linked) for (var i = 0; i < this.linked.length; ++i) { + var link = this.linked[i]; + if (link.doc != other) continue; + this.linked.splice(i, 1); + other.unlinkDoc(this); + detachSharedMarkers(findSharedMarkers(this)); + break; + } + // If the histories were shared, split them again + if (other.history == this.history) { + var splitIds = [other.id]; + linkedDocs(other, function(doc) {splitIds.push(doc.id);}, true); + other.history = new History(null); + other.history.done = copyHistoryArray(this.history.done, splitIds); + other.history.undone = copyHistoryArray(this.history.undone, splitIds); + } + }, + iterLinkedDocs: function(f) {linkedDocs(this, f);}, + + getMode: function() {return this.mode;}, + getEditor: function() {return this.cm;} + }); + + // Public alias. + Doc.prototype.eachLine = Doc.prototype.iter; + + // Set up methods on CodeMirror's prototype to redirect to the editor's document. + var dontDelegate = "iter insert remove copy getEditor".split(" "); + for (var prop in Doc.prototype) if (Doc.prototype.hasOwnProperty(prop) && indexOf(dontDelegate, prop) < 0) + CodeMirror.prototype[prop] = (function(method) { + return function() {return method.apply(this.doc, arguments);}; + })(Doc.prototype[prop]); + + eventMixin(Doc); + + // Call f for all linked documents. + function linkedDocs(doc, f, sharedHistOnly) { + function propagate(doc, skip, sharedHist) { + if (doc.linked) for (var i = 0; i < doc.linked.length; ++i) { + var rel = doc.linked[i]; + if (rel.doc == skip) continue; + var shared = sharedHist && rel.sharedHist; + if (sharedHistOnly && !shared) continue; + f(rel.doc, shared); + propagate(rel.doc, doc, shared); + } + } + propagate(doc, null, true); + } + + // Attach a document to an editor. + function attachDoc(cm, doc) { + if (doc.cm) throw new Error("This document is already in use."); + cm.doc = doc; + doc.cm = cm; + estimateLineHeights(cm); + loadMode(cm); + if (!cm.options.lineWrapping) findMaxLine(cm); + cm.options.mode = doc.modeOption; + regChange(cm); + } + + // LINE UTILITIES + + // Find the line object corresponding to the given line number. + function getLine(doc, n) { + n -= doc.first; + if (n < 0 || n >= doc.size) throw new Error("There is no line " + (n + doc.first) + " in the document."); + for (var chunk = doc; !chunk.lines;) { + for (var i = 0;; ++i) { + var child = chunk.children[i], sz = child.chunkSize(); + if (n < sz) { chunk = child; break; } + n -= sz; + } + } + return chunk.lines[n]; + } + + // Get the part of a document between two positions, as an array of + // strings. + function getBetween(doc, start, end) { + var out = [], n = start.line; + doc.iter(start.line, end.line + 1, function(line) { + var text = line.text; + if (n == end.line) text = text.slice(0, end.ch); + if (n == start.line) text = text.slice(start.ch); + out.push(text); + ++n; + }); + return out; + } + // Get the lines between from and to, as array of strings. + function getLines(doc, from, to) { + var out = []; + doc.iter(from, to, function(line) { out.push(line.text); }); + return out; + } + + // Update the height of a line, propagating the height change + // upwards to parent nodes. + function updateLineHeight(line, height) { + var diff = height - line.height; + if (diff) for (var n = line; n; n = n.parent) n.height += diff; + } + + // Given a line object, find its line number by walking up through + // its parent links. + function lineNo(line) { + if (line.parent == null) return null; + var cur = line.parent, no = indexOf(cur.lines, line); + for (var chunk = cur.parent; chunk; cur = chunk, chunk = chunk.parent) { + for (var i = 0;; ++i) { + if (chunk.children[i] == cur) break; + no += chunk.children[i].chunkSize(); + } + } + return no + cur.first; + } + + // Find the line at the given vertical position, using the height + // information in the document tree. + function lineAtHeight(chunk, h) { + var n = chunk.first; + outer: do { + for (var i = 0; i < chunk.children.length; ++i) { + var child = chunk.children[i], ch = child.height; + if (h < ch) { chunk = child; continue outer; } + h -= ch; + n += child.chunkSize(); + } + return n; + } while (!chunk.lines); + for (var i = 0; i < chunk.lines.length; ++i) { + var line = chunk.lines[i], lh = line.height; + if (h < lh) break; + h -= lh; + } + return n + i; + } + + + // Find the height above the given line. + function heightAtLine(lineObj) { + lineObj = visualLine(lineObj); + + var h = 0, chunk = lineObj.parent; + for (var i = 0; i < chunk.lines.length; ++i) { + var line = chunk.lines[i]; + if (line == lineObj) break; + else h += line.height; + } + for (var p = chunk.parent; p; chunk = p, p = chunk.parent) { + for (var i = 0; i < p.children.length; ++i) { + var cur = p.children[i]; + if (cur == chunk) break; + else h += cur.height; + } + } + return h; + } + + // Get the bidi ordering for the given line (and cache it). Returns + // false for lines that are fully left-to-right, and an array of + // BidiSpan objects otherwise. + function getOrder(line) { + var order = line.order; + if (order == null) order = line.order = bidiOrdering(line.text); + return order; + } + + // HISTORY + + function History(startGen) { + // Arrays of change events and selections. Doing something adds an + // event to done and clears undo. Undoing moves events from done + // to undone, redoing moves them in the other direction. + this.done = []; this.undone = []; + this.undoDepth = Infinity; + // Used to track when changes can be merged into a single undo + // event + this.lastModTime = this.lastSelTime = 0; + this.lastOp = this.lastSelOp = null; + this.lastOrigin = this.lastSelOrigin = null; + // Used by the isClean() method + this.generation = this.maxGeneration = startGen || 1; + } + + // Create a history change event from an updateDoc-style change + // object. + function historyChangeFromChange(doc, change) { + var histChange = {from: copyPos(change.from), to: changeEnd(change), text: getBetween(doc, change.from, change.to)}; + attachLocalSpans(doc, histChange, change.from.line, change.to.line + 1); + linkedDocs(doc, function(doc) {attachLocalSpans(doc, histChange, change.from.line, change.to.line + 1);}, true); + return histChange; + } + + // Pop all selection events off the end of a history array. Stop at + // a change event. + function clearSelectionEvents(array) { + while (array.length) { + var last = lst(array); + if (last.ranges) array.pop(); + else break; + } + } + + // Find the top change event in the history. Pop off selection + // events that are in the way. + function lastChangeEvent(hist, force) { + if (force) { + clearSelectionEvents(hist.done); + return lst(hist.done); + } else if (hist.done.length && !lst(hist.done).ranges) { + return lst(hist.done); + } else if (hist.done.length > 1 && !hist.done[hist.done.length - 2].ranges) { + hist.done.pop(); + return lst(hist.done); + } + } + + // Register a change in the history. Merges changes that are within + // a single operation, ore are close together with an origin that + // allows merging (starting with "+") into a single event. + function addChangeToHistory(doc, change, selAfter, opId) { + var hist = doc.history; + hist.undone.length = 0; + var time = +new Date, cur; + + if ((hist.lastOp == opId || + hist.lastOrigin == change.origin && change.origin && + ((change.origin.charAt(0) == "+" && doc.cm && hist.lastModTime > time - doc.cm.options.historyEventDelay) || + change.origin.charAt(0) == "*")) && + (cur = lastChangeEvent(hist, hist.lastOp == opId))) { + // Merge this change into the last event + var last = lst(cur.changes); + if (cmp(change.from, change.to) == 0 && cmp(change.from, last.to) == 0) { + // Optimized case for simple insertion -- don't want to add + // new changesets for every character typed + last.to = changeEnd(change); + } else { + // Add new sub-event + cur.changes.push(historyChangeFromChange(doc, change)); + } + } else { + // Can not be merged, start a new event. + var before = lst(hist.done); + if (!before || !before.ranges) + pushSelectionToHistory(doc.sel, hist.done); + cur = {changes: [historyChangeFromChange(doc, change)], + generation: hist.generation}; + hist.done.push(cur); + while (hist.done.length > hist.undoDepth) { + hist.done.shift(); + if (!hist.done[0].ranges) hist.done.shift(); + } + } + hist.done.push(selAfter); + hist.generation = ++hist.maxGeneration; + hist.lastModTime = hist.lastSelTime = time; + hist.lastOp = hist.lastSelOp = opId; + hist.lastOrigin = hist.lastSelOrigin = change.origin; + + if (!last) signal(doc, "historyAdded"); + } + + function selectionEventCanBeMerged(doc, origin, prev, sel) { + var ch = origin.charAt(0); + return ch == "*" || + ch == "+" && + prev.ranges.length == sel.ranges.length && + prev.somethingSelected() == sel.somethingSelected() && + new Date - doc.history.lastSelTime <= (doc.cm ? doc.cm.options.historyEventDelay : 500); + } + + // Called whenever the selection changes, sets the new selection as + // the pending selection in the history, and pushes the old pending + // selection into the 'done' array when it was significantly + // different (in number of selected ranges, emptiness, or time). + function addSelectionToHistory(doc, sel, opId, options) { + var hist = doc.history, origin = options && options.origin; + + // A new event is started when the previous origin does not match + // the current, or the origins don't allow matching. Origins + // starting with * are always merged, those starting with + are + // merged when similar and close together in time. + if (opId == hist.lastSelOp || + (origin && hist.lastSelOrigin == origin && + (hist.lastModTime == hist.lastSelTime && hist.lastOrigin == origin || + selectionEventCanBeMerged(doc, origin, lst(hist.done), sel)))) + hist.done[hist.done.length - 1] = sel; + else + pushSelectionToHistory(sel, hist.done); + + hist.lastSelTime = +new Date; + hist.lastSelOrigin = origin; + hist.lastSelOp = opId; + if (options && options.clearRedo !== false) + clearSelectionEvents(hist.undone); + } + + function pushSelectionToHistory(sel, dest) { + var top = lst(dest); + if (!(top && top.ranges && top.equals(sel))) + dest.push(sel); + } + + // Used to store marked span information in the history. + function attachLocalSpans(doc, change, from, to) { + var existing = change["spans_" + doc.id], n = 0; + doc.iter(Math.max(doc.first, from), Math.min(doc.first + doc.size, to), function(line) { + if (line.markedSpans) + (existing || (existing = change["spans_" + doc.id] = {}))[n] = line.markedSpans; + ++n; + }); + } + + // When un/re-doing restores text containing marked spans, those + // that have been explicitly cleared should not be restored. + function removeClearedSpans(spans) { + if (!spans) return null; + for (var i = 0, out; i < spans.length; ++i) { + if (spans[i].marker.explicitlyCleared) { if (!out) out = spans.slice(0, i); } + else if (out) out.push(spans[i]); + } + return !out ? spans : out.length ? out : null; + } + + // Retrieve and filter the old marked spans stored in a change event. + function getOldSpans(doc, change) { + var found = change["spans_" + doc.id]; + if (!found) return null; + for (var i = 0, nw = []; i < change.text.length; ++i) + nw.push(removeClearedSpans(found[i])); + return nw; + } + + // Used both to provide a JSON-safe object in .getHistory, and, when + // detaching a document, to split the history in two + function copyHistoryArray(events, newGroup, instantiateSel) { + for (var i = 0, copy = []; i < events.length; ++i) { + var event = events[i]; + if (event.ranges) { + copy.push(instantiateSel ? Selection.prototype.deepCopy.call(event) : event); + continue; + } + var changes = event.changes, newChanges = []; + copy.push({changes: newChanges}); + for (var j = 0; j < changes.length; ++j) { + var change = changes[j], m; + newChanges.push({from: change.from, to: change.to, text: change.text}); + if (newGroup) for (var prop in change) if (m = prop.match(/^spans_(\d+)$/)) { + if (indexOf(newGroup, Number(m[1])) > -1) { + lst(newChanges)[prop] = change[prop]; + delete change[prop]; + } + } + } + } + return copy; + } + + // Rebasing/resetting history to deal with externally-sourced changes + + function rebaseHistSelSingle(pos, from, to, diff) { + if (to < pos.line) { + pos.line += diff; + } else if (from < pos.line) { + pos.line = from; + pos.ch = 0; + } + } + + // Tries to rebase an array of history events given a change in the + // document. If the change touches the same lines as the event, the + // event, and everything 'behind' it, is discarded. If the change is + // before the event, the event's positions are updated. Uses a + // copy-on-write scheme for the positions, to avoid having to + // reallocate them all on every rebase, but also avoid problems with + // shared position objects being unsafely updated. + function rebaseHistArray(array, from, to, diff) { + for (var i = 0; i < array.length; ++i) { + var sub = array[i], ok = true; + if (sub.ranges) { + if (!sub.copied) { sub = array[i] = sub.deepCopy(); sub.copied = true; } + for (var j = 0; j < sub.ranges.length; j++) { + rebaseHistSelSingle(sub.ranges[j].anchor, from, to, diff); + rebaseHistSelSingle(sub.ranges[j].head, from, to, diff); + } + continue; + } + for (var j = 0; j < sub.changes.length; ++j) { + var cur = sub.changes[j]; + if (to < cur.from.line) { + cur.from = Pos(cur.from.line + diff, cur.from.ch); + cur.to = Pos(cur.to.line + diff, cur.to.ch); + } else if (from <= cur.to.line) { + ok = false; + break; + } + } + if (!ok) { + array.splice(0, i + 1); + i = 0; + } + } + } + + function rebaseHist(hist, change) { + var from = change.from.line, to = change.to.line, diff = change.text.length - (to - from) - 1; + rebaseHistArray(hist.done, from, to, diff); + rebaseHistArray(hist.undone, from, to, diff); + } + + // EVENT UTILITIES + + // Due to the fact that we still support jurassic IE versions, some + // compatibility wrappers are needed. + + var e_preventDefault = CodeMirror.e_preventDefault = function(e) { + if (e.preventDefault) e.preventDefault(); + else e.returnValue = false; + }; + var e_stopPropagation = CodeMirror.e_stopPropagation = function(e) { + if (e.stopPropagation) e.stopPropagation(); + else e.cancelBubble = true; + }; + function e_defaultPrevented(e) { + return e.defaultPrevented != null ? e.defaultPrevented : e.returnValue == false; + } + var e_stop = CodeMirror.e_stop = function(e) {e_preventDefault(e); e_stopPropagation(e);}; + + function e_target(e) {return e.target || e.srcElement;} + function e_button(e) { + var b = e.which; + if (b == null) { + if (e.button & 1) b = 1; + else if (e.button & 2) b = 3; + else if (e.button & 4) b = 2; + } + if (mac && e.ctrlKey && b == 1) b = 3; + return b; + } + + // EVENT HANDLING + + // Lightweight event framework. on/off also work on DOM nodes, + // registering native DOM handlers. + + var on = CodeMirror.on = function(emitter, type, f) { + if (emitter.addEventListener) + emitter.addEventListener(type, f, false); + else if (emitter.attachEvent) + emitter.attachEvent("on" + type, f); + else { + var map = emitter._handlers || (emitter._handlers = {}); + var arr = map[type] || (map[type] = []); + arr.push(f); + } + }; + + var off = CodeMirror.off = function(emitter, type, f) { + if (emitter.removeEventListener) + emitter.removeEventListener(type, f, false); + else if (emitter.detachEvent) + emitter.detachEvent("on" + type, f); + else { + var arr = emitter._handlers && emitter._handlers[type]; + if (!arr) return; + for (var i = 0; i < arr.length; ++i) + if (arr[i] == f) { arr.splice(i, 1); break; } + } + }; + + var signal = CodeMirror.signal = function(emitter, type /*, values...*/) { + var arr = emitter._handlers && emitter._handlers[type]; + if (!arr) return; + var args = Array.prototype.slice.call(arguments, 2); + for (var i = 0; i < arr.length; ++i) arr[i].apply(null, args); + }; + + var orphanDelayedCallbacks = null; + + // Often, we want to signal events at a point where we are in the + // middle of some work, but don't want the handler to start calling + // other methods on the editor, which might be in an inconsistent + // state or simply not expect any other events to happen. + // signalLater looks whether there are any handlers, and schedules + // them to be executed when the last operation ends, or, if no + // operation is active, when a timeout fires. + function signalLater(emitter, type /*, values...*/) { + var arr = emitter._handlers && emitter._handlers[type]; + if (!arr) return; + var args = Array.prototype.slice.call(arguments, 2), list; + if (operationGroup) { + list = operationGroup.delayedCallbacks; + } else if (orphanDelayedCallbacks) { + list = orphanDelayedCallbacks; + } else { + list = orphanDelayedCallbacks = []; + setTimeout(fireOrphanDelayed, 0); + } + function bnd(f) {return function(){f.apply(null, args);};}; + for (var i = 0; i < arr.length; ++i) + list.push(bnd(arr[i])); + } + + function fireOrphanDelayed() { + var delayed = orphanDelayedCallbacks; + orphanDelayedCallbacks = null; + for (var i = 0; i < delayed.length; ++i) delayed[i](); + } + + // The DOM events that CodeMirror handles can be overridden by + // registering a (non-DOM) handler on the editor for the event name, + // and preventDefault-ing the event in that handler. + function signalDOMEvent(cm, e, override) { + if (typeof e == "string") + e = {type: e, preventDefault: function() { this.defaultPrevented = true; }}; + signal(cm, override || e.type, cm, e); + return e_defaultPrevented(e) || e.codemirrorIgnore; + } + + function signalCursorActivity(cm) { + var arr = cm._handlers && cm._handlers.cursorActivity; + if (!arr) return; + var set = cm.curOp.cursorActivityHandlers || (cm.curOp.cursorActivityHandlers = []); + for (var i = 0; i < arr.length; ++i) if (indexOf(set, arr[i]) == -1) + set.push(arr[i]); + } + + function hasHandler(emitter, type) { + var arr = emitter._handlers && emitter._handlers[type]; + return arr && arr.length > 0; + } + + // Add on and off methods to a constructor's prototype, to make + // registering events on such objects more convenient. + function eventMixin(ctor) { + ctor.prototype.on = function(type, f) {on(this, type, f);}; + ctor.prototype.off = function(type, f) {off(this, type, f);}; + } + + // MISC UTILITIES + + // Number of pixels added to scroller and sizer to hide scrollbar + var scrollerGap = 30; + + // Returned or thrown by various protocols to signal 'I'm not + // handling this'. + var Pass = CodeMirror.Pass = {toString: function(){return "CodeMirror.Pass";}}; + + // Reused option objects for setSelection & friends + var sel_dontScroll = {scroll: false}, sel_mouse = {origin: "*mouse"}, sel_move = {origin: "+move"}; + + function Delayed() {this.id = null;} + Delayed.prototype.set = function(ms, f) { + clearTimeout(this.id); + this.id = setTimeout(f, ms); + }; + + // Counts the column offset in a string, taking tabs into account. + // Used mostly to find indentation. + var countColumn = CodeMirror.countColumn = function(string, end, tabSize, startIndex, startValue) { + if (end == null) { + end = string.search(/[^\s\u00a0]/); + if (end == -1) end = string.length; + } + for (var i = startIndex || 0, n = startValue || 0;;) { + var nextTab = string.indexOf("\t", i); + if (nextTab < 0 || nextTab >= end) + return n + (end - i); + n += nextTab - i; + n += tabSize - (n % tabSize); + i = nextTab + 1; + } + }; + + // The inverse of countColumn -- find the offset that corresponds to + // a particular column. + function findColumn(string, goal, tabSize) { + for (var pos = 0, col = 0;;) { + var nextTab = string.indexOf("\t", pos); + if (nextTab == -1) nextTab = string.length; + var skipped = nextTab - pos; + if (nextTab == string.length || col + skipped >= goal) + return pos + Math.min(skipped, goal - col); + col += nextTab - pos; + col += tabSize - (col % tabSize); + pos = nextTab + 1; + if (col >= goal) return pos; + } + } + + var spaceStrs = [""]; + function spaceStr(n) { + while (spaceStrs.length <= n) + spaceStrs.push(lst(spaceStrs) + " "); + return spaceStrs[n]; + } + + function lst(arr) { return arr[arr.length-1]; } + + var selectInput = function(node) { node.select(); }; + if (ios) // Mobile Safari apparently has a bug where select() is broken. + selectInput = function(node) { node.selectionStart = 0; node.selectionEnd = node.value.length; }; + else if (ie) // Suppress mysterious IE10 errors + selectInput = function(node) { try { node.select(); } catch(_e) {} }; + + function indexOf(array, elt) { + for (var i = 0; i < array.length; ++i) + if (array[i] == elt) return i; + return -1; + } + function map(array, f) { + var out = []; + for (var i = 0; i < array.length; i++) out[i] = f(array[i], i); + return out; + } + + function nothing() {} + + function createObj(base, props) { + var inst; + if (Object.create) { + inst = Object.create(base); + } else { + nothing.prototype = base; + inst = new nothing(); + } + if (props) copyObj(props, inst); + return inst; + }; + + function copyObj(obj, target, overwrite) { + if (!target) target = {}; + for (var prop in obj) + if (obj.hasOwnProperty(prop) && (overwrite !== false || !target.hasOwnProperty(prop))) + target[prop] = obj[prop]; + return target; + } + + function bind(f) { + var args = Array.prototype.slice.call(arguments, 1); + return function(){return f.apply(null, args);}; + } + + var nonASCIISingleCaseWordChar = /[\u00df\u0587\u0590-\u05f4\u0600-\u06ff\u3040-\u309f\u30a0-\u30ff\u3400-\u4db5\u4e00-\u9fcc\uac00-\ud7af]/; + var isWordCharBasic = CodeMirror.isWordChar = function(ch) { + return /\w/.test(ch) || ch > "\x80" && + (ch.toUpperCase() != ch.toLowerCase() || nonASCIISingleCaseWordChar.test(ch)); + }; + function isWordChar(ch, helper) { + if (!helper) return isWordCharBasic(ch); + if (helper.source.indexOf("\\w") > -1 && isWordCharBasic(ch)) return true; + return helper.test(ch); + } + + function isEmpty(obj) { + for (var n in obj) if (obj.hasOwnProperty(n) && obj[n]) return false; + return true; + } + + // Extending unicode characters. A series of a non-extending char + + // any number of extending chars is treated as a single unit as far + // as editing and measuring is concerned. This is not fully correct, + // since some scripts/fonts/browsers also treat other configurations + // of code points as a group. + var extendingChars = /[\u0300-\u036f\u0483-\u0489\u0591-\u05bd\u05bf\u05c1\u05c2\u05c4\u05c5\u05c7\u0610-\u061a\u064b-\u065e\u0670\u06d6-\u06dc\u06de-\u06e4\u06e7\u06e8\u06ea-\u06ed\u0711\u0730-\u074a\u07a6-\u07b0\u07eb-\u07f3\u0816-\u0819\u081b-\u0823\u0825-\u0827\u0829-\u082d\u0900-\u0902\u093c\u0941-\u0948\u094d\u0951-\u0955\u0962\u0963\u0981\u09bc\u09be\u09c1-\u09c4\u09cd\u09d7\u09e2\u09e3\u0a01\u0a02\u0a3c\u0a41\u0a42\u0a47\u0a48\u0a4b-\u0a4d\u0a51\u0a70\u0a71\u0a75\u0a81\u0a82\u0abc\u0ac1-\u0ac5\u0ac7\u0ac8\u0acd\u0ae2\u0ae3\u0b01\u0b3c\u0b3e\u0b3f\u0b41-\u0b44\u0b4d\u0b56\u0b57\u0b62\u0b63\u0b82\u0bbe\u0bc0\u0bcd\u0bd7\u0c3e-\u0c40\u0c46-\u0c48\u0c4a-\u0c4d\u0c55\u0c56\u0c62\u0c63\u0cbc\u0cbf\u0cc2\u0cc6\u0ccc\u0ccd\u0cd5\u0cd6\u0ce2\u0ce3\u0d3e\u0d41-\u0d44\u0d4d\u0d57\u0d62\u0d63\u0dca\u0dcf\u0dd2-\u0dd4\u0dd6\u0ddf\u0e31\u0e34-\u0e3a\u0e47-\u0e4e\u0eb1\u0eb4-\u0eb9\u0ebb\u0ebc\u0ec8-\u0ecd\u0f18\u0f19\u0f35\u0f37\u0f39\u0f71-\u0f7e\u0f80-\u0f84\u0f86\u0f87\u0f90-\u0f97\u0f99-\u0fbc\u0fc6\u102d-\u1030\u1032-\u1037\u1039\u103a\u103d\u103e\u1058\u1059\u105e-\u1060\u1071-\u1074\u1082\u1085\u1086\u108d\u109d\u135f\u1712-\u1714\u1732-\u1734\u1752\u1753\u1772\u1773\u17b7-\u17bd\u17c6\u17c9-\u17d3\u17dd\u180b-\u180d\u18a9\u1920-\u1922\u1927\u1928\u1932\u1939-\u193b\u1a17\u1a18\u1a56\u1a58-\u1a5e\u1a60\u1a62\u1a65-\u1a6c\u1a73-\u1a7c\u1a7f\u1b00-\u1b03\u1b34\u1b36-\u1b3a\u1b3c\u1b42\u1b6b-\u1b73\u1b80\u1b81\u1ba2-\u1ba5\u1ba8\u1ba9\u1c2c-\u1c33\u1c36\u1c37\u1cd0-\u1cd2\u1cd4-\u1ce0\u1ce2-\u1ce8\u1ced\u1dc0-\u1de6\u1dfd-\u1dff\u200c\u200d\u20d0-\u20f0\u2cef-\u2cf1\u2de0-\u2dff\u302a-\u302f\u3099\u309a\ua66f-\ua672\ua67c\ua67d\ua6f0\ua6f1\ua802\ua806\ua80b\ua825\ua826\ua8c4\ua8e0-\ua8f1\ua926-\ua92d\ua947-\ua951\ua980-\ua982\ua9b3\ua9b6-\ua9b9\ua9bc\uaa29-\uaa2e\uaa31\uaa32\uaa35\uaa36\uaa43\uaa4c\uaab0\uaab2-\uaab4\uaab7\uaab8\uaabe\uaabf\uaac1\uabe5\uabe8\uabed\udc00-\udfff\ufb1e\ufe00-\ufe0f\ufe20-\ufe26\uff9e\uff9f]/; + function isExtendingChar(ch) { return ch.charCodeAt(0) >= 768 && extendingChars.test(ch); } + + // DOM UTILITIES + + function elt(tag, content, className, style) { + var e = document.createElement(tag); + if (className) e.className = className; + if (style) e.style.cssText = style; + if (typeof content == "string") e.appendChild(document.createTextNode(content)); + else if (content) for (var i = 0; i < content.length; ++i) e.appendChild(content[i]); + return e; + } + + var range; + if (document.createRange) range = function(node, start, end, endNode) { + var r = document.createRange(); + r.setEnd(endNode || node, end); + r.setStart(node, start); + return r; + }; + else range = function(node, start, end) { + var r = document.body.createTextRange(); + try { r.moveToElementText(node.parentNode); } + catch(e) { return r; } + r.collapse(true); + r.moveEnd("character", end); + r.moveStart("character", start); + return r; + }; + + function removeChildren(e) { + for (var count = e.childNodes.length; count > 0; --count) + e.removeChild(e.firstChild); + return e; + } + + function removeChildrenAndAdd(parent, e) { + return removeChildren(parent).appendChild(e); + } + + var contains = CodeMirror.contains = function(parent, child) { + if (child.nodeType == 3) // Android browser always returns false when child is a textnode + child = child.parentNode; + if (parent.contains) + return parent.contains(child); + do { + if (child.nodeType == 11) child = child.host; + if (child == parent) return true; + } while (child = child.parentNode); + }; + + function activeElt() { return document.activeElement; } + // Older versions of IE throws unspecified error when touching + // document.activeElement in some cases (during loading, in iframe) + if (ie && ie_version < 11) activeElt = function() { + try { return document.activeElement; } + catch(e) { return document.body; } + }; + + function classTest(cls) { return new RegExp("(^|\\s)" + cls + "(?:$|\\s)\\s*"); } + var rmClass = CodeMirror.rmClass = function(node, cls) { + var current = node.className; + var match = classTest(cls).exec(current); + if (match) { + var after = current.slice(match.index + match[0].length); + node.className = current.slice(0, match.index) + (after ? match[1] + after : ""); + } + }; + var addClass = CodeMirror.addClass = function(node, cls) { + var current = node.className; + if (!classTest(cls).test(current)) node.className += (current ? " " : "") + cls; + }; + function joinClasses(a, b) { + var as = a.split(" "); + for (var i = 0; i < as.length; i++) + if (as[i] && !classTest(as[i]).test(b)) b += " " + as[i]; + return b; + } + + // WINDOW-WIDE EVENTS + + // These must be handled carefully, because naively registering a + // handler for each editor will cause the editors to never be + // garbage collected. + + function forEachCodeMirror(f) { + if (!document.body.getElementsByClassName) return; + var byClass = document.body.getElementsByClassName("CodeMirror"); + for (var i = 0; i < byClass.length; i++) { + var cm = byClass[i].CodeMirror; + if (cm) f(cm); + } + } + + var globalsRegistered = false; + function ensureGlobalHandlers() { + if (globalsRegistered) return; + registerGlobalHandlers(); + globalsRegistered = true; + } + function registerGlobalHandlers() { + // When the window resizes, we need to refresh active editors. + var resizeTimer; + on(window, "resize", function() { + if (resizeTimer == null) resizeTimer = setTimeout(function() { + resizeTimer = null; + forEachCodeMirror(onResize); + }, 100); + }); + // When the window loses focus, we want to show the editor as blurred + on(window, "blur", function() { + forEachCodeMirror(onBlur); + }); + } + + // FEATURE DETECTION + + // Detect drag-and-drop + var dragAndDrop = function() { + // There is *some* kind of drag-and-drop support in IE6-8, but I + // couldn't get it to work yet. + if (ie && ie_version < 9) return false; + var div = elt('div'); + return "draggable" in div || "dragDrop" in div; + }(); + + var zwspSupported; + function zeroWidthElement(measure) { + if (zwspSupported == null) { + var test = elt("span", "\u200b"); + removeChildrenAndAdd(measure, elt("span", [test, document.createTextNode("x")])); + if (measure.firstChild.offsetHeight != 0) + zwspSupported = test.offsetWidth <= 1 && test.offsetHeight > 2 && !(ie && ie_version < 8); + } + var node = zwspSupported ? elt("span", "\u200b") : + elt("span", "\u00a0", null, "display: inline-block; width: 1px; margin-right: -1px"); + node.setAttribute("cm-text", ""); + return node; + } + + // Feature-detect IE's crummy client rect reporting for bidi text + var badBidiRects; + function hasBadBidiRects(measure) { + if (badBidiRects != null) return badBidiRects; + var txt = removeChildrenAndAdd(measure, document.createTextNode("A\u062eA")); + var r0 = range(txt, 0, 1).getBoundingClientRect(); + if (!r0 || r0.left == r0.right) return false; // Safari returns null in some cases (#2780) + var r1 = range(txt, 1, 2).getBoundingClientRect(); + return badBidiRects = (r1.right - r0.right < 3); + } + + // See if "".split is the broken IE version, if so, provide an + // alternative way to split lines. + var splitLines = CodeMirror.splitLines = "\n\nb".split(/\n/).length != 3 ? function(string) { + var pos = 0, result = [], l = string.length; + while (pos <= l) { + var nl = string.indexOf("\n", pos); + if (nl == -1) nl = string.length; + var line = string.slice(pos, string.charAt(nl - 1) == "\r" ? nl - 1 : nl); + var rt = line.indexOf("\r"); + if (rt != -1) { + result.push(line.slice(0, rt)); + pos += rt + 1; + } else { + result.push(line); + pos = nl + 1; + } + } + return result; + } : function(string){return string.split(/\r\n?|\n/);}; + + var hasSelection = window.getSelection ? function(te) { + try { return te.selectionStart != te.selectionEnd; } + catch(e) { return false; } + } : function(te) { + try {var range = te.ownerDocument.selection.createRange();} + catch(e) {} + if (!range || range.parentElement() != te) return false; + return range.compareEndPoints("StartToEnd", range) != 0; + }; + + var hasCopyEvent = (function() { + var e = elt("div"); + if ("oncopy" in e) return true; + e.setAttribute("oncopy", "return;"); + return typeof e.oncopy == "function"; + })(); + + var badZoomedRects = null; + function hasBadZoomedRects(measure) { + if (badZoomedRects != null) return badZoomedRects; + var node = removeChildrenAndAdd(measure, elt("span", "x")); + var normal = node.getBoundingClientRect(); + var fromRange = range(node, 0, 1).getBoundingClientRect(); + return badZoomedRects = Math.abs(normal.left - fromRange.left) > 1; + } + + // KEY NAMES + + var keyNames = {3: "Enter", 8: "Backspace", 9: "Tab", 13: "Enter", 16: "Shift", 17: "Ctrl", 18: "Alt", + 19: "Pause", 20: "CapsLock", 27: "Esc", 32: "Space", 33: "PageUp", 34: "PageDown", 35: "End", + 36: "Home", 37: "Left", 38: "Up", 39: "Right", 40: "Down", 44: "PrintScrn", 45: "Insert", + 46: "Delete", 59: ";", 61: "=", 91: "Mod", 92: "Mod", 93: "Mod", 107: "=", 109: "-", 127: "Delete", + 173: "-", 186: ";", 187: "=", 188: ",", 189: "-", 190: ".", 191: "/", 192: "`", 219: "[", 220: "\\", + 221: "]", 222: "'", 63232: "Up", 63233: "Down", 63234: "Left", 63235: "Right", 63272: "Delete", + 63273: "Home", 63275: "End", 63276: "PageUp", 63277: "PageDown", 63302: "Insert"}; + CodeMirror.keyNames = keyNames; + (function() { + // Number keys + for (var i = 0; i < 10; i++) keyNames[i + 48] = keyNames[i + 96] = String(i); + // Alphabetic keys + for (var i = 65; i <= 90; i++) keyNames[i] = String.fromCharCode(i); + // Function keys + for (var i = 1; i <= 12; i++) keyNames[i + 111] = keyNames[i + 63235] = "F" + i; + })(); + + // BIDI HELPERS + + function iterateBidiSections(order, from, to, f) { + if (!order) return f(from, to, "ltr"); + var found = false; + for (var i = 0; i < order.length; ++i) { + var part = order[i]; + if (part.from < to && part.to > from || from == to && part.to == from) { + f(Math.max(part.from, from), Math.min(part.to, to), part.level == 1 ? "rtl" : "ltr"); + found = true; + } + } + if (!found) f(from, to, "ltr"); + } + + function bidiLeft(part) { return part.level % 2 ? part.to : part.from; } + function bidiRight(part) { return part.level % 2 ? part.from : part.to; } + + function lineLeft(line) { var order = getOrder(line); return order ? bidiLeft(order[0]) : 0; } + function lineRight(line) { + var order = getOrder(line); + if (!order) return line.text.length; + return bidiRight(lst(order)); + } + + function lineStart(cm, lineN) { + var line = getLine(cm.doc, lineN); + var visual = visualLine(line); + if (visual != line) lineN = lineNo(visual); + var order = getOrder(visual); + var ch = !order ? 0 : order[0].level % 2 ? lineRight(visual) : lineLeft(visual); + return Pos(lineN, ch); + } + function lineEnd(cm, lineN) { + var merged, line = getLine(cm.doc, lineN); + while (merged = collapsedSpanAtEnd(line)) { + line = merged.find(1, true).line; + lineN = null; + } + var order = getOrder(line); + var ch = !order ? line.text.length : order[0].level % 2 ? lineLeft(line) : lineRight(line); + return Pos(lineN == null ? lineNo(line) : lineN, ch); + } + function lineStartSmart(cm, pos) { + var start = lineStart(cm, pos.line); + var line = getLine(cm.doc, start.line); + var order = getOrder(line); + if (!order || order[0].level == 0) { + var firstNonWS = Math.max(0, line.text.search(/\S/)); + var inWS = pos.line == start.line && pos.ch <= firstNonWS && pos.ch; + return Pos(start.line, inWS ? 0 : firstNonWS); + } + return start; + } + + function compareBidiLevel(order, a, b) { + var linedir = order[0].level; + if (a == linedir) return true; + if (b == linedir) return false; + return a < b; + } + var bidiOther; + function getBidiPartAt(order, pos) { + bidiOther = null; + for (var i = 0, found; i < order.length; ++i) { + var cur = order[i]; + if (cur.from < pos && cur.to > pos) return i; + if ((cur.from == pos || cur.to == pos)) { + if (found == null) { + found = i; + } else if (compareBidiLevel(order, cur.level, order[found].level)) { + if (cur.from != cur.to) bidiOther = found; + return i; + } else { + if (cur.from != cur.to) bidiOther = i; + return found; + } + } + } + return found; + } + + function moveInLine(line, pos, dir, byUnit) { + if (!byUnit) return pos + dir; + do pos += dir; + while (pos > 0 && isExtendingChar(line.text.charAt(pos))); + return pos; + } + + // This is needed in order to move 'visually' through bi-directional + // text -- i.e., pressing left should make the cursor go left, even + // when in RTL text. The tricky part is the 'jumps', where RTL and + // LTR text touch each other. This often requires the cursor offset + // to move more than one unit, in order to visually move one unit. + function moveVisually(line, start, dir, byUnit) { + var bidi = getOrder(line); + if (!bidi) return moveLogically(line, start, dir, byUnit); + var pos = getBidiPartAt(bidi, start), part = bidi[pos]; + var target = moveInLine(line, start, part.level % 2 ? -dir : dir, byUnit); + + for (;;) { + if (target > part.from && target < part.to) return target; + if (target == part.from || target == part.to) { + if (getBidiPartAt(bidi, target) == pos) return target; + part = bidi[pos += dir]; + return (dir > 0) == part.level % 2 ? part.to : part.from; + } else { + part = bidi[pos += dir]; + if (!part) return null; + if ((dir > 0) == part.level % 2) + target = moveInLine(line, part.to, -1, byUnit); + else + target = moveInLine(line, part.from, 1, byUnit); + } + } + } + + function moveLogically(line, start, dir, byUnit) { + var target = start + dir; + if (byUnit) while (target > 0 && isExtendingChar(line.text.charAt(target))) target += dir; + return target < 0 || target > line.text.length ? null : target; + } + + // Bidirectional ordering algorithm + // See http://unicode.org/reports/tr9/tr9-13.html for the algorithm + // that this (partially) implements. + + // One-char codes used for character types: + // L (L): Left-to-Right + // R (R): Right-to-Left + // r (AL): Right-to-Left Arabic + // 1 (EN): European Number + // + (ES): European Number Separator + // % (ET): European Number Terminator + // n (AN): Arabic Number + // , (CS): Common Number Separator + // m (NSM): Non-Spacing Mark + // b (BN): Boundary Neutral + // s (B): Paragraph Separator + // t (S): Segment Separator + // w (WS): Whitespace + // N (ON): Other Neutrals + + // Returns null if characters are ordered as they appear + // (left-to-right), or an array of sections ({from, to, level} + // objects) in the order in which they occur visually. + var bidiOrdering = (function() { + // Character types for codepoints 0 to 0xff + var lowTypes = "bbbbbbbbbtstwsbbbbbbbbbbbbbbssstwNN%%%NNNNNN,N,N1111111111NNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNbbbbbbsbbbbbbbbbbbbbbbbbbbbbbbbbb,N%%%%NNNNLNNNNN%%11NLNNN1LNNNNNLLLLLLLLLLLLLLLLLLLLLLLNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLN"; + // Character types for codepoints 0x600 to 0x6ff + var arabicTypes = "rrrrrrrrrrrr,rNNmmmmmmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmmmmmmmmrrrrrrrnnnnnnnnnn%nnrrrmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmmmmmmmmmmmmmNmmmm"; + function charType(code) { + if (code <= 0xf7) return lowTypes.charAt(code); + else if (0x590 <= code && code <= 0x5f4) return "R"; + else if (0x600 <= code && code <= 0x6ed) return arabicTypes.charAt(code - 0x600); + else if (0x6ee <= code && code <= 0x8ac) return "r"; + else if (0x2000 <= code && code <= 0x200b) return "w"; + else if (code == 0x200c) return "b"; + else return "L"; + } + + var bidiRE = /[\u0590-\u05f4\u0600-\u06ff\u0700-\u08ac]/; + var isNeutral = /[stwN]/, isStrong = /[LRr]/, countsAsLeft = /[Lb1n]/, countsAsNum = /[1n]/; + // Browsers seem to always treat the boundaries of block elements as being L. + var outerType = "L"; + + function BidiSpan(level, from, to) { + this.level = level; + this.from = from; this.to = to; + } + + return function(str) { + if (!bidiRE.test(str)) return false; + var len = str.length, types = []; + for (var i = 0, type; i < len; ++i) + types.push(type = charType(str.charCodeAt(i))); + + // W1. Examine each non-spacing mark (NSM) in the level run, and + // change the type of the NSM to the type of the previous + // character. If the NSM is at the start of the level run, it will + // get the type of sor. + for (var i = 0, prev = outerType; i < len; ++i) { + var type = types[i]; + if (type == "m") types[i] = prev; + else prev = type; + } + + // W2. Search backwards from each instance of a European number + // until the first strong type (R, L, AL, or sor) is found. If an + // AL is found, change the type of the European number to Arabic + // number. + // W3. Change all ALs to R. + for (var i = 0, cur = outerType; i < len; ++i) { + var type = types[i]; + if (type == "1" && cur == "r") types[i] = "n"; + else if (isStrong.test(type)) { cur = type; if (type == "r") types[i] = "R"; } + } + + // W4. A single European separator between two European numbers + // changes to a European number. A single common separator between + // two numbers of the same type changes to that type. + for (var i = 1, prev = types[0]; i < len - 1; ++i) { + var type = types[i]; + if (type == "+" && prev == "1" && types[i+1] == "1") types[i] = "1"; + else if (type == "," && prev == types[i+1] && + (prev == "1" || prev == "n")) types[i] = prev; + prev = type; + } + + // W5. A sequence of European terminators adjacent to European + // numbers changes to all European numbers. + // W6. Otherwise, separators and terminators change to Other + // Neutral. + for (var i = 0; i < len; ++i) { + var type = types[i]; + if (type == ",") types[i] = "N"; + else if (type == "%") { + for (var end = i + 1; end < len && types[end] == "%"; ++end) {} + var replace = (i && types[i-1] == "!") || (end < len && types[end] == "1") ? "1" : "N"; + for (var j = i; j < end; ++j) types[j] = replace; + i = end - 1; + } + } + + // W7. Search backwards from each instance of a European number + // until the first strong type (R, L, or sor) is found. If an L is + // found, then change the type of the European number to L. + for (var i = 0, cur = outerType; i < len; ++i) { + var type = types[i]; + if (cur == "L" && type == "1") types[i] = "L"; + else if (isStrong.test(type)) cur = type; + } + + // N1. A sequence of neutrals takes the direction of the + // surrounding strong text if the text on both sides has the same + // direction. European and Arabic numbers act as if they were R in + // terms of their influence on neutrals. Start-of-level-run (sor) + // and end-of-level-run (eor) are used at level run boundaries. + // N2. Any remaining neutrals take the embedding direction. + for (var i = 0; i < len; ++i) { + if (isNeutral.test(types[i])) { + for (var end = i + 1; end < len && isNeutral.test(types[end]); ++end) {} + var before = (i ? types[i-1] : outerType) == "L"; + var after = (end < len ? types[end] : outerType) == "L"; + var replace = before || after ? "L" : "R"; + for (var j = i; j < end; ++j) types[j] = replace; + i = end - 1; + } + } + + // Here we depart from the documented algorithm, in order to avoid + // building up an actual levels array. Since there are only three + // levels (0, 1, 2) in an implementation that doesn't take + // explicit embedding into account, we can build up the order on + // the fly, without following the level-based algorithm. + var order = [], m; + for (var i = 0; i < len;) { + if (countsAsLeft.test(types[i])) { + var start = i; + for (++i; i < len && countsAsLeft.test(types[i]); ++i) {} + order.push(new BidiSpan(0, start, i)); + } else { + var pos = i, at = order.length; + for (++i; i < len && types[i] != "L"; ++i) {} + for (var j = pos; j < i;) { + if (countsAsNum.test(types[j])) { + if (pos < j) order.splice(at, 0, new BidiSpan(1, pos, j)); + var nstart = j; + for (++j; j < i && countsAsNum.test(types[j]); ++j) {} + order.splice(at, 0, new BidiSpan(2, nstart, j)); + pos = j; + } else ++j; + } + if (pos < i) order.splice(at, 0, new BidiSpan(1, pos, i)); + } + } + if (order[0].level == 1 && (m = str.match(/^\s+/))) { + order[0].from = m[0].length; + order.unshift(new BidiSpan(0, 0, m[0].length)); + } + if (lst(order).level == 1 && (m = str.match(/\s+$/))) { + lst(order).to -= m[0].length; + order.push(new BidiSpan(0, len - m[0].length, len)); + } + if (order[0].level != lst(order).level) + order.push(new BidiSpan(order[0].level, len, len)); + + return order; + }; + })(); + + // THE END + + CodeMirror.version = "5.1.1"; + + return CodeMirror; +}); \ No newline at end of file diff --git a/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.policy.edit/public/js/policy-edit.js b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.policy.edit/public/js/policy-edit.js new file mode 100644 index 0000000000..d42dd0109a --- /dev/null +++ b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.policy.edit/public/js/policy-edit.js @@ -0,0 +1,729 @@ +/* + * Copyright (c) 2016, 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. + */ + +var validateStep = {}; +var skipStep = {}; +var stepForwardFrom = {}; +var stepBackFrom = {}; +var policy = {}; +var configuredOperations = []; + +/** + * Method to update the visibility (i.e. disabled or enabled view) + * of grouped input according to the values + * that they currently possess. + * @param domElement HTML grouped-input element with class name "grouped-input" + */ +var updateGroupedInputVisibility = function (domElement) { + if ($(".parent-input:first", domElement).is(":checked")) { + if ($(".grouped-child-input:first", domElement).hasClass("disabled")) { + $(".grouped-child-input:first", domElement).removeClass("disabled"); + } + $(".child-input", domElement).each(function () { + $(this).prop('disabled', false); + }); + } else { + if (!$(".grouped-child-input:first", domElement).hasClass("disabled")) { + $(".grouped-child-input:first", domElement).addClass("disabled"); + } + $(".child-input", domElement).each(function () { + $(this).prop('disabled', true); + }); + } +}; + +skipStep["policy-platform"] = function (policyPayloadObj) { + policy["name"] = policyPayloadObj["policyName"]; + policy["platform"] = policyPayloadObj["profile"]["deviceType"]["name"]; + policy["platformId"] = policyPayloadObj["profile"]["deviceType"]["id"]; + var userRoleInput = $("#user-roles-input"); + var ownershipInput = $("#ownership-input"); + var userInput = $("#users-input"); + var actionInput = $("#action-input"); + var policyNameInput = $("#policy-name-input"); + var policyDescriptionInput = $("#policy-description-input"); + userRoleInput.val(policyPayloadObj.roles).trigger("change"); + userInput.val(policyPayloadObj.users).trigger("change"); + ownershipInput.val(policyPayloadObj.ownershipType); + actionInput.val(policyPayloadObj.compliance); + policyNameInput.val(policyPayloadObj["policyName"]); + policyDescriptionInput.val(policyPayloadObj["description"]); + // updating next-page wizard title with selected platform + $("#policy-profile-page-wizard-title").text("EDIT " + policy["platform"] + " POLICY - " + policy["name"]); + + var profileFeaturesList = policyPayloadObj["profile"]["profileFeaturesList"]; + if (profileFeaturesList.length > 0){ + var content = profileFeaturesList[0]["content"]; + var policyDefinitionObj = JSON.parse(content); + window.queryEditor.setValue(policyDefinitionObj["policyDefinition"]); + } +}; + +/** + * Checks if provided number is valid against a range. + * + * @param numberInput Number Input + * @param min Minimum Limit + * @param max Maximum Limit + * @returns {boolean} Returns true if input is within the specified range + */ +var inputIsValidAgainstRange = function (numberInput, min, max) { + return (numberInput == min || (numberInput > min && numberInput < max) || numberInput == max); +}; + +/** + * Checks if provided input is valid against RegEx input. + * + * @param regExp Regular expression + * @param input Input string to check + * @returns {boolean} Returns true if input matches RegEx + */ +var inputIsValidAgainstRegExp = function (regExp, input) { + return regExp.test(input); +}; + +validateStep["policy-profile"] = function () { + return true; +} + +stepForwardFrom["policy-profile"] = function () { + policy["profile"] = operationModule.generateProfile(policy["platform"], configuredOperations); + // updating next-page wizard title with selected platform + $("#policy-criteria-page-wizard-title").text("EDIT " + policy["platform"] + " POLICY - " + policy["name"]); +}; + +stepForwardFrom["policy-criteria"] = function () { + $("input[type='radio'].select-users-radio").each(function () { + if ($(this).is(':radio')) { + if ($(this).is(":checked")) { + if ($(this).attr("id") == "users-radio-btn") { + policy["selectedUsers"] = $("#users-input").val(); + } else if ($(this).attr("id") == "user-roles-radio-btn") { + policy["selectedUserRoles"] = $("#user-roles-input").val(); + } + } + } + }); + policy["selectedNonCompliantAction"] = $("#action-input").find(":selected").data("action"); + policy["selectedOwnership"] = $("#ownership-input").val(); + // updating next-page wizard title with selected platform + $("#policy-naming-page-wizard-title").text("EDIT " + policy["platform"] + " POLICY - " + policy["name"]); +}; + +/** + * Checks if provided input is valid against provided length range. + * + * @param input Alphanumeric or non-alphanumeric input + * @param minLength Minimum Required Length + * @param maxLength Maximum Required Length + * @returns {boolean} Returns true if input matches the provided minimum length and maximum length + */ +var inputIsValidAgainstLength = function (input, minLength, maxLength) { + var length = input.length; + return (length == minLength || (length > minLength && length < maxLength) || length == maxLength); +}; + +validateStep["policy-naming"] = function () { + var validationStatus = {}; + + // taking values of inputs to be validated + var policyName = $("input#policy-name-input").val(); + // starting validation process and updating validationStatus + if (!policyName) { + validationStatus["error"] = true; + validationStatus["mainErrorMsg"] = "Policy name is empty. You cannot proceed."; + } else if (!inputIsValidAgainstLength(policyName, 1, 30)) { + validationStatus["error"] = true; + validationStatus["mainErrorMsg"] = + "Policy name exceeds maximum allowed length. Please check."; + } else { + validationStatus["error"] = false; + } + // ending validation process + + // start taking specific actions upon validation + var wizardIsToBeContinued; + if (validationStatus["error"]) { + wizardIsToBeContinued = false; + var mainErrorMsgWrapper = "#policy-naming-main-error-msg"; + var mainErrorMsg = mainErrorMsgWrapper + " span"; + $(mainErrorMsg).text(validationStatus["mainErrorMsg"]); + $(mainErrorMsgWrapper).removeClass("hidden"); + } else { + wizardIsToBeContinued = true; + } + + return wizardIsToBeContinued; +}; + +validateStep["policy-naming-publish"] = function () { + var validationStatus = {}; + + // taking values of inputs to be validated + var policyName = $("input#policy-name-input").val(); + // starting validation process and updating validationStatus + if (!policyName) { + validationStatus["error"] = true; + validationStatus["mainErrorMsg"] = "Policy name is empty. You cannot proceed."; + } else if (!inputIsValidAgainstLength(policyName, 1, 30)) { + validationStatus["error"] = true; + validationStatus["mainErrorMsg"] = + "Policy name exceeds maximum allowed length. Please check."; + } else { + validationStatus["error"] = false; + } + // ending validation process + + // start taking specific actions upon validation + var wizardIsToBeContinued; + if (validationStatus["error"]) { + wizardIsToBeContinued = false; + var mainErrorMsgWrapper = "#policy-naming-main-error-msg"; + var mainErrorMsg = mainErrorMsgWrapper + " span"; + $(mainErrorMsg).text(validationStatus["mainErrorMsg"]); + $(mainErrorMsgWrapper).removeClass("hidden"); + } else { + wizardIsToBeContinued = true; + } + + return wizardIsToBeContinued; +}; + +stepForwardFrom["policy-naming-publish"] = function () { + policy["policyName"] = $("#policy-name-input").val(); + policy["description"] = $("#policy-description-input").val(); + //All data is collected. Policy can now be updated. + updatePolicy(policy, "publish"); +}; +stepForwardFrom["policy-naming"] = function () { + policy["policyName"] = $("#policy-name-input").val(); + policy["description"] = $("#policy-description-input").val(); + //All data is collected. Policy can now be updated. + updatePolicy(policy, "save"); +}; + +var updatePolicy = function (policy, state) { + var profilePayloads = [{ + "featureCode": "CONFIG", + "deviceTypeId": policy["platformId"], + "content": {"policyDefinition": window.queryEditor.getValue()} + }]; + + var payload = { + "policyName": policy["policyName"], + "description": policy["description"], + "compliance": policy["selectedNonCompliantAction"], + "ownershipType": "ANY", + "profile": { + "profileName": policy["policyName"], + "deviceType": { + "id": policy["platformId"], + "name": policy["platform"] + }, + "profileFeaturesList": profilePayloads + } + }; + + if (policy["selectedUsers"]) { + payload["users"] = policy["selectedUsers"]; + } else if (policy["selectedUserRoles"]) { + payload["roles"] = policy["selectedUserRoles"]; + } else { + payload["users"] = []; + payload["roles"] = []; + } + + var serviceURL = "/devicemgt_admin/policies/" + getParameterByName("id"); + invokerUtil.put( + serviceURL, + payload, + // on success + function () { + if (state == "save"){ + var policyList = []; + policyList.push(getParameterByName("id")); + serviceURL = "/devicemgt_admin/policies/inactivate"; + invokerUtil.put( + serviceURL, + policyList, + // on success + function () { + $(".add-policy").addClass("hidden"); + $(".policy-message").removeClass("hidden"); + }, + // on error + function () { + + } + ); + }else if(state == "publish"){ + var policyList = []; + policyList.push(getParameterByName("id")); + serviceURL = "/devicemgt_admin/policies/activate"; + invokerUtil.put( + serviceURL, + policyList, + // on success + function () { + $(".add-policy").addClass("hidden"); + $(".policy-naming").addClass("hidden"); + $(".policy-message").removeClass("hidden"); + publishToDevice(); + }, + // on error + function () { + + } + ); + } + }, + // on error + function () { + + } + ); +}; + + +function publishToDevice() { + var payload = { + "policyName": policy["policyName"], + "description": policy["description"], + "compliance": policy["selectedNonCompliantAction"], + "ownershipType": "ANY", + "profile": { + "profileName": policy["policyName"], + "deviceType": { + "id": policy["platformId"], + "name": policy["platform"] + }, + "policyDefinition": window.queryEditor.getValue(), + "policyDescription": policy["description"] + } + }; + + var successCallback = function (data, status) { + console.log("Data: " + data + "\nStatus: " + status); + }; + + var data = { + url: "/devicemgt/api/policies/add", + type: "POST", + contentType: "application/json", + accept: "application/json", + success: successCallback, + data: JSON.stringify(payload) + }; + + $.ajax(data).fail(function (jqXHR) { + console.log("Error: " + jqXHR); + }); + +} + +// Start of HTML embedded invoke methods +var showAdvanceOperation = function (operation, button) { + $(button).addClass('selected'); + $(button).siblings().removeClass('selected'); + var hiddenOperation = ".wr-hidden-operations-content > div"; + $(hiddenOperation + '[data-operation="' + operation + '"]').show(); + $(hiddenOperation + '[data-operation="' + operation + '"]').siblings().hide(); +}; + +// Start of functions related to grid-input-view + +/** + * Method to set count id to cloned elements. + * @param {object} addFormContainer + */ +var setId = function (addFormContainer) { + $(addFormContainer).find("[data-add-form-clone]").each(function (i) { + $(this).attr("id", $(this).attr("data-add-form-clone").slice(1) + "-" + (i + 1)); + if ($(this).find(".index").length > 0) { + $(this).find(".index").html(i + 1); + } + }); +}; + +/** + * Method to set count id to cloned elements. + * @param {object} addFormContainer + */ +var showHideHelpText = function (addFormContainer) { + var helpText = "[data-help-text=add-form]"; + if ($(addFormContainer).find("[data-add-form-clone]").length > 0) { + $(addFormContainer).find(helpText).hide(); + } else { + $(addFormContainer).find(helpText).show(); + } +}; + +// End of functions related to grid-input-view + +/** + * This method will return query parameter value given its name. + * @param name Query parameter name + * @returns {string} Query parameter value + */ +var getParameterByName = function (name) { + name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]"); + var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"), + results = regex.exec(location.search); + return results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " ")); +}; + +function formatRepo (user) { + if (user.loading) { + return user.text + } + if (!user.username){ + return; + } + var markup = '
      ' + + '
      ' + + '
      ' + + '
      ' + user.username + '
      '; + if (user.firstname) { + markup += '
      ' + user.firstname + '
      '; + } + if (user.emailAddress) { + markup += '
      ' + user.emailAddress + '
      '; + } + markup += '
      '; + return markup; +} + +function formatRepoSelection (user) { + return user.username || user.text;; +} + +$(document).ready(function () { + + window.queryEditor = CodeMirror.fromTextArea(document.getElementById('policy-definition-input'), { + mode: MIME_TYPE_SIDDHI_QL, + indentWithTabs: true, + smartIndent: true, + lineNumbers: true, + matchBrackets: true, + autofocus: true, + extraKeys: { + "Shift-2": function (cm) { + insertStr(cm, cm.getCursor(), '@'); + CodeMirror.showHint(cm, getAnnotationHints); + }, + "Ctrl-Space": "autocomplete" + } + }); + + // Adding initial state of wizard-steps. + $("#policy-profile-wizard-steps").html($(".wr-steps").html()); + + $("select.select2[multiple=multiple]").select2({ + "tags": true + }); + + $("#users-input").select2({ + multiple:true, + tags: true, + ajax: { + url: window.location.origin + "/devicemgt/api/invoker/execute/", + method: "POST", + dataType: 'json', + delay: 250, + id: function (user) { + return user.username; + }, + data: function (params) { + var postData = {}; + postData.actionMethod = "GET"; + postData.actionUrl = "/devicemgt_admin/users?q=ad"; + postData.actionPayload = JSON.stringify({ + q: params.term, // search term + page: params.page + }); + + return JSON.stringify(postData); + }, + processResults: function (data, page) { + var newData = []; + $.each(data.responseContent, function (index, value) { + value.id = value.username; + newData.push(value); + }); + return { + results: newData + }; + }, + cache: true + }, + escapeMarkup: function (markup) { return markup; }, // let our custom formatter work + minimumInputLength: 1, + templateResult: formatRepo, // omitted for brevity, see the source of this page + templateSelection: formatRepoSelection // omitted for brevity, see the source of this page + }); + + var policyPayloadObj; + invokerUtil.get( + "/devicemgt_admin/policies/" + getParameterByName("id"), + // on success + function (data) { + data = JSON.parse(data); + policyPayloadObj = data["responseContent"]; + skipStep["policy-platform"](policyPayloadObj); + }, + // on error + function () { + // should be redirected to an error page + } + ); + + $("#users-select-field").hide(); + $("#user-roles-select-field").show(); + + $("input[type='radio'].select-users-radio").change(function () { + if ($("#users-radio-btn").is(":checked")) { + $("#user-roles-select-field").hide(); + $("#users-select-field").show(); + } + if ($("#user-roles-radio-btn").is(":checked")) { + $("#users-select-field").hide(); + $("#user-roles-select-field").show(); + } + }); + + // Support for special input type "ANY" on user(s) & user-role(s) selection + $("#user-roles-input").select2({ + "tags": true + }).on("select2:select", function (e) { + if (e.params.data.id == "ANY") { + $(this).val("ANY").trigger("change"); + } else { + $("option[value=ANY]", this).prop("selected", false).parent().trigger("change"); + } + }); + + // Maintains an array of configured features of the profile + var advanceOperations = ".wr-advance-operations"; + $(advanceOperations).on("click", ".wr-input-control.switch", function (event) { + var operationCode = $(this).parents(".operation-data").data("operation-code"); + var operation = $(this).parents(".operation-data").data("operation"); + var operationDataWrapper = $(this).data("target"); + // prevents event bubbling by figuring out what element it's being called from. + if (event.target.tagName == "INPUT") { + var featureConfiguredIcon; + if ($("input[type='checkbox']", this).is(":checked")) { + configuredOperations.push(operationCode); + // when a feature is enabled, if "zero-configured-features" msg is available, hide that. + var zeroConfiguredOperationsErrorMsg = "#policy-profile-main-error-msg"; + if (!$(zeroConfiguredOperationsErrorMsg).hasClass("hidden")) { + $(zeroConfiguredOperationsErrorMsg).addClass("hidden"); + } + // add configured-state-icon to the feature + featureConfiguredIcon = "#" + operation + "-configured"; + if ($(featureConfiguredIcon).hasClass("hidden")) { + $(featureConfiguredIcon).removeClass("hidden"); + } + } else { + //splicing the array if operation is present. + var index = $.inArray(operationCode, configuredOperations); + if (index != -1) { + configuredOperations.splice(index, 1); + } + // when a feature is disabled, clearing all its current configured, error or success states + var subErrorMsgWrapper = "#" + operation + "-feature-error-msg"; + var subErrorIcon = "#" + operation + "-error"; + var subOkIcon = "#" + operation + "-ok"; + featureConfiguredIcon = "#" + operation + "-configured"; + + if (!$(subErrorMsgWrapper).hasClass("hidden")) { + $(subErrorMsgWrapper).addClass("hidden"); + } + if (!$(subErrorIcon).hasClass("hidden")) { + $(subErrorIcon).addClass("hidden"); + } + if (!$(subOkIcon).hasClass("hidden")) { + $(subOkIcon).addClass("hidden"); + } + if (!$(featureConfiguredIcon).hasClass("hidden")) { + $(featureConfiguredIcon).addClass("hidden"); + } + // reinitializing input fields into the defaults + $(operationDataWrapper + " input").each( + function () { + if ($(this).is("input:text")) { + $(this).val($(this).data("default")); + } else if ($(this).is("input:password")) { + $(this).val(""); + } else if ($(this).is("input:checkbox")) { + $(this).prop("checked", $(this).data("default")); + // if this checkbox is the parent input of a grouped-input + if ($(this).hasClass("parent-input")) { + var groupedInput = $(this).parent().parent().parent(); + updateGroupedInputVisibility(groupedInput); + } + } + } + ); + // reinitializing select fields into the defaults + $(operationDataWrapper + " select").each( + function () { + var defaultOption = $(this).data("default"); + $("option:eq(" + defaultOption + ")", this).prop("selected", "selected"); + } + ); + // collapsing expanded-panes (upon the selection of html-select-options) if any + $(operationDataWrapper + " .expanded").each( + function () { + if ($(this).hasClass("expanded")) { + $(this).removeClass("expanded"); + } + $(this).slideUp(); + } + ); + // removing all entries of grid-input elements if exist + $(operationDataWrapper + " .grouped-array-input").each( + function () { + var gridInputs = $(this).find("[data-add-form-clone]"); + if (gridInputs.length > 0) { + gridInputs.remove(); + } + var helpTexts = $(this).find("[data-help-text=add-form]"); + if (helpTexts.length > 0) { + helpTexts.show(); + } + } + ); + } + } + }); + + // adding support for cloning multiple profiles per feature with cloneable class definitions + $(advanceOperations).on("click", ".multi-view.add.enabled", function () { + // get a copy of .cloneable and create new .cloned div element + var cloned = "

      " + $(".cloneable", $(this).parent().parent()).html() + "
      "; + // append newly created .cloned div element to panel-body + $(this).parent().parent().append(cloned); + // enable remove action of newly cloned div element + $(".cloned", $(this).parent().parent()).each( + function () { + if ($(".multi-view.remove", this).hasClass("disabled")) { + $(".multi-view.remove", this).removeClass("disabled"); + } + if (!$(".multi-view.remove", this).hasClass("enabled")) { + $(".multi-view.remove", this).addClass("enabled"); + } + } + ); + }); + + $(advanceOperations).on("click", ".multi-view.remove.enabled", function () { + $(this).parent().remove(); + }); + + // enabling or disabling grouped-input based on the status of a parent check-box + $(advanceOperations).on("click", ".grouped-input", function () { + updateGroupedInputVisibility(this); + }); + + // add form entry click function for grid inputs + $(advanceOperations).on("click", "[data-click-event=add-form]", function () { + var addFormContainer = $("[data-add-form-container=" + $(this).attr("href") + "]"); + var clonedForm = $("[data-add-form=" + $(this).attr("href") + "]").clone(). + find("[data-add-form-element=clone]").attr("data-add-form-clone", $(this).attr("href")); + + // adding class .child-input to capture text-input-array-values + $("input, select", clonedForm).addClass("child-input"); + + $(addFormContainer).append(clonedForm); + setId(addFormContainer); + showHideHelpText(addFormContainer); + }); + + // remove form entry click function for grid inputs + $(advanceOperations).on("click", "[data-click-event=remove-form]", function () { + var addFormContainer = $("[data-add-form-container=" + $(this).attr("href") + "]"); + + $(this).closest("[data-add-form-element=clone]").remove(); + setId(addFormContainer); + showHideHelpText(addFormContainer); + }); + + $(".wizard-stepper").click(function () { + // button clicked here can be either a continue button or a back button. + var currentStep = $(this).data("current"); + var validationIsRequired = $(this).data("validate"); + var wizardIsToBeContinued; + + if (validationIsRequired) { + wizardIsToBeContinued = validateStep[currentStep](); + } else { + wizardIsToBeContinued = true; + } + + if (wizardIsToBeContinued) { + // When moving back and forth, following code segment will + // remove if there are any visible error-messages. + var errorMsgWrappers = ".alert.alert-danger"; + $(errorMsgWrappers).each( + function () { + if (!$(this).hasClass("hidden")) { + $(this).addClass("hidden"); + } + } + ); + + var nextStep = $(this).data("next"); + var isBackBtn = $(this).data("is-back-btn"); + + // if current button is a continuation... + if (!isBackBtn) { + // initiate stepForwardFrom[*] functions to gather form data. + if (stepForwardFrom[currentStep]) { + stepForwardFrom[currentStep](this); + } + } else { + // initiate stepBackFrom[*] functions to rollback. + if (stepBackFrom[currentStep]) { + stepBackFrom[currentStep](); + } + } + + // following step occurs only at the last stage of the wizard. + if (!nextStep) { + window.location.href = $(this).data("direct"); + } + + // updating next wizard step as current. + $(".itm-wiz").each(function () { + var step = $(this).data("step"); + if (step == nextStep) { + $(this).addClass("itm-wiz-current"); + } else { + $(this).removeClass("itm-wiz-current"); + } + }); + + // adding next update of wizard-steps. + $("#" + nextStep + "-wizard-steps").html($(".wr-steps").html()); + + // hiding current section of the wizard and showing next section. + $("." + currentStep).addClass("hidden"); + $("." + nextStep).removeClass("hidden"); + } + }); +}); \ No newline at end of file diff --git a/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.policy.edit/public/js/sql.js b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.policy.edit/public/js/sql.js new file mode 100644 index 0000000000..9f92fbc938 --- /dev/null +++ b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.policy.edit/public/js/sql.js @@ -0,0 +1,310 @@ +/* + * Copyright (c) 2016, 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. + */ + +/*Annotations, Annotation Names and relevant tokens*/ +var ANNOTATION_IMPORT = "Import"; +var ANNOTATION_EXPORT = "Export"; + +var ANNOTATION_TOKEN_AT = "@"; +var ANNOTATION_TOKEN_OPENING_BRACKET = "("; +var ANNOTATION_TOKEN_CLOSING_BRACKET = ")"; + +var REGEX_LINE_STARTING_WITH_PLAN = /^@Plan.*/g; +var REGEX_LINE_STARTING_WITH_SINGLE_LINE_COMMENT = /^--.*/g; +var REGEX_LINE_STARTING_WITH_MULTI_LINE_COMMENT = /^\/\*.*\*\//g; +var REGEX_LINE_STARTING_WITH_IMPORT_STATEMENT = /^@Import.*/g; + +var SIDDHI_STATEMENT_DELIMETER = ";"; +var SIDDHI_LINE_BREAK = "\n"; +var SIDDHI_LINE_BREAK_CHARACTER = '\n'; +var SIDDHI_SINGLE_QUOTE = "'"; +var SIDDHI_SPACE_LITERAL = " "; + +var SIDDHI_LITERAL_DEFINE_STREAM = "define stream"; + +var MIME_TYPE_SIDDHI_QL = "text/siddhi-ql"; + + +CodeMirror.defineMode("sql", function (config, parserConfig) { + "use strict"; + + var client = parserConfig.client || {}, + atoms = parserConfig.atoms || {"false":true, "true":true, "null":true}, + builtin = parserConfig.builtin || {}, + keywords = parserConfig.keywords || {}, + operatorChars = parserConfig.operatorChars || /^[*+\-%<>!=&|~^]/, + support = parserConfig.support || {}, + hooks = parserConfig.hooks || {}, + dateSQL = parserConfig.dateSQL || {"date":true, "time":true, "timestamp":true}; + + function tokenBase(stream, state) { + var ch = stream.next(); + + // call hooks from the mime type + if (hooks[ch]) { + var result = hooks[ch](stream, state); + if (result !== false) return result; + } + + if (ch.charCodeAt(0) > 47 && ch.charCodeAt(0) < 58) { + // numbers + // ref: http://dev.mysql.com/doc/refman/5.5/en/number-literals.html + stream.match(/^[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?/); + support.decimallessFloat == true && stream.eat('.'); + return "number"; + } else if (ch == "'" || (ch == '"' && support.doubleQuote)) { + // strings + // ref: http://dev.mysql.com/doc/refman/5.5/en/string-literals.html + state.tokenize = tokenLiteral(ch); + return state.tokenize(stream, state); + } else if (/^[\(\),\;\[\]]/.test(ch)) { + // no highlightning + return null; + } else if ((ch == "-" && stream.eat("-") && (!support.commentSpaceRequired || stream.eat(" ")))) { + // 1-line comments + // ref: https://kb.askmonty.org/en/comment-syntax/ + stream.skipToEnd(); + return "comment"; + } else if (ch == "/" && stream.eat("*")) { + // multi-line comments + // ref: https://kb.askmonty.org/en/comment-syntax/ + state.tokenize = tokenComment; + return state.tokenize(stream, state); + } else if (ch == ".") { + // .1 for 0.1 + if (support.zerolessFloat == true && stream.match(/^(?:\d+(?:e[+-]?\d+)?)/i)) { + return "number"; + } + } else { + stream.eatWhile(/^[_\-\w\d]/); /* Character '-' will also be eaten, to prevent the highlight happening in keywords being embedded in non-keyword strings. For example, 'all' in 'all-nonkeyword' */ + var word = stream.current().toLowerCase(); // Added toLowerCase() to highlight keywords in a case insensitive manner. + // dates (standard SQL syntax) + // ref: http://dev.mysql.com/doc/refman/5.5/en/date-and-time-literals.html + if (dateSQL.hasOwnProperty(word) && (stream.match(/^( )+'[^']*'/) || stream.match(/^( )+"[^"]*"/))) + return "number"; + if (atoms.hasOwnProperty(word)) return "atom"; + if (builtin.hasOwnProperty(word)) return "builtin"; + if (keywords.hasOwnProperty(word)) return "keyword"; + if (client.hasOwnProperty(word)) return "string-2"; + return null; + } + } + + // 'string', with char specified in quote escaped by '\' + function tokenLiteral(quote) { + return function (stream, state) { + var escaped = false, ch; + while ((ch = stream.next()) != null) { + if (ch == quote && !escaped) { + state.tokenize = tokenBase; + break; + } + escaped = !escaped && ch == "\\"; + } + return "string"; + }; + } + + function tokenComment(stream, state) { + while (true) { + if (stream.skipTo("*")) { + stream.next(); + if (stream.eat("/")) { + state.tokenize = tokenBase; + break; + } + } else { + stream.skipToEnd(); + break; + } + } + return "comment"; + } + + function pushContext(stream, state, type) { + state.context = { + prev:state.context, + indent:stream.indentation(), + col:stream.column(), + type:type + }; + } + + function popContext(state) { + state.indent = state.context.indent; + state.context = state.context.prev; + } + + return { + startState:function () { + return {tokenize:tokenBase, context:null}; + }, + + token:function (stream, state) { + if (stream.sol()) { + if (state.context && state.context.align == null) + state.context.align = false; + } + if (stream.eatSpace()) return null; + + var style = state.tokenize(stream, state); + if (style == "comment") return style; + + if (state.context && state.context.align == null) + state.context.align = true; + + var tok = stream.current(); + if (tok == "(") + pushContext(stream, state, ")"); + else if (tok == "[") + pushContext(stream, state, "]"); + else if (state.context && state.context.type == tok) + popContext(state); + return style; + }, + + indent:function (state, textAfter) { + var cx = state.context; + if (!cx) return CodeMirror.Pass; + var closing = textAfter.charAt(0) == cx.type; + if (cx.align) return cx.col + (closing ? 0 : 1); + else return cx.indent + (closing ? 0 : config.indentUnit); + }, + + blockCommentStart: "/*", + blockCommentEnd: "*/", + lineComment: "--" + }; +}); + +(function () { + "use strict"; + + // `identifier` + function hookIdentifier(stream) { + // MySQL/MariaDB identifiers + // ref: http://dev.mysql.com/doc/refman/5.6/en/identifier-qualifiers.html + var ch; + while ((ch = stream.next()) != null) { + if (ch == "`" && !stream.eat("`")) return "variable-2"; + } + stream.backUp(stream.current().length - 1); + return stream.eatWhile(/\w/) ? "variable-2" : null; + } + + // variable token + function hookVar(stream) { + // variables + // @@prefix.varName @varName + // varName can be quoted with ` or ' or " + // ref: http://dev.mysql.com/doc/refman/5.5/en/user-variables.html + if (stream.eat("@")) { + stream.match(/^session\./); + stream.match(/^local\./); + stream.match(/^global\./); + } + + if (stream.eat("'")) { + stream.match(/^.*'/); + return "variable-2"; + } else if (stream.eat('"')) { + stream.match(/^.*"/); + return "variable-2"; + } else if (stream.eat("`")) { + stream.match(/^.*`/); + return "variable-2"; + } else if (stream.match(/^[0-9a-zA-Z$\.\_]+/)) { + return "variable-2"; + } + return null; + } + + ; + + // short client keyword token + function hookClient(stream) { + // \N means NULL + // ref: http://dev.mysql.com/doc/refman/5.5/en/null-values.html + if (stream.eat("N")) { + return "atom"; + } + // \g, etc + // ref: http://dev.mysql.com/doc/refman/5.5/en/mysql-commands.html + return stream.match(/^[a-zA-Z.#!?]/) ? "variable-2" : null; + } + + // these keywords are used by all SQL dialects (however, a mode can still overwrite it) + var sqlKeywordsWithoutSymbols = "all and as begin by contains define delete end events " + + "every first for from full group having inner insert into join last " + + "left not of on or outer output partition raw return right select snapshot stream table "; + var sqlKeywords = ", : ? # ( ) " + sqlKeywordsWithoutSymbols; + var builtIn = "bool double float int long object string "; + var atoms = "false true null "; + var dateSQL = "days hours milliseconds minutes months seconds "; + var allSqlSuggestions = sqlKeywordsWithoutSymbols + builtIn + atoms + dateSQL; + + // turn a space-separated list into an array + function set(str) { + var obj = {}, words = str.split(" "); + for (var i = 0; i < words.length; ++i) obj[words[i]] = true; + return obj; + } + + // A generic SQL Mode. It's not a standard, it just try to support what is generally supported + CodeMirror.defineMIME(MIME_TYPE_SIDDHI_QL, { + name:"sql", + keywords:set(sqlKeywords), + builtin:set(builtIn), + atoms:set(atoms), + operatorChars:/^[*+%<>!=/]/, + dateSQL:set(dateSQL), + support:set("doubleQuote "), + allSqlSuggestions:set(allSqlSuggestions) + }); +}()); + +/* + How Properties of Mime Types are used by SQL Mode + ================================================= + + keywords: + A list of keywords you want to be highlighted. + functions: + A list of function names you want to be highlighted. + builtin: + A list of builtin types you want to be highlighted (if you want types to be of class "builtin" instead of "keyword"). + operatorChars: + All characters that must be handled as operators. + client: + Commands parsed and executed by the client (not the server). + support: + A list of supported syntaxes which are not common, but are supported by more than 1 DBMS. + * ODBCdotTable: .tableName + * zerolessFloat: .1 + * doubleQuote + * nCharCast: N'string' + * charsetCast: _utf8'string' + * commentHash: use # char for comments + * commentSlashSlash: use // for comments + * commentSpaceRequired: require a space after -- for comments + atoms: + Keywords that must be highlighted as atoms,. Some DBMS's support more atoms than others: + UNKNOWN, INFINITY, UNDERFLOW, NaN... + dateSQL: + Used for date/time SQL standard syntax, because not all DBMS's support same temporal types. + */ \ No newline at end of file diff --git a/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.policy.view/public/css/codemirror.css b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.policy.view/public/css/codemirror.css new file mode 100644 index 0000000000..e749a5211d --- /dev/null +++ b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.policy.view/public/css/codemirror.css @@ -0,0 +1,342 @@ +/* + * Copyright (c) 2016, 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. + */ + +/* BASICS */ + +.CodeMirror { + /* Set height, width, borders, and global font properties here */ + font-family: monospace; + height: 300px; + color: black; +} + +/* PADDING */ + +.CodeMirror-lines { + padding: 4px 0; /* Vertical padding around content */ +} +.CodeMirror pre { + padding: 0 4px; /* Horizontal padding of content */ +} + +.CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler { + background-color: white; /* The little square between H and V scrollbars */ +} + +/* GUTTER */ + +.CodeMirror-gutters { + border-right: 1px solid #ddd; + background-color: #f7f7f7; + white-space: nowrap; +} +.CodeMirror-linenumbers {} +.CodeMirror-linenumber { + padding: 0 3px 0 5px; + min-width: 20px; + text-align: right; + color: #999; + white-space: nowrap; + left: -30px; +} + +.CodeMirror-guttermarker { color: black; } +.CodeMirror-guttermarker-subtle { color: #999; } + +/* CURSOR */ + +.CodeMirror div.CodeMirror-cursor { + border-left: 1px solid black; +} +/* Shown when moving in bi-directional text */ +.CodeMirror div.CodeMirror-secondarycursor { + border-left: 1px solid silver; +} +.CodeMirror.cm-fat-cursor div.CodeMirror-cursor { + width: auto; + border: 0; + background: #7e7; +} +.CodeMirror.cm-fat-cursor div.CodeMirror-cursors { + z-index: 1; +} + +.cm-animate-fat-cursor { + width: auto; + border: 0; + -webkit-animation: blink 1.06s steps(1) infinite; + -moz-animation: blink 1.06s steps(1) infinite; + animation: blink 1.06s steps(1) infinite; +} +@-moz-keyframes blink { + 0% { background: #7e7; } + 50% { background: none; } + 100% { background: #7e7; } +} +@-webkit-keyframes blink { + 0% { background: #7e7; } + 50% { background: none; } + 100% { background: #7e7; } +} +@keyframes blink { + 0% { background: #7e7; } + 50% { background: none; } + 100% { background: #7e7; } +} + +/* Can style cursor different in overwrite (non-insert) mode */ +div.CodeMirror-overwrite div.CodeMirror-cursor {} + +.cm-tab { display: inline-block; text-decoration: inherit; } + +.CodeMirror-ruler { + border-left: 1px solid #ccc; + position: absolute; +} + +/* DEFAULT THEME */ + +.cm-s-default .cm-keyword {color: #708;} +.cm-s-default .cm-atom {color: #219;} +.cm-s-default .cm-number {color: #164;} +.cm-s-default .cm-def {color: #00f;} +.cm-s-default .cm-variable, +.cm-s-default .cm-punctuation, +.cm-s-default .cm-property, +.cm-s-default .cm-operator {} +.cm-s-default .cm-variable-2 {color: #05a;} +.cm-s-default .cm-variable-3 {color: #085;} +.cm-s-default .cm-comment {color: #a50;} +.cm-s-default .cm-string {color: #a11;} +.cm-s-default .cm-string-2 {color: #f50;} +.cm-s-default .cm-meta {color: #555;} +.cm-s-default .cm-qualifier {color: #555;} +.cm-s-default .cm-builtin {color: #30a;} +.cm-s-default .cm-bracket {color: #997;} +.cm-s-default .cm-tag {color: #170;} +.cm-s-default .cm-attribute {color: #00c;} +.cm-s-default .cm-header {color: blue;} +.cm-s-default .cm-quote {color: #090;} +.cm-s-default .cm-hr {color: #999;} +.cm-s-default .cm-link {color: #00c;} + +.cm-negative {color: #d44;} +.cm-positive {color: #292;} +.cm-header, .cm-strong {font-weight: bold;} +.cm-em {font-style: italic;} +.cm-link {text-decoration: underline;} +.cm-strikethrough {text-decoration: line-through;} + +.cm-s-default .cm-error {color: #f00;} +.cm-invalidchar {color: #f00;} + +/* Default styles for common addons */ + +div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;} +div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;} +.CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); } +.CodeMirror-activeline-background {background: #e8f2ff;} + +/* STOP */ + +/* The rest of this file contains styles related to the mechanics of + the editor. You probably shouldn't touch them. */ + +.CodeMirror { + position: relative; + overflow: hidden; + background: white; +} + +.CodeMirror-scroll { + overflow: scroll !important; /* Things will break if this is overridden */ + /* 30px is the magic margin used to hide the element's real scrollbars */ + /* See overflow: hidden in .CodeMirror */ + margin-bottom: -30px; margin-right: -30px; + padding-bottom: 30px; + height: 100%; + outline: none; /* Prevent dragging from highlighting the element */ + position: relative; +} +.CodeMirror-sizer { + position: relative; + border-right: 30px solid transparent; +} + +/* The fake, visible scrollbars. Used to force redraw during scrolling + before actuall scrolling happens, thus preventing shaking and + flickering artifacts. */ +.CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler { + position: absolute; + z-index: 6; + display: none; +} +.CodeMirror-vscrollbar { + right: 0; top: 0; + overflow-x: hidden; + overflow-y: scroll; +} +.CodeMirror-hscrollbar { + bottom: 0; left: 0; + overflow-y: hidden; + overflow-x: scroll; +} +.CodeMirror-scrollbar-filler { + right: 0; bottom: 0; +} +.CodeMirror-gutter-filler { + left: 0; bottom: 0; +} + +.CodeMirror-gutters { + position: absolute; left: 0; top: 0; + z-index: 3; +} +.CodeMirror-gutter { + white-space: normal; + height: 100%; + display: inline-block; + /* Hack to make IE7 behave */ + *zoom:1; + *display:inline; +} +.CodeMirror-gutter-wrapper { + position: absolute; + z-index: 4; + height: 100%; +} +.CodeMirror-gutter-elt { + position: absolute; + cursor: default; + z-index: 4; + left: -30px; +} +.CodeMirror-gutter-wrapper { + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; +} + +.CodeMirror-lines { + cursor: text; + min-height: 1px; /* prevents collapsing before first draw */ +} +.CodeMirror pre { + /* Reset some styles that the rest of the page might have set */ + -moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0; + border-width: 0; + background: transparent; + font-family: inherit; + font-size: inherit; + margin: 0; + white-space: pre; + word-wrap: normal; + line-height: inherit; + color: inherit; + z-index: 2; + position: relative; + overflow: visible; + -webkit-tap-highlight-color: transparent; +} +.CodeMirror-wrap pre { + word-wrap: break-word; + white-space: pre-wrap; + word-break: normal; +} + +.CodeMirror-linebackground { + position: absolute; + left: 0; right: 0; top: 0; bottom: 0; + z-index: 0; +} + +.CodeMirror-linewidget { + position: relative; + z-index: 2; + overflow: auto; +} + +.CodeMirror-widget {} + +.CodeMirror-code { + outline: none; +} + +/* Force content-box sizing for the elements where we expect it */ +.CodeMirror-scroll, +.CodeMirror-sizer, +.CodeMirror-gutter, +.CodeMirror-gutters, +.CodeMirror-linenumber { + -moz-box-sizing: content-box; + box-sizing: content-box; +} + +.CodeMirror-measure { + position: absolute; + width: 100%; + height: 0; + overflow: hidden; + visibility: hidden; +} +.CodeMirror-measure pre { position: static; } + +.CodeMirror div.CodeMirror-cursor { + position: absolute; + border-right: none; + width: 0; +} + +div.CodeMirror-cursors { + visibility: hidden; + position: relative; + z-index: 3; +} +.CodeMirror-focused div.CodeMirror-cursors { + visibility: visible; +} + +.CodeMirror-selected { background: #d9d9d9; } +.CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; } +.CodeMirror-crosshair { cursor: crosshair; } +.CodeMirror ::selection { background: #d7d4f0; } +.CodeMirror ::-moz-selection { background: #d7d4f0; } + +.cm-searching { + background: #ffa; + background: rgba(255, 255, 0, .4); +} + +/* IE7 hack to prevent it from returning funny offsetTops on the spans */ +.CodeMirror span { *vertical-align: text-bottom; } + +/* Used to force a border model for a node */ +.cm-force-border { padding-right: .1px; } + +@media print { + /* Hide the cursor when printing */ + .CodeMirror div.CodeMirror-cursors { + visibility: hidden; + } +} + +/* See issue #2901 */ +.cm-tab-wrap-hack:after { content: ''; } + +/* Help users use markselection to safely style text background */ +span.CodeMirror-selectedtext { background: none; } \ No newline at end of file diff --git a/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.policy.view/public/js/codemirror.js b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.policy.view/public/js/codemirror.js new file mode 100644 index 0000000000..20f3f95ed9 --- /dev/null +++ b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.policy.view/public/js/codemirror.js @@ -0,0 +1,8720 @@ +/* + * Copyright (c) 2016, 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. + */ + +// This is CodeMirror (http://codemirror.net), a code editor +// implemented in JavaScript on top of the browser's DOM. +// +// You can find some technical background for some of the code below +// at http://marijnhaverbeke.nl/blog/#cm-internals . + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + module.exports = mod(); + else if (typeof define == "function" && define.amd) // AMD + return define([], mod); + else // Plain browser env + this.CodeMirror = mod(); +})(function() { + "use strict"; + + // BROWSER SNIFFING + + // Kludges for bugs and behavior differences that can't be feature + // detected are enabled based on userAgent etc sniffing. + + var gecko = /gecko\/\d/i.test(navigator.userAgent); + var ie_upto10 = /MSIE \d/.test(navigator.userAgent); + var ie_11up = /Trident\/(?:[7-9]|\d{2,})\..*rv:(\d+)/.exec(navigator.userAgent); + var ie = ie_upto10 || ie_11up; + var ie_version = ie && (ie_upto10 ? document.documentMode || 6 : ie_11up[1]); + var webkit = /WebKit\//.test(navigator.userAgent); + var qtwebkit = webkit && /Qt\/\d+\.\d+/.test(navigator.userAgent); + var chrome = /Chrome\//.test(navigator.userAgent); + var presto = /Opera\//.test(navigator.userAgent); + var safari = /Apple Computer/.test(navigator.vendor); + var mac_geMountainLion = /Mac OS X 1\d\D([8-9]|\d\d)\D/.test(navigator.userAgent); + var phantom = /PhantomJS/.test(navigator.userAgent); + + var ios = /AppleWebKit/.test(navigator.userAgent) && /Mobile\/\w+/.test(navigator.userAgent); + // This is woefully incomplete. Suggestions for alternative methods welcome. + var mobile = ios || /Android|webOS|BlackBerry|Opera Mini|Opera Mobi|IEMobile/i.test(navigator.userAgent); + var mac = ios || /Mac/.test(navigator.platform); + var windows = /win/i.test(navigator.platform); + + var presto_version = presto && navigator.userAgent.match(/Version\/(\d*\.\d*)/); + if (presto_version) presto_version = Number(presto_version[1]); + if (presto_version && presto_version >= 15) { presto = false; webkit = true; } + // Some browsers use the wrong event properties to signal cmd/ctrl on OS X + var flipCtrlCmd = mac && (qtwebkit || presto && (presto_version == null || presto_version < 12.11)); + var captureRightClick = gecko || (ie && ie_version >= 9); + + // Optimize some code when these features are not used. + var sawReadOnlySpans = false, sawCollapsedSpans = false; + + // EDITOR CONSTRUCTOR + + // A CodeMirror instance represents an editor. This is the object + // that user code is usually dealing with. + + function CodeMirror(place, options) { + if (!(this instanceof CodeMirror)) return new CodeMirror(place, options); + + this.options = options = options ? copyObj(options) : {}; + // Determine effective options based on given values and defaults. + copyObj(defaults, options, false); + setGuttersForLineNumbers(options); + + var doc = options.value; + if (typeof doc == "string") doc = new Doc(doc, options.mode); + this.doc = doc; + + var input = new CodeMirror.inputStyles[options.inputStyle](this); + var display = this.display = new Display(place, doc, input); + display.wrapper.CodeMirror = this; + updateGutters(this); + themeChanged(this); + if (options.lineWrapping) + this.display.wrapper.className += " CodeMirror-wrap"; + if (options.autofocus && !mobile) display.input.focus(); + initScrollbars(this); + + this.state = { + keyMaps: [], // stores maps added by addKeyMap + overlays: [], // highlighting overlays, as added by addOverlay + modeGen: 0, // bumped when mode/overlay changes, used to invalidate highlighting info + overwrite: false, + delayingBlurEvent: false, + focused: false, + suppressEdits: false, // used to disable editing during key handlers when in readOnly mode + pasteIncoming: false, cutIncoming: false, // help recognize paste/cut edits in input.poll + draggingText: false, + highlight: new Delayed(), // stores highlight worker timeout + keySeq: null, // Unfinished key sequence + specialChars: null + }; + + var cm = this; + + // Override magic textarea content restore that IE sometimes does + // on our hidden textarea on reload + if (ie && ie_version < 11) setTimeout(function() { cm.display.input.reset(true); }, 20); + + registerEventHandlers(this); + ensureGlobalHandlers(); + + startOperation(this); + this.curOp.forceUpdate = true; + attachDoc(this, doc); + + if ((options.autofocus && !mobile) || cm.hasFocus()) + setTimeout(bind(onFocus, this), 20); + else + onBlur(this); + + for (var opt in optionHandlers) if (optionHandlers.hasOwnProperty(opt)) + optionHandlers[opt](this, options[opt], Init); + maybeUpdateLineNumberWidth(this); + if (options.finishInit) options.finishInit(this); + for (var i = 0; i < initHooks.length; ++i) initHooks[i](this); + endOperation(this); + // Suppress optimizelegibility in Webkit, since it breaks text + // measuring on line wrapping boundaries. + if (webkit && options.lineWrapping && + getComputedStyle(display.lineDiv).textRendering == "optimizelegibility") + display.lineDiv.style.textRendering = "auto"; + } + + // DISPLAY CONSTRUCTOR + + // The display handles the DOM integration, both for input reading + // and content drawing. It holds references to DOM nodes and + // display-related state. + + function Display(place, doc, input) { + var d = this; + this.input = input; + + // Covers bottom-right square when both scrollbars are present. + d.scrollbarFiller = elt("div", null, "CodeMirror-scrollbar-filler"); + d.scrollbarFiller.setAttribute("cm-not-content", "true"); + // Covers bottom of gutter when coverGutterNextToScrollbar is on + // and h scrollbar is present. + d.gutterFiller = elt("div", null, "CodeMirror-gutter-filler"); + d.gutterFiller.setAttribute("cm-not-content", "true"); + // Will contain the actual code, positioned to cover the viewport. + d.lineDiv = elt("div", null, "CodeMirror-code"); + // Elements are added to these to represent selection and cursors. + d.selectionDiv = elt("div", null, null, "position: relative; z-index: 1"); + d.cursorDiv = elt("div", null, "CodeMirror-cursors"); + // A visibility: hidden element used to find the size of things. + d.measure = elt("div", null, "CodeMirror-measure"); + // When lines outside of the viewport are measured, they are drawn in this. + d.lineMeasure = elt("div", null, "CodeMirror-measure"); + // Wraps everything that needs to exist inside the vertically-padded coordinate system + d.lineSpace = elt("div", [d.measure, d.lineMeasure, d.selectionDiv, d.cursorDiv, d.lineDiv], + null, "position: relative; outline: none"); + // Moved around its parent to cover visible view. + d.mover = elt("div", [elt("div", [d.lineSpace], "CodeMirror-lines")], null, "position: relative"); + // Set to the height of the document, allowing scrolling. + d.sizer = elt("div", [d.mover], "CodeMirror-sizer"); + d.sizerWidth = null; + // Behavior of elts with overflow: auto and padding is + // inconsistent across browsers. This is used to ensure the + // scrollable area is big enough. + d.heightForcer = elt("div", null, null, "position: absolute; height: " + scrollerGap + "px; width: 1px;"); + // Will contain the gutters, if any. + d.gutters = elt("div", null, "CodeMirror-gutters"); + d.lineGutter = null; + // Actual scrollable element. + d.scroller = elt("div", [d.sizer, d.heightForcer, d.gutters], "CodeMirror-scroll"); + d.scroller.setAttribute("tabIndex", "-1"); + // The element in which the editor lives. + d.wrapper = elt("div", [d.scrollbarFiller, d.gutterFiller, d.scroller], "CodeMirror"); + + // Work around IE7 z-index bug (not perfect, hence IE7 not really being supported) + if (ie && ie_version < 8) { d.gutters.style.zIndex = -1; d.scroller.style.paddingRight = 0; } + if (!webkit && !(gecko && mobile)) d.scroller.draggable = true; + + if (place) { + if (place.appendChild) place.appendChild(d.wrapper); + else place(d.wrapper); + } + + // Current rendered range (may be bigger than the view window). + d.viewFrom = d.viewTo = doc.first; + d.reportedViewFrom = d.reportedViewTo = doc.first; + // Information about the rendered lines. + d.view = []; + d.renderedView = null; + // Holds info about a single rendered line when it was rendered + // for measurement, while not in view. + d.externalMeasured = null; + // Empty space (in pixels) above the view + d.viewOffset = 0; + d.lastWrapHeight = d.lastWrapWidth = 0; + d.updateLineNumbers = null; + + d.nativeBarWidth = d.barHeight = d.barWidth = 0; + d.scrollbarsClipped = false; + + // Used to only resize the line number gutter when necessary (when + // the amount of lines crosses a boundary that makes its width change) + d.lineNumWidth = d.lineNumInnerWidth = d.lineNumChars = null; + // Set to true when a non-horizontal-scrolling line widget is + // added. As an optimization, line widget aligning is skipped when + // this is false. + d.alignWidgets = false; + + d.cachedCharWidth = d.cachedTextHeight = d.cachedPaddingH = null; + + // Tracks the maximum line length so that the horizontal scrollbar + // can be kept static when scrolling. + d.maxLine = null; + d.maxLineLength = 0; + d.maxLineChanged = false; + + // Used for measuring wheel scrolling granularity + d.wheelDX = d.wheelDY = d.wheelStartX = d.wheelStartY = null; + + // True when shift is held down. + d.shift = false; + + // Used to track whether anything happened since the context menu + // was opened. + d.selForContextMenu = null; + + d.activeTouch = null; + + input.init(d); + } + + // STATE UPDATES + + // Used to get the editor into a consistent state again when options change. + + function loadMode(cm) { + cm.doc.mode = CodeMirror.getMode(cm.options, cm.doc.modeOption); + resetModeState(cm); + } + + function resetModeState(cm) { + cm.doc.iter(function(line) { + if (line.stateAfter) line.stateAfter = null; + if (line.styles) line.styles = null; + }); + cm.doc.frontier = cm.doc.first; + startWorker(cm, 100); + cm.state.modeGen++; + if (cm.curOp) regChange(cm); + } + + function wrappingChanged(cm) { + if (cm.options.lineWrapping) { + addClass(cm.display.wrapper, "CodeMirror-wrap"); + cm.display.sizer.style.minWidth = ""; + cm.display.sizerWidth = null; + } else { + rmClass(cm.display.wrapper, "CodeMirror-wrap"); + findMaxLine(cm); + } + estimateLineHeights(cm); + regChange(cm); + clearCaches(cm); + setTimeout(function(){updateScrollbars(cm);}, 100); + } + + // Returns a function that estimates the height of a line, to use as + // first approximation until the line becomes visible (and is thus + // properly measurable). + function estimateHeight(cm) { + var th = textHeight(cm.display), wrapping = cm.options.lineWrapping; + var perLine = wrapping && Math.max(5, cm.display.scroller.clientWidth / charWidth(cm.display) - 3); + return function(line) { + if (lineIsHidden(cm.doc, line)) return 0; + + var widgetsHeight = 0; + if (line.widgets) for (var i = 0; i < line.widgets.length; i++) { + if (line.widgets[i].height) widgetsHeight += line.widgets[i].height; + } + + if (wrapping) + return widgetsHeight + (Math.ceil(line.text.length / perLine) || 1) * th; + else + return widgetsHeight + th; + }; + } + + function estimateLineHeights(cm) { + var doc = cm.doc, est = estimateHeight(cm); + doc.iter(function(line) { + var estHeight = est(line); + if (estHeight != line.height) updateLineHeight(line, estHeight); + }); + } + + function themeChanged(cm) { + cm.display.wrapper.className = cm.display.wrapper.className.replace(/\s*cm-s-\S+/g, "") + + cm.options.theme.replace(/(^|\s)\s*/g, " cm-s-"); + clearCaches(cm); + } + + function guttersChanged(cm) { + updateGutters(cm); + regChange(cm); + setTimeout(function(){alignHorizontally(cm);}, 20); + } + + // Rebuild the gutter elements, ensure the margin to the left of the + // code matches their width. + function updateGutters(cm) { + var gutters = cm.display.gutters, specs = cm.options.gutters; + removeChildren(gutters); + for (var i = 0; i < specs.length; ++i) { + var gutterClass = specs[i]; + var gElt = gutters.appendChild(elt("div", null, "CodeMirror-gutter " + gutterClass)); + if (gutterClass == "CodeMirror-linenumbers") { + cm.display.lineGutter = gElt; + gElt.style.width = (cm.display.lineNumWidth || 1) + "px"; + } + } + gutters.style.display = i ? "" : "none"; + updateGutterSpace(cm); + } + + function updateGutterSpace(cm) { + var width = cm.display.gutters.offsetWidth; + cm.display.sizer.style.marginLeft = width + "px"; + } + + // Compute the character length of a line, taking into account + // collapsed ranges (see markText) that might hide parts, and join + // other lines onto it. + function lineLength(line) { + if (line.height == 0) return 0; + var len = line.text.length, merged, cur = line; + while (merged = collapsedSpanAtStart(cur)) { + var found = merged.find(0, true); + cur = found.from.line; + len += found.from.ch - found.to.ch; + } + cur = line; + while (merged = collapsedSpanAtEnd(cur)) { + var found = merged.find(0, true); + len -= cur.text.length - found.from.ch; + cur = found.to.line; + len += cur.text.length - found.to.ch; + } + return len; + } + + // Find the longest line in the document. + function findMaxLine(cm) { + var d = cm.display, doc = cm.doc; + d.maxLine = getLine(doc, doc.first); + d.maxLineLength = lineLength(d.maxLine); + d.maxLineChanged = true; + doc.iter(function(line) { + var len = lineLength(line); + if (len > d.maxLineLength) { + d.maxLineLength = len; + d.maxLine = line; + } + }); + } + + // Make sure the gutters options contains the element + // "CodeMirror-linenumbers" when the lineNumbers option is true. + function setGuttersForLineNumbers(options) { + var found = indexOf(options.gutters, "CodeMirror-linenumbers"); + if (found == -1 && options.lineNumbers) { + options.gutters = options.gutters.concat(["CodeMirror-linenumbers"]); + } else if (found > -1 && !options.lineNumbers) { + options.gutters = options.gutters.slice(0); + options.gutters.splice(found, 1); + } + } + + // SCROLLBARS + + // Prepare DOM reads needed to update the scrollbars. Done in one + // shot to minimize update/measure roundtrips. + function measureForScrollbars(cm) { + var d = cm.display, gutterW = d.gutters.offsetWidth; + var docH = Math.round(cm.doc.height + paddingVert(cm.display)); + return { + clientHeight: d.scroller.clientHeight, + viewHeight: d.wrapper.clientHeight, + scrollWidth: d.scroller.scrollWidth, clientWidth: d.scroller.clientWidth, + viewWidth: d.wrapper.clientWidth, + barLeft: cm.options.fixedGutter ? gutterW : 0, + docHeight: docH, + scrollHeight: docH + scrollGap(cm) + d.barHeight, + nativeBarWidth: d.nativeBarWidth, + gutterWidth: gutterW + }; + } + + function NativeScrollbars(place, scroll, cm) { + this.cm = cm; + var vert = this.vert = elt("div", [elt("div", null, null, "min-width: 1px")], "CodeMirror-vscrollbar"); + var horiz = this.horiz = elt("div", [elt("div", null, null, "height: 100%; min-height: 1px")], "CodeMirror-hscrollbar"); + place(vert); place(horiz); + + on(vert, "scroll", function() { + if (vert.clientHeight) scroll(vert.scrollTop, "vertical"); + }); + on(horiz, "scroll", function() { + if (horiz.clientWidth) scroll(horiz.scrollLeft, "horizontal"); + }); + + this.checkedOverlay = false; + // Need to set a minimum width to see the scrollbar on IE7 (but must not set it on IE8). + if (ie && ie_version < 8) this.horiz.style.minHeight = this.vert.style.minWidth = "18px"; + } + + NativeScrollbars.prototype = copyObj({ + update: function(measure) { + var needsH = measure.scrollWidth > measure.clientWidth + 1; + var needsV = measure.scrollHeight > measure.clientHeight + 1; + var sWidth = measure.nativeBarWidth; + + if (needsV) { + this.vert.style.display = "block"; + this.vert.style.bottom = needsH ? sWidth + "px" : "0"; + var totalHeight = measure.viewHeight - (needsH ? sWidth : 0); + // A bug in IE8 can cause this value to be negative, so guard it. + this.vert.firstChild.style.height = + Math.max(0, measure.scrollHeight - measure.clientHeight + totalHeight) + "px"; + } else { + this.vert.style.display = ""; + this.vert.firstChild.style.height = "0"; + } + + if (needsH) { + this.horiz.style.display = "block"; + this.horiz.style.right = needsV ? sWidth + "px" : "0"; + this.horiz.style.left = measure.barLeft + "px"; + var totalWidth = measure.viewWidth - measure.barLeft - (needsV ? sWidth : 0); + this.horiz.firstChild.style.width = + (measure.scrollWidth - measure.clientWidth + totalWidth) + "px"; + } else { + this.horiz.style.display = ""; + this.horiz.firstChild.style.width = "0"; + } + + if (!this.checkedOverlay && measure.clientHeight > 0) { + if (sWidth == 0) this.overlayHack(); + this.checkedOverlay = true; + } + + return {right: needsV ? sWidth : 0, bottom: needsH ? sWidth : 0}; + }, + setScrollLeft: function(pos) { + if (this.horiz.scrollLeft != pos) this.horiz.scrollLeft = pos; + }, + setScrollTop: function(pos) { + if (this.vert.scrollTop != pos) this.vert.scrollTop = pos; + }, + overlayHack: function() { + var w = mac && !mac_geMountainLion ? "12px" : "18px"; + this.horiz.style.minHeight = this.vert.style.minWidth = w; + var self = this; + var barMouseDown = function(e) { + if (e_target(e) != self.vert && e_target(e) != self.horiz) + operation(self.cm, onMouseDown)(e); + }; + on(this.vert, "mousedown", barMouseDown); + on(this.horiz, "mousedown", barMouseDown); + }, + clear: function() { + var parent = this.horiz.parentNode; + parent.removeChild(this.horiz); + parent.removeChild(this.vert); + } + }, NativeScrollbars.prototype); + + function NullScrollbars() {} + + NullScrollbars.prototype = copyObj({ + update: function() { return {bottom: 0, right: 0}; }, + setScrollLeft: function() {}, + setScrollTop: function() {}, + clear: function() {} + }, NullScrollbars.prototype); + + CodeMirror.scrollbarModel = {"native": NativeScrollbars, "null": NullScrollbars}; + + function initScrollbars(cm) { + if (cm.display.scrollbars) { + cm.display.scrollbars.clear(); + if (cm.display.scrollbars.addClass) + rmClass(cm.display.wrapper, cm.display.scrollbars.addClass); + } + + cm.display.scrollbars = new CodeMirror.scrollbarModel[cm.options.scrollbarStyle](function(node) { + cm.display.wrapper.insertBefore(node, cm.display.scrollbarFiller); + // Prevent clicks in the scrollbars from killing focus + on(node, "mousedown", function() { + if (cm.state.focused) setTimeout(function() { cm.display.input.focus(); }, 0); + }); + node.setAttribute("cm-not-content", "true"); + }, function(pos, axis) { + if (axis == "horizontal") setScrollLeft(cm, pos); + else setScrollTop(cm, pos); + }, cm); + if (cm.display.scrollbars.addClass) + addClass(cm.display.wrapper, cm.display.scrollbars.addClass); + } + + function updateScrollbars(cm, measure) { + if (!measure) measure = measureForScrollbars(cm); + var startWidth = cm.display.barWidth, startHeight = cm.display.barHeight; + updateScrollbarsInner(cm, measure); + for (var i = 0; i < 4 && startWidth != cm.display.barWidth || startHeight != cm.display.barHeight; i++) { + if (startWidth != cm.display.barWidth && cm.options.lineWrapping) + updateHeightsInViewport(cm); + updateScrollbarsInner(cm, measureForScrollbars(cm)); + startWidth = cm.display.barWidth; startHeight = cm.display.barHeight; + } + } + + // Re-synchronize the fake scrollbars with the actual size of the + // content. + function updateScrollbarsInner(cm, measure) { + var d = cm.display; + var sizes = d.scrollbars.update(measure); + + d.sizer.style.paddingRight = (d.barWidth = sizes.right) + "px"; + d.sizer.style.paddingBottom = (d.barHeight = sizes.bottom) + "px"; + + if (sizes.right && sizes.bottom) { + d.scrollbarFiller.style.display = "block"; + d.scrollbarFiller.style.height = sizes.bottom + "px"; + d.scrollbarFiller.style.width = sizes.right + "px"; + } else d.scrollbarFiller.style.display = ""; + if (sizes.bottom && cm.options.coverGutterNextToScrollbar && cm.options.fixedGutter) { + d.gutterFiller.style.display = "block"; + d.gutterFiller.style.height = sizes.bottom + "px"; + d.gutterFiller.style.width = measure.gutterWidth + "px"; + } else d.gutterFiller.style.display = ""; + } + + // Compute the lines that are visible in a given viewport (defaults + // the the current scroll position). viewport may contain top, + // height, and ensure (see op.scrollToPos) properties. + function visibleLines(display, doc, viewport) { + var top = viewport && viewport.top != null ? Math.max(0, viewport.top) : display.scroller.scrollTop; + top = Math.floor(top - paddingTop(display)); + var bottom = viewport && viewport.bottom != null ? viewport.bottom : top + display.wrapper.clientHeight; + + var from = lineAtHeight(doc, top), to = lineAtHeight(doc, bottom); + // Ensure is a {from: {line, ch}, to: {line, ch}} object, and + // forces those lines into the viewport (if possible). + if (viewport && viewport.ensure) { + var ensureFrom = viewport.ensure.from.line, ensureTo = viewport.ensure.to.line; + if (ensureFrom < from) { + from = ensureFrom; + to = lineAtHeight(doc, heightAtLine(getLine(doc, ensureFrom)) + display.wrapper.clientHeight); + } else if (Math.min(ensureTo, doc.lastLine()) >= to) { + from = lineAtHeight(doc, heightAtLine(getLine(doc, ensureTo)) - display.wrapper.clientHeight); + to = ensureTo; + } + } + return {from: from, to: Math.max(to, from + 1)}; + } + + // LINE NUMBERS + + // Re-align line numbers and gutter marks to compensate for + // horizontal scrolling. + function alignHorizontally(cm) { + var display = cm.display, view = display.view; + if (!display.alignWidgets && (!display.gutters.firstChild || !cm.options.fixedGutter)) return; + var comp = compensateForHScroll(display) - display.scroller.scrollLeft + cm.doc.scrollLeft; + var gutterW = display.gutters.offsetWidth, left = comp + "px"; + for (var i = 0; i < view.length; i++) if (!view[i].hidden) { + if (cm.options.fixedGutter && view[i].gutter) + view[i].gutter.style.left = left; + var align = view[i].alignable; + if (align) for (var j = 0; j < align.length; j++) + align[j].style.left = left; + } + if (cm.options.fixedGutter) + display.gutters.style.left = (comp + gutterW) + "px"; + } + + // Used to ensure that the line number gutter is still the right + // size for the current document size. Returns true when an update + // is needed. + function maybeUpdateLineNumberWidth(cm) { + if (!cm.options.lineNumbers) return false; + var doc = cm.doc, last = lineNumberFor(cm.options, doc.first + doc.size - 1), display = cm.display; + if (last.length != display.lineNumChars) { + var test = display.measure.appendChild(elt("div", [elt("div", last)], + "CodeMirror-linenumber CodeMirror-gutter-elt")); + var innerW = test.firstChild.offsetWidth, padding = test.offsetWidth - innerW; + display.lineGutter.style.width = ""; + display.lineNumInnerWidth = Math.max(innerW, display.lineGutter.offsetWidth - padding) + 1; + display.lineNumWidth = display.lineNumInnerWidth + padding; + display.lineNumChars = display.lineNumInnerWidth ? last.length : -1; + display.lineGutter.style.width = display.lineNumWidth + "px"; + updateGutterSpace(cm); + return true; + } + return false; + } + + function lineNumberFor(options, i) { + return String(options.lineNumberFormatter(i + options.firstLineNumber)); + } + + // Computes display.scroller.scrollLeft + display.gutters.offsetWidth, + // but using getBoundingClientRect to get a sub-pixel-accurate + // result. + function compensateForHScroll(display) { + return display.scroller.getBoundingClientRect().left - display.sizer.getBoundingClientRect().left; + } + + // DISPLAY DRAWING + + function DisplayUpdate(cm, viewport, force) { + var display = cm.display; + + this.viewport = viewport; + // Store some values that we'll need later (but don't want to force a relayout for) + this.visible = visibleLines(display, cm.doc, viewport); + this.editorIsHidden = !display.wrapper.offsetWidth; + this.wrapperHeight = display.wrapper.clientHeight; + this.wrapperWidth = display.wrapper.clientWidth; + this.oldDisplayWidth = displayWidth(cm); + this.force = force; + this.dims = getDimensions(cm); + this.events = []; + } + + DisplayUpdate.prototype.signal = function(emitter, type) { + if (hasHandler(emitter, type)) + this.events.push(arguments); + }; + DisplayUpdate.prototype.finish = function() { + for (var i = 0; i < this.events.length; i++) + signal.apply(null, this.events[i]); + }; + + function maybeClipScrollbars(cm) { + var display = cm.display; + if (!display.scrollbarsClipped && display.scroller.offsetWidth) { + display.nativeBarWidth = display.scroller.offsetWidth - display.scroller.clientWidth; + display.heightForcer.style.height = scrollGap(cm) + "px"; + display.sizer.style.marginBottom = -display.nativeBarWidth + "px"; + display.sizer.style.borderRightWidth = scrollGap(cm) + "px"; + display.scrollbarsClipped = true; + } + } + + // Does the actual updating of the line display. Bails out + // (returning false) when there is nothing to be done and forced is + // false. + function updateDisplayIfNeeded(cm, update) { + var display = cm.display, doc = cm.doc; + + if (update.editorIsHidden) { + resetView(cm); + return false; + } + + // Bail out if the visible area is already rendered and nothing changed. + if (!update.force && + update.visible.from >= display.viewFrom && update.visible.to <= display.viewTo && + (display.updateLineNumbers == null || display.updateLineNumbers >= display.viewTo) && + display.renderedView == display.view && countDirtyView(cm) == 0) + return false; + + if (maybeUpdateLineNumberWidth(cm)) { + resetView(cm); + update.dims = getDimensions(cm); + } + + // Compute a suitable new viewport (from & to) + var end = doc.first + doc.size; + var from = Math.max(update.visible.from - cm.options.viewportMargin, doc.first); + var to = Math.min(end, update.visible.to + cm.options.viewportMargin); + if (display.viewFrom < from && from - display.viewFrom < 20) from = Math.max(doc.first, display.viewFrom); + if (display.viewTo > to && display.viewTo - to < 20) to = Math.min(end, display.viewTo); + if (sawCollapsedSpans) { + from = visualLineNo(cm.doc, from); + to = visualLineEndNo(cm.doc, to); + } + + var different = from != display.viewFrom || to != display.viewTo || + display.lastWrapHeight != update.wrapperHeight || display.lastWrapWidth != update.wrapperWidth; + adjustView(cm, from, to); + + display.viewOffset = heightAtLine(getLine(cm.doc, display.viewFrom)); + // Position the mover div to align with the current scroll position + cm.display.mover.style.top = display.viewOffset + "px"; + + var toUpdate = countDirtyView(cm); + if (!different && toUpdate == 0 && !update.force && display.renderedView == display.view && + (display.updateLineNumbers == null || display.updateLineNumbers >= display.viewTo)) + return false; + + // For big changes, we hide the enclosing element during the + // update, since that speeds up the operations on most browsers. + var focused = activeElt(); + if (toUpdate > 4) display.lineDiv.style.display = "none"; + patchDisplay(cm, display.updateLineNumbers, update.dims); + if (toUpdate > 4) display.lineDiv.style.display = ""; + display.renderedView = display.view; + // There might have been a widget with a focused element that got + // hidden or updated, if so re-focus it. + if (focused && activeElt() != focused && focused.offsetHeight) focused.focus(); + + // Prevent selection and cursors from interfering with the scroll + // width and height. + removeChildren(display.cursorDiv); + removeChildren(display.selectionDiv); + display.gutters.style.height = 0; + + if (different) { + display.lastWrapHeight = update.wrapperHeight; + display.lastWrapWidth = update.wrapperWidth; + startWorker(cm, 400); + } + + display.updateLineNumbers = null; + + return true; + } + + function postUpdateDisplay(cm, update) { + var force = update.force, viewport = update.viewport; + for (var first = true;; first = false) { + if (first && cm.options.lineWrapping && update.oldDisplayWidth != displayWidth(cm)) { + force = true; + } else { + force = false; + // Clip forced viewport to actual scrollable area. + if (viewport && viewport.top != null) + viewport = {top: Math.min(cm.doc.height + paddingVert(cm.display) - displayHeight(cm), viewport.top)}; + // Updated line heights might result in the drawn area not + // actually covering the viewport. Keep looping until it does. + update.visible = visibleLines(cm.display, cm.doc, viewport); + if (update.visible.from >= cm.display.viewFrom && update.visible.to <= cm.display.viewTo) + break; + } + if (!updateDisplayIfNeeded(cm, update)) break; + updateHeightsInViewport(cm); + var barMeasure = measureForScrollbars(cm); + updateSelection(cm); + setDocumentHeight(cm, barMeasure); + updateScrollbars(cm, barMeasure); + } + + update.signal(cm, "update", cm); + if (cm.display.viewFrom != cm.display.reportedViewFrom || cm.display.viewTo != cm.display.reportedViewTo) { + update.signal(cm, "viewportChange", cm, cm.display.viewFrom, cm.display.viewTo); + cm.display.reportedViewFrom = cm.display.viewFrom; cm.display.reportedViewTo = cm.display.viewTo; + } + } + + function updateDisplaySimple(cm, viewport) { + var update = new DisplayUpdate(cm, viewport); + if (updateDisplayIfNeeded(cm, update)) { + updateHeightsInViewport(cm); + postUpdateDisplay(cm, update); + var barMeasure = measureForScrollbars(cm); + updateSelection(cm); + setDocumentHeight(cm, barMeasure); + updateScrollbars(cm, barMeasure); + update.finish(); + } + } + + function setDocumentHeight(cm, measure) { + cm.display.sizer.style.minHeight = measure.docHeight + "px"; + var total = measure.docHeight + cm.display.barHeight; + cm.display.heightForcer.style.top = total + "px"; + cm.display.gutters.style.height = Math.max(total + scrollGap(cm), measure.clientHeight) + "px"; + } + + // Read the actual heights of the rendered lines, and update their + // stored heights to match. + function updateHeightsInViewport(cm) { + var display = cm.display; + var prevBottom = display.lineDiv.offsetTop; + for (var i = 0; i < display.view.length; i++) { + var cur = display.view[i], height; + if (cur.hidden) continue; + if (ie && ie_version < 8) { + var bot = cur.node.offsetTop + cur.node.offsetHeight; + height = bot - prevBottom; + prevBottom = bot; + } else { + var box = cur.node.getBoundingClientRect(); + height = box.bottom - box.top; + } + var diff = cur.line.height - height; + if (height < 2) height = textHeight(display); + if (diff > .001 || diff < -.001) { + updateLineHeight(cur.line, height); + updateWidgetHeight(cur.line); + if (cur.rest) for (var j = 0; j < cur.rest.length; j++) + updateWidgetHeight(cur.rest[j]); + } + } + } + + // Read and store the height of line widgets associated with the + // given line. + function updateWidgetHeight(line) { + if (line.widgets) for (var i = 0; i < line.widgets.length; ++i) + line.widgets[i].height = line.widgets[i].node.offsetHeight; + } + + // Do a bulk-read of the DOM positions and sizes needed to draw the + // view, so that we don't interleave reading and writing to the DOM. + function getDimensions(cm) { + var d = cm.display, left = {}, width = {}; + var gutterLeft = d.gutters.clientLeft; + for (var n = d.gutters.firstChild, i = 0; n; n = n.nextSibling, ++i) { + left[cm.options.gutters[i]] = n.offsetLeft + n.clientLeft + gutterLeft; + width[cm.options.gutters[i]] = n.clientWidth; + } + return {fixedPos: compensateForHScroll(d), + gutterTotalWidth: d.gutters.offsetWidth, + gutterLeft: left, + gutterWidth: width, + wrapperWidth: d.wrapper.clientWidth}; + } + + // Sync the actual display DOM structure with display.view, removing + // nodes for lines that are no longer in view, and creating the ones + // that are not there yet, and updating the ones that are out of + // date. + function patchDisplay(cm, updateNumbersFrom, dims) { + var display = cm.display, lineNumbers = cm.options.lineNumbers; + var container = display.lineDiv, cur = container.firstChild; + + function rm(node) { + var next = node.nextSibling; + // Works around a throw-scroll bug in OS X Webkit + if (webkit && mac && cm.display.currentWheelTarget == node) + node.style.display = "none"; + else + node.parentNode.removeChild(node); + return next; + } + + var view = display.view, lineN = display.viewFrom; + // Loop over the elements in the view, syncing cur (the DOM nodes + // in display.lineDiv) with the view as we go. + for (var i = 0; i < view.length; i++) { + var lineView = view[i]; + if (lineView.hidden) { + } else if (!lineView.node || lineView.node.parentNode != container) { // Not drawn yet + var node = buildLineElement(cm, lineView, lineN, dims); + container.insertBefore(node, cur); + } else { // Already drawn + while (cur != lineView.node) cur = rm(cur); + var updateNumber = lineNumbers && updateNumbersFrom != null && + updateNumbersFrom <= lineN && lineView.lineNumber; + if (lineView.changes) { + if (indexOf(lineView.changes, "gutter") > -1) updateNumber = false; + updateLineForChanges(cm, lineView, lineN, dims); + } + if (updateNumber) { + removeChildren(lineView.lineNumber); + lineView.lineNumber.appendChild(document.createTextNode(lineNumberFor(cm.options, lineN))); + } + cur = lineView.node.nextSibling; + } + lineN += lineView.size; + } + while (cur) cur = rm(cur); + } + + // When an aspect of a line changes, a string is added to + // lineView.changes. This updates the relevant part of the line's + // DOM structure. + function updateLineForChanges(cm, lineView, lineN, dims) { + for (var j = 0; j < lineView.changes.length; j++) { + var type = lineView.changes[j]; + if (type == "text") updateLineText(cm, lineView); + else if (type == "gutter") updateLineGutter(cm, lineView, lineN, dims); + else if (type == "class") updateLineClasses(lineView); + else if (type == "widget") updateLineWidgets(cm, lineView, dims); + } + lineView.changes = null; + } + + // Lines with gutter elements, widgets or a background class need to + // be wrapped, and have the extra elements added to the wrapper div + function ensureLineWrapped(lineView) { + if (lineView.node == lineView.text) { + lineView.node = elt("div", null, null, "position: relative"); + if (lineView.text.parentNode) + lineView.text.parentNode.replaceChild(lineView.node, lineView.text); + lineView.node.appendChild(lineView.text); + if (ie && ie_version < 8) lineView.node.style.zIndex = 2; + } + return lineView.node; + } + + function updateLineBackground(lineView) { + var cls = lineView.bgClass ? lineView.bgClass + " " + (lineView.line.bgClass || "") : lineView.line.bgClass; + if (cls) cls += " CodeMirror-linebackground"; + if (lineView.background) { + if (cls) lineView.background.className = cls; + else { lineView.background.parentNode.removeChild(lineView.background); lineView.background = null; } + } else if (cls) { + var wrap = ensureLineWrapped(lineView); + lineView.background = wrap.insertBefore(elt("div", null, cls), wrap.firstChild); + } + } + + // Wrapper around buildLineContent which will reuse the structure + // in display.externalMeasured when possible. + function getLineContent(cm, lineView) { + var ext = cm.display.externalMeasured; + if (ext && ext.line == lineView.line) { + cm.display.externalMeasured = null; + lineView.measure = ext.measure; + return ext.built; + } + return buildLineContent(cm, lineView); + } + + // Redraw the line's text. Interacts with the background and text + // classes because the mode may output tokens that influence these + // classes. + function updateLineText(cm, lineView) { + var cls = lineView.text.className; + var built = getLineContent(cm, lineView); + if (lineView.text == lineView.node) lineView.node = built.pre; + lineView.text.parentNode.replaceChild(built.pre, lineView.text); + lineView.text = built.pre; + if (built.bgClass != lineView.bgClass || built.textClass != lineView.textClass) { + lineView.bgClass = built.bgClass; + lineView.textClass = built.textClass; + updateLineClasses(lineView); + } else if (cls) { + lineView.text.className = cls; + } + } + + function updateLineClasses(lineView) { + updateLineBackground(lineView); + if (lineView.line.wrapClass) + ensureLineWrapped(lineView).className = lineView.line.wrapClass; + else if (lineView.node != lineView.text) + lineView.node.className = ""; + var textClass = lineView.textClass ? lineView.textClass + " " + (lineView.line.textClass || "") : lineView.line.textClass; + lineView.text.className = textClass || ""; + } + + function updateLineGutter(cm, lineView, lineN, dims) { + if (lineView.gutter) { + lineView.node.removeChild(lineView.gutter); + lineView.gutter = null; + } + var markers = lineView.line.gutterMarkers; + if (cm.options.lineNumbers || markers) { + var wrap = ensureLineWrapped(lineView); + var gutterWrap = lineView.gutter = elt("div", null, "CodeMirror-gutter-wrapper", "left: " + + (cm.options.fixedGutter ? dims.fixedPos : -dims.gutterTotalWidth) + + "px; width: " + dims.gutterTotalWidth + "px"); + cm.display.input.setUneditable(gutterWrap); + wrap.insertBefore(gutterWrap, lineView.text); + if (lineView.line.gutterClass) + gutterWrap.className += " " + lineView.line.gutterClass; + if (cm.options.lineNumbers && (!markers || !markers["CodeMirror-linenumbers"])) + lineView.lineNumber = gutterWrap.appendChild( + elt("div", lineNumberFor(cm.options, lineN), + "CodeMirror-linenumber CodeMirror-gutter-elt", + "left: " + dims.gutterLeft["CodeMirror-linenumbers"] + "px; width: " + + cm.display.lineNumInnerWidth + "px")); + if (markers) for (var k = 0; k < cm.options.gutters.length; ++k) { + var id = cm.options.gutters[k], found = markers.hasOwnProperty(id) && markers[id]; + if (found) + gutterWrap.appendChild(elt("div", [found], "CodeMirror-gutter-elt", "left: " + + dims.gutterLeft[id] + "px; width: " + dims.gutterWidth[id] + "px")); + } + } + } + + function updateLineWidgets(cm, lineView, dims) { + if (lineView.alignable) lineView.alignable = null; + for (var node = lineView.node.firstChild, next; node; node = next) { + var next = node.nextSibling; + if (node.className == "CodeMirror-linewidget") + lineView.node.removeChild(node); + } + insertLineWidgets(cm, lineView, dims); + } + + // Build a line's DOM representation from scratch + function buildLineElement(cm, lineView, lineN, dims) { + var built = getLineContent(cm, lineView); + lineView.text = lineView.node = built.pre; + if (built.bgClass) lineView.bgClass = built.bgClass; + if (built.textClass) lineView.textClass = built.textClass; + + updateLineClasses(lineView); + updateLineGutter(cm, lineView, lineN, dims); + insertLineWidgets(cm, lineView, dims); + return lineView.node; + } + + // A lineView may contain multiple logical lines (when merged by + // collapsed spans). The widgets for all of them need to be drawn. + function insertLineWidgets(cm, lineView, dims) { + insertLineWidgetsFor(cm, lineView.line, lineView, dims, true); + if (lineView.rest) for (var i = 0; i < lineView.rest.length; i++) + insertLineWidgetsFor(cm, lineView.rest[i], lineView, dims, false); + } + + function insertLineWidgetsFor(cm, line, lineView, dims, allowAbove) { + if (!line.widgets) return; + var wrap = ensureLineWrapped(lineView); + for (var i = 0, ws = line.widgets; i < ws.length; ++i) { + var widget = ws[i], node = elt("div", [widget.node], "CodeMirror-linewidget"); + if (!widget.handleMouseEvents) node.setAttribute("cm-ignore-events", "true"); + positionLineWidget(widget, node, lineView, dims); + cm.display.input.setUneditable(node); + if (allowAbove && widget.above) + wrap.insertBefore(node, lineView.gutter || lineView.text); + else + wrap.appendChild(node); + signalLater(widget, "redraw"); + } + } + + function positionLineWidget(widget, node, lineView, dims) { + if (widget.noHScroll) { + (lineView.alignable || (lineView.alignable = [])).push(node); + var width = dims.wrapperWidth; + node.style.left = dims.fixedPos + "px"; + if (!widget.coverGutter) { + width -= dims.gutterTotalWidth; + node.style.paddingLeft = dims.gutterTotalWidth + "px"; + } + node.style.width = width + "px"; + } + if (widget.coverGutter) { + node.style.zIndex = 5; + node.style.position = "relative"; + if (!widget.noHScroll) node.style.marginLeft = -dims.gutterTotalWidth + "px"; + } + } + + // POSITION OBJECT + + // A Pos instance represents a position within the text. + var Pos = CodeMirror.Pos = function(line, ch) { + if (!(this instanceof Pos)) return new Pos(line, ch); + this.line = line; this.ch = ch; + }; + + // Compare two positions, return 0 if they are the same, a negative + // number when a is less, and a positive number otherwise. + var cmp = CodeMirror.cmpPos = function(a, b) { return a.line - b.line || a.ch - b.ch; }; + + function copyPos(x) {return Pos(x.line, x.ch);} + function maxPos(a, b) { return cmp(a, b) < 0 ? b : a; } + function minPos(a, b) { return cmp(a, b) < 0 ? a : b; } + + // INPUT HANDLING + + function ensureFocus(cm) { + if (!cm.state.focused) { cm.display.input.focus(); onFocus(cm); } + } + + function isReadOnly(cm) { + return cm.options.readOnly || cm.doc.cantEdit; + } + + // This will be set to an array of strings when copying, so that, + // when pasting, we know what kind of selections the copied text + // was made out of. + var lastCopied = null; + + function applyTextInput(cm, inserted, deleted, sel) { + var doc = cm.doc; + cm.display.shift = false; + if (!sel) sel = doc.sel; + + var textLines = splitLines(inserted), multiPaste = null; + // When pasing N lines into N selections, insert one line per selection + if (cm.state.pasteIncoming && sel.ranges.length > 1) { + if (lastCopied && lastCopied.join("\n") == inserted) + multiPaste = sel.ranges.length % lastCopied.length == 0 && map(lastCopied, splitLines); + else if (textLines.length == sel.ranges.length) + multiPaste = map(textLines, function(l) { return [l]; }); + } + + // Normal behavior is to insert the new text into every selection + for (var i = sel.ranges.length - 1; i >= 0; i--) { + var range = sel.ranges[i]; + var from = range.from(), to = range.to(); + if (range.empty()) { + if (deleted && deleted > 0) // Handle deletion + from = Pos(from.line, from.ch - deleted); + else if (cm.state.overwrite && !cm.state.pasteIncoming) // Handle overwrite + to = Pos(to.line, Math.min(getLine(doc, to.line).text.length, to.ch + lst(textLines).length)); + } + var updateInput = cm.curOp.updateInput; + var changeEvent = {from: from, to: to, text: multiPaste ? multiPaste[i % multiPaste.length] : textLines, + origin: cm.state.pasteIncoming ? "paste" : cm.state.cutIncoming ? "cut" : "+input"}; + makeChange(cm.doc, changeEvent); + signalLater(cm, "inputRead", cm, changeEvent); + // When an 'electric' character is inserted, immediately trigger a reindent + if (inserted && !cm.state.pasteIncoming && cm.options.electricChars && + cm.options.smartIndent && range.head.ch < 100 && + (!i || sel.ranges[i - 1].head.line != range.head.line)) { + var mode = cm.getModeAt(range.head); + var end = changeEnd(changeEvent); + if (mode.electricChars) { + for (var j = 0; j < mode.electricChars.length; j++) + if (inserted.indexOf(mode.electricChars.charAt(j)) > -1) { + indentLine(cm, end.line, "smart"); + break; + } + } else if (mode.electricInput) { + if (mode.electricInput.test(getLine(doc, end.line).text.slice(0, end.ch))) + indentLine(cm, end.line, "smart"); + } + } + } + ensureCursorVisible(cm); + cm.curOp.updateInput = updateInput; + cm.curOp.typing = true; + cm.state.pasteIncoming = cm.state.cutIncoming = false; + } + + function copyableRanges(cm) { + var text = [], ranges = []; + for (var i = 0; i < cm.doc.sel.ranges.length; i++) { + var line = cm.doc.sel.ranges[i].head.line; + var lineRange = {anchor: Pos(line, 0), head: Pos(line + 1, 0)}; + ranges.push(lineRange); + text.push(cm.getRange(lineRange.anchor, lineRange.head)); + } + return {text: text, ranges: ranges}; + } + + function disableBrowserMagic(field) { + field.setAttribute("autocorrect", "off"); + field.setAttribute("autocapitalize", "off"); + field.setAttribute("spellcheck", "false"); + } + + // TEXTAREA INPUT STYLE + + function TextareaInput(cm) { + this.cm = cm; + // See input.poll and input.reset + this.prevInput = ""; + + // Flag that indicates whether we expect input to appear real soon + // now (after some event like 'keypress' or 'input') and are + // polling intensively. + this.pollingFast = false; + // Self-resetting timeout for the poller + this.polling = new Delayed(); + // Tracks when input.reset has punted to just putting a short + // string into the textarea instead of the full selection. + this.inaccurateSelection = false; + // Used to work around IE issue with selection being forgotten when focus moves away from textarea + this.hasSelection = false; + }; + + function hiddenTextarea() { + var te = elt("textarea", null, null, "position: absolute; padding: 0; width: 1px; height: 1em; outline: none"); + var div = elt("div", [te], null, "overflow: hidden; position: relative; width: 3px; height: 0px;"); + // The textarea is kept positioned near the cursor to prevent the + // fact that it'll be scrolled into view on input from scrolling + // our fake cursor out of view. On webkit, when wrap=off, paste is + // very slow. So make the area wide instead. + if (webkit) te.style.width = "1000px"; + else te.setAttribute("wrap", "off"); + // If border: 0; -- iOS fails to open keyboard (issue #1287) + if (ios) te.style.border = "1px solid black"; + disableBrowserMagic(te); + return div; + } + + TextareaInput.prototype = copyObj({ + init: function(display) { + var input = this, cm = this.cm; + + // Wraps and hides input textarea + var div = this.wrapper = hiddenTextarea(); + // The semihidden textarea that is focused when the editor is + // focused, and receives input. + var te = this.textarea = div.firstChild; + display.wrapper.insertBefore(div, display.wrapper.firstChild); + + // Needed to hide big blue blinking cursor on Mobile Safari (doesn't seem to work in iOS 8 anymore) + if (ios) te.style.width = "0px"; + + on(te, "input", function() { + if (ie && ie_version >= 9 && input.hasSelection) input.hasSelection = null; + input.poll(); + }); + + on(te, "paste", function() { + // Workaround for webkit bug https://bugs.webkit.org/show_bug.cgi?id=90206 + // Add a char to the end of textarea before paste occur so that + // selection doesn't span to the end of textarea. + if (webkit && !cm.state.fakedLastChar && !(new Date - cm.state.lastMiddleDown < 200)) { + var start = te.selectionStart, end = te.selectionEnd; + te.value += "$"; + // The selection end needs to be set before the start, otherwise there + // can be an intermediate non-empty selection between the two, which + // can override the middle-click paste buffer on linux and cause the + // wrong thing to get pasted. + te.selectionEnd = end; + te.selectionStart = start; + cm.state.fakedLastChar = true; + } + cm.state.pasteIncoming = true; + input.fastPoll(); + }); + + function prepareCopyCut(e) { + if (cm.somethingSelected()) { + lastCopied = cm.getSelections(); + if (input.inaccurateSelection) { + input.prevInput = ""; + input.inaccurateSelection = false; + te.value = lastCopied.join("\n"); + selectInput(te); + } + } else if (!cm.options.lineWiseCopyCut) { + return; + } else { + var ranges = copyableRanges(cm); + lastCopied = ranges.text; + if (e.type == "cut") { + cm.setSelections(ranges.ranges, null, sel_dontScroll); + } else { + input.prevInput = ""; + te.value = ranges.text.join("\n"); + selectInput(te); + } + } + if (e.type == "cut") cm.state.cutIncoming = true; + } + on(te, "cut", prepareCopyCut); + on(te, "copy", prepareCopyCut); + + on(display.scroller, "paste", function(e) { + if (eventInWidget(display, e)) return; + cm.state.pasteIncoming = true; + input.focus(); + }); + + // Prevent normal selection in the editor (we handle our own) + on(display.lineSpace, "selectstart", function(e) { + if (!eventInWidget(display, e)) e_preventDefault(e); + }); + }, + + prepareSelection: function() { + // Redraw the selection and/or cursor + var cm = this.cm, display = cm.display, doc = cm.doc; + var result = prepareSelection(cm); + + // Move the hidden textarea near the cursor to prevent scrolling artifacts + if (cm.options.moveInputWithCursor) { + var headPos = cursorCoords(cm, doc.sel.primary().head, "div"); + var wrapOff = display.wrapper.getBoundingClientRect(), lineOff = display.lineDiv.getBoundingClientRect(); + result.teTop = Math.max(0, Math.min(display.wrapper.clientHeight - 10, + headPos.top + lineOff.top - wrapOff.top)); + result.teLeft = Math.max(0, Math.min(display.wrapper.clientWidth - 10, + headPos.left + lineOff.left - wrapOff.left)); + } + + return result; + }, + + showSelection: function(drawn) { + var cm = this.cm, display = cm.display; + removeChildrenAndAdd(display.cursorDiv, drawn.cursors); + removeChildrenAndAdd(display.selectionDiv, drawn.selection); + if (drawn.teTop != null) { + this.wrapper.style.top = drawn.teTop + "px"; + this.wrapper.style.left = drawn.teLeft + "px"; + } + }, + + // Reset the input to correspond to the selection (or to be empty, + // when not typing and nothing is selected) + reset: function(typing) { + if (this.contextMenuPending) return; + var minimal, selected, cm = this.cm, doc = cm.doc; + if (cm.somethingSelected()) { + this.prevInput = ""; + var range = doc.sel.primary(); + minimal = hasCopyEvent && + (range.to().line - range.from().line > 100 || (selected = cm.getSelection()).length > 1000); + var content = minimal ? "-" : selected || cm.getSelection(); + this.textarea.value = content; + if (cm.state.focused) selectInput(this.textarea); + if (ie && ie_version >= 9) this.hasSelection = content; + } else if (!typing) { + this.prevInput = this.textarea.value = ""; + if (ie && ie_version >= 9) this.hasSelection = null; + } + this.inaccurateSelection = minimal; + }, + + getField: function() { return this.textarea; }, + + supportsTouch: function() { return false; }, + + focus: function() { + if (this.cm.options.readOnly != "nocursor" && (!mobile || activeElt() != this.textarea)) { + try { this.textarea.focus(); } + catch (e) {} // IE8 will throw if the textarea is display: none or not in DOM + } + }, + + blur: function() { this.textarea.blur(); }, + + resetPosition: function() { + this.wrapper.style.top = this.wrapper.style.left = 0; + }, + + receivedFocus: function() { this.slowPoll(); }, + + // Poll for input changes, using the normal rate of polling. This + // runs as long as the editor is focused. + slowPoll: function() { + var input = this; + if (input.pollingFast) return; + input.polling.set(this.cm.options.pollInterval, function() { + input.poll(); + if (input.cm.state.focused) input.slowPoll(); + }); + }, + + // When an event has just come in that is likely to add or change + // something in the input textarea, we poll faster, to ensure that + // the change appears on the screen quickly. + fastPoll: function() { + var missed = false, input = this; + input.pollingFast = true; + function p() { + var changed = input.poll(); + if (!changed && !missed) {missed = true; input.polling.set(60, p);} + else {input.pollingFast = false; input.slowPoll();} + } + input.polling.set(20, p); + }, + + // Read input from the textarea, and update the document to match. + // When something is selected, it is present in the textarea, and + // selected (unless it is huge, in which case a placeholder is + // used). When nothing is selected, the cursor sits after previously + // seen text (can be empty), which is stored in prevInput (we must + // not reset the textarea when typing, because that breaks IME). + poll: function() { + var cm = this.cm, input = this.textarea, prevInput = this.prevInput; + // Since this is called a *lot*, try to bail out as cheaply as + // possible when it is clear that nothing happened. hasSelection + // will be the case when there is a lot of text in the textarea, + // in which case reading its value would be expensive. + if (!cm.state.focused || (hasSelection(input) && !prevInput) || + isReadOnly(cm) || cm.options.disableInput || cm.state.keySeq) + return false; + // See paste handler for more on the fakedLastChar kludge + if (cm.state.pasteIncoming && cm.state.fakedLastChar) { + input.value = input.value.substring(0, input.value.length - 1); + cm.state.fakedLastChar = false; + } + var text = input.value; + // If nothing changed, bail. + if (text == prevInput && !cm.somethingSelected()) return false; + // Work around nonsensical selection resetting in IE9/10, and + // inexplicable appearance of private area unicode characters on + // some key combos in Mac (#2689). + if (ie && ie_version >= 9 && this.hasSelection === text || + mac && /[\uf700-\uf7ff]/.test(text)) { + cm.display.input.reset(); + return false; + } + + if (cm.doc.sel == cm.display.selForContextMenu) { + var first = text.charCodeAt(0); + if (first == 0x200b && !prevInput) prevInput = "\u200b"; + if (first == 0x21da) { this.reset(); return this.cm.execCommand("undo"); } + } + // Find the part of the input that is actually new + var same = 0, l = Math.min(prevInput.length, text.length); + while (same < l && prevInput.charCodeAt(same) == text.charCodeAt(same)) ++same; + + var self = this; + runInOp(cm, function() { + applyTextInput(cm, text.slice(same), prevInput.length - same); + + // Don't leave long text in the textarea, since it makes further polling slow + if (text.length > 1000 || text.indexOf("\n") > -1) input.value = self.prevInput = ""; + else self.prevInput = text; + }); + return true; + }, + + ensurePolled: function() { + if (this.pollingFast && this.poll()) this.pollingFast = false; + }, + + onKeyPress: function() { + if (ie && ie_version >= 9) this.hasSelection = null; + this.fastPoll(); + }, + + onContextMenu: function(e) { + var input = this, cm = input.cm, display = cm.display, te = input.textarea; + var pos = posFromMouse(cm, e), scrollPos = display.scroller.scrollTop; + if (!pos || presto) return; // Opera is difficult. + + // Reset the current text selection only if the click is done outside of the selection + // and 'resetSelectionOnContextMenu' option is true. + var reset = cm.options.resetSelectionOnContextMenu; + if (reset && cm.doc.sel.contains(pos) == -1) + operation(cm, setSelection)(cm.doc, simpleSelection(pos), sel_dontScroll); + + var oldCSS = te.style.cssText; + input.wrapper.style.position = "absolute"; + te.style.cssText = "position: fixed; width: 30px; height: 30px; top: " + (e.clientY - 5) + + "px; left: " + (e.clientX - 5) + "px; z-index: 1000; background: " + + (ie ? "rgba(255, 255, 255, .05)" : "transparent") + + "; outline: none; border-width: 0; outline: none; overflow: hidden; opacity: .05; filter: alpha(opacity=5);"; + if (webkit) var oldScrollY = window.scrollY; // Work around Chrome issue (#2712) + display.input.focus(); + if (webkit) window.scrollTo(null, oldScrollY); + display.input.reset(); + // Adds "Select all" to context menu in FF + if (!cm.somethingSelected()) te.value = input.prevInput = " "; + input.contextMenuPending = true; + display.selForContextMenu = cm.doc.sel; + clearTimeout(display.detectingSelectAll); + + // Select-all will be greyed out if there's nothing to select, so + // this adds a zero-width space so that we can later check whether + // it got selected. + function prepareSelectAllHack() { + if (te.selectionStart != null) { + var selected = cm.somethingSelected(); + var extval = "\u200b" + (selected ? te.value : ""); + te.value = "\u21da"; // Used to catch context-menu undo + te.value = extval; + input.prevInput = selected ? "" : "\u200b"; + te.selectionStart = 1; te.selectionEnd = extval.length; + // Re-set this, in case some other handler touched the + // selection in the meantime. + display.selForContextMenu = cm.doc.sel; + } + } + function rehide() { + input.contextMenuPending = false; + input.wrapper.style.position = "relative"; + te.style.cssText = oldCSS; + if (ie && ie_version < 9) display.scrollbars.setScrollTop(display.scroller.scrollTop = scrollPos); + + // Try to detect the user choosing select-all + if (te.selectionStart != null) { + if (!ie || (ie && ie_version < 9)) prepareSelectAllHack(); + var i = 0, poll = function() { + if (display.selForContextMenu == cm.doc.sel && te.selectionStart == 0 && + te.selectionEnd > 0 && input.prevInput == "\u200b") + operation(cm, commands.selectAll)(cm); + else if (i++ < 10) display.detectingSelectAll = setTimeout(poll, 500); + else display.input.reset(); + }; + display.detectingSelectAll = setTimeout(poll, 200); + } + } + + if (ie && ie_version >= 9) prepareSelectAllHack(); + if (captureRightClick) { + e_stop(e); + var mouseup = function() { + off(window, "mouseup", mouseup); + setTimeout(rehide, 20); + }; + on(window, "mouseup", mouseup); + } else { + setTimeout(rehide, 50); + } + }, + + setUneditable: nothing, + + needsContentAttribute: false + }, TextareaInput.prototype); + + // CONTENTEDITABLE INPUT STYLE + + function ContentEditableInput(cm) { + this.cm = cm; + this.lastAnchorNode = this.lastAnchorOffset = this.lastFocusNode = this.lastFocusOffset = null; + this.polling = new Delayed(); + this.gracePeriod = false; + } + + ContentEditableInput.prototype = copyObj({ + init: function(display) { + var input = this, cm = input.cm; + var div = input.div = display.lineDiv; + div.contentEditable = "true"; + disableBrowserMagic(div); + + on(div, "paste", function(e) { + var pasted = e.clipboardData && e.clipboardData.getData("text/plain"); + if (pasted) { + e.preventDefault(); + cm.replaceSelection(pasted, null, "paste"); + } + }); + + on(div, "compositionstart", function(e) { + var data = e.data; + input.composing = {sel: cm.doc.sel, data: data, startData: data}; + if (!data) return; + var prim = cm.doc.sel.primary(); + var line = cm.getLine(prim.head.line); + var found = line.indexOf(data, Math.max(0, prim.head.ch - data.length)); + if (found > -1 && found <= prim.head.ch) + input.composing.sel = simpleSelection(Pos(prim.head.line, found), + Pos(prim.head.line, found + data.length)); + }); + on(div, "compositionupdate", function(e) { + input.composing.data = e.data; + }); + on(div, "compositionend", function(e) { + var ours = input.composing; + if (!ours) return; + if (e.data != ours.startData && !/\u200b/.test(e.data)) + ours.data = e.data; + // Need a small delay to prevent other code (input event, + // selection polling) from doing damage when fired right after + // compositionend. + setTimeout(function() { + if (!ours.handled) + input.applyComposition(ours); + if (input.composing == ours) + input.composing = null; + }, 50); + }); + + on(div, "touchstart", function() { + input.forceCompositionEnd(); + }); + + on(div, "input", function() { + if (input.composing) return; + if (!input.pollContent()) + runInOp(input.cm, function() {regChange(cm);}); + }); + + function onCopyCut(e) { + if (cm.somethingSelected()) { + lastCopied = cm.getSelections(); + if (e.type == "cut") cm.replaceSelection("", null, "cut"); + } else if (!cm.options.lineWiseCopyCut) { + return; + } else { + var ranges = copyableRanges(cm); + lastCopied = ranges.text; + if (e.type == "cut") { + cm.operation(function() { + cm.setSelections(ranges.ranges, 0, sel_dontScroll); + cm.replaceSelection("", null, "cut"); + }); + } + } + // iOS exposes the clipboard API, but seems to discard content inserted into it + if (e.clipboardData && !ios) { + e.preventDefault(); + e.clipboardData.clearData(); + e.clipboardData.setData("text/plain", lastCopied.join("\n")); + } else { + // Old-fashioned briefly-focus-a-textarea hack + var kludge = hiddenTextarea(), te = kludge.firstChild; + cm.display.lineSpace.insertBefore(kludge, cm.display.lineSpace.firstChild); + te.value = lastCopied.join("\n"); + var hadFocus = document.activeElement; + selectInput(te); + setTimeout(function() { + cm.display.lineSpace.removeChild(kludge); + hadFocus.focus(); + }, 50); + } + } + on(div, "copy", onCopyCut); + on(div, "cut", onCopyCut); + }, + + prepareSelection: function() { + var result = prepareSelection(this.cm, false); + result.focus = this.cm.state.focused; + return result; + }, + + showSelection: function(info) { + if (!info || !this.cm.display.view.length) return; + if (info.focus) this.showPrimarySelection(); + this.showMultipleSelections(info); + }, + + showPrimarySelection: function() { + var sel = window.getSelection(), prim = this.cm.doc.sel.primary(); + var curAnchor = domToPos(this.cm, sel.anchorNode, sel.anchorOffset); + var curFocus = domToPos(this.cm, sel.focusNode, sel.focusOffset); + if (curAnchor && !curAnchor.bad && curFocus && !curFocus.bad && + cmp(minPos(curAnchor, curFocus), prim.from()) == 0 && + cmp(maxPos(curAnchor, curFocus), prim.to()) == 0) + return; + + var start = posToDOM(this.cm, prim.from()); + var end = posToDOM(this.cm, prim.to()); + if (!start && !end) return; + + var view = this.cm.display.view; + var old = sel.rangeCount && sel.getRangeAt(0); + if (!start) { + start = {node: view[0].measure.map[2], offset: 0}; + } else if (!end) { // FIXME dangerously hacky + var measure = view[view.length - 1].measure; + var map = measure.maps ? measure.maps[measure.maps.length - 1] : measure.map; + end = {node: map[map.length - 1], offset: map[map.length - 2] - map[map.length - 3]}; + } + + try { var rng = range(start.node, start.offset, end.offset, end.node); } + catch(e) {} // Our model of the DOM might be outdated, in which case the range we try to set can be impossible + if (rng) { + sel.removeAllRanges(); + sel.addRange(rng); + if (old && sel.anchorNode == null) sel.addRange(old); + else if (gecko) this.startGracePeriod(); + } + this.rememberSelection(); + }, + + startGracePeriod: function() { + var input = this; + clearTimeout(this.gracePeriod); + this.gracePeriod = setTimeout(function() { + input.gracePeriod = false; + if (input.selectionChanged()) + input.cm.operation(function() { input.cm.curOp.selectionChanged = true; }); + }, 20); + }, + + showMultipleSelections: function(info) { + removeChildrenAndAdd(this.cm.display.cursorDiv, info.cursors); + removeChildrenAndAdd(this.cm.display.selectionDiv, info.selection); + }, + + rememberSelection: function() { + var sel = window.getSelection(); + this.lastAnchorNode = sel.anchorNode; this.lastAnchorOffset = sel.anchorOffset; + this.lastFocusNode = sel.focusNode; this.lastFocusOffset = sel.focusOffset; + }, + + selectionInEditor: function() { + var sel = window.getSelection(); + if (!sel.rangeCount) return false; + var node = sel.getRangeAt(0).commonAncestorContainer; + return contains(this.div, node); + }, + + focus: function() { + if (this.cm.options.readOnly != "nocursor") this.div.focus(); + }, + blur: function() { this.div.blur(); }, + getField: function() { return this.div; }, + + supportsTouch: function() { return true; }, + + receivedFocus: function() { + var input = this; + if (this.selectionInEditor()) + this.pollSelection(); + else + runInOp(this.cm, function() { input.cm.curOp.selectionChanged = true; }); + + function poll() { + if (input.cm.state.focused) { + input.pollSelection(); + input.polling.set(input.cm.options.pollInterval, poll); + } + } + this.polling.set(this.cm.options.pollInterval, poll); + }, + + selectionChanged: function() { + var sel = window.getSelection(); + return sel.anchorNode != this.lastAnchorNode || sel.anchorOffset != this.lastAnchorOffset || + sel.focusNode != this.lastFocusNode || sel.focusOffset != this.lastFocusOffset; + }, + + pollSelection: function() { + if (!this.composing && !this.gracePeriod && this.selectionChanged()) { + var sel = window.getSelection(), cm = this.cm; + this.rememberSelection(); + var anchor = domToPos(cm, sel.anchorNode, sel.anchorOffset); + var head = domToPos(cm, sel.focusNode, sel.focusOffset); + if (anchor && head) runInOp(cm, function() { + setSelection(cm.doc, simpleSelection(anchor, head), sel_dontScroll); + if (anchor.bad || head.bad) cm.curOp.selectionChanged = true; + }); + } + }, + + pollContent: function() { + var cm = this.cm, display = cm.display, sel = cm.doc.sel.primary(); + var from = sel.from(), to = sel.to(); + if (from.line < display.viewFrom || to.line > display.viewTo - 1) return false; + + var fromIndex; + if (from.line == display.viewFrom || (fromIndex = findViewIndex(cm, from.line)) == 0) { + var fromLine = lineNo(display.view[0].line); + var fromNode = display.view[0].node; + } else { + var fromLine = lineNo(display.view[fromIndex].line); + var fromNode = display.view[fromIndex - 1].node.nextSibling; + } + var toIndex = findViewIndex(cm, to.line); + if (toIndex == display.view.length - 1) { + var toLine = display.viewTo - 1; + var toNode = display.view[toIndex].node; + } else { + var toLine = lineNo(display.view[toIndex + 1].line) - 1; + var toNode = display.view[toIndex + 1].node.previousSibling; + } + + var newText = splitLines(domTextBetween(cm, fromNode, toNode, fromLine, toLine)); + var oldText = getBetween(cm.doc, Pos(fromLine, 0), Pos(toLine, getLine(cm.doc, toLine).text.length)); + while (newText.length > 1 && oldText.length > 1) { + if (lst(newText) == lst(oldText)) { newText.pop(); oldText.pop(); toLine--; } + else if (newText[0] == oldText[0]) { newText.shift(); oldText.shift(); fromLine++; } + else break; + } + + var cutFront = 0, cutEnd = 0; + var newTop = newText[0], oldTop = oldText[0], maxCutFront = Math.min(newTop.length, oldTop.length); + while (cutFront < maxCutFront && newTop.charCodeAt(cutFront) == oldTop.charCodeAt(cutFront)) + ++cutFront; + var newBot = lst(newText), oldBot = lst(oldText); + var maxCutEnd = Math.min(newBot.length - (newText.length == 1 ? cutFront : 0), + oldBot.length - (oldText.length == 1 ? cutFront : 0)); + while (cutEnd < maxCutEnd && + newBot.charCodeAt(newBot.length - cutEnd - 1) == oldBot.charCodeAt(oldBot.length - cutEnd - 1)) + ++cutEnd; + + newText[newText.length - 1] = newBot.slice(0, newBot.length - cutEnd); + newText[0] = newText[0].slice(cutFront); + + var chFrom = Pos(fromLine, cutFront); + var chTo = Pos(toLine, oldText.length ? lst(oldText).length - cutEnd : 0); + if (newText.length > 1 || newText[0] || cmp(chFrom, chTo)) { + replaceRange(cm.doc, newText, chFrom, chTo, "+input"); + return true; + } + }, + + ensurePolled: function() { + this.forceCompositionEnd(); + }, + reset: function() { + this.forceCompositionEnd(); + }, + forceCompositionEnd: function() { + if (!this.composing || this.composing.handled) return; + this.applyComposition(this.composing); + this.composing.handled = true; + this.div.blur(); + this.div.focus(); + }, + applyComposition: function(composing) { + if (composing.data && composing.data != composing.startData) + operation(this.cm, applyTextInput)(this.cm, composing.data, 0, composing.sel); + }, + + setUneditable: function(node) { + node.setAttribute("contenteditable", "false"); + }, + + onKeyPress: function(e) { + e.preventDefault(); + operation(this.cm, applyTextInput)(this.cm, String.fromCharCode(e.charCode == null ? e.keyCode : e.charCode), 0); + }, + + onContextMenu: nothing, + resetPosition: nothing, + + needsContentAttribute: true + }, ContentEditableInput.prototype); + + function posToDOM(cm, pos) { + var view = findViewForLine(cm, pos.line); + if (!view || view.hidden) return null; + var line = getLine(cm.doc, pos.line); + var info = mapFromLineView(view, line, pos.line); + + var order = getOrder(line), side = "left"; + if (order) { + var partPos = getBidiPartAt(order, pos.ch); + side = partPos % 2 ? "right" : "left"; + } + var result = nodeAndOffsetInLineMap(info.map, pos.ch, "left"); + result.offset = result.collapse == "right" ? result.end : result.start; + return result; + } + + function badPos(pos, bad) { if (bad) pos.bad = true; return pos; } + + function domToPos(cm, node, offset) { + var lineNode; + if (node == cm.display.lineDiv) { + lineNode = cm.display.lineDiv.childNodes[offset]; + if (!lineNode) return badPos(cm.clipPos(Pos(cm.display.viewTo - 1)), true); + node = null; offset = 0; + } else { + for (lineNode = node;; lineNode = lineNode.parentNode) { + if (!lineNode || lineNode == cm.display.lineDiv) return null; + if (lineNode.parentNode && lineNode.parentNode == cm.display.lineDiv) break; + } + } + for (var i = 0; i < cm.display.view.length; i++) { + var lineView = cm.display.view[i]; + if (lineView.node == lineNode) + return locateNodeInLineView(lineView, node, offset); + } + } + + function locateNodeInLineView(lineView, node, offset) { + var wrapper = lineView.text.firstChild, bad = false; + if (!node || !contains(wrapper, node)) return badPos(Pos(lineNo(lineView.line), 0), true); + if (node == wrapper) { + bad = true; + node = wrapper.childNodes[offset]; + offset = 0; + if (!node) { + var line = lineView.rest ? lst(lineView.rest) : lineView.line; + return badPos(Pos(lineNo(line), line.text.length), bad); + } + } + + var textNode = node.nodeType == 3 ? node : null, topNode = node; + if (!textNode && node.childNodes.length == 1 && node.firstChild.nodeType == 3) { + textNode = node.firstChild; + if (offset) offset = textNode.nodeValue.length; + } + while (topNode.parentNode != wrapper) topNode = topNode.parentNode; + var measure = lineView.measure, maps = measure.maps; + + function find(textNode, topNode, offset) { + for (var i = -1; i < (maps ? maps.length : 0); i++) { + var map = i < 0 ? measure.map : maps[i]; + for (var j = 0; j < map.length; j += 3) { + var curNode = map[j + 2]; + if (curNode == textNode || curNode == topNode) { + var line = lineNo(i < 0 ? lineView.line : lineView.rest[i]); + var ch = map[j] + offset; + if (offset < 0 || curNode != textNode) ch = map[j + (offset ? 1 : 0)]; + return Pos(line, ch); + } + } + } + } + var found = find(textNode, topNode, offset); + if (found) return badPos(found, bad); + + // FIXME this is all really shaky. might handle the few cases it needs to handle, but likely to cause problems + for (var after = topNode.nextSibling, dist = textNode ? textNode.nodeValue.length - offset : 0; after; after = after.nextSibling) { + found = find(after, after.firstChild, 0); + if (found) + return badPos(Pos(found.line, found.ch - dist), bad); + else + dist += after.textContent.length; + } + for (var before = topNode.previousSibling, dist = offset; before; before = before.previousSibling) { + found = find(before, before.firstChild, -1); + if (found) + return badPos(Pos(found.line, found.ch + dist), bad); + else + dist += after.textContent.length; + } + } + + function domTextBetween(cm, from, to, fromLine, toLine) { + var text = "", closing = false; + function recognizeMarker(id) { return function(marker) { return marker.id == id; }; } + function walk(node) { + if (node.nodeType == 1) { + var cmText = node.getAttribute("cm-text"); + if (cmText != null) { + if (cmText == "") cmText = node.textContent.replace(/\u200b/g, ""); + text += cmText; + return; + } + var markerID = node.getAttribute("cm-marker"), range; + if (markerID) { + var found = cm.findMarks(Pos(fromLine, 0), Pos(toLine + 1, 0), recognizeMarker(+markerID)); + if (found.length && (range = found[0].find())) + text += getBetween(cm.doc, range.from, range.to).join("\n"); + return; + } + if (node.getAttribute("contenteditable") == "false") return; + for (var i = 0; i < node.childNodes.length; i++) + walk(node.childNodes[i]); + if (/^(pre|div|p)$/i.test(node.nodeName)) + closing = true; + } else if (node.nodeType == 3) { + var val = node.nodeValue; + if (!val) return; + if (closing) { + text += "\n"; + closing = false; + } + text += val; + } + } + for (;;) { + walk(from); + if (from == to) break; + from = from.nextSibling; + } + return text; + } + + CodeMirror.inputStyles = {"textarea": TextareaInput, "contenteditable": ContentEditableInput}; + + // SELECTION / CURSOR + + // Selection objects are immutable. A new one is created every time + // the selection changes. A selection is one or more non-overlapping + // (and non-touching) ranges, sorted, and an integer that indicates + // which one is the primary selection (the one that's scrolled into + // view, that getCursor returns, etc). + function Selection(ranges, primIndex) { + this.ranges = ranges; + this.primIndex = primIndex; + } + + Selection.prototype = { + primary: function() { return this.ranges[this.primIndex]; }, + equals: function(other) { + if (other == this) return true; + if (other.primIndex != this.primIndex || other.ranges.length != this.ranges.length) return false; + for (var i = 0; i < this.ranges.length; i++) { + var here = this.ranges[i], there = other.ranges[i]; + if (cmp(here.anchor, there.anchor) != 0 || cmp(here.head, there.head) != 0) return false; + } + return true; + }, + deepCopy: function() { + for (var out = [], i = 0; i < this.ranges.length; i++) + out[i] = new Range(copyPos(this.ranges[i].anchor), copyPos(this.ranges[i].head)); + return new Selection(out, this.primIndex); + }, + somethingSelected: function() { + for (var i = 0; i < this.ranges.length; i++) + if (!this.ranges[i].empty()) return true; + return false; + }, + contains: function(pos, end) { + if (!end) end = pos; + for (var i = 0; i < this.ranges.length; i++) { + var range = this.ranges[i]; + if (cmp(end, range.from()) >= 0 && cmp(pos, range.to()) <= 0) + return i; + } + return -1; + } + }; + + function Range(anchor, head) { + this.anchor = anchor; this.head = head; + } + + Range.prototype = { + from: function() { return minPos(this.anchor, this.head); }, + to: function() { return maxPos(this.anchor, this.head); }, + empty: function() { + return this.head.line == this.anchor.line && this.head.ch == this.anchor.ch; + } + }; + + // Take an unsorted, potentially overlapping set of ranges, and + // build a selection out of it. 'Consumes' ranges array (modifying + // it). + function normalizeSelection(ranges, primIndex) { + var prim = ranges[primIndex]; + ranges.sort(function(a, b) { return cmp(a.from(), b.from()); }); + primIndex = indexOf(ranges, prim); + for (var i = 1; i < ranges.length; i++) { + var cur = ranges[i], prev = ranges[i - 1]; + if (cmp(prev.to(), cur.from()) >= 0) { + var from = minPos(prev.from(), cur.from()), to = maxPos(prev.to(), cur.to()); + var inv = prev.empty() ? cur.from() == cur.head : prev.from() == prev.head; + if (i <= primIndex) --primIndex; + ranges.splice(--i, 2, new Range(inv ? to : from, inv ? from : to)); + } + } + return new Selection(ranges, primIndex); + } + + function simpleSelection(anchor, head) { + return new Selection([new Range(anchor, head || anchor)], 0); + } + + // Most of the external API clips given positions to make sure they + // actually exist within the document. + function clipLine(doc, n) {return Math.max(doc.first, Math.min(n, doc.first + doc.size - 1));} + function clipPos(doc, pos) { + if (pos.line < doc.first) return Pos(doc.first, 0); + var last = doc.first + doc.size - 1; + if (pos.line > last) return Pos(last, getLine(doc, last).text.length); + return clipToLen(pos, getLine(doc, pos.line).text.length); + } + function clipToLen(pos, linelen) { + var ch = pos.ch; + if (ch == null || ch > linelen) return Pos(pos.line, linelen); + else if (ch < 0) return Pos(pos.line, 0); + else return pos; + } + function isLine(doc, l) {return l >= doc.first && l < doc.first + doc.size;} + function clipPosArray(doc, array) { + for (var out = [], i = 0; i < array.length; i++) out[i] = clipPos(doc, array[i]); + return out; + } + + // SELECTION UPDATES + + // The 'scroll' parameter given to many of these indicated whether + // the new cursor position should be scrolled into view after + // modifying the selection. + + // If shift is held or the extend flag is set, extends a range to + // include a given position (and optionally a second position). + // Otherwise, simply returns the range between the given positions. + // Used for cursor motion and such. + function extendRange(doc, range, head, other) { + if (doc.cm && doc.cm.display.shift || doc.extend) { + var anchor = range.anchor; + if (other) { + var posBefore = cmp(head, anchor) < 0; + if (posBefore != (cmp(other, anchor) < 0)) { + anchor = head; + head = other; + } else if (posBefore != (cmp(head, other) < 0)) { + head = other; + } + } + return new Range(anchor, head); + } else { + return new Range(other || head, head); + } + } + + // Extend the primary selection range, discard the rest. + function extendSelection(doc, head, other, options) { + setSelection(doc, new Selection([extendRange(doc, doc.sel.primary(), head, other)], 0), options); + } + + // Extend all selections (pos is an array of selections with length + // equal the number of selections) + function extendSelections(doc, heads, options) { + for (var out = [], i = 0; i < doc.sel.ranges.length; i++) + out[i] = extendRange(doc, doc.sel.ranges[i], heads[i], null); + var newSel = normalizeSelection(out, doc.sel.primIndex); + setSelection(doc, newSel, options); + } + + // Updates a single range in the selection. + function replaceOneSelection(doc, i, range, options) { + var ranges = doc.sel.ranges.slice(0); + ranges[i] = range; + setSelection(doc, normalizeSelection(ranges, doc.sel.primIndex), options); + } + + // Reset the selection to a single range. + function setSimpleSelection(doc, anchor, head, options) { + setSelection(doc, simpleSelection(anchor, head), options); + } + + // Give beforeSelectionChange handlers a change to influence a + // selection update. + function filterSelectionChange(doc, sel) { + var obj = { + ranges: sel.ranges, + update: function(ranges) { + this.ranges = []; + for (var i = 0; i < ranges.length; i++) + this.ranges[i] = new Range(clipPos(doc, ranges[i].anchor), + clipPos(doc, ranges[i].head)); + } + }; + signal(doc, "beforeSelectionChange", doc, obj); + if (doc.cm) signal(doc.cm, "beforeSelectionChange", doc.cm, obj); + if (obj.ranges != sel.ranges) return normalizeSelection(obj.ranges, obj.ranges.length - 1); + else return sel; + } + + function setSelectionReplaceHistory(doc, sel, options) { + var done = doc.history.done, last = lst(done); + if (last && last.ranges) { + done[done.length - 1] = sel; + setSelectionNoUndo(doc, sel, options); + } else { + setSelection(doc, sel, options); + } + } + + // Set a new selection. + function setSelection(doc, sel, options) { + setSelectionNoUndo(doc, sel, options); + addSelectionToHistory(doc, doc.sel, doc.cm ? doc.cm.curOp.id : NaN, options); + } + + function setSelectionNoUndo(doc, sel, options) { + if (hasHandler(doc, "beforeSelectionChange") || doc.cm && hasHandler(doc.cm, "beforeSelectionChange")) + sel = filterSelectionChange(doc, sel); + + var bias = options && options.bias || + (cmp(sel.primary().head, doc.sel.primary().head) < 0 ? -1 : 1); + setSelectionInner(doc, skipAtomicInSelection(doc, sel, bias, true)); + + if (!(options && options.scroll === false) && doc.cm) + ensureCursorVisible(doc.cm); + } + + function setSelectionInner(doc, sel) { + if (sel.equals(doc.sel)) return; + + doc.sel = sel; + + if (doc.cm) { + doc.cm.curOp.updateInput = doc.cm.curOp.selectionChanged = true; + signalCursorActivity(doc.cm); + } + signalLater(doc, "cursorActivity", doc); + } + + // Verify that the selection does not partially select any atomic + // marked ranges. + function reCheckSelection(doc) { + setSelectionInner(doc, skipAtomicInSelection(doc, doc.sel, null, false), sel_dontScroll); + } + + // Return a selection that does not partially select any atomic + // ranges. + function skipAtomicInSelection(doc, sel, bias, mayClear) { + var out; + for (var i = 0; i < sel.ranges.length; i++) { + var range = sel.ranges[i]; + var newAnchor = skipAtomic(doc, range.anchor, bias, mayClear); + var newHead = skipAtomic(doc, range.head, bias, mayClear); + if (out || newAnchor != range.anchor || newHead != range.head) { + if (!out) out = sel.ranges.slice(0, i); + out[i] = new Range(newAnchor, newHead); + } + } + return out ? normalizeSelection(out, sel.primIndex) : sel; + } + + // Ensure a given position is not inside an atomic range. + function skipAtomic(doc, pos, bias, mayClear) { + var flipped = false, curPos = pos; + var dir = bias || 1; + doc.cantEdit = false; + search: for (;;) { + var line = getLine(doc, curPos.line); + if (line.markedSpans) { + for (var i = 0; i < line.markedSpans.length; ++i) { + var sp = line.markedSpans[i], m = sp.marker; + if ((sp.from == null || (m.inclusiveLeft ? sp.from <= curPos.ch : sp.from < curPos.ch)) && + (sp.to == null || (m.inclusiveRight ? sp.to >= curPos.ch : sp.to > curPos.ch))) { + if (mayClear) { + signal(m, "beforeCursorEnter"); + if (m.explicitlyCleared) { + if (!line.markedSpans) break; + else {--i; continue;} + } + } + if (!m.atomic) continue; + var newPos = m.find(dir < 0 ? -1 : 1); + if (cmp(newPos, curPos) == 0) { + newPos.ch += dir; + if (newPos.ch < 0) { + if (newPos.line > doc.first) newPos = clipPos(doc, Pos(newPos.line - 1)); + else newPos = null; + } else if (newPos.ch > line.text.length) { + if (newPos.line < doc.first + doc.size - 1) newPos = Pos(newPos.line + 1, 0); + else newPos = null; + } + if (!newPos) { + if (flipped) { + // Driven in a corner -- no valid cursor position found at all + // -- try again *with* clearing, if we didn't already + if (!mayClear) return skipAtomic(doc, pos, bias, true); + // Otherwise, turn off editing until further notice, and return the start of the doc + doc.cantEdit = true; + return Pos(doc.first, 0); + } + flipped = true; newPos = pos; dir = -dir; + } + } + curPos = newPos; + continue search; + } + } + } + return curPos; + } + } + + // SELECTION DRAWING + + function updateSelection(cm) { + cm.display.input.showSelection(cm.display.input.prepareSelection()); + } + + function prepareSelection(cm, primary) { + var doc = cm.doc, result = {}; + var curFragment = result.cursors = document.createDocumentFragment(); + var selFragment = result.selection = document.createDocumentFragment(); + + for (var i = 0; i < doc.sel.ranges.length; i++) { + if (primary === false && i == doc.sel.primIndex) continue; + var range = doc.sel.ranges[i]; + var collapsed = range.empty(); + if (collapsed || cm.options.showCursorWhenSelecting) + drawSelectionCursor(cm, range, curFragment); + if (!collapsed) + drawSelectionRange(cm, range, selFragment); + } + return result; + } + + // Draws a cursor for the given range + function drawSelectionCursor(cm, range, output) { + var pos = cursorCoords(cm, range.head, "div", null, null, !cm.options.singleCursorHeightPerLine); + + var cursor = output.appendChild(elt("div", "\u00a0", "CodeMirror-cursor")); + cursor.style.left = pos.left + "px"; + cursor.style.top = pos.top + "px"; + cursor.style.height = Math.max(0, pos.bottom - pos.top) * cm.options.cursorHeight + "px"; + + if (pos.other) { + // Secondary cursor, shown when on a 'jump' in bi-directional text + var otherCursor = output.appendChild(elt("div", "\u00a0", "CodeMirror-cursor CodeMirror-secondarycursor")); + otherCursor.style.display = ""; + otherCursor.style.left = pos.other.left + "px"; + otherCursor.style.top = pos.other.top + "px"; + otherCursor.style.height = (pos.other.bottom - pos.other.top) * .85 + "px"; + } + } + + // Draws the given range as a highlighted selection + function drawSelectionRange(cm, range, output) { + var display = cm.display, doc = cm.doc; + var fragment = document.createDocumentFragment(); + var padding = paddingH(cm.display), leftSide = padding.left; + var rightSide = Math.max(display.sizerWidth, displayWidth(cm) - display.sizer.offsetLeft) - padding.right; + + function add(left, top, width, bottom) { + if (top < 0) top = 0; + top = Math.round(top); + bottom = Math.round(bottom); + fragment.appendChild(elt("div", null, "CodeMirror-selected", "position: absolute; left: " + left + + "px; top: " + top + "px; width: " + (width == null ? rightSide - left : width) + + "px; height: " + (bottom - top) + "px")); + } + + function drawForLine(line, fromArg, toArg) { + var lineObj = getLine(doc, line); + var lineLen = lineObj.text.length; + var start, end; + function coords(ch, bias) { + return charCoords(cm, Pos(line, ch), "div", lineObj, bias); + } + + iterateBidiSections(getOrder(lineObj), fromArg || 0, toArg == null ? lineLen : toArg, function(from, to, dir) { + var leftPos = coords(from, "left"), rightPos, left, right; + if (from == to) { + rightPos = leftPos; + left = right = leftPos.left; + } else { + rightPos = coords(to - 1, "right"); + if (dir == "rtl") { var tmp = leftPos; leftPos = rightPos; rightPos = tmp; } + left = leftPos.left; + right = rightPos.right; + } + if (fromArg == null && from == 0) left = leftSide; + if (rightPos.top - leftPos.top > 3) { // Different lines, draw top part + add(left, leftPos.top, null, leftPos.bottom); + left = leftSide; + if (leftPos.bottom < rightPos.top) add(left, leftPos.bottom, null, rightPos.top); + } + if (toArg == null && to == lineLen) right = rightSide; + if (!start || leftPos.top < start.top || leftPos.top == start.top && leftPos.left < start.left) + start = leftPos; + if (!end || rightPos.bottom > end.bottom || rightPos.bottom == end.bottom && rightPos.right > end.right) + end = rightPos; + if (left < leftSide + 1) left = leftSide; + add(left, rightPos.top, right - left, rightPos.bottom); + }); + return {start: start, end: end}; + } + + var sFrom = range.from(), sTo = range.to(); + if (sFrom.line == sTo.line) { + drawForLine(sFrom.line, sFrom.ch, sTo.ch); + } else { + var fromLine = getLine(doc, sFrom.line), toLine = getLine(doc, sTo.line); + var singleVLine = visualLine(fromLine) == visualLine(toLine); + var leftEnd = drawForLine(sFrom.line, sFrom.ch, singleVLine ? fromLine.text.length + 1 : null).end; + var rightStart = drawForLine(sTo.line, singleVLine ? 0 : null, sTo.ch).start; + if (singleVLine) { + if (leftEnd.top < rightStart.top - 2) { + add(leftEnd.right, leftEnd.top, null, leftEnd.bottom); + add(leftSide, rightStart.top, rightStart.left, rightStart.bottom); + } else { + add(leftEnd.right, leftEnd.top, rightStart.left - leftEnd.right, leftEnd.bottom); + } + } + if (leftEnd.bottom < rightStart.top) + add(leftSide, leftEnd.bottom, null, rightStart.top); + } + + output.appendChild(fragment); + } + + // Cursor-blinking + function restartBlink(cm) { + if (!cm.state.focused) return; + var display = cm.display; + clearInterval(display.blinker); + var on = true; + display.cursorDiv.style.visibility = ""; + if (cm.options.cursorBlinkRate > 0) + display.blinker = setInterval(function() { + display.cursorDiv.style.visibility = (on = !on) ? "" : "hidden"; + }, cm.options.cursorBlinkRate); + else if (cm.options.cursorBlinkRate < 0) + display.cursorDiv.style.visibility = "hidden"; + } + + // HIGHLIGHT WORKER + + function startWorker(cm, time) { + if (cm.doc.mode.startState && cm.doc.frontier < cm.display.viewTo) + cm.state.highlight.set(time, bind(highlightWorker, cm)); + } + + function highlightWorker(cm) { + var doc = cm.doc; + if (doc.frontier < doc.first) doc.frontier = doc.first; + if (doc.frontier >= cm.display.viewTo) return; + var end = +new Date + cm.options.workTime; + var state = copyState(doc.mode, getStateBefore(cm, doc.frontier)); + var changedLines = []; + + doc.iter(doc.frontier, Math.min(doc.first + doc.size, cm.display.viewTo + 500), function(line) { + if (doc.frontier >= cm.display.viewFrom) { // Visible + var oldStyles = line.styles; + var highlighted = highlightLine(cm, line, state, true); + line.styles = highlighted.styles; + var oldCls = line.styleClasses, newCls = highlighted.classes; + if (newCls) line.styleClasses = newCls; + else if (oldCls) line.styleClasses = null; + var ischange = !oldStyles || oldStyles.length != line.styles.length || + oldCls != newCls && (!oldCls || !newCls || oldCls.bgClass != newCls.bgClass || oldCls.textClass != newCls.textClass); + for (var i = 0; !ischange && i < oldStyles.length; ++i) ischange = oldStyles[i] != line.styles[i]; + if (ischange) changedLines.push(doc.frontier); + line.stateAfter = copyState(doc.mode, state); + } else { + processLine(cm, line.text, state); + line.stateAfter = doc.frontier % 5 == 0 ? copyState(doc.mode, state) : null; + } + ++doc.frontier; + if (+new Date > end) { + startWorker(cm, cm.options.workDelay); + return true; + } + }); + if (changedLines.length) runInOp(cm, function() { + for (var i = 0; i < changedLines.length; i++) + regLineChange(cm, changedLines[i], "text"); + }); + } + + // Finds the line to start with when starting a parse. Tries to + // find a line with a stateAfter, so that it can start with a + // valid state. If that fails, it returns the line with the + // smallest indentation, which tends to need the least context to + // parse correctly. + function findStartLine(cm, n, precise) { + var minindent, minline, doc = cm.doc; + var lim = precise ? -1 : n - (cm.doc.mode.innerMode ? 1000 : 100); + for (var search = n; search > lim; --search) { + if (search <= doc.first) return doc.first; + var line = getLine(doc, search - 1); + if (line.stateAfter && (!precise || search <= doc.frontier)) return search; + var indented = countColumn(line.text, null, cm.options.tabSize); + if (minline == null || minindent > indented) { + minline = search - 1; + minindent = indented; + } + } + return minline; + } + + function getStateBefore(cm, n, precise) { + var doc = cm.doc, display = cm.display; + if (!doc.mode.startState) return true; + var pos = findStartLine(cm, n, precise), state = pos > doc.first && getLine(doc, pos-1).stateAfter; + if (!state) state = startState(doc.mode); + else state = copyState(doc.mode, state); + doc.iter(pos, n, function(line) { + processLine(cm, line.text, state); + var save = pos == n - 1 || pos % 5 == 0 || pos >= display.viewFrom && pos < display.viewTo; + line.stateAfter = save ? copyState(doc.mode, state) : null; + ++pos; + }); + if (precise) doc.frontier = pos; + return state; + } + + // POSITION MEASUREMENT + + function paddingTop(display) {return display.lineSpace.offsetTop;} + function paddingVert(display) {return display.mover.offsetHeight - display.lineSpace.offsetHeight;} + function paddingH(display) { + if (display.cachedPaddingH) return display.cachedPaddingH; + var e = removeChildrenAndAdd(display.measure, elt("pre", "x")); + var style = window.getComputedStyle ? window.getComputedStyle(e) : e.currentStyle; + var data = {left: parseInt(style.paddingLeft), right: parseInt(style.paddingRight)}; + if (!isNaN(data.left) && !isNaN(data.right)) display.cachedPaddingH = data; + return data; + } + + function scrollGap(cm) { return scrollerGap - cm.display.nativeBarWidth; } + function displayWidth(cm) { + return cm.display.scroller.clientWidth - scrollGap(cm) - cm.display.barWidth; + } + function displayHeight(cm) { + return cm.display.scroller.clientHeight - scrollGap(cm) - cm.display.barHeight; + } + + // Ensure the lineView.wrapping.heights array is populated. This is + // an array of bottom offsets for the lines that make up a drawn + // line. When lineWrapping is on, there might be more than one + // height. + function ensureLineHeights(cm, lineView, rect) { + var wrapping = cm.options.lineWrapping; + var curWidth = wrapping && displayWidth(cm); + if (!lineView.measure.heights || wrapping && lineView.measure.width != curWidth) { + var heights = lineView.measure.heights = []; + if (wrapping) { + lineView.measure.width = curWidth; + var rects = lineView.text.firstChild.getClientRects(); + for (var i = 0; i < rects.length - 1; i++) { + var cur = rects[i], next = rects[i + 1]; + if (Math.abs(cur.bottom - next.bottom) > 2) + heights.push((cur.bottom + next.top) / 2 - rect.top); + } + } + heights.push(rect.bottom - rect.top); + } + } + + // Find a line map (mapping character offsets to text nodes) and a + // measurement cache for the given line number. (A line view might + // contain multiple lines when collapsed ranges are present.) + function mapFromLineView(lineView, line, lineN) { + if (lineView.line == line) + return {map: lineView.measure.map, cache: lineView.measure.cache}; + for (var i = 0; i < lineView.rest.length; i++) + if (lineView.rest[i] == line) + return {map: lineView.measure.maps[i], cache: lineView.measure.caches[i]}; + for (var i = 0; i < lineView.rest.length; i++) + if (lineNo(lineView.rest[i]) > lineN) + return {map: lineView.measure.maps[i], cache: lineView.measure.caches[i], before: true}; + } + + // Render a line into the hidden node display.externalMeasured. Used + // when measurement is needed for a line that's not in the viewport. + function updateExternalMeasurement(cm, line) { + line = visualLine(line); + var lineN = lineNo(line); + var view = cm.display.externalMeasured = new LineView(cm.doc, line, lineN); + view.lineN = lineN; + var built = view.built = buildLineContent(cm, view); + view.text = built.pre; + removeChildrenAndAdd(cm.display.lineMeasure, built.pre); + return view; + } + + // Get a {top, bottom, left, right} box (in line-local coordinates) + // for a given character. + function measureChar(cm, line, ch, bias) { + return measureCharPrepared(cm, prepareMeasureForLine(cm, line), ch, bias); + } + + // Find a line view that corresponds to the given line number. + function findViewForLine(cm, lineN) { + if (lineN >= cm.display.viewFrom && lineN < cm.display.viewTo) + return cm.display.view[findViewIndex(cm, lineN)]; + var ext = cm.display.externalMeasured; + if (ext && lineN >= ext.lineN && lineN < ext.lineN + ext.size) + return ext; + } + + // Measurement can be split in two steps, the set-up work that + // applies to the whole line, and the measurement of the actual + // character. Functions like coordsChar, that need to do a lot of + // measurements in a row, can thus ensure that the set-up work is + // only done once. + function prepareMeasureForLine(cm, line) { + var lineN = lineNo(line); + var view = findViewForLine(cm, lineN); + if (view && !view.text) + view = null; + else if (view && view.changes) + updateLineForChanges(cm, view, lineN, getDimensions(cm)); + if (!view) + view = updateExternalMeasurement(cm, line); + + var info = mapFromLineView(view, line, lineN); + return { + line: line, view: view, rect: null, + map: info.map, cache: info.cache, before: info.before, + hasHeights: false + }; + } + + // Given a prepared measurement object, measures the position of an + // actual character (or fetches it from the cache). + function measureCharPrepared(cm, prepared, ch, bias, varHeight) { + if (prepared.before) ch = -1; + var key = ch + (bias || ""), found; + if (prepared.cache.hasOwnProperty(key)) { + found = prepared.cache[key]; + } else { + if (!prepared.rect) + prepared.rect = prepared.view.text.getBoundingClientRect(); + if (!prepared.hasHeights) { + ensureLineHeights(cm, prepared.view, prepared.rect); + prepared.hasHeights = true; + } + found = measureCharInner(cm, prepared, ch, bias); + if (!found.bogus) prepared.cache[key] = found; + } + return {left: found.left, right: found.right, + top: varHeight ? found.rtop : found.top, + bottom: varHeight ? found.rbottom : found.bottom}; + } + + var nullRect = {left: 0, right: 0, top: 0, bottom: 0}; + + function nodeAndOffsetInLineMap(map, ch, bias) { + var node, start, end, collapse; + // First, search the line map for the text node corresponding to, + // or closest to, the target character. + for (var i = 0; i < map.length; i += 3) { + var mStart = map[i], mEnd = map[i + 1]; + if (ch < mStart) { + start = 0; end = 1; + collapse = "left"; + } else if (ch < mEnd) { + start = ch - mStart; + end = start + 1; + } else if (i == map.length - 3 || ch == mEnd && map[i + 3] > ch) { + end = mEnd - mStart; + start = end - 1; + if (ch >= mEnd) collapse = "right"; + } + if (start != null) { + node = map[i + 2]; + if (mStart == mEnd && bias == (node.insertLeft ? "left" : "right")) + collapse = bias; + if (bias == "left" && start == 0) + while (i && map[i - 2] == map[i - 3] && map[i - 1].insertLeft) { + node = map[(i -= 3) + 2]; + collapse = "left"; + } + if (bias == "right" && start == mEnd - mStart) + while (i < map.length - 3 && map[i + 3] == map[i + 4] && !map[i + 5].insertLeft) { + node = map[(i += 3) + 2]; + collapse = "right"; + } + break; + } + } + return {node: node, start: start, end: end, collapse: collapse, coverStart: mStart, coverEnd: mEnd}; + } + + function measureCharInner(cm, prepared, ch, bias) { + var place = nodeAndOffsetInLineMap(prepared.map, ch, bias); + var node = place.node, start = place.start, end = place.end, collapse = place.collapse; + + var rect; + if (node.nodeType == 3) { // If it is a text node, use a range to retrieve the coordinates. + for (var i = 0; i < 4; i++) { // Retry a maximum of 4 times when nonsense rectangles are returned + while (start && isExtendingChar(prepared.line.text.charAt(place.coverStart + start))) --start; + while (place.coverStart + end < place.coverEnd && isExtendingChar(prepared.line.text.charAt(place.coverStart + end))) ++end; + if (ie && ie_version < 9 && start == 0 && end == place.coverEnd - place.coverStart) { + rect = node.parentNode.getBoundingClientRect(); + } else if (ie && cm.options.lineWrapping) { + var rects = range(node, start, end).getClientRects(); + if (rects.length) + rect = rects[bias == "right" ? rects.length - 1 : 0]; + else + rect = nullRect; + } else { + rect = range(node, start, end).getBoundingClientRect() || nullRect; + } + if (rect.left || rect.right || start == 0) break; + end = start; + start = start - 1; + collapse = "right"; + } + if (ie && ie_version < 11) rect = maybeUpdateRectForZooming(cm.display.measure, rect); + } else { // If it is a widget, simply get the box for the whole widget. + if (start > 0) collapse = bias = "right"; + var rects; + if (cm.options.lineWrapping && (rects = node.getClientRects()).length > 1) + rect = rects[bias == "right" ? rects.length - 1 : 0]; + else + rect = node.getBoundingClientRect(); + } + if (ie && ie_version < 9 && !start && (!rect || !rect.left && !rect.right)) { + var rSpan = node.parentNode.getClientRects()[0]; + if (rSpan) + rect = {left: rSpan.left, right: rSpan.left + charWidth(cm.display), top: rSpan.top, bottom: rSpan.bottom}; + else + rect = nullRect; + } + + var rtop = rect.top - prepared.rect.top, rbot = rect.bottom - prepared.rect.top; + var mid = (rtop + rbot) / 2; + var heights = prepared.view.measure.heights; + for (var i = 0; i < heights.length - 1; i++) + if (mid < heights[i]) break; + var top = i ? heights[i - 1] : 0, bot = heights[i]; + var result = {left: (collapse == "right" ? rect.right : rect.left) - prepared.rect.left, + right: (collapse == "left" ? rect.left : rect.right) - prepared.rect.left, + top: top, bottom: bot}; + if (!rect.left && !rect.right) result.bogus = true; + if (!cm.options.singleCursorHeightPerLine) { result.rtop = rtop; result.rbottom = rbot; } + + return result; + } + + // Work around problem with bounding client rects on ranges being + // returned incorrectly when zoomed on IE10 and below. + function maybeUpdateRectForZooming(measure, rect) { + if (!window.screen || screen.logicalXDPI == null || + screen.logicalXDPI == screen.deviceXDPI || !hasBadZoomedRects(measure)) + return rect; + var scaleX = screen.logicalXDPI / screen.deviceXDPI; + var scaleY = screen.logicalYDPI / screen.deviceYDPI; + return {left: rect.left * scaleX, right: rect.right * scaleX, + top: rect.top * scaleY, bottom: rect.bottom * scaleY}; + } + + function clearLineMeasurementCacheFor(lineView) { + if (lineView.measure) { + lineView.measure.cache = {}; + lineView.measure.heights = null; + if (lineView.rest) for (var i = 0; i < lineView.rest.length; i++) + lineView.measure.caches[i] = {}; + } + } + + function clearLineMeasurementCache(cm) { + cm.display.externalMeasure = null; + removeChildren(cm.display.lineMeasure); + for (var i = 0; i < cm.display.view.length; i++) + clearLineMeasurementCacheFor(cm.display.view[i]); + } + + function clearCaches(cm) { + clearLineMeasurementCache(cm); + cm.display.cachedCharWidth = cm.display.cachedTextHeight = cm.display.cachedPaddingH = null; + if (!cm.options.lineWrapping) cm.display.maxLineChanged = true; + cm.display.lineNumChars = null; + } + + function pageScrollX() { return window.pageXOffset || (document.documentElement || document.body).scrollLeft; } + function pageScrollY() { return window.pageYOffset || (document.documentElement || document.body).scrollTop; } + + // Converts a {top, bottom, left, right} box from line-local + // coordinates into another coordinate system. Context may be one of + // "line", "div" (display.lineDiv), "local"/null (editor), "window", + // or "page". + function intoCoordSystem(cm, lineObj, rect, context) { + if (lineObj.widgets) for (var i = 0; i < lineObj.widgets.length; ++i) if (lineObj.widgets[i].above) { + var size = widgetHeight(lineObj.widgets[i]); + rect.top += size; rect.bottom += size; + } + if (context == "line") return rect; + if (!context) context = "local"; + var yOff = heightAtLine(lineObj); + if (context == "local") yOff += paddingTop(cm.display); + else yOff -= cm.display.viewOffset; + if (context == "page" || context == "window") { + var lOff = cm.display.lineSpace.getBoundingClientRect(); + yOff += lOff.top + (context == "window" ? 0 : pageScrollY()); + var xOff = lOff.left + (context == "window" ? 0 : pageScrollX()); + rect.left += xOff; rect.right += xOff; + } + rect.top += yOff; rect.bottom += yOff; + return rect; + } + + // Coverts a box from "div" coords to another coordinate system. + // Context may be "window", "page", "div", or "local"/null. + function fromCoordSystem(cm, coords, context) { + if (context == "div") return coords; + var left = coords.left, top = coords.top; + // First move into "page" coordinate system + if (context == "page") { + left -= pageScrollX(); + top -= pageScrollY(); + } else if (context == "local" || !context) { + var localBox = cm.display.sizer.getBoundingClientRect(); + left += localBox.left; + top += localBox.top; + } + + var lineSpaceBox = cm.display.lineSpace.getBoundingClientRect(); + return {left: left - lineSpaceBox.left, top: top - lineSpaceBox.top}; + } + + function charCoords(cm, pos, context, lineObj, bias) { + if (!lineObj) lineObj = getLine(cm.doc, pos.line); + return intoCoordSystem(cm, lineObj, measureChar(cm, lineObj, pos.ch, bias), context); + } + + // Returns a box for a given cursor position, which may have an + // 'other' property containing the position of the secondary cursor + // on a bidi boundary. + function cursorCoords(cm, pos, context, lineObj, preparedMeasure, varHeight) { + lineObj = lineObj || getLine(cm.doc, pos.line); + if (!preparedMeasure) preparedMeasure = prepareMeasureForLine(cm, lineObj); + function get(ch, right) { + var m = measureCharPrepared(cm, preparedMeasure, ch, right ? "right" : "left", varHeight); + if (right) m.left = m.right; else m.right = m.left; + return intoCoordSystem(cm, lineObj, m, context); + } + function getBidi(ch, partPos) { + var part = order[partPos], right = part.level % 2; + if (ch == bidiLeft(part) && partPos && part.level < order[partPos - 1].level) { + part = order[--partPos]; + ch = bidiRight(part) - (part.level % 2 ? 0 : 1); + right = true; + } else if (ch == bidiRight(part) && partPos < order.length - 1 && part.level < order[partPos + 1].level) { + part = order[++partPos]; + ch = bidiLeft(part) - part.level % 2; + right = false; + } + if (right && ch == part.to && ch > part.from) return get(ch - 1); + return get(ch, right); + } + var order = getOrder(lineObj), ch = pos.ch; + if (!order) return get(ch); + var partPos = getBidiPartAt(order, ch); + var val = getBidi(ch, partPos); + if (bidiOther != null) val.other = getBidi(ch, bidiOther); + return val; + } + + // Used to cheaply estimate the coordinates for a position. Used for + // intermediate scroll updates. + function estimateCoords(cm, pos) { + var left = 0, pos = clipPos(cm.doc, pos); + if (!cm.options.lineWrapping) left = charWidth(cm.display) * pos.ch; + var lineObj = getLine(cm.doc, pos.line); + var top = heightAtLine(lineObj) + paddingTop(cm.display); + return {left: left, right: left, top: top, bottom: top + lineObj.height}; + } + + // Positions returned by coordsChar contain some extra information. + // xRel is the relative x position of the input coordinates compared + // to the found position (so xRel > 0 means the coordinates are to + // the right of the character position, for example). When outside + // is true, that means the coordinates lie outside the line's + // vertical range. + function PosWithInfo(line, ch, outside, xRel) { + var pos = Pos(line, ch); + pos.xRel = xRel; + if (outside) pos.outside = true; + return pos; + } + + // Compute the character position closest to the given coordinates. + // Input must be lineSpace-local ("div" coordinate system). + function coordsChar(cm, x, y) { + var doc = cm.doc; + y += cm.display.viewOffset; + if (y < 0) return PosWithInfo(doc.first, 0, true, -1); + var lineN = lineAtHeight(doc, y), last = doc.first + doc.size - 1; + if (lineN > last) + return PosWithInfo(doc.first + doc.size - 1, getLine(doc, last).text.length, true, 1); + if (x < 0) x = 0; + + var lineObj = getLine(doc, lineN); + for (;;) { + var found = coordsCharInner(cm, lineObj, lineN, x, y); + var merged = collapsedSpanAtEnd(lineObj); + var mergedPos = merged && merged.find(0, true); + if (merged && (found.ch > mergedPos.from.ch || found.ch == mergedPos.from.ch && found.xRel > 0)) + lineN = lineNo(lineObj = mergedPos.to.line); + else + return found; + } + } + + function coordsCharInner(cm, lineObj, lineNo, x, y) { + var innerOff = y - heightAtLine(lineObj); + var wrongLine = false, adjust = 2 * cm.display.wrapper.clientWidth; + var preparedMeasure = prepareMeasureForLine(cm, lineObj); + + function getX(ch) { + var sp = cursorCoords(cm, Pos(lineNo, ch), "line", lineObj, preparedMeasure); + wrongLine = true; + if (innerOff > sp.bottom) return sp.left - adjust; + else if (innerOff < sp.top) return sp.left + adjust; + else wrongLine = false; + return sp.left; + } + + var bidi = getOrder(lineObj), dist = lineObj.text.length; + var from = lineLeft(lineObj), to = lineRight(lineObj); + var fromX = getX(from), fromOutside = wrongLine, toX = getX(to), toOutside = wrongLine; + + if (x > toX) return PosWithInfo(lineNo, to, toOutside, 1); + // Do a binary search between these bounds. + for (;;) { + if (bidi ? to == from || to == moveVisually(lineObj, from, 1) : to - from <= 1) { + var ch = x < fromX || x - fromX <= toX - x ? from : to; + var xDiff = x - (ch == from ? fromX : toX); + while (isExtendingChar(lineObj.text.charAt(ch))) ++ch; + var pos = PosWithInfo(lineNo, ch, ch == from ? fromOutside : toOutside, + xDiff < -1 ? -1 : xDiff > 1 ? 1 : 0); + return pos; + } + var step = Math.ceil(dist / 2), middle = from + step; + if (bidi) { + middle = from; + for (var i = 0; i < step; ++i) middle = moveVisually(lineObj, middle, 1); + } + var middleX = getX(middle); + if (middleX > x) {to = middle; toX = middleX; if (toOutside = wrongLine) toX += 1000; dist = step;} + else {from = middle; fromX = middleX; fromOutside = wrongLine; dist -= step;} + } + } + + var measureText; + // Compute the default text height. + function textHeight(display) { + if (display.cachedTextHeight != null) return display.cachedTextHeight; + if (measureText == null) { + measureText = elt("pre"); + // Measure a bunch of lines, for browsers that compute + // fractional heights. + for (var i = 0; i < 49; ++i) { + measureText.appendChild(document.createTextNode("x")); + measureText.appendChild(elt("br")); + } + measureText.appendChild(document.createTextNode("x")); + } + removeChildrenAndAdd(display.measure, measureText); + var height = measureText.offsetHeight / 50; + if (height > 3) display.cachedTextHeight = height; + removeChildren(display.measure); + return height || 1; + } + + // Compute the default character width. + function charWidth(display) { + if (display.cachedCharWidth != null) return display.cachedCharWidth; + var anchor = elt("span", "xxxxxxxxxx"); + var pre = elt("pre", [anchor]); + removeChildrenAndAdd(display.measure, pre); + var rect = anchor.getBoundingClientRect(), width = (rect.right - rect.left) / 10; + if (width > 2) display.cachedCharWidth = width; + return width || 10; + } + + // OPERATIONS + + // Operations are used to wrap a series of changes to the editor + // state in such a way that each change won't have to update the + // cursor and display (which would be awkward, slow, and + // error-prone). Instead, display updates are batched and then all + // combined and executed at once. + + var operationGroup = null; + + var nextOpId = 0; + // Start a new operation. + function startOperation(cm) { + cm.curOp = { + cm: cm, + viewChanged: false, // Flag that indicates that lines might need to be redrawn + startHeight: cm.doc.height, // Used to detect need to update scrollbar + forceUpdate: false, // Used to force a redraw + updateInput: null, // Whether to reset the input textarea + typing: false, // Whether this reset should be careful to leave existing text (for compositing) + changeObjs: null, // Accumulated changes, for firing change events + cursorActivityHandlers: null, // Set of handlers to fire cursorActivity on + cursorActivityCalled: 0, // Tracks which cursorActivity handlers have been called already + selectionChanged: false, // Whether the selection needs to be redrawn + updateMaxLine: false, // Set when the widest line needs to be determined anew + scrollLeft: null, scrollTop: null, // Intermediate scroll position, not pushed to DOM yet + scrollToPos: null, // Used to scroll to a specific position + focus: false, + id: ++nextOpId // Unique ID + }; + if (operationGroup) { + operationGroup.ops.push(cm.curOp); + } else { + cm.curOp.ownsGroup = operationGroup = { + ops: [cm.curOp], + delayedCallbacks: [] + }; + } + } + + function fireCallbacksForOps(group) { + // Calls delayed callbacks and cursorActivity handlers until no + // new ones appear + var callbacks = group.delayedCallbacks, i = 0; + do { + for (; i < callbacks.length; i++) + callbacks[i](); + for (var j = 0; j < group.ops.length; j++) { + var op = group.ops[j]; + if (op.cursorActivityHandlers) + while (op.cursorActivityCalled < op.cursorActivityHandlers.length) + op.cursorActivityHandlers[op.cursorActivityCalled++](op.cm); + } + } while (i < callbacks.length); + } + + // Finish an operation, updating the display and signalling delayed events + function endOperation(cm) { + var op = cm.curOp, group = op.ownsGroup; + if (!group) return; + + try { fireCallbacksForOps(group); } + finally { + operationGroup = null; + for (var i = 0; i < group.ops.length; i++) + group.ops[i].cm.curOp = null; + endOperations(group); + } + } + + // The DOM updates done when an operation finishes are batched so + // that the minimum number of relayouts are required. + function endOperations(group) { + var ops = group.ops; + for (var i = 0; i < ops.length; i++) // Read DOM + endOperation_R1(ops[i]); + for (var i = 0; i < ops.length; i++) // Write DOM (maybe) + endOperation_W1(ops[i]); + for (var i = 0; i < ops.length; i++) // Read DOM + endOperation_R2(ops[i]); + for (var i = 0; i < ops.length; i++) // Write DOM (maybe) + endOperation_W2(ops[i]); + for (var i = 0; i < ops.length; i++) // Read DOM + endOperation_finish(ops[i]); + } + + function endOperation_R1(op) { + var cm = op.cm, display = cm.display; + maybeClipScrollbars(cm); + if (op.updateMaxLine) findMaxLine(cm); + + op.mustUpdate = op.viewChanged || op.forceUpdate || op.scrollTop != null || + op.scrollToPos && (op.scrollToPos.from.line < display.viewFrom || + op.scrollToPos.to.line >= display.viewTo) || + display.maxLineChanged && cm.options.lineWrapping; + op.update = op.mustUpdate && + new DisplayUpdate(cm, op.mustUpdate && {top: op.scrollTop, ensure: op.scrollToPos}, op.forceUpdate); + } + + function endOperation_W1(op) { + op.updatedDisplay = op.mustUpdate && updateDisplayIfNeeded(op.cm, op.update); + } + + function endOperation_R2(op) { + var cm = op.cm, display = cm.display; + if (op.updatedDisplay) updateHeightsInViewport(cm); + + op.barMeasure = measureForScrollbars(cm); + + // If the max line changed since it was last measured, measure it, + // and ensure the document's width matches it. + // updateDisplay_W2 will use these properties to do the actual resizing + if (display.maxLineChanged && !cm.options.lineWrapping) { + op.adjustWidthTo = measureChar(cm, display.maxLine, display.maxLine.text.length).left + 3; + cm.display.sizerWidth = op.adjustWidthTo; + op.barMeasure.scrollWidth = + Math.max(display.scroller.clientWidth, display.sizer.offsetLeft + op.adjustWidthTo + scrollGap(cm) + cm.display.barWidth); + op.maxScrollLeft = Math.max(0, display.sizer.offsetLeft + op.adjustWidthTo - displayWidth(cm)); + } + + if (op.updatedDisplay || op.selectionChanged) + op.preparedSelection = display.input.prepareSelection(); + } + + function endOperation_W2(op) { + var cm = op.cm; + + if (op.adjustWidthTo != null) { + cm.display.sizer.style.minWidth = op.adjustWidthTo + "px"; + if (op.maxScrollLeft < cm.doc.scrollLeft) + setScrollLeft(cm, Math.min(cm.display.scroller.scrollLeft, op.maxScrollLeft), true); + cm.display.maxLineChanged = false; + } + + if (op.preparedSelection) + cm.display.input.showSelection(op.preparedSelection); + if (op.updatedDisplay) + setDocumentHeight(cm, op.barMeasure); + if (op.updatedDisplay || op.startHeight != cm.doc.height) + updateScrollbars(cm, op.barMeasure); + + if (op.selectionChanged) restartBlink(cm); + + if (cm.state.focused && op.updateInput) + cm.display.input.reset(op.typing); + if (op.focus && op.focus == activeElt()) ensureFocus(op.cm); + } + + function endOperation_finish(op) { + var cm = op.cm, display = cm.display, doc = cm.doc; + + if (op.updatedDisplay) postUpdateDisplay(cm, op.update); + + // Abort mouse wheel delta measurement, when scrolling explicitly + if (display.wheelStartX != null && (op.scrollTop != null || op.scrollLeft != null || op.scrollToPos)) + display.wheelStartX = display.wheelStartY = null; + + // Propagate the scroll position to the actual DOM scroller + if (op.scrollTop != null && (display.scroller.scrollTop != op.scrollTop || op.forceScroll)) { + doc.scrollTop = Math.max(0, Math.min(display.scroller.scrollHeight - display.scroller.clientHeight, op.scrollTop)); + display.scrollbars.setScrollTop(doc.scrollTop); + display.scroller.scrollTop = doc.scrollTop; + } + if (op.scrollLeft != null && (display.scroller.scrollLeft != op.scrollLeft || op.forceScroll)) { + doc.scrollLeft = Math.max(0, Math.min(display.scroller.scrollWidth - displayWidth(cm), op.scrollLeft)); + display.scrollbars.setScrollLeft(doc.scrollLeft); + display.scroller.scrollLeft = doc.scrollLeft; + alignHorizontally(cm); + } + // If we need to scroll a specific position into view, do so. + if (op.scrollToPos) { + var coords = scrollPosIntoView(cm, clipPos(doc, op.scrollToPos.from), + clipPos(doc, op.scrollToPos.to), op.scrollToPos.margin); + if (op.scrollToPos.isCursor && cm.state.focused) maybeScrollWindow(cm, coords); + } + + // Fire events for markers that are hidden/unidden by editing or + // undoing + var hidden = op.maybeHiddenMarkers, unhidden = op.maybeUnhiddenMarkers; + if (hidden) for (var i = 0; i < hidden.length; ++i) + if (!hidden[i].lines.length) signal(hidden[i], "hide"); + if (unhidden) for (var i = 0; i < unhidden.length; ++i) + if (unhidden[i].lines.length) signal(unhidden[i], "unhide"); + + if (display.wrapper.offsetHeight) + doc.scrollTop = cm.display.scroller.scrollTop; + + // Fire change events, and delayed event handlers + if (op.changeObjs) + signal(cm, "changes", cm, op.changeObjs); + if (op.update) + op.update.finish(); + } + + // Run the given function in an operation + function runInOp(cm, f) { + if (cm.curOp) return f(); + startOperation(cm); + try { return f(); } + finally { endOperation(cm); } + } + // Wraps a function in an operation. Returns the wrapped function. + function operation(cm, f) { + return function() { + if (cm.curOp) return f.apply(cm, arguments); + startOperation(cm); + try { return f.apply(cm, arguments); } + finally { endOperation(cm); } + }; + } + // Used to add methods to editor and doc instances, wrapping them in + // operations. + function methodOp(f) { + return function() { + if (this.curOp) return f.apply(this, arguments); + startOperation(this); + try { return f.apply(this, arguments); } + finally { endOperation(this); } + }; + } + function docMethodOp(f) { + return function() { + var cm = this.cm; + if (!cm || cm.curOp) return f.apply(this, arguments); + startOperation(cm); + try { return f.apply(this, arguments); } + finally { endOperation(cm); } + }; + } + + // VIEW TRACKING + + // These objects are used to represent the visible (currently drawn) + // part of the document. A LineView may correspond to multiple + // logical lines, if those are connected by collapsed ranges. + function LineView(doc, line, lineN) { + // The starting line + this.line = line; + // Continuing lines, if any + this.rest = visualLineContinued(line); + // Number of logical lines in this visual line + this.size = this.rest ? lineNo(lst(this.rest)) - lineN + 1 : 1; + this.node = this.text = null; + this.hidden = lineIsHidden(doc, line); + } + + // Create a range of LineView objects for the given lines. + function buildViewArray(cm, from, to) { + var array = [], nextPos; + for (var pos = from; pos < to; pos = nextPos) { + var view = new LineView(cm.doc, getLine(cm.doc, pos), pos); + nextPos = pos + view.size; + array.push(view); + } + return array; + } + + // Updates the display.view data structure for a given change to the + // document. From and to are in pre-change coordinates. Lendiff is + // the amount of lines added or subtracted by the change. This is + // used for changes that span multiple lines, or change the way + // lines are divided into visual lines. regLineChange (below) + // registers single-line changes. + function regChange(cm, from, to, lendiff) { + if (from == null) from = cm.doc.first; + if (to == null) to = cm.doc.first + cm.doc.size; + if (!lendiff) lendiff = 0; + + var display = cm.display; + if (lendiff && to < display.viewTo && + (display.updateLineNumbers == null || display.updateLineNumbers > from)) + display.updateLineNumbers = from; + + cm.curOp.viewChanged = true; + + if (from >= display.viewTo) { // Change after + if (sawCollapsedSpans && visualLineNo(cm.doc, from) < display.viewTo) + resetView(cm); + } else if (to <= display.viewFrom) { // Change before + if (sawCollapsedSpans && visualLineEndNo(cm.doc, to + lendiff) > display.viewFrom) { + resetView(cm); + } else { + display.viewFrom += lendiff; + display.viewTo += lendiff; + } + } else if (from <= display.viewFrom && to >= display.viewTo) { // Full overlap + resetView(cm); + } else if (from <= display.viewFrom) { // Top overlap + var cut = viewCuttingPoint(cm, to, to + lendiff, 1); + if (cut) { + display.view = display.view.slice(cut.index); + display.viewFrom = cut.lineN; + display.viewTo += lendiff; + } else { + resetView(cm); + } + } else if (to >= display.viewTo) { // Bottom overlap + var cut = viewCuttingPoint(cm, from, from, -1); + if (cut) { + display.view = display.view.slice(0, cut.index); + display.viewTo = cut.lineN; + } else { + resetView(cm); + } + } else { // Gap in the middle + var cutTop = viewCuttingPoint(cm, from, from, -1); + var cutBot = viewCuttingPoint(cm, to, to + lendiff, 1); + if (cutTop && cutBot) { + display.view = display.view.slice(0, cutTop.index) + .concat(buildViewArray(cm, cutTop.lineN, cutBot.lineN)) + .concat(display.view.slice(cutBot.index)); + display.viewTo += lendiff; + } else { + resetView(cm); + } + } + + var ext = display.externalMeasured; + if (ext) { + if (to < ext.lineN) + ext.lineN += lendiff; + else if (from < ext.lineN + ext.size) + display.externalMeasured = null; + } + } + + // Register a change to a single line. Type must be one of "text", + // "gutter", "class", "widget" + function regLineChange(cm, line, type) { + cm.curOp.viewChanged = true; + var display = cm.display, ext = cm.display.externalMeasured; + if (ext && line >= ext.lineN && line < ext.lineN + ext.size) + display.externalMeasured = null; + + if (line < display.viewFrom || line >= display.viewTo) return; + var lineView = display.view[findViewIndex(cm, line)]; + if (lineView.node == null) return; + var arr = lineView.changes || (lineView.changes = []); + if (indexOf(arr, type) == -1) arr.push(type); + } + + // Clear the view. + function resetView(cm) { + cm.display.viewFrom = cm.display.viewTo = cm.doc.first; + cm.display.view = []; + cm.display.viewOffset = 0; + } + + // Find the view element corresponding to a given line. Return null + // when the line isn't visible. + function findViewIndex(cm, n) { + if (n >= cm.display.viewTo) return null; + n -= cm.display.viewFrom; + if (n < 0) return null; + var view = cm.display.view; + for (var i = 0; i < view.length; i++) { + n -= view[i].size; + if (n < 0) return i; + } + } + + function viewCuttingPoint(cm, oldN, newN, dir) { + var index = findViewIndex(cm, oldN), diff, view = cm.display.view; + if (!sawCollapsedSpans || newN == cm.doc.first + cm.doc.size) + return {index: index, lineN: newN}; + for (var i = 0, n = cm.display.viewFrom; i < index; i++) + n += view[i].size; + if (n != oldN) { + if (dir > 0) { + if (index == view.length - 1) return null; + diff = (n + view[index].size) - oldN; + index++; + } else { + diff = n - oldN; + } + oldN += diff; newN += diff; + } + while (visualLineNo(cm.doc, newN) != newN) { + if (index == (dir < 0 ? 0 : view.length - 1)) return null; + newN += dir * view[index - (dir < 0 ? 1 : 0)].size; + index += dir; + } + return {index: index, lineN: newN}; + } + + // Force the view to cover a given range, adding empty view element + // or clipping off existing ones as needed. + function adjustView(cm, from, to) { + var display = cm.display, view = display.view; + if (view.length == 0 || from >= display.viewTo || to <= display.viewFrom) { + display.view = buildViewArray(cm, from, to); + display.viewFrom = from; + } else { + if (display.viewFrom > from) + display.view = buildViewArray(cm, from, display.viewFrom).concat(display.view); + else if (display.viewFrom < from) + display.view = display.view.slice(findViewIndex(cm, from)); + display.viewFrom = from; + if (display.viewTo < to) + display.view = display.view.concat(buildViewArray(cm, display.viewTo, to)); + else if (display.viewTo > to) + display.view = display.view.slice(0, findViewIndex(cm, to)); + } + display.viewTo = to; + } + + // Count the number of lines in the view whose DOM representation is + // out of date (or nonexistent). + function countDirtyView(cm) { + var view = cm.display.view, dirty = 0; + for (var i = 0; i < view.length; i++) { + var lineView = view[i]; + if (!lineView.hidden && (!lineView.node || lineView.changes)) ++dirty; + } + return dirty; + } + + // EVENT HANDLERS + + // Attach the necessary event handlers when initializing the editor + function registerEventHandlers(cm) { + var d = cm.display; + on(d.scroller, "mousedown", operation(cm, onMouseDown)); + // Older IE's will not fire a second mousedown for a double click + if (ie && ie_version < 11) + on(d.scroller, "dblclick", operation(cm, function(e) { + if (signalDOMEvent(cm, e)) return; + var pos = posFromMouse(cm, e); + if (!pos || clickInGutter(cm, e) || eventInWidget(cm.display, e)) return; + e_preventDefault(e); + var word = cm.findWordAt(pos); + extendSelection(cm.doc, word.anchor, word.head); + })); + else + on(d.scroller, "dblclick", function(e) { signalDOMEvent(cm, e) || e_preventDefault(e); }); + // Some browsers fire contextmenu *after* opening the menu, at + // which point we can't mess with it anymore. Context menu is + // handled in onMouseDown for these browsers. + if (!captureRightClick) on(d.scroller, "contextmenu", function(e) {onContextMenu(cm, e);}); + + // Used to suppress mouse event handling when a touch happens + var touchFinished, prevTouch = {end: 0}; + function finishTouch() { + if (d.activeTouch) { + touchFinished = setTimeout(function() {d.activeTouch = null;}, 1000); + prevTouch = d.activeTouch; + prevTouch.end = +new Date; + } + }; + function isMouseLikeTouchEvent(e) { + if (e.touches.length != 1) return false; + var touch = e.touches[0]; + return touch.radiusX <= 1 && touch.radiusY <= 1; + } + function farAway(touch, other) { + if (other.left == null) return true; + var dx = other.left - touch.left, dy = other.top - touch.top; + return dx * dx + dy * dy > 20 * 20; + } + on(d.scroller, "touchstart", function(e) { + if (!isMouseLikeTouchEvent(e)) { + clearTimeout(touchFinished); + var now = +new Date; + d.activeTouch = {start: now, moved: false, + prev: now - prevTouch.end <= 300 ? prevTouch : null}; + if (e.touches.length == 1) { + d.activeTouch.left = e.touches[0].pageX; + d.activeTouch.top = e.touches[0].pageY; + } + } + }); + on(d.scroller, "touchmove", function() { + if (d.activeTouch) d.activeTouch.moved = true; + }); + on(d.scroller, "touchend", function(e) { + var touch = d.activeTouch; + if (touch && !eventInWidget(d, e) && touch.left != null && + !touch.moved && new Date - touch.start < 300) { + var pos = cm.coordsChar(d.activeTouch, "page"), range; + if (!touch.prev || farAway(touch, touch.prev)) // Single tap + range = new Range(pos, pos); + else if (!touch.prev.prev || farAway(touch, touch.prev.prev)) // Double tap + range = cm.findWordAt(pos); + else // Triple tap + range = new Range(Pos(pos.line, 0), clipPos(cm.doc, Pos(pos.line + 1, 0))); + cm.setSelection(range.anchor, range.head); + cm.focus(); + e_preventDefault(e); + } + finishTouch(); + }); + on(d.scroller, "touchcancel", finishTouch); + + // Sync scrolling between fake scrollbars and real scrollable + // area, ensure viewport is updated when scrolling. + on(d.scroller, "scroll", function() { + if (d.scroller.clientHeight) { + setScrollTop(cm, d.scroller.scrollTop); + setScrollLeft(cm, d.scroller.scrollLeft, true); + signal(cm, "scroll", cm); + } + }); + + // Listen to wheel events in order to try and update the viewport on time. + on(d.scroller, "mousewheel", function(e){onScrollWheel(cm, e);}); + on(d.scroller, "DOMMouseScroll", function(e){onScrollWheel(cm, e);}); + + // Prevent wrapper from ever scrolling + on(d.wrapper, "scroll", function() { d.wrapper.scrollTop = d.wrapper.scrollLeft = 0; }); + + d.dragFunctions = { + simple: function(e) {if (!signalDOMEvent(cm, e)) e_stop(e);}, + start: function(e){onDragStart(cm, e);}, + drop: operation(cm, onDrop) + }; + + var inp = d.input.getField(); + on(inp, "keyup", function(e) { onKeyUp.call(cm, e); }); + on(inp, "keydown", operation(cm, onKeyDown)); + on(inp, "keypress", operation(cm, onKeyPress)); + on(inp, "focus", bind(onFocus, cm)); + on(inp, "blur", bind(onBlur, cm)); + } + + function dragDropChanged(cm, value, old) { + var wasOn = old && old != CodeMirror.Init; + if (!value != !wasOn) { + var funcs = cm.display.dragFunctions; + var toggle = value ? on : off; + toggle(cm.display.scroller, "dragstart", funcs.start); + toggle(cm.display.scroller, "dragenter", funcs.simple); + toggle(cm.display.scroller, "dragover", funcs.simple); + toggle(cm.display.scroller, "drop", funcs.drop); + } + } + + // Called when the window resizes + function onResize(cm) { + var d = cm.display; + if (d.lastWrapHeight == d.wrapper.clientHeight && d.lastWrapWidth == d.wrapper.clientWidth) + return; + // Might be a text scaling operation, clear size caches. + d.cachedCharWidth = d.cachedTextHeight = d.cachedPaddingH = null; + d.scrollbarsClipped = false; + cm.setSize(); + } + + // MOUSE EVENTS + + // Return true when the given mouse event happened in a widget + function eventInWidget(display, e) { + for (var n = e_target(e); n != display.wrapper; n = n.parentNode) { + if (!n || (n.nodeType == 1 && n.getAttribute("cm-ignore-events") == "true") || + (n.parentNode == display.sizer && n != display.mover)) + return true; + } + } + + // Given a mouse event, find the corresponding position. If liberal + // is false, it checks whether a gutter or scrollbar was clicked, + // and returns null if it was. forRect is used by rectangular + // selections, and tries to estimate a character position even for + // coordinates beyond the right of the text. + function posFromMouse(cm, e, liberal, forRect) { + var display = cm.display; + if (!liberal && e_target(e).getAttribute("cm-not-content") == "true") return null; + + var x, y, space = display.lineSpace.getBoundingClientRect(); + // Fails unpredictably on IE[67] when mouse is dragged around quickly. + try { x = e.clientX - space.left; y = e.clientY - space.top; } + catch (e) { return null; } + var coords = coordsChar(cm, x, y), line; + if (forRect && coords.xRel == 1 && (line = getLine(cm.doc, coords.line).text).length == coords.ch) { + var colDiff = countColumn(line, line.length, cm.options.tabSize) - line.length; + coords = Pos(coords.line, Math.max(0, Math.round((x - paddingH(cm.display).left) / charWidth(cm.display)) - colDiff)); + } + return coords; + } + + // A mouse down can be a single click, double click, triple click, + // start of selection drag, start of text drag, new cursor + // (ctrl-click), rectangle drag (alt-drag), or xwin + // middle-click-paste. Or it might be a click on something we should + // not interfere with, such as a scrollbar or widget. + function onMouseDown(e) { + var cm = this, display = cm.display; + if (display.activeTouch && display.input.supportsTouch() || signalDOMEvent(cm, e)) return; + display.shift = e.shiftKey; + + if (eventInWidget(display, e)) { + if (!webkit) { + // Briefly turn off draggability, to allow widgets to do + // normal dragging things. + display.scroller.draggable = false; + setTimeout(function(){display.scroller.draggable = true;}, 100); + } + return; + } + if (clickInGutter(cm, e)) return; + var start = posFromMouse(cm, e); + window.focus(); + + switch (e_button(e)) { + case 1: + if (start) + leftButtonDown(cm, e, start); + else if (e_target(e) == display.scroller) + e_preventDefault(e); + break; + case 2: + if (webkit) cm.state.lastMiddleDown = +new Date; + if (start) extendSelection(cm.doc, start); + setTimeout(function() {display.input.focus();}, 20); + e_preventDefault(e); + break; + case 3: + if (captureRightClick) onContextMenu(cm, e); + else delayBlurEvent(cm); + break; + } + } + + var lastClick, lastDoubleClick; + function leftButtonDown(cm, e, start) { + if (ie) setTimeout(bind(ensureFocus, cm), 0); + else cm.curOp.focus = activeElt(); + + var now = +new Date, type; + if (lastDoubleClick && lastDoubleClick.time > now - 400 && cmp(lastDoubleClick.pos, start) == 0) { + type = "triple"; + } else if (lastClick && lastClick.time > now - 400 && cmp(lastClick.pos, start) == 0) { + type = "double"; + lastDoubleClick = {time: now, pos: start}; + } else { + type = "single"; + lastClick = {time: now, pos: start}; + } + + var sel = cm.doc.sel, modifier = mac ? e.metaKey : e.ctrlKey, contained; + if (cm.options.dragDrop && dragAndDrop && !isReadOnly(cm) && + type == "single" && (contained = sel.contains(start)) > -1 && + !sel.ranges[contained].empty()) + leftButtonStartDrag(cm, e, start, modifier); + else + leftButtonSelect(cm, e, start, type, modifier); + } + + // Start a text drag. When it ends, see if any dragging actually + // happen, and treat as a click if it didn't. + function leftButtonStartDrag(cm, e, start, modifier) { + var display = cm.display; + var dragEnd = operation(cm, function(e2) { + if (webkit) display.scroller.draggable = false; + cm.state.draggingText = false; + off(document, "mouseup", dragEnd); + off(display.scroller, "drop", dragEnd); + if (Math.abs(e.clientX - e2.clientX) + Math.abs(e.clientY - e2.clientY) < 10) { + e_preventDefault(e2); + if (!modifier) + extendSelection(cm.doc, start); + // Work around unexplainable focus problem in IE9 (#2127) and Chrome (#3081) + if (webkit || ie && ie_version == 9) + setTimeout(function() {document.body.focus(); display.input.focus();}, 20); + else + display.input.focus(); + } + }); + // Let the drag handler handle this. + if (webkit) display.scroller.draggable = true; + cm.state.draggingText = dragEnd; + // IE's approach to draggable + if (display.scroller.dragDrop) display.scroller.dragDrop(); + on(document, "mouseup", dragEnd); + on(display.scroller, "drop", dragEnd); + } + + // Normal selection, as opposed to text dragging. + function leftButtonSelect(cm, e, start, type, addNew) { + var display = cm.display, doc = cm.doc; + e_preventDefault(e); + + var ourRange, ourIndex, startSel = doc.sel, ranges = startSel.ranges; + if (addNew && !e.shiftKey) { + ourIndex = doc.sel.contains(start); + if (ourIndex > -1) + ourRange = ranges[ourIndex]; + else + ourRange = new Range(start, start); + } else { + ourRange = doc.sel.primary(); + ourIndex = doc.sel.primIndex; + } + + if (e.altKey) { + type = "rect"; + if (!addNew) ourRange = new Range(start, start); + start = posFromMouse(cm, e, true, true); + ourIndex = -1; + } else if (type == "double") { + var word = cm.findWordAt(start); + if (cm.display.shift || doc.extend) + ourRange = extendRange(doc, ourRange, word.anchor, word.head); + else + ourRange = word; + } else if (type == "triple") { + var line = new Range(Pos(start.line, 0), clipPos(doc, Pos(start.line + 1, 0))); + if (cm.display.shift || doc.extend) + ourRange = extendRange(doc, ourRange, line.anchor, line.head); + else + ourRange = line; + } else { + ourRange = extendRange(doc, ourRange, start); + } + + if (!addNew) { + ourIndex = 0; + setSelection(doc, new Selection([ourRange], 0), sel_mouse); + startSel = doc.sel; + } else if (ourIndex == -1) { + ourIndex = ranges.length; + setSelection(doc, normalizeSelection(ranges.concat([ourRange]), ourIndex), + {scroll: false, origin: "*mouse"}); + } else if (ranges.length > 1 && ranges[ourIndex].empty() && type == "single" && !e.shiftKey) { + setSelection(doc, normalizeSelection(ranges.slice(0, ourIndex).concat(ranges.slice(ourIndex + 1)), 0)); + startSel = doc.sel; + } else { + replaceOneSelection(doc, ourIndex, ourRange, sel_mouse); + } + + var lastPos = start; + function extendTo(pos) { + if (cmp(lastPos, pos) == 0) return; + lastPos = pos; + + if (type == "rect") { + var ranges = [], tabSize = cm.options.tabSize; + var startCol = countColumn(getLine(doc, start.line).text, start.ch, tabSize); + var posCol = countColumn(getLine(doc, pos.line).text, pos.ch, tabSize); + var left = Math.min(startCol, posCol), right = Math.max(startCol, posCol); + for (var line = Math.min(start.line, pos.line), end = Math.min(cm.lastLine(), Math.max(start.line, pos.line)); + line <= end; line++) { + var text = getLine(doc, line).text, leftPos = findColumn(text, left, tabSize); + if (left == right) + ranges.push(new Range(Pos(line, leftPos), Pos(line, leftPos))); + else if (text.length > leftPos) + ranges.push(new Range(Pos(line, leftPos), Pos(line, findColumn(text, right, tabSize)))); + } + if (!ranges.length) ranges.push(new Range(start, start)); + setSelection(doc, normalizeSelection(startSel.ranges.slice(0, ourIndex).concat(ranges), ourIndex), + {origin: "*mouse", scroll: false}); + cm.scrollIntoView(pos); + } else { + var oldRange = ourRange; + var anchor = oldRange.anchor, head = pos; + if (type != "single") { + if (type == "double") + var range = cm.findWordAt(pos); + else + var range = new Range(Pos(pos.line, 0), clipPos(doc, Pos(pos.line + 1, 0))); + if (cmp(range.anchor, anchor) > 0) { + head = range.head; + anchor = minPos(oldRange.from(), range.anchor); + } else { + head = range.anchor; + anchor = maxPos(oldRange.to(), range.head); + } + } + var ranges = startSel.ranges.slice(0); + ranges[ourIndex] = new Range(clipPos(doc, anchor), head); + setSelection(doc, normalizeSelection(ranges, ourIndex), sel_mouse); + } + } + + var editorSize = display.wrapper.getBoundingClientRect(); + // Used to ensure timeout re-tries don't fire when another extend + // happened in the meantime (clearTimeout isn't reliable -- at + // least on Chrome, the timeouts still happen even when cleared, + // if the clear happens after their scheduled firing time). + var counter = 0; + + function extend(e) { + var curCount = ++counter; + var cur = posFromMouse(cm, e, true, type == "rect"); + if (!cur) return; + if (cmp(cur, lastPos) != 0) { + cm.curOp.focus = activeElt(); + extendTo(cur); + var visible = visibleLines(display, doc); + if (cur.line >= visible.to || cur.line < visible.from) + setTimeout(operation(cm, function(){if (counter == curCount) extend(e);}), 150); + } else { + var outside = e.clientY < editorSize.top ? -20 : e.clientY > editorSize.bottom ? 20 : 0; + if (outside) setTimeout(operation(cm, function() { + if (counter != curCount) return; + display.scroller.scrollTop += outside; + extend(e); + }), 50); + } + } + + function done(e) { + counter = Infinity; + e_preventDefault(e); + display.input.focus(); + off(document, "mousemove", move); + off(document, "mouseup", up); + doc.history.lastSelOrigin = null; + } + + var move = operation(cm, function(e) { + if (!e_button(e)) done(e); + else extend(e); + }); + var up = operation(cm, done); + on(document, "mousemove", move); + on(document, "mouseup", up); + } + + // Determines whether an event happened in the gutter, and fires the + // handlers for the corresponding event. + function gutterEvent(cm, e, type, prevent, signalfn) { + try { var mX = e.clientX, mY = e.clientY; } + catch(e) { return false; } + if (mX >= Math.floor(cm.display.gutters.getBoundingClientRect().right)) return false; + if (prevent) e_preventDefault(e); + + var display = cm.display; + var lineBox = display.lineDiv.getBoundingClientRect(); + + if (mY > lineBox.bottom || !hasHandler(cm, type)) return e_defaultPrevented(e); + mY -= lineBox.top - display.viewOffset; + + for (var i = 0; i < cm.options.gutters.length; ++i) { + var g = display.gutters.childNodes[i]; + if (g && g.getBoundingClientRect().right >= mX) { + var line = lineAtHeight(cm.doc, mY); + var gutter = cm.options.gutters[i]; + signalfn(cm, type, cm, line, gutter, e); + return e_defaultPrevented(e); + } + } + } + + function clickInGutter(cm, e) { + return gutterEvent(cm, e, "gutterClick", true, signalLater); + } + + // Kludge to work around strange IE behavior where it'll sometimes + // re-fire a series of drag-related events right after the drop (#1551) + var lastDrop = 0; + + function onDrop(e) { + var cm = this; + if (signalDOMEvent(cm, e) || eventInWidget(cm.display, e)) + return; + e_preventDefault(e); + if (ie) lastDrop = +new Date; + var pos = posFromMouse(cm, e, true), files = e.dataTransfer.files; + if (!pos || isReadOnly(cm)) return; + // Might be a file drop, in which case we simply extract the text + // and insert it. + if (files && files.length && window.FileReader && window.File) { + var n = files.length, text = Array(n), read = 0; + var loadFile = function(file, i) { + var reader = new FileReader; + reader.onload = operation(cm, function() { + text[i] = reader.result; + if (++read == n) { + pos = clipPos(cm.doc, pos); + var change = {from: pos, to: pos, text: splitLines(text.join("\n")), origin: "paste"}; + makeChange(cm.doc, change); + setSelectionReplaceHistory(cm.doc, simpleSelection(pos, changeEnd(change))); + } + }); + reader.readAsText(file); + }; + for (var i = 0; i < n; ++i) loadFile(files[i], i); + } else { // Normal drop + // Don't do a replace if the drop happened inside of the selected text. + if (cm.state.draggingText && cm.doc.sel.contains(pos) > -1) { + cm.state.draggingText(e); + // Ensure the editor is re-focused + setTimeout(function() {cm.display.input.focus();}, 20); + return; + } + try { + var text = e.dataTransfer.getData("Text"); + if (text) { + if (cm.state.draggingText && !(mac ? e.altKey : e.ctrlKey)) + var selected = cm.listSelections(); + setSelectionNoUndo(cm.doc, simpleSelection(pos, pos)); + if (selected) for (var i = 0; i < selected.length; ++i) + replaceRange(cm.doc, "", selected[i].anchor, selected[i].head, "drag"); + cm.replaceSelection(text, "around", "paste"); + cm.display.input.focus(); + } + } + catch(e){} + } + } + + function onDragStart(cm, e) { + if (ie && (!cm.state.draggingText || +new Date - lastDrop < 100)) { e_stop(e); return; } + if (signalDOMEvent(cm, e) || eventInWidget(cm.display, e)) return; + + e.dataTransfer.setData("Text", cm.getSelection()); + + // Use dummy image instead of default browsers image. + // Recent Safari (~6.0.2) have a tendency to segfault when this happens, so we don't do it there. + if (e.dataTransfer.setDragImage && !safari) { + var img = elt("img", null, null, "position: fixed; left: 0; top: 0;"); + img.src = "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="; + if (presto) { + img.width = img.height = 1; + cm.display.wrapper.appendChild(img); + // Force a relayout, or Opera won't use our image for some obscure reason + img._top = img.offsetTop; + } + e.dataTransfer.setDragImage(img, 0, 0); + if (presto) img.parentNode.removeChild(img); + } + } + + // SCROLL EVENTS + + // Sync the scrollable area and scrollbars, ensure the viewport + // covers the visible area. + function setScrollTop(cm, val) { + if (Math.abs(cm.doc.scrollTop - val) < 2) return; + cm.doc.scrollTop = val; + if (!gecko) updateDisplaySimple(cm, {top: val}); + if (cm.display.scroller.scrollTop != val) cm.display.scroller.scrollTop = val; + cm.display.scrollbars.setScrollTop(val); + if (gecko) updateDisplaySimple(cm); + startWorker(cm, 100); + } + // Sync scroller and scrollbar, ensure the gutter elements are + // aligned. + function setScrollLeft(cm, val, isScroller) { + if (isScroller ? val == cm.doc.scrollLeft : Math.abs(cm.doc.scrollLeft - val) < 2) return; + val = Math.min(val, cm.display.scroller.scrollWidth - cm.display.scroller.clientWidth); + cm.doc.scrollLeft = val; + alignHorizontally(cm); + if (cm.display.scroller.scrollLeft != val) cm.display.scroller.scrollLeft = val; + cm.display.scrollbars.setScrollLeft(val); + } + + // Since the delta values reported on mouse wheel events are + // unstandardized between browsers and even browser versions, and + // generally horribly unpredictable, this code starts by measuring + // the scroll effect that the first few mouse wheel events have, + // and, from that, detects the way it can convert deltas to pixel + // offsets afterwards. + // + // The reason we want to know the amount a wheel event will scroll + // is that it gives us a chance to update the display before the + // actual scrolling happens, reducing flickering. + + var wheelSamples = 0, wheelPixelsPerUnit = null; + // Fill in a browser-detected starting value on browsers where we + // know one. These don't have to be accurate -- the result of them + // being wrong would just be a slight flicker on the first wheel + // scroll (if it is large enough). + if (ie) wheelPixelsPerUnit = -.53; + else if (gecko) wheelPixelsPerUnit = 15; + else if (chrome) wheelPixelsPerUnit = -.7; + else if (safari) wheelPixelsPerUnit = -1/3; + + var wheelEventDelta = function(e) { + var dx = e.wheelDeltaX, dy = e.wheelDeltaY; + if (dx == null && e.detail && e.axis == e.HORIZONTAL_AXIS) dx = e.detail; + if (dy == null && e.detail && e.axis == e.VERTICAL_AXIS) dy = e.detail; + else if (dy == null) dy = e.wheelDelta; + return {x: dx, y: dy}; + }; + CodeMirror.wheelEventPixels = function(e) { + var delta = wheelEventDelta(e); + delta.x *= wheelPixelsPerUnit; + delta.y *= wheelPixelsPerUnit; + return delta; + }; + + function onScrollWheel(cm, e) { + var delta = wheelEventDelta(e), dx = delta.x, dy = delta.y; + + var display = cm.display, scroll = display.scroller; + // Quit if there's nothing to scroll here + if (!(dx && scroll.scrollWidth > scroll.clientWidth || + dy && scroll.scrollHeight > scroll.clientHeight)) return; + + // Webkit browsers on OS X abort momentum scrolls when the target + // of the scroll event is removed from the scrollable element. + // This hack (see related code in patchDisplay) makes sure the + // element is kept around. + if (dy && mac && webkit) { + outer: for (var cur = e.target, view = display.view; cur != scroll; cur = cur.parentNode) { + for (var i = 0; i < view.length; i++) { + if (view[i].node == cur) { + cm.display.currentWheelTarget = cur; + break outer; + } + } + } + } + + // On some browsers, horizontal scrolling will cause redraws to + // happen before the gutter has been realigned, causing it to + // wriggle around in a most unseemly way. When we have an + // estimated pixels/delta value, we just handle horizontal + // scrolling entirely here. It'll be slightly off from native, but + // better than glitching out. + if (dx && !gecko && !presto && wheelPixelsPerUnit != null) { + if (dy) + setScrollTop(cm, Math.max(0, Math.min(scroll.scrollTop + dy * wheelPixelsPerUnit, scroll.scrollHeight - scroll.clientHeight))); + setScrollLeft(cm, Math.max(0, Math.min(scroll.scrollLeft + dx * wheelPixelsPerUnit, scroll.scrollWidth - scroll.clientWidth))); + e_preventDefault(e); + display.wheelStartX = null; // Abort measurement, if in progress + return; + } + + // 'Project' the visible viewport to cover the area that is being + // scrolled into view (if we know enough to estimate it). + if (dy && wheelPixelsPerUnit != null) { + var pixels = dy * wheelPixelsPerUnit; + var top = cm.doc.scrollTop, bot = top + display.wrapper.clientHeight; + if (pixels < 0) top = Math.max(0, top + pixels - 50); + else bot = Math.min(cm.doc.height, bot + pixels + 50); + updateDisplaySimple(cm, {top: top, bottom: bot}); + } + + if (wheelSamples < 20) { + if (display.wheelStartX == null) { + display.wheelStartX = scroll.scrollLeft; display.wheelStartY = scroll.scrollTop; + display.wheelDX = dx; display.wheelDY = dy; + setTimeout(function() { + if (display.wheelStartX == null) return; + var movedX = scroll.scrollLeft - display.wheelStartX; + var movedY = scroll.scrollTop - display.wheelStartY; + var sample = (movedY && display.wheelDY && movedY / display.wheelDY) || + (movedX && display.wheelDX && movedX / display.wheelDX); + display.wheelStartX = display.wheelStartY = null; + if (!sample) return; + wheelPixelsPerUnit = (wheelPixelsPerUnit * wheelSamples + sample) / (wheelSamples + 1); + ++wheelSamples; + }, 200); + } else { + display.wheelDX += dx; display.wheelDY += dy; + } + } + } + + // KEY EVENTS + + // Run a handler that was bound to a key. + function doHandleBinding(cm, bound, dropShift) { + if (typeof bound == "string") { + bound = commands[bound]; + if (!bound) return false; + } + // Ensure previous input has been read, so that the handler sees a + // consistent view of the document + cm.display.input.ensurePolled(); + var prevShift = cm.display.shift, done = false; + try { + if (isReadOnly(cm)) cm.state.suppressEdits = true; + if (dropShift) cm.display.shift = false; + done = bound(cm) != Pass; + } finally { + cm.display.shift = prevShift; + cm.state.suppressEdits = false; + } + return done; + } + + function lookupKeyForEditor(cm, name, handle) { + for (var i = 0; i < cm.state.keyMaps.length; i++) { + var result = lookupKey(name, cm.state.keyMaps[i], handle, cm); + if (result) return result; + } + return (cm.options.extraKeys && lookupKey(name, cm.options.extraKeys, handle, cm)) + || lookupKey(name, cm.options.keyMap, handle, cm); + } + + var stopSeq = new Delayed; + function dispatchKey(cm, name, e, handle) { + var seq = cm.state.keySeq; + if (seq) { + if (isModifierKey(name)) return "handled"; + stopSeq.set(50, function() { + if (cm.state.keySeq == seq) { + cm.state.keySeq = null; + cm.display.input.reset(); + } + }); + name = seq + " " + name; + } + var result = lookupKeyForEditor(cm, name, handle); + + if (result == "multi") + cm.state.keySeq = name; + if (result == "handled") + signalLater(cm, "keyHandled", cm, name, e); + + if (result == "handled" || result == "multi") { + e_preventDefault(e); + restartBlink(cm); + } + + if (seq && !result && /\'$/.test(name)) { + e_preventDefault(e); + return true; + } + return !!result; + } + + // Handle a key from the keydown event. + function handleKeyBinding(cm, e) { + var name = keyName(e, true); + if (!name) return false; + + if (e.shiftKey && !cm.state.keySeq) { + // First try to resolve full name (including 'Shift-'). Failing + // that, see if there is a cursor-motion command (starting with + // 'go') bound to the keyname without 'Shift-'. + return dispatchKey(cm, "Shift-" + name, e, function(b) {return doHandleBinding(cm, b, true);}) + || dispatchKey(cm, name, e, function(b) { + if (typeof b == "string" ? /^go[A-Z]/.test(b) : b.motion) + return doHandleBinding(cm, b); + }); + } else { + return dispatchKey(cm, name, e, function(b) { return doHandleBinding(cm, b); }); + } + } + + // Handle a key from the keypress event + function handleCharBinding(cm, e, ch) { + return dispatchKey(cm, "'" + ch + "'", e, + function(b) { return doHandleBinding(cm, b, true); }); + } + + var lastStoppedKey = null; + function onKeyDown(e) { + var cm = this; + cm.curOp.focus = activeElt(); + if (signalDOMEvent(cm, e)) return; + // IE does strange things with escape. + if (ie && ie_version < 11 && e.keyCode == 27) e.returnValue = false; + var code = e.keyCode; + cm.display.shift = code == 16 || e.shiftKey; + var handled = handleKeyBinding(cm, e); + if (presto) { + lastStoppedKey = handled ? code : null; + // Opera has no cut event... we try to at least catch the key combo + if (!handled && code == 88 && !hasCopyEvent && (mac ? e.metaKey : e.ctrlKey)) + cm.replaceSelection("", null, "cut"); + } + + // Turn mouse into crosshair when Alt is held on Mac. + if (code == 18 && !/\bCodeMirror-crosshair\b/.test(cm.display.lineDiv.className)) + showCrossHair(cm); + } + + function showCrossHair(cm) { + var lineDiv = cm.display.lineDiv; + addClass(lineDiv, "CodeMirror-crosshair"); + + function up(e) { + if (e.keyCode == 18 || !e.altKey) { + rmClass(lineDiv, "CodeMirror-crosshair"); + off(document, "keyup", up); + off(document, "mouseover", up); + } + } + on(document, "keyup", up); + on(document, "mouseover", up); + } + + function onKeyUp(e) { + if (e.keyCode == 16) this.doc.sel.shift = false; + signalDOMEvent(this, e); + } + + function onKeyPress(e) { + var cm = this; + if (eventInWidget(cm.display, e) || signalDOMEvent(cm, e) || e.ctrlKey && !e.altKey || mac && e.metaKey) return; + var keyCode = e.keyCode, charCode = e.charCode; + if (presto && keyCode == lastStoppedKey) {lastStoppedKey = null; e_preventDefault(e); return;} + if ((presto && (!e.which || e.which < 10)) && handleKeyBinding(cm, e)) return; + var ch = String.fromCharCode(charCode == null ? keyCode : charCode); + if (handleCharBinding(cm, e, ch)) return; + cm.display.input.onKeyPress(e); + } + + // FOCUS/BLUR EVENTS + + function delayBlurEvent(cm) { + cm.state.delayingBlurEvent = true; + setTimeout(function() { + if (cm.state.delayingBlurEvent) { + cm.state.delayingBlurEvent = false; + onBlur(cm); + } + }, 100); + } + + function onFocus(cm) { + if (cm.state.delayingBlurEvent) cm.state.delayingBlurEvent = false; + + if (cm.options.readOnly == "nocursor") return; + if (!cm.state.focused) { + signal(cm, "focus", cm); + cm.state.focused = true; + addClass(cm.display.wrapper, "CodeMirror-focused"); + // This test prevents this from firing when a context + // menu is closed (since the input reset would kill the + // select-all detection hack) + if (!cm.curOp && cm.display.selForContextMenu != cm.doc.sel) { + cm.display.input.reset(); + if (webkit) setTimeout(function() { cm.display.input.reset(true); }, 20); // Issue #1730 + } + cm.display.input.receivedFocus(); + } + restartBlink(cm); + } + function onBlur(cm) { + if (cm.state.delayingBlurEvent) return; + + if (cm.state.focused) { + signal(cm, "blur", cm); + cm.state.focused = false; + rmClass(cm.display.wrapper, "CodeMirror-focused"); + } + clearInterval(cm.display.blinker); + setTimeout(function() {if (!cm.state.focused) cm.display.shift = false;}, 150); + } + + // CONTEXT MENU HANDLING + + // To make the context menu work, we need to briefly unhide the + // textarea (making it as unobtrusive as possible) to let the + // right-click take effect on it. + function onContextMenu(cm, e) { + if (eventInWidget(cm.display, e) || contextMenuInGutter(cm, e)) return; + cm.display.input.onContextMenu(e); + } + + function contextMenuInGutter(cm, e) { + if (!hasHandler(cm, "gutterContextMenu")) return false; + return gutterEvent(cm, e, "gutterContextMenu", false, signal); + } + + // UPDATING + + // Compute the position of the end of a change (its 'to' property + // refers to the pre-change end). + var changeEnd = CodeMirror.changeEnd = function(change) { + if (!change.text) return change.to; + return Pos(change.from.line + change.text.length - 1, + lst(change.text).length + (change.text.length == 1 ? change.from.ch : 0)); + }; + + // Adjust a position to refer to the post-change position of the + // same text, or the end of the change if the change covers it. + function adjustForChange(pos, change) { + if (cmp(pos, change.from) < 0) return pos; + if (cmp(pos, change.to) <= 0) return changeEnd(change); + + var line = pos.line + change.text.length - (change.to.line - change.from.line) - 1, ch = pos.ch; + if (pos.line == change.to.line) ch += changeEnd(change).ch - change.to.ch; + return Pos(line, ch); + } + + function computeSelAfterChange(doc, change) { + var out = []; + for (var i = 0; i < doc.sel.ranges.length; i++) { + var range = doc.sel.ranges[i]; + out.push(new Range(adjustForChange(range.anchor, change), + adjustForChange(range.head, change))); + } + return normalizeSelection(out, doc.sel.primIndex); + } + + function offsetPos(pos, old, nw) { + if (pos.line == old.line) + return Pos(nw.line, pos.ch - old.ch + nw.ch); + else + return Pos(nw.line + (pos.line - old.line), pos.ch); + } + + // Used by replaceSelections to allow moving the selection to the + // start or around the replaced test. Hint may be "start" or "around". + function computeReplacedSel(doc, changes, hint) { + var out = []; + var oldPrev = Pos(doc.first, 0), newPrev = oldPrev; + for (var i = 0; i < changes.length; i++) { + var change = changes[i]; + var from = offsetPos(change.from, oldPrev, newPrev); + var to = offsetPos(changeEnd(change), oldPrev, newPrev); + oldPrev = change.to; + newPrev = to; + if (hint == "around") { + var range = doc.sel.ranges[i], inv = cmp(range.head, range.anchor) < 0; + out[i] = new Range(inv ? to : from, inv ? from : to); + } else { + out[i] = new Range(from, from); + } + } + return new Selection(out, doc.sel.primIndex); + } + + // Allow "beforeChange" event handlers to influence a change + function filterChange(doc, change, update) { + var obj = { + canceled: false, + from: change.from, + to: change.to, + text: change.text, + origin: change.origin, + cancel: function() { this.canceled = true; } + }; + if (update) obj.update = function(from, to, text, origin) { + if (from) this.from = clipPos(doc, from); + if (to) this.to = clipPos(doc, to); + if (text) this.text = text; + if (origin !== undefined) this.origin = origin; + }; + signal(doc, "beforeChange", doc, obj); + if (doc.cm) signal(doc.cm, "beforeChange", doc.cm, obj); + + if (obj.canceled) return null; + return {from: obj.from, to: obj.to, text: obj.text, origin: obj.origin}; + } + + // Apply a change to a document, and add it to the document's + // history, and propagating it to all linked documents. + function makeChange(doc, change, ignoreReadOnly) { + if (doc.cm) { + if (!doc.cm.curOp) return operation(doc.cm, makeChange)(doc, change, ignoreReadOnly); + if (doc.cm.state.suppressEdits) return; + } + + if (hasHandler(doc, "beforeChange") || doc.cm && hasHandler(doc.cm, "beforeChange")) { + change = filterChange(doc, change, true); + if (!change) return; + } + + // Possibly split or suppress the update based on the presence + // of read-only spans in its range. + var split = sawReadOnlySpans && !ignoreReadOnly && removeReadOnlyRanges(doc, change.from, change.to); + if (split) { + for (var i = split.length - 1; i >= 0; --i) + makeChangeInner(doc, {from: split[i].from, to: split[i].to, text: i ? [""] : change.text}); + } else { + makeChangeInner(doc, change); + } + } + + function makeChangeInner(doc, change) { + if (change.text.length == 1 && change.text[0] == "" && cmp(change.from, change.to) == 0) return; + var selAfter = computeSelAfterChange(doc, change); + addChangeToHistory(doc, change, selAfter, doc.cm ? doc.cm.curOp.id : NaN); + + makeChangeSingleDoc(doc, change, selAfter, stretchSpansOverChange(doc, change)); + var rebased = []; + + linkedDocs(doc, function(doc, sharedHist) { + if (!sharedHist && indexOf(rebased, doc.history) == -1) { + rebaseHist(doc.history, change); + rebased.push(doc.history); + } + makeChangeSingleDoc(doc, change, null, stretchSpansOverChange(doc, change)); + }); + } + + // Revert a change stored in a document's history. + function makeChangeFromHistory(doc, type, allowSelectionOnly) { + if (doc.cm && doc.cm.state.suppressEdits) return; + + var hist = doc.history, event, selAfter = doc.sel; + var source = type == "undo" ? hist.done : hist.undone, dest = type == "undo" ? hist.undone : hist.done; + + // Verify that there is a useable event (so that ctrl-z won't + // needlessly clear selection events) + for (var i = 0; i < source.length; i++) { + event = source[i]; + if (allowSelectionOnly ? event.ranges && !event.equals(doc.sel) : !event.ranges) + break; + } + if (i == source.length) return; + hist.lastOrigin = hist.lastSelOrigin = null; + + for (;;) { + event = source.pop(); + if (event.ranges) { + pushSelectionToHistory(event, dest); + if (allowSelectionOnly && !event.equals(doc.sel)) { + setSelection(doc, event, {clearRedo: false}); + return; + } + selAfter = event; + } + else break; + } + + // Build up a reverse change object to add to the opposite history + // stack (redo when undoing, and vice versa). + var antiChanges = []; + pushSelectionToHistory(selAfter, dest); + dest.push({changes: antiChanges, generation: hist.generation}); + hist.generation = event.generation || ++hist.maxGeneration; + + var filter = hasHandler(doc, "beforeChange") || doc.cm && hasHandler(doc.cm, "beforeChange"); + + for (var i = event.changes.length - 1; i >= 0; --i) { + var change = event.changes[i]; + change.origin = type; + if (filter && !filterChange(doc, change, false)) { + source.length = 0; + return; + } + + antiChanges.push(historyChangeFromChange(doc, change)); + + var after = i ? computeSelAfterChange(doc, change) : lst(source); + makeChangeSingleDoc(doc, change, after, mergeOldSpans(doc, change)); + if (!i && doc.cm) doc.cm.scrollIntoView({from: change.from, to: changeEnd(change)}); + var rebased = []; + + // Propagate to the linked documents + linkedDocs(doc, function(doc, sharedHist) { + if (!sharedHist && indexOf(rebased, doc.history) == -1) { + rebaseHist(doc.history, change); + rebased.push(doc.history); + } + makeChangeSingleDoc(doc, change, null, mergeOldSpans(doc, change)); + }); + } + } + + // Sub-views need their line numbers shifted when text is added + // above or below them in the parent document. + function shiftDoc(doc, distance) { + if (distance == 0) return; + doc.first += distance; + doc.sel = new Selection(map(doc.sel.ranges, function(range) { + return new Range(Pos(range.anchor.line + distance, range.anchor.ch), + Pos(range.head.line + distance, range.head.ch)); + }), doc.sel.primIndex); + if (doc.cm) { + regChange(doc.cm, doc.first, doc.first - distance, distance); + for (var d = doc.cm.display, l = d.viewFrom; l < d.viewTo; l++) + regLineChange(doc.cm, l, "gutter"); + } + } + + // More lower-level change function, handling only a single document + // (not linked ones). + function makeChangeSingleDoc(doc, change, selAfter, spans) { + if (doc.cm && !doc.cm.curOp) + return operation(doc.cm, makeChangeSingleDoc)(doc, change, selAfter, spans); + + if (change.to.line < doc.first) { + shiftDoc(doc, change.text.length - 1 - (change.to.line - change.from.line)); + return; + } + if (change.from.line > doc.lastLine()) return; + + // Clip the change to the size of this doc + if (change.from.line < doc.first) { + var shift = change.text.length - 1 - (doc.first - change.from.line); + shiftDoc(doc, shift); + change = {from: Pos(doc.first, 0), to: Pos(change.to.line + shift, change.to.ch), + text: [lst(change.text)], origin: change.origin}; + } + var last = doc.lastLine(); + if (change.to.line > last) { + change = {from: change.from, to: Pos(last, getLine(doc, last).text.length), + text: [change.text[0]], origin: change.origin}; + } + + change.removed = getBetween(doc, change.from, change.to); + + if (!selAfter) selAfter = computeSelAfterChange(doc, change); + if (doc.cm) makeChangeSingleDocInEditor(doc.cm, change, spans); + else updateDoc(doc, change, spans); + setSelectionNoUndo(doc, selAfter, sel_dontScroll); + } + + // Handle the interaction of a change to a document with the editor + // that this document is part of. + function makeChangeSingleDocInEditor(cm, change, spans) { + var doc = cm.doc, display = cm.display, from = change.from, to = change.to; + + var recomputeMaxLength = false, checkWidthStart = from.line; + if (!cm.options.lineWrapping) { + checkWidthStart = lineNo(visualLine(getLine(doc, from.line))); + doc.iter(checkWidthStart, to.line + 1, function(line) { + if (line == display.maxLine) { + recomputeMaxLength = true; + return true; + } + }); + } + + if (doc.sel.contains(change.from, change.to) > -1) + signalCursorActivity(cm); + + updateDoc(doc, change, spans, estimateHeight(cm)); + + if (!cm.options.lineWrapping) { + doc.iter(checkWidthStart, from.line + change.text.length, function(line) { + var len = lineLength(line); + if (len > display.maxLineLength) { + display.maxLine = line; + display.maxLineLength = len; + display.maxLineChanged = true; + recomputeMaxLength = false; + } + }); + if (recomputeMaxLength) cm.curOp.updateMaxLine = true; + } + + // Adjust frontier, schedule worker + doc.frontier = Math.min(doc.frontier, from.line); + startWorker(cm, 400); + + var lendiff = change.text.length - (to.line - from.line) - 1; + // Remember that these lines changed, for updating the display + if (change.full) + regChange(cm); + else if (from.line == to.line && change.text.length == 1 && !isWholeLineUpdate(cm.doc, change)) + regLineChange(cm, from.line, "text"); + else + regChange(cm, from.line, to.line + 1, lendiff); + + var changesHandler = hasHandler(cm, "changes"), changeHandler = hasHandler(cm, "change"); + if (changeHandler || changesHandler) { + var obj = { + from: from, to: to, + text: change.text, + removed: change.removed, + origin: change.origin + }; + if (changeHandler) signalLater(cm, "change", cm, obj); + if (changesHandler) (cm.curOp.changeObjs || (cm.curOp.changeObjs = [])).push(obj); + } + cm.display.selForContextMenu = null; + } + + function replaceRange(doc, code, from, to, origin) { + if (!to) to = from; + if (cmp(to, from) < 0) { var tmp = to; to = from; from = tmp; } + if (typeof code == "string") code = splitLines(code); + makeChange(doc, {from: from, to: to, text: code, origin: origin}); + } + + // SCROLLING THINGS INTO VIEW + + // If an editor sits on the top or bottom of the window, partially + // scrolled out of view, this ensures that the cursor is visible. + function maybeScrollWindow(cm, coords) { + if (signalDOMEvent(cm, "scrollCursorIntoView")) return; + + var display = cm.display, box = display.sizer.getBoundingClientRect(), doScroll = null; + if (coords.top + box.top < 0) doScroll = true; + else if (coords.bottom + box.top > (window.innerHeight || document.documentElement.clientHeight)) doScroll = false; + if (doScroll != null && !phantom) { + var scrollNode = elt("div", "\u200b", null, "position: absolute; top: " + + (coords.top - display.viewOffset - paddingTop(cm.display)) + "px; height: " + + (coords.bottom - coords.top + scrollGap(cm) + display.barHeight) + "px; left: " + + coords.left + "px; width: 2px;"); + cm.display.lineSpace.appendChild(scrollNode); + scrollNode.scrollIntoView(doScroll); + cm.display.lineSpace.removeChild(scrollNode); + } + } + + // Scroll a given position into view (immediately), verifying that + // it actually became visible (as line heights are accurately + // measured, the position of something may 'drift' during drawing). + function scrollPosIntoView(cm, pos, end, margin) { + if (margin == null) margin = 0; + for (var limit = 0; limit < 5; limit++) { + var changed = false, coords = cursorCoords(cm, pos); + var endCoords = !end || end == pos ? coords : cursorCoords(cm, end); + var scrollPos = calculateScrollPos(cm, Math.min(coords.left, endCoords.left), + Math.min(coords.top, endCoords.top) - margin, + Math.max(coords.left, endCoords.left), + Math.max(coords.bottom, endCoords.bottom) + margin); + var startTop = cm.doc.scrollTop, startLeft = cm.doc.scrollLeft; + if (scrollPos.scrollTop != null) { + setScrollTop(cm, scrollPos.scrollTop); + if (Math.abs(cm.doc.scrollTop - startTop) > 1) changed = true; + } + if (scrollPos.scrollLeft != null) { + setScrollLeft(cm, scrollPos.scrollLeft); + if (Math.abs(cm.doc.scrollLeft - startLeft) > 1) changed = true; + } + if (!changed) break; + } + return coords; + } + + // Scroll a given set of coordinates into view (immediately). + function scrollIntoView(cm, x1, y1, x2, y2) { + var scrollPos = calculateScrollPos(cm, x1, y1, x2, y2); + if (scrollPos.scrollTop != null) setScrollTop(cm, scrollPos.scrollTop); + if (scrollPos.scrollLeft != null) setScrollLeft(cm, scrollPos.scrollLeft); + } + + // Calculate a new scroll position needed to scroll the given + // rectangle into view. Returns an object with scrollTop and + // scrollLeft properties. When these are undefined, the + // vertical/horizontal position does not need to be adjusted. + function calculateScrollPos(cm, x1, y1, x2, y2) { + var display = cm.display, snapMargin = textHeight(cm.display); + if (y1 < 0) y1 = 0; + var screentop = cm.curOp && cm.curOp.scrollTop != null ? cm.curOp.scrollTop : display.scroller.scrollTop; + var screen = displayHeight(cm), result = {}; + if (y2 - y1 > screen) y2 = y1 + screen; + var docBottom = cm.doc.height + paddingVert(display); + var atTop = y1 < snapMargin, atBottom = y2 > docBottom - snapMargin; + if (y1 < screentop) { + result.scrollTop = atTop ? 0 : y1; + } else if (y2 > screentop + screen) { + var newTop = Math.min(y1, (atBottom ? docBottom : y2) - screen); + if (newTop != screentop) result.scrollTop = newTop; + } + + var screenleft = cm.curOp && cm.curOp.scrollLeft != null ? cm.curOp.scrollLeft : display.scroller.scrollLeft; + var screenw = displayWidth(cm) - (cm.options.fixedGutter ? display.gutters.offsetWidth : 0); + var tooWide = x2 - x1 > screenw; + if (tooWide) x2 = x1 + screenw; + if (x1 < 10) + result.scrollLeft = 0; + else if (x1 < screenleft) + result.scrollLeft = Math.max(0, x1 - (tooWide ? 0 : 10)); + else if (x2 > screenw + screenleft - 3) + result.scrollLeft = x2 + (tooWide ? 0 : 10) - screenw; + return result; + } + + // Store a relative adjustment to the scroll position in the current + // operation (to be applied when the operation finishes). + function addToScrollPos(cm, left, top) { + if (left != null || top != null) resolveScrollToPos(cm); + if (left != null) + cm.curOp.scrollLeft = (cm.curOp.scrollLeft == null ? cm.doc.scrollLeft : cm.curOp.scrollLeft) + left; + if (top != null) + cm.curOp.scrollTop = (cm.curOp.scrollTop == null ? cm.doc.scrollTop : cm.curOp.scrollTop) + top; + } + + // Make sure that at the end of the operation the current cursor is + // shown. + function ensureCursorVisible(cm) { + resolveScrollToPos(cm); + var cur = cm.getCursor(), from = cur, to = cur; + if (!cm.options.lineWrapping) { + from = cur.ch ? Pos(cur.line, cur.ch - 1) : cur; + to = Pos(cur.line, cur.ch + 1); + } + cm.curOp.scrollToPos = {from: from, to: to, margin: cm.options.cursorScrollMargin, isCursor: true}; + } + + // When an operation has its scrollToPos property set, and another + // scroll action is applied before the end of the operation, this + // 'simulates' scrolling that position into view in a cheap way, so + // that the effect of intermediate scroll commands is not ignored. + function resolveScrollToPos(cm) { + var range = cm.curOp.scrollToPos; + if (range) { + cm.curOp.scrollToPos = null; + var from = estimateCoords(cm, range.from), to = estimateCoords(cm, range.to); + var sPos = calculateScrollPos(cm, Math.min(from.left, to.left), + Math.min(from.top, to.top) - range.margin, + Math.max(from.right, to.right), + Math.max(from.bottom, to.bottom) + range.margin); + cm.scrollTo(sPos.scrollLeft, sPos.scrollTop); + } + } + + // API UTILITIES + + // Indent the given line. The how parameter can be "smart", + // "add"/null, "subtract", or "prev". When aggressive is false + // (typically set to true for forced single-line indents), empty + // lines are not indented, and places where the mode returns Pass + // are left alone. + function indentLine(cm, n, how, aggressive) { + var doc = cm.doc, state; + if (how == null) how = "add"; + if (how == "smart") { + // Fall back to "prev" when the mode doesn't have an indentation + // method. + if (!doc.mode.indent) how = "prev"; + else state = getStateBefore(cm, n); + } + + var tabSize = cm.options.tabSize; + var line = getLine(doc, n), curSpace = countColumn(line.text, null, tabSize); + if (line.stateAfter) line.stateAfter = null; + var curSpaceString = line.text.match(/^\s*/)[0], indentation; + if (!aggressive && !/\S/.test(line.text)) { + indentation = 0; + how = "not"; + } else if (how == "smart") { + indentation = doc.mode.indent(state, line.text.slice(curSpaceString.length), line.text); + if (indentation == Pass || indentation > 150) { + if (!aggressive) return; + how = "prev"; + } + } + if (how == "prev") { + if (n > doc.first) indentation = countColumn(getLine(doc, n-1).text, null, tabSize); + else indentation = 0; + } else if (how == "add") { + indentation = curSpace + cm.options.indentUnit; + } else if (how == "subtract") { + indentation = curSpace - cm.options.indentUnit; + } else if (typeof how == "number") { + indentation = curSpace + how; + } + indentation = Math.max(0, indentation); + + var indentString = "", pos = 0; + if (cm.options.indentWithTabs) + for (var i = Math.floor(indentation / tabSize); i; --i) {pos += tabSize; indentString += "\t";} + if (pos < indentation) indentString += spaceStr(indentation - pos); + + if (indentString != curSpaceString) { + replaceRange(doc, indentString, Pos(n, 0), Pos(n, curSpaceString.length), "+input"); + } else { + // Ensure that, if the cursor was in the whitespace at the start + // of the line, it is moved to the end of that space. + for (var i = 0; i < doc.sel.ranges.length; i++) { + var range = doc.sel.ranges[i]; + if (range.head.line == n && range.head.ch < curSpaceString.length) { + var pos = Pos(n, curSpaceString.length); + replaceOneSelection(doc, i, new Range(pos, pos)); + break; + } + } + } + line.stateAfter = null; + } + + // Utility for applying a change to a line by handle or number, + // returning the number and optionally registering the line as + // changed. + function changeLine(doc, handle, changeType, op) { + var no = handle, line = handle; + if (typeof handle == "number") line = getLine(doc, clipLine(doc, handle)); + else no = lineNo(handle); + if (no == null) return null; + if (op(line, no) && doc.cm) regLineChange(doc.cm, no, changeType); + return line; + } + + // Helper for deleting text near the selection(s), used to implement + // backspace, delete, and similar functionality. + function deleteNearSelection(cm, compute) { + var ranges = cm.doc.sel.ranges, kill = []; + // Build up a set of ranges to kill first, merging overlapping + // ranges. + for (var i = 0; i < ranges.length; i++) { + var toKill = compute(ranges[i]); + while (kill.length && cmp(toKill.from, lst(kill).to) <= 0) { + var replaced = kill.pop(); + if (cmp(replaced.from, toKill.from) < 0) { + toKill.from = replaced.from; + break; + } + } + kill.push(toKill); + } + // Next, remove those actual ranges. + runInOp(cm, function() { + for (var i = kill.length - 1; i >= 0; i--) + replaceRange(cm.doc, "", kill[i].from, kill[i].to, "+delete"); + ensureCursorVisible(cm); + }); + } + + // Used for horizontal relative motion. Dir is -1 or 1 (left or + // right), unit can be "char", "column" (like char, but doesn't + // cross line boundaries), "word" (across next word), or "group" (to + // the start of next group of word or non-word-non-whitespace + // chars). The visually param controls whether, in right-to-left + // text, direction 1 means to move towards the next index in the + // string, or towards the character to the right of the current + // position. The resulting position will have a hitSide=true + // property if it reached the end of the document. + function findPosH(doc, pos, dir, unit, visually) { + var line = pos.line, ch = pos.ch, origDir = dir; + var lineObj = getLine(doc, line); + var possible = true; + function findNextLine() { + var l = line + dir; + if (l < doc.first || l >= doc.first + doc.size) return (possible = false); + line = l; + return lineObj = getLine(doc, l); + } + function moveOnce(boundToLine) { + var next = (visually ? moveVisually : moveLogically)(lineObj, ch, dir, true); + if (next == null) { + if (!boundToLine && findNextLine()) { + if (visually) ch = (dir < 0 ? lineRight : lineLeft)(lineObj); + else ch = dir < 0 ? lineObj.text.length : 0; + } else return (possible = false); + } else ch = next; + return true; + } + + if (unit == "char") moveOnce(); + else if (unit == "column") moveOnce(true); + else if (unit == "word" || unit == "group") { + var sawType = null, group = unit == "group"; + var helper = doc.cm && doc.cm.getHelper(pos, "wordChars"); + for (var first = true;; first = false) { + if (dir < 0 && !moveOnce(!first)) break; + var cur = lineObj.text.charAt(ch) || "\n"; + var type = isWordChar(cur, helper) ? "w" + : group && cur == "\n" ? "n" + : !group || /\s/.test(cur) ? null + : "p"; + if (group && !first && !type) type = "s"; + if (sawType && sawType != type) { + if (dir < 0) {dir = 1; moveOnce();} + break; + } + + if (type) sawType = type; + if (dir > 0 && !moveOnce(!first)) break; + } + } + var result = skipAtomic(doc, Pos(line, ch), origDir, true); + if (!possible) result.hitSide = true; + return result; + } + + // For relative vertical movement. Dir may be -1 or 1. Unit can be + // "page" or "line". The resulting position will have a hitSide=true + // property if it reached the end of the document. + function findPosV(cm, pos, dir, unit) { + var doc = cm.doc, x = pos.left, y; + if (unit == "page") { + var pageSize = Math.min(cm.display.wrapper.clientHeight, window.innerHeight || document.documentElement.clientHeight); + y = pos.top + dir * (pageSize - (dir < 0 ? 1.5 : .5) * textHeight(cm.display)); + } else if (unit == "line") { + y = dir > 0 ? pos.bottom + 3 : pos.top - 3; + } + for (;;) { + var target = coordsChar(cm, x, y); + if (!target.outside) break; + if (dir < 0 ? y <= 0 : y >= doc.height) { target.hitSide = true; break; } + y += dir * 5; + } + return target; + } + + // EDITOR METHODS + + // The publicly visible API. Note that methodOp(f) means + // 'wrap f in an operation, performed on its `this` parameter'. + + // This is not the complete set of editor methods. Most of the + // methods defined on the Doc type are also injected into + // CodeMirror.prototype, for backwards compatibility and + // convenience. + + CodeMirror.prototype = { + constructor: CodeMirror, + focus: function(){window.focus(); this.display.input.focus();}, + + setOption: function(option, value) { + var options = this.options, old = options[option]; + if (options[option] == value && option != "mode") return; + options[option] = value; + if (optionHandlers.hasOwnProperty(option)) + operation(this, optionHandlers[option])(this, value, old); + }, + + getOption: function(option) {return this.options[option];}, + getDoc: function() {return this.doc;}, + + addKeyMap: function(map, bottom) { + this.state.keyMaps[bottom ? "push" : "unshift"](getKeyMap(map)); + }, + removeKeyMap: function(map) { + var maps = this.state.keyMaps; + for (var i = 0; i < maps.length; ++i) + if (maps[i] == map || maps[i].name == map) { + maps.splice(i, 1); + return true; + } + }, + + addOverlay: methodOp(function(spec, options) { + var mode = spec.token ? spec : CodeMirror.getMode(this.options, spec); + if (mode.startState) throw new Error("Overlays may not be stateful."); + this.state.overlays.push({mode: mode, modeSpec: spec, opaque: options && options.opaque}); + this.state.modeGen++; + regChange(this); + }), + removeOverlay: methodOp(function(spec) { + var overlays = this.state.overlays; + for (var i = 0; i < overlays.length; ++i) { + var cur = overlays[i].modeSpec; + if (cur == spec || typeof spec == "string" && cur.name == spec) { + overlays.splice(i, 1); + this.state.modeGen++; + regChange(this); + return; + } + } + }), + + indentLine: methodOp(function(n, dir, aggressive) { + if (typeof dir != "string" && typeof dir != "number") { + if (dir == null) dir = this.options.smartIndent ? "smart" : "prev"; + else dir = dir ? "add" : "subtract"; + } + if (isLine(this.doc, n)) indentLine(this, n, dir, aggressive); + }), + indentSelection: methodOp(function(how) { + var ranges = this.doc.sel.ranges, end = -1; + for (var i = 0; i < ranges.length; i++) { + var range = ranges[i]; + if (!range.empty()) { + var from = range.from(), to = range.to(); + var start = Math.max(end, from.line); + end = Math.min(this.lastLine(), to.line - (to.ch ? 0 : 1)) + 1; + for (var j = start; j < end; ++j) + indentLine(this, j, how); + var newRanges = this.doc.sel.ranges; + if (from.ch == 0 && ranges.length == newRanges.length && newRanges[i].from().ch > 0) + replaceOneSelection(this.doc, i, new Range(from, newRanges[i].to()), sel_dontScroll); + } else if (range.head.line > end) { + indentLine(this, range.head.line, how, true); + end = range.head.line; + if (i == this.doc.sel.primIndex) ensureCursorVisible(this); + } + } + }), + + // Fetch the parser token for a given character. Useful for hacks + // that want to inspect the mode state (say, for completion). + getTokenAt: function(pos, precise) { + return takeToken(this, pos, precise); + }, + + getLineTokens: function(line, precise) { + return takeToken(this, Pos(line), precise, true); + }, + + getTokenTypeAt: function(pos) { + pos = clipPos(this.doc, pos); + var styles = getLineStyles(this, getLine(this.doc, pos.line)); + var before = 0, after = (styles.length - 1) / 2, ch = pos.ch; + var type; + if (ch == 0) type = styles[2]; + else for (;;) { + var mid = (before + after) >> 1; + if ((mid ? styles[mid * 2 - 1] : 0) >= ch) after = mid; + else if (styles[mid * 2 + 1] < ch) before = mid + 1; + else { type = styles[mid * 2 + 2]; break; } + } + var cut = type ? type.indexOf("cm-overlay ") : -1; + return cut < 0 ? type : cut == 0 ? null : type.slice(0, cut - 1); + }, + + getModeAt: function(pos) { + var mode = this.doc.mode; + if (!mode.innerMode) return mode; + return CodeMirror.innerMode(mode, this.getTokenAt(pos).state).mode; + }, + + getHelper: function(pos, type) { + return this.getHelpers(pos, type)[0]; + }, + + getHelpers: function(pos, type) { + var found = []; + if (!helpers.hasOwnProperty(type)) return found; + var help = helpers[type], mode = this.getModeAt(pos); + if (typeof mode[type] == "string") { + if (help[mode[type]]) found.push(help[mode[type]]); + } else if (mode[type]) { + for (var i = 0; i < mode[type].length; i++) { + var val = help[mode[type][i]]; + if (val) found.push(val); + } + } else if (mode.helperType && help[mode.helperType]) { + found.push(help[mode.helperType]); + } else if (help[mode.name]) { + found.push(help[mode.name]); + } + for (var i = 0; i < help._global.length; i++) { + var cur = help._global[i]; + if (cur.pred(mode, this) && indexOf(found, cur.val) == -1) + found.push(cur.val); + } + return found; + }, + + getStateAfter: function(line, precise) { + var doc = this.doc; + line = clipLine(doc, line == null ? doc.first + doc.size - 1: line); + return getStateBefore(this, line + 1, precise); + }, + + cursorCoords: function(start, mode) { + var pos, range = this.doc.sel.primary(); + if (start == null) pos = range.head; + else if (typeof start == "object") pos = clipPos(this.doc, start); + else pos = start ? range.from() : range.to(); + return cursorCoords(this, pos, mode || "page"); + }, + + charCoords: function(pos, mode) { + return charCoords(this, clipPos(this.doc, pos), mode || "page"); + }, + + coordsChar: function(coords, mode) { + coords = fromCoordSystem(this, coords, mode || "page"); + return coordsChar(this, coords.left, coords.top); + }, + + lineAtHeight: function(height, mode) { + height = fromCoordSystem(this, {top: height, left: 0}, mode || "page").top; + return lineAtHeight(this.doc, height + this.display.viewOffset); + }, + heightAtLine: function(line, mode) { + var end = false, lineObj; + if (typeof line == "number") { + var last = this.doc.first + this.doc.size - 1; + if (line < this.doc.first) line = this.doc.first; + else if (line > last) { line = last; end = true; } + lineObj = getLine(this.doc, line); + } else { + lineObj = line; + } + return intoCoordSystem(this, lineObj, {top: 0, left: 0}, mode || "page").top + + (end ? this.doc.height - heightAtLine(lineObj) : 0); + }, + + defaultTextHeight: function() { return textHeight(this.display); }, + defaultCharWidth: function() { return charWidth(this.display); }, + + setGutterMarker: methodOp(function(line, gutterID, value) { + return changeLine(this.doc, line, "gutter", function(line) { + var markers = line.gutterMarkers || (line.gutterMarkers = {}); + markers[gutterID] = value; + if (!value && isEmpty(markers)) line.gutterMarkers = null; + return true; + }); + }), + + clearGutter: methodOp(function(gutterID) { + var cm = this, doc = cm.doc, i = doc.first; + doc.iter(function(line) { + if (line.gutterMarkers && line.gutterMarkers[gutterID]) { + line.gutterMarkers[gutterID] = null; + regLineChange(cm, i, "gutter"); + if (isEmpty(line.gutterMarkers)) line.gutterMarkers = null; + } + ++i; + }); + }), + + lineInfo: function(line) { + if (typeof line == "number") { + if (!isLine(this.doc, line)) return null; + var n = line; + line = getLine(this.doc, line); + if (!line) return null; + } else { + var n = lineNo(line); + if (n == null) return null; + } + return {line: n, handle: line, text: line.text, gutterMarkers: line.gutterMarkers, + textClass: line.textClass, bgClass: line.bgClass, wrapClass: line.wrapClass, + widgets: line.widgets}; + }, + + getViewport: function() { return {from: this.display.viewFrom, to: this.display.viewTo};}, + + addWidget: function(pos, node, scroll, vert, horiz) { + var display = this.display; + pos = cursorCoords(this, clipPos(this.doc, pos)); + var top = pos.bottom, left = pos.left; + node.style.position = "absolute"; + node.setAttribute("cm-ignore-events", "true"); + this.display.input.setUneditable(node); + display.sizer.appendChild(node); + if (vert == "over") { + top = pos.top; + } else if (vert == "above" || vert == "near") { + var vspace = Math.max(display.wrapper.clientHeight, this.doc.height), + hspace = Math.max(display.sizer.clientWidth, display.lineSpace.clientWidth); + // Default to positioning above (if specified and possible); otherwise default to positioning below + if ((vert == 'above' || pos.bottom + node.offsetHeight > vspace) && pos.top > node.offsetHeight) + top = pos.top - node.offsetHeight; + else if (pos.bottom + node.offsetHeight <= vspace) + top = pos.bottom; + if (left + node.offsetWidth > hspace) + left = hspace - node.offsetWidth; + } + node.style.top = top + "px"; + node.style.left = node.style.right = ""; + if (horiz == "right") { + left = display.sizer.clientWidth - node.offsetWidth; + node.style.right = "0px"; + } else { + if (horiz == "left") left = 0; + else if (horiz == "middle") left = (display.sizer.clientWidth - node.offsetWidth) / 2; + node.style.left = left + "px"; + } + if (scroll) + scrollIntoView(this, left, top, left + node.offsetWidth, top + node.offsetHeight); + }, + + triggerOnKeyDown: methodOp(onKeyDown), + triggerOnKeyPress: methodOp(onKeyPress), + triggerOnKeyUp: onKeyUp, + + execCommand: function(cmd) { + if (commands.hasOwnProperty(cmd)) + return commands[cmd](this); + }, + + findPosH: function(from, amount, unit, visually) { + var dir = 1; + if (amount < 0) { dir = -1; amount = -amount; } + for (var i = 0, cur = clipPos(this.doc, from); i < amount; ++i) { + cur = findPosH(this.doc, cur, dir, unit, visually); + if (cur.hitSide) break; + } + return cur; + }, + + moveH: methodOp(function(dir, unit) { + var cm = this; + cm.extendSelectionsBy(function(range) { + if (cm.display.shift || cm.doc.extend || range.empty()) + return findPosH(cm.doc, range.head, dir, unit, cm.options.rtlMoveVisually); + else + return dir < 0 ? range.from() : range.to(); + }, sel_move); + }), + + deleteH: methodOp(function(dir, unit) { + var sel = this.doc.sel, doc = this.doc; + if (sel.somethingSelected()) + doc.replaceSelection("", null, "+delete"); + else + deleteNearSelection(this, function(range) { + var other = findPosH(doc, range.head, dir, unit, false); + return dir < 0 ? {from: other, to: range.head} : {from: range.head, to: other}; + }); + }), + + findPosV: function(from, amount, unit, goalColumn) { + var dir = 1, x = goalColumn; + if (amount < 0) { dir = -1; amount = -amount; } + for (var i = 0, cur = clipPos(this.doc, from); i < amount; ++i) { + var coords = cursorCoords(this, cur, "div"); + if (x == null) x = coords.left; + else coords.left = x; + cur = findPosV(this, coords, dir, unit); + if (cur.hitSide) break; + } + return cur; + }, + + moveV: methodOp(function(dir, unit) { + var cm = this, doc = this.doc, goals = []; + var collapse = !cm.display.shift && !doc.extend && doc.sel.somethingSelected(); + doc.extendSelectionsBy(function(range) { + if (collapse) + return dir < 0 ? range.from() : range.to(); + var headPos = cursorCoords(cm, range.head, "div"); + if (range.goalColumn != null) headPos.left = range.goalColumn; + goals.push(headPos.left); + var pos = findPosV(cm, headPos, dir, unit); + if (unit == "page" && range == doc.sel.primary()) + addToScrollPos(cm, null, charCoords(cm, pos, "div").top - headPos.top); + return pos; + }, sel_move); + if (goals.length) for (var i = 0; i < doc.sel.ranges.length; i++) + doc.sel.ranges[i].goalColumn = goals[i]; + }), + + // Find the word at the given position (as returned by coordsChar). + findWordAt: function(pos) { + var doc = this.doc, line = getLine(doc, pos.line).text; + var start = pos.ch, end = pos.ch; + if (line) { + var helper = this.getHelper(pos, "wordChars"); + if ((pos.xRel < 0 || end == line.length) && start) --start; else ++end; + var startChar = line.charAt(start); + var check = isWordChar(startChar, helper) + ? function(ch) { return isWordChar(ch, helper); } + : /\s/.test(startChar) ? function(ch) {return /\s/.test(ch);} + : function(ch) {return !/\s/.test(ch) && !isWordChar(ch);}; + while (start > 0 && check(line.charAt(start - 1))) --start; + while (end < line.length && check(line.charAt(end))) ++end; + } + return new Range(Pos(pos.line, start), Pos(pos.line, end)); + }, + + toggleOverwrite: function(value) { + if (value != null && value == this.state.overwrite) return; + if (this.state.overwrite = !this.state.overwrite) + addClass(this.display.cursorDiv, "CodeMirror-overwrite"); + else + rmClass(this.display.cursorDiv, "CodeMirror-overwrite"); + + signal(this, "overwriteToggle", this, this.state.overwrite); + }, + hasFocus: function() { return this.display.input.getField() == activeElt(); }, + + scrollTo: methodOp(function(x, y) { + if (x != null || y != null) resolveScrollToPos(this); + if (x != null) this.curOp.scrollLeft = x; + if (y != null) this.curOp.scrollTop = y; + }), + getScrollInfo: function() { + var scroller = this.display.scroller; + return {left: scroller.scrollLeft, top: scroller.scrollTop, + height: scroller.scrollHeight - scrollGap(this) - this.display.barHeight, + width: scroller.scrollWidth - scrollGap(this) - this.display.barWidth, + clientHeight: displayHeight(this), clientWidth: displayWidth(this)}; + }, + + scrollIntoView: methodOp(function(range, margin) { + if (range == null) { + range = {from: this.doc.sel.primary().head, to: null}; + if (margin == null) margin = this.options.cursorScrollMargin; + } else if (typeof range == "number") { + range = {from: Pos(range, 0), to: null}; + } else if (range.from == null) { + range = {from: range, to: null}; + } + if (!range.to) range.to = range.from; + range.margin = margin || 0; + + if (range.from.line != null) { + resolveScrollToPos(this); + this.curOp.scrollToPos = range; + } else { + var sPos = calculateScrollPos(this, Math.min(range.from.left, range.to.left), + Math.min(range.from.top, range.to.top) - range.margin, + Math.max(range.from.right, range.to.right), + Math.max(range.from.bottom, range.to.bottom) + range.margin); + this.scrollTo(sPos.scrollLeft, sPos.scrollTop); + } + }), + + setSize: methodOp(function(width, height) { + var cm = this; + function interpret(val) { + return typeof val == "number" || /^\d+$/.test(String(val)) ? val + "px" : val; + } + if (width != null) cm.display.wrapper.style.width = interpret(width); + if (height != null) cm.display.wrapper.style.height = interpret(height); + if (cm.options.lineWrapping) clearLineMeasurementCache(this); + var lineNo = cm.display.viewFrom; + cm.doc.iter(lineNo, cm.display.viewTo, function(line) { + if (line.widgets) for (var i = 0; i < line.widgets.length; i++) + if (line.widgets[i].noHScroll) { regLineChange(cm, lineNo, "widget"); break; } + ++lineNo; + }); + cm.curOp.forceUpdate = true; + signal(cm, "refresh", this); + }), + + operation: function(f){return runInOp(this, f);}, + + refresh: methodOp(function() { + var oldHeight = this.display.cachedTextHeight; + regChange(this); + this.curOp.forceUpdate = true; + clearCaches(this); + this.scrollTo(this.doc.scrollLeft, this.doc.scrollTop); + updateGutterSpace(this); + if (oldHeight == null || Math.abs(oldHeight - textHeight(this.display)) > .5) + estimateLineHeights(this); + signal(this, "refresh", this); + }), + + swapDoc: methodOp(function(doc) { + var old = this.doc; + old.cm = null; + attachDoc(this, doc); + clearCaches(this); + this.display.input.reset(); + this.scrollTo(doc.scrollLeft, doc.scrollTop); + this.curOp.forceScroll = true; + signalLater(this, "swapDoc", this, old); + return old; + }), + + getInputField: function(){return this.display.input.getField();}, + getWrapperElement: function(){return this.display.wrapper;}, + getScrollerElement: function(){return this.display.scroller;}, + getGutterElement: function(){return this.display.gutters;} + }; + eventMixin(CodeMirror); + + // OPTION DEFAULTS + + // The default configuration options. + var defaults = CodeMirror.defaults = {}; + // Functions to run when options are changed. + var optionHandlers = CodeMirror.optionHandlers = {}; + + function option(name, deflt, handle, notOnInit) { + CodeMirror.defaults[name] = deflt; + if (handle) optionHandlers[name] = + notOnInit ? function(cm, val, old) {if (old != Init) handle(cm, val, old);} : handle; + } + + // Passed to option handlers when there is no old value. + var Init = CodeMirror.Init = {toString: function(){return "CodeMirror.Init";}}; + + // These two are, on init, called from the constructor because they + // have to be initialized before the editor can start at all. + option("value", "", function(cm, val) { + cm.setValue(val); + }, true); + option("mode", null, function(cm, val) { + cm.doc.modeOption = val; + loadMode(cm); + }, true); + + option("indentUnit", 2, loadMode, true); + option("indentWithTabs", false); + option("smartIndent", true); + option("tabSize", 4, function(cm) { + resetModeState(cm); + clearCaches(cm); + regChange(cm); + }, true); + option("specialChars", /[\t\u0000-\u0019\u00ad\u200b-\u200f\u2028\u2029\ufeff]/g, function(cm, val, old) { + cm.state.specialChars = new RegExp(val.source + (val.test("\t") ? "" : "|\t"), "g"); + if (old != CodeMirror.Init) cm.refresh(); + }); + option("specialCharPlaceholder", defaultSpecialCharPlaceholder, function(cm) {cm.refresh();}, true); + option("electricChars", true); + option("inputStyle", mobile ? "contenteditable" : "textarea", function() { + throw new Error("inputStyle can not (yet) be changed in a running editor"); // FIXME + }, true); + option("rtlMoveVisually", !windows); + option("wholeLineUpdateBefore", true); + + option("theme", "default", function(cm) { + themeChanged(cm); + guttersChanged(cm); + }, true); + option("keyMap", "default", function(cm, val, old) { + var next = getKeyMap(val); + var prev = old != CodeMirror.Init && getKeyMap(old); + if (prev && prev.detach) prev.detach(cm, next); + if (next.attach) next.attach(cm, prev || null); + }); + option("extraKeys", null); + + option("lineWrapping", false, wrappingChanged, true); + option("gutters", [], function(cm) { + setGuttersForLineNumbers(cm.options); + guttersChanged(cm); + }, true); + option("fixedGutter", true, function(cm, val) { + cm.display.gutters.style.left = val ? compensateForHScroll(cm.display) + "px" : "0"; + cm.refresh(); + }, true); + option("coverGutterNextToScrollbar", false, function(cm) {updateScrollbars(cm);}, true); + option("scrollbarStyle", "native", function(cm) { + initScrollbars(cm); + updateScrollbars(cm); + cm.display.scrollbars.setScrollTop(cm.doc.scrollTop); + cm.display.scrollbars.setScrollLeft(cm.doc.scrollLeft); + }, true); + option("lineNumbers", false, function(cm) { + setGuttersForLineNumbers(cm.options); + guttersChanged(cm); + }, true); + option("firstLineNumber", 1, guttersChanged, true); + option("lineNumberFormatter", function(integer) {return integer;}, guttersChanged, true); + option("showCursorWhenSelecting", false, updateSelection, true); + + option("resetSelectionOnContextMenu", true); + option("lineWiseCopyCut", true); + + option("readOnly", false, function(cm, val) { + if (val == "nocursor") { + onBlur(cm); + cm.display.input.blur(); + cm.display.disabled = true; + } else { + cm.display.disabled = false; + if (!val) cm.display.input.reset(); + } + }); + option("disableInput", false, function(cm, val) {if (!val) cm.display.input.reset();}, true); + option("dragDrop", true, dragDropChanged); + + option("cursorBlinkRate", 530); + option("cursorScrollMargin", 0); + option("cursorHeight", 1, updateSelection, true); + option("singleCursorHeightPerLine", true, updateSelection, true); + option("workTime", 100); + option("workDelay", 100); + option("flattenSpans", true, resetModeState, true); + option("addModeClass", false, resetModeState, true); + option("pollInterval", 100); + option("undoDepth", 200, function(cm, val){cm.doc.history.undoDepth = val;}); + option("historyEventDelay", 1250); + option("viewportMargin", 10, function(cm){cm.refresh();}, true); + option("maxHighlightLength", 10000, resetModeState, true); + option("moveInputWithCursor", true, function(cm, val) { + if (!val) cm.display.input.resetPosition(); + }); + + option("tabindex", null, function(cm, val) { + cm.display.input.getField().tabIndex = val || ""; + }); + option("autofocus", null); + + // MODE DEFINITION AND QUERYING + + // Known modes, by name and by MIME + var modes = CodeMirror.modes = {}, mimeModes = CodeMirror.mimeModes = {}; + + // Extra arguments are stored as the mode's dependencies, which is + // used by (legacy) mechanisms like loadmode.js to automatically + // load a mode. (Preferred mechanism is the require/define calls.) + CodeMirror.defineMode = function(name, mode) { + if (!CodeMirror.defaults.mode && name != "null") CodeMirror.defaults.mode = name; + if (arguments.length > 2) + mode.dependencies = Array.prototype.slice.call(arguments, 2); + modes[name] = mode; + }; + + CodeMirror.defineMIME = function(mime, spec) { + mimeModes[mime] = spec; + }; + + // Given a MIME type, a {name, ...options} config object, or a name + // string, return a mode config object. + CodeMirror.resolveMode = function(spec) { + if (typeof spec == "string" && mimeModes.hasOwnProperty(spec)) { + spec = mimeModes[spec]; + } else if (spec && typeof spec.name == "string" && mimeModes.hasOwnProperty(spec.name)) { + var found = mimeModes[spec.name]; + if (typeof found == "string") found = {name: found}; + spec = createObj(found, spec); + spec.name = found.name; + } else if (typeof spec == "string" && /^[\w\-]+\/[\w\-]+\+xml$/.test(spec)) { + return CodeMirror.resolveMode("application/xml"); + } + if (typeof spec == "string") return {name: spec}; + else return spec || {name: "null"}; + }; + + // Given a mode spec (anything that resolveMode accepts), find and + // initialize an actual mode object. + CodeMirror.getMode = function(options, spec) { + var spec = CodeMirror.resolveMode(spec); + var mfactory = modes[spec.name]; + if (!mfactory) return CodeMirror.getMode(options, "text/plain"); + var modeObj = mfactory(options, spec); + if (modeExtensions.hasOwnProperty(spec.name)) { + var exts = modeExtensions[spec.name]; + for (var prop in exts) { + if (!exts.hasOwnProperty(prop)) continue; + if (modeObj.hasOwnProperty(prop)) modeObj["_" + prop] = modeObj[prop]; + modeObj[prop] = exts[prop]; + } + } + modeObj.name = spec.name; + if (spec.helperType) modeObj.helperType = spec.helperType; + if (spec.modeProps) for (var prop in spec.modeProps) + modeObj[prop] = spec.modeProps[prop]; + + return modeObj; + }; + + // Minimal default mode. + CodeMirror.defineMode("null", function() { + return {token: function(stream) {stream.skipToEnd();}}; + }); + CodeMirror.defineMIME("text/plain", "null"); + + // This can be used to attach properties to mode objects from + // outside the actual mode definition. + var modeExtensions = CodeMirror.modeExtensions = {}; + CodeMirror.extendMode = function(mode, properties) { + var exts = modeExtensions.hasOwnProperty(mode) ? modeExtensions[mode] : (modeExtensions[mode] = {}); + copyObj(properties, exts); + }; + + // EXTENSIONS + + CodeMirror.defineExtension = function(name, func) { + CodeMirror.prototype[name] = func; + }; + CodeMirror.defineDocExtension = function(name, func) { + Doc.prototype[name] = func; + }; + CodeMirror.defineOption = option; + + var initHooks = []; + CodeMirror.defineInitHook = function(f) {initHooks.push(f);}; + + var helpers = CodeMirror.helpers = {}; + CodeMirror.registerHelper = function(type, name, value) { + if (!helpers.hasOwnProperty(type)) helpers[type] = CodeMirror[type] = {_global: []}; + helpers[type][name] = value; + }; + CodeMirror.registerGlobalHelper = function(type, name, predicate, value) { + CodeMirror.registerHelper(type, name, value); + helpers[type]._global.push({pred: predicate, val: value}); + }; + + // MODE STATE HANDLING + + // Utility functions for working with state. Exported because nested + // modes need to do this for their inner modes. + + var copyState = CodeMirror.copyState = function(mode, state) { + if (state === true) return state; + if (mode.copyState) return mode.copyState(state); + var nstate = {}; + for (var n in state) { + var val = state[n]; + if (val instanceof Array) val = val.concat([]); + nstate[n] = val; + } + return nstate; + }; + + var startState = CodeMirror.startState = function(mode, a1, a2) { + return mode.startState ? mode.startState(a1, a2) : true; + }; + + // Given a mode and a state (for that mode), find the inner mode and + // state at the position that the state refers to. + CodeMirror.innerMode = function(mode, state) { + while (mode.innerMode) { + var info = mode.innerMode(state); + if (!info || info.mode == mode) break; + state = info.state; + mode = info.mode; + } + return info || {mode: mode, state: state}; + }; + + // STANDARD COMMANDS + + // Commands are parameter-less actions that can be performed on an + // editor, mostly used for keybindings. + var commands = CodeMirror.commands = { + selectAll: function(cm) {cm.setSelection(Pos(cm.firstLine(), 0), Pos(cm.lastLine()), sel_dontScroll);}, + singleSelection: function(cm) { + cm.setSelection(cm.getCursor("anchor"), cm.getCursor("head"), sel_dontScroll); + }, + killLine: function(cm) { + deleteNearSelection(cm, function(range) { + if (range.empty()) { + var len = getLine(cm.doc, range.head.line).text.length; + if (range.head.ch == len && range.head.line < cm.lastLine()) + return {from: range.head, to: Pos(range.head.line + 1, 0)}; + else + return {from: range.head, to: Pos(range.head.line, len)}; + } else { + return {from: range.from(), to: range.to()}; + } + }); + }, + deleteLine: function(cm) { + deleteNearSelection(cm, function(range) { + return {from: Pos(range.from().line, 0), + to: clipPos(cm.doc, Pos(range.to().line + 1, 0))}; + }); + }, + delLineLeft: function(cm) { + deleteNearSelection(cm, function(range) { + return {from: Pos(range.from().line, 0), to: range.from()}; + }); + }, + delWrappedLineLeft: function(cm) { + deleteNearSelection(cm, function(range) { + var top = cm.charCoords(range.head, "div").top + 5; + var leftPos = cm.coordsChar({left: 0, top: top}, "div"); + return {from: leftPos, to: range.from()}; + }); + }, + delWrappedLineRight: function(cm) { + deleteNearSelection(cm, function(range) { + var top = cm.charCoords(range.head, "div").top + 5; + var rightPos = cm.coordsChar({left: cm.display.lineDiv.offsetWidth + 100, top: top}, "div"); + return {from: range.from(), to: rightPos }; + }); + }, + undo: function(cm) {cm.undo();}, + redo: function(cm) {cm.redo();}, + undoSelection: function(cm) {cm.undoSelection();}, + redoSelection: function(cm) {cm.redoSelection();}, + goDocStart: function(cm) {cm.extendSelection(Pos(cm.firstLine(), 0));}, + goDocEnd: function(cm) {cm.extendSelection(Pos(cm.lastLine()));}, + goLineStart: function(cm) { + cm.extendSelectionsBy(function(range) { return lineStart(cm, range.head.line); }, + {origin: "+move", bias: 1}); + }, + goLineStartSmart: function(cm) { + cm.extendSelectionsBy(function(range) { + return lineStartSmart(cm, range.head); + }, {origin: "+move", bias: 1}); + }, + goLineEnd: function(cm) { + cm.extendSelectionsBy(function(range) { return lineEnd(cm, range.head.line); }, + {origin: "+move", bias: -1}); + }, + goLineRight: function(cm) { + cm.extendSelectionsBy(function(range) { + var top = cm.charCoords(range.head, "div").top + 5; + return cm.coordsChar({left: cm.display.lineDiv.offsetWidth + 100, top: top}, "div"); + }, sel_move); + }, + goLineLeft: function(cm) { + cm.extendSelectionsBy(function(range) { + var top = cm.charCoords(range.head, "div").top + 5; + return cm.coordsChar({left: 0, top: top}, "div"); + }, sel_move); + }, + goLineLeftSmart: function(cm) { + cm.extendSelectionsBy(function(range) { + var top = cm.charCoords(range.head, "div").top + 5; + var pos = cm.coordsChar({left: 0, top: top}, "div"); + if (pos.ch < cm.getLine(pos.line).search(/\S/)) return lineStartSmart(cm, range.head); + return pos; + }, sel_move); + }, + goLineUp: function(cm) {cm.moveV(-1, "line");}, + goLineDown: function(cm) {cm.moveV(1, "line");}, + goPageUp: function(cm) {cm.moveV(-1, "page");}, + goPageDown: function(cm) {cm.moveV(1, "page");}, + goCharLeft: function(cm) {cm.moveH(-1, "char");}, + goCharRight: function(cm) {cm.moveH(1, "char");}, + goColumnLeft: function(cm) {cm.moveH(-1, "column");}, + goColumnRight: function(cm) {cm.moveH(1, "column");}, + goWordLeft: function(cm) {cm.moveH(-1, "word");}, + goGroupRight: function(cm) {cm.moveH(1, "group");}, + goGroupLeft: function(cm) {cm.moveH(-1, "group");}, + goWordRight: function(cm) {cm.moveH(1, "word");}, + delCharBefore: function(cm) {cm.deleteH(-1, "char");}, + delCharAfter: function(cm) {cm.deleteH(1, "char");}, + delWordBefore: function(cm) {cm.deleteH(-1, "word");}, + delWordAfter: function(cm) {cm.deleteH(1, "word");}, + delGroupBefore: function(cm) {cm.deleteH(-1, "group");}, + delGroupAfter: function(cm) {cm.deleteH(1, "group");}, + indentAuto: function(cm) {cm.indentSelection("smart");}, + indentMore: function(cm) {cm.indentSelection("add");}, + indentLess: function(cm) {cm.indentSelection("subtract");}, + insertTab: function(cm) {cm.replaceSelection("\t");}, + insertSoftTab: function(cm) { + var spaces = [], ranges = cm.listSelections(), tabSize = cm.options.tabSize; + for (var i = 0; i < ranges.length; i++) { + var pos = ranges[i].from(); + var col = countColumn(cm.getLine(pos.line), pos.ch, tabSize); + spaces.push(new Array(tabSize - col % tabSize + 1).join(" ")); + } + cm.replaceSelections(spaces); + }, + defaultTab: function(cm) { + if (cm.somethingSelected()) cm.indentSelection("add"); + else cm.execCommand("insertTab"); + }, + transposeChars: function(cm) { + runInOp(cm, function() { + var ranges = cm.listSelections(), newSel = []; + for (var i = 0; i < ranges.length; i++) { + var cur = ranges[i].head, line = getLine(cm.doc, cur.line).text; + if (line) { + if (cur.ch == line.length) cur = new Pos(cur.line, cur.ch - 1); + if (cur.ch > 0) { + cur = new Pos(cur.line, cur.ch + 1); + cm.replaceRange(line.charAt(cur.ch - 1) + line.charAt(cur.ch - 2), + Pos(cur.line, cur.ch - 2), cur, "+transpose"); + } else if (cur.line > cm.doc.first) { + var prev = getLine(cm.doc, cur.line - 1).text; + if (prev) + cm.replaceRange(line.charAt(0) + "\n" + prev.charAt(prev.length - 1), + Pos(cur.line - 1, prev.length - 1), Pos(cur.line, 1), "+transpose"); + } + } + newSel.push(new Range(cur, cur)); + } + cm.setSelections(newSel); + }); + }, + newlineAndIndent: function(cm) { + runInOp(cm, function() { + var len = cm.listSelections().length; + for (var i = 0; i < len; i++) { + var range = cm.listSelections()[i]; + cm.replaceRange("\n", range.anchor, range.head, "+input"); + cm.indentLine(range.from().line + 1, null, true); + ensureCursorVisible(cm); + } + }); + }, + toggleOverwrite: function(cm) {cm.toggleOverwrite();} + }; + + + // STANDARD KEYMAPS + + var keyMap = CodeMirror.keyMap = {}; + + keyMap.basic = { + "Left": "goCharLeft", "Right": "goCharRight", "Up": "goLineUp", "Down": "goLineDown", + "End": "goLineEnd", "Home": "goLineStartSmart", "PageUp": "goPageUp", "PageDown": "goPageDown", + "Delete": "delCharAfter", "Backspace": "delCharBefore", "Shift-Backspace": "delCharBefore", + "Tab": "defaultTab", "Shift-Tab": "indentAuto", + "Enter": "newlineAndIndent", "Insert": "toggleOverwrite", + "Esc": "singleSelection" + }; + // Note that the save and find-related commands aren't defined by + // default. User code or addons can define them. Unknown commands + // are simply ignored. + keyMap.pcDefault = { + "Ctrl-A": "selectAll", "Ctrl-D": "deleteLine", "Ctrl-Z": "undo", "Shift-Ctrl-Z": "redo", "Ctrl-Y": "redo", + "Ctrl-Home": "goDocStart", "Ctrl-End": "goDocEnd", "Ctrl-Up": "goLineUp", "Ctrl-Down": "goLineDown", + "Ctrl-Left": "goGroupLeft", "Ctrl-Right": "goGroupRight", "Alt-Left": "goLineStart", "Alt-Right": "goLineEnd", + "Ctrl-Backspace": "delGroupBefore", "Ctrl-Delete": "delGroupAfter", "Ctrl-S": "save", "Ctrl-F": "find", + "Ctrl-G": "findNext", "Shift-Ctrl-G": "findPrev", "Shift-Ctrl-F": "replace", "Shift-Ctrl-R": "replaceAll", + "Ctrl-[": "indentLess", "Ctrl-]": "indentMore", + "Ctrl-U": "undoSelection", "Shift-Ctrl-U": "redoSelection", "Alt-U": "redoSelection", + fallthrough: "basic" + }; + // Very basic readline/emacs-style bindings, which are standard on Mac. + keyMap.emacsy = { + "Ctrl-F": "goCharRight", "Ctrl-B": "goCharLeft", "Ctrl-P": "goLineUp", "Ctrl-N": "goLineDown", + "Alt-F": "goWordRight", "Alt-B": "goWordLeft", "Ctrl-A": "goLineStart", "Ctrl-E": "goLineEnd", + "Ctrl-V": "goPageDown", "Shift-Ctrl-V": "goPageUp", "Ctrl-D": "delCharAfter", "Ctrl-H": "delCharBefore", + "Alt-D": "delWordAfter", "Alt-Backspace": "delWordBefore", "Ctrl-K": "killLine", "Ctrl-T": "transposeChars" + }; + keyMap.macDefault = { + "Cmd-A": "selectAll", "Cmd-D": "deleteLine", "Cmd-Z": "undo", "Shift-Cmd-Z": "redo", "Cmd-Y": "redo", + "Cmd-Home": "goDocStart", "Cmd-Up": "goDocStart", "Cmd-End": "goDocEnd", "Cmd-Down": "goDocEnd", "Alt-Left": "goGroupLeft", + "Alt-Right": "goGroupRight", "Cmd-Left": "goLineLeft", "Cmd-Right": "goLineRight", "Alt-Backspace": "delGroupBefore", + "Ctrl-Alt-Backspace": "delGroupAfter", "Alt-Delete": "delGroupAfter", "Cmd-S": "save", "Cmd-F": "find", + "Cmd-G": "findNext", "Shift-Cmd-G": "findPrev", "Cmd-Alt-F": "replace", "Shift-Cmd-Alt-F": "replaceAll", + "Cmd-[": "indentLess", "Cmd-]": "indentMore", "Cmd-Backspace": "delWrappedLineLeft", "Cmd-Delete": "delWrappedLineRight", + "Cmd-U": "undoSelection", "Shift-Cmd-U": "redoSelection", "Ctrl-Up": "goDocStart", "Ctrl-Down": "goDocEnd", + fallthrough: ["basic", "emacsy"] + }; + keyMap["default"] = mac ? keyMap.macDefault : keyMap.pcDefault; + + // KEYMAP DISPATCH + + function normalizeKeyName(name) { + var parts = name.split(/-(?!$)/), name = parts[parts.length - 1]; + var alt, ctrl, shift, cmd; + for (var i = 0; i < parts.length - 1; i++) { + var mod = parts[i]; + if (/^(cmd|meta|m)$/i.test(mod)) cmd = true; + else if (/^a(lt)?$/i.test(mod)) alt = true; + else if (/^(c|ctrl|control)$/i.test(mod)) ctrl = true; + else if (/^s(hift)$/i.test(mod)) shift = true; + else throw new Error("Unrecognized modifier name: " + mod); + } + if (alt) name = "Alt-" + name; + if (ctrl) name = "Ctrl-" + name; + if (cmd) name = "Cmd-" + name; + if (shift) name = "Shift-" + name; + return name; + } + + // This is a kludge to keep keymaps mostly working as raw objects + // (backwards compatibility) while at the same time support features + // like normalization and multi-stroke key bindings. It compiles a + // new normalized keymap, and then updates the old object to reflect + // this. + CodeMirror.normalizeKeyMap = function(keymap) { + var copy = {}; + for (var keyname in keymap) if (keymap.hasOwnProperty(keyname)) { + var value = keymap[keyname]; + if (/^(name|fallthrough|(de|at)tach)$/.test(keyname)) continue; + if (value == "...") { delete keymap[keyname]; continue; } + + var keys = map(keyname.split(" "), normalizeKeyName); + for (var i = 0; i < keys.length; i++) { + var val, name; + if (i == keys.length - 1) { + name = keyname; + val = value; + } else { + name = keys.slice(0, i + 1).join(" "); + val = "..."; + } + var prev = copy[name]; + if (!prev) copy[name] = val; + else if (prev != val) throw new Error("Inconsistent bindings for " + name); + } + delete keymap[keyname]; + } + for (var prop in copy) keymap[prop] = copy[prop]; + return keymap; + }; + + var lookupKey = CodeMirror.lookupKey = function(key, map, handle, context) { + map = getKeyMap(map); + var found = map.call ? map.call(key, context) : map[key]; + if (found === false) return "nothing"; + if (found === "...") return "multi"; + if (found != null && handle(found)) return "handled"; + + if (map.fallthrough) { + if (Object.prototype.toString.call(map.fallthrough) != "[object Array]") + return lookupKey(key, map.fallthrough, handle, context); + for (var i = 0; i < map.fallthrough.length; i++) { + var result = lookupKey(key, map.fallthrough[i], handle, context); + if (result) return result; + } + } + }; + + // Modifier key presses don't count as 'real' key presses for the + // purpose of keymap fallthrough. + var isModifierKey = CodeMirror.isModifierKey = function(value) { + var name = typeof value == "string" ? value : keyNames[value.keyCode]; + return name == "Ctrl" || name == "Alt" || name == "Shift" || name == "Mod"; + }; + + // Look up the name of a key as indicated by an event object. + var keyName = CodeMirror.keyName = function(event, noShift) { + if (presto && event.keyCode == 34 && event["char"]) return false; + var base = keyNames[event.keyCode], name = base; + if (name == null || event.altGraphKey) return false; + if (event.altKey && base != "Alt") name = "Alt-" + name; + if ((flipCtrlCmd ? event.metaKey : event.ctrlKey) && base != "Ctrl") name = "Ctrl-" + name; + if ((flipCtrlCmd ? event.ctrlKey : event.metaKey) && base != "Cmd") name = "Cmd-" + name; + if (!noShift && event.shiftKey && base != "Shift") name = "Shift-" + name; + return name; + }; + + function getKeyMap(val) { + return typeof val == "string" ? keyMap[val] : val; + } + + // FROMTEXTAREA + + CodeMirror.fromTextArea = function(textarea, options) { + options = options ? copyObj(options) : {}; + options.value = textarea.value; + if (!options.tabindex && textarea.tabIndex) + options.tabindex = textarea.tabIndex; + if (!options.placeholder && textarea.placeholder) + options.placeholder = textarea.placeholder; + // Set autofocus to true if this textarea is focused, or if it has + // autofocus and no other element is focused. + if (options.autofocus == null) { + var hasFocus = activeElt(); + options.autofocus = hasFocus == textarea || + textarea.getAttribute("autofocus") != null && hasFocus == document.body; + } + + function save() {textarea.value = cm.getValue();} + if (textarea.form) { + on(textarea.form, "submit", save); + // Deplorable hack to make the submit method do the right thing. + if (!options.leaveSubmitMethodAlone) { + var form = textarea.form, realSubmit = form.submit; + try { + var wrappedSubmit = form.submit = function() { + save(); + form.submit = realSubmit; + form.submit(); + form.submit = wrappedSubmit; + }; + } catch(e) {} + } + } + + options.finishInit = function(cm) { + cm.save = save; + cm.getTextArea = function() { return textarea; }; + cm.toTextArea = function() { + cm.toTextArea = isNaN; // Prevent this from being ran twice + save(); + textarea.parentNode.removeChild(cm.getWrapperElement()); + textarea.style.display = ""; + if (textarea.form) { + off(textarea.form, "submit", save); + if (typeof textarea.form.submit == "function") + textarea.form.submit = realSubmit; + } + }; + }; + + textarea.style.display = "none"; + var cm = CodeMirror(function(node) { + textarea.parentNode.insertBefore(node, textarea.nextSibling); + }, options); + return cm; + }; + + // STRING STREAM + + // Fed to the mode parsers, provides helper functions to make + // parsers more succinct. + + var StringStream = CodeMirror.StringStream = function(string, tabSize) { + this.pos = this.start = 0; + this.string = string; + this.tabSize = tabSize || 8; + this.lastColumnPos = this.lastColumnValue = 0; + this.lineStart = 0; + }; + + StringStream.prototype = { + eol: function() {return this.pos >= this.string.length;}, + sol: function() {return this.pos == this.lineStart;}, + peek: function() {return this.string.charAt(this.pos) || undefined;}, + next: function() { + if (this.pos < this.string.length) + return this.string.charAt(this.pos++); + }, + eat: function(match) { + var ch = this.string.charAt(this.pos); + if (typeof match == "string") var ok = ch == match; + else var ok = ch && (match.test ? match.test(ch) : match(ch)); + if (ok) {++this.pos; return ch;} + }, + eatWhile: function(match) { + var start = this.pos; + while (this.eat(match)){} + return this.pos > start; + }, + eatSpace: function() { + var start = this.pos; + while (/[\s\u00a0]/.test(this.string.charAt(this.pos))) ++this.pos; + return this.pos > start; + }, + skipToEnd: function() {this.pos = this.string.length;}, + skipTo: function(ch) { + var found = this.string.indexOf(ch, this.pos); + if (found > -1) {this.pos = found; return true;} + }, + backUp: function(n) {this.pos -= n;}, + column: function() { + if (this.lastColumnPos < this.start) { + this.lastColumnValue = countColumn(this.string, this.start, this.tabSize, this.lastColumnPos, this.lastColumnValue); + this.lastColumnPos = this.start; + } + return this.lastColumnValue - (this.lineStart ? countColumn(this.string, this.lineStart, this.tabSize) : 0); + }, + indentation: function() { + return countColumn(this.string, null, this.tabSize) - + (this.lineStart ? countColumn(this.string, this.lineStart, this.tabSize) : 0); + }, + match: function(pattern, consume, caseInsensitive) { + if (typeof pattern == "string") { + var cased = function(str) {return caseInsensitive ? str.toLowerCase() : str;}; + var substr = this.string.substr(this.pos, pattern.length); + if (cased(substr) == cased(pattern)) { + if (consume !== false) this.pos += pattern.length; + return true; + } + } else { + var match = this.string.slice(this.pos).match(pattern); + if (match && match.index > 0) return null; + if (match && consume !== false) this.pos += match[0].length; + return match; + } + }, + current: function(){return this.string.slice(this.start, this.pos);}, + hideFirstChars: function(n, inner) { + this.lineStart += n; + try { return inner(); } + finally { this.lineStart -= n; } + } + }; + + // TEXTMARKERS + + // Created with markText and setBookmark methods. A TextMarker is a + // handle that can be used to clear or find a marked position in the + // document. Line objects hold arrays (markedSpans) containing + // {from, to, marker} object pointing to such marker objects, and + // indicating that such a marker is present on that line. Multiple + // lines may point to the same marker when it spans across lines. + // The spans will have null for their from/to properties when the + // marker continues beyond the start/end of the line. Markers have + // links back to the lines they currently touch. + + var nextMarkerId = 0; + + var TextMarker = CodeMirror.TextMarker = function(doc, type) { + this.lines = []; + this.type = type; + this.doc = doc; + this.id = ++nextMarkerId; + }; + eventMixin(TextMarker); + + // Clear the marker. + TextMarker.prototype.clear = function() { + if (this.explicitlyCleared) return; + var cm = this.doc.cm, withOp = cm && !cm.curOp; + if (withOp) startOperation(cm); + if (hasHandler(this, "clear")) { + var found = this.find(); + if (found) signalLater(this, "clear", found.from, found.to); + } + var min = null, max = null; + for (var i = 0; i < this.lines.length; ++i) { + var line = this.lines[i]; + var span = getMarkedSpanFor(line.markedSpans, this); + if (cm && !this.collapsed) regLineChange(cm, lineNo(line), "text"); + else if (cm) { + if (span.to != null) max = lineNo(line); + if (span.from != null) min = lineNo(line); + } + line.markedSpans = removeMarkedSpan(line.markedSpans, span); + if (span.from == null && this.collapsed && !lineIsHidden(this.doc, line) && cm) + updateLineHeight(line, textHeight(cm.display)); + } + if (cm && this.collapsed && !cm.options.lineWrapping) for (var i = 0; i < this.lines.length; ++i) { + var visual = visualLine(this.lines[i]), len = lineLength(visual); + if (len > cm.display.maxLineLength) { + cm.display.maxLine = visual; + cm.display.maxLineLength = len; + cm.display.maxLineChanged = true; + } + } + + if (min != null && cm && this.collapsed) regChange(cm, min, max + 1); + this.lines.length = 0; + this.explicitlyCleared = true; + if (this.atomic && this.doc.cantEdit) { + this.doc.cantEdit = false; + if (cm) reCheckSelection(cm.doc); + } + if (cm) signalLater(cm, "markerCleared", cm, this); + if (withOp) endOperation(cm); + if (this.parent) this.parent.clear(); + }; + + // Find the position of the marker in the document. Returns a {from, + // to} object by default. Side can be passed to get a specific side + // -- 0 (both), -1 (left), or 1 (right). When lineObj is true, the + // Pos objects returned contain a line object, rather than a line + // number (used to prevent looking up the same line twice). + TextMarker.prototype.find = function(side, lineObj) { + if (side == null && this.type == "bookmark") side = 1; + var from, to; + for (var i = 0; i < this.lines.length; ++i) { + var line = this.lines[i]; + var span = getMarkedSpanFor(line.markedSpans, this); + if (span.from != null) { + from = Pos(lineObj ? line : lineNo(line), span.from); + if (side == -1) return from; + } + if (span.to != null) { + to = Pos(lineObj ? line : lineNo(line), span.to); + if (side == 1) return to; + } + } + return from && {from: from, to: to}; + }; + + // Signals that the marker's widget changed, and surrounding layout + // should be recomputed. + TextMarker.prototype.changed = function() { + var pos = this.find(-1, true), widget = this, cm = this.doc.cm; + if (!pos || !cm) return; + runInOp(cm, function() { + var line = pos.line, lineN = lineNo(pos.line); + var view = findViewForLine(cm, lineN); + if (view) { + clearLineMeasurementCacheFor(view); + cm.curOp.selectionChanged = cm.curOp.forceUpdate = true; + } + cm.curOp.updateMaxLine = true; + if (!lineIsHidden(widget.doc, line) && widget.height != null) { + var oldHeight = widget.height; + widget.height = null; + var dHeight = widgetHeight(widget) - oldHeight; + if (dHeight) + updateLineHeight(line, line.height + dHeight); + } + }); + }; + + TextMarker.prototype.attachLine = function(line) { + if (!this.lines.length && this.doc.cm) { + var op = this.doc.cm.curOp; + if (!op.maybeHiddenMarkers || indexOf(op.maybeHiddenMarkers, this) == -1) + (op.maybeUnhiddenMarkers || (op.maybeUnhiddenMarkers = [])).push(this); + } + this.lines.push(line); + }; + TextMarker.prototype.detachLine = function(line) { + this.lines.splice(indexOf(this.lines, line), 1); + if (!this.lines.length && this.doc.cm) { + var op = this.doc.cm.curOp; + (op.maybeHiddenMarkers || (op.maybeHiddenMarkers = [])).push(this); + } + }; + + // Collapsed markers have unique ids, in order to be able to order + // them, which is needed for uniquely determining an outer marker + // when they overlap (they may nest, but not partially overlap). + var nextMarkerId = 0; + + // Create a marker, wire it up to the right lines, and + function markText(doc, from, to, options, type) { + // Shared markers (across linked documents) are handled separately + // (markTextShared will call out to this again, once per + // document). + if (options && options.shared) return markTextShared(doc, from, to, options, type); + // Ensure we are in an operation. + if (doc.cm && !doc.cm.curOp) return operation(doc.cm, markText)(doc, from, to, options, type); + + var marker = new TextMarker(doc, type), diff = cmp(from, to); + if (options) copyObj(options, marker, false); + // Don't connect empty markers unless clearWhenEmpty is false + if (diff > 0 || diff == 0 && marker.clearWhenEmpty !== false) + return marker; + if (marker.replacedWith) { + // Showing up as a widget implies collapsed (widget replaces text) + marker.collapsed = true; + marker.widgetNode = elt("span", [marker.replacedWith], "CodeMirror-widget"); + if (!options.handleMouseEvents) marker.widgetNode.setAttribute("cm-ignore-events", "true"); + if (options.insertLeft) marker.widgetNode.insertLeft = true; + } + if (marker.collapsed) { + if (conflictingCollapsedRange(doc, from.line, from, to, marker) || + from.line != to.line && conflictingCollapsedRange(doc, to.line, from, to, marker)) + throw new Error("Inserting collapsed marker partially overlapping an existing one"); + sawCollapsedSpans = true; + } + + if (marker.addToHistory) + addChangeToHistory(doc, {from: from, to: to, origin: "markText"}, doc.sel, NaN); + + var curLine = from.line, cm = doc.cm, updateMaxLine; + doc.iter(curLine, to.line + 1, function(line) { + if (cm && marker.collapsed && !cm.options.lineWrapping && visualLine(line) == cm.display.maxLine) + updateMaxLine = true; + if (marker.collapsed && curLine != from.line) updateLineHeight(line, 0); + addMarkedSpan(line, new MarkedSpan(marker, + curLine == from.line ? from.ch : null, + curLine == to.line ? to.ch : null)); + ++curLine; + }); + // lineIsHidden depends on the presence of the spans, so needs a second pass + if (marker.collapsed) doc.iter(from.line, to.line + 1, function(line) { + if (lineIsHidden(doc, line)) updateLineHeight(line, 0); + }); + + if (marker.clearOnEnter) on(marker, "beforeCursorEnter", function() { marker.clear(); }); + + if (marker.readOnly) { + sawReadOnlySpans = true; + if (doc.history.done.length || doc.history.undone.length) + doc.clearHistory(); + } + if (marker.collapsed) { + marker.id = ++nextMarkerId; + marker.atomic = true; + } + if (cm) { + // Sync editor state + if (updateMaxLine) cm.curOp.updateMaxLine = true; + if (marker.collapsed) + regChange(cm, from.line, to.line + 1); + else if (marker.className || marker.title || marker.startStyle || marker.endStyle || marker.css) + for (var i = from.line; i <= to.line; i++) regLineChange(cm, i, "text"); + if (marker.atomic) reCheckSelection(cm.doc); + signalLater(cm, "markerAdded", cm, marker); + } + return marker; + } + + // SHARED TEXTMARKERS + + // A shared marker spans multiple linked documents. It is + // implemented as a meta-marker-object controlling multiple normal + // markers. + var SharedTextMarker = CodeMirror.SharedTextMarker = function(markers, primary) { + this.markers = markers; + this.primary = primary; + for (var i = 0; i < markers.length; ++i) + markers[i].parent = this; + }; + eventMixin(SharedTextMarker); + + SharedTextMarker.prototype.clear = function() { + if (this.explicitlyCleared) return; + this.explicitlyCleared = true; + for (var i = 0; i < this.markers.length; ++i) + this.markers[i].clear(); + signalLater(this, "clear"); + }; + SharedTextMarker.prototype.find = function(side, lineObj) { + return this.primary.find(side, lineObj); + }; + + function markTextShared(doc, from, to, options, type) { + options = copyObj(options); + options.shared = false; + var markers = [markText(doc, from, to, options, type)], primary = markers[0]; + var widget = options.widgetNode; + linkedDocs(doc, function(doc) { + if (widget) options.widgetNode = widget.cloneNode(true); + markers.push(markText(doc, clipPos(doc, from), clipPos(doc, to), options, type)); + for (var i = 0; i < doc.linked.length; ++i) + if (doc.linked[i].isParent) return; + primary = lst(markers); + }); + return new SharedTextMarker(markers, primary); + } + + function findSharedMarkers(doc) { + return doc.findMarks(Pos(doc.first, 0), doc.clipPos(Pos(doc.lastLine())), + function(m) { return m.parent; }); + } + + function copySharedMarkers(doc, markers) { + for (var i = 0; i < markers.length; i++) { + var marker = markers[i], pos = marker.find(); + var mFrom = doc.clipPos(pos.from), mTo = doc.clipPos(pos.to); + if (cmp(mFrom, mTo)) { + var subMark = markText(doc, mFrom, mTo, marker.primary, marker.primary.type); + marker.markers.push(subMark); + subMark.parent = marker; + } + } + } + + function detachSharedMarkers(markers) { + for (var i = 0; i < markers.length; i++) { + var marker = markers[i], linked = [marker.primary.doc];; + linkedDocs(marker.primary.doc, function(d) { linked.push(d); }); + for (var j = 0; j < marker.markers.length; j++) { + var subMarker = marker.markers[j]; + if (indexOf(linked, subMarker.doc) == -1) { + subMarker.parent = null; + marker.markers.splice(j--, 1); + } + } + } + } + + // TEXTMARKER SPANS + + function MarkedSpan(marker, from, to) { + this.marker = marker; + this.from = from; this.to = to; + } + + // Search an array of spans for a span matching the given marker. + function getMarkedSpanFor(spans, marker) { + if (spans) for (var i = 0; i < spans.length; ++i) { + var span = spans[i]; + if (span.marker == marker) return span; + } + } + // Remove a span from an array, returning undefined if no spans are + // left (we don't store arrays for lines without spans). + function removeMarkedSpan(spans, span) { + for (var r, i = 0; i < spans.length; ++i) + if (spans[i] != span) (r || (r = [])).push(spans[i]); + return r; + } + // Add a span to a line. + function addMarkedSpan(line, span) { + line.markedSpans = line.markedSpans ? line.markedSpans.concat([span]) : [span]; + span.marker.attachLine(line); + } + + // Used for the algorithm that adjusts markers for a change in the + // document. These functions cut an array of spans at a given + // character position, returning an array of remaining chunks (or + // undefined if nothing remains). + function markedSpansBefore(old, startCh, isInsert) { + if (old) for (var i = 0, nw; i < old.length; ++i) { + var span = old[i], marker = span.marker; + var startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= startCh : span.from < startCh); + if (startsBefore || span.from == startCh && marker.type == "bookmark" && (!isInsert || !span.marker.insertLeft)) { + var endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= startCh : span.to > startCh); + (nw || (nw = [])).push(new MarkedSpan(marker, span.from, endsAfter ? null : span.to)); + } + } + return nw; + } + function markedSpansAfter(old, endCh, isInsert) { + if (old) for (var i = 0, nw; i < old.length; ++i) { + var span = old[i], marker = span.marker; + var endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= endCh : span.to > endCh); + if (endsAfter || span.from == endCh && marker.type == "bookmark" && (!isInsert || span.marker.insertLeft)) { + var startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= endCh : span.from < endCh); + (nw || (nw = [])).push(new MarkedSpan(marker, startsBefore ? null : span.from - endCh, + span.to == null ? null : span.to - endCh)); + } + } + return nw; + } + + // Given a change object, compute the new set of marker spans that + // cover the line in which the change took place. Removes spans + // entirely within the change, reconnects spans belonging to the + // same marker that appear on both sides of the change, and cuts off + // spans partially within the change. Returns an array of span + // arrays with one element for each line in (after) the change. + function stretchSpansOverChange(doc, change) { + if (change.full) return null; + var oldFirst = isLine(doc, change.from.line) && getLine(doc, change.from.line).markedSpans; + var oldLast = isLine(doc, change.to.line) && getLine(doc, change.to.line).markedSpans; + if (!oldFirst && !oldLast) return null; + + var startCh = change.from.ch, endCh = change.to.ch, isInsert = cmp(change.from, change.to) == 0; + // Get the spans that 'stick out' on both sides + var first = markedSpansBefore(oldFirst, startCh, isInsert); + var last = markedSpansAfter(oldLast, endCh, isInsert); + + // Next, merge those two ends + var sameLine = change.text.length == 1, offset = lst(change.text).length + (sameLine ? startCh : 0); + if (first) { + // Fix up .to properties of first + for (var i = 0; i < first.length; ++i) { + var span = first[i]; + if (span.to == null) { + var found = getMarkedSpanFor(last, span.marker); + if (!found) span.to = startCh; + else if (sameLine) span.to = found.to == null ? null : found.to + offset; + } + } + } + if (last) { + // Fix up .from in last (or move them into first in case of sameLine) + for (var i = 0; i < last.length; ++i) { + var span = last[i]; + if (span.to != null) span.to += offset; + if (span.from == null) { + var found = getMarkedSpanFor(first, span.marker); + if (!found) { + span.from = offset; + if (sameLine) (first || (first = [])).push(span); + } + } else { + span.from += offset; + if (sameLine) (first || (first = [])).push(span); + } + } + } + // Make sure we didn't create any zero-length spans + if (first) first = clearEmptySpans(first); + if (last && last != first) last = clearEmptySpans(last); + + var newMarkers = [first]; + if (!sameLine) { + // Fill gap with whole-line-spans + var gap = change.text.length - 2, gapMarkers; + if (gap > 0 && first) + for (var i = 0; i < first.length; ++i) + if (first[i].to == null) + (gapMarkers || (gapMarkers = [])).push(new MarkedSpan(first[i].marker, null, null)); + for (var i = 0; i < gap; ++i) + newMarkers.push(gapMarkers); + newMarkers.push(last); + } + return newMarkers; + } + + // Remove spans that are empty and don't have a clearWhenEmpty + // option of false. + function clearEmptySpans(spans) { + for (var i = 0; i < spans.length; ++i) { + var span = spans[i]; + if (span.from != null && span.from == span.to && span.marker.clearWhenEmpty !== false) + spans.splice(i--, 1); + } + if (!spans.length) return null; + return spans; + } + + // Used for un/re-doing changes from the history. Combines the + // result of computing the existing spans with the set of spans that + // existed in the history (so that deleting around a span and then + // undoing brings back the span). + function mergeOldSpans(doc, change) { + var old = getOldSpans(doc, change); + var stretched = stretchSpansOverChange(doc, change); + if (!old) return stretched; + if (!stretched) return old; + + for (var i = 0; i < old.length; ++i) { + var oldCur = old[i], stretchCur = stretched[i]; + if (oldCur && stretchCur) { + spans: for (var j = 0; j < stretchCur.length; ++j) { + var span = stretchCur[j]; + for (var k = 0; k < oldCur.length; ++k) + if (oldCur[k].marker == span.marker) continue spans; + oldCur.push(span); + } + } else if (stretchCur) { + old[i] = stretchCur; + } + } + return old; + } + + // Used to 'clip' out readOnly ranges when making a change. + function removeReadOnlyRanges(doc, from, to) { + var markers = null; + doc.iter(from.line, to.line + 1, function(line) { + if (line.markedSpans) for (var i = 0; i < line.markedSpans.length; ++i) { + var mark = line.markedSpans[i].marker; + if (mark.readOnly && (!markers || indexOf(markers, mark) == -1)) + (markers || (markers = [])).push(mark); + } + }); + if (!markers) return null; + var parts = [{from: from, to: to}]; + for (var i = 0; i < markers.length; ++i) { + var mk = markers[i], m = mk.find(0); + for (var j = 0; j < parts.length; ++j) { + var p = parts[j]; + if (cmp(p.to, m.from) < 0 || cmp(p.from, m.to) > 0) continue; + var newParts = [j, 1], dfrom = cmp(p.from, m.from), dto = cmp(p.to, m.to); + if (dfrom < 0 || !mk.inclusiveLeft && !dfrom) + newParts.push({from: p.from, to: m.from}); + if (dto > 0 || !mk.inclusiveRight && !dto) + newParts.push({from: m.to, to: p.to}); + parts.splice.apply(parts, newParts); + j += newParts.length - 1; + } + } + return parts; + } + + // Connect or disconnect spans from a line. + function detachMarkedSpans(line) { + var spans = line.markedSpans; + if (!spans) return; + for (var i = 0; i < spans.length; ++i) + spans[i].marker.detachLine(line); + line.markedSpans = null; + } + function attachMarkedSpans(line, spans) { + if (!spans) return; + for (var i = 0; i < spans.length; ++i) + spans[i].marker.attachLine(line); + line.markedSpans = spans; + } + + // Helpers used when computing which overlapping collapsed span + // counts as the larger one. + function extraLeft(marker) { return marker.inclusiveLeft ? -1 : 0; } + function extraRight(marker) { return marker.inclusiveRight ? 1 : 0; } + + // Returns a number indicating which of two overlapping collapsed + // spans is larger (and thus includes the other). Falls back to + // comparing ids when the spans cover exactly the same range. + function compareCollapsedMarkers(a, b) { + var lenDiff = a.lines.length - b.lines.length; + if (lenDiff != 0) return lenDiff; + var aPos = a.find(), bPos = b.find(); + var fromCmp = cmp(aPos.from, bPos.from) || extraLeft(a) - extraLeft(b); + if (fromCmp) return -fromCmp; + var toCmp = cmp(aPos.to, bPos.to) || extraRight(a) - extraRight(b); + if (toCmp) return toCmp; + return b.id - a.id; + } + + // Find out whether a line ends or starts in a collapsed span. If + // so, return the marker for that span. + function collapsedSpanAtSide(line, start) { + var sps = sawCollapsedSpans && line.markedSpans, found; + if (sps) for (var sp, i = 0; i < sps.length; ++i) { + sp = sps[i]; + if (sp.marker.collapsed && (start ? sp.from : sp.to) == null && + (!found || compareCollapsedMarkers(found, sp.marker) < 0)) + found = sp.marker; + } + return found; + } + function collapsedSpanAtStart(line) { return collapsedSpanAtSide(line, true); } + function collapsedSpanAtEnd(line) { return collapsedSpanAtSide(line, false); } + + // Test whether there exists a collapsed span that partially + // overlaps (covers the start or end, but not both) of a new span. + // Such overlap is not allowed. + function conflictingCollapsedRange(doc, lineNo, from, to, marker) { + var line = getLine(doc, lineNo); + var sps = sawCollapsedSpans && line.markedSpans; + if (sps) for (var i = 0; i < sps.length; ++i) { + var sp = sps[i]; + if (!sp.marker.collapsed) continue; + var found = sp.marker.find(0); + var fromCmp = cmp(found.from, from) || extraLeft(sp.marker) - extraLeft(marker); + var toCmp = cmp(found.to, to) || extraRight(sp.marker) - extraRight(marker); + if (fromCmp >= 0 && toCmp <= 0 || fromCmp <= 0 && toCmp >= 0) continue; + if (fromCmp <= 0 && (cmp(found.to, from) > 0 || (sp.marker.inclusiveRight && marker.inclusiveLeft)) || + fromCmp >= 0 && (cmp(found.from, to) < 0 || (sp.marker.inclusiveLeft && marker.inclusiveRight))) + return true; + } + } + + // A visual line is a line as drawn on the screen. Folding, for + // example, can cause multiple logical lines to appear on the same + // visual line. This finds the start of the visual line that the + // given line is part of (usually that is the line itself). + function visualLine(line) { + var merged; + while (merged = collapsedSpanAtStart(line)) + line = merged.find(-1, true).line; + return line; + } + + // Returns an array of logical lines that continue the visual line + // started by the argument, or undefined if there are no such lines. + function visualLineContinued(line) { + var merged, lines; + while (merged = collapsedSpanAtEnd(line)) { + line = merged.find(1, true).line; + (lines || (lines = [])).push(line); + } + return lines; + } + + // Get the line number of the start of the visual line that the + // given line number is part of. + function visualLineNo(doc, lineN) { + var line = getLine(doc, lineN), vis = visualLine(line); + if (line == vis) return lineN; + return lineNo(vis); + } + // Get the line number of the start of the next visual line after + // the given line. + function visualLineEndNo(doc, lineN) { + if (lineN > doc.lastLine()) return lineN; + var line = getLine(doc, lineN), merged; + if (!lineIsHidden(doc, line)) return lineN; + while (merged = collapsedSpanAtEnd(line)) + line = merged.find(1, true).line; + return lineNo(line) + 1; + } + + // Compute whether a line is hidden. Lines count as hidden when they + // are part of a visual line that starts with another line, or when + // they are entirely covered by collapsed, non-widget span. + function lineIsHidden(doc, line) { + var sps = sawCollapsedSpans && line.markedSpans; + if (sps) for (var sp, i = 0; i < sps.length; ++i) { + sp = sps[i]; + if (!sp.marker.collapsed) continue; + if (sp.from == null) return true; + if (sp.marker.widgetNode) continue; + if (sp.from == 0 && sp.marker.inclusiveLeft && lineIsHiddenInner(doc, line, sp)) + return true; + } + } + function lineIsHiddenInner(doc, line, span) { + if (span.to == null) { + var end = span.marker.find(1, true); + return lineIsHiddenInner(doc, end.line, getMarkedSpanFor(end.line.markedSpans, span.marker)); + } + if (span.marker.inclusiveRight && span.to == line.text.length) + return true; + for (var sp, i = 0; i < line.markedSpans.length; ++i) { + sp = line.markedSpans[i]; + if (sp.marker.collapsed && !sp.marker.widgetNode && sp.from == span.to && + (sp.to == null || sp.to != span.from) && + (sp.marker.inclusiveLeft || span.marker.inclusiveRight) && + lineIsHiddenInner(doc, line, sp)) return true; + } + } + + // LINE WIDGETS + + // Line widgets are block elements displayed above or below a line. + + var LineWidget = CodeMirror.LineWidget = function(doc, node, options) { + if (options) for (var opt in options) if (options.hasOwnProperty(opt)) + this[opt] = options[opt]; + this.doc = doc; + this.node = node; + }; + eventMixin(LineWidget); + + function adjustScrollWhenAboveVisible(cm, line, diff) { + if (heightAtLine(line) < ((cm.curOp && cm.curOp.scrollTop) || cm.doc.scrollTop)) + addToScrollPos(cm, null, diff); + } + + LineWidget.prototype.clear = function() { + var cm = this.doc.cm, ws = this.line.widgets, line = this.line, no = lineNo(line); + if (no == null || !ws) return; + for (var i = 0; i < ws.length; ++i) if (ws[i] == this) ws.splice(i--, 1); + if (!ws.length) line.widgets = null; + var height = widgetHeight(this); + updateLineHeight(line, Math.max(0, line.height - height)); + if (cm) runInOp(cm, function() { + adjustScrollWhenAboveVisible(cm, line, -height); + regLineChange(cm, no, "widget"); + }); + }; + LineWidget.prototype.changed = function() { + var oldH = this.height, cm = this.doc.cm, line = this.line; + this.height = null; + var diff = widgetHeight(this) - oldH; + if (!diff) return; + updateLineHeight(line, line.height + diff); + if (cm) runInOp(cm, function() { + cm.curOp.forceUpdate = true; + adjustScrollWhenAboveVisible(cm, line, diff); + }); + }; + + function widgetHeight(widget) { + if (widget.height != null) return widget.height; + var cm = widget.doc.cm; + if (!cm) return 0; + if (!contains(document.body, widget.node)) { + var parentStyle = "position: relative;"; + if (widget.coverGutter) + parentStyle += "margin-left: -" + cm.display.gutters.offsetWidth + "px;"; + if (widget.noHScroll) + parentStyle += "width: " + cm.display.wrapper.clientWidth + "px;"; + removeChildrenAndAdd(cm.display.measure, elt("div", [widget.node], null, parentStyle)); + } + return widget.height = widget.node.offsetHeight; + } + + function addLineWidget(doc, handle, node, options) { + var widget = new LineWidget(doc, node, options); + var cm = doc.cm; + if (cm && widget.noHScroll) cm.display.alignWidgets = true; + changeLine(doc, handle, "widget", function(line) { + var widgets = line.widgets || (line.widgets = []); + if (widget.insertAt == null) widgets.push(widget); + else widgets.splice(Math.min(widgets.length - 1, Math.max(0, widget.insertAt)), 0, widget); + widget.line = line; + if (cm && !lineIsHidden(doc, line)) { + var aboveVisible = heightAtLine(line) < doc.scrollTop; + updateLineHeight(line, line.height + widgetHeight(widget)); + if (aboveVisible) addToScrollPos(cm, null, widget.height); + cm.curOp.forceUpdate = true; + } + return true; + }); + return widget; + } + + // LINE DATA STRUCTURE + + // Line objects. These hold state related to a line, including + // highlighting info (the styles array). + var Line = CodeMirror.Line = function(text, markedSpans, estimateHeight) { + this.text = text; + attachMarkedSpans(this, markedSpans); + this.height = estimateHeight ? estimateHeight(this) : 1; + }; + eventMixin(Line); + Line.prototype.lineNo = function() { return lineNo(this); }; + + // Change the content (text, markers) of a line. Automatically + // invalidates cached information and tries to re-estimate the + // line's height. + function updateLine(line, text, markedSpans, estimateHeight) { + line.text = text; + if (line.stateAfter) line.stateAfter = null; + if (line.styles) line.styles = null; + if (line.order != null) line.order = null; + detachMarkedSpans(line); + attachMarkedSpans(line, markedSpans); + var estHeight = estimateHeight ? estimateHeight(line) : 1; + if (estHeight != line.height) updateLineHeight(line, estHeight); + } + + // Detach a line from the document tree and its markers. + function cleanUpLine(line) { + line.parent = null; + detachMarkedSpans(line); + } + + function extractLineClasses(type, output) { + if (type) for (;;) { + var lineClass = type.match(/(?:^|\s+)line-(background-)?(\S+)/); + if (!lineClass) break; + type = type.slice(0, lineClass.index) + type.slice(lineClass.index + lineClass[0].length); + var prop = lineClass[1] ? "bgClass" : "textClass"; + if (output[prop] == null) + output[prop] = lineClass[2]; + else if (!(new RegExp("(?:^|\s)" + lineClass[2] + "(?:$|\s)")).test(output[prop])) + output[prop] += " " + lineClass[2]; + } + return type; + } + + function callBlankLine(mode, state) { + if (mode.blankLine) return mode.blankLine(state); + if (!mode.innerMode) return; + var inner = CodeMirror.innerMode(mode, state); + if (inner.mode.blankLine) return inner.mode.blankLine(inner.state); + } + + function readToken(mode, stream, state, inner) { + for (var i = 0; i < 10; i++) { + if (inner) inner[0] = CodeMirror.innerMode(mode, state).mode; + var style = mode.token(stream, state); + if (stream.pos > stream.start) return style; + } + throw new Error("Mode " + mode.name + " failed to advance stream."); + } + + // Utility for getTokenAt and getLineTokens + function takeToken(cm, pos, precise, asArray) { + function getObj(copy) { + return {start: stream.start, end: stream.pos, + string: stream.current(), + type: style || null, + state: copy ? copyState(doc.mode, state) : state}; + } + + var doc = cm.doc, mode = doc.mode, style; + pos = clipPos(doc, pos); + var line = getLine(doc, pos.line), state = getStateBefore(cm, pos.line, precise); + var stream = new StringStream(line.text, cm.options.tabSize), tokens; + if (asArray) tokens = []; + while ((asArray || stream.pos < pos.ch) && !stream.eol()) { + stream.start = stream.pos; + style = readToken(mode, stream, state); + if (asArray) tokens.push(getObj(true)); + } + return asArray ? tokens : getObj(); + } + + // Run the given mode's parser over a line, calling f for each token. + function runMode(cm, text, mode, state, f, lineClasses, forceToEnd) { + var flattenSpans = mode.flattenSpans; + if (flattenSpans == null) flattenSpans = cm.options.flattenSpans; + var curStart = 0, curStyle = null; + var stream = new StringStream(text, cm.options.tabSize), style; + var inner = cm.options.addModeClass && [null]; + if (text == "") extractLineClasses(callBlankLine(mode, state), lineClasses); + while (!stream.eol()) { + if (stream.pos > cm.options.maxHighlightLength) { + flattenSpans = false; + if (forceToEnd) processLine(cm, text, state, stream.pos); + stream.pos = text.length; + style = null; + } else { + style = extractLineClasses(readToken(mode, stream, state, inner), lineClasses); + } + if (inner) { + var mName = inner[0].name; + if (mName) style = "m-" + (style ? mName + " " + style : mName); + } + if (!flattenSpans || curStyle != style) { + while (curStart < stream.start) { + curStart = Math.min(stream.start, curStart + 50000); + f(curStart, curStyle); + } + curStyle = style; + } + stream.start = stream.pos; + } + while (curStart < stream.pos) { + // Webkit seems to refuse to render text nodes longer than 57444 characters + var pos = Math.min(stream.pos, curStart + 50000); + f(pos, curStyle); + curStart = pos; + } + } + + // Compute a style array (an array starting with a mode generation + // -- for invalidation -- followed by pairs of end positions and + // style strings), which is used to highlight the tokens on the + // line. + function highlightLine(cm, line, state, forceToEnd) { + // A styles array always starts with a number identifying the + // mode/overlays that it is based on (for easy invalidation). + var st = [cm.state.modeGen], lineClasses = {}; + // Compute the base array of styles + runMode(cm, line.text, cm.doc.mode, state, function(end, style) { + st.push(end, style); + }, lineClasses, forceToEnd); + + // Run overlays, adjust style array. + for (var o = 0; o < cm.state.overlays.length; ++o) { + var overlay = cm.state.overlays[o], i = 1, at = 0; + runMode(cm, line.text, overlay.mode, true, function(end, style) { + var start = i; + // Ensure there's a token end at the current position, and that i points at it + while (at < end) { + var i_end = st[i]; + if (i_end > end) + st.splice(i, 1, end, st[i+1], i_end); + i += 2; + at = Math.min(end, i_end); + } + if (!style) return; + if (overlay.opaque) { + st.splice(start, i - start, end, "cm-overlay " + style); + i = start + 2; + } else { + for (; start < i; start += 2) { + var cur = st[start+1]; + st[start+1] = (cur ? cur + " " : "") + "cm-overlay " + style; + } + } + }, lineClasses); + } + + return {styles: st, classes: lineClasses.bgClass || lineClasses.textClass ? lineClasses : null}; + } + + function getLineStyles(cm, line, updateFrontier) { + if (!line.styles || line.styles[0] != cm.state.modeGen) { + var result = highlightLine(cm, line, line.stateAfter = getStateBefore(cm, lineNo(line))); + line.styles = result.styles; + if (result.classes) line.styleClasses = result.classes; + else if (line.styleClasses) line.styleClasses = null; + if (updateFrontier === cm.doc.frontier) cm.doc.frontier++; + } + return line.styles; + } + + // Lightweight form of highlight -- proceed over this line and + // update state, but don't save a style array. Used for lines that + // aren't currently visible. + function processLine(cm, text, state, startAt) { + var mode = cm.doc.mode; + var stream = new StringStream(text, cm.options.tabSize); + stream.start = stream.pos = startAt || 0; + if (text == "") callBlankLine(mode, state); + while (!stream.eol() && stream.pos <= cm.options.maxHighlightLength) { + readToken(mode, stream, state); + stream.start = stream.pos; + } + } + + // Convert a style as returned by a mode (either null, or a string + // containing one or more styles) to a CSS style. This is cached, + // and also looks for line-wide styles. + var styleToClassCache = {}, styleToClassCacheWithMode = {}; + function interpretTokenStyle(style, options) { + if (!style || /^\s*$/.test(style)) return null; + var cache = options.addModeClass ? styleToClassCacheWithMode : styleToClassCache; + return cache[style] || + (cache[style] = style.replace(/\S+/g, "cm-$&")); + } + + // Render the DOM representation of the text of a line. Also builds + // up a 'line map', which points at the DOM nodes that represent + // specific stretches of text, and is used by the measuring code. + // The returned object contains the DOM node, this map, and + // information about line-wide styles that were set by the mode. + function buildLineContent(cm, lineView) { + // The padding-right forces the element to have a 'border', which + // is needed on Webkit to be able to get line-level bounding + // rectangles for it (in measureChar). + var content = elt("span", null, null, webkit ? "padding-right: .1px" : null); + var builder = {pre: elt("pre", [content]), content: content, + col: 0, pos: 0, cm: cm, + splitSpaces: (ie || webkit) && cm.getOption("lineWrapping")}; + lineView.measure = {}; + + // Iterate over the logical lines that make up this visual line. + for (var i = 0; i <= (lineView.rest ? lineView.rest.length : 0); i++) { + var line = i ? lineView.rest[i - 1] : lineView.line, order; + builder.pos = 0; + builder.addToken = buildToken; + // Optionally wire in some hacks into the token-rendering + // algorithm, to deal with browser quirks. + if (hasBadBidiRects(cm.display.measure) && (order = getOrder(line))) + builder.addToken = buildTokenBadBidi(builder.addToken, order); + builder.map = []; + var allowFrontierUpdate = lineView != cm.display.externalMeasured && lineNo(line); + insertLineContent(line, builder, getLineStyles(cm, line, allowFrontierUpdate)); + if (line.styleClasses) { + if (line.styleClasses.bgClass) + builder.bgClass = joinClasses(line.styleClasses.bgClass, builder.bgClass || ""); + if (line.styleClasses.textClass) + builder.textClass = joinClasses(line.styleClasses.textClass, builder.textClass || ""); + } + + // Ensure at least a single node is present, for measuring. + if (builder.map.length == 0) + builder.map.push(0, 0, builder.content.appendChild(zeroWidthElement(cm.display.measure))); + + // Store the map and a cache object for the current logical line + if (i == 0) { + lineView.measure.map = builder.map; + lineView.measure.cache = {}; + } else { + (lineView.measure.maps || (lineView.measure.maps = [])).push(builder.map); + (lineView.measure.caches || (lineView.measure.caches = [])).push({}); + } + } + + // See issue #2901 + if (webkit && /\bcm-tab\b/.test(builder.content.lastChild.className)) + builder.content.className = "cm-tab-wrap-hack"; + + signal(cm, "renderLine", cm, lineView.line, builder.pre); + if (builder.pre.className) + builder.textClass = joinClasses(builder.pre.className, builder.textClass || ""); + + return builder; + } + + function defaultSpecialCharPlaceholder(ch) { + var token = elt("span", "\u2022", "cm-invalidchar"); + token.title = "\\u" + ch.charCodeAt(0).toString(16); + token.setAttribute("aria-label", token.title); + return token; + } + + // Build up the DOM representation for a single token, and add it to + // the line map. Takes care to render special characters separately. + function buildToken(builder, text, style, startStyle, endStyle, title, css) { + if (!text) return; + var displayText = builder.splitSpaces ? text.replace(/ {3,}/g, splitSpaces) : text; + var special = builder.cm.state.specialChars, mustWrap = false; + if (!special.test(text)) { + builder.col += text.length; + var content = document.createTextNode(displayText); + builder.map.push(builder.pos, builder.pos + text.length, content); + if (ie && ie_version < 9) mustWrap = true; + builder.pos += text.length; + } else { + var content = document.createDocumentFragment(), pos = 0; + while (true) { + special.lastIndex = pos; + var m = special.exec(text); + var skipped = m ? m.index - pos : text.length - pos; + if (skipped) { + var txt = document.createTextNode(displayText.slice(pos, pos + skipped)); + if (ie && ie_version < 9) content.appendChild(elt("span", [txt])); + else content.appendChild(txt); + builder.map.push(builder.pos, builder.pos + skipped, txt); + builder.col += skipped; + builder.pos += skipped; + } + if (!m) break; + pos += skipped + 1; + if (m[0] == "\t") { + var tabSize = builder.cm.options.tabSize, tabWidth = tabSize - builder.col % tabSize; + var txt = content.appendChild(elt("span", spaceStr(tabWidth), "cm-tab")); + txt.setAttribute("role", "presentation"); + txt.setAttribute("cm-text", "\t"); + builder.col += tabWidth; + } else { + var txt = builder.cm.options.specialCharPlaceholder(m[0]); + txt.setAttribute("cm-text", m[0]); + if (ie && ie_version < 9) content.appendChild(elt("span", [txt])); + else content.appendChild(txt); + builder.col += 1; + } + builder.map.push(builder.pos, builder.pos + 1, txt); + builder.pos++; + } + } + if (style || startStyle || endStyle || mustWrap || css) { + var fullStyle = style || ""; + if (startStyle) fullStyle += startStyle; + if (endStyle) fullStyle += endStyle; + var token = elt("span", [content], fullStyle, css); + if (title) token.title = title; + return builder.content.appendChild(token); + } + builder.content.appendChild(content); + } + + function splitSpaces(old) { + var out = " "; + for (var i = 0; i < old.length - 2; ++i) out += i % 2 ? " " : "\u00a0"; + out += " "; + return out; + } + + // Work around nonsense dimensions being reported for stretches of + // right-to-left text. + function buildTokenBadBidi(inner, order) { + return function(builder, text, style, startStyle, endStyle, title, css) { + style = style ? style + " cm-force-border" : "cm-force-border"; + var start = builder.pos, end = start + text.length; + for (;;) { + // Find the part that overlaps with the start of this text + for (var i = 0; i < order.length; i++) { + var part = order[i]; + if (part.to > start && part.from <= start) break; + } + if (part.to >= end) return inner(builder, text, style, startStyle, endStyle, title, css); + inner(builder, text.slice(0, part.to - start), style, startStyle, null, title, css); + startStyle = null; + text = text.slice(part.to - start); + start = part.to; + } + }; + } + + function buildCollapsedSpan(builder, size, marker, ignoreWidget) { + var widget = !ignoreWidget && marker.widgetNode; + if (widget) builder.map.push(builder.pos, builder.pos + size, widget); + if (!ignoreWidget && builder.cm.display.input.needsContentAttribute) { + if (!widget) + widget = builder.content.appendChild(document.createElement("span")); + widget.setAttribute("cm-marker", marker.id); + } + if (widget) { + builder.cm.display.input.setUneditable(widget); + builder.content.appendChild(widget); + } + builder.pos += size; + } + + // Outputs a number of spans to make up a line, taking highlighting + // and marked text into account. + function insertLineContent(line, builder, styles) { + var spans = line.markedSpans, allText = line.text, at = 0; + if (!spans) { + for (var i = 1; i < styles.length; i+=2) + builder.addToken(builder, allText.slice(at, at = styles[i]), interpretTokenStyle(styles[i+1], builder.cm.options)); + return; + } + + var len = allText.length, pos = 0, i = 1, text = "", style, css; + var nextChange = 0, spanStyle, spanEndStyle, spanStartStyle, title, collapsed; + for (;;) { + if (nextChange == pos) { // Update current marker set + spanStyle = spanEndStyle = spanStartStyle = title = css = ""; + collapsed = null; nextChange = Infinity; + var foundBookmarks = []; + for (var j = 0; j < spans.length; ++j) { + var sp = spans[j], m = sp.marker; + if (sp.from <= pos && (sp.to == null || sp.to > pos)) { + if (sp.to != null && nextChange > sp.to) { nextChange = sp.to; spanEndStyle = ""; } + if (m.className) spanStyle += " " + m.className; + if (m.css) css = m.css; + if (m.startStyle && sp.from == pos) spanStartStyle += " " + m.startStyle; + if (m.endStyle && sp.to == nextChange) spanEndStyle += " " + m.endStyle; + if (m.title && !title) title = m.title; + if (m.collapsed && (!collapsed || compareCollapsedMarkers(collapsed.marker, m) < 0)) + collapsed = sp; + } else if (sp.from > pos && nextChange > sp.from) { + nextChange = sp.from; + } + if (m.type == "bookmark" && sp.from == pos && m.widgetNode) foundBookmarks.push(m); + } + if (collapsed && (collapsed.from || 0) == pos) { + buildCollapsedSpan(builder, (collapsed.to == null ? len + 1 : collapsed.to) - pos, + collapsed.marker, collapsed.from == null); + if (collapsed.to == null) return; + } + if (!collapsed && foundBookmarks.length) for (var j = 0; j < foundBookmarks.length; ++j) + buildCollapsedSpan(builder, 0, foundBookmarks[j]); + } + if (pos >= len) break; + + var upto = Math.min(len, nextChange); + while (true) { + if (text) { + var end = pos + text.length; + if (!collapsed) { + var tokenText = end > upto ? text.slice(0, upto - pos) : text; + builder.addToken(builder, tokenText, style ? style + spanStyle : spanStyle, + spanStartStyle, pos + tokenText.length == nextChange ? spanEndStyle : "", title, css); + } + if (end >= upto) {text = text.slice(upto - pos); pos = upto; break;} + pos = end; + spanStartStyle = ""; + } + text = allText.slice(at, at = styles[i++]); + style = interpretTokenStyle(styles[i++], builder.cm.options); + } + } + } + + // DOCUMENT DATA STRUCTURE + + // By default, updates that start and end at the beginning of a line + // are treated specially, in order to make the association of line + // widgets and marker elements with the text behave more intuitive. + function isWholeLineUpdate(doc, change) { + return change.from.ch == 0 && change.to.ch == 0 && lst(change.text) == "" && + (!doc.cm || doc.cm.options.wholeLineUpdateBefore); + } + + // Perform a change on the document data structure. + function updateDoc(doc, change, markedSpans, estimateHeight) { + function spansFor(n) {return markedSpans ? markedSpans[n] : null;} + function update(line, text, spans) { + updateLine(line, text, spans, estimateHeight); + signalLater(line, "change", line, change); + } + function linesFor(start, end) { + for (var i = start, result = []; i < end; ++i) + result.push(new Line(text[i], spansFor(i), estimateHeight)); + return result; + } + + var from = change.from, to = change.to, text = change.text; + var firstLine = getLine(doc, from.line), lastLine = getLine(doc, to.line); + var lastText = lst(text), lastSpans = spansFor(text.length - 1), nlines = to.line - from.line; + + // Adjust the line structure + if (change.full) { + doc.insert(0, linesFor(0, text.length)); + doc.remove(text.length, doc.size - text.length); + } else if (isWholeLineUpdate(doc, change)) { + // This is a whole-line replace. Treated specially to make + // sure line objects move the way they are supposed to. + var added = linesFor(0, text.length - 1); + update(lastLine, lastLine.text, lastSpans); + if (nlines) doc.remove(from.line, nlines); + if (added.length) doc.insert(from.line, added); + } else if (firstLine == lastLine) { + if (text.length == 1) { + update(firstLine, firstLine.text.slice(0, from.ch) + lastText + firstLine.text.slice(to.ch), lastSpans); + } else { + var added = linesFor(1, text.length - 1); + added.push(new Line(lastText + firstLine.text.slice(to.ch), lastSpans, estimateHeight)); + update(firstLine, firstLine.text.slice(0, from.ch) + text[0], spansFor(0)); + doc.insert(from.line + 1, added); + } + } else if (text.length == 1) { + update(firstLine, firstLine.text.slice(0, from.ch) + text[0] + lastLine.text.slice(to.ch), spansFor(0)); + doc.remove(from.line + 1, nlines); + } else { + update(firstLine, firstLine.text.slice(0, from.ch) + text[0], spansFor(0)); + update(lastLine, lastText + lastLine.text.slice(to.ch), lastSpans); + var added = linesFor(1, text.length - 1); + if (nlines > 1) doc.remove(from.line + 1, nlines - 1); + doc.insert(from.line + 1, added); + } + + signalLater(doc, "change", doc, change); + } + + // The document is represented as a BTree consisting of leaves, with + // chunk of lines in them, and branches, with up to ten leaves or + // other branch nodes below them. The top node is always a branch + // node, and is the document object itself (meaning it has + // additional methods and properties). + // + // All nodes have parent links. The tree is used both to go from + // line numbers to line objects, and to go from objects to numbers. + // It also indexes by height, and is used to convert between height + // and line object, and to find the total height of the document. + // + // See also http://marijnhaverbeke.nl/blog/codemirror-line-tree.html + + function LeafChunk(lines) { + this.lines = lines; + this.parent = null; + for (var i = 0, height = 0; i < lines.length; ++i) { + lines[i].parent = this; + height += lines[i].height; + } + this.height = height; + } + + LeafChunk.prototype = { + chunkSize: function() { return this.lines.length; }, + // Remove the n lines at offset 'at'. + removeInner: function(at, n) { + for (var i = at, e = at + n; i < e; ++i) { + var line = this.lines[i]; + this.height -= line.height; + cleanUpLine(line); + signalLater(line, "delete"); + } + this.lines.splice(at, n); + }, + // Helper used to collapse a small branch into a single leaf. + collapse: function(lines) { + lines.push.apply(lines, this.lines); + }, + // Insert the given array of lines at offset 'at', count them as + // having the given height. + insertInner: function(at, lines, height) { + this.height += height; + this.lines = this.lines.slice(0, at).concat(lines).concat(this.lines.slice(at)); + for (var i = 0; i < lines.length; ++i) lines[i].parent = this; + }, + // Used to iterate over a part of the tree. + iterN: function(at, n, op) { + for (var e = at + n; at < e; ++at) + if (op(this.lines[at])) return true; + } + }; + + function BranchChunk(children) { + this.children = children; + var size = 0, height = 0; + for (var i = 0; i < children.length; ++i) { + var ch = children[i]; + size += ch.chunkSize(); height += ch.height; + ch.parent = this; + } + this.size = size; + this.height = height; + this.parent = null; + } + + BranchChunk.prototype = { + chunkSize: function() { return this.size; }, + removeInner: function(at, n) { + this.size -= n; + for (var i = 0; i < this.children.length; ++i) { + var child = this.children[i], sz = child.chunkSize(); + if (at < sz) { + var rm = Math.min(n, sz - at), oldHeight = child.height; + child.removeInner(at, rm); + this.height -= oldHeight - child.height; + if (sz == rm) { this.children.splice(i--, 1); child.parent = null; } + if ((n -= rm) == 0) break; + at = 0; + } else at -= sz; + } + // If the result is smaller than 25 lines, ensure that it is a + // single leaf node. + if (this.size - n < 25 && + (this.children.length > 1 || !(this.children[0] instanceof LeafChunk))) { + var lines = []; + this.collapse(lines); + this.children = [new LeafChunk(lines)]; + this.children[0].parent = this; + } + }, + collapse: function(lines) { + for (var i = 0; i < this.children.length; ++i) this.children[i].collapse(lines); + }, + insertInner: function(at, lines, height) { + this.size += lines.length; + this.height += height; + for (var i = 0; i < this.children.length; ++i) { + var child = this.children[i], sz = child.chunkSize(); + if (at <= sz) { + child.insertInner(at, lines, height); + if (child.lines && child.lines.length > 50) { + while (child.lines.length > 50) { + var spilled = child.lines.splice(child.lines.length - 25, 25); + var newleaf = new LeafChunk(spilled); + child.height -= newleaf.height; + this.children.splice(i + 1, 0, newleaf); + newleaf.parent = this; + } + this.maybeSpill(); + } + break; + } + at -= sz; + } + }, + // When a node has grown, check whether it should be split. + maybeSpill: function() { + if (this.children.length <= 10) return; + var me = this; + do { + var spilled = me.children.splice(me.children.length - 5, 5); + var sibling = new BranchChunk(spilled); + if (!me.parent) { // Become the parent node + var copy = new BranchChunk(me.children); + copy.parent = me; + me.children = [copy, sibling]; + me = copy; + } else { + me.size -= sibling.size; + me.height -= sibling.height; + var myIndex = indexOf(me.parent.children, me); + me.parent.children.splice(myIndex + 1, 0, sibling); + } + sibling.parent = me.parent; + } while (me.children.length > 10); + me.parent.maybeSpill(); + }, + iterN: function(at, n, op) { + for (var i = 0; i < this.children.length; ++i) { + var child = this.children[i], sz = child.chunkSize(); + if (at < sz) { + var used = Math.min(n, sz - at); + if (child.iterN(at, used, op)) return true; + if ((n -= used) == 0) break; + at = 0; + } else at -= sz; + } + } + }; + + var nextDocId = 0; + var Doc = CodeMirror.Doc = function(text, mode, firstLine) { + if (!(this instanceof Doc)) return new Doc(text, mode, firstLine); + if (firstLine == null) firstLine = 0; + + BranchChunk.call(this, [new LeafChunk([new Line("", null)])]); + this.first = firstLine; + this.scrollTop = this.scrollLeft = 0; + this.cantEdit = false; + this.cleanGeneration = 1; + this.frontier = firstLine; + var start = Pos(firstLine, 0); + this.sel = simpleSelection(start); + this.history = new History(null); + this.id = ++nextDocId; + this.modeOption = mode; + + if (typeof text == "string") text = splitLines(text); + updateDoc(this, {from: start, to: start, text: text}); + setSelection(this, simpleSelection(start), sel_dontScroll); + }; + + Doc.prototype = createObj(BranchChunk.prototype, { + constructor: Doc, + // Iterate over the document. Supports two forms -- with only one + // argument, it calls that for each line in the document. With + // three, it iterates over the range given by the first two (with + // the second being non-inclusive). + iter: function(from, to, op) { + if (op) this.iterN(from - this.first, to - from, op); + else this.iterN(this.first, this.first + this.size, from); + }, + + // Non-public interface for adding and removing lines. + insert: function(at, lines) { + var height = 0; + for (var i = 0; i < lines.length; ++i) height += lines[i].height; + this.insertInner(at - this.first, lines, height); + }, + remove: function(at, n) { this.removeInner(at - this.first, n); }, + + // From here, the methods are part of the public interface. Most + // are also available from CodeMirror (editor) instances. + + getValue: function(lineSep) { + var lines = getLines(this, this.first, this.first + this.size); + if (lineSep === false) return lines; + return lines.join(lineSep || "\n"); + }, + setValue: docMethodOp(function(code) { + var top = Pos(this.first, 0), last = this.first + this.size - 1; + makeChange(this, {from: top, to: Pos(last, getLine(this, last).text.length), + text: splitLines(code), origin: "setValue", full: true}, true); + setSelection(this, simpleSelection(top)); + }), + replaceRange: function(code, from, to, origin) { + from = clipPos(this, from); + to = to ? clipPos(this, to) : from; + replaceRange(this, code, from, to, origin); + }, + getRange: function(from, to, lineSep) { + var lines = getBetween(this, clipPos(this, from), clipPos(this, to)); + if (lineSep === false) return lines; + return lines.join(lineSep || "\n"); + }, + + getLine: function(line) {var l = this.getLineHandle(line); return l && l.text;}, + + getLineHandle: function(line) {if (isLine(this, line)) return getLine(this, line);}, + getLineNumber: function(line) {return lineNo(line);}, + + getLineHandleVisualStart: function(line) { + if (typeof line == "number") line = getLine(this, line); + return visualLine(line); + }, + + lineCount: function() {return this.size;}, + firstLine: function() {return this.first;}, + lastLine: function() {return this.first + this.size - 1;}, + + clipPos: function(pos) {return clipPos(this, pos);}, + + getCursor: function(start) { + var range = this.sel.primary(), pos; + if (start == null || start == "head") pos = range.head; + else if (start == "anchor") pos = range.anchor; + else if (start == "end" || start == "to" || start === false) pos = range.to(); + else pos = range.from(); + return pos; + }, + listSelections: function() { return this.sel.ranges; }, + somethingSelected: function() {return this.sel.somethingSelected();}, + + setCursor: docMethodOp(function(line, ch, options) { + setSimpleSelection(this, clipPos(this, typeof line == "number" ? Pos(line, ch || 0) : line), null, options); + }), + setSelection: docMethodOp(function(anchor, head, options) { + setSimpleSelection(this, clipPos(this, anchor), clipPos(this, head || anchor), options); + }), + extendSelection: docMethodOp(function(head, other, options) { + extendSelection(this, clipPos(this, head), other && clipPos(this, other), options); + }), + extendSelections: docMethodOp(function(heads, options) { + extendSelections(this, clipPosArray(this, heads, options)); + }), + extendSelectionsBy: docMethodOp(function(f, options) { + extendSelections(this, map(this.sel.ranges, f), options); + }), + setSelections: docMethodOp(function(ranges, primary, options) { + if (!ranges.length) return; + for (var i = 0, out = []; i < ranges.length; i++) + out[i] = new Range(clipPos(this, ranges[i].anchor), + clipPos(this, ranges[i].head)); + if (primary == null) primary = Math.min(ranges.length - 1, this.sel.primIndex); + setSelection(this, normalizeSelection(out, primary), options); + }), + addSelection: docMethodOp(function(anchor, head, options) { + var ranges = this.sel.ranges.slice(0); + ranges.push(new Range(clipPos(this, anchor), clipPos(this, head || anchor))); + setSelection(this, normalizeSelection(ranges, ranges.length - 1), options); + }), + + getSelection: function(lineSep) { + var ranges = this.sel.ranges, lines; + for (var i = 0; i < ranges.length; i++) { + var sel = getBetween(this, ranges[i].from(), ranges[i].to()); + lines = lines ? lines.concat(sel) : sel; + } + if (lineSep === false) return lines; + else return lines.join(lineSep || "\n"); + }, + getSelections: function(lineSep) { + var parts = [], ranges = this.sel.ranges; + for (var i = 0; i < ranges.length; i++) { + var sel = getBetween(this, ranges[i].from(), ranges[i].to()); + if (lineSep !== false) sel = sel.join(lineSep || "\n"); + parts[i] = sel; + } + return parts; + }, + replaceSelection: function(code, collapse, origin) { + var dup = []; + for (var i = 0; i < this.sel.ranges.length; i++) + dup[i] = code; + this.replaceSelections(dup, collapse, origin || "+input"); + }, + replaceSelections: docMethodOp(function(code, collapse, origin) { + var changes = [], sel = this.sel; + for (var i = 0; i < sel.ranges.length; i++) { + var range = sel.ranges[i]; + changes[i] = {from: range.from(), to: range.to(), text: splitLines(code[i]), origin: origin}; + } + var newSel = collapse && collapse != "end" && computeReplacedSel(this, changes, collapse); + for (var i = changes.length - 1; i >= 0; i--) + makeChange(this, changes[i]); + if (newSel) setSelectionReplaceHistory(this, newSel); + else if (this.cm) ensureCursorVisible(this.cm); + }), + undo: docMethodOp(function() {makeChangeFromHistory(this, "undo");}), + redo: docMethodOp(function() {makeChangeFromHistory(this, "redo");}), + undoSelection: docMethodOp(function() {makeChangeFromHistory(this, "undo", true);}), + redoSelection: docMethodOp(function() {makeChangeFromHistory(this, "redo", true);}), + + setExtending: function(val) {this.extend = val;}, + getExtending: function() {return this.extend;}, + + historySize: function() { + var hist = this.history, done = 0, undone = 0; + for (var i = 0; i < hist.done.length; i++) if (!hist.done[i].ranges) ++done; + for (var i = 0; i < hist.undone.length; i++) if (!hist.undone[i].ranges) ++undone; + return {undo: done, redo: undone}; + }, + clearHistory: function() {this.history = new History(this.history.maxGeneration);}, + + markClean: function() { + this.cleanGeneration = this.changeGeneration(true); + }, + changeGeneration: function(forceSplit) { + if (forceSplit) + this.history.lastOp = this.history.lastSelOp = this.history.lastOrigin = null; + return this.history.generation; + }, + isClean: function (gen) { + return this.history.generation == (gen || this.cleanGeneration); + }, + + getHistory: function() { + return {done: copyHistoryArray(this.history.done), + undone: copyHistoryArray(this.history.undone)}; + }, + setHistory: function(histData) { + var hist = this.history = new History(this.history.maxGeneration); + hist.done = copyHistoryArray(histData.done.slice(0), null, true); + hist.undone = copyHistoryArray(histData.undone.slice(0), null, true); + }, + + addLineClass: docMethodOp(function(handle, where, cls) { + return changeLine(this, handle, where == "gutter" ? "gutter" : "class", function(line) { + var prop = where == "text" ? "textClass" + : where == "background" ? "bgClass" + : where == "gutter" ? "gutterClass" : "wrapClass"; + if (!line[prop]) line[prop] = cls; + else if (classTest(cls).test(line[prop])) return false; + else line[prop] += " " + cls; + return true; + }); + }), + removeLineClass: docMethodOp(function(handle, where, cls) { + return changeLine(this, handle, where == "gutter" ? "gutter" : "class", function(line) { + var prop = where == "text" ? "textClass" + : where == "background" ? "bgClass" + : where == "gutter" ? "gutterClass" : "wrapClass"; + var cur = line[prop]; + if (!cur) return false; + else if (cls == null) line[prop] = null; + else { + var found = cur.match(classTest(cls)); + if (!found) return false; + var end = found.index + found[0].length; + line[prop] = cur.slice(0, found.index) + (!found.index || end == cur.length ? "" : " ") + cur.slice(end) || null; + } + return true; + }); + }), + + addLineWidget: docMethodOp(function(handle, node, options) { + return addLineWidget(this, handle, node, options); + }), + removeLineWidget: function(widget) { widget.clear(); }, + + markText: function(from, to, options) { + return markText(this, clipPos(this, from), clipPos(this, to), options, "range"); + }, + setBookmark: function(pos, options) { + var realOpts = {replacedWith: options && (options.nodeType == null ? options.widget : options), + insertLeft: options && options.insertLeft, + clearWhenEmpty: false, shared: options && options.shared, + handleMouseEvents: options && options.handleMouseEvents}; + pos = clipPos(this, pos); + return markText(this, pos, pos, realOpts, "bookmark"); + }, + findMarksAt: function(pos) { + pos = clipPos(this, pos); + var markers = [], spans = getLine(this, pos.line).markedSpans; + if (spans) for (var i = 0; i < spans.length; ++i) { + var span = spans[i]; + if ((span.from == null || span.from <= pos.ch) && + (span.to == null || span.to >= pos.ch)) + markers.push(span.marker.parent || span.marker); + } + return markers; + }, + findMarks: function(from, to, filter) { + from = clipPos(this, from); to = clipPos(this, to); + var found = [], lineNo = from.line; + this.iter(from.line, to.line + 1, function(line) { + var spans = line.markedSpans; + if (spans) for (var i = 0; i < spans.length; i++) { + var span = spans[i]; + if (!(lineNo == from.line && from.ch > span.to || + span.from == null && lineNo != from.line|| + lineNo == to.line && span.from > to.ch) && + (!filter || filter(span.marker))) + found.push(span.marker.parent || span.marker); + } + ++lineNo; + }); + return found; + }, + getAllMarks: function() { + var markers = []; + this.iter(function(line) { + var sps = line.markedSpans; + if (sps) for (var i = 0; i < sps.length; ++i) + if (sps[i].from != null) markers.push(sps[i].marker); + }); + return markers; + }, + + posFromIndex: function(off) { + var ch, lineNo = this.first; + this.iter(function(line) { + var sz = line.text.length + 1; + if (sz > off) { ch = off; return true; } + off -= sz; + ++lineNo; + }); + return clipPos(this, Pos(lineNo, ch)); + }, + indexFromPos: function (coords) { + coords = clipPos(this, coords); + var index = coords.ch; + if (coords.line < this.first || coords.ch < 0) return 0; + this.iter(this.first, coords.line, function (line) { + index += line.text.length + 1; + }); + return index; + }, + + copy: function(copyHistory) { + var doc = new Doc(getLines(this, this.first, this.first + this.size), this.modeOption, this.first); + doc.scrollTop = this.scrollTop; doc.scrollLeft = this.scrollLeft; + doc.sel = this.sel; + doc.extend = false; + if (copyHistory) { + doc.history.undoDepth = this.history.undoDepth; + doc.setHistory(this.getHistory()); + } + return doc; + }, + + linkedDoc: function(options) { + if (!options) options = {}; + var from = this.first, to = this.first + this.size; + if (options.from != null && options.from > from) from = options.from; + if (options.to != null && options.to < to) to = options.to; + var copy = new Doc(getLines(this, from, to), options.mode || this.modeOption, from); + if (options.sharedHist) copy.history = this.history; + (this.linked || (this.linked = [])).push({doc: copy, sharedHist: options.sharedHist}); + copy.linked = [{doc: this, isParent: true, sharedHist: options.sharedHist}]; + copySharedMarkers(copy, findSharedMarkers(this)); + return copy; + }, + unlinkDoc: function(other) { + if (other instanceof CodeMirror) other = other.doc; + if (this.linked) for (var i = 0; i < this.linked.length; ++i) { + var link = this.linked[i]; + if (link.doc != other) continue; + this.linked.splice(i, 1); + other.unlinkDoc(this); + detachSharedMarkers(findSharedMarkers(this)); + break; + } + // If the histories were shared, split them again + if (other.history == this.history) { + var splitIds = [other.id]; + linkedDocs(other, function(doc) {splitIds.push(doc.id);}, true); + other.history = new History(null); + other.history.done = copyHistoryArray(this.history.done, splitIds); + other.history.undone = copyHistoryArray(this.history.undone, splitIds); + } + }, + iterLinkedDocs: function(f) {linkedDocs(this, f);}, + + getMode: function() {return this.mode;}, + getEditor: function() {return this.cm;} + }); + + // Public alias. + Doc.prototype.eachLine = Doc.prototype.iter; + + // Set up methods on CodeMirror's prototype to redirect to the editor's document. + var dontDelegate = "iter insert remove copy getEditor".split(" "); + for (var prop in Doc.prototype) if (Doc.prototype.hasOwnProperty(prop) && indexOf(dontDelegate, prop) < 0) + CodeMirror.prototype[prop] = (function(method) { + return function() {return method.apply(this.doc, arguments);}; + })(Doc.prototype[prop]); + + eventMixin(Doc); + + // Call f for all linked documents. + function linkedDocs(doc, f, sharedHistOnly) { + function propagate(doc, skip, sharedHist) { + if (doc.linked) for (var i = 0; i < doc.linked.length; ++i) { + var rel = doc.linked[i]; + if (rel.doc == skip) continue; + var shared = sharedHist && rel.sharedHist; + if (sharedHistOnly && !shared) continue; + f(rel.doc, shared); + propagate(rel.doc, doc, shared); + } + } + propagate(doc, null, true); + } + + // Attach a document to an editor. + function attachDoc(cm, doc) { + if (doc.cm) throw new Error("This document is already in use."); + cm.doc = doc; + doc.cm = cm; + estimateLineHeights(cm); + loadMode(cm); + if (!cm.options.lineWrapping) findMaxLine(cm); + cm.options.mode = doc.modeOption; + regChange(cm); + } + + // LINE UTILITIES + + // Find the line object corresponding to the given line number. + function getLine(doc, n) { + n -= doc.first; + if (n < 0 || n >= doc.size) throw new Error("There is no line " + (n + doc.first) + " in the document."); + for (var chunk = doc; !chunk.lines;) { + for (var i = 0;; ++i) { + var child = chunk.children[i], sz = child.chunkSize(); + if (n < sz) { chunk = child; break; } + n -= sz; + } + } + return chunk.lines[n]; + } + + // Get the part of a document between two positions, as an array of + // strings. + function getBetween(doc, start, end) { + var out = [], n = start.line; + doc.iter(start.line, end.line + 1, function(line) { + var text = line.text; + if (n == end.line) text = text.slice(0, end.ch); + if (n == start.line) text = text.slice(start.ch); + out.push(text); + ++n; + }); + return out; + } + // Get the lines between from and to, as array of strings. + function getLines(doc, from, to) { + var out = []; + doc.iter(from, to, function(line) { out.push(line.text); }); + return out; + } + + // Update the height of a line, propagating the height change + // upwards to parent nodes. + function updateLineHeight(line, height) { + var diff = height - line.height; + if (diff) for (var n = line; n; n = n.parent) n.height += diff; + } + + // Given a line object, find its line number by walking up through + // its parent links. + function lineNo(line) { + if (line.parent == null) return null; + var cur = line.parent, no = indexOf(cur.lines, line); + for (var chunk = cur.parent; chunk; cur = chunk, chunk = chunk.parent) { + for (var i = 0;; ++i) { + if (chunk.children[i] == cur) break; + no += chunk.children[i].chunkSize(); + } + } + return no + cur.first; + } + + // Find the line at the given vertical position, using the height + // information in the document tree. + function lineAtHeight(chunk, h) { + var n = chunk.first; + outer: do { + for (var i = 0; i < chunk.children.length; ++i) { + var child = chunk.children[i], ch = child.height; + if (h < ch) { chunk = child; continue outer; } + h -= ch; + n += child.chunkSize(); + } + return n; + } while (!chunk.lines); + for (var i = 0; i < chunk.lines.length; ++i) { + var line = chunk.lines[i], lh = line.height; + if (h < lh) break; + h -= lh; + } + return n + i; + } + + + // Find the height above the given line. + function heightAtLine(lineObj) { + lineObj = visualLine(lineObj); + + var h = 0, chunk = lineObj.parent; + for (var i = 0; i < chunk.lines.length; ++i) { + var line = chunk.lines[i]; + if (line == lineObj) break; + else h += line.height; + } + for (var p = chunk.parent; p; chunk = p, p = chunk.parent) { + for (var i = 0; i < p.children.length; ++i) { + var cur = p.children[i]; + if (cur == chunk) break; + else h += cur.height; + } + } + return h; + } + + // Get the bidi ordering for the given line (and cache it). Returns + // false for lines that are fully left-to-right, and an array of + // BidiSpan objects otherwise. + function getOrder(line) { + var order = line.order; + if (order == null) order = line.order = bidiOrdering(line.text); + return order; + } + + // HISTORY + + function History(startGen) { + // Arrays of change events and selections. Doing something adds an + // event to done and clears undo. Undoing moves events from done + // to undone, redoing moves them in the other direction. + this.done = []; this.undone = []; + this.undoDepth = Infinity; + // Used to track when changes can be merged into a single undo + // event + this.lastModTime = this.lastSelTime = 0; + this.lastOp = this.lastSelOp = null; + this.lastOrigin = this.lastSelOrigin = null; + // Used by the isClean() method + this.generation = this.maxGeneration = startGen || 1; + } + + // Create a history change event from an updateDoc-style change + // object. + function historyChangeFromChange(doc, change) { + var histChange = {from: copyPos(change.from), to: changeEnd(change), text: getBetween(doc, change.from, change.to)}; + attachLocalSpans(doc, histChange, change.from.line, change.to.line + 1); + linkedDocs(doc, function(doc) {attachLocalSpans(doc, histChange, change.from.line, change.to.line + 1);}, true); + return histChange; + } + + // Pop all selection events off the end of a history array. Stop at + // a change event. + function clearSelectionEvents(array) { + while (array.length) { + var last = lst(array); + if (last.ranges) array.pop(); + else break; + } + } + + // Find the top change event in the history. Pop off selection + // events that are in the way. + function lastChangeEvent(hist, force) { + if (force) { + clearSelectionEvents(hist.done); + return lst(hist.done); + } else if (hist.done.length && !lst(hist.done).ranges) { + return lst(hist.done); + } else if (hist.done.length > 1 && !hist.done[hist.done.length - 2].ranges) { + hist.done.pop(); + return lst(hist.done); + } + } + + // Register a change in the history. Merges changes that are within + // a single operation, ore are close together with an origin that + // allows merging (starting with "+") into a single event. + function addChangeToHistory(doc, change, selAfter, opId) { + var hist = doc.history; + hist.undone.length = 0; + var time = +new Date, cur; + + if ((hist.lastOp == opId || + hist.lastOrigin == change.origin && change.origin && + ((change.origin.charAt(0) == "+" && doc.cm && hist.lastModTime > time - doc.cm.options.historyEventDelay) || + change.origin.charAt(0) == "*")) && + (cur = lastChangeEvent(hist, hist.lastOp == opId))) { + // Merge this change into the last event + var last = lst(cur.changes); + if (cmp(change.from, change.to) == 0 && cmp(change.from, last.to) == 0) { + // Optimized case for simple insertion -- don't want to add + // new changesets for every character typed + last.to = changeEnd(change); + } else { + // Add new sub-event + cur.changes.push(historyChangeFromChange(doc, change)); + } + } else { + // Can not be merged, start a new event. + var before = lst(hist.done); + if (!before || !before.ranges) + pushSelectionToHistory(doc.sel, hist.done); + cur = {changes: [historyChangeFromChange(doc, change)], + generation: hist.generation}; + hist.done.push(cur); + while (hist.done.length > hist.undoDepth) { + hist.done.shift(); + if (!hist.done[0].ranges) hist.done.shift(); + } + } + hist.done.push(selAfter); + hist.generation = ++hist.maxGeneration; + hist.lastModTime = hist.lastSelTime = time; + hist.lastOp = hist.lastSelOp = opId; + hist.lastOrigin = hist.lastSelOrigin = change.origin; + + if (!last) signal(doc, "historyAdded"); + } + + function selectionEventCanBeMerged(doc, origin, prev, sel) { + var ch = origin.charAt(0); + return ch == "*" || + ch == "+" && + prev.ranges.length == sel.ranges.length && + prev.somethingSelected() == sel.somethingSelected() && + new Date - doc.history.lastSelTime <= (doc.cm ? doc.cm.options.historyEventDelay : 500); + } + + // Called whenever the selection changes, sets the new selection as + // the pending selection in the history, and pushes the old pending + // selection into the 'done' array when it was significantly + // different (in number of selected ranges, emptiness, or time). + function addSelectionToHistory(doc, sel, opId, options) { + var hist = doc.history, origin = options && options.origin; + + // A new event is started when the previous origin does not match + // the current, or the origins don't allow matching. Origins + // starting with * are always merged, those starting with + are + // merged when similar and close together in time. + if (opId == hist.lastSelOp || + (origin && hist.lastSelOrigin == origin && + (hist.lastModTime == hist.lastSelTime && hist.lastOrigin == origin || + selectionEventCanBeMerged(doc, origin, lst(hist.done), sel)))) + hist.done[hist.done.length - 1] = sel; + else + pushSelectionToHistory(sel, hist.done); + + hist.lastSelTime = +new Date; + hist.lastSelOrigin = origin; + hist.lastSelOp = opId; + if (options && options.clearRedo !== false) + clearSelectionEvents(hist.undone); + } + + function pushSelectionToHistory(sel, dest) { + var top = lst(dest); + if (!(top && top.ranges && top.equals(sel))) + dest.push(sel); + } + + // Used to store marked span information in the history. + function attachLocalSpans(doc, change, from, to) { + var existing = change["spans_" + doc.id], n = 0; + doc.iter(Math.max(doc.first, from), Math.min(doc.first + doc.size, to), function(line) { + if (line.markedSpans) + (existing || (existing = change["spans_" + doc.id] = {}))[n] = line.markedSpans; + ++n; + }); + } + + // When un/re-doing restores text containing marked spans, those + // that have been explicitly cleared should not be restored. + function removeClearedSpans(spans) { + if (!spans) return null; + for (var i = 0, out; i < spans.length; ++i) { + if (spans[i].marker.explicitlyCleared) { if (!out) out = spans.slice(0, i); } + else if (out) out.push(spans[i]); + } + return !out ? spans : out.length ? out : null; + } + + // Retrieve and filter the old marked spans stored in a change event. + function getOldSpans(doc, change) { + var found = change["spans_" + doc.id]; + if (!found) return null; + for (var i = 0, nw = []; i < change.text.length; ++i) + nw.push(removeClearedSpans(found[i])); + return nw; + } + + // Used both to provide a JSON-safe object in .getHistory, and, when + // detaching a document, to split the history in two + function copyHistoryArray(events, newGroup, instantiateSel) { + for (var i = 0, copy = []; i < events.length; ++i) { + var event = events[i]; + if (event.ranges) { + copy.push(instantiateSel ? Selection.prototype.deepCopy.call(event) : event); + continue; + } + var changes = event.changes, newChanges = []; + copy.push({changes: newChanges}); + for (var j = 0; j < changes.length; ++j) { + var change = changes[j], m; + newChanges.push({from: change.from, to: change.to, text: change.text}); + if (newGroup) for (var prop in change) if (m = prop.match(/^spans_(\d+)$/)) { + if (indexOf(newGroup, Number(m[1])) > -1) { + lst(newChanges)[prop] = change[prop]; + delete change[prop]; + } + } + } + } + return copy; + } + + // Rebasing/resetting history to deal with externally-sourced changes + + function rebaseHistSelSingle(pos, from, to, diff) { + if (to < pos.line) { + pos.line += diff; + } else if (from < pos.line) { + pos.line = from; + pos.ch = 0; + } + } + + // Tries to rebase an array of history events given a change in the + // document. If the change touches the same lines as the event, the + // event, and everything 'behind' it, is discarded. If the change is + // before the event, the event's positions are updated. Uses a + // copy-on-write scheme for the positions, to avoid having to + // reallocate them all on every rebase, but also avoid problems with + // shared position objects being unsafely updated. + function rebaseHistArray(array, from, to, diff) { + for (var i = 0; i < array.length; ++i) { + var sub = array[i], ok = true; + if (sub.ranges) { + if (!sub.copied) { sub = array[i] = sub.deepCopy(); sub.copied = true; } + for (var j = 0; j < sub.ranges.length; j++) { + rebaseHistSelSingle(sub.ranges[j].anchor, from, to, diff); + rebaseHistSelSingle(sub.ranges[j].head, from, to, diff); + } + continue; + } + for (var j = 0; j < sub.changes.length; ++j) { + var cur = sub.changes[j]; + if (to < cur.from.line) { + cur.from = Pos(cur.from.line + diff, cur.from.ch); + cur.to = Pos(cur.to.line + diff, cur.to.ch); + } else if (from <= cur.to.line) { + ok = false; + break; + } + } + if (!ok) { + array.splice(0, i + 1); + i = 0; + } + } + } + + function rebaseHist(hist, change) { + var from = change.from.line, to = change.to.line, diff = change.text.length - (to - from) - 1; + rebaseHistArray(hist.done, from, to, diff); + rebaseHistArray(hist.undone, from, to, diff); + } + + // EVENT UTILITIES + + // Due to the fact that we still support jurassic IE versions, some + // compatibility wrappers are needed. + + var e_preventDefault = CodeMirror.e_preventDefault = function(e) { + if (e.preventDefault) e.preventDefault(); + else e.returnValue = false; + }; + var e_stopPropagation = CodeMirror.e_stopPropagation = function(e) { + if (e.stopPropagation) e.stopPropagation(); + else e.cancelBubble = true; + }; + function e_defaultPrevented(e) { + return e.defaultPrevented != null ? e.defaultPrevented : e.returnValue == false; + } + var e_stop = CodeMirror.e_stop = function(e) {e_preventDefault(e); e_stopPropagation(e);}; + + function e_target(e) {return e.target || e.srcElement;} + function e_button(e) { + var b = e.which; + if (b == null) { + if (e.button & 1) b = 1; + else if (e.button & 2) b = 3; + else if (e.button & 4) b = 2; + } + if (mac && e.ctrlKey && b == 1) b = 3; + return b; + } + + // EVENT HANDLING + + // Lightweight event framework. on/off also work on DOM nodes, + // registering native DOM handlers. + + var on = CodeMirror.on = function(emitter, type, f) { + if (emitter.addEventListener) + emitter.addEventListener(type, f, false); + else if (emitter.attachEvent) + emitter.attachEvent("on" + type, f); + else { + var map = emitter._handlers || (emitter._handlers = {}); + var arr = map[type] || (map[type] = []); + arr.push(f); + } + }; + + var off = CodeMirror.off = function(emitter, type, f) { + if (emitter.removeEventListener) + emitter.removeEventListener(type, f, false); + else if (emitter.detachEvent) + emitter.detachEvent("on" + type, f); + else { + var arr = emitter._handlers && emitter._handlers[type]; + if (!arr) return; + for (var i = 0; i < arr.length; ++i) + if (arr[i] == f) { arr.splice(i, 1); break; } + } + }; + + var signal = CodeMirror.signal = function(emitter, type /*, values...*/) { + var arr = emitter._handlers && emitter._handlers[type]; + if (!arr) return; + var args = Array.prototype.slice.call(arguments, 2); + for (var i = 0; i < arr.length; ++i) arr[i].apply(null, args); + }; + + var orphanDelayedCallbacks = null; + + // Often, we want to signal events at a point where we are in the + // middle of some work, but don't want the handler to start calling + // other methods on the editor, which might be in an inconsistent + // state or simply not expect any other events to happen. + // signalLater looks whether there are any handlers, and schedules + // them to be executed when the last operation ends, or, if no + // operation is active, when a timeout fires. + function signalLater(emitter, type /*, values...*/) { + var arr = emitter._handlers && emitter._handlers[type]; + if (!arr) return; + var args = Array.prototype.slice.call(arguments, 2), list; + if (operationGroup) { + list = operationGroup.delayedCallbacks; + } else if (orphanDelayedCallbacks) { + list = orphanDelayedCallbacks; + } else { + list = orphanDelayedCallbacks = []; + setTimeout(fireOrphanDelayed, 0); + } + function bnd(f) {return function(){f.apply(null, args);};}; + for (var i = 0; i < arr.length; ++i) + list.push(bnd(arr[i])); + } + + function fireOrphanDelayed() { + var delayed = orphanDelayedCallbacks; + orphanDelayedCallbacks = null; + for (var i = 0; i < delayed.length; ++i) delayed[i](); + } + + // The DOM events that CodeMirror handles can be overridden by + // registering a (non-DOM) handler on the editor for the event name, + // and preventDefault-ing the event in that handler. + function signalDOMEvent(cm, e, override) { + if (typeof e == "string") + e = {type: e, preventDefault: function() { this.defaultPrevented = true; }}; + signal(cm, override || e.type, cm, e); + return e_defaultPrevented(e) || e.codemirrorIgnore; + } + + function signalCursorActivity(cm) { + var arr = cm._handlers && cm._handlers.cursorActivity; + if (!arr) return; + var set = cm.curOp.cursorActivityHandlers || (cm.curOp.cursorActivityHandlers = []); + for (var i = 0; i < arr.length; ++i) if (indexOf(set, arr[i]) == -1) + set.push(arr[i]); + } + + function hasHandler(emitter, type) { + var arr = emitter._handlers && emitter._handlers[type]; + return arr && arr.length > 0; + } + + // Add on and off methods to a constructor's prototype, to make + // registering events on such objects more convenient. + function eventMixin(ctor) { + ctor.prototype.on = function(type, f) {on(this, type, f);}; + ctor.prototype.off = function(type, f) {off(this, type, f);}; + } + + // MISC UTILITIES + + // Number of pixels added to scroller and sizer to hide scrollbar + var scrollerGap = 30; + + // Returned or thrown by various protocols to signal 'I'm not + // handling this'. + var Pass = CodeMirror.Pass = {toString: function(){return "CodeMirror.Pass";}}; + + // Reused option objects for setSelection & friends + var sel_dontScroll = {scroll: false}, sel_mouse = {origin: "*mouse"}, sel_move = {origin: "+move"}; + + function Delayed() {this.id = null;} + Delayed.prototype.set = function(ms, f) { + clearTimeout(this.id); + this.id = setTimeout(f, ms); + }; + + // Counts the column offset in a string, taking tabs into account. + // Used mostly to find indentation. + var countColumn = CodeMirror.countColumn = function(string, end, tabSize, startIndex, startValue) { + if (end == null) { + end = string.search(/[^\s\u00a0]/); + if (end == -1) end = string.length; + } + for (var i = startIndex || 0, n = startValue || 0;;) { + var nextTab = string.indexOf("\t", i); + if (nextTab < 0 || nextTab >= end) + return n + (end - i); + n += nextTab - i; + n += tabSize - (n % tabSize); + i = nextTab + 1; + } + }; + + // The inverse of countColumn -- find the offset that corresponds to + // a particular column. + function findColumn(string, goal, tabSize) { + for (var pos = 0, col = 0;;) { + var nextTab = string.indexOf("\t", pos); + if (nextTab == -1) nextTab = string.length; + var skipped = nextTab - pos; + if (nextTab == string.length || col + skipped >= goal) + return pos + Math.min(skipped, goal - col); + col += nextTab - pos; + col += tabSize - (col % tabSize); + pos = nextTab + 1; + if (col >= goal) return pos; + } + } + + var spaceStrs = [""]; + function spaceStr(n) { + while (spaceStrs.length <= n) + spaceStrs.push(lst(spaceStrs) + " "); + return spaceStrs[n]; + } + + function lst(arr) { return arr[arr.length-1]; } + + var selectInput = function(node) { node.select(); }; + if (ios) // Mobile Safari apparently has a bug where select() is broken. + selectInput = function(node) { node.selectionStart = 0; node.selectionEnd = node.value.length; }; + else if (ie) // Suppress mysterious IE10 errors + selectInput = function(node) { try { node.select(); } catch(_e) {} }; + + function indexOf(array, elt) { + for (var i = 0; i < array.length; ++i) + if (array[i] == elt) return i; + return -1; + } + function map(array, f) { + var out = []; + for (var i = 0; i < array.length; i++) out[i] = f(array[i], i); + return out; + } + + function nothing() {} + + function createObj(base, props) { + var inst; + if (Object.create) { + inst = Object.create(base); + } else { + nothing.prototype = base; + inst = new nothing(); + } + if (props) copyObj(props, inst); + return inst; + }; + + function copyObj(obj, target, overwrite) { + if (!target) target = {}; + for (var prop in obj) + if (obj.hasOwnProperty(prop) && (overwrite !== false || !target.hasOwnProperty(prop))) + target[prop] = obj[prop]; + return target; + } + + function bind(f) { + var args = Array.prototype.slice.call(arguments, 1); + return function(){return f.apply(null, args);}; + } + + var nonASCIISingleCaseWordChar = /[\u00df\u0587\u0590-\u05f4\u0600-\u06ff\u3040-\u309f\u30a0-\u30ff\u3400-\u4db5\u4e00-\u9fcc\uac00-\ud7af]/; + var isWordCharBasic = CodeMirror.isWordChar = function(ch) { + return /\w/.test(ch) || ch > "\x80" && + (ch.toUpperCase() != ch.toLowerCase() || nonASCIISingleCaseWordChar.test(ch)); + }; + function isWordChar(ch, helper) { + if (!helper) return isWordCharBasic(ch); + if (helper.source.indexOf("\\w") > -1 && isWordCharBasic(ch)) return true; + return helper.test(ch); + } + + function isEmpty(obj) { + for (var n in obj) if (obj.hasOwnProperty(n) && obj[n]) return false; + return true; + } + + // Extending unicode characters. A series of a non-extending char + + // any number of extending chars is treated as a single unit as far + // as editing and measuring is concerned. This is not fully correct, + // since some scripts/fonts/browsers also treat other configurations + // of code points as a group. + var extendingChars = /[\u0300-\u036f\u0483-\u0489\u0591-\u05bd\u05bf\u05c1\u05c2\u05c4\u05c5\u05c7\u0610-\u061a\u064b-\u065e\u0670\u06d6-\u06dc\u06de-\u06e4\u06e7\u06e8\u06ea-\u06ed\u0711\u0730-\u074a\u07a6-\u07b0\u07eb-\u07f3\u0816-\u0819\u081b-\u0823\u0825-\u0827\u0829-\u082d\u0900-\u0902\u093c\u0941-\u0948\u094d\u0951-\u0955\u0962\u0963\u0981\u09bc\u09be\u09c1-\u09c4\u09cd\u09d7\u09e2\u09e3\u0a01\u0a02\u0a3c\u0a41\u0a42\u0a47\u0a48\u0a4b-\u0a4d\u0a51\u0a70\u0a71\u0a75\u0a81\u0a82\u0abc\u0ac1-\u0ac5\u0ac7\u0ac8\u0acd\u0ae2\u0ae3\u0b01\u0b3c\u0b3e\u0b3f\u0b41-\u0b44\u0b4d\u0b56\u0b57\u0b62\u0b63\u0b82\u0bbe\u0bc0\u0bcd\u0bd7\u0c3e-\u0c40\u0c46-\u0c48\u0c4a-\u0c4d\u0c55\u0c56\u0c62\u0c63\u0cbc\u0cbf\u0cc2\u0cc6\u0ccc\u0ccd\u0cd5\u0cd6\u0ce2\u0ce3\u0d3e\u0d41-\u0d44\u0d4d\u0d57\u0d62\u0d63\u0dca\u0dcf\u0dd2-\u0dd4\u0dd6\u0ddf\u0e31\u0e34-\u0e3a\u0e47-\u0e4e\u0eb1\u0eb4-\u0eb9\u0ebb\u0ebc\u0ec8-\u0ecd\u0f18\u0f19\u0f35\u0f37\u0f39\u0f71-\u0f7e\u0f80-\u0f84\u0f86\u0f87\u0f90-\u0f97\u0f99-\u0fbc\u0fc6\u102d-\u1030\u1032-\u1037\u1039\u103a\u103d\u103e\u1058\u1059\u105e-\u1060\u1071-\u1074\u1082\u1085\u1086\u108d\u109d\u135f\u1712-\u1714\u1732-\u1734\u1752\u1753\u1772\u1773\u17b7-\u17bd\u17c6\u17c9-\u17d3\u17dd\u180b-\u180d\u18a9\u1920-\u1922\u1927\u1928\u1932\u1939-\u193b\u1a17\u1a18\u1a56\u1a58-\u1a5e\u1a60\u1a62\u1a65-\u1a6c\u1a73-\u1a7c\u1a7f\u1b00-\u1b03\u1b34\u1b36-\u1b3a\u1b3c\u1b42\u1b6b-\u1b73\u1b80\u1b81\u1ba2-\u1ba5\u1ba8\u1ba9\u1c2c-\u1c33\u1c36\u1c37\u1cd0-\u1cd2\u1cd4-\u1ce0\u1ce2-\u1ce8\u1ced\u1dc0-\u1de6\u1dfd-\u1dff\u200c\u200d\u20d0-\u20f0\u2cef-\u2cf1\u2de0-\u2dff\u302a-\u302f\u3099\u309a\ua66f-\ua672\ua67c\ua67d\ua6f0\ua6f1\ua802\ua806\ua80b\ua825\ua826\ua8c4\ua8e0-\ua8f1\ua926-\ua92d\ua947-\ua951\ua980-\ua982\ua9b3\ua9b6-\ua9b9\ua9bc\uaa29-\uaa2e\uaa31\uaa32\uaa35\uaa36\uaa43\uaa4c\uaab0\uaab2-\uaab4\uaab7\uaab8\uaabe\uaabf\uaac1\uabe5\uabe8\uabed\udc00-\udfff\ufb1e\ufe00-\ufe0f\ufe20-\ufe26\uff9e\uff9f]/; + function isExtendingChar(ch) { return ch.charCodeAt(0) >= 768 && extendingChars.test(ch); } + + // DOM UTILITIES + + function elt(tag, content, className, style) { + var e = document.createElement(tag); + if (className) e.className = className; + if (style) e.style.cssText = style; + if (typeof content == "string") e.appendChild(document.createTextNode(content)); + else if (content) for (var i = 0; i < content.length; ++i) e.appendChild(content[i]); + return e; + } + + var range; + if (document.createRange) range = function(node, start, end, endNode) { + var r = document.createRange(); + r.setEnd(endNode || node, end); + r.setStart(node, start); + return r; + }; + else range = function(node, start, end) { + var r = document.body.createTextRange(); + try { r.moveToElementText(node.parentNode); } + catch(e) { return r; } + r.collapse(true); + r.moveEnd("character", end); + r.moveStart("character", start); + return r; + }; + + function removeChildren(e) { + for (var count = e.childNodes.length; count > 0; --count) + e.removeChild(e.firstChild); + return e; + } + + function removeChildrenAndAdd(parent, e) { + return removeChildren(parent).appendChild(e); + } + + var contains = CodeMirror.contains = function(parent, child) { + if (child.nodeType == 3) // Android browser always returns false when child is a textnode + child = child.parentNode; + if (parent.contains) + return parent.contains(child); + do { + if (child.nodeType == 11) child = child.host; + if (child == parent) return true; + } while (child = child.parentNode); + }; + + function activeElt() { return document.activeElement; } + // Older versions of IE throws unspecified error when touching + // document.activeElement in some cases (during loading, in iframe) + if (ie && ie_version < 11) activeElt = function() { + try { return document.activeElement; } + catch(e) { return document.body; } + }; + + function classTest(cls) { return new RegExp("(^|\\s)" + cls + "(?:$|\\s)\\s*"); } + var rmClass = CodeMirror.rmClass = function(node, cls) { + var current = node.className; + var match = classTest(cls).exec(current); + if (match) { + var after = current.slice(match.index + match[0].length); + node.className = current.slice(0, match.index) + (after ? match[1] + after : ""); + } + }; + var addClass = CodeMirror.addClass = function(node, cls) { + var current = node.className; + if (!classTest(cls).test(current)) node.className += (current ? " " : "") + cls; + }; + function joinClasses(a, b) { + var as = a.split(" "); + for (var i = 0; i < as.length; i++) + if (as[i] && !classTest(as[i]).test(b)) b += " " + as[i]; + return b; + } + + // WINDOW-WIDE EVENTS + + // These must be handled carefully, because naively registering a + // handler for each editor will cause the editors to never be + // garbage collected. + + function forEachCodeMirror(f) { + if (!document.body.getElementsByClassName) return; + var byClass = document.body.getElementsByClassName("CodeMirror"); + for (var i = 0; i < byClass.length; i++) { + var cm = byClass[i].CodeMirror; + if (cm) f(cm); + } + } + + var globalsRegistered = false; + function ensureGlobalHandlers() { + if (globalsRegistered) return; + registerGlobalHandlers(); + globalsRegistered = true; + } + function registerGlobalHandlers() { + // When the window resizes, we need to refresh active editors. + var resizeTimer; + on(window, "resize", function() { + if (resizeTimer == null) resizeTimer = setTimeout(function() { + resizeTimer = null; + forEachCodeMirror(onResize); + }, 100); + }); + // When the window loses focus, we want to show the editor as blurred + on(window, "blur", function() { + forEachCodeMirror(onBlur); + }); + } + + // FEATURE DETECTION + + // Detect drag-and-drop + var dragAndDrop = function() { + // There is *some* kind of drag-and-drop support in IE6-8, but I + // couldn't get it to work yet. + if (ie && ie_version < 9) return false; + var div = elt('div'); + return "draggable" in div || "dragDrop" in div; + }(); + + var zwspSupported; + function zeroWidthElement(measure) { + if (zwspSupported == null) { + var test = elt("span", "\u200b"); + removeChildrenAndAdd(measure, elt("span", [test, document.createTextNode("x")])); + if (measure.firstChild.offsetHeight != 0) + zwspSupported = test.offsetWidth <= 1 && test.offsetHeight > 2 && !(ie && ie_version < 8); + } + var node = zwspSupported ? elt("span", "\u200b") : + elt("span", "\u00a0", null, "display: inline-block; width: 1px; margin-right: -1px"); + node.setAttribute("cm-text", ""); + return node; + } + + // Feature-detect IE's crummy client rect reporting for bidi text + var badBidiRects; + function hasBadBidiRects(measure) { + if (badBidiRects != null) return badBidiRects; + var txt = removeChildrenAndAdd(measure, document.createTextNode("A\u062eA")); + var r0 = range(txt, 0, 1).getBoundingClientRect(); + if (!r0 || r0.left == r0.right) return false; // Safari returns null in some cases (#2780) + var r1 = range(txt, 1, 2).getBoundingClientRect(); + return badBidiRects = (r1.right - r0.right < 3); + } + + // See if "".split is the broken IE version, if so, provide an + // alternative way to split lines. + var splitLines = CodeMirror.splitLines = "\n\nb".split(/\n/).length != 3 ? function(string) { + var pos = 0, result = [], l = string.length; + while (pos <= l) { + var nl = string.indexOf("\n", pos); + if (nl == -1) nl = string.length; + var line = string.slice(pos, string.charAt(nl - 1) == "\r" ? nl - 1 : nl); + var rt = line.indexOf("\r"); + if (rt != -1) { + result.push(line.slice(0, rt)); + pos += rt + 1; + } else { + result.push(line); + pos = nl + 1; + } + } + return result; + } : function(string){return string.split(/\r\n?|\n/);}; + + var hasSelection = window.getSelection ? function(te) { + try { return te.selectionStart != te.selectionEnd; } + catch(e) { return false; } + } : function(te) { + try {var range = te.ownerDocument.selection.createRange();} + catch(e) {} + if (!range || range.parentElement() != te) return false; + return range.compareEndPoints("StartToEnd", range) != 0; + }; + + var hasCopyEvent = (function() { + var e = elt("div"); + if ("oncopy" in e) return true; + e.setAttribute("oncopy", "return;"); + return typeof e.oncopy == "function"; + })(); + + var badZoomedRects = null; + function hasBadZoomedRects(measure) { + if (badZoomedRects != null) return badZoomedRects; + var node = removeChildrenAndAdd(measure, elt("span", "x")); + var normal = node.getBoundingClientRect(); + var fromRange = range(node, 0, 1).getBoundingClientRect(); + return badZoomedRects = Math.abs(normal.left - fromRange.left) > 1; + } + + // KEY NAMES + + var keyNames = {3: "Enter", 8: "Backspace", 9: "Tab", 13: "Enter", 16: "Shift", 17: "Ctrl", 18: "Alt", + 19: "Pause", 20: "CapsLock", 27: "Esc", 32: "Space", 33: "PageUp", 34: "PageDown", 35: "End", + 36: "Home", 37: "Left", 38: "Up", 39: "Right", 40: "Down", 44: "PrintScrn", 45: "Insert", + 46: "Delete", 59: ";", 61: "=", 91: "Mod", 92: "Mod", 93: "Mod", 107: "=", 109: "-", 127: "Delete", + 173: "-", 186: ";", 187: "=", 188: ",", 189: "-", 190: ".", 191: "/", 192: "`", 219: "[", 220: "\\", + 221: "]", 222: "'", 63232: "Up", 63233: "Down", 63234: "Left", 63235: "Right", 63272: "Delete", + 63273: "Home", 63275: "End", 63276: "PageUp", 63277: "PageDown", 63302: "Insert"}; + CodeMirror.keyNames = keyNames; + (function() { + // Number keys + for (var i = 0; i < 10; i++) keyNames[i + 48] = keyNames[i + 96] = String(i); + // Alphabetic keys + for (var i = 65; i <= 90; i++) keyNames[i] = String.fromCharCode(i); + // Function keys + for (var i = 1; i <= 12; i++) keyNames[i + 111] = keyNames[i + 63235] = "F" + i; + })(); + + // BIDI HELPERS + + function iterateBidiSections(order, from, to, f) { + if (!order) return f(from, to, "ltr"); + var found = false; + for (var i = 0; i < order.length; ++i) { + var part = order[i]; + if (part.from < to && part.to > from || from == to && part.to == from) { + f(Math.max(part.from, from), Math.min(part.to, to), part.level == 1 ? "rtl" : "ltr"); + found = true; + } + } + if (!found) f(from, to, "ltr"); + } + + function bidiLeft(part) { return part.level % 2 ? part.to : part.from; } + function bidiRight(part) { return part.level % 2 ? part.from : part.to; } + + function lineLeft(line) { var order = getOrder(line); return order ? bidiLeft(order[0]) : 0; } + function lineRight(line) { + var order = getOrder(line); + if (!order) return line.text.length; + return bidiRight(lst(order)); + } + + function lineStart(cm, lineN) { + var line = getLine(cm.doc, lineN); + var visual = visualLine(line); + if (visual != line) lineN = lineNo(visual); + var order = getOrder(visual); + var ch = !order ? 0 : order[0].level % 2 ? lineRight(visual) : lineLeft(visual); + return Pos(lineN, ch); + } + function lineEnd(cm, lineN) { + var merged, line = getLine(cm.doc, lineN); + while (merged = collapsedSpanAtEnd(line)) { + line = merged.find(1, true).line; + lineN = null; + } + var order = getOrder(line); + var ch = !order ? line.text.length : order[0].level % 2 ? lineLeft(line) : lineRight(line); + return Pos(lineN == null ? lineNo(line) : lineN, ch); + } + function lineStartSmart(cm, pos) { + var start = lineStart(cm, pos.line); + var line = getLine(cm.doc, start.line); + var order = getOrder(line); + if (!order || order[0].level == 0) { + var firstNonWS = Math.max(0, line.text.search(/\S/)); + var inWS = pos.line == start.line && pos.ch <= firstNonWS && pos.ch; + return Pos(start.line, inWS ? 0 : firstNonWS); + } + return start; + } + + function compareBidiLevel(order, a, b) { + var linedir = order[0].level; + if (a == linedir) return true; + if (b == linedir) return false; + return a < b; + } + var bidiOther; + function getBidiPartAt(order, pos) { + bidiOther = null; + for (var i = 0, found; i < order.length; ++i) { + var cur = order[i]; + if (cur.from < pos && cur.to > pos) return i; + if ((cur.from == pos || cur.to == pos)) { + if (found == null) { + found = i; + } else if (compareBidiLevel(order, cur.level, order[found].level)) { + if (cur.from != cur.to) bidiOther = found; + return i; + } else { + if (cur.from != cur.to) bidiOther = i; + return found; + } + } + } + return found; + } + + function moveInLine(line, pos, dir, byUnit) { + if (!byUnit) return pos + dir; + do pos += dir; + while (pos > 0 && isExtendingChar(line.text.charAt(pos))); + return pos; + } + + // This is needed in order to move 'visually' through bi-directional + // text -- i.e., pressing left should make the cursor go left, even + // when in RTL text. The tricky part is the 'jumps', where RTL and + // LTR text touch each other. This often requires the cursor offset + // to move more than one unit, in order to visually move one unit. + function moveVisually(line, start, dir, byUnit) { + var bidi = getOrder(line); + if (!bidi) return moveLogically(line, start, dir, byUnit); + var pos = getBidiPartAt(bidi, start), part = bidi[pos]; + var target = moveInLine(line, start, part.level % 2 ? -dir : dir, byUnit); + + for (;;) { + if (target > part.from && target < part.to) return target; + if (target == part.from || target == part.to) { + if (getBidiPartAt(bidi, target) == pos) return target; + part = bidi[pos += dir]; + return (dir > 0) == part.level % 2 ? part.to : part.from; + } else { + part = bidi[pos += dir]; + if (!part) return null; + if ((dir > 0) == part.level % 2) + target = moveInLine(line, part.to, -1, byUnit); + else + target = moveInLine(line, part.from, 1, byUnit); + } + } + } + + function moveLogically(line, start, dir, byUnit) { + var target = start + dir; + if (byUnit) while (target > 0 && isExtendingChar(line.text.charAt(target))) target += dir; + return target < 0 || target > line.text.length ? null : target; + } + + // Bidirectional ordering algorithm + // See http://unicode.org/reports/tr9/tr9-13.html for the algorithm + // that this (partially) implements. + + // One-char codes used for character types: + // L (L): Left-to-Right + // R (R): Right-to-Left + // r (AL): Right-to-Left Arabic + // 1 (EN): European Number + // + (ES): European Number Separator + // % (ET): European Number Terminator + // n (AN): Arabic Number + // , (CS): Common Number Separator + // m (NSM): Non-Spacing Mark + // b (BN): Boundary Neutral + // s (B): Paragraph Separator + // t (S): Segment Separator + // w (WS): Whitespace + // N (ON): Other Neutrals + + // Returns null if characters are ordered as they appear + // (left-to-right), or an array of sections ({from, to, level} + // objects) in the order in which they occur visually. + var bidiOrdering = (function() { + // Character types for codepoints 0 to 0xff + var lowTypes = "bbbbbbbbbtstwsbbbbbbbbbbbbbbssstwNN%%%NNNNNN,N,N1111111111NNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNbbbbbbsbbbbbbbbbbbbbbbbbbbbbbbbbb,N%%%%NNNNLNNNNN%%11NLNNN1LNNNNNLLLLLLLLLLLLLLLLLLLLLLLNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLN"; + // Character types for codepoints 0x600 to 0x6ff + var arabicTypes = "rrrrrrrrrrrr,rNNmmmmmmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmmmmmmmmrrrrrrrnnnnnnnnnn%nnrrrmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmmmmmmmmmmmmmNmmmm"; + function charType(code) { + if (code <= 0xf7) return lowTypes.charAt(code); + else if (0x590 <= code && code <= 0x5f4) return "R"; + else if (0x600 <= code && code <= 0x6ed) return arabicTypes.charAt(code - 0x600); + else if (0x6ee <= code && code <= 0x8ac) return "r"; + else if (0x2000 <= code && code <= 0x200b) return "w"; + else if (code == 0x200c) return "b"; + else return "L"; + } + + var bidiRE = /[\u0590-\u05f4\u0600-\u06ff\u0700-\u08ac]/; + var isNeutral = /[stwN]/, isStrong = /[LRr]/, countsAsLeft = /[Lb1n]/, countsAsNum = /[1n]/; + // Browsers seem to always treat the boundaries of block elements as being L. + var outerType = "L"; + + function BidiSpan(level, from, to) { + this.level = level; + this.from = from; this.to = to; + } + + return function(str) { + if (!bidiRE.test(str)) return false; + var len = str.length, types = []; + for (var i = 0, type; i < len; ++i) + types.push(type = charType(str.charCodeAt(i))); + + // W1. Examine each non-spacing mark (NSM) in the level run, and + // change the type of the NSM to the type of the previous + // character. If the NSM is at the start of the level run, it will + // get the type of sor. + for (var i = 0, prev = outerType; i < len; ++i) { + var type = types[i]; + if (type == "m") types[i] = prev; + else prev = type; + } + + // W2. Search backwards from each instance of a European number + // until the first strong type (R, L, AL, or sor) is found. If an + // AL is found, change the type of the European number to Arabic + // number. + // W3. Change all ALs to R. + for (var i = 0, cur = outerType; i < len; ++i) { + var type = types[i]; + if (type == "1" && cur == "r") types[i] = "n"; + else if (isStrong.test(type)) { cur = type; if (type == "r") types[i] = "R"; } + } + + // W4. A single European separator between two European numbers + // changes to a European number. A single common separator between + // two numbers of the same type changes to that type. + for (var i = 1, prev = types[0]; i < len - 1; ++i) { + var type = types[i]; + if (type == "+" && prev == "1" && types[i+1] == "1") types[i] = "1"; + else if (type == "," && prev == types[i+1] && + (prev == "1" || prev == "n")) types[i] = prev; + prev = type; + } + + // W5. A sequence of European terminators adjacent to European + // numbers changes to all European numbers. + // W6. Otherwise, separators and terminators change to Other + // Neutral. + for (var i = 0; i < len; ++i) { + var type = types[i]; + if (type == ",") types[i] = "N"; + else if (type == "%") { + for (var end = i + 1; end < len && types[end] == "%"; ++end) {} + var replace = (i && types[i-1] == "!") || (end < len && types[end] == "1") ? "1" : "N"; + for (var j = i; j < end; ++j) types[j] = replace; + i = end - 1; + } + } + + // W7. Search backwards from each instance of a European number + // until the first strong type (R, L, or sor) is found. If an L is + // found, then change the type of the European number to L. + for (var i = 0, cur = outerType; i < len; ++i) { + var type = types[i]; + if (cur == "L" && type == "1") types[i] = "L"; + else if (isStrong.test(type)) cur = type; + } + + // N1. A sequence of neutrals takes the direction of the + // surrounding strong text if the text on both sides has the same + // direction. European and Arabic numbers act as if they were R in + // terms of their influence on neutrals. Start-of-level-run (sor) + // and end-of-level-run (eor) are used at level run boundaries. + // N2. Any remaining neutrals take the embedding direction. + for (var i = 0; i < len; ++i) { + if (isNeutral.test(types[i])) { + for (var end = i + 1; end < len && isNeutral.test(types[end]); ++end) {} + var before = (i ? types[i-1] : outerType) == "L"; + var after = (end < len ? types[end] : outerType) == "L"; + var replace = before || after ? "L" : "R"; + for (var j = i; j < end; ++j) types[j] = replace; + i = end - 1; + } + } + + // Here we depart from the documented algorithm, in order to avoid + // building up an actual levels array. Since there are only three + // levels (0, 1, 2) in an implementation that doesn't take + // explicit embedding into account, we can build up the order on + // the fly, without following the level-based algorithm. + var order = [], m; + for (var i = 0; i < len;) { + if (countsAsLeft.test(types[i])) { + var start = i; + for (++i; i < len && countsAsLeft.test(types[i]); ++i) {} + order.push(new BidiSpan(0, start, i)); + } else { + var pos = i, at = order.length; + for (++i; i < len && types[i] != "L"; ++i) {} + for (var j = pos; j < i;) { + if (countsAsNum.test(types[j])) { + if (pos < j) order.splice(at, 0, new BidiSpan(1, pos, j)); + var nstart = j; + for (++j; j < i && countsAsNum.test(types[j]); ++j) {} + order.splice(at, 0, new BidiSpan(2, nstart, j)); + pos = j; + } else ++j; + } + if (pos < i) order.splice(at, 0, new BidiSpan(1, pos, i)); + } + } + if (order[0].level == 1 && (m = str.match(/^\s+/))) { + order[0].from = m[0].length; + order.unshift(new BidiSpan(0, 0, m[0].length)); + } + if (lst(order).level == 1 && (m = str.match(/\s+$/))) { + lst(order).to -= m[0].length; + order.push(new BidiSpan(0, len - m[0].length, len)); + } + if (order[0].level != lst(order).level) + order.push(new BidiSpan(order[0].level, len, len)); + + return order; + }; + })(); + + // THE END + + CodeMirror.version = "5.1.1"; + + return CodeMirror; +}); \ No newline at end of file diff --git a/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.policy.view/public/js/policy-view.js b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.policy.view/public/js/policy-view.js new file mode 100644 index 0000000000..67e2c7f8ac --- /dev/null +++ b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.policy.view/public/js/policy-view.js @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2016, 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. + */ + +var skipStep = {}; +var policy = {}; + + +skipStep["policy-platform"] = function (policyPayloadObj) { + console.log(policyPayloadObj); + policy["name"] = policyPayloadObj["policyName"]; + policy["platform"] = policyPayloadObj["profile"]["deviceType"]["name"]; + policy["platformId"] = policyPayloadObj["profile"]["deviceType"]["id"]; + var userRoleInput = $("#user-roles-input"); + var ownershipInput = $("#ownership-input"); + var userInput = $("#users-select-field"); + var actionInput = $("#action-input"); + var policyNameInput = $("#policy-name-input"); + var policyDescriptionInput = $("#policy-description-input"); + userRoleInput.val(policyPayloadObj.roles); + userInput.val(policyPayloadObj.users); + ownershipInput.val(policyPayloadObj.ownershipType); + actionInput.val(policyPayloadObj.compliance); + policyNameInput.val(policyPayloadObj["policyName"]); + policyDescriptionInput.val(policyPayloadObj["description"]); + // updating next-page wizard title with selected platform + $("#policy-heading").text(policy["platform"].toUpperCase() + " POLICY - " + policy["name"].toUpperCase()); + $("#policy-platform").text(policy["platform"].toUpperCase()); + $("#policy-assignment").text(policyPayloadObj.ownershipType); + $("#policy-action").text(policyPayloadObj.compliance.toUpperCase()); + $("#policy-description").text(policyPayloadObj["description"]); + var policyStatus = "Active"; + if(policyPayloadObj["active"] == true && policyPayloadObj["updated"] == true) { + policyStatus = ' Active/Updated'; + } else if(policyPayloadObj["active"] == true && policyPayloadObj["updated"] == false) { + policyStatus = ' Active'; + } else if(policyPayloadObj["active"] == false && policyPayloadObj["updated"] == true) { + policyStatus = ' Inactive/Updated'; + } else if(policyPayloadObj["active"] == false && policyPayloadObj["updated"] == false) { + policyStatus = ' Inactive'; + } + + $("#policy-status").html(policyStatus); + + if(policyPayloadObj.users.length > 0) { + $("#policy-users").text(policyPayloadObj.users.toString().split(",").join(", ")); + } else { + $("#users-row").addClass("hidden"); + } + + if(policyPayloadObj.roles.length > 0) { + $("#policy-roles").text(policyPayloadObj.roles.toString().split(",").join(", ")); + } else { + $("#roles-row").addClass("hidden"); + } + var profileFeaturesList = policyPayloadObj["profile"]["profileFeaturesList"]; + if (profileFeaturesList.length > 0){ + var content = profileFeaturesList[0]["content"]; + var policyDefinitionObj = JSON.parse(content); + window.queryEditor.setValue(policyDefinitionObj["policyDefinition"]); + } +}; + + +// End of functions related to grid-input-view + +/** + * This method will return query parameter value given its name. + * @param name Query parameter name + * @returns {string} Query parameter value + */ +var getParameterByName = function (name) { + name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]"); + var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"), + results = regex.exec(location.search); + return results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " ")); +}; + +$(document).ready(function () { + + window.queryEditor = CodeMirror.fromTextArea(document.getElementById('policy-definition-input'), { + mode: MIME_TYPE_SIDDHI_QL, + indentWithTabs: true, + smartIndent: true, + lineNumbers: true, + matchBrackets: true, + autofocus: true, + readOnly: true, + extraKeys: { + "Shift-2": function (cm) { + insertStr(cm, cm.getCursor(), '@'); + CodeMirror.showHint(cm, getAnnotationHints); + }, + "Ctrl-Space": "autocomplete" + } + }); + + var policyPayloadObj; + invokerUtil.get( + "/devicemgt_admin/policies/" + getParameterByName("id"), + // on success + function (data) { + // console.log("success: " + JSON.stringify(data)); + data = JSON.parse(data); + policyPayloadObj = data["responseContent"]; + skipStep["policy-platform"](policyPayloadObj); + }, + // on error + function () { + // should be redirected to an error page + } + ); + +}); \ No newline at end of file diff --git a/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.policy.view/public/js/sql.js b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.policy.view/public/js/sql.js new file mode 100644 index 0000000000..9f92fbc938 --- /dev/null +++ b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.policy.view/public/js/sql.js @@ -0,0 +1,310 @@ +/* + * Copyright (c) 2016, 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. + */ + +/*Annotations, Annotation Names and relevant tokens*/ +var ANNOTATION_IMPORT = "Import"; +var ANNOTATION_EXPORT = "Export"; + +var ANNOTATION_TOKEN_AT = "@"; +var ANNOTATION_TOKEN_OPENING_BRACKET = "("; +var ANNOTATION_TOKEN_CLOSING_BRACKET = ")"; + +var REGEX_LINE_STARTING_WITH_PLAN = /^@Plan.*/g; +var REGEX_LINE_STARTING_WITH_SINGLE_LINE_COMMENT = /^--.*/g; +var REGEX_LINE_STARTING_WITH_MULTI_LINE_COMMENT = /^\/\*.*\*\//g; +var REGEX_LINE_STARTING_WITH_IMPORT_STATEMENT = /^@Import.*/g; + +var SIDDHI_STATEMENT_DELIMETER = ";"; +var SIDDHI_LINE_BREAK = "\n"; +var SIDDHI_LINE_BREAK_CHARACTER = '\n'; +var SIDDHI_SINGLE_QUOTE = "'"; +var SIDDHI_SPACE_LITERAL = " "; + +var SIDDHI_LITERAL_DEFINE_STREAM = "define stream"; + +var MIME_TYPE_SIDDHI_QL = "text/siddhi-ql"; + + +CodeMirror.defineMode("sql", function (config, parserConfig) { + "use strict"; + + var client = parserConfig.client || {}, + atoms = parserConfig.atoms || {"false":true, "true":true, "null":true}, + builtin = parserConfig.builtin || {}, + keywords = parserConfig.keywords || {}, + operatorChars = parserConfig.operatorChars || /^[*+\-%<>!=&|~^]/, + support = parserConfig.support || {}, + hooks = parserConfig.hooks || {}, + dateSQL = parserConfig.dateSQL || {"date":true, "time":true, "timestamp":true}; + + function tokenBase(stream, state) { + var ch = stream.next(); + + // call hooks from the mime type + if (hooks[ch]) { + var result = hooks[ch](stream, state); + if (result !== false) return result; + } + + if (ch.charCodeAt(0) > 47 && ch.charCodeAt(0) < 58) { + // numbers + // ref: http://dev.mysql.com/doc/refman/5.5/en/number-literals.html + stream.match(/^[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?/); + support.decimallessFloat == true && stream.eat('.'); + return "number"; + } else if (ch == "'" || (ch == '"' && support.doubleQuote)) { + // strings + // ref: http://dev.mysql.com/doc/refman/5.5/en/string-literals.html + state.tokenize = tokenLiteral(ch); + return state.tokenize(stream, state); + } else if (/^[\(\),\;\[\]]/.test(ch)) { + // no highlightning + return null; + } else if ((ch == "-" && stream.eat("-") && (!support.commentSpaceRequired || stream.eat(" ")))) { + // 1-line comments + // ref: https://kb.askmonty.org/en/comment-syntax/ + stream.skipToEnd(); + return "comment"; + } else if (ch == "/" && stream.eat("*")) { + // multi-line comments + // ref: https://kb.askmonty.org/en/comment-syntax/ + state.tokenize = tokenComment; + return state.tokenize(stream, state); + } else if (ch == ".") { + // .1 for 0.1 + if (support.zerolessFloat == true && stream.match(/^(?:\d+(?:e[+-]?\d+)?)/i)) { + return "number"; + } + } else { + stream.eatWhile(/^[_\-\w\d]/); /* Character '-' will also be eaten, to prevent the highlight happening in keywords being embedded in non-keyword strings. For example, 'all' in 'all-nonkeyword' */ + var word = stream.current().toLowerCase(); // Added toLowerCase() to highlight keywords in a case insensitive manner. + // dates (standard SQL syntax) + // ref: http://dev.mysql.com/doc/refman/5.5/en/date-and-time-literals.html + if (dateSQL.hasOwnProperty(word) && (stream.match(/^( )+'[^']*'/) || stream.match(/^( )+"[^"]*"/))) + return "number"; + if (atoms.hasOwnProperty(word)) return "atom"; + if (builtin.hasOwnProperty(word)) return "builtin"; + if (keywords.hasOwnProperty(word)) return "keyword"; + if (client.hasOwnProperty(word)) return "string-2"; + return null; + } + } + + // 'string', with char specified in quote escaped by '\' + function tokenLiteral(quote) { + return function (stream, state) { + var escaped = false, ch; + while ((ch = stream.next()) != null) { + if (ch == quote && !escaped) { + state.tokenize = tokenBase; + break; + } + escaped = !escaped && ch == "\\"; + } + return "string"; + }; + } + + function tokenComment(stream, state) { + while (true) { + if (stream.skipTo("*")) { + stream.next(); + if (stream.eat("/")) { + state.tokenize = tokenBase; + break; + } + } else { + stream.skipToEnd(); + break; + } + } + return "comment"; + } + + function pushContext(stream, state, type) { + state.context = { + prev:state.context, + indent:stream.indentation(), + col:stream.column(), + type:type + }; + } + + function popContext(state) { + state.indent = state.context.indent; + state.context = state.context.prev; + } + + return { + startState:function () { + return {tokenize:tokenBase, context:null}; + }, + + token:function (stream, state) { + if (stream.sol()) { + if (state.context && state.context.align == null) + state.context.align = false; + } + if (stream.eatSpace()) return null; + + var style = state.tokenize(stream, state); + if (style == "comment") return style; + + if (state.context && state.context.align == null) + state.context.align = true; + + var tok = stream.current(); + if (tok == "(") + pushContext(stream, state, ")"); + else if (tok == "[") + pushContext(stream, state, "]"); + else if (state.context && state.context.type == tok) + popContext(state); + return style; + }, + + indent:function (state, textAfter) { + var cx = state.context; + if (!cx) return CodeMirror.Pass; + var closing = textAfter.charAt(0) == cx.type; + if (cx.align) return cx.col + (closing ? 0 : 1); + else return cx.indent + (closing ? 0 : config.indentUnit); + }, + + blockCommentStart: "/*", + blockCommentEnd: "*/", + lineComment: "--" + }; +}); + +(function () { + "use strict"; + + // `identifier` + function hookIdentifier(stream) { + // MySQL/MariaDB identifiers + // ref: http://dev.mysql.com/doc/refman/5.6/en/identifier-qualifiers.html + var ch; + while ((ch = stream.next()) != null) { + if (ch == "`" && !stream.eat("`")) return "variable-2"; + } + stream.backUp(stream.current().length - 1); + return stream.eatWhile(/\w/) ? "variable-2" : null; + } + + // variable token + function hookVar(stream) { + // variables + // @@prefix.varName @varName + // varName can be quoted with ` or ' or " + // ref: http://dev.mysql.com/doc/refman/5.5/en/user-variables.html + if (stream.eat("@")) { + stream.match(/^session\./); + stream.match(/^local\./); + stream.match(/^global\./); + } + + if (stream.eat("'")) { + stream.match(/^.*'/); + return "variable-2"; + } else if (stream.eat('"')) { + stream.match(/^.*"/); + return "variable-2"; + } else if (stream.eat("`")) { + stream.match(/^.*`/); + return "variable-2"; + } else if (stream.match(/^[0-9a-zA-Z$\.\_]+/)) { + return "variable-2"; + } + return null; + } + + ; + + // short client keyword token + function hookClient(stream) { + // \N means NULL + // ref: http://dev.mysql.com/doc/refman/5.5/en/null-values.html + if (stream.eat("N")) { + return "atom"; + } + // \g, etc + // ref: http://dev.mysql.com/doc/refman/5.5/en/mysql-commands.html + return stream.match(/^[a-zA-Z.#!?]/) ? "variable-2" : null; + } + + // these keywords are used by all SQL dialects (however, a mode can still overwrite it) + var sqlKeywordsWithoutSymbols = "all and as begin by contains define delete end events " + + "every first for from full group having inner insert into join last " + + "left not of on or outer output partition raw return right select snapshot stream table "; + var sqlKeywords = ", : ? # ( ) " + sqlKeywordsWithoutSymbols; + var builtIn = "bool double float int long object string "; + var atoms = "false true null "; + var dateSQL = "days hours milliseconds minutes months seconds "; + var allSqlSuggestions = sqlKeywordsWithoutSymbols + builtIn + atoms + dateSQL; + + // turn a space-separated list into an array + function set(str) { + var obj = {}, words = str.split(" "); + for (var i = 0; i < words.length; ++i) obj[words[i]] = true; + return obj; + } + + // A generic SQL Mode. It's not a standard, it just try to support what is generally supported + CodeMirror.defineMIME(MIME_TYPE_SIDDHI_QL, { + name:"sql", + keywords:set(sqlKeywords), + builtin:set(builtIn), + atoms:set(atoms), + operatorChars:/^[*+%<>!=/]/, + dateSQL:set(dateSQL), + support:set("doubleQuote "), + allSqlSuggestions:set(allSqlSuggestions) + }); +}()); + +/* + How Properties of Mime Types are used by SQL Mode + ================================================= + + keywords: + A list of keywords you want to be highlighted. + functions: + A list of function names you want to be highlighted. + builtin: + A list of builtin types you want to be highlighted (if you want types to be of class "builtin" instead of "keyword"). + operatorChars: + All characters that must be handled as operators. + client: + Commands parsed and executed by the client (not the server). + support: + A list of supported syntaxes which are not common, but are supported by more than 1 DBMS. + * ODBCdotTable: .tableName + * zerolessFloat: .1 + * doubleQuote + * nCharCast: N'string' + * charsetCast: _utf8'string' + * commentHash: use # char for comments + * commentSlashSlash: use // for comments + * commentSpaceRequired: require a space after -- for comments + atoms: + Keywords that must be highlighted as atoms,. Some DBMS's support more atoms than others: + UNKNOWN, INFINITY, UNDERFLOW, NaN... + dateSQL: + Used for date/time SQL standard syntax, because not all DBMS's support same temporal types. + */ \ No newline at end of file diff --git a/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.policy.view/view.hbs b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.policy.view/view.hbs new file mode 100644 index 0000000000..6cf20df0e1 --- /dev/null +++ b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.policy.view/view.hbs @@ -0,0 +1,95 @@ +{{#zone "topCss"}} + {{css "css/codemirror.css"}} +{{/zone}} +{{#zone "contentTitle"}} +
      +
      + +
      +
      +{{/zone}} + + +
      + +
      +
      +
      +
      +
      + Policy Overview +
      + {{#defineZone "policy-detail-properties"}} + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      + Platform +
      Assignment Group +
      Action upon + non-compliance +
      Status
      Assigned Users
      Assigned Roles
      + {{/defineZone}} +
      + Description +
      +
      +
      +
      +
      + +
      + Profile Information +
      +
      + +
      +
      +
      + +
      +
      +
      +
      +
      +
      +
      + +
      +
      + +
      +{{#zone "bottomJs"}} + {{js "js/codemirror.js"}} + {{js "js/sql.js"}} + {{js "js/policy-view.js"}} +{{/zone}} \ No newline at end of file diff --git a/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.policy.view/view.js b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.policy.view/view.js new file mode 100644 index 0000000000..36b130ca07 --- /dev/null +++ b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.policy.view/view.js @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2016, 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("policy-view-edit-unit backend js"); + log.debug("calling policy-view-edit-unit"); + var userModule = require("/app/modules/user.js").userModule; + context.roles = userModule.getRoles().content; + return context; +} \ No newline at end of file diff --git a/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.policy.view/view.json b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.policy.view/view.json new file mode 100644 index 0000000000..fd25901297 --- /dev/null +++ b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.policy.view/view.json @@ -0,0 +1,3 @@ +{ + "version" : "1.0.0" +} \ No newline at end of file diff --git a/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.policy.wizard/public/css/codemirror.css b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.policy.wizard/public/css/codemirror.css new file mode 100644 index 0000000000..e749a5211d --- /dev/null +++ b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.policy.wizard/public/css/codemirror.css @@ -0,0 +1,342 @@ +/* + * Copyright (c) 2016, 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. + */ + +/* BASICS */ + +.CodeMirror { + /* Set height, width, borders, and global font properties here */ + font-family: monospace; + height: 300px; + color: black; +} + +/* PADDING */ + +.CodeMirror-lines { + padding: 4px 0; /* Vertical padding around content */ +} +.CodeMirror pre { + padding: 0 4px; /* Horizontal padding of content */ +} + +.CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler { + background-color: white; /* The little square between H and V scrollbars */ +} + +/* GUTTER */ + +.CodeMirror-gutters { + border-right: 1px solid #ddd; + background-color: #f7f7f7; + white-space: nowrap; +} +.CodeMirror-linenumbers {} +.CodeMirror-linenumber { + padding: 0 3px 0 5px; + min-width: 20px; + text-align: right; + color: #999; + white-space: nowrap; + left: -30px; +} + +.CodeMirror-guttermarker { color: black; } +.CodeMirror-guttermarker-subtle { color: #999; } + +/* CURSOR */ + +.CodeMirror div.CodeMirror-cursor { + border-left: 1px solid black; +} +/* Shown when moving in bi-directional text */ +.CodeMirror div.CodeMirror-secondarycursor { + border-left: 1px solid silver; +} +.CodeMirror.cm-fat-cursor div.CodeMirror-cursor { + width: auto; + border: 0; + background: #7e7; +} +.CodeMirror.cm-fat-cursor div.CodeMirror-cursors { + z-index: 1; +} + +.cm-animate-fat-cursor { + width: auto; + border: 0; + -webkit-animation: blink 1.06s steps(1) infinite; + -moz-animation: blink 1.06s steps(1) infinite; + animation: blink 1.06s steps(1) infinite; +} +@-moz-keyframes blink { + 0% { background: #7e7; } + 50% { background: none; } + 100% { background: #7e7; } +} +@-webkit-keyframes blink { + 0% { background: #7e7; } + 50% { background: none; } + 100% { background: #7e7; } +} +@keyframes blink { + 0% { background: #7e7; } + 50% { background: none; } + 100% { background: #7e7; } +} + +/* Can style cursor different in overwrite (non-insert) mode */ +div.CodeMirror-overwrite div.CodeMirror-cursor {} + +.cm-tab { display: inline-block; text-decoration: inherit; } + +.CodeMirror-ruler { + border-left: 1px solid #ccc; + position: absolute; +} + +/* DEFAULT THEME */ + +.cm-s-default .cm-keyword {color: #708;} +.cm-s-default .cm-atom {color: #219;} +.cm-s-default .cm-number {color: #164;} +.cm-s-default .cm-def {color: #00f;} +.cm-s-default .cm-variable, +.cm-s-default .cm-punctuation, +.cm-s-default .cm-property, +.cm-s-default .cm-operator {} +.cm-s-default .cm-variable-2 {color: #05a;} +.cm-s-default .cm-variable-3 {color: #085;} +.cm-s-default .cm-comment {color: #a50;} +.cm-s-default .cm-string {color: #a11;} +.cm-s-default .cm-string-2 {color: #f50;} +.cm-s-default .cm-meta {color: #555;} +.cm-s-default .cm-qualifier {color: #555;} +.cm-s-default .cm-builtin {color: #30a;} +.cm-s-default .cm-bracket {color: #997;} +.cm-s-default .cm-tag {color: #170;} +.cm-s-default .cm-attribute {color: #00c;} +.cm-s-default .cm-header {color: blue;} +.cm-s-default .cm-quote {color: #090;} +.cm-s-default .cm-hr {color: #999;} +.cm-s-default .cm-link {color: #00c;} + +.cm-negative {color: #d44;} +.cm-positive {color: #292;} +.cm-header, .cm-strong {font-weight: bold;} +.cm-em {font-style: italic;} +.cm-link {text-decoration: underline;} +.cm-strikethrough {text-decoration: line-through;} + +.cm-s-default .cm-error {color: #f00;} +.cm-invalidchar {color: #f00;} + +/* Default styles for common addons */ + +div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;} +div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;} +.CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); } +.CodeMirror-activeline-background {background: #e8f2ff;} + +/* STOP */ + +/* The rest of this file contains styles related to the mechanics of + the editor. You probably shouldn't touch them. */ + +.CodeMirror { + position: relative; + overflow: hidden; + background: white; +} + +.CodeMirror-scroll { + overflow: scroll !important; /* Things will break if this is overridden */ + /* 30px is the magic margin used to hide the element's real scrollbars */ + /* See overflow: hidden in .CodeMirror */ + margin-bottom: -30px; margin-right: -30px; + padding-bottom: 30px; + height: 100%; + outline: none; /* Prevent dragging from highlighting the element */ + position: relative; +} +.CodeMirror-sizer { + position: relative; + border-right: 30px solid transparent; +} + +/* The fake, visible scrollbars. Used to force redraw during scrolling + before actuall scrolling happens, thus preventing shaking and + flickering artifacts. */ +.CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler { + position: absolute; + z-index: 6; + display: none; +} +.CodeMirror-vscrollbar { + right: 0; top: 0; + overflow-x: hidden; + overflow-y: scroll; +} +.CodeMirror-hscrollbar { + bottom: 0; left: 0; + overflow-y: hidden; + overflow-x: scroll; +} +.CodeMirror-scrollbar-filler { + right: 0; bottom: 0; +} +.CodeMirror-gutter-filler { + left: 0; bottom: 0; +} + +.CodeMirror-gutters { + position: absolute; left: 0; top: 0; + z-index: 3; +} +.CodeMirror-gutter { + white-space: normal; + height: 100%; + display: inline-block; + /* Hack to make IE7 behave */ + *zoom:1; + *display:inline; +} +.CodeMirror-gutter-wrapper { + position: absolute; + z-index: 4; + height: 100%; +} +.CodeMirror-gutter-elt { + position: absolute; + cursor: default; + z-index: 4; + left: -30px; +} +.CodeMirror-gutter-wrapper { + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; +} + +.CodeMirror-lines { + cursor: text; + min-height: 1px; /* prevents collapsing before first draw */ +} +.CodeMirror pre { + /* Reset some styles that the rest of the page might have set */ + -moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0; + border-width: 0; + background: transparent; + font-family: inherit; + font-size: inherit; + margin: 0; + white-space: pre; + word-wrap: normal; + line-height: inherit; + color: inherit; + z-index: 2; + position: relative; + overflow: visible; + -webkit-tap-highlight-color: transparent; +} +.CodeMirror-wrap pre { + word-wrap: break-word; + white-space: pre-wrap; + word-break: normal; +} + +.CodeMirror-linebackground { + position: absolute; + left: 0; right: 0; top: 0; bottom: 0; + z-index: 0; +} + +.CodeMirror-linewidget { + position: relative; + z-index: 2; + overflow: auto; +} + +.CodeMirror-widget {} + +.CodeMirror-code { + outline: none; +} + +/* Force content-box sizing for the elements where we expect it */ +.CodeMirror-scroll, +.CodeMirror-sizer, +.CodeMirror-gutter, +.CodeMirror-gutters, +.CodeMirror-linenumber { + -moz-box-sizing: content-box; + box-sizing: content-box; +} + +.CodeMirror-measure { + position: absolute; + width: 100%; + height: 0; + overflow: hidden; + visibility: hidden; +} +.CodeMirror-measure pre { position: static; } + +.CodeMirror div.CodeMirror-cursor { + position: absolute; + border-right: none; + width: 0; +} + +div.CodeMirror-cursors { + visibility: hidden; + position: relative; + z-index: 3; +} +.CodeMirror-focused div.CodeMirror-cursors { + visibility: visible; +} + +.CodeMirror-selected { background: #d9d9d9; } +.CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; } +.CodeMirror-crosshair { cursor: crosshair; } +.CodeMirror ::selection { background: #d7d4f0; } +.CodeMirror ::-moz-selection { background: #d7d4f0; } + +.cm-searching { + background: #ffa; + background: rgba(255, 255, 0, .4); +} + +/* IE7 hack to prevent it from returning funny offsetTops on the spans */ +.CodeMirror span { *vertical-align: text-bottom; } + +/* Used to force a border model for a node */ +.cm-force-border { padding-right: .1px; } + +@media print { + /* Hide the cursor when printing */ + .CodeMirror div.CodeMirror-cursors { + visibility: hidden; + } +} + +/* See issue #2901 */ +.cm-tab-wrap-hack:after { content: ''; } + +/* Help users use markselection to safely style text background */ +span.CodeMirror-selectedtext { background: none; } \ No newline at end of file diff --git a/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.policy.wizard/public/js/codemirror.js b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.policy.wizard/public/js/codemirror.js new file mode 100644 index 0000000000..20f3f95ed9 --- /dev/null +++ b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.policy.wizard/public/js/codemirror.js @@ -0,0 +1,8720 @@ +/* + * Copyright (c) 2016, 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. + */ + +// This is CodeMirror (http://codemirror.net), a code editor +// implemented in JavaScript on top of the browser's DOM. +// +// You can find some technical background for some of the code below +// at http://marijnhaverbeke.nl/blog/#cm-internals . + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + module.exports = mod(); + else if (typeof define == "function" && define.amd) // AMD + return define([], mod); + else // Plain browser env + this.CodeMirror = mod(); +})(function() { + "use strict"; + + // BROWSER SNIFFING + + // Kludges for bugs and behavior differences that can't be feature + // detected are enabled based on userAgent etc sniffing. + + var gecko = /gecko\/\d/i.test(navigator.userAgent); + var ie_upto10 = /MSIE \d/.test(navigator.userAgent); + var ie_11up = /Trident\/(?:[7-9]|\d{2,})\..*rv:(\d+)/.exec(navigator.userAgent); + var ie = ie_upto10 || ie_11up; + var ie_version = ie && (ie_upto10 ? document.documentMode || 6 : ie_11up[1]); + var webkit = /WebKit\//.test(navigator.userAgent); + var qtwebkit = webkit && /Qt\/\d+\.\d+/.test(navigator.userAgent); + var chrome = /Chrome\//.test(navigator.userAgent); + var presto = /Opera\//.test(navigator.userAgent); + var safari = /Apple Computer/.test(navigator.vendor); + var mac_geMountainLion = /Mac OS X 1\d\D([8-9]|\d\d)\D/.test(navigator.userAgent); + var phantom = /PhantomJS/.test(navigator.userAgent); + + var ios = /AppleWebKit/.test(navigator.userAgent) && /Mobile\/\w+/.test(navigator.userAgent); + // This is woefully incomplete. Suggestions for alternative methods welcome. + var mobile = ios || /Android|webOS|BlackBerry|Opera Mini|Opera Mobi|IEMobile/i.test(navigator.userAgent); + var mac = ios || /Mac/.test(navigator.platform); + var windows = /win/i.test(navigator.platform); + + var presto_version = presto && navigator.userAgent.match(/Version\/(\d*\.\d*)/); + if (presto_version) presto_version = Number(presto_version[1]); + if (presto_version && presto_version >= 15) { presto = false; webkit = true; } + // Some browsers use the wrong event properties to signal cmd/ctrl on OS X + var flipCtrlCmd = mac && (qtwebkit || presto && (presto_version == null || presto_version < 12.11)); + var captureRightClick = gecko || (ie && ie_version >= 9); + + // Optimize some code when these features are not used. + var sawReadOnlySpans = false, sawCollapsedSpans = false; + + // EDITOR CONSTRUCTOR + + // A CodeMirror instance represents an editor. This is the object + // that user code is usually dealing with. + + function CodeMirror(place, options) { + if (!(this instanceof CodeMirror)) return new CodeMirror(place, options); + + this.options = options = options ? copyObj(options) : {}; + // Determine effective options based on given values and defaults. + copyObj(defaults, options, false); + setGuttersForLineNumbers(options); + + var doc = options.value; + if (typeof doc == "string") doc = new Doc(doc, options.mode); + this.doc = doc; + + var input = new CodeMirror.inputStyles[options.inputStyle](this); + var display = this.display = new Display(place, doc, input); + display.wrapper.CodeMirror = this; + updateGutters(this); + themeChanged(this); + if (options.lineWrapping) + this.display.wrapper.className += " CodeMirror-wrap"; + if (options.autofocus && !mobile) display.input.focus(); + initScrollbars(this); + + this.state = { + keyMaps: [], // stores maps added by addKeyMap + overlays: [], // highlighting overlays, as added by addOverlay + modeGen: 0, // bumped when mode/overlay changes, used to invalidate highlighting info + overwrite: false, + delayingBlurEvent: false, + focused: false, + suppressEdits: false, // used to disable editing during key handlers when in readOnly mode + pasteIncoming: false, cutIncoming: false, // help recognize paste/cut edits in input.poll + draggingText: false, + highlight: new Delayed(), // stores highlight worker timeout + keySeq: null, // Unfinished key sequence + specialChars: null + }; + + var cm = this; + + // Override magic textarea content restore that IE sometimes does + // on our hidden textarea on reload + if (ie && ie_version < 11) setTimeout(function() { cm.display.input.reset(true); }, 20); + + registerEventHandlers(this); + ensureGlobalHandlers(); + + startOperation(this); + this.curOp.forceUpdate = true; + attachDoc(this, doc); + + if ((options.autofocus && !mobile) || cm.hasFocus()) + setTimeout(bind(onFocus, this), 20); + else + onBlur(this); + + for (var opt in optionHandlers) if (optionHandlers.hasOwnProperty(opt)) + optionHandlers[opt](this, options[opt], Init); + maybeUpdateLineNumberWidth(this); + if (options.finishInit) options.finishInit(this); + for (var i = 0; i < initHooks.length; ++i) initHooks[i](this); + endOperation(this); + // Suppress optimizelegibility in Webkit, since it breaks text + // measuring on line wrapping boundaries. + if (webkit && options.lineWrapping && + getComputedStyle(display.lineDiv).textRendering == "optimizelegibility") + display.lineDiv.style.textRendering = "auto"; + } + + // DISPLAY CONSTRUCTOR + + // The display handles the DOM integration, both for input reading + // and content drawing. It holds references to DOM nodes and + // display-related state. + + function Display(place, doc, input) { + var d = this; + this.input = input; + + // Covers bottom-right square when both scrollbars are present. + d.scrollbarFiller = elt("div", null, "CodeMirror-scrollbar-filler"); + d.scrollbarFiller.setAttribute("cm-not-content", "true"); + // Covers bottom of gutter when coverGutterNextToScrollbar is on + // and h scrollbar is present. + d.gutterFiller = elt("div", null, "CodeMirror-gutter-filler"); + d.gutterFiller.setAttribute("cm-not-content", "true"); + // Will contain the actual code, positioned to cover the viewport. + d.lineDiv = elt("div", null, "CodeMirror-code"); + // Elements are added to these to represent selection and cursors. + d.selectionDiv = elt("div", null, null, "position: relative; z-index: 1"); + d.cursorDiv = elt("div", null, "CodeMirror-cursors"); + // A visibility: hidden element used to find the size of things. + d.measure = elt("div", null, "CodeMirror-measure"); + // When lines outside of the viewport are measured, they are drawn in this. + d.lineMeasure = elt("div", null, "CodeMirror-measure"); + // Wraps everything that needs to exist inside the vertically-padded coordinate system + d.lineSpace = elt("div", [d.measure, d.lineMeasure, d.selectionDiv, d.cursorDiv, d.lineDiv], + null, "position: relative; outline: none"); + // Moved around its parent to cover visible view. + d.mover = elt("div", [elt("div", [d.lineSpace], "CodeMirror-lines")], null, "position: relative"); + // Set to the height of the document, allowing scrolling. + d.sizer = elt("div", [d.mover], "CodeMirror-sizer"); + d.sizerWidth = null; + // Behavior of elts with overflow: auto and padding is + // inconsistent across browsers. This is used to ensure the + // scrollable area is big enough. + d.heightForcer = elt("div", null, null, "position: absolute; height: " + scrollerGap + "px; width: 1px;"); + // Will contain the gutters, if any. + d.gutters = elt("div", null, "CodeMirror-gutters"); + d.lineGutter = null; + // Actual scrollable element. + d.scroller = elt("div", [d.sizer, d.heightForcer, d.gutters], "CodeMirror-scroll"); + d.scroller.setAttribute("tabIndex", "-1"); + // The element in which the editor lives. + d.wrapper = elt("div", [d.scrollbarFiller, d.gutterFiller, d.scroller], "CodeMirror"); + + // Work around IE7 z-index bug (not perfect, hence IE7 not really being supported) + if (ie && ie_version < 8) { d.gutters.style.zIndex = -1; d.scroller.style.paddingRight = 0; } + if (!webkit && !(gecko && mobile)) d.scroller.draggable = true; + + if (place) { + if (place.appendChild) place.appendChild(d.wrapper); + else place(d.wrapper); + } + + // Current rendered range (may be bigger than the view window). + d.viewFrom = d.viewTo = doc.first; + d.reportedViewFrom = d.reportedViewTo = doc.first; + // Information about the rendered lines. + d.view = []; + d.renderedView = null; + // Holds info about a single rendered line when it was rendered + // for measurement, while not in view. + d.externalMeasured = null; + // Empty space (in pixels) above the view + d.viewOffset = 0; + d.lastWrapHeight = d.lastWrapWidth = 0; + d.updateLineNumbers = null; + + d.nativeBarWidth = d.barHeight = d.barWidth = 0; + d.scrollbarsClipped = false; + + // Used to only resize the line number gutter when necessary (when + // the amount of lines crosses a boundary that makes its width change) + d.lineNumWidth = d.lineNumInnerWidth = d.lineNumChars = null; + // Set to true when a non-horizontal-scrolling line widget is + // added. As an optimization, line widget aligning is skipped when + // this is false. + d.alignWidgets = false; + + d.cachedCharWidth = d.cachedTextHeight = d.cachedPaddingH = null; + + // Tracks the maximum line length so that the horizontal scrollbar + // can be kept static when scrolling. + d.maxLine = null; + d.maxLineLength = 0; + d.maxLineChanged = false; + + // Used for measuring wheel scrolling granularity + d.wheelDX = d.wheelDY = d.wheelStartX = d.wheelStartY = null; + + // True when shift is held down. + d.shift = false; + + // Used to track whether anything happened since the context menu + // was opened. + d.selForContextMenu = null; + + d.activeTouch = null; + + input.init(d); + } + + // STATE UPDATES + + // Used to get the editor into a consistent state again when options change. + + function loadMode(cm) { + cm.doc.mode = CodeMirror.getMode(cm.options, cm.doc.modeOption); + resetModeState(cm); + } + + function resetModeState(cm) { + cm.doc.iter(function(line) { + if (line.stateAfter) line.stateAfter = null; + if (line.styles) line.styles = null; + }); + cm.doc.frontier = cm.doc.first; + startWorker(cm, 100); + cm.state.modeGen++; + if (cm.curOp) regChange(cm); + } + + function wrappingChanged(cm) { + if (cm.options.lineWrapping) { + addClass(cm.display.wrapper, "CodeMirror-wrap"); + cm.display.sizer.style.minWidth = ""; + cm.display.sizerWidth = null; + } else { + rmClass(cm.display.wrapper, "CodeMirror-wrap"); + findMaxLine(cm); + } + estimateLineHeights(cm); + regChange(cm); + clearCaches(cm); + setTimeout(function(){updateScrollbars(cm);}, 100); + } + + // Returns a function that estimates the height of a line, to use as + // first approximation until the line becomes visible (and is thus + // properly measurable). + function estimateHeight(cm) { + var th = textHeight(cm.display), wrapping = cm.options.lineWrapping; + var perLine = wrapping && Math.max(5, cm.display.scroller.clientWidth / charWidth(cm.display) - 3); + return function(line) { + if (lineIsHidden(cm.doc, line)) return 0; + + var widgetsHeight = 0; + if (line.widgets) for (var i = 0; i < line.widgets.length; i++) { + if (line.widgets[i].height) widgetsHeight += line.widgets[i].height; + } + + if (wrapping) + return widgetsHeight + (Math.ceil(line.text.length / perLine) || 1) * th; + else + return widgetsHeight + th; + }; + } + + function estimateLineHeights(cm) { + var doc = cm.doc, est = estimateHeight(cm); + doc.iter(function(line) { + var estHeight = est(line); + if (estHeight != line.height) updateLineHeight(line, estHeight); + }); + } + + function themeChanged(cm) { + cm.display.wrapper.className = cm.display.wrapper.className.replace(/\s*cm-s-\S+/g, "") + + cm.options.theme.replace(/(^|\s)\s*/g, " cm-s-"); + clearCaches(cm); + } + + function guttersChanged(cm) { + updateGutters(cm); + regChange(cm); + setTimeout(function(){alignHorizontally(cm);}, 20); + } + + // Rebuild the gutter elements, ensure the margin to the left of the + // code matches their width. + function updateGutters(cm) { + var gutters = cm.display.gutters, specs = cm.options.gutters; + removeChildren(gutters); + for (var i = 0; i < specs.length; ++i) { + var gutterClass = specs[i]; + var gElt = gutters.appendChild(elt("div", null, "CodeMirror-gutter " + gutterClass)); + if (gutterClass == "CodeMirror-linenumbers") { + cm.display.lineGutter = gElt; + gElt.style.width = (cm.display.lineNumWidth || 1) + "px"; + } + } + gutters.style.display = i ? "" : "none"; + updateGutterSpace(cm); + } + + function updateGutterSpace(cm) { + var width = cm.display.gutters.offsetWidth; + cm.display.sizer.style.marginLeft = width + "px"; + } + + // Compute the character length of a line, taking into account + // collapsed ranges (see markText) that might hide parts, and join + // other lines onto it. + function lineLength(line) { + if (line.height == 0) return 0; + var len = line.text.length, merged, cur = line; + while (merged = collapsedSpanAtStart(cur)) { + var found = merged.find(0, true); + cur = found.from.line; + len += found.from.ch - found.to.ch; + } + cur = line; + while (merged = collapsedSpanAtEnd(cur)) { + var found = merged.find(0, true); + len -= cur.text.length - found.from.ch; + cur = found.to.line; + len += cur.text.length - found.to.ch; + } + return len; + } + + // Find the longest line in the document. + function findMaxLine(cm) { + var d = cm.display, doc = cm.doc; + d.maxLine = getLine(doc, doc.first); + d.maxLineLength = lineLength(d.maxLine); + d.maxLineChanged = true; + doc.iter(function(line) { + var len = lineLength(line); + if (len > d.maxLineLength) { + d.maxLineLength = len; + d.maxLine = line; + } + }); + } + + // Make sure the gutters options contains the element + // "CodeMirror-linenumbers" when the lineNumbers option is true. + function setGuttersForLineNumbers(options) { + var found = indexOf(options.gutters, "CodeMirror-linenumbers"); + if (found == -1 && options.lineNumbers) { + options.gutters = options.gutters.concat(["CodeMirror-linenumbers"]); + } else if (found > -1 && !options.lineNumbers) { + options.gutters = options.gutters.slice(0); + options.gutters.splice(found, 1); + } + } + + // SCROLLBARS + + // Prepare DOM reads needed to update the scrollbars. Done in one + // shot to minimize update/measure roundtrips. + function measureForScrollbars(cm) { + var d = cm.display, gutterW = d.gutters.offsetWidth; + var docH = Math.round(cm.doc.height + paddingVert(cm.display)); + return { + clientHeight: d.scroller.clientHeight, + viewHeight: d.wrapper.clientHeight, + scrollWidth: d.scroller.scrollWidth, clientWidth: d.scroller.clientWidth, + viewWidth: d.wrapper.clientWidth, + barLeft: cm.options.fixedGutter ? gutterW : 0, + docHeight: docH, + scrollHeight: docH + scrollGap(cm) + d.barHeight, + nativeBarWidth: d.nativeBarWidth, + gutterWidth: gutterW + }; + } + + function NativeScrollbars(place, scroll, cm) { + this.cm = cm; + var vert = this.vert = elt("div", [elt("div", null, null, "min-width: 1px")], "CodeMirror-vscrollbar"); + var horiz = this.horiz = elt("div", [elt("div", null, null, "height: 100%; min-height: 1px")], "CodeMirror-hscrollbar"); + place(vert); place(horiz); + + on(vert, "scroll", function() { + if (vert.clientHeight) scroll(vert.scrollTop, "vertical"); + }); + on(horiz, "scroll", function() { + if (horiz.clientWidth) scroll(horiz.scrollLeft, "horizontal"); + }); + + this.checkedOverlay = false; + // Need to set a minimum width to see the scrollbar on IE7 (but must not set it on IE8). + if (ie && ie_version < 8) this.horiz.style.minHeight = this.vert.style.minWidth = "18px"; + } + + NativeScrollbars.prototype = copyObj({ + update: function(measure) { + var needsH = measure.scrollWidth > measure.clientWidth + 1; + var needsV = measure.scrollHeight > measure.clientHeight + 1; + var sWidth = measure.nativeBarWidth; + + if (needsV) { + this.vert.style.display = "block"; + this.vert.style.bottom = needsH ? sWidth + "px" : "0"; + var totalHeight = measure.viewHeight - (needsH ? sWidth : 0); + // A bug in IE8 can cause this value to be negative, so guard it. + this.vert.firstChild.style.height = + Math.max(0, measure.scrollHeight - measure.clientHeight + totalHeight) + "px"; + } else { + this.vert.style.display = ""; + this.vert.firstChild.style.height = "0"; + } + + if (needsH) { + this.horiz.style.display = "block"; + this.horiz.style.right = needsV ? sWidth + "px" : "0"; + this.horiz.style.left = measure.barLeft + "px"; + var totalWidth = measure.viewWidth - measure.barLeft - (needsV ? sWidth : 0); + this.horiz.firstChild.style.width = + (measure.scrollWidth - measure.clientWidth + totalWidth) + "px"; + } else { + this.horiz.style.display = ""; + this.horiz.firstChild.style.width = "0"; + } + + if (!this.checkedOverlay && measure.clientHeight > 0) { + if (sWidth == 0) this.overlayHack(); + this.checkedOverlay = true; + } + + return {right: needsV ? sWidth : 0, bottom: needsH ? sWidth : 0}; + }, + setScrollLeft: function(pos) { + if (this.horiz.scrollLeft != pos) this.horiz.scrollLeft = pos; + }, + setScrollTop: function(pos) { + if (this.vert.scrollTop != pos) this.vert.scrollTop = pos; + }, + overlayHack: function() { + var w = mac && !mac_geMountainLion ? "12px" : "18px"; + this.horiz.style.minHeight = this.vert.style.minWidth = w; + var self = this; + var barMouseDown = function(e) { + if (e_target(e) != self.vert && e_target(e) != self.horiz) + operation(self.cm, onMouseDown)(e); + }; + on(this.vert, "mousedown", barMouseDown); + on(this.horiz, "mousedown", barMouseDown); + }, + clear: function() { + var parent = this.horiz.parentNode; + parent.removeChild(this.horiz); + parent.removeChild(this.vert); + } + }, NativeScrollbars.prototype); + + function NullScrollbars() {} + + NullScrollbars.prototype = copyObj({ + update: function() { return {bottom: 0, right: 0}; }, + setScrollLeft: function() {}, + setScrollTop: function() {}, + clear: function() {} + }, NullScrollbars.prototype); + + CodeMirror.scrollbarModel = {"native": NativeScrollbars, "null": NullScrollbars}; + + function initScrollbars(cm) { + if (cm.display.scrollbars) { + cm.display.scrollbars.clear(); + if (cm.display.scrollbars.addClass) + rmClass(cm.display.wrapper, cm.display.scrollbars.addClass); + } + + cm.display.scrollbars = new CodeMirror.scrollbarModel[cm.options.scrollbarStyle](function(node) { + cm.display.wrapper.insertBefore(node, cm.display.scrollbarFiller); + // Prevent clicks in the scrollbars from killing focus + on(node, "mousedown", function() { + if (cm.state.focused) setTimeout(function() { cm.display.input.focus(); }, 0); + }); + node.setAttribute("cm-not-content", "true"); + }, function(pos, axis) { + if (axis == "horizontal") setScrollLeft(cm, pos); + else setScrollTop(cm, pos); + }, cm); + if (cm.display.scrollbars.addClass) + addClass(cm.display.wrapper, cm.display.scrollbars.addClass); + } + + function updateScrollbars(cm, measure) { + if (!measure) measure = measureForScrollbars(cm); + var startWidth = cm.display.barWidth, startHeight = cm.display.barHeight; + updateScrollbarsInner(cm, measure); + for (var i = 0; i < 4 && startWidth != cm.display.barWidth || startHeight != cm.display.barHeight; i++) { + if (startWidth != cm.display.barWidth && cm.options.lineWrapping) + updateHeightsInViewport(cm); + updateScrollbarsInner(cm, measureForScrollbars(cm)); + startWidth = cm.display.barWidth; startHeight = cm.display.barHeight; + } + } + + // Re-synchronize the fake scrollbars with the actual size of the + // content. + function updateScrollbarsInner(cm, measure) { + var d = cm.display; + var sizes = d.scrollbars.update(measure); + + d.sizer.style.paddingRight = (d.barWidth = sizes.right) + "px"; + d.sizer.style.paddingBottom = (d.barHeight = sizes.bottom) + "px"; + + if (sizes.right && sizes.bottom) { + d.scrollbarFiller.style.display = "block"; + d.scrollbarFiller.style.height = sizes.bottom + "px"; + d.scrollbarFiller.style.width = sizes.right + "px"; + } else d.scrollbarFiller.style.display = ""; + if (sizes.bottom && cm.options.coverGutterNextToScrollbar && cm.options.fixedGutter) { + d.gutterFiller.style.display = "block"; + d.gutterFiller.style.height = sizes.bottom + "px"; + d.gutterFiller.style.width = measure.gutterWidth + "px"; + } else d.gutterFiller.style.display = ""; + } + + // Compute the lines that are visible in a given viewport (defaults + // the the current scroll position). viewport may contain top, + // height, and ensure (see op.scrollToPos) properties. + function visibleLines(display, doc, viewport) { + var top = viewport && viewport.top != null ? Math.max(0, viewport.top) : display.scroller.scrollTop; + top = Math.floor(top - paddingTop(display)); + var bottom = viewport && viewport.bottom != null ? viewport.bottom : top + display.wrapper.clientHeight; + + var from = lineAtHeight(doc, top), to = lineAtHeight(doc, bottom); + // Ensure is a {from: {line, ch}, to: {line, ch}} object, and + // forces those lines into the viewport (if possible). + if (viewport && viewport.ensure) { + var ensureFrom = viewport.ensure.from.line, ensureTo = viewport.ensure.to.line; + if (ensureFrom < from) { + from = ensureFrom; + to = lineAtHeight(doc, heightAtLine(getLine(doc, ensureFrom)) + display.wrapper.clientHeight); + } else if (Math.min(ensureTo, doc.lastLine()) >= to) { + from = lineAtHeight(doc, heightAtLine(getLine(doc, ensureTo)) - display.wrapper.clientHeight); + to = ensureTo; + } + } + return {from: from, to: Math.max(to, from + 1)}; + } + + // LINE NUMBERS + + // Re-align line numbers and gutter marks to compensate for + // horizontal scrolling. + function alignHorizontally(cm) { + var display = cm.display, view = display.view; + if (!display.alignWidgets && (!display.gutters.firstChild || !cm.options.fixedGutter)) return; + var comp = compensateForHScroll(display) - display.scroller.scrollLeft + cm.doc.scrollLeft; + var gutterW = display.gutters.offsetWidth, left = comp + "px"; + for (var i = 0; i < view.length; i++) if (!view[i].hidden) { + if (cm.options.fixedGutter && view[i].gutter) + view[i].gutter.style.left = left; + var align = view[i].alignable; + if (align) for (var j = 0; j < align.length; j++) + align[j].style.left = left; + } + if (cm.options.fixedGutter) + display.gutters.style.left = (comp + gutterW) + "px"; + } + + // Used to ensure that the line number gutter is still the right + // size for the current document size. Returns true when an update + // is needed. + function maybeUpdateLineNumberWidth(cm) { + if (!cm.options.lineNumbers) return false; + var doc = cm.doc, last = lineNumberFor(cm.options, doc.first + doc.size - 1), display = cm.display; + if (last.length != display.lineNumChars) { + var test = display.measure.appendChild(elt("div", [elt("div", last)], + "CodeMirror-linenumber CodeMirror-gutter-elt")); + var innerW = test.firstChild.offsetWidth, padding = test.offsetWidth - innerW; + display.lineGutter.style.width = ""; + display.lineNumInnerWidth = Math.max(innerW, display.lineGutter.offsetWidth - padding) + 1; + display.lineNumWidth = display.lineNumInnerWidth + padding; + display.lineNumChars = display.lineNumInnerWidth ? last.length : -1; + display.lineGutter.style.width = display.lineNumWidth + "px"; + updateGutterSpace(cm); + return true; + } + return false; + } + + function lineNumberFor(options, i) { + return String(options.lineNumberFormatter(i + options.firstLineNumber)); + } + + // Computes display.scroller.scrollLeft + display.gutters.offsetWidth, + // but using getBoundingClientRect to get a sub-pixel-accurate + // result. + function compensateForHScroll(display) { + return display.scroller.getBoundingClientRect().left - display.sizer.getBoundingClientRect().left; + } + + // DISPLAY DRAWING + + function DisplayUpdate(cm, viewport, force) { + var display = cm.display; + + this.viewport = viewport; + // Store some values that we'll need later (but don't want to force a relayout for) + this.visible = visibleLines(display, cm.doc, viewport); + this.editorIsHidden = !display.wrapper.offsetWidth; + this.wrapperHeight = display.wrapper.clientHeight; + this.wrapperWidth = display.wrapper.clientWidth; + this.oldDisplayWidth = displayWidth(cm); + this.force = force; + this.dims = getDimensions(cm); + this.events = []; + } + + DisplayUpdate.prototype.signal = function(emitter, type) { + if (hasHandler(emitter, type)) + this.events.push(arguments); + }; + DisplayUpdate.prototype.finish = function() { + for (var i = 0; i < this.events.length; i++) + signal.apply(null, this.events[i]); + }; + + function maybeClipScrollbars(cm) { + var display = cm.display; + if (!display.scrollbarsClipped && display.scroller.offsetWidth) { + display.nativeBarWidth = display.scroller.offsetWidth - display.scroller.clientWidth; + display.heightForcer.style.height = scrollGap(cm) + "px"; + display.sizer.style.marginBottom = -display.nativeBarWidth + "px"; + display.sizer.style.borderRightWidth = scrollGap(cm) + "px"; + display.scrollbarsClipped = true; + } + } + + // Does the actual updating of the line display. Bails out + // (returning false) when there is nothing to be done and forced is + // false. + function updateDisplayIfNeeded(cm, update) { + var display = cm.display, doc = cm.doc; + + if (update.editorIsHidden) { + resetView(cm); + return false; + } + + // Bail out if the visible area is already rendered and nothing changed. + if (!update.force && + update.visible.from >= display.viewFrom && update.visible.to <= display.viewTo && + (display.updateLineNumbers == null || display.updateLineNumbers >= display.viewTo) && + display.renderedView == display.view && countDirtyView(cm) == 0) + return false; + + if (maybeUpdateLineNumberWidth(cm)) { + resetView(cm); + update.dims = getDimensions(cm); + } + + // Compute a suitable new viewport (from & to) + var end = doc.first + doc.size; + var from = Math.max(update.visible.from - cm.options.viewportMargin, doc.first); + var to = Math.min(end, update.visible.to + cm.options.viewportMargin); + if (display.viewFrom < from && from - display.viewFrom < 20) from = Math.max(doc.first, display.viewFrom); + if (display.viewTo > to && display.viewTo - to < 20) to = Math.min(end, display.viewTo); + if (sawCollapsedSpans) { + from = visualLineNo(cm.doc, from); + to = visualLineEndNo(cm.doc, to); + } + + var different = from != display.viewFrom || to != display.viewTo || + display.lastWrapHeight != update.wrapperHeight || display.lastWrapWidth != update.wrapperWidth; + adjustView(cm, from, to); + + display.viewOffset = heightAtLine(getLine(cm.doc, display.viewFrom)); + // Position the mover div to align with the current scroll position + cm.display.mover.style.top = display.viewOffset + "px"; + + var toUpdate = countDirtyView(cm); + if (!different && toUpdate == 0 && !update.force && display.renderedView == display.view && + (display.updateLineNumbers == null || display.updateLineNumbers >= display.viewTo)) + return false; + + // For big changes, we hide the enclosing element during the + // update, since that speeds up the operations on most browsers. + var focused = activeElt(); + if (toUpdate > 4) display.lineDiv.style.display = "none"; + patchDisplay(cm, display.updateLineNumbers, update.dims); + if (toUpdate > 4) display.lineDiv.style.display = ""; + display.renderedView = display.view; + // There might have been a widget with a focused element that got + // hidden or updated, if so re-focus it. + if (focused && activeElt() != focused && focused.offsetHeight) focused.focus(); + + // Prevent selection and cursors from interfering with the scroll + // width and height. + removeChildren(display.cursorDiv); + removeChildren(display.selectionDiv); + display.gutters.style.height = 0; + + if (different) { + display.lastWrapHeight = update.wrapperHeight; + display.lastWrapWidth = update.wrapperWidth; + startWorker(cm, 400); + } + + display.updateLineNumbers = null; + + return true; + } + + function postUpdateDisplay(cm, update) { + var force = update.force, viewport = update.viewport; + for (var first = true;; first = false) { + if (first && cm.options.lineWrapping && update.oldDisplayWidth != displayWidth(cm)) { + force = true; + } else { + force = false; + // Clip forced viewport to actual scrollable area. + if (viewport && viewport.top != null) + viewport = {top: Math.min(cm.doc.height + paddingVert(cm.display) - displayHeight(cm), viewport.top)}; + // Updated line heights might result in the drawn area not + // actually covering the viewport. Keep looping until it does. + update.visible = visibleLines(cm.display, cm.doc, viewport); + if (update.visible.from >= cm.display.viewFrom && update.visible.to <= cm.display.viewTo) + break; + } + if (!updateDisplayIfNeeded(cm, update)) break; + updateHeightsInViewport(cm); + var barMeasure = measureForScrollbars(cm); + updateSelection(cm); + setDocumentHeight(cm, barMeasure); + updateScrollbars(cm, barMeasure); + } + + update.signal(cm, "update", cm); + if (cm.display.viewFrom != cm.display.reportedViewFrom || cm.display.viewTo != cm.display.reportedViewTo) { + update.signal(cm, "viewportChange", cm, cm.display.viewFrom, cm.display.viewTo); + cm.display.reportedViewFrom = cm.display.viewFrom; cm.display.reportedViewTo = cm.display.viewTo; + } + } + + function updateDisplaySimple(cm, viewport) { + var update = new DisplayUpdate(cm, viewport); + if (updateDisplayIfNeeded(cm, update)) { + updateHeightsInViewport(cm); + postUpdateDisplay(cm, update); + var barMeasure = measureForScrollbars(cm); + updateSelection(cm); + setDocumentHeight(cm, barMeasure); + updateScrollbars(cm, barMeasure); + update.finish(); + } + } + + function setDocumentHeight(cm, measure) { + cm.display.sizer.style.minHeight = measure.docHeight + "px"; + var total = measure.docHeight + cm.display.barHeight; + cm.display.heightForcer.style.top = total + "px"; + cm.display.gutters.style.height = Math.max(total + scrollGap(cm), measure.clientHeight) + "px"; + } + + // Read the actual heights of the rendered lines, and update their + // stored heights to match. + function updateHeightsInViewport(cm) { + var display = cm.display; + var prevBottom = display.lineDiv.offsetTop; + for (var i = 0; i < display.view.length; i++) { + var cur = display.view[i], height; + if (cur.hidden) continue; + if (ie && ie_version < 8) { + var bot = cur.node.offsetTop + cur.node.offsetHeight; + height = bot - prevBottom; + prevBottom = bot; + } else { + var box = cur.node.getBoundingClientRect(); + height = box.bottom - box.top; + } + var diff = cur.line.height - height; + if (height < 2) height = textHeight(display); + if (diff > .001 || diff < -.001) { + updateLineHeight(cur.line, height); + updateWidgetHeight(cur.line); + if (cur.rest) for (var j = 0; j < cur.rest.length; j++) + updateWidgetHeight(cur.rest[j]); + } + } + } + + // Read and store the height of line widgets associated with the + // given line. + function updateWidgetHeight(line) { + if (line.widgets) for (var i = 0; i < line.widgets.length; ++i) + line.widgets[i].height = line.widgets[i].node.offsetHeight; + } + + // Do a bulk-read of the DOM positions and sizes needed to draw the + // view, so that we don't interleave reading and writing to the DOM. + function getDimensions(cm) { + var d = cm.display, left = {}, width = {}; + var gutterLeft = d.gutters.clientLeft; + for (var n = d.gutters.firstChild, i = 0; n; n = n.nextSibling, ++i) { + left[cm.options.gutters[i]] = n.offsetLeft + n.clientLeft + gutterLeft; + width[cm.options.gutters[i]] = n.clientWidth; + } + return {fixedPos: compensateForHScroll(d), + gutterTotalWidth: d.gutters.offsetWidth, + gutterLeft: left, + gutterWidth: width, + wrapperWidth: d.wrapper.clientWidth}; + } + + // Sync the actual display DOM structure with display.view, removing + // nodes for lines that are no longer in view, and creating the ones + // that are not there yet, and updating the ones that are out of + // date. + function patchDisplay(cm, updateNumbersFrom, dims) { + var display = cm.display, lineNumbers = cm.options.lineNumbers; + var container = display.lineDiv, cur = container.firstChild; + + function rm(node) { + var next = node.nextSibling; + // Works around a throw-scroll bug in OS X Webkit + if (webkit && mac && cm.display.currentWheelTarget == node) + node.style.display = "none"; + else + node.parentNode.removeChild(node); + return next; + } + + var view = display.view, lineN = display.viewFrom; + // Loop over the elements in the view, syncing cur (the DOM nodes + // in display.lineDiv) with the view as we go. + for (var i = 0; i < view.length; i++) { + var lineView = view[i]; + if (lineView.hidden) { + } else if (!lineView.node || lineView.node.parentNode != container) { // Not drawn yet + var node = buildLineElement(cm, lineView, lineN, dims); + container.insertBefore(node, cur); + } else { // Already drawn + while (cur != lineView.node) cur = rm(cur); + var updateNumber = lineNumbers && updateNumbersFrom != null && + updateNumbersFrom <= lineN && lineView.lineNumber; + if (lineView.changes) { + if (indexOf(lineView.changes, "gutter") > -1) updateNumber = false; + updateLineForChanges(cm, lineView, lineN, dims); + } + if (updateNumber) { + removeChildren(lineView.lineNumber); + lineView.lineNumber.appendChild(document.createTextNode(lineNumberFor(cm.options, lineN))); + } + cur = lineView.node.nextSibling; + } + lineN += lineView.size; + } + while (cur) cur = rm(cur); + } + + // When an aspect of a line changes, a string is added to + // lineView.changes. This updates the relevant part of the line's + // DOM structure. + function updateLineForChanges(cm, lineView, lineN, dims) { + for (var j = 0; j < lineView.changes.length; j++) { + var type = lineView.changes[j]; + if (type == "text") updateLineText(cm, lineView); + else if (type == "gutter") updateLineGutter(cm, lineView, lineN, dims); + else if (type == "class") updateLineClasses(lineView); + else if (type == "widget") updateLineWidgets(cm, lineView, dims); + } + lineView.changes = null; + } + + // Lines with gutter elements, widgets or a background class need to + // be wrapped, and have the extra elements added to the wrapper div + function ensureLineWrapped(lineView) { + if (lineView.node == lineView.text) { + lineView.node = elt("div", null, null, "position: relative"); + if (lineView.text.parentNode) + lineView.text.parentNode.replaceChild(lineView.node, lineView.text); + lineView.node.appendChild(lineView.text); + if (ie && ie_version < 8) lineView.node.style.zIndex = 2; + } + return lineView.node; + } + + function updateLineBackground(lineView) { + var cls = lineView.bgClass ? lineView.bgClass + " " + (lineView.line.bgClass || "") : lineView.line.bgClass; + if (cls) cls += " CodeMirror-linebackground"; + if (lineView.background) { + if (cls) lineView.background.className = cls; + else { lineView.background.parentNode.removeChild(lineView.background); lineView.background = null; } + } else if (cls) { + var wrap = ensureLineWrapped(lineView); + lineView.background = wrap.insertBefore(elt("div", null, cls), wrap.firstChild); + } + } + + // Wrapper around buildLineContent which will reuse the structure + // in display.externalMeasured when possible. + function getLineContent(cm, lineView) { + var ext = cm.display.externalMeasured; + if (ext && ext.line == lineView.line) { + cm.display.externalMeasured = null; + lineView.measure = ext.measure; + return ext.built; + } + return buildLineContent(cm, lineView); + } + + // Redraw the line's text. Interacts with the background and text + // classes because the mode may output tokens that influence these + // classes. + function updateLineText(cm, lineView) { + var cls = lineView.text.className; + var built = getLineContent(cm, lineView); + if (lineView.text == lineView.node) lineView.node = built.pre; + lineView.text.parentNode.replaceChild(built.pre, lineView.text); + lineView.text = built.pre; + if (built.bgClass != lineView.bgClass || built.textClass != lineView.textClass) { + lineView.bgClass = built.bgClass; + lineView.textClass = built.textClass; + updateLineClasses(lineView); + } else if (cls) { + lineView.text.className = cls; + } + } + + function updateLineClasses(lineView) { + updateLineBackground(lineView); + if (lineView.line.wrapClass) + ensureLineWrapped(lineView).className = lineView.line.wrapClass; + else if (lineView.node != lineView.text) + lineView.node.className = ""; + var textClass = lineView.textClass ? lineView.textClass + " " + (lineView.line.textClass || "") : lineView.line.textClass; + lineView.text.className = textClass || ""; + } + + function updateLineGutter(cm, lineView, lineN, dims) { + if (lineView.gutter) { + lineView.node.removeChild(lineView.gutter); + lineView.gutter = null; + } + var markers = lineView.line.gutterMarkers; + if (cm.options.lineNumbers || markers) { + var wrap = ensureLineWrapped(lineView); + var gutterWrap = lineView.gutter = elt("div", null, "CodeMirror-gutter-wrapper", "left: " + + (cm.options.fixedGutter ? dims.fixedPos : -dims.gutterTotalWidth) + + "px; width: " + dims.gutterTotalWidth + "px"); + cm.display.input.setUneditable(gutterWrap); + wrap.insertBefore(gutterWrap, lineView.text); + if (lineView.line.gutterClass) + gutterWrap.className += " " + lineView.line.gutterClass; + if (cm.options.lineNumbers && (!markers || !markers["CodeMirror-linenumbers"])) + lineView.lineNumber = gutterWrap.appendChild( + elt("div", lineNumberFor(cm.options, lineN), + "CodeMirror-linenumber CodeMirror-gutter-elt", + "left: " + dims.gutterLeft["CodeMirror-linenumbers"] + "px; width: " + + cm.display.lineNumInnerWidth + "px")); + if (markers) for (var k = 0; k < cm.options.gutters.length; ++k) { + var id = cm.options.gutters[k], found = markers.hasOwnProperty(id) && markers[id]; + if (found) + gutterWrap.appendChild(elt("div", [found], "CodeMirror-gutter-elt", "left: " + + dims.gutterLeft[id] + "px; width: " + dims.gutterWidth[id] + "px")); + } + } + } + + function updateLineWidgets(cm, lineView, dims) { + if (lineView.alignable) lineView.alignable = null; + for (var node = lineView.node.firstChild, next; node; node = next) { + var next = node.nextSibling; + if (node.className == "CodeMirror-linewidget") + lineView.node.removeChild(node); + } + insertLineWidgets(cm, lineView, dims); + } + + // Build a line's DOM representation from scratch + function buildLineElement(cm, lineView, lineN, dims) { + var built = getLineContent(cm, lineView); + lineView.text = lineView.node = built.pre; + if (built.bgClass) lineView.bgClass = built.bgClass; + if (built.textClass) lineView.textClass = built.textClass; + + updateLineClasses(lineView); + updateLineGutter(cm, lineView, lineN, dims); + insertLineWidgets(cm, lineView, dims); + return lineView.node; + } + + // A lineView may contain multiple logical lines (when merged by + // collapsed spans). The widgets for all of them need to be drawn. + function insertLineWidgets(cm, lineView, dims) { + insertLineWidgetsFor(cm, lineView.line, lineView, dims, true); + if (lineView.rest) for (var i = 0; i < lineView.rest.length; i++) + insertLineWidgetsFor(cm, lineView.rest[i], lineView, dims, false); + } + + function insertLineWidgetsFor(cm, line, lineView, dims, allowAbove) { + if (!line.widgets) return; + var wrap = ensureLineWrapped(lineView); + for (var i = 0, ws = line.widgets; i < ws.length; ++i) { + var widget = ws[i], node = elt("div", [widget.node], "CodeMirror-linewidget"); + if (!widget.handleMouseEvents) node.setAttribute("cm-ignore-events", "true"); + positionLineWidget(widget, node, lineView, dims); + cm.display.input.setUneditable(node); + if (allowAbove && widget.above) + wrap.insertBefore(node, lineView.gutter || lineView.text); + else + wrap.appendChild(node); + signalLater(widget, "redraw"); + } + } + + function positionLineWidget(widget, node, lineView, dims) { + if (widget.noHScroll) { + (lineView.alignable || (lineView.alignable = [])).push(node); + var width = dims.wrapperWidth; + node.style.left = dims.fixedPos + "px"; + if (!widget.coverGutter) { + width -= dims.gutterTotalWidth; + node.style.paddingLeft = dims.gutterTotalWidth + "px"; + } + node.style.width = width + "px"; + } + if (widget.coverGutter) { + node.style.zIndex = 5; + node.style.position = "relative"; + if (!widget.noHScroll) node.style.marginLeft = -dims.gutterTotalWidth + "px"; + } + } + + // POSITION OBJECT + + // A Pos instance represents a position within the text. + var Pos = CodeMirror.Pos = function(line, ch) { + if (!(this instanceof Pos)) return new Pos(line, ch); + this.line = line; this.ch = ch; + }; + + // Compare two positions, return 0 if they are the same, a negative + // number when a is less, and a positive number otherwise. + var cmp = CodeMirror.cmpPos = function(a, b) { return a.line - b.line || a.ch - b.ch; }; + + function copyPos(x) {return Pos(x.line, x.ch);} + function maxPos(a, b) { return cmp(a, b) < 0 ? b : a; } + function minPos(a, b) { return cmp(a, b) < 0 ? a : b; } + + // INPUT HANDLING + + function ensureFocus(cm) { + if (!cm.state.focused) { cm.display.input.focus(); onFocus(cm); } + } + + function isReadOnly(cm) { + return cm.options.readOnly || cm.doc.cantEdit; + } + + // This will be set to an array of strings when copying, so that, + // when pasting, we know what kind of selections the copied text + // was made out of. + var lastCopied = null; + + function applyTextInput(cm, inserted, deleted, sel) { + var doc = cm.doc; + cm.display.shift = false; + if (!sel) sel = doc.sel; + + var textLines = splitLines(inserted), multiPaste = null; + // When pasing N lines into N selections, insert one line per selection + if (cm.state.pasteIncoming && sel.ranges.length > 1) { + if (lastCopied && lastCopied.join("\n") == inserted) + multiPaste = sel.ranges.length % lastCopied.length == 0 && map(lastCopied, splitLines); + else if (textLines.length == sel.ranges.length) + multiPaste = map(textLines, function(l) { return [l]; }); + } + + // Normal behavior is to insert the new text into every selection + for (var i = sel.ranges.length - 1; i >= 0; i--) { + var range = sel.ranges[i]; + var from = range.from(), to = range.to(); + if (range.empty()) { + if (deleted && deleted > 0) // Handle deletion + from = Pos(from.line, from.ch - deleted); + else if (cm.state.overwrite && !cm.state.pasteIncoming) // Handle overwrite + to = Pos(to.line, Math.min(getLine(doc, to.line).text.length, to.ch + lst(textLines).length)); + } + var updateInput = cm.curOp.updateInput; + var changeEvent = {from: from, to: to, text: multiPaste ? multiPaste[i % multiPaste.length] : textLines, + origin: cm.state.pasteIncoming ? "paste" : cm.state.cutIncoming ? "cut" : "+input"}; + makeChange(cm.doc, changeEvent); + signalLater(cm, "inputRead", cm, changeEvent); + // When an 'electric' character is inserted, immediately trigger a reindent + if (inserted && !cm.state.pasteIncoming && cm.options.electricChars && + cm.options.smartIndent && range.head.ch < 100 && + (!i || sel.ranges[i - 1].head.line != range.head.line)) { + var mode = cm.getModeAt(range.head); + var end = changeEnd(changeEvent); + if (mode.electricChars) { + for (var j = 0; j < mode.electricChars.length; j++) + if (inserted.indexOf(mode.electricChars.charAt(j)) > -1) { + indentLine(cm, end.line, "smart"); + break; + } + } else if (mode.electricInput) { + if (mode.electricInput.test(getLine(doc, end.line).text.slice(0, end.ch))) + indentLine(cm, end.line, "smart"); + } + } + } + ensureCursorVisible(cm); + cm.curOp.updateInput = updateInput; + cm.curOp.typing = true; + cm.state.pasteIncoming = cm.state.cutIncoming = false; + } + + function copyableRanges(cm) { + var text = [], ranges = []; + for (var i = 0; i < cm.doc.sel.ranges.length; i++) { + var line = cm.doc.sel.ranges[i].head.line; + var lineRange = {anchor: Pos(line, 0), head: Pos(line + 1, 0)}; + ranges.push(lineRange); + text.push(cm.getRange(lineRange.anchor, lineRange.head)); + } + return {text: text, ranges: ranges}; + } + + function disableBrowserMagic(field) { + field.setAttribute("autocorrect", "off"); + field.setAttribute("autocapitalize", "off"); + field.setAttribute("spellcheck", "false"); + } + + // TEXTAREA INPUT STYLE + + function TextareaInput(cm) { + this.cm = cm; + // See input.poll and input.reset + this.prevInput = ""; + + // Flag that indicates whether we expect input to appear real soon + // now (after some event like 'keypress' or 'input') and are + // polling intensively. + this.pollingFast = false; + // Self-resetting timeout for the poller + this.polling = new Delayed(); + // Tracks when input.reset has punted to just putting a short + // string into the textarea instead of the full selection. + this.inaccurateSelection = false; + // Used to work around IE issue with selection being forgotten when focus moves away from textarea + this.hasSelection = false; + }; + + function hiddenTextarea() { + var te = elt("textarea", null, null, "position: absolute; padding: 0; width: 1px; height: 1em; outline: none"); + var div = elt("div", [te], null, "overflow: hidden; position: relative; width: 3px; height: 0px;"); + // The textarea is kept positioned near the cursor to prevent the + // fact that it'll be scrolled into view on input from scrolling + // our fake cursor out of view. On webkit, when wrap=off, paste is + // very slow. So make the area wide instead. + if (webkit) te.style.width = "1000px"; + else te.setAttribute("wrap", "off"); + // If border: 0; -- iOS fails to open keyboard (issue #1287) + if (ios) te.style.border = "1px solid black"; + disableBrowserMagic(te); + return div; + } + + TextareaInput.prototype = copyObj({ + init: function(display) { + var input = this, cm = this.cm; + + // Wraps and hides input textarea + var div = this.wrapper = hiddenTextarea(); + // The semihidden textarea that is focused when the editor is + // focused, and receives input. + var te = this.textarea = div.firstChild; + display.wrapper.insertBefore(div, display.wrapper.firstChild); + + // Needed to hide big blue blinking cursor on Mobile Safari (doesn't seem to work in iOS 8 anymore) + if (ios) te.style.width = "0px"; + + on(te, "input", function() { + if (ie && ie_version >= 9 && input.hasSelection) input.hasSelection = null; + input.poll(); + }); + + on(te, "paste", function() { + // Workaround for webkit bug https://bugs.webkit.org/show_bug.cgi?id=90206 + // Add a char to the end of textarea before paste occur so that + // selection doesn't span to the end of textarea. + if (webkit && !cm.state.fakedLastChar && !(new Date - cm.state.lastMiddleDown < 200)) { + var start = te.selectionStart, end = te.selectionEnd; + te.value += "$"; + // The selection end needs to be set before the start, otherwise there + // can be an intermediate non-empty selection between the two, which + // can override the middle-click paste buffer on linux and cause the + // wrong thing to get pasted. + te.selectionEnd = end; + te.selectionStart = start; + cm.state.fakedLastChar = true; + } + cm.state.pasteIncoming = true; + input.fastPoll(); + }); + + function prepareCopyCut(e) { + if (cm.somethingSelected()) { + lastCopied = cm.getSelections(); + if (input.inaccurateSelection) { + input.prevInput = ""; + input.inaccurateSelection = false; + te.value = lastCopied.join("\n"); + selectInput(te); + } + } else if (!cm.options.lineWiseCopyCut) { + return; + } else { + var ranges = copyableRanges(cm); + lastCopied = ranges.text; + if (e.type == "cut") { + cm.setSelections(ranges.ranges, null, sel_dontScroll); + } else { + input.prevInput = ""; + te.value = ranges.text.join("\n"); + selectInput(te); + } + } + if (e.type == "cut") cm.state.cutIncoming = true; + } + on(te, "cut", prepareCopyCut); + on(te, "copy", prepareCopyCut); + + on(display.scroller, "paste", function(e) { + if (eventInWidget(display, e)) return; + cm.state.pasteIncoming = true; + input.focus(); + }); + + // Prevent normal selection in the editor (we handle our own) + on(display.lineSpace, "selectstart", function(e) { + if (!eventInWidget(display, e)) e_preventDefault(e); + }); + }, + + prepareSelection: function() { + // Redraw the selection and/or cursor + var cm = this.cm, display = cm.display, doc = cm.doc; + var result = prepareSelection(cm); + + // Move the hidden textarea near the cursor to prevent scrolling artifacts + if (cm.options.moveInputWithCursor) { + var headPos = cursorCoords(cm, doc.sel.primary().head, "div"); + var wrapOff = display.wrapper.getBoundingClientRect(), lineOff = display.lineDiv.getBoundingClientRect(); + result.teTop = Math.max(0, Math.min(display.wrapper.clientHeight - 10, + headPos.top + lineOff.top - wrapOff.top)); + result.teLeft = Math.max(0, Math.min(display.wrapper.clientWidth - 10, + headPos.left + lineOff.left - wrapOff.left)); + } + + return result; + }, + + showSelection: function(drawn) { + var cm = this.cm, display = cm.display; + removeChildrenAndAdd(display.cursorDiv, drawn.cursors); + removeChildrenAndAdd(display.selectionDiv, drawn.selection); + if (drawn.teTop != null) { + this.wrapper.style.top = drawn.teTop + "px"; + this.wrapper.style.left = drawn.teLeft + "px"; + } + }, + + // Reset the input to correspond to the selection (or to be empty, + // when not typing and nothing is selected) + reset: function(typing) { + if (this.contextMenuPending) return; + var minimal, selected, cm = this.cm, doc = cm.doc; + if (cm.somethingSelected()) { + this.prevInput = ""; + var range = doc.sel.primary(); + minimal = hasCopyEvent && + (range.to().line - range.from().line > 100 || (selected = cm.getSelection()).length > 1000); + var content = minimal ? "-" : selected || cm.getSelection(); + this.textarea.value = content; + if (cm.state.focused) selectInput(this.textarea); + if (ie && ie_version >= 9) this.hasSelection = content; + } else if (!typing) { + this.prevInput = this.textarea.value = ""; + if (ie && ie_version >= 9) this.hasSelection = null; + } + this.inaccurateSelection = minimal; + }, + + getField: function() { return this.textarea; }, + + supportsTouch: function() { return false; }, + + focus: function() { + if (this.cm.options.readOnly != "nocursor" && (!mobile || activeElt() != this.textarea)) { + try { this.textarea.focus(); } + catch (e) {} // IE8 will throw if the textarea is display: none or not in DOM + } + }, + + blur: function() { this.textarea.blur(); }, + + resetPosition: function() { + this.wrapper.style.top = this.wrapper.style.left = 0; + }, + + receivedFocus: function() { this.slowPoll(); }, + + // Poll for input changes, using the normal rate of polling. This + // runs as long as the editor is focused. + slowPoll: function() { + var input = this; + if (input.pollingFast) return; + input.polling.set(this.cm.options.pollInterval, function() { + input.poll(); + if (input.cm.state.focused) input.slowPoll(); + }); + }, + + // When an event has just come in that is likely to add or change + // something in the input textarea, we poll faster, to ensure that + // the change appears on the screen quickly. + fastPoll: function() { + var missed = false, input = this; + input.pollingFast = true; + function p() { + var changed = input.poll(); + if (!changed && !missed) {missed = true; input.polling.set(60, p);} + else {input.pollingFast = false; input.slowPoll();} + } + input.polling.set(20, p); + }, + + // Read input from the textarea, and update the document to match. + // When something is selected, it is present in the textarea, and + // selected (unless it is huge, in which case a placeholder is + // used). When nothing is selected, the cursor sits after previously + // seen text (can be empty), which is stored in prevInput (we must + // not reset the textarea when typing, because that breaks IME). + poll: function() { + var cm = this.cm, input = this.textarea, prevInput = this.prevInput; + // Since this is called a *lot*, try to bail out as cheaply as + // possible when it is clear that nothing happened. hasSelection + // will be the case when there is a lot of text in the textarea, + // in which case reading its value would be expensive. + if (!cm.state.focused || (hasSelection(input) && !prevInput) || + isReadOnly(cm) || cm.options.disableInput || cm.state.keySeq) + return false; + // See paste handler for more on the fakedLastChar kludge + if (cm.state.pasteIncoming && cm.state.fakedLastChar) { + input.value = input.value.substring(0, input.value.length - 1); + cm.state.fakedLastChar = false; + } + var text = input.value; + // If nothing changed, bail. + if (text == prevInput && !cm.somethingSelected()) return false; + // Work around nonsensical selection resetting in IE9/10, and + // inexplicable appearance of private area unicode characters on + // some key combos in Mac (#2689). + if (ie && ie_version >= 9 && this.hasSelection === text || + mac && /[\uf700-\uf7ff]/.test(text)) { + cm.display.input.reset(); + return false; + } + + if (cm.doc.sel == cm.display.selForContextMenu) { + var first = text.charCodeAt(0); + if (first == 0x200b && !prevInput) prevInput = "\u200b"; + if (first == 0x21da) { this.reset(); return this.cm.execCommand("undo"); } + } + // Find the part of the input that is actually new + var same = 0, l = Math.min(prevInput.length, text.length); + while (same < l && prevInput.charCodeAt(same) == text.charCodeAt(same)) ++same; + + var self = this; + runInOp(cm, function() { + applyTextInput(cm, text.slice(same), prevInput.length - same); + + // Don't leave long text in the textarea, since it makes further polling slow + if (text.length > 1000 || text.indexOf("\n") > -1) input.value = self.prevInput = ""; + else self.prevInput = text; + }); + return true; + }, + + ensurePolled: function() { + if (this.pollingFast && this.poll()) this.pollingFast = false; + }, + + onKeyPress: function() { + if (ie && ie_version >= 9) this.hasSelection = null; + this.fastPoll(); + }, + + onContextMenu: function(e) { + var input = this, cm = input.cm, display = cm.display, te = input.textarea; + var pos = posFromMouse(cm, e), scrollPos = display.scroller.scrollTop; + if (!pos || presto) return; // Opera is difficult. + + // Reset the current text selection only if the click is done outside of the selection + // and 'resetSelectionOnContextMenu' option is true. + var reset = cm.options.resetSelectionOnContextMenu; + if (reset && cm.doc.sel.contains(pos) == -1) + operation(cm, setSelection)(cm.doc, simpleSelection(pos), sel_dontScroll); + + var oldCSS = te.style.cssText; + input.wrapper.style.position = "absolute"; + te.style.cssText = "position: fixed; width: 30px; height: 30px; top: " + (e.clientY - 5) + + "px; left: " + (e.clientX - 5) + "px; z-index: 1000; background: " + + (ie ? "rgba(255, 255, 255, .05)" : "transparent") + + "; outline: none; border-width: 0; outline: none; overflow: hidden; opacity: .05; filter: alpha(opacity=5);"; + if (webkit) var oldScrollY = window.scrollY; // Work around Chrome issue (#2712) + display.input.focus(); + if (webkit) window.scrollTo(null, oldScrollY); + display.input.reset(); + // Adds "Select all" to context menu in FF + if (!cm.somethingSelected()) te.value = input.prevInput = " "; + input.contextMenuPending = true; + display.selForContextMenu = cm.doc.sel; + clearTimeout(display.detectingSelectAll); + + // Select-all will be greyed out if there's nothing to select, so + // this adds a zero-width space so that we can later check whether + // it got selected. + function prepareSelectAllHack() { + if (te.selectionStart != null) { + var selected = cm.somethingSelected(); + var extval = "\u200b" + (selected ? te.value : ""); + te.value = "\u21da"; // Used to catch context-menu undo + te.value = extval; + input.prevInput = selected ? "" : "\u200b"; + te.selectionStart = 1; te.selectionEnd = extval.length; + // Re-set this, in case some other handler touched the + // selection in the meantime. + display.selForContextMenu = cm.doc.sel; + } + } + function rehide() { + input.contextMenuPending = false; + input.wrapper.style.position = "relative"; + te.style.cssText = oldCSS; + if (ie && ie_version < 9) display.scrollbars.setScrollTop(display.scroller.scrollTop = scrollPos); + + // Try to detect the user choosing select-all + if (te.selectionStart != null) { + if (!ie || (ie && ie_version < 9)) prepareSelectAllHack(); + var i = 0, poll = function() { + if (display.selForContextMenu == cm.doc.sel && te.selectionStart == 0 && + te.selectionEnd > 0 && input.prevInput == "\u200b") + operation(cm, commands.selectAll)(cm); + else if (i++ < 10) display.detectingSelectAll = setTimeout(poll, 500); + else display.input.reset(); + }; + display.detectingSelectAll = setTimeout(poll, 200); + } + } + + if (ie && ie_version >= 9) prepareSelectAllHack(); + if (captureRightClick) { + e_stop(e); + var mouseup = function() { + off(window, "mouseup", mouseup); + setTimeout(rehide, 20); + }; + on(window, "mouseup", mouseup); + } else { + setTimeout(rehide, 50); + } + }, + + setUneditable: nothing, + + needsContentAttribute: false + }, TextareaInput.prototype); + + // CONTENTEDITABLE INPUT STYLE + + function ContentEditableInput(cm) { + this.cm = cm; + this.lastAnchorNode = this.lastAnchorOffset = this.lastFocusNode = this.lastFocusOffset = null; + this.polling = new Delayed(); + this.gracePeriod = false; + } + + ContentEditableInput.prototype = copyObj({ + init: function(display) { + var input = this, cm = input.cm; + var div = input.div = display.lineDiv; + div.contentEditable = "true"; + disableBrowserMagic(div); + + on(div, "paste", function(e) { + var pasted = e.clipboardData && e.clipboardData.getData("text/plain"); + if (pasted) { + e.preventDefault(); + cm.replaceSelection(pasted, null, "paste"); + } + }); + + on(div, "compositionstart", function(e) { + var data = e.data; + input.composing = {sel: cm.doc.sel, data: data, startData: data}; + if (!data) return; + var prim = cm.doc.sel.primary(); + var line = cm.getLine(prim.head.line); + var found = line.indexOf(data, Math.max(0, prim.head.ch - data.length)); + if (found > -1 && found <= prim.head.ch) + input.composing.sel = simpleSelection(Pos(prim.head.line, found), + Pos(prim.head.line, found + data.length)); + }); + on(div, "compositionupdate", function(e) { + input.composing.data = e.data; + }); + on(div, "compositionend", function(e) { + var ours = input.composing; + if (!ours) return; + if (e.data != ours.startData && !/\u200b/.test(e.data)) + ours.data = e.data; + // Need a small delay to prevent other code (input event, + // selection polling) from doing damage when fired right after + // compositionend. + setTimeout(function() { + if (!ours.handled) + input.applyComposition(ours); + if (input.composing == ours) + input.composing = null; + }, 50); + }); + + on(div, "touchstart", function() { + input.forceCompositionEnd(); + }); + + on(div, "input", function() { + if (input.composing) return; + if (!input.pollContent()) + runInOp(input.cm, function() {regChange(cm);}); + }); + + function onCopyCut(e) { + if (cm.somethingSelected()) { + lastCopied = cm.getSelections(); + if (e.type == "cut") cm.replaceSelection("", null, "cut"); + } else if (!cm.options.lineWiseCopyCut) { + return; + } else { + var ranges = copyableRanges(cm); + lastCopied = ranges.text; + if (e.type == "cut") { + cm.operation(function() { + cm.setSelections(ranges.ranges, 0, sel_dontScroll); + cm.replaceSelection("", null, "cut"); + }); + } + } + // iOS exposes the clipboard API, but seems to discard content inserted into it + if (e.clipboardData && !ios) { + e.preventDefault(); + e.clipboardData.clearData(); + e.clipboardData.setData("text/plain", lastCopied.join("\n")); + } else { + // Old-fashioned briefly-focus-a-textarea hack + var kludge = hiddenTextarea(), te = kludge.firstChild; + cm.display.lineSpace.insertBefore(kludge, cm.display.lineSpace.firstChild); + te.value = lastCopied.join("\n"); + var hadFocus = document.activeElement; + selectInput(te); + setTimeout(function() { + cm.display.lineSpace.removeChild(kludge); + hadFocus.focus(); + }, 50); + } + } + on(div, "copy", onCopyCut); + on(div, "cut", onCopyCut); + }, + + prepareSelection: function() { + var result = prepareSelection(this.cm, false); + result.focus = this.cm.state.focused; + return result; + }, + + showSelection: function(info) { + if (!info || !this.cm.display.view.length) return; + if (info.focus) this.showPrimarySelection(); + this.showMultipleSelections(info); + }, + + showPrimarySelection: function() { + var sel = window.getSelection(), prim = this.cm.doc.sel.primary(); + var curAnchor = domToPos(this.cm, sel.anchorNode, sel.anchorOffset); + var curFocus = domToPos(this.cm, sel.focusNode, sel.focusOffset); + if (curAnchor && !curAnchor.bad && curFocus && !curFocus.bad && + cmp(minPos(curAnchor, curFocus), prim.from()) == 0 && + cmp(maxPos(curAnchor, curFocus), prim.to()) == 0) + return; + + var start = posToDOM(this.cm, prim.from()); + var end = posToDOM(this.cm, prim.to()); + if (!start && !end) return; + + var view = this.cm.display.view; + var old = sel.rangeCount && sel.getRangeAt(0); + if (!start) { + start = {node: view[0].measure.map[2], offset: 0}; + } else if (!end) { // FIXME dangerously hacky + var measure = view[view.length - 1].measure; + var map = measure.maps ? measure.maps[measure.maps.length - 1] : measure.map; + end = {node: map[map.length - 1], offset: map[map.length - 2] - map[map.length - 3]}; + } + + try { var rng = range(start.node, start.offset, end.offset, end.node); } + catch(e) {} // Our model of the DOM might be outdated, in which case the range we try to set can be impossible + if (rng) { + sel.removeAllRanges(); + sel.addRange(rng); + if (old && sel.anchorNode == null) sel.addRange(old); + else if (gecko) this.startGracePeriod(); + } + this.rememberSelection(); + }, + + startGracePeriod: function() { + var input = this; + clearTimeout(this.gracePeriod); + this.gracePeriod = setTimeout(function() { + input.gracePeriod = false; + if (input.selectionChanged()) + input.cm.operation(function() { input.cm.curOp.selectionChanged = true; }); + }, 20); + }, + + showMultipleSelections: function(info) { + removeChildrenAndAdd(this.cm.display.cursorDiv, info.cursors); + removeChildrenAndAdd(this.cm.display.selectionDiv, info.selection); + }, + + rememberSelection: function() { + var sel = window.getSelection(); + this.lastAnchorNode = sel.anchorNode; this.lastAnchorOffset = sel.anchorOffset; + this.lastFocusNode = sel.focusNode; this.lastFocusOffset = sel.focusOffset; + }, + + selectionInEditor: function() { + var sel = window.getSelection(); + if (!sel.rangeCount) return false; + var node = sel.getRangeAt(0).commonAncestorContainer; + return contains(this.div, node); + }, + + focus: function() { + if (this.cm.options.readOnly != "nocursor") this.div.focus(); + }, + blur: function() { this.div.blur(); }, + getField: function() { return this.div; }, + + supportsTouch: function() { return true; }, + + receivedFocus: function() { + var input = this; + if (this.selectionInEditor()) + this.pollSelection(); + else + runInOp(this.cm, function() { input.cm.curOp.selectionChanged = true; }); + + function poll() { + if (input.cm.state.focused) { + input.pollSelection(); + input.polling.set(input.cm.options.pollInterval, poll); + } + } + this.polling.set(this.cm.options.pollInterval, poll); + }, + + selectionChanged: function() { + var sel = window.getSelection(); + return sel.anchorNode != this.lastAnchorNode || sel.anchorOffset != this.lastAnchorOffset || + sel.focusNode != this.lastFocusNode || sel.focusOffset != this.lastFocusOffset; + }, + + pollSelection: function() { + if (!this.composing && !this.gracePeriod && this.selectionChanged()) { + var sel = window.getSelection(), cm = this.cm; + this.rememberSelection(); + var anchor = domToPos(cm, sel.anchorNode, sel.anchorOffset); + var head = domToPos(cm, sel.focusNode, sel.focusOffset); + if (anchor && head) runInOp(cm, function() { + setSelection(cm.doc, simpleSelection(anchor, head), sel_dontScroll); + if (anchor.bad || head.bad) cm.curOp.selectionChanged = true; + }); + } + }, + + pollContent: function() { + var cm = this.cm, display = cm.display, sel = cm.doc.sel.primary(); + var from = sel.from(), to = sel.to(); + if (from.line < display.viewFrom || to.line > display.viewTo - 1) return false; + + var fromIndex; + if (from.line == display.viewFrom || (fromIndex = findViewIndex(cm, from.line)) == 0) { + var fromLine = lineNo(display.view[0].line); + var fromNode = display.view[0].node; + } else { + var fromLine = lineNo(display.view[fromIndex].line); + var fromNode = display.view[fromIndex - 1].node.nextSibling; + } + var toIndex = findViewIndex(cm, to.line); + if (toIndex == display.view.length - 1) { + var toLine = display.viewTo - 1; + var toNode = display.view[toIndex].node; + } else { + var toLine = lineNo(display.view[toIndex + 1].line) - 1; + var toNode = display.view[toIndex + 1].node.previousSibling; + } + + var newText = splitLines(domTextBetween(cm, fromNode, toNode, fromLine, toLine)); + var oldText = getBetween(cm.doc, Pos(fromLine, 0), Pos(toLine, getLine(cm.doc, toLine).text.length)); + while (newText.length > 1 && oldText.length > 1) { + if (lst(newText) == lst(oldText)) { newText.pop(); oldText.pop(); toLine--; } + else if (newText[0] == oldText[0]) { newText.shift(); oldText.shift(); fromLine++; } + else break; + } + + var cutFront = 0, cutEnd = 0; + var newTop = newText[0], oldTop = oldText[0], maxCutFront = Math.min(newTop.length, oldTop.length); + while (cutFront < maxCutFront && newTop.charCodeAt(cutFront) == oldTop.charCodeAt(cutFront)) + ++cutFront; + var newBot = lst(newText), oldBot = lst(oldText); + var maxCutEnd = Math.min(newBot.length - (newText.length == 1 ? cutFront : 0), + oldBot.length - (oldText.length == 1 ? cutFront : 0)); + while (cutEnd < maxCutEnd && + newBot.charCodeAt(newBot.length - cutEnd - 1) == oldBot.charCodeAt(oldBot.length - cutEnd - 1)) + ++cutEnd; + + newText[newText.length - 1] = newBot.slice(0, newBot.length - cutEnd); + newText[0] = newText[0].slice(cutFront); + + var chFrom = Pos(fromLine, cutFront); + var chTo = Pos(toLine, oldText.length ? lst(oldText).length - cutEnd : 0); + if (newText.length > 1 || newText[0] || cmp(chFrom, chTo)) { + replaceRange(cm.doc, newText, chFrom, chTo, "+input"); + return true; + } + }, + + ensurePolled: function() { + this.forceCompositionEnd(); + }, + reset: function() { + this.forceCompositionEnd(); + }, + forceCompositionEnd: function() { + if (!this.composing || this.composing.handled) return; + this.applyComposition(this.composing); + this.composing.handled = true; + this.div.blur(); + this.div.focus(); + }, + applyComposition: function(composing) { + if (composing.data && composing.data != composing.startData) + operation(this.cm, applyTextInput)(this.cm, composing.data, 0, composing.sel); + }, + + setUneditable: function(node) { + node.setAttribute("contenteditable", "false"); + }, + + onKeyPress: function(e) { + e.preventDefault(); + operation(this.cm, applyTextInput)(this.cm, String.fromCharCode(e.charCode == null ? e.keyCode : e.charCode), 0); + }, + + onContextMenu: nothing, + resetPosition: nothing, + + needsContentAttribute: true + }, ContentEditableInput.prototype); + + function posToDOM(cm, pos) { + var view = findViewForLine(cm, pos.line); + if (!view || view.hidden) return null; + var line = getLine(cm.doc, pos.line); + var info = mapFromLineView(view, line, pos.line); + + var order = getOrder(line), side = "left"; + if (order) { + var partPos = getBidiPartAt(order, pos.ch); + side = partPos % 2 ? "right" : "left"; + } + var result = nodeAndOffsetInLineMap(info.map, pos.ch, "left"); + result.offset = result.collapse == "right" ? result.end : result.start; + return result; + } + + function badPos(pos, bad) { if (bad) pos.bad = true; return pos; } + + function domToPos(cm, node, offset) { + var lineNode; + if (node == cm.display.lineDiv) { + lineNode = cm.display.lineDiv.childNodes[offset]; + if (!lineNode) return badPos(cm.clipPos(Pos(cm.display.viewTo - 1)), true); + node = null; offset = 0; + } else { + for (lineNode = node;; lineNode = lineNode.parentNode) { + if (!lineNode || lineNode == cm.display.lineDiv) return null; + if (lineNode.parentNode && lineNode.parentNode == cm.display.lineDiv) break; + } + } + for (var i = 0; i < cm.display.view.length; i++) { + var lineView = cm.display.view[i]; + if (lineView.node == lineNode) + return locateNodeInLineView(lineView, node, offset); + } + } + + function locateNodeInLineView(lineView, node, offset) { + var wrapper = lineView.text.firstChild, bad = false; + if (!node || !contains(wrapper, node)) return badPos(Pos(lineNo(lineView.line), 0), true); + if (node == wrapper) { + bad = true; + node = wrapper.childNodes[offset]; + offset = 0; + if (!node) { + var line = lineView.rest ? lst(lineView.rest) : lineView.line; + return badPos(Pos(lineNo(line), line.text.length), bad); + } + } + + var textNode = node.nodeType == 3 ? node : null, topNode = node; + if (!textNode && node.childNodes.length == 1 && node.firstChild.nodeType == 3) { + textNode = node.firstChild; + if (offset) offset = textNode.nodeValue.length; + } + while (topNode.parentNode != wrapper) topNode = topNode.parentNode; + var measure = lineView.measure, maps = measure.maps; + + function find(textNode, topNode, offset) { + for (var i = -1; i < (maps ? maps.length : 0); i++) { + var map = i < 0 ? measure.map : maps[i]; + for (var j = 0; j < map.length; j += 3) { + var curNode = map[j + 2]; + if (curNode == textNode || curNode == topNode) { + var line = lineNo(i < 0 ? lineView.line : lineView.rest[i]); + var ch = map[j] + offset; + if (offset < 0 || curNode != textNode) ch = map[j + (offset ? 1 : 0)]; + return Pos(line, ch); + } + } + } + } + var found = find(textNode, topNode, offset); + if (found) return badPos(found, bad); + + // FIXME this is all really shaky. might handle the few cases it needs to handle, but likely to cause problems + for (var after = topNode.nextSibling, dist = textNode ? textNode.nodeValue.length - offset : 0; after; after = after.nextSibling) { + found = find(after, after.firstChild, 0); + if (found) + return badPos(Pos(found.line, found.ch - dist), bad); + else + dist += after.textContent.length; + } + for (var before = topNode.previousSibling, dist = offset; before; before = before.previousSibling) { + found = find(before, before.firstChild, -1); + if (found) + return badPos(Pos(found.line, found.ch + dist), bad); + else + dist += after.textContent.length; + } + } + + function domTextBetween(cm, from, to, fromLine, toLine) { + var text = "", closing = false; + function recognizeMarker(id) { return function(marker) { return marker.id == id; }; } + function walk(node) { + if (node.nodeType == 1) { + var cmText = node.getAttribute("cm-text"); + if (cmText != null) { + if (cmText == "") cmText = node.textContent.replace(/\u200b/g, ""); + text += cmText; + return; + } + var markerID = node.getAttribute("cm-marker"), range; + if (markerID) { + var found = cm.findMarks(Pos(fromLine, 0), Pos(toLine + 1, 0), recognizeMarker(+markerID)); + if (found.length && (range = found[0].find())) + text += getBetween(cm.doc, range.from, range.to).join("\n"); + return; + } + if (node.getAttribute("contenteditable") == "false") return; + for (var i = 0; i < node.childNodes.length; i++) + walk(node.childNodes[i]); + if (/^(pre|div|p)$/i.test(node.nodeName)) + closing = true; + } else if (node.nodeType == 3) { + var val = node.nodeValue; + if (!val) return; + if (closing) { + text += "\n"; + closing = false; + } + text += val; + } + } + for (;;) { + walk(from); + if (from == to) break; + from = from.nextSibling; + } + return text; + } + + CodeMirror.inputStyles = {"textarea": TextareaInput, "contenteditable": ContentEditableInput}; + + // SELECTION / CURSOR + + // Selection objects are immutable. A new one is created every time + // the selection changes. A selection is one or more non-overlapping + // (and non-touching) ranges, sorted, and an integer that indicates + // which one is the primary selection (the one that's scrolled into + // view, that getCursor returns, etc). + function Selection(ranges, primIndex) { + this.ranges = ranges; + this.primIndex = primIndex; + } + + Selection.prototype = { + primary: function() { return this.ranges[this.primIndex]; }, + equals: function(other) { + if (other == this) return true; + if (other.primIndex != this.primIndex || other.ranges.length != this.ranges.length) return false; + for (var i = 0; i < this.ranges.length; i++) { + var here = this.ranges[i], there = other.ranges[i]; + if (cmp(here.anchor, there.anchor) != 0 || cmp(here.head, there.head) != 0) return false; + } + return true; + }, + deepCopy: function() { + for (var out = [], i = 0; i < this.ranges.length; i++) + out[i] = new Range(copyPos(this.ranges[i].anchor), copyPos(this.ranges[i].head)); + return new Selection(out, this.primIndex); + }, + somethingSelected: function() { + for (var i = 0; i < this.ranges.length; i++) + if (!this.ranges[i].empty()) return true; + return false; + }, + contains: function(pos, end) { + if (!end) end = pos; + for (var i = 0; i < this.ranges.length; i++) { + var range = this.ranges[i]; + if (cmp(end, range.from()) >= 0 && cmp(pos, range.to()) <= 0) + return i; + } + return -1; + } + }; + + function Range(anchor, head) { + this.anchor = anchor; this.head = head; + } + + Range.prototype = { + from: function() { return minPos(this.anchor, this.head); }, + to: function() { return maxPos(this.anchor, this.head); }, + empty: function() { + return this.head.line == this.anchor.line && this.head.ch == this.anchor.ch; + } + }; + + // Take an unsorted, potentially overlapping set of ranges, and + // build a selection out of it. 'Consumes' ranges array (modifying + // it). + function normalizeSelection(ranges, primIndex) { + var prim = ranges[primIndex]; + ranges.sort(function(a, b) { return cmp(a.from(), b.from()); }); + primIndex = indexOf(ranges, prim); + for (var i = 1; i < ranges.length; i++) { + var cur = ranges[i], prev = ranges[i - 1]; + if (cmp(prev.to(), cur.from()) >= 0) { + var from = minPos(prev.from(), cur.from()), to = maxPos(prev.to(), cur.to()); + var inv = prev.empty() ? cur.from() == cur.head : prev.from() == prev.head; + if (i <= primIndex) --primIndex; + ranges.splice(--i, 2, new Range(inv ? to : from, inv ? from : to)); + } + } + return new Selection(ranges, primIndex); + } + + function simpleSelection(anchor, head) { + return new Selection([new Range(anchor, head || anchor)], 0); + } + + // Most of the external API clips given positions to make sure they + // actually exist within the document. + function clipLine(doc, n) {return Math.max(doc.first, Math.min(n, doc.first + doc.size - 1));} + function clipPos(doc, pos) { + if (pos.line < doc.first) return Pos(doc.first, 0); + var last = doc.first + doc.size - 1; + if (pos.line > last) return Pos(last, getLine(doc, last).text.length); + return clipToLen(pos, getLine(doc, pos.line).text.length); + } + function clipToLen(pos, linelen) { + var ch = pos.ch; + if (ch == null || ch > linelen) return Pos(pos.line, linelen); + else if (ch < 0) return Pos(pos.line, 0); + else return pos; + } + function isLine(doc, l) {return l >= doc.first && l < doc.first + doc.size;} + function clipPosArray(doc, array) { + for (var out = [], i = 0; i < array.length; i++) out[i] = clipPos(doc, array[i]); + return out; + } + + // SELECTION UPDATES + + // The 'scroll' parameter given to many of these indicated whether + // the new cursor position should be scrolled into view after + // modifying the selection. + + // If shift is held or the extend flag is set, extends a range to + // include a given position (and optionally a second position). + // Otherwise, simply returns the range between the given positions. + // Used for cursor motion and such. + function extendRange(doc, range, head, other) { + if (doc.cm && doc.cm.display.shift || doc.extend) { + var anchor = range.anchor; + if (other) { + var posBefore = cmp(head, anchor) < 0; + if (posBefore != (cmp(other, anchor) < 0)) { + anchor = head; + head = other; + } else if (posBefore != (cmp(head, other) < 0)) { + head = other; + } + } + return new Range(anchor, head); + } else { + return new Range(other || head, head); + } + } + + // Extend the primary selection range, discard the rest. + function extendSelection(doc, head, other, options) { + setSelection(doc, new Selection([extendRange(doc, doc.sel.primary(), head, other)], 0), options); + } + + // Extend all selections (pos is an array of selections with length + // equal the number of selections) + function extendSelections(doc, heads, options) { + for (var out = [], i = 0; i < doc.sel.ranges.length; i++) + out[i] = extendRange(doc, doc.sel.ranges[i], heads[i], null); + var newSel = normalizeSelection(out, doc.sel.primIndex); + setSelection(doc, newSel, options); + } + + // Updates a single range in the selection. + function replaceOneSelection(doc, i, range, options) { + var ranges = doc.sel.ranges.slice(0); + ranges[i] = range; + setSelection(doc, normalizeSelection(ranges, doc.sel.primIndex), options); + } + + // Reset the selection to a single range. + function setSimpleSelection(doc, anchor, head, options) { + setSelection(doc, simpleSelection(anchor, head), options); + } + + // Give beforeSelectionChange handlers a change to influence a + // selection update. + function filterSelectionChange(doc, sel) { + var obj = { + ranges: sel.ranges, + update: function(ranges) { + this.ranges = []; + for (var i = 0; i < ranges.length; i++) + this.ranges[i] = new Range(clipPos(doc, ranges[i].anchor), + clipPos(doc, ranges[i].head)); + } + }; + signal(doc, "beforeSelectionChange", doc, obj); + if (doc.cm) signal(doc.cm, "beforeSelectionChange", doc.cm, obj); + if (obj.ranges != sel.ranges) return normalizeSelection(obj.ranges, obj.ranges.length - 1); + else return sel; + } + + function setSelectionReplaceHistory(doc, sel, options) { + var done = doc.history.done, last = lst(done); + if (last && last.ranges) { + done[done.length - 1] = sel; + setSelectionNoUndo(doc, sel, options); + } else { + setSelection(doc, sel, options); + } + } + + // Set a new selection. + function setSelection(doc, sel, options) { + setSelectionNoUndo(doc, sel, options); + addSelectionToHistory(doc, doc.sel, doc.cm ? doc.cm.curOp.id : NaN, options); + } + + function setSelectionNoUndo(doc, sel, options) { + if (hasHandler(doc, "beforeSelectionChange") || doc.cm && hasHandler(doc.cm, "beforeSelectionChange")) + sel = filterSelectionChange(doc, sel); + + var bias = options && options.bias || + (cmp(sel.primary().head, doc.sel.primary().head) < 0 ? -1 : 1); + setSelectionInner(doc, skipAtomicInSelection(doc, sel, bias, true)); + + if (!(options && options.scroll === false) && doc.cm) + ensureCursorVisible(doc.cm); + } + + function setSelectionInner(doc, sel) { + if (sel.equals(doc.sel)) return; + + doc.sel = sel; + + if (doc.cm) { + doc.cm.curOp.updateInput = doc.cm.curOp.selectionChanged = true; + signalCursorActivity(doc.cm); + } + signalLater(doc, "cursorActivity", doc); + } + + // Verify that the selection does not partially select any atomic + // marked ranges. + function reCheckSelection(doc) { + setSelectionInner(doc, skipAtomicInSelection(doc, doc.sel, null, false), sel_dontScroll); + } + + // Return a selection that does not partially select any atomic + // ranges. + function skipAtomicInSelection(doc, sel, bias, mayClear) { + var out; + for (var i = 0; i < sel.ranges.length; i++) { + var range = sel.ranges[i]; + var newAnchor = skipAtomic(doc, range.anchor, bias, mayClear); + var newHead = skipAtomic(doc, range.head, bias, mayClear); + if (out || newAnchor != range.anchor || newHead != range.head) { + if (!out) out = sel.ranges.slice(0, i); + out[i] = new Range(newAnchor, newHead); + } + } + return out ? normalizeSelection(out, sel.primIndex) : sel; + } + + // Ensure a given position is not inside an atomic range. + function skipAtomic(doc, pos, bias, mayClear) { + var flipped = false, curPos = pos; + var dir = bias || 1; + doc.cantEdit = false; + search: for (;;) { + var line = getLine(doc, curPos.line); + if (line.markedSpans) { + for (var i = 0; i < line.markedSpans.length; ++i) { + var sp = line.markedSpans[i], m = sp.marker; + if ((sp.from == null || (m.inclusiveLeft ? sp.from <= curPos.ch : sp.from < curPos.ch)) && + (sp.to == null || (m.inclusiveRight ? sp.to >= curPos.ch : sp.to > curPos.ch))) { + if (mayClear) { + signal(m, "beforeCursorEnter"); + if (m.explicitlyCleared) { + if (!line.markedSpans) break; + else {--i; continue;} + } + } + if (!m.atomic) continue; + var newPos = m.find(dir < 0 ? -1 : 1); + if (cmp(newPos, curPos) == 0) { + newPos.ch += dir; + if (newPos.ch < 0) { + if (newPos.line > doc.first) newPos = clipPos(doc, Pos(newPos.line - 1)); + else newPos = null; + } else if (newPos.ch > line.text.length) { + if (newPos.line < doc.first + doc.size - 1) newPos = Pos(newPos.line + 1, 0); + else newPos = null; + } + if (!newPos) { + if (flipped) { + // Driven in a corner -- no valid cursor position found at all + // -- try again *with* clearing, if we didn't already + if (!mayClear) return skipAtomic(doc, pos, bias, true); + // Otherwise, turn off editing until further notice, and return the start of the doc + doc.cantEdit = true; + return Pos(doc.first, 0); + } + flipped = true; newPos = pos; dir = -dir; + } + } + curPos = newPos; + continue search; + } + } + } + return curPos; + } + } + + // SELECTION DRAWING + + function updateSelection(cm) { + cm.display.input.showSelection(cm.display.input.prepareSelection()); + } + + function prepareSelection(cm, primary) { + var doc = cm.doc, result = {}; + var curFragment = result.cursors = document.createDocumentFragment(); + var selFragment = result.selection = document.createDocumentFragment(); + + for (var i = 0; i < doc.sel.ranges.length; i++) { + if (primary === false && i == doc.sel.primIndex) continue; + var range = doc.sel.ranges[i]; + var collapsed = range.empty(); + if (collapsed || cm.options.showCursorWhenSelecting) + drawSelectionCursor(cm, range, curFragment); + if (!collapsed) + drawSelectionRange(cm, range, selFragment); + } + return result; + } + + // Draws a cursor for the given range + function drawSelectionCursor(cm, range, output) { + var pos = cursorCoords(cm, range.head, "div", null, null, !cm.options.singleCursorHeightPerLine); + + var cursor = output.appendChild(elt("div", "\u00a0", "CodeMirror-cursor")); + cursor.style.left = pos.left + "px"; + cursor.style.top = pos.top + "px"; + cursor.style.height = Math.max(0, pos.bottom - pos.top) * cm.options.cursorHeight + "px"; + + if (pos.other) { + // Secondary cursor, shown when on a 'jump' in bi-directional text + var otherCursor = output.appendChild(elt("div", "\u00a0", "CodeMirror-cursor CodeMirror-secondarycursor")); + otherCursor.style.display = ""; + otherCursor.style.left = pos.other.left + "px"; + otherCursor.style.top = pos.other.top + "px"; + otherCursor.style.height = (pos.other.bottom - pos.other.top) * .85 + "px"; + } + } + + // Draws the given range as a highlighted selection + function drawSelectionRange(cm, range, output) { + var display = cm.display, doc = cm.doc; + var fragment = document.createDocumentFragment(); + var padding = paddingH(cm.display), leftSide = padding.left; + var rightSide = Math.max(display.sizerWidth, displayWidth(cm) - display.sizer.offsetLeft) - padding.right; + + function add(left, top, width, bottom) { + if (top < 0) top = 0; + top = Math.round(top); + bottom = Math.round(bottom); + fragment.appendChild(elt("div", null, "CodeMirror-selected", "position: absolute; left: " + left + + "px; top: " + top + "px; width: " + (width == null ? rightSide - left : width) + + "px; height: " + (bottom - top) + "px")); + } + + function drawForLine(line, fromArg, toArg) { + var lineObj = getLine(doc, line); + var lineLen = lineObj.text.length; + var start, end; + function coords(ch, bias) { + return charCoords(cm, Pos(line, ch), "div", lineObj, bias); + } + + iterateBidiSections(getOrder(lineObj), fromArg || 0, toArg == null ? lineLen : toArg, function(from, to, dir) { + var leftPos = coords(from, "left"), rightPos, left, right; + if (from == to) { + rightPos = leftPos; + left = right = leftPos.left; + } else { + rightPos = coords(to - 1, "right"); + if (dir == "rtl") { var tmp = leftPos; leftPos = rightPos; rightPos = tmp; } + left = leftPos.left; + right = rightPos.right; + } + if (fromArg == null && from == 0) left = leftSide; + if (rightPos.top - leftPos.top > 3) { // Different lines, draw top part + add(left, leftPos.top, null, leftPos.bottom); + left = leftSide; + if (leftPos.bottom < rightPos.top) add(left, leftPos.bottom, null, rightPos.top); + } + if (toArg == null && to == lineLen) right = rightSide; + if (!start || leftPos.top < start.top || leftPos.top == start.top && leftPos.left < start.left) + start = leftPos; + if (!end || rightPos.bottom > end.bottom || rightPos.bottom == end.bottom && rightPos.right > end.right) + end = rightPos; + if (left < leftSide + 1) left = leftSide; + add(left, rightPos.top, right - left, rightPos.bottom); + }); + return {start: start, end: end}; + } + + var sFrom = range.from(), sTo = range.to(); + if (sFrom.line == sTo.line) { + drawForLine(sFrom.line, sFrom.ch, sTo.ch); + } else { + var fromLine = getLine(doc, sFrom.line), toLine = getLine(doc, sTo.line); + var singleVLine = visualLine(fromLine) == visualLine(toLine); + var leftEnd = drawForLine(sFrom.line, sFrom.ch, singleVLine ? fromLine.text.length + 1 : null).end; + var rightStart = drawForLine(sTo.line, singleVLine ? 0 : null, sTo.ch).start; + if (singleVLine) { + if (leftEnd.top < rightStart.top - 2) { + add(leftEnd.right, leftEnd.top, null, leftEnd.bottom); + add(leftSide, rightStart.top, rightStart.left, rightStart.bottom); + } else { + add(leftEnd.right, leftEnd.top, rightStart.left - leftEnd.right, leftEnd.bottom); + } + } + if (leftEnd.bottom < rightStart.top) + add(leftSide, leftEnd.bottom, null, rightStart.top); + } + + output.appendChild(fragment); + } + + // Cursor-blinking + function restartBlink(cm) { + if (!cm.state.focused) return; + var display = cm.display; + clearInterval(display.blinker); + var on = true; + display.cursorDiv.style.visibility = ""; + if (cm.options.cursorBlinkRate > 0) + display.blinker = setInterval(function() { + display.cursorDiv.style.visibility = (on = !on) ? "" : "hidden"; + }, cm.options.cursorBlinkRate); + else if (cm.options.cursorBlinkRate < 0) + display.cursorDiv.style.visibility = "hidden"; + } + + // HIGHLIGHT WORKER + + function startWorker(cm, time) { + if (cm.doc.mode.startState && cm.doc.frontier < cm.display.viewTo) + cm.state.highlight.set(time, bind(highlightWorker, cm)); + } + + function highlightWorker(cm) { + var doc = cm.doc; + if (doc.frontier < doc.first) doc.frontier = doc.first; + if (doc.frontier >= cm.display.viewTo) return; + var end = +new Date + cm.options.workTime; + var state = copyState(doc.mode, getStateBefore(cm, doc.frontier)); + var changedLines = []; + + doc.iter(doc.frontier, Math.min(doc.first + doc.size, cm.display.viewTo + 500), function(line) { + if (doc.frontier >= cm.display.viewFrom) { // Visible + var oldStyles = line.styles; + var highlighted = highlightLine(cm, line, state, true); + line.styles = highlighted.styles; + var oldCls = line.styleClasses, newCls = highlighted.classes; + if (newCls) line.styleClasses = newCls; + else if (oldCls) line.styleClasses = null; + var ischange = !oldStyles || oldStyles.length != line.styles.length || + oldCls != newCls && (!oldCls || !newCls || oldCls.bgClass != newCls.bgClass || oldCls.textClass != newCls.textClass); + for (var i = 0; !ischange && i < oldStyles.length; ++i) ischange = oldStyles[i] != line.styles[i]; + if (ischange) changedLines.push(doc.frontier); + line.stateAfter = copyState(doc.mode, state); + } else { + processLine(cm, line.text, state); + line.stateAfter = doc.frontier % 5 == 0 ? copyState(doc.mode, state) : null; + } + ++doc.frontier; + if (+new Date > end) { + startWorker(cm, cm.options.workDelay); + return true; + } + }); + if (changedLines.length) runInOp(cm, function() { + for (var i = 0; i < changedLines.length; i++) + regLineChange(cm, changedLines[i], "text"); + }); + } + + // Finds the line to start with when starting a parse. Tries to + // find a line with a stateAfter, so that it can start with a + // valid state. If that fails, it returns the line with the + // smallest indentation, which tends to need the least context to + // parse correctly. + function findStartLine(cm, n, precise) { + var minindent, minline, doc = cm.doc; + var lim = precise ? -1 : n - (cm.doc.mode.innerMode ? 1000 : 100); + for (var search = n; search > lim; --search) { + if (search <= doc.first) return doc.first; + var line = getLine(doc, search - 1); + if (line.stateAfter && (!precise || search <= doc.frontier)) return search; + var indented = countColumn(line.text, null, cm.options.tabSize); + if (minline == null || minindent > indented) { + minline = search - 1; + minindent = indented; + } + } + return minline; + } + + function getStateBefore(cm, n, precise) { + var doc = cm.doc, display = cm.display; + if (!doc.mode.startState) return true; + var pos = findStartLine(cm, n, precise), state = pos > doc.first && getLine(doc, pos-1).stateAfter; + if (!state) state = startState(doc.mode); + else state = copyState(doc.mode, state); + doc.iter(pos, n, function(line) { + processLine(cm, line.text, state); + var save = pos == n - 1 || pos % 5 == 0 || pos >= display.viewFrom && pos < display.viewTo; + line.stateAfter = save ? copyState(doc.mode, state) : null; + ++pos; + }); + if (precise) doc.frontier = pos; + return state; + } + + // POSITION MEASUREMENT + + function paddingTop(display) {return display.lineSpace.offsetTop;} + function paddingVert(display) {return display.mover.offsetHeight - display.lineSpace.offsetHeight;} + function paddingH(display) { + if (display.cachedPaddingH) return display.cachedPaddingH; + var e = removeChildrenAndAdd(display.measure, elt("pre", "x")); + var style = window.getComputedStyle ? window.getComputedStyle(e) : e.currentStyle; + var data = {left: parseInt(style.paddingLeft), right: parseInt(style.paddingRight)}; + if (!isNaN(data.left) && !isNaN(data.right)) display.cachedPaddingH = data; + return data; + } + + function scrollGap(cm) { return scrollerGap - cm.display.nativeBarWidth; } + function displayWidth(cm) { + return cm.display.scroller.clientWidth - scrollGap(cm) - cm.display.barWidth; + } + function displayHeight(cm) { + return cm.display.scroller.clientHeight - scrollGap(cm) - cm.display.barHeight; + } + + // Ensure the lineView.wrapping.heights array is populated. This is + // an array of bottom offsets for the lines that make up a drawn + // line. When lineWrapping is on, there might be more than one + // height. + function ensureLineHeights(cm, lineView, rect) { + var wrapping = cm.options.lineWrapping; + var curWidth = wrapping && displayWidth(cm); + if (!lineView.measure.heights || wrapping && lineView.measure.width != curWidth) { + var heights = lineView.measure.heights = []; + if (wrapping) { + lineView.measure.width = curWidth; + var rects = lineView.text.firstChild.getClientRects(); + for (var i = 0; i < rects.length - 1; i++) { + var cur = rects[i], next = rects[i + 1]; + if (Math.abs(cur.bottom - next.bottom) > 2) + heights.push((cur.bottom + next.top) / 2 - rect.top); + } + } + heights.push(rect.bottom - rect.top); + } + } + + // Find a line map (mapping character offsets to text nodes) and a + // measurement cache for the given line number. (A line view might + // contain multiple lines when collapsed ranges are present.) + function mapFromLineView(lineView, line, lineN) { + if (lineView.line == line) + return {map: lineView.measure.map, cache: lineView.measure.cache}; + for (var i = 0; i < lineView.rest.length; i++) + if (lineView.rest[i] == line) + return {map: lineView.measure.maps[i], cache: lineView.measure.caches[i]}; + for (var i = 0; i < lineView.rest.length; i++) + if (lineNo(lineView.rest[i]) > lineN) + return {map: lineView.measure.maps[i], cache: lineView.measure.caches[i], before: true}; + } + + // Render a line into the hidden node display.externalMeasured. Used + // when measurement is needed for a line that's not in the viewport. + function updateExternalMeasurement(cm, line) { + line = visualLine(line); + var lineN = lineNo(line); + var view = cm.display.externalMeasured = new LineView(cm.doc, line, lineN); + view.lineN = lineN; + var built = view.built = buildLineContent(cm, view); + view.text = built.pre; + removeChildrenAndAdd(cm.display.lineMeasure, built.pre); + return view; + } + + // Get a {top, bottom, left, right} box (in line-local coordinates) + // for a given character. + function measureChar(cm, line, ch, bias) { + return measureCharPrepared(cm, prepareMeasureForLine(cm, line), ch, bias); + } + + // Find a line view that corresponds to the given line number. + function findViewForLine(cm, lineN) { + if (lineN >= cm.display.viewFrom && lineN < cm.display.viewTo) + return cm.display.view[findViewIndex(cm, lineN)]; + var ext = cm.display.externalMeasured; + if (ext && lineN >= ext.lineN && lineN < ext.lineN + ext.size) + return ext; + } + + // Measurement can be split in two steps, the set-up work that + // applies to the whole line, and the measurement of the actual + // character. Functions like coordsChar, that need to do a lot of + // measurements in a row, can thus ensure that the set-up work is + // only done once. + function prepareMeasureForLine(cm, line) { + var lineN = lineNo(line); + var view = findViewForLine(cm, lineN); + if (view && !view.text) + view = null; + else if (view && view.changes) + updateLineForChanges(cm, view, lineN, getDimensions(cm)); + if (!view) + view = updateExternalMeasurement(cm, line); + + var info = mapFromLineView(view, line, lineN); + return { + line: line, view: view, rect: null, + map: info.map, cache: info.cache, before: info.before, + hasHeights: false + }; + } + + // Given a prepared measurement object, measures the position of an + // actual character (or fetches it from the cache). + function measureCharPrepared(cm, prepared, ch, bias, varHeight) { + if (prepared.before) ch = -1; + var key = ch + (bias || ""), found; + if (prepared.cache.hasOwnProperty(key)) { + found = prepared.cache[key]; + } else { + if (!prepared.rect) + prepared.rect = prepared.view.text.getBoundingClientRect(); + if (!prepared.hasHeights) { + ensureLineHeights(cm, prepared.view, prepared.rect); + prepared.hasHeights = true; + } + found = measureCharInner(cm, prepared, ch, bias); + if (!found.bogus) prepared.cache[key] = found; + } + return {left: found.left, right: found.right, + top: varHeight ? found.rtop : found.top, + bottom: varHeight ? found.rbottom : found.bottom}; + } + + var nullRect = {left: 0, right: 0, top: 0, bottom: 0}; + + function nodeAndOffsetInLineMap(map, ch, bias) { + var node, start, end, collapse; + // First, search the line map for the text node corresponding to, + // or closest to, the target character. + for (var i = 0; i < map.length; i += 3) { + var mStart = map[i], mEnd = map[i + 1]; + if (ch < mStart) { + start = 0; end = 1; + collapse = "left"; + } else if (ch < mEnd) { + start = ch - mStart; + end = start + 1; + } else if (i == map.length - 3 || ch == mEnd && map[i + 3] > ch) { + end = mEnd - mStart; + start = end - 1; + if (ch >= mEnd) collapse = "right"; + } + if (start != null) { + node = map[i + 2]; + if (mStart == mEnd && bias == (node.insertLeft ? "left" : "right")) + collapse = bias; + if (bias == "left" && start == 0) + while (i && map[i - 2] == map[i - 3] && map[i - 1].insertLeft) { + node = map[(i -= 3) + 2]; + collapse = "left"; + } + if (bias == "right" && start == mEnd - mStart) + while (i < map.length - 3 && map[i + 3] == map[i + 4] && !map[i + 5].insertLeft) { + node = map[(i += 3) + 2]; + collapse = "right"; + } + break; + } + } + return {node: node, start: start, end: end, collapse: collapse, coverStart: mStart, coverEnd: mEnd}; + } + + function measureCharInner(cm, prepared, ch, bias) { + var place = nodeAndOffsetInLineMap(prepared.map, ch, bias); + var node = place.node, start = place.start, end = place.end, collapse = place.collapse; + + var rect; + if (node.nodeType == 3) { // If it is a text node, use a range to retrieve the coordinates. + for (var i = 0; i < 4; i++) { // Retry a maximum of 4 times when nonsense rectangles are returned + while (start && isExtendingChar(prepared.line.text.charAt(place.coverStart + start))) --start; + while (place.coverStart + end < place.coverEnd && isExtendingChar(prepared.line.text.charAt(place.coverStart + end))) ++end; + if (ie && ie_version < 9 && start == 0 && end == place.coverEnd - place.coverStart) { + rect = node.parentNode.getBoundingClientRect(); + } else if (ie && cm.options.lineWrapping) { + var rects = range(node, start, end).getClientRects(); + if (rects.length) + rect = rects[bias == "right" ? rects.length - 1 : 0]; + else + rect = nullRect; + } else { + rect = range(node, start, end).getBoundingClientRect() || nullRect; + } + if (rect.left || rect.right || start == 0) break; + end = start; + start = start - 1; + collapse = "right"; + } + if (ie && ie_version < 11) rect = maybeUpdateRectForZooming(cm.display.measure, rect); + } else { // If it is a widget, simply get the box for the whole widget. + if (start > 0) collapse = bias = "right"; + var rects; + if (cm.options.lineWrapping && (rects = node.getClientRects()).length > 1) + rect = rects[bias == "right" ? rects.length - 1 : 0]; + else + rect = node.getBoundingClientRect(); + } + if (ie && ie_version < 9 && !start && (!rect || !rect.left && !rect.right)) { + var rSpan = node.parentNode.getClientRects()[0]; + if (rSpan) + rect = {left: rSpan.left, right: rSpan.left + charWidth(cm.display), top: rSpan.top, bottom: rSpan.bottom}; + else + rect = nullRect; + } + + var rtop = rect.top - prepared.rect.top, rbot = rect.bottom - prepared.rect.top; + var mid = (rtop + rbot) / 2; + var heights = prepared.view.measure.heights; + for (var i = 0; i < heights.length - 1; i++) + if (mid < heights[i]) break; + var top = i ? heights[i - 1] : 0, bot = heights[i]; + var result = {left: (collapse == "right" ? rect.right : rect.left) - prepared.rect.left, + right: (collapse == "left" ? rect.left : rect.right) - prepared.rect.left, + top: top, bottom: bot}; + if (!rect.left && !rect.right) result.bogus = true; + if (!cm.options.singleCursorHeightPerLine) { result.rtop = rtop; result.rbottom = rbot; } + + return result; + } + + // Work around problem with bounding client rects on ranges being + // returned incorrectly when zoomed on IE10 and below. + function maybeUpdateRectForZooming(measure, rect) { + if (!window.screen || screen.logicalXDPI == null || + screen.logicalXDPI == screen.deviceXDPI || !hasBadZoomedRects(measure)) + return rect; + var scaleX = screen.logicalXDPI / screen.deviceXDPI; + var scaleY = screen.logicalYDPI / screen.deviceYDPI; + return {left: rect.left * scaleX, right: rect.right * scaleX, + top: rect.top * scaleY, bottom: rect.bottom * scaleY}; + } + + function clearLineMeasurementCacheFor(lineView) { + if (lineView.measure) { + lineView.measure.cache = {}; + lineView.measure.heights = null; + if (lineView.rest) for (var i = 0; i < lineView.rest.length; i++) + lineView.measure.caches[i] = {}; + } + } + + function clearLineMeasurementCache(cm) { + cm.display.externalMeasure = null; + removeChildren(cm.display.lineMeasure); + for (var i = 0; i < cm.display.view.length; i++) + clearLineMeasurementCacheFor(cm.display.view[i]); + } + + function clearCaches(cm) { + clearLineMeasurementCache(cm); + cm.display.cachedCharWidth = cm.display.cachedTextHeight = cm.display.cachedPaddingH = null; + if (!cm.options.lineWrapping) cm.display.maxLineChanged = true; + cm.display.lineNumChars = null; + } + + function pageScrollX() { return window.pageXOffset || (document.documentElement || document.body).scrollLeft; } + function pageScrollY() { return window.pageYOffset || (document.documentElement || document.body).scrollTop; } + + // Converts a {top, bottom, left, right} box from line-local + // coordinates into another coordinate system. Context may be one of + // "line", "div" (display.lineDiv), "local"/null (editor), "window", + // or "page". + function intoCoordSystem(cm, lineObj, rect, context) { + if (lineObj.widgets) for (var i = 0; i < lineObj.widgets.length; ++i) if (lineObj.widgets[i].above) { + var size = widgetHeight(lineObj.widgets[i]); + rect.top += size; rect.bottom += size; + } + if (context == "line") return rect; + if (!context) context = "local"; + var yOff = heightAtLine(lineObj); + if (context == "local") yOff += paddingTop(cm.display); + else yOff -= cm.display.viewOffset; + if (context == "page" || context == "window") { + var lOff = cm.display.lineSpace.getBoundingClientRect(); + yOff += lOff.top + (context == "window" ? 0 : pageScrollY()); + var xOff = lOff.left + (context == "window" ? 0 : pageScrollX()); + rect.left += xOff; rect.right += xOff; + } + rect.top += yOff; rect.bottom += yOff; + return rect; + } + + // Coverts a box from "div" coords to another coordinate system. + // Context may be "window", "page", "div", or "local"/null. + function fromCoordSystem(cm, coords, context) { + if (context == "div") return coords; + var left = coords.left, top = coords.top; + // First move into "page" coordinate system + if (context == "page") { + left -= pageScrollX(); + top -= pageScrollY(); + } else if (context == "local" || !context) { + var localBox = cm.display.sizer.getBoundingClientRect(); + left += localBox.left; + top += localBox.top; + } + + var lineSpaceBox = cm.display.lineSpace.getBoundingClientRect(); + return {left: left - lineSpaceBox.left, top: top - lineSpaceBox.top}; + } + + function charCoords(cm, pos, context, lineObj, bias) { + if (!lineObj) lineObj = getLine(cm.doc, pos.line); + return intoCoordSystem(cm, lineObj, measureChar(cm, lineObj, pos.ch, bias), context); + } + + // Returns a box for a given cursor position, which may have an + // 'other' property containing the position of the secondary cursor + // on a bidi boundary. + function cursorCoords(cm, pos, context, lineObj, preparedMeasure, varHeight) { + lineObj = lineObj || getLine(cm.doc, pos.line); + if (!preparedMeasure) preparedMeasure = prepareMeasureForLine(cm, lineObj); + function get(ch, right) { + var m = measureCharPrepared(cm, preparedMeasure, ch, right ? "right" : "left", varHeight); + if (right) m.left = m.right; else m.right = m.left; + return intoCoordSystem(cm, lineObj, m, context); + } + function getBidi(ch, partPos) { + var part = order[partPos], right = part.level % 2; + if (ch == bidiLeft(part) && partPos && part.level < order[partPos - 1].level) { + part = order[--partPos]; + ch = bidiRight(part) - (part.level % 2 ? 0 : 1); + right = true; + } else if (ch == bidiRight(part) && partPos < order.length - 1 && part.level < order[partPos + 1].level) { + part = order[++partPos]; + ch = bidiLeft(part) - part.level % 2; + right = false; + } + if (right && ch == part.to && ch > part.from) return get(ch - 1); + return get(ch, right); + } + var order = getOrder(lineObj), ch = pos.ch; + if (!order) return get(ch); + var partPos = getBidiPartAt(order, ch); + var val = getBidi(ch, partPos); + if (bidiOther != null) val.other = getBidi(ch, bidiOther); + return val; + } + + // Used to cheaply estimate the coordinates for a position. Used for + // intermediate scroll updates. + function estimateCoords(cm, pos) { + var left = 0, pos = clipPos(cm.doc, pos); + if (!cm.options.lineWrapping) left = charWidth(cm.display) * pos.ch; + var lineObj = getLine(cm.doc, pos.line); + var top = heightAtLine(lineObj) + paddingTop(cm.display); + return {left: left, right: left, top: top, bottom: top + lineObj.height}; + } + + // Positions returned by coordsChar contain some extra information. + // xRel is the relative x position of the input coordinates compared + // to the found position (so xRel > 0 means the coordinates are to + // the right of the character position, for example). When outside + // is true, that means the coordinates lie outside the line's + // vertical range. + function PosWithInfo(line, ch, outside, xRel) { + var pos = Pos(line, ch); + pos.xRel = xRel; + if (outside) pos.outside = true; + return pos; + } + + // Compute the character position closest to the given coordinates. + // Input must be lineSpace-local ("div" coordinate system). + function coordsChar(cm, x, y) { + var doc = cm.doc; + y += cm.display.viewOffset; + if (y < 0) return PosWithInfo(doc.first, 0, true, -1); + var lineN = lineAtHeight(doc, y), last = doc.first + doc.size - 1; + if (lineN > last) + return PosWithInfo(doc.first + doc.size - 1, getLine(doc, last).text.length, true, 1); + if (x < 0) x = 0; + + var lineObj = getLine(doc, lineN); + for (;;) { + var found = coordsCharInner(cm, lineObj, lineN, x, y); + var merged = collapsedSpanAtEnd(lineObj); + var mergedPos = merged && merged.find(0, true); + if (merged && (found.ch > mergedPos.from.ch || found.ch == mergedPos.from.ch && found.xRel > 0)) + lineN = lineNo(lineObj = mergedPos.to.line); + else + return found; + } + } + + function coordsCharInner(cm, lineObj, lineNo, x, y) { + var innerOff = y - heightAtLine(lineObj); + var wrongLine = false, adjust = 2 * cm.display.wrapper.clientWidth; + var preparedMeasure = prepareMeasureForLine(cm, lineObj); + + function getX(ch) { + var sp = cursorCoords(cm, Pos(lineNo, ch), "line", lineObj, preparedMeasure); + wrongLine = true; + if (innerOff > sp.bottom) return sp.left - adjust; + else if (innerOff < sp.top) return sp.left + adjust; + else wrongLine = false; + return sp.left; + } + + var bidi = getOrder(lineObj), dist = lineObj.text.length; + var from = lineLeft(lineObj), to = lineRight(lineObj); + var fromX = getX(from), fromOutside = wrongLine, toX = getX(to), toOutside = wrongLine; + + if (x > toX) return PosWithInfo(lineNo, to, toOutside, 1); + // Do a binary search between these bounds. + for (;;) { + if (bidi ? to == from || to == moveVisually(lineObj, from, 1) : to - from <= 1) { + var ch = x < fromX || x - fromX <= toX - x ? from : to; + var xDiff = x - (ch == from ? fromX : toX); + while (isExtendingChar(lineObj.text.charAt(ch))) ++ch; + var pos = PosWithInfo(lineNo, ch, ch == from ? fromOutside : toOutside, + xDiff < -1 ? -1 : xDiff > 1 ? 1 : 0); + return pos; + } + var step = Math.ceil(dist / 2), middle = from + step; + if (bidi) { + middle = from; + for (var i = 0; i < step; ++i) middle = moveVisually(lineObj, middle, 1); + } + var middleX = getX(middle); + if (middleX > x) {to = middle; toX = middleX; if (toOutside = wrongLine) toX += 1000; dist = step;} + else {from = middle; fromX = middleX; fromOutside = wrongLine; dist -= step;} + } + } + + var measureText; + // Compute the default text height. + function textHeight(display) { + if (display.cachedTextHeight != null) return display.cachedTextHeight; + if (measureText == null) { + measureText = elt("pre"); + // Measure a bunch of lines, for browsers that compute + // fractional heights. + for (var i = 0; i < 49; ++i) { + measureText.appendChild(document.createTextNode("x")); + measureText.appendChild(elt("br")); + } + measureText.appendChild(document.createTextNode("x")); + } + removeChildrenAndAdd(display.measure, measureText); + var height = measureText.offsetHeight / 50; + if (height > 3) display.cachedTextHeight = height; + removeChildren(display.measure); + return height || 1; + } + + // Compute the default character width. + function charWidth(display) { + if (display.cachedCharWidth != null) return display.cachedCharWidth; + var anchor = elt("span", "xxxxxxxxxx"); + var pre = elt("pre", [anchor]); + removeChildrenAndAdd(display.measure, pre); + var rect = anchor.getBoundingClientRect(), width = (rect.right - rect.left) / 10; + if (width > 2) display.cachedCharWidth = width; + return width || 10; + } + + // OPERATIONS + + // Operations are used to wrap a series of changes to the editor + // state in such a way that each change won't have to update the + // cursor and display (which would be awkward, slow, and + // error-prone). Instead, display updates are batched and then all + // combined and executed at once. + + var operationGroup = null; + + var nextOpId = 0; + // Start a new operation. + function startOperation(cm) { + cm.curOp = { + cm: cm, + viewChanged: false, // Flag that indicates that lines might need to be redrawn + startHeight: cm.doc.height, // Used to detect need to update scrollbar + forceUpdate: false, // Used to force a redraw + updateInput: null, // Whether to reset the input textarea + typing: false, // Whether this reset should be careful to leave existing text (for compositing) + changeObjs: null, // Accumulated changes, for firing change events + cursorActivityHandlers: null, // Set of handlers to fire cursorActivity on + cursorActivityCalled: 0, // Tracks which cursorActivity handlers have been called already + selectionChanged: false, // Whether the selection needs to be redrawn + updateMaxLine: false, // Set when the widest line needs to be determined anew + scrollLeft: null, scrollTop: null, // Intermediate scroll position, not pushed to DOM yet + scrollToPos: null, // Used to scroll to a specific position + focus: false, + id: ++nextOpId // Unique ID + }; + if (operationGroup) { + operationGroup.ops.push(cm.curOp); + } else { + cm.curOp.ownsGroup = operationGroup = { + ops: [cm.curOp], + delayedCallbacks: [] + }; + } + } + + function fireCallbacksForOps(group) { + // Calls delayed callbacks and cursorActivity handlers until no + // new ones appear + var callbacks = group.delayedCallbacks, i = 0; + do { + for (; i < callbacks.length; i++) + callbacks[i](); + for (var j = 0; j < group.ops.length; j++) { + var op = group.ops[j]; + if (op.cursorActivityHandlers) + while (op.cursorActivityCalled < op.cursorActivityHandlers.length) + op.cursorActivityHandlers[op.cursorActivityCalled++](op.cm); + } + } while (i < callbacks.length); + } + + // Finish an operation, updating the display and signalling delayed events + function endOperation(cm) { + var op = cm.curOp, group = op.ownsGroup; + if (!group) return; + + try { fireCallbacksForOps(group); } + finally { + operationGroup = null; + for (var i = 0; i < group.ops.length; i++) + group.ops[i].cm.curOp = null; + endOperations(group); + } + } + + // The DOM updates done when an operation finishes are batched so + // that the minimum number of relayouts are required. + function endOperations(group) { + var ops = group.ops; + for (var i = 0; i < ops.length; i++) // Read DOM + endOperation_R1(ops[i]); + for (var i = 0; i < ops.length; i++) // Write DOM (maybe) + endOperation_W1(ops[i]); + for (var i = 0; i < ops.length; i++) // Read DOM + endOperation_R2(ops[i]); + for (var i = 0; i < ops.length; i++) // Write DOM (maybe) + endOperation_W2(ops[i]); + for (var i = 0; i < ops.length; i++) // Read DOM + endOperation_finish(ops[i]); + } + + function endOperation_R1(op) { + var cm = op.cm, display = cm.display; + maybeClipScrollbars(cm); + if (op.updateMaxLine) findMaxLine(cm); + + op.mustUpdate = op.viewChanged || op.forceUpdate || op.scrollTop != null || + op.scrollToPos && (op.scrollToPos.from.line < display.viewFrom || + op.scrollToPos.to.line >= display.viewTo) || + display.maxLineChanged && cm.options.lineWrapping; + op.update = op.mustUpdate && + new DisplayUpdate(cm, op.mustUpdate && {top: op.scrollTop, ensure: op.scrollToPos}, op.forceUpdate); + } + + function endOperation_W1(op) { + op.updatedDisplay = op.mustUpdate && updateDisplayIfNeeded(op.cm, op.update); + } + + function endOperation_R2(op) { + var cm = op.cm, display = cm.display; + if (op.updatedDisplay) updateHeightsInViewport(cm); + + op.barMeasure = measureForScrollbars(cm); + + // If the max line changed since it was last measured, measure it, + // and ensure the document's width matches it. + // updateDisplay_W2 will use these properties to do the actual resizing + if (display.maxLineChanged && !cm.options.lineWrapping) { + op.adjustWidthTo = measureChar(cm, display.maxLine, display.maxLine.text.length).left + 3; + cm.display.sizerWidth = op.adjustWidthTo; + op.barMeasure.scrollWidth = + Math.max(display.scroller.clientWidth, display.sizer.offsetLeft + op.adjustWidthTo + scrollGap(cm) + cm.display.barWidth); + op.maxScrollLeft = Math.max(0, display.sizer.offsetLeft + op.adjustWidthTo - displayWidth(cm)); + } + + if (op.updatedDisplay || op.selectionChanged) + op.preparedSelection = display.input.prepareSelection(); + } + + function endOperation_W2(op) { + var cm = op.cm; + + if (op.adjustWidthTo != null) { + cm.display.sizer.style.minWidth = op.adjustWidthTo + "px"; + if (op.maxScrollLeft < cm.doc.scrollLeft) + setScrollLeft(cm, Math.min(cm.display.scroller.scrollLeft, op.maxScrollLeft), true); + cm.display.maxLineChanged = false; + } + + if (op.preparedSelection) + cm.display.input.showSelection(op.preparedSelection); + if (op.updatedDisplay) + setDocumentHeight(cm, op.barMeasure); + if (op.updatedDisplay || op.startHeight != cm.doc.height) + updateScrollbars(cm, op.barMeasure); + + if (op.selectionChanged) restartBlink(cm); + + if (cm.state.focused && op.updateInput) + cm.display.input.reset(op.typing); + if (op.focus && op.focus == activeElt()) ensureFocus(op.cm); + } + + function endOperation_finish(op) { + var cm = op.cm, display = cm.display, doc = cm.doc; + + if (op.updatedDisplay) postUpdateDisplay(cm, op.update); + + // Abort mouse wheel delta measurement, when scrolling explicitly + if (display.wheelStartX != null && (op.scrollTop != null || op.scrollLeft != null || op.scrollToPos)) + display.wheelStartX = display.wheelStartY = null; + + // Propagate the scroll position to the actual DOM scroller + if (op.scrollTop != null && (display.scroller.scrollTop != op.scrollTop || op.forceScroll)) { + doc.scrollTop = Math.max(0, Math.min(display.scroller.scrollHeight - display.scroller.clientHeight, op.scrollTop)); + display.scrollbars.setScrollTop(doc.scrollTop); + display.scroller.scrollTop = doc.scrollTop; + } + if (op.scrollLeft != null && (display.scroller.scrollLeft != op.scrollLeft || op.forceScroll)) { + doc.scrollLeft = Math.max(0, Math.min(display.scroller.scrollWidth - displayWidth(cm), op.scrollLeft)); + display.scrollbars.setScrollLeft(doc.scrollLeft); + display.scroller.scrollLeft = doc.scrollLeft; + alignHorizontally(cm); + } + // If we need to scroll a specific position into view, do so. + if (op.scrollToPos) { + var coords = scrollPosIntoView(cm, clipPos(doc, op.scrollToPos.from), + clipPos(doc, op.scrollToPos.to), op.scrollToPos.margin); + if (op.scrollToPos.isCursor && cm.state.focused) maybeScrollWindow(cm, coords); + } + + // Fire events for markers that are hidden/unidden by editing or + // undoing + var hidden = op.maybeHiddenMarkers, unhidden = op.maybeUnhiddenMarkers; + if (hidden) for (var i = 0; i < hidden.length; ++i) + if (!hidden[i].lines.length) signal(hidden[i], "hide"); + if (unhidden) for (var i = 0; i < unhidden.length; ++i) + if (unhidden[i].lines.length) signal(unhidden[i], "unhide"); + + if (display.wrapper.offsetHeight) + doc.scrollTop = cm.display.scroller.scrollTop; + + // Fire change events, and delayed event handlers + if (op.changeObjs) + signal(cm, "changes", cm, op.changeObjs); + if (op.update) + op.update.finish(); + } + + // Run the given function in an operation + function runInOp(cm, f) { + if (cm.curOp) return f(); + startOperation(cm); + try { return f(); } + finally { endOperation(cm); } + } + // Wraps a function in an operation. Returns the wrapped function. + function operation(cm, f) { + return function() { + if (cm.curOp) return f.apply(cm, arguments); + startOperation(cm); + try { return f.apply(cm, arguments); } + finally { endOperation(cm); } + }; + } + // Used to add methods to editor and doc instances, wrapping them in + // operations. + function methodOp(f) { + return function() { + if (this.curOp) return f.apply(this, arguments); + startOperation(this); + try { return f.apply(this, arguments); } + finally { endOperation(this); } + }; + } + function docMethodOp(f) { + return function() { + var cm = this.cm; + if (!cm || cm.curOp) return f.apply(this, arguments); + startOperation(cm); + try { return f.apply(this, arguments); } + finally { endOperation(cm); } + }; + } + + // VIEW TRACKING + + // These objects are used to represent the visible (currently drawn) + // part of the document. A LineView may correspond to multiple + // logical lines, if those are connected by collapsed ranges. + function LineView(doc, line, lineN) { + // The starting line + this.line = line; + // Continuing lines, if any + this.rest = visualLineContinued(line); + // Number of logical lines in this visual line + this.size = this.rest ? lineNo(lst(this.rest)) - lineN + 1 : 1; + this.node = this.text = null; + this.hidden = lineIsHidden(doc, line); + } + + // Create a range of LineView objects for the given lines. + function buildViewArray(cm, from, to) { + var array = [], nextPos; + for (var pos = from; pos < to; pos = nextPos) { + var view = new LineView(cm.doc, getLine(cm.doc, pos), pos); + nextPos = pos + view.size; + array.push(view); + } + return array; + } + + // Updates the display.view data structure for a given change to the + // document. From and to are in pre-change coordinates. Lendiff is + // the amount of lines added or subtracted by the change. This is + // used for changes that span multiple lines, or change the way + // lines are divided into visual lines. regLineChange (below) + // registers single-line changes. + function regChange(cm, from, to, lendiff) { + if (from == null) from = cm.doc.first; + if (to == null) to = cm.doc.first + cm.doc.size; + if (!lendiff) lendiff = 0; + + var display = cm.display; + if (lendiff && to < display.viewTo && + (display.updateLineNumbers == null || display.updateLineNumbers > from)) + display.updateLineNumbers = from; + + cm.curOp.viewChanged = true; + + if (from >= display.viewTo) { // Change after + if (sawCollapsedSpans && visualLineNo(cm.doc, from) < display.viewTo) + resetView(cm); + } else if (to <= display.viewFrom) { // Change before + if (sawCollapsedSpans && visualLineEndNo(cm.doc, to + lendiff) > display.viewFrom) { + resetView(cm); + } else { + display.viewFrom += lendiff; + display.viewTo += lendiff; + } + } else if (from <= display.viewFrom && to >= display.viewTo) { // Full overlap + resetView(cm); + } else if (from <= display.viewFrom) { // Top overlap + var cut = viewCuttingPoint(cm, to, to + lendiff, 1); + if (cut) { + display.view = display.view.slice(cut.index); + display.viewFrom = cut.lineN; + display.viewTo += lendiff; + } else { + resetView(cm); + } + } else if (to >= display.viewTo) { // Bottom overlap + var cut = viewCuttingPoint(cm, from, from, -1); + if (cut) { + display.view = display.view.slice(0, cut.index); + display.viewTo = cut.lineN; + } else { + resetView(cm); + } + } else { // Gap in the middle + var cutTop = viewCuttingPoint(cm, from, from, -1); + var cutBot = viewCuttingPoint(cm, to, to + lendiff, 1); + if (cutTop && cutBot) { + display.view = display.view.slice(0, cutTop.index) + .concat(buildViewArray(cm, cutTop.lineN, cutBot.lineN)) + .concat(display.view.slice(cutBot.index)); + display.viewTo += lendiff; + } else { + resetView(cm); + } + } + + var ext = display.externalMeasured; + if (ext) { + if (to < ext.lineN) + ext.lineN += lendiff; + else if (from < ext.lineN + ext.size) + display.externalMeasured = null; + } + } + + // Register a change to a single line. Type must be one of "text", + // "gutter", "class", "widget" + function regLineChange(cm, line, type) { + cm.curOp.viewChanged = true; + var display = cm.display, ext = cm.display.externalMeasured; + if (ext && line >= ext.lineN && line < ext.lineN + ext.size) + display.externalMeasured = null; + + if (line < display.viewFrom || line >= display.viewTo) return; + var lineView = display.view[findViewIndex(cm, line)]; + if (lineView.node == null) return; + var arr = lineView.changes || (lineView.changes = []); + if (indexOf(arr, type) == -1) arr.push(type); + } + + // Clear the view. + function resetView(cm) { + cm.display.viewFrom = cm.display.viewTo = cm.doc.first; + cm.display.view = []; + cm.display.viewOffset = 0; + } + + // Find the view element corresponding to a given line. Return null + // when the line isn't visible. + function findViewIndex(cm, n) { + if (n >= cm.display.viewTo) return null; + n -= cm.display.viewFrom; + if (n < 0) return null; + var view = cm.display.view; + for (var i = 0; i < view.length; i++) { + n -= view[i].size; + if (n < 0) return i; + } + } + + function viewCuttingPoint(cm, oldN, newN, dir) { + var index = findViewIndex(cm, oldN), diff, view = cm.display.view; + if (!sawCollapsedSpans || newN == cm.doc.first + cm.doc.size) + return {index: index, lineN: newN}; + for (var i = 0, n = cm.display.viewFrom; i < index; i++) + n += view[i].size; + if (n != oldN) { + if (dir > 0) { + if (index == view.length - 1) return null; + diff = (n + view[index].size) - oldN; + index++; + } else { + diff = n - oldN; + } + oldN += diff; newN += diff; + } + while (visualLineNo(cm.doc, newN) != newN) { + if (index == (dir < 0 ? 0 : view.length - 1)) return null; + newN += dir * view[index - (dir < 0 ? 1 : 0)].size; + index += dir; + } + return {index: index, lineN: newN}; + } + + // Force the view to cover a given range, adding empty view element + // or clipping off existing ones as needed. + function adjustView(cm, from, to) { + var display = cm.display, view = display.view; + if (view.length == 0 || from >= display.viewTo || to <= display.viewFrom) { + display.view = buildViewArray(cm, from, to); + display.viewFrom = from; + } else { + if (display.viewFrom > from) + display.view = buildViewArray(cm, from, display.viewFrom).concat(display.view); + else if (display.viewFrom < from) + display.view = display.view.slice(findViewIndex(cm, from)); + display.viewFrom = from; + if (display.viewTo < to) + display.view = display.view.concat(buildViewArray(cm, display.viewTo, to)); + else if (display.viewTo > to) + display.view = display.view.slice(0, findViewIndex(cm, to)); + } + display.viewTo = to; + } + + // Count the number of lines in the view whose DOM representation is + // out of date (or nonexistent). + function countDirtyView(cm) { + var view = cm.display.view, dirty = 0; + for (var i = 0; i < view.length; i++) { + var lineView = view[i]; + if (!lineView.hidden && (!lineView.node || lineView.changes)) ++dirty; + } + return dirty; + } + + // EVENT HANDLERS + + // Attach the necessary event handlers when initializing the editor + function registerEventHandlers(cm) { + var d = cm.display; + on(d.scroller, "mousedown", operation(cm, onMouseDown)); + // Older IE's will not fire a second mousedown for a double click + if (ie && ie_version < 11) + on(d.scroller, "dblclick", operation(cm, function(e) { + if (signalDOMEvent(cm, e)) return; + var pos = posFromMouse(cm, e); + if (!pos || clickInGutter(cm, e) || eventInWidget(cm.display, e)) return; + e_preventDefault(e); + var word = cm.findWordAt(pos); + extendSelection(cm.doc, word.anchor, word.head); + })); + else + on(d.scroller, "dblclick", function(e) { signalDOMEvent(cm, e) || e_preventDefault(e); }); + // Some browsers fire contextmenu *after* opening the menu, at + // which point we can't mess with it anymore. Context menu is + // handled in onMouseDown for these browsers. + if (!captureRightClick) on(d.scroller, "contextmenu", function(e) {onContextMenu(cm, e);}); + + // Used to suppress mouse event handling when a touch happens + var touchFinished, prevTouch = {end: 0}; + function finishTouch() { + if (d.activeTouch) { + touchFinished = setTimeout(function() {d.activeTouch = null;}, 1000); + prevTouch = d.activeTouch; + prevTouch.end = +new Date; + } + }; + function isMouseLikeTouchEvent(e) { + if (e.touches.length != 1) return false; + var touch = e.touches[0]; + return touch.radiusX <= 1 && touch.radiusY <= 1; + } + function farAway(touch, other) { + if (other.left == null) return true; + var dx = other.left - touch.left, dy = other.top - touch.top; + return dx * dx + dy * dy > 20 * 20; + } + on(d.scroller, "touchstart", function(e) { + if (!isMouseLikeTouchEvent(e)) { + clearTimeout(touchFinished); + var now = +new Date; + d.activeTouch = {start: now, moved: false, + prev: now - prevTouch.end <= 300 ? prevTouch : null}; + if (e.touches.length == 1) { + d.activeTouch.left = e.touches[0].pageX; + d.activeTouch.top = e.touches[0].pageY; + } + } + }); + on(d.scroller, "touchmove", function() { + if (d.activeTouch) d.activeTouch.moved = true; + }); + on(d.scroller, "touchend", function(e) { + var touch = d.activeTouch; + if (touch && !eventInWidget(d, e) && touch.left != null && + !touch.moved && new Date - touch.start < 300) { + var pos = cm.coordsChar(d.activeTouch, "page"), range; + if (!touch.prev || farAway(touch, touch.prev)) // Single tap + range = new Range(pos, pos); + else if (!touch.prev.prev || farAway(touch, touch.prev.prev)) // Double tap + range = cm.findWordAt(pos); + else // Triple tap + range = new Range(Pos(pos.line, 0), clipPos(cm.doc, Pos(pos.line + 1, 0))); + cm.setSelection(range.anchor, range.head); + cm.focus(); + e_preventDefault(e); + } + finishTouch(); + }); + on(d.scroller, "touchcancel", finishTouch); + + // Sync scrolling between fake scrollbars and real scrollable + // area, ensure viewport is updated when scrolling. + on(d.scroller, "scroll", function() { + if (d.scroller.clientHeight) { + setScrollTop(cm, d.scroller.scrollTop); + setScrollLeft(cm, d.scroller.scrollLeft, true); + signal(cm, "scroll", cm); + } + }); + + // Listen to wheel events in order to try and update the viewport on time. + on(d.scroller, "mousewheel", function(e){onScrollWheel(cm, e);}); + on(d.scroller, "DOMMouseScroll", function(e){onScrollWheel(cm, e);}); + + // Prevent wrapper from ever scrolling + on(d.wrapper, "scroll", function() { d.wrapper.scrollTop = d.wrapper.scrollLeft = 0; }); + + d.dragFunctions = { + simple: function(e) {if (!signalDOMEvent(cm, e)) e_stop(e);}, + start: function(e){onDragStart(cm, e);}, + drop: operation(cm, onDrop) + }; + + var inp = d.input.getField(); + on(inp, "keyup", function(e) { onKeyUp.call(cm, e); }); + on(inp, "keydown", operation(cm, onKeyDown)); + on(inp, "keypress", operation(cm, onKeyPress)); + on(inp, "focus", bind(onFocus, cm)); + on(inp, "blur", bind(onBlur, cm)); + } + + function dragDropChanged(cm, value, old) { + var wasOn = old && old != CodeMirror.Init; + if (!value != !wasOn) { + var funcs = cm.display.dragFunctions; + var toggle = value ? on : off; + toggle(cm.display.scroller, "dragstart", funcs.start); + toggle(cm.display.scroller, "dragenter", funcs.simple); + toggle(cm.display.scroller, "dragover", funcs.simple); + toggle(cm.display.scroller, "drop", funcs.drop); + } + } + + // Called when the window resizes + function onResize(cm) { + var d = cm.display; + if (d.lastWrapHeight == d.wrapper.clientHeight && d.lastWrapWidth == d.wrapper.clientWidth) + return; + // Might be a text scaling operation, clear size caches. + d.cachedCharWidth = d.cachedTextHeight = d.cachedPaddingH = null; + d.scrollbarsClipped = false; + cm.setSize(); + } + + // MOUSE EVENTS + + // Return true when the given mouse event happened in a widget + function eventInWidget(display, e) { + for (var n = e_target(e); n != display.wrapper; n = n.parentNode) { + if (!n || (n.nodeType == 1 && n.getAttribute("cm-ignore-events") == "true") || + (n.parentNode == display.sizer && n != display.mover)) + return true; + } + } + + // Given a mouse event, find the corresponding position. If liberal + // is false, it checks whether a gutter or scrollbar was clicked, + // and returns null if it was. forRect is used by rectangular + // selections, and tries to estimate a character position even for + // coordinates beyond the right of the text. + function posFromMouse(cm, e, liberal, forRect) { + var display = cm.display; + if (!liberal && e_target(e).getAttribute("cm-not-content") == "true") return null; + + var x, y, space = display.lineSpace.getBoundingClientRect(); + // Fails unpredictably on IE[67] when mouse is dragged around quickly. + try { x = e.clientX - space.left; y = e.clientY - space.top; } + catch (e) { return null; } + var coords = coordsChar(cm, x, y), line; + if (forRect && coords.xRel == 1 && (line = getLine(cm.doc, coords.line).text).length == coords.ch) { + var colDiff = countColumn(line, line.length, cm.options.tabSize) - line.length; + coords = Pos(coords.line, Math.max(0, Math.round((x - paddingH(cm.display).left) / charWidth(cm.display)) - colDiff)); + } + return coords; + } + + // A mouse down can be a single click, double click, triple click, + // start of selection drag, start of text drag, new cursor + // (ctrl-click), rectangle drag (alt-drag), or xwin + // middle-click-paste. Or it might be a click on something we should + // not interfere with, such as a scrollbar or widget. + function onMouseDown(e) { + var cm = this, display = cm.display; + if (display.activeTouch && display.input.supportsTouch() || signalDOMEvent(cm, e)) return; + display.shift = e.shiftKey; + + if (eventInWidget(display, e)) { + if (!webkit) { + // Briefly turn off draggability, to allow widgets to do + // normal dragging things. + display.scroller.draggable = false; + setTimeout(function(){display.scroller.draggable = true;}, 100); + } + return; + } + if (clickInGutter(cm, e)) return; + var start = posFromMouse(cm, e); + window.focus(); + + switch (e_button(e)) { + case 1: + if (start) + leftButtonDown(cm, e, start); + else if (e_target(e) == display.scroller) + e_preventDefault(e); + break; + case 2: + if (webkit) cm.state.lastMiddleDown = +new Date; + if (start) extendSelection(cm.doc, start); + setTimeout(function() {display.input.focus();}, 20); + e_preventDefault(e); + break; + case 3: + if (captureRightClick) onContextMenu(cm, e); + else delayBlurEvent(cm); + break; + } + } + + var lastClick, lastDoubleClick; + function leftButtonDown(cm, e, start) { + if (ie) setTimeout(bind(ensureFocus, cm), 0); + else cm.curOp.focus = activeElt(); + + var now = +new Date, type; + if (lastDoubleClick && lastDoubleClick.time > now - 400 && cmp(lastDoubleClick.pos, start) == 0) { + type = "triple"; + } else if (lastClick && lastClick.time > now - 400 && cmp(lastClick.pos, start) == 0) { + type = "double"; + lastDoubleClick = {time: now, pos: start}; + } else { + type = "single"; + lastClick = {time: now, pos: start}; + } + + var sel = cm.doc.sel, modifier = mac ? e.metaKey : e.ctrlKey, contained; + if (cm.options.dragDrop && dragAndDrop && !isReadOnly(cm) && + type == "single" && (contained = sel.contains(start)) > -1 && + !sel.ranges[contained].empty()) + leftButtonStartDrag(cm, e, start, modifier); + else + leftButtonSelect(cm, e, start, type, modifier); + } + + // Start a text drag. When it ends, see if any dragging actually + // happen, and treat as a click if it didn't. + function leftButtonStartDrag(cm, e, start, modifier) { + var display = cm.display; + var dragEnd = operation(cm, function(e2) { + if (webkit) display.scroller.draggable = false; + cm.state.draggingText = false; + off(document, "mouseup", dragEnd); + off(display.scroller, "drop", dragEnd); + if (Math.abs(e.clientX - e2.clientX) + Math.abs(e.clientY - e2.clientY) < 10) { + e_preventDefault(e2); + if (!modifier) + extendSelection(cm.doc, start); + // Work around unexplainable focus problem in IE9 (#2127) and Chrome (#3081) + if (webkit || ie && ie_version == 9) + setTimeout(function() {document.body.focus(); display.input.focus();}, 20); + else + display.input.focus(); + } + }); + // Let the drag handler handle this. + if (webkit) display.scroller.draggable = true; + cm.state.draggingText = dragEnd; + // IE's approach to draggable + if (display.scroller.dragDrop) display.scroller.dragDrop(); + on(document, "mouseup", dragEnd); + on(display.scroller, "drop", dragEnd); + } + + // Normal selection, as opposed to text dragging. + function leftButtonSelect(cm, e, start, type, addNew) { + var display = cm.display, doc = cm.doc; + e_preventDefault(e); + + var ourRange, ourIndex, startSel = doc.sel, ranges = startSel.ranges; + if (addNew && !e.shiftKey) { + ourIndex = doc.sel.contains(start); + if (ourIndex > -1) + ourRange = ranges[ourIndex]; + else + ourRange = new Range(start, start); + } else { + ourRange = doc.sel.primary(); + ourIndex = doc.sel.primIndex; + } + + if (e.altKey) { + type = "rect"; + if (!addNew) ourRange = new Range(start, start); + start = posFromMouse(cm, e, true, true); + ourIndex = -1; + } else if (type == "double") { + var word = cm.findWordAt(start); + if (cm.display.shift || doc.extend) + ourRange = extendRange(doc, ourRange, word.anchor, word.head); + else + ourRange = word; + } else if (type == "triple") { + var line = new Range(Pos(start.line, 0), clipPos(doc, Pos(start.line + 1, 0))); + if (cm.display.shift || doc.extend) + ourRange = extendRange(doc, ourRange, line.anchor, line.head); + else + ourRange = line; + } else { + ourRange = extendRange(doc, ourRange, start); + } + + if (!addNew) { + ourIndex = 0; + setSelection(doc, new Selection([ourRange], 0), sel_mouse); + startSel = doc.sel; + } else if (ourIndex == -1) { + ourIndex = ranges.length; + setSelection(doc, normalizeSelection(ranges.concat([ourRange]), ourIndex), + {scroll: false, origin: "*mouse"}); + } else if (ranges.length > 1 && ranges[ourIndex].empty() && type == "single" && !e.shiftKey) { + setSelection(doc, normalizeSelection(ranges.slice(0, ourIndex).concat(ranges.slice(ourIndex + 1)), 0)); + startSel = doc.sel; + } else { + replaceOneSelection(doc, ourIndex, ourRange, sel_mouse); + } + + var lastPos = start; + function extendTo(pos) { + if (cmp(lastPos, pos) == 0) return; + lastPos = pos; + + if (type == "rect") { + var ranges = [], tabSize = cm.options.tabSize; + var startCol = countColumn(getLine(doc, start.line).text, start.ch, tabSize); + var posCol = countColumn(getLine(doc, pos.line).text, pos.ch, tabSize); + var left = Math.min(startCol, posCol), right = Math.max(startCol, posCol); + for (var line = Math.min(start.line, pos.line), end = Math.min(cm.lastLine(), Math.max(start.line, pos.line)); + line <= end; line++) { + var text = getLine(doc, line).text, leftPos = findColumn(text, left, tabSize); + if (left == right) + ranges.push(new Range(Pos(line, leftPos), Pos(line, leftPos))); + else if (text.length > leftPos) + ranges.push(new Range(Pos(line, leftPos), Pos(line, findColumn(text, right, tabSize)))); + } + if (!ranges.length) ranges.push(new Range(start, start)); + setSelection(doc, normalizeSelection(startSel.ranges.slice(0, ourIndex).concat(ranges), ourIndex), + {origin: "*mouse", scroll: false}); + cm.scrollIntoView(pos); + } else { + var oldRange = ourRange; + var anchor = oldRange.anchor, head = pos; + if (type != "single") { + if (type == "double") + var range = cm.findWordAt(pos); + else + var range = new Range(Pos(pos.line, 0), clipPos(doc, Pos(pos.line + 1, 0))); + if (cmp(range.anchor, anchor) > 0) { + head = range.head; + anchor = minPos(oldRange.from(), range.anchor); + } else { + head = range.anchor; + anchor = maxPos(oldRange.to(), range.head); + } + } + var ranges = startSel.ranges.slice(0); + ranges[ourIndex] = new Range(clipPos(doc, anchor), head); + setSelection(doc, normalizeSelection(ranges, ourIndex), sel_mouse); + } + } + + var editorSize = display.wrapper.getBoundingClientRect(); + // Used to ensure timeout re-tries don't fire when another extend + // happened in the meantime (clearTimeout isn't reliable -- at + // least on Chrome, the timeouts still happen even when cleared, + // if the clear happens after their scheduled firing time). + var counter = 0; + + function extend(e) { + var curCount = ++counter; + var cur = posFromMouse(cm, e, true, type == "rect"); + if (!cur) return; + if (cmp(cur, lastPos) != 0) { + cm.curOp.focus = activeElt(); + extendTo(cur); + var visible = visibleLines(display, doc); + if (cur.line >= visible.to || cur.line < visible.from) + setTimeout(operation(cm, function(){if (counter == curCount) extend(e);}), 150); + } else { + var outside = e.clientY < editorSize.top ? -20 : e.clientY > editorSize.bottom ? 20 : 0; + if (outside) setTimeout(operation(cm, function() { + if (counter != curCount) return; + display.scroller.scrollTop += outside; + extend(e); + }), 50); + } + } + + function done(e) { + counter = Infinity; + e_preventDefault(e); + display.input.focus(); + off(document, "mousemove", move); + off(document, "mouseup", up); + doc.history.lastSelOrigin = null; + } + + var move = operation(cm, function(e) { + if (!e_button(e)) done(e); + else extend(e); + }); + var up = operation(cm, done); + on(document, "mousemove", move); + on(document, "mouseup", up); + } + + // Determines whether an event happened in the gutter, and fires the + // handlers for the corresponding event. + function gutterEvent(cm, e, type, prevent, signalfn) { + try { var mX = e.clientX, mY = e.clientY; } + catch(e) { return false; } + if (mX >= Math.floor(cm.display.gutters.getBoundingClientRect().right)) return false; + if (prevent) e_preventDefault(e); + + var display = cm.display; + var lineBox = display.lineDiv.getBoundingClientRect(); + + if (mY > lineBox.bottom || !hasHandler(cm, type)) return e_defaultPrevented(e); + mY -= lineBox.top - display.viewOffset; + + for (var i = 0; i < cm.options.gutters.length; ++i) { + var g = display.gutters.childNodes[i]; + if (g && g.getBoundingClientRect().right >= mX) { + var line = lineAtHeight(cm.doc, mY); + var gutter = cm.options.gutters[i]; + signalfn(cm, type, cm, line, gutter, e); + return e_defaultPrevented(e); + } + } + } + + function clickInGutter(cm, e) { + return gutterEvent(cm, e, "gutterClick", true, signalLater); + } + + // Kludge to work around strange IE behavior where it'll sometimes + // re-fire a series of drag-related events right after the drop (#1551) + var lastDrop = 0; + + function onDrop(e) { + var cm = this; + if (signalDOMEvent(cm, e) || eventInWidget(cm.display, e)) + return; + e_preventDefault(e); + if (ie) lastDrop = +new Date; + var pos = posFromMouse(cm, e, true), files = e.dataTransfer.files; + if (!pos || isReadOnly(cm)) return; + // Might be a file drop, in which case we simply extract the text + // and insert it. + if (files && files.length && window.FileReader && window.File) { + var n = files.length, text = Array(n), read = 0; + var loadFile = function(file, i) { + var reader = new FileReader; + reader.onload = operation(cm, function() { + text[i] = reader.result; + if (++read == n) { + pos = clipPos(cm.doc, pos); + var change = {from: pos, to: pos, text: splitLines(text.join("\n")), origin: "paste"}; + makeChange(cm.doc, change); + setSelectionReplaceHistory(cm.doc, simpleSelection(pos, changeEnd(change))); + } + }); + reader.readAsText(file); + }; + for (var i = 0; i < n; ++i) loadFile(files[i], i); + } else { // Normal drop + // Don't do a replace if the drop happened inside of the selected text. + if (cm.state.draggingText && cm.doc.sel.contains(pos) > -1) { + cm.state.draggingText(e); + // Ensure the editor is re-focused + setTimeout(function() {cm.display.input.focus();}, 20); + return; + } + try { + var text = e.dataTransfer.getData("Text"); + if (text) { + if (cm.state.draggingText && !(mac ? e.altKey : e.ctrlKey)) + var selected = cm.listSelections(); + setSelectionNoUndo(cm.doc, simpleSelection(pos, pos)); + if (selected) for (var i = 0; i < selected.length; ++i) + replaceRange(cm.doc, "", selected[i].anchor, selected[i].head, "drag"); + cm.replaceSelection(text, "around", "paste"); + cm.display.input.focus(); + } + } + catch(e){} + } + } + + function onDragStart(cm, e) { + if (ie && (!cm.state.draggingText || +new Date - lastDrop < 100)) { e_stop(e); return; } + if (signalDOMEvent(cm, e) || eventInWidget(cm.display, e)) return; + + e.dataTransfer.setData("Text", cm.getSelection()); + + // Use dummy image instead of default browsers image. + // Recent Safari (~6.0.2) have a tendency to segfault when this happens, so we don't do it there. + if (e.dataTransfer.setDragImage && !safari) { + var img = elt("img", null, null, "position: fixed; left: 0; top: 0;"); + img.src = "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="; + if (presto) { + img.width = img.height = 1; + cm.display.wrapper.appendChild(img); + // Force a relayout, or Opera won't use our image for some obscure reason + img._top = img.offsetTop; + } + e.dataTransfer.setDragImage(img, 0, 0); + if (presto) img.parentNode.removeChild(img); + } + } + + // SCROLL EVENTS + + // Sync the scrollable area and scrollbars, ensure the viewport + // covers the visible area. + function setScrollTop(cm, val) { + if (Math.abs(cm.doc.scrollTop - val) < 2) return; + cm.doc.scrollTop = val; + if (!gecko) updateDisplaySimple(cm, {top: val}); + if (cm.display.scroller.scrollTop != val) cm.display.scroller.scrollTop = val; + cm.display.scrollbars.setScrollTop(val); + if (gecko) updateDisplaySimple(cm); + startWorker(cm, 100); + } + // Sync scroller and scrollbar, ensure the gutter elements are + // aligned. + function setScrollLeft(cm, val, isScroller) { + if (isScroller ? val == cm.doc.scrollLeft : Math.abs(cm.doc.scrollLeft - val) < 2) return; + val = Math.min(val, cm.display.scroller.scrollWidth - cm.display.scroller.clientWidth); + cm.doc.scrollLeft = val; + alignHorizontally(cm); + if (cm.display.scroller.scrollLeft != val) cm.display.scroller.scrollLeft = val; + cm.display.scrollbars.setScrollLeft(val); + } + + // Since the delta values reported on mouse wheel events are + // unstandardized between browsers and even browser versions, and + // generally horribly unpredictable, this code starts by measuring + // the scroll effect that the first few mouse wheel events have, + // and, from that, detects the way it can convert deltas to pixel + // offsets afterwards. + // + // The reason we want to know the amount a wheel event will scroll + // is that it gives us a chance to update the display before the + // actual scrolling happens, reducing flickering. + + var wheelSamples = 0, wheelPixelsPerUnit = null; + // Fill in a browser-detected starting value on browsers where we + // know one. These don't have to be accurate -- the result of them + // being wrong would just be a slight flicker on the first wheel + // scroll (if it is large enough). + if (ie) wheelPixelsPerUnit = -.53; + else if (gecko) wheelPixelsPerUnit = 15; + else if (chrome) wheelPixelsPerUnit = -.7; + else if (safari) wheelPixelsPerUnit = -1/3; + + var wheelEventDelta = function(e) { + var dx = e.wheelDeltaX, dy = e.wheelDeltaY; + if (dx == null && e.detail && e.axis == e.HORIZONTAL_AXIS) dx = e.detail; + if (dy == null && e.detail && e.axis == e.VERTICAL_AXIS) dy = e.detail; + else if (dy == null) dy = e.wheelDelta; + return {x: dx, y: dy}; + }; + CodeMirror.wheelEventPixels = function(e) { + var delta = wheelEventDelta(e); + delta.x *= wheelPixelsPerUnit; + delta.y *= wheelPixelsPerUnit; + return delta; + }; + + function onScrollWheel(cm, e) { + var delta = wheelEventDelta(e), dx = delta.x, dy = delta.y; + + var display = cm.display, scroll = display.scroller; + // Quit if there's nothing to scroll here + if (!(dx && scroll.scrollWidth > scroll.clientWidth || + dy && scroll.scrollHeight > scroll.clientHeight)) return; + + // Webkit browsers on OS X abort momentum scrolls when the target + // of the scroll event is removed from the scrollable element. + // This hack (see related code in patchDisplay) makes sure the + // element is kept around. + if (dy && mac && webkit) { + outer: for (var cur = e.target, view = display.view; cur != scroll; cur = cur.parentNode) { + for (var i = 0; i < view.length; i++) { + if (view[i].node == cur) { + cm.display.currentWheelTarget = cur; + break outer; + } + } + } + } + + // On some browsers, horizontal scrolling will cause redraws to + // happen before the gutter has been realigned, causing it to + // wriggle around in a most unseemly way. When we have an + // estimated pixels/delta value, we just handle horizontal + // scrolling entirely here. It'll be slightly off from native, but + // better than glitching out. + if (dx && !gecko && !presto && wheelPixelsPerUnit != null) { + if (dy) + setScrollTop(cm, Math.max(0, Math.min(scroll.scrollTop + dy * wheelPixelsPerUnit, scroll.scrollHeight - scroll.clientHeight))); + setScrollLeft(cm, Math.max(0, Math.min(scroll.scrollLeft + dx * wheelPixelsPerUnit, scroll.scrollWidth - scroll.clientWidth))); + e_preventDefault(e); + display.wheelStartX = null; // Abort measurement, if in progress + return; + } + + // 'Project' the visible viewport to cover the area that is being + // scrolled into view (if we know enough to estimate it). + if (dy && wheelPixelsPerUnit != null) { + var pixels = dy * wheelPixelsPerUnit; + var top = cm.doc.scrollTop, bot = top + display.wrapper.clientHeight; + if (pixels < 0) top = Math.max(0, top + pixels - 50); + else bot = Math.min(cm.doc.height, bot + pixels + 50); + updateDisplaySimple(cm, {top: top, bottom: bot}); + } + + if (wheelSamples < 20) { + if (display.wheelStartX == null) { + display.wheelStartX = scroll.scrollLeft; display.wheelStartY = scroll.scrollTop; + display.wheelDX = dx; display.wheelDY = dy; + setTimeout(function() { + if (display.wheelStartX == null) return; + var movedX = scroll.scrollLeft - display.wheelStartX; + var movedY = scroll.scrollTop - display.wheelStartY; + var sample = (movedY && display.wheelDY && movedY / display.wheelDY) || + (movedX && display.wheelDX && movedX / display.wheelDX); + display.wheelStartX = display.wheelStartY = null; + if (!sample) return; + wheelPixelsPerUnit = (wheelPixelsPerUnit * wheelSamples + sample) / (wheelSamples + 1); + ++wheelSamples; + }, 200); + } else { + display.wheelDX += dx; display.wheelDY += dy; + } + } + } + + // KEY EVENTS + + // Run a handler that was bound to a key. + function doHandleBinding(cm, bound, dropShift) { + if (typeof bound == "string") { + bound = commands[bound]; + if (!bound) return false; + } + // Ensure previous input has been read, so that the handler sees a + // consistent view of the document + cm.display.input.ensurePolled(); + var prevShift = cm.display.shift, done = false; + try { + if (isReadOnly(cm)) cm.state.suppressEdits = true; + if (dropShift) cm.display.shift = false; + done = bound(cm) != Pass; + } finally { + cm.display.shift = prevShift; + cm.state.suppressEdits = false; + } + return done; + } + + function lookupKeyForEditor(cm, name, handle) { + for (var i = 0; i < cm.state.keyMaps.length; i++) { + var result = lookupKey(name, cm.state.keyMaps[i], handle, cm); + if (result) return result; + } + return (cm.options.extraKeys && lookupKey(name, cm.options.extraKeys, handle, cm)) + || lookupKey(name, cm.options.keyMap, handle, cm); + } + + var stopSeq = new Delayed; + function dispatchKey(cm, name, e, handle) { + var seq = cm.state.keySeq; + if (seq) { + if (isModifierKey(name)) return "handled"; + stopSeq.set(50, function() { + if (cm.state.keySeq == seq) { + cm.state.keySeq = null; + cm.display.input.reset(); + } + }); + name = seq + " " + name; + } + var result = lookupKeyForEditor(cm, name, handle); + + if (result == "multi") + cm.state.keySeq = name; + if (result == "handled") + signalLater(cm, "keyHandled", cm, name, e); + + if (result == "handled" || result == "multi") { + e_preventDefault(e); + restartBlink(cm); + } + + if (seq && !result && /\'$/.test(name)) { + e_preventDefault(e); + return true; + } + return !!result; + } + + // Handle a key from the keydown event. + function handleKeyBinding(cm, e) { + var name = keyName(e, true); + if (!name) return false; + + if (e.shiftKey && !cm.state.keySeq) { + // First try to resolve full name (including 'Shift-'). Failing + // that, see if there is a cursor-motion command (starting with + // 'go') bound to the keyname without 'Shift-'. + return dispatchKey(cm, "Shift-" + name, e, function(b) {return doHandleBinding(cm, b, true);}) + || dispatchKey(cm, name, e, function(b) { + if (typeof b == "string" ? /^go[A-Z]/.test(b) : b.motion) + return doHandleBinding(cm, b); + }); + } else { + return dispatchKey(cm, name, e, function(b) { return doHandleBinding(cm, b); }); + } + } + + // Handle a key from the keypress event + function handleCharBinding(cm, e, ch) { + return dispatchKey(cm, "'" + ch + "'", e, + function(b) { return doHandleBinding(cm, b, true); }); + } + + var lastStoppedKey = null; + function onKeyDown(e) { + var cm = this; + cm.curOp.focus = activeElt(); + if (signalDOMEvent(cm, e)) return; + // IE does strange things with escape. + if (ie && ie_version < 11 && e.keyCode == 27) e.returnValue = false; + var code = e.keyCode; + cm.display.shift = code == 16 || e.shiftKey; + var handled = handleKeyBinding(cm, e); + if (presto) { + lastStoppedKey = handled ? code : null; + // Opera has no cut event... we try to at least catch the key combo + if (!handled && code == 88 && !hasCopyEvent && (mac ? e.metaKey : e.ctrlKey)) + cm.replaceSelection("", null, "cut"); + } + + // Turn mouse into crosshair when Alt is held on Mac. + if (code == 18 && !/\bCodeMirror-crosshair\b/.test(cm.display.lineDiv.className)) + showCrossHair(cm); + } + + function showCrossHair(cm) { + var lineDiv = cm.display.lineDiv; + addClass(lineDiv, "CodeMirror-crosshair"); + + function up(e) { + if (e.keyCode == 18 || !e.altKey) { + rmClass(lineDiv, "CodeMirror-crosshair"); + off(document, "keyup", up); + off(document, "mouseover", up); + } + } + on(document, "keyup", up); + on(document, "mouseover", up); + } + + function onKeyUp(e) { + if (e.keyCode == 16) this.doc.sel.shift = false; + signalDOMEvent(this, e); + } + + function onKeyPress(e) { + var cm = this; + if (eventInWidget(cm.display, e) || signalDOMEvent(cm, e) || e.ctrlKey && !e.altKey || mac && e.metaKey) return; + var keyCode = e.keyCode, charCode = e.charCode; + if (presto && keyCode == lastStoppedKey) {lastStoppedKey = null; e_preventDefault(e); return;} + if ((presto && (!e.which || e.which < 10)) && handleKeyBinding(cm, e)) return; + var ch = String.fromCharCode(charCode == null ? keyCode : charCode); + if (handleCharBinding(cm, e, ch)) return; + cm.display.input.onKeyPress(e); + } + + // FOCUS/BLUR EVENTS + + function delayBlurEvent(cm) { + cm.state.delayingBlurEvent = true; + setTimeout(function() { + if (cm.state.delayingBlurEvent) { + cm.state.delayingBlurEvent = false; + onBlur(cm); + } + }, 100); + } + + function onFocus(cm) { + if (cm.state.delayingBlurEvent) cm.state.delayingBlurEvent = false; + + if (cm.options.readOnly == "nocursor") return; + if (!cm.state.focused) { + signal(cm, "focus", cm); + cm.state.focused = true; + addClass(cm.display.wrapper, "CodeMirror-focused"); + // This test prevents this from firing when a context + // menu is closed (since the input reset would kill the + // select-all detection hack) + if (!cm.curOp && cm.display.selForContextMenu != cm.doc.sel) { + cm.display.input.reset(); + if (webkit) setTimeout(function() { cm.display.input.reset(true); }, 20); // Issue #1730 + } + cm.display.input.receivedFocus(); + } + restartBlink(cm); + } + function onBlur(cm) { + if (cm.state.delayingBlurEvent) return; + + if (cm.state.focused) { + signal(cm, "blur", cm); + cm.state.focused = false; + rmClass(cm.display.wrapper, "CodeMirror-focused"); + } + clearInterval(cm.display.blinker); + setTimeout(function() {if (!cm.state.focused) cm.display.shift = false;}, 150); + } + + // CONTEXT MENU HANDLING + + // To make the context menu work, we need to briefly unhide the + // textarea (making it as unobtrusive as possible) to let the + // right-click take effect on it. + function onContextMenu(cm, e) { + if (eventInWidget(cm.display, e) || contextMenuInGutter(cm, e)) return; + cm.display.input.onContextMenu(e); + } + + function contextMenuInGutter(cm, e) { + if (!hasHandler(cm, "gutterContextMenu")) return false; + return gutterEvent(cm, e, "gutterContextMenu", false, signal); + } + + // UPDATING + + // Compute the position of the end of a change (its 'to' property + // refers to the pre-change end). + var changeEnd = CodeMirror.changeEnd = function(change) { + if (!change.text) return change.to; + return Pos(change.from.line + change.text.length - 1, + lst(change.text).length + (change.text.length == 1 ? change.from.ch : 0)); + }; + + // Adjust a position to refer to the post-change position of the + // same text, or the end of the change if the change covers it. + function adjustForChange(pos, change) { + if (cmp(pos, change.from) < 0) return pos; + if (cmp(pos, change.to) <= 0) return changeEnd(change); + + var line = pos.line + change.text.length - (change.to.line - change.from.line) - 1, ch = pos.ch; + if (pos.line == change.to.line) ch += changeEnd(change).ch - change.to.ch; + return Pos(line, ch); + } + + function computeSelAfterChange(doc, change) { + var out = []; + for (var i = 0; i < doc.sel.ranges.length; i++) { + var range = doc.sel.ranges[i]; + out.push(new Range(adjustForChange(range.anchor, change), + adjustForChange(range.head, change))); + } + return normalizeSelection(out, doc.sel.primIndex); + } + + function offsetPos(pos, old, nw) { + if (pos.line == old.line) + return Pos(nw.line, pos.ch - old.ch + nw.ch); + else + return Pos(nw.line + (pos.line - old.line), pos.ch); + } + + // Used by replaceSelections to allow moving the selection to the + // start or around the replaced test. Hint may be "start" or "around". + function computeReplacedSel(doc, changes, hint) { + var out = []; + var oldPrev = Pos(doc.first, 0), newPrev = oldPrev; + for (var i = 0; i < changes.length; i++) { + var change = changes[i]; + var from = offsetPos(change.from, oldPrev, newPrev); + var to = offsetPos(changeEnd(change), oldPrev, newPrev); + oldPrev = change.to; + newPrev = to; + if (hint == "around") { + var range = doc.sel.ranges[i], inv = cmp(range.head, range.anchor) < 0; + out[i] = new Range(inv ? to : from, inv ? from : to); + } else { + out[i] = new Range(from, from); + } + } + return new Selection(out, doc.sel.primIndex); + } + + // Allow "beforeChange" event handlers to influence a change + function filterChange(doc, change, update) { + var obj = { + canceled: false, + from: change.from, + to: change.to, + text: change.text, + origin: change.origin, + cancel: function() { this.canceled = true; } + }; + if (update) obj.update = function(from, to, text, origin) { + if (from) this.from = clipPos(doc, from); + if (to) this.to = clipPos(doc, to); + if (text) this.text = text; + if (origin !== undefined) this.origin = origin; + }; + signal(doc, "beforeChange", doc, obj); + if (doc.cm) signal(doc.cm, "beforeChange", doc.cm, obj); + + if (obj.canceled) return null; + return {from: obj.from, to: obj.to, text: obj.text, origin: obj.origin}; + } + + // Apply a change to a document, and add it to the document's + // history, and propagating it to all linked documents. + function makeChange(doc, change, ignoreReadOnly) { + if (doc.cm) { + if (!doc.cm.curOp) return operation(doc.cm, makeChange)(doc, change, ignoreReadOnly); + if (doc.cm.state.suppressEdits) return; + } + + if (hasHandler(doc, "beforeChange") || doc.cm && hasHandler(doc.cm, "beforeChange")) { + change = filterChange(doc, change, true); + if (!change) return; + } + + // Possibly split or suppress the update based on the presence + // of read-only spans in its range. + var split = sawReadOnlySpans && !ignoreReadOnly && removeReadOnlyRanges(doc, change.from, change.to); + if (split) { + for (var i = split.length - 1; i >= 0; --i) + makeChangeInner(doc, {from: split[i].from, to: split[i].to, text: i ? [""] : change.text}); + } else { + makeChangeInner(doc, change); + } + } + + function makeChangeInner(doc, change) { + if (change.text.length == 1 && change.text[0] == "" && cmp(change.from, change.to) == 0) return; + var selAfter = computeSelAfterChange(doc, change); + addChangeToHistory(doc, change, selAfter, doc.cm ? doc.cm.curOp.id : NaN); + + makeChangeSingleDoc(doc, change, selAfter, stretchSpansOverChange(doc, change)); + var rebased = []; + + linkedDocs(doc, function(doc, sharedHist) { + if (!sharedHist && indexOf(rebased, doc.history) == -1) { + rebaseHist(doc.history, change); + rebased.push(doc.history); + } + makeChangeSingleDoc(doc, change, null, stretchSpansOverChange(doc, change)); + }); + } + + // Revert a change stored in a document's history. + function makeChangeFromHistory(doc, type, allowSelectionOnly) { + if (doc.cm && doc.cm.state.suppressEdits) return; + + var hist = doc.history, event, selAfter = doc.sel; + var source = type == "undo" ? hist.done : hist.undone, dest = type == "undo" ? hist.undone : hist.done; + + // Verify that there is a useable event (so that ctrl-z won't + // needlessly clear selection events) + for (var i = 0; i < source.length; i++) { + event = source[i]; + if (allowSelectionOnly ? event.ranges && !event.equals(doc.sel) : !event.ranges) + break; + } + if (i == source.length) return; + hist.lastOrigin = hist.lastSelOrigin = null; + + for (;;) { + event = source.pop(); + if (event.ranges) { + pushSelectionToHistory(event, dest); + if (allowSelectionOnly && !event.equals(doc.sel)) { + setSelection(doc, event, {clearRedo: false}); + return; + } + selAfter = event; + } + else break; + } + + // Build up a reverse change object to add to the opposite history + // stack (redo when undoing, and vice versa). + var antiChanges = []; + pushSelectionToHistory(selAfter, dest); + dest.push({changes: antiChanges, generation: hist.generation}); + hist.generation = event.generation || ++hist.maxGeneration; + + var filter = hasHandler(doc, "beforeChange") || doc.cm && hasHandler(doc.cm, "beforeChange"); + + for (var i = event.changes.length - 1; i >= 0; --i) { + var change = event.changes[i]; + change.origin = type; + if (filter && !filterChange(doc, change, false)) { + source.length = 0; + return; + } + + antiChanges.push(historyChangeFromChange(doc, change)); + + var after = i ? computeSelAfterChange(doc, change) : lst(source); + makeChangeSingleDoc(doc, change, after, mergeOldSpans(doc, change)); + if (!i && doc.cm) doc.cm.scrollIntoView({from: change.from, to: changeEnd(change)}); + var rebased = []; + + // Propagate to the linked documents + linkedDocs(doc, function(doc, sharedHist) { + if (!sharedHist && indexOf(rebased, doc.history) == -1) { + rebaseHist(doc.history, change); + rebased.push(doc.history); + } + makeChangeSingleDoc(doc, change, null, mergeOldSpans(doc, change)); + }); + } + } + + // Sub-views need their line numbers shifted when text is added + // above or below them in the parent document. + function shiftDoc(doc, distance) { + if (distance == 0) return; + doc.first += distance; + doc.sel = new Selection(map(doc.sel.ranges, function(range) { + return new Range(Pos(range.anchor.line + distance, range.anchor.ch), + Pos(range.head.line + distance, range.head.ch)); + }), doc.sel.primIndex); + if (doc.cm) { + regChange(doc.cm, doc.first, doc.first - distance, distance); + for (var d = doc.cm.display, l = d.viewFrom; l < d.viewTo; l++) + regLineChange(doc.cm, l, "gutter"); + } + } + + // More lower-level change function, handling only a single document + // (not linked ones). + function makeChangeSingleDoc(doc, change, selAfter, spans) { + if (doc.cm && !doc.cm.curOp) + return operation(doc.cm, makeChangeSingleDoc)(doc, change, selAfter, spans); + + if (change.to.line < doc.first) { + shiftDoc(doc, change.text.length - 1 - (change.to.line - change.from.line)); + return; + } + if (change.from.line > doc.lastLine()) return; + + // Clip the change to the size of this doc + if (change.from.line < doc.first) { + var shift = change.text.length - 1 - (doc.first - change.from.line); + shiftDoc(doc, shift); + change = {from: Pos(doc.first, 0), to: Pos(change.to.line + shift, change.to.ch), + text: [lst(change.text)], origin: change.origin}; + } + var last = doc.lastLine(); + if (change.to.line > last) { + change = {from: change.from, to: Pos(last, getLine(doc, last).text.length), + text: [change.text[0]], origin: change.origin}; + } + + change.removed = getBetween(doc, change.from, change.to); + + if (!selAfter) selAfter = computeSelAfterChange(doc, change); + if (doc.cm) makeChangeSingleDocInEditor(doc.cm, change, spans); + else updateDoc(doc, change, spans); + setSelectionNoUndo(doc, selAfter, sel_dontScroll); + } + + // Handle the interaction of a change to a document with the editor + // that this document is part of. + function makeChangeSingleDocInEditor(cm, change, spans) { + var doc = cm.doc, display = cm.display, from = change.from, to = change.to; + + var recomputeMaxLength = false, checkWidthStart = from.line; + if (!cm.options.lineWrapping) { + checkWidthStart = lineNo(visualLine(getLine(doc, from.line))); + doc.iter(checkWidthStart, to.line + 1, function(line) { + if (line == display.maxLine) { + recomputeMaxLength = true; + return true; + } + }); + } + + if (doc.sel.contains(change.from, change.to) > -1) + signalCursorActivity(cm); + + updateDoc(doc, change, spans, estimateHeight(cm)); + + if (!cm.options.lineWrapping) { + doc.iter(checkWidthStart, from.line + change.text.length, function(line) { + var len = lineLength(line); + if (len > display.maxLineLength) { + display.maxLine = line; + display.maxLineLength = len; + display.maxLineChanged = true; + recomputeMaxLength = false; + } + }); + if (recomputeMaxLength) cm.curOp.updateMaxLine = true; + } + + // Adjust frontier, schedule worker + doc.frontier = Math.min(doc.frontier, from.line); + startWorker(cm, 400); + + var lendiff = change.text.length - (to.line - from.line) - 1; + // Remember that these lines changed, for updating the display + if (change.full) + regChange(cm); + else if (from.line == to.line && change.text.length == 1 && !isWholeLineUpdate(cm.doc, change)) + regLineChange(cm, from.line, "text"); + else + regChange(cm, from.line, to.line + 1, lendiff); + + var changesHandler = hasHandler(cm, "changes"), changeHandler = hasHandler(cm, "change"); + if (changeHandler || changesHandler) { + var obj = { + from: from, to: to, + text: change.text, + removed: change.removed, + origin: change.origin + }; + if (changeHandler) signalLater(cm, "change", cm, obj); + if (changesHandler) (cm.curOp.changeObjs || (cm.curOp.changeObjs = [])).push(obj); + } + cm.display.selForContextMenu = null; + } + + function replaceRange(doc, code, from, to, origin) { + if (!to) to = from; + if (cmp(to, from) < 0) { var tmp = to; to = from; from = tmp; } + if (typeof code == "string") code = splitLines(code); + makeChange(doc, {from: from, to: to, text: code, origin: origin}); + } + + // SCROLLING THINGS INTO VIEW + + // If an editor sits on the top or bottom of the window, partially + // scrolled out of view, this ensures that the cursor is visible. + function maybeScrollWindow(cm, coords) { + if (signalDOMEvent(cm, "scrollCursorIntoView")) return; + + var display = cm.display, box = display.sizer.getBoundingClientRect(), doScroll = null; + if (coords.top + box.top < 0) doScroll = true; + else if (coords.bottom + box.top > (window.innerHeight || document.documentElement.clientHeight)) doScroll = false; + if (doScroll != null && !phantom) { + var scrollNode = elt("div", "\u200b", null, "position: absolute; top: " + + (coords.top - display.viewOffset - paddingTop(cm.display)) + "px; height: " + + (coords.bottom - coords.top + scrollGap(cm) + display.barHeight) + "px; left: " + + coords.left + "px; width: 2px;"); + cm.display.lineSpace.appendChild(scrollNode); + scrollNode.scrollIntoView(doScroll); + cm.display.lineSpace.removeChild(scrollNode); + } + } + + // Scroll a given position into view (immediately), verifying that + // it actually became visible (as line heights are accurately + // measured, the position of something may 'drift' during drawing). + function scrollPosIntoView(cm, pos, end, margin) { + if (margin == null) margin = 0; + for (var limit = 0; limit < 5; limit++) { + var changed = false, coords = cursorCoords(cm, pos); + var endCoords = !end || end == pos ? coords : cursorCoords(cm, end); + var scrollPos = calculateScrollPos(cm, Math.min(coords.left, endCoords.left), + Math.min(coords.top, endCoords.top) - margin, + Math.max(coords.left, endCoords.left), + Math.max(coords.bottom, endCoords.bottom) + margin); + var startTop = cm.doc.scrollTop, startLeft = cm.doc.scrollLeft; + if (scrollPos.scrollTop != null) { + setScrollTop(cm, scrollPos.scrollTop); + if (Math.abs(cm.doc.scrollTop - startTop) > 1) changed = true; + } + if (scrollPos.scrollLeft != null) { + setScrollLeft(cm, scrollPos.scrollLeft); + if (Math.abs(cm.doc.scrollLeft - startLeft) > 1) changed = true; + } + if (!changed) break; + } + return coords; + } + + // Scroll a given set of coordinates into view (immediately). + function scrollIntoView(cm, x1, y1, x2, y2) { + var scrollPos = calculateScrollPos(cm, x1, y1, x2, y2); + if (scrollPos.scrollTop != null) setScrollTop(cm, scrollPos.scrollTop); + if (scrollPos.scrollLeft != null) setScrollLeft(cm, scrollPos.scrollLeft); + } + + // Calculate a new scroll position needed to scroll the given + // rectangle into view. Returns an object with scrollTop and + // scrollLeft properties. When these are undefined, the + // vertical/horizontal position does not need to be adjusted. + function calculateScrollPos(cm, x1, y1, x2, y2) { + var display = cm.display, snapMargin = textHeight(cm.display); + if (y1 < 0) y1 = 0; + var screentop = cm.curOp && cm.curOp.scrollTop != null ? cm.curOp.scrollTop : display.scroller.scrollTop; + var screen = displayHeight(cm), result = {}; + if (y2 - y1 > screen) y2 = y1 + screen; + var docBottom = cm.doc.height + paddingVert(display); + var atTop = y1 < snapMargin, atBottom = y2 > docBottom - snapMargin; + if (y1 < screentop) { + result.scrollTop = atTop ? 0 : y1; + } else if (y2 > screentop + screen) { + var newTop = Math.min(y1, (atBottom ? docBottom : y2) - screen); + if (newTop != screentop) result.scrollTop = newTop; + } + + var screenleft = cm.curOp && cm.curOp.scrollLeft != null ? cm.curOp.scrollLeft : display.scroller.scrollLeft; + var screenw = displayWidth(cm) - (cm.options.fixedGutter ? display.gutters.offsetWidth : 0); + var tooWide = x2 - x1 > screenw; + if (tooWide) x2 = x1 + screenw; + if (x1 < 10) + result.scrollLeft = 0; + else if (x1 < screenleft) + result.scrollLeft = Math.max(0, x1 - (tooWide ? 0 : 10)); + else if (x2 > screenw + screenleft - 3) + result.scrollLeft = x2 + (tooWide ? 0 : 10) - screenw; + return result; + } + + // Store a relative adjustment to the scroll position in the current + // operation (to be applied when the operation finishes). + function addToScrollPos(cm, left, top) { + if (left != null || top != null) resolveScrollToPos(cm); + if (left != null) + cm.curOp.scrollLeft = (cm.curOp.scrollLeft == null ? cm.doc.scrollLeft : cm.curOp.scrollLeft) + left; + if (top != null) + cm.curOp.scrollTop = (cm.curOp.scrollTop == null ? cm.doc.scrollTop : cm.curOp.scrollTop) + top; + } + + // Make sure that at the end of the operation the current cursor is + // shown. + function ensureCursorVisible(cm) { + resolveScrollToPos(cm); + var cur = cm.getCursor(), from = cur, to = cur; + if (!cm.options.lineWrapping) { + from = cur.ch ? Pos(cur.line, cur.ch - 1) : cur; + to = Pos(cur.line, cur.ch + 1); + } + cm.curOp.scrollToPos = {from: from, to: to, margin: cm.options.cursorScrollMargin, isCursor: true}; + } + + // When an operation has its scrollToPos property set, and another + // scroll action is applied before the end of the operation, this + // 'simulates' scrolling that position into view in a cheap way, so + // that the effect of intermediate scroll commands is not ignored. + function resolveScrollToPos(cm) { + var range = cm.curOp.scrollToPos; + if (range) { + cm.curOp.scrollToPos = null; + var from = estimateCoords(cm, range.from), to = estimateCoords(cm, range.to); + var sPos = calculateScrollPos(cm, Math.min(from.left, to.left), + Math.min(from.top, to.top) - range.margin, + Math.max(from.right, to.right), + Math.max(from.bottom, to.bottom) + range.margin); + cm.scrollTo(sPos.scrollLeft, sPos.scrollTop); + } + } + + // API UTILITIES + + // Indent the given line. The how parameter can be "smart", + // "add"/null, "subtract", or "prev". When aggressive is false + // (typically set to true for forced single-line indents), empty + // lines are not indented, and places where the mode returns Pass + // are left alone. + function indentLine(cm, n, how, aggressive) { + var doc = cm.doc, state; + if (how == null) how = "add"; + if (how == "smart") { + // Fall back to "prev" when the mode doesn't have an indentation + // method. + if (!doc.mode.indent) how = "prev"; + else state = getStateBefore(cm, n); + } + + var tabSize = cm.options.tabSize; + var line = getLine(doc, n), curSpace = countColumn(line.text, null, tabSize); + if (line.stateAfter) line.stateAfter = null; + var curSpaceString = line.text.match(/^\s*/)[0], indentation; + if (!aggressive && !/\S/.test(line.text)) { + indentation = 0; + how = "not"; + } else if (how == "smart") { + indentation = doc.mode.indent(state, line.text.slice(curSpaceString.length), line.text); + if (indentation == Pass || indentation > 150) { + if (!aggressive) return; + how = "prev"; + } + } + if (how == "prev") { + if (n > doc.first) indentation = countColumn(getLine(doc, n-1).text, null, tabSize); + else indentation = 0; + } else if (how == "add") { + indentation = curSpace + cm.options.indentUnit; + } else if (how == "subtract") { + indentation = curSpace - cm.options.indentUnit; + } else if (typeof how == "number") { + indentation = curSpace + how; + } + indentation = Math.max(0, indentation); + + var indentString = "", pos = 0; + if (cm.options.indentWithTabs) + for (var i = Math.floor(indentation / tabSize); i; --i) {pos += tabSize; indentString += "\t";} + if (pos < indentation) indentString += spaceStr(indentation - pos); + + if (indentString != curSpaceString) { + replaceRange(doc, indentString, Pos(n, 0), Pos(n, curSpaceString.length), "+input"); + } else { + // Ensure that, if the cursor was in the whitespace at the start + // of the line, it is moved to the end of that space. + for (var i = 0; i < doc.sel.ranges.length; i++) { + var range = doc.sel.ranges[i]; + if (range.head.line == n && range.head.ch < curSpaceString.length) { + var pos = Pos(n, curSpaceString.length); + replaceOneSelection(doc, i, new Range(pos, pos)); + break; + } + } + } + line.stateAfter = null; + } + + // Utility for applying a change to a line by handle or number, + // returning the number and optionally registering the line as + // changed. + function changeLine(doc, handle, changeType, op) { + var no = handle, line = handle; + if (typeof handle == "number") line = getLine(doc, clipLine(doc, handle)); + else no = lineNo(handle); + if (no == null) return null; + if (op(line, no) && doc.cm) regLineChange(doc.cm, no, changeType); + return line; + } + + // Helper for deleting text near the selection(s), used to implement + // backspace, delete, and similar functionality. + function deleteNearSelection(cm, compute) { + var ranges = cm.doc.sel.ranges, kill = []; + // Build up a set of ranges to kill first, merging overlapping + // ranges. + for (var i = 0; i < ranges.length; i++) { + var toKill = compute(ranges[i]); + while (kill.length && cmp(toKill.from, lst(kill).to) <= 0) { + var replaced = kill.pop(); + if (cmp(replaced.from, toKill.from) < 0) { + toKill.from = replaced.from; + break; + } + } + kill.push(toKill); + } + // Next, remove those actual ranges. + runInOp(cm, function() { + for (var i = kill.length - 1; i >= 0; i--) + replaceRange(cm.doc, "", kill[i].from, kill[i].to, "+delete"); + ensureCursorVisible(cm); + }); + } + + // Used for horizontal relative motion. Dir is -1 or 1 (left or + // right), unit can be "char", "column" (like char, but doesn't + // cross line boundaries), "word" (across next word), or "group" (to + // the start of next group of word or non-word-non-whitespace + // chars). The visually param controls whether, in right-to-left + // text, direction 1 means to move towards the next index in the + // string, or towards the character to the right of the current + // position. The resulting position will have a hitSide=true + // property if it reached the end of the document. + function findPosH(doc, pos, dir, unit, visually) { + var line = pos.line, ch = pos.ch, origDir = dir; + var lineObj = getLine(doc, line); + var possible = true; + function findNextLine() { + var l = line + dir; + if (l < doc.first || l >= doc.first + doc.size) return (possible = false); + line = l; + return lineObj = getLine(doc, l); + } + function moveOnce(boundToLine) { + var next = (visually ? moveVisually : moveLogically)(lineObj, ch, dir, true); + if (next == null) { + if (!boundToLine && findNextLine()) { + if (visually) ch = (dir < 0 ? lineRight : lineLeft)(lineObj); + else ch = dir < 0 ? lineObj.text.length : 0; + } else return (possible = false); + } else ch = next; + return true; + } + + if (unit == "char") moveOnce(); + else if (unit == "column") moveOnce(true); + else if (unit == "word" || unit == "group") { + var sawType = null, group = unit == "group"; + var helper = doc.cm && doc.cm.getHelper(pos, "wordChars"); + for (var first = true;; first = false) { + if (dir < 0 && !moveOnce(!first)) break; + var cur = lineObj.text.charAt(ch) || "\n"; + var type = isWordChar(cur, helper) ? "w" + : group && cur == "\n" ? "n" + : !group || /\s/.test(cur) ? null + : "p"; + if (group && !first && !type) type = "s"; + if (sawType && sawType != type) { + if (dir < 0) {dir = 1; moveOnce();} + break; + } + + if (type) sawType = type; + if (dir > 0 && !moveOnce(!first)) break; + } + } + var result = skipAtomic(doc, Pos(line, ch), origDir, true); + if (!possible) result.hitSide = true; + return result; + } + + // For relative vertical movement. Dir may be -1 or 1. Unit can be + // "page" or "line". The resulting position will have a hitSide=true + // property if it reached the end of the document. + function findPosV(cm, pos, dir, unit) { + var doc = cm.doc, x = pos.left, y; + if (unit == "page") { + var pageSize = Math.min(cm.display.wrapper.clientHeight, window.innerHeight || document.documentElement.clientHeight); + y = pos.top + dir * (pageSize - (dir < 0 ? 1.5 : .5) * textHeight(cm.display)); + } else if (unit == "line") { + y = dir > 0 ? pos.bottom + 3 : pos.top - 3; + } + for (;;) { + var target = coordsChar(cm, x, y); + if (!target.outside) break; + if (dir < 0 ? y <= 0 : y >= doc.height) { target.hitSide = true; break; } + y += dir * 5; + } + return target; + } + + // EDITOR METHODS + + // The publicly visible API. Note that methodOp(f) means + // 'wrap f in an operation, performed on its `this` parameter'. + + // This is not the complete set of editor methods. Most of the + // methods defined on the Doc type are also injected into + // CodeMirror.prototype, for backwards compatibility and + // convenience. + + CodeMirror.prototype = { + constructor: CodeMirror, + focus: function(){window.focus(); this.display.input.focus();}, + + setOption: function(option, value) { + var options = this.options, old = options[option]; + if (options[option] == value && option != "mode") return; + options[option] = value; + if (optionHandlers.hasOwnProperty(option)) + operation(this, optionHandlers[option])(this, value, old); + }, + + getOption: function(option) {return this.options[option];}, + getDoc: function() {return this.doc;}, + + addKeyMap: function(map, bottom) { + this.state.keyMaps[bottom ? "push" : "unshift"](getKeyMap(map)); + }, + removeKeyMap: function(map) { + var maps = this.state.keyMaps; + for (var i = 0; i < maps.length; ++i) + if (maps[i] == map || maps[i].name == map) { + maps.splice(i, 1); + return true; + } + }, + + addOverlay: methodOp(function(spec, options) { + var mode = spec.token ? spec : CodeMirror.getMode(this.options, spec); + if (mode.startState) throw new Error("Overlays may not be stateful."); + this.state.overlays.push({mode: mode, modeSpec: spec, opaque: options && options.opaque}); + this.state.modeGen++; + regChange(this); + }), + removeOverlay: methodOp(function(spec) { + var overlays = this.state.overlays; + for (var i = 0; i < overlays.length; ++i) { + var cur = overlays[i].modeSpec; + if (cur == spec || typeof spec == "string" && cur.name == spec) { + overlays.splice(i, 1); + this.state.modeGen++; + regChange(this); + return; + } + } + }), + + indentLine: methodOp(function(n, dir, aggressive) { + if (typeof dir != "string" && typeof dir != "number") { + if (dir == null) dir = this.options.smartIndent ? "smart" : "prev"; + else dir = dir ? "add" : "subtract"; + } + if (isLine(this.doc, n)) indentLine(this, n, dir, aggressive); + }), + indentSelection: methodOp(function(how) { + var ranges = this.doc.sel.ranges, end = -1; + for (var i = 0; i < ranges.length; i++) { + var range = ranges[i]; + if (!range.empty()) { + var from = range.from(), to = range.to(); + var start = Math.max(end, from.line); + end = Math.min(this.lastLine(), to.line - (to.ch ? 0 : 1)) + 1; + for (var j = start; j < end; ++j) + indentLine(this, j, how); + var newRanges = this.doc.sel.ranges; + if (from.ch == 0 && ranges.length == newRanges.length && newRanges[i].from().ch > 0) + replaceOneSelection(this.doc, i, new Range(from, newRanges[i].to()), sel_dontScroll); + } else if (range.head.line > end) { + indentLine(this, range.head.line, how, true); + end = range.head.line; + if (i == this.doc.sel.primIndex) ensureCursorVisible(this); + } + } + }), + + // Fetch the parser token for a given character. Useful for hacks + // that want to inspect the mode state (say, for completion). + getTokenAt: function(pos, precise) { + return takeToken(this, pos, precise); + }, + + getLineTokens: function(line, precise) { + return takeToken(this, Pos(line), precise, true); + }, + + getTokenTypeAt: function(pos) { + pos = clipPos(this.doc, pos); + var styles = getLineStyles(this, getLine(this.doc, pos.line)); + var before = 0, after = (styles.length - 1) / 2, ch = pos.ch; + var type; + if (ch == 0) type = styles[2]; + else for (;;) { + var mid = (before + after) >> 1; + if ((mid ? styles[mid * 2 - 1] : 0) >= ch) after = mid; + else if (styles[mid * 2 + 1] < ch) before = mid + 1; + else { type = styles[mid * 2 + 2]; break; } + } + var cut = type ? type.indexOf("cm-overlay ") : -1; + return cut < 0 ? type : cut == 0 ? null : type.slice(0, cut - 1); + }, + + getModeAt: function(pos) { + var mode = this.doc.mode; + if (!mode.innerMode) return mode; + return CodeMirror.innerMode(mode, this.getTokenAt(pos).state).mode; + }, + + getHelper: function(pos, type) { + return this.getHelpers(pos, type)[0]; + }, + + getHelpers: function(pos, type) { + var found = []; + if (!helpers.hasOwnProperty(type)) return found; + var help = helpers[type], mode = this.getModeAt(pos); + if (typeof mode[type] == "string") { + if (help[mode[type]]) found.push(help[mode[type]]); + } else if (mode[type]) { + for (var i = 0; i < mode[type].length; i++) { + var val = help[mode[type][i]]; + if (val) found.push(val); + } + } else if (mode.helperType && help[mode.helperType]) { + found.push(help[mode.helperType]); + } else if (help[mode.name]) { + found.push(help[mode.name]); + } + for (var i = 0; i < help._global.length; i++) { + var cur = help._global[i]; + if (cur.pred(mode, this) && indexOf(found, cur.val) == -1) + found.push(cur.val); + } + return found; + }, + + getStateAfter: function(line, precise) { + var doc = this.doc; + line = clipLine(doc, line == null ? doc.first + doc.size - 1: line); + return getStateBefore(this, line + 1, precise); + }, + + cursorCoords: function(start, mode) { + var pos, range = this.doc.sel.primary(); + if (start == null) pos = range.head; + else if (typeof start == "object") pos = clipPos(this.doc, start); + else pos = start ? range.from() : range.to(); + return cursorCoords(this, pos, mode || "page"); + }, + + charCoords: function(pos, mode) { + return charCoords(this, clipPos(this.doc, pos), mode || "page"); + }, + + coordsChar: function(coords, mode) { + coords = fromCoordSystem(this, coords, mode || "page"); + return coordsChar(this, coords.left, coords.top); + }, + + lineAtHeight: function(height, mode) { + height = fromCoordSystem(this, {top: height, left: 0}, mode || "page").top; + return lineAtHeight(this.doc, height + this.display.viewOffset); + }, + heightAtLine: function(line, mode) { + var end = false, lineObj; + if (typeof line == "number") { + var last = this.doc.first + this.doc.size - 1; + if (line < this.doc.first) line = this.doc.first; + else if (line > last) { line = last; end = true; } + lineObj = getLine(this.doc, line); + } else { + lineObj = line; + } + return intoCoordSystem(this, lineObj, {top: 0, left: 0}, mode || "page").top + + (end ? this.doc.height - heightAtLine(lineObj) : 0); + }, + + defaultTextHeight: function() { return textHeight(this.display); }, + defaultCharWidth: function() { return charWidth(this.display); }, + + setGutterMarker: methodOp(function(line, gutterID, value) { + return changeLine(this.doc, line, "gutter", function(line) { + var markers = line.gutterMarkers || (line.gutterMarkers = {}); + markers[gutterID] = value; + if (!value && isEmpty(markers)) line.gutterMarkers = null; + return true; + }); + }), + + clearGutter: methodOp(function(gutterID) { + var cm = this, doc = cm.doc, i = doc.first; + doc.iter(function(line) { + if (line.gutterMarkers && line.gutterMarkers[gutterID]) { + line.gutterMarkers[gutterID] = null; + regLineChange(cm, i, "gutter"); + if (isEmpty(line.gutterMarkers)) line.gutterMarkers = null; + } + ++i; + }); + }), + + lineInfo: function(line) { + if (typeof line == "number") { + if (!isLine(this.doc, line)) return null; + var n = line; + line = getLine(this.doc, line); + if (!line) return null; + } else { + var n = lineNo(line); + if (n == null) return null; + } + return {line: n, handle: line, text: line.text, gutterMarkers: line.gutterMarkers, + textClass: line.textClass, bgClass: line.bgClass, wrapClass: line.wrapClass, + widgets: line.widgets}; + }, + + getViewport: function() { return {from: this.display.viewFrom, to: this.display.viewTo};}, + + addWidget: function(pos, node, scroll, vert, horiz) { + var display = this.display; + pos = cursorCoords(this, clipPos(this.doc, pos)); + var top = pos.bottom, left = pos.left; + node.style.position = "absolute"; + node.setAttribute("cm-ignore-events", "true"); + this.display.input.setUneditable(node); + display.sizer.appendChild(node); + if (vert == "over") { + top = pos.top; + } else if (vert == "above" || vert == "near") { + var vspace = Math.max(display.wrapper.clientHeight, this.doc.height), + hspace = Math.max(display.sizer.clientWidth, display.lineSpace.clientWidth); + // Default to positioning above (if specified and possible); otherwise default to positioning below + if ((vert == 'above' || pos.bottom + node.offsetHeight > vspace) && pos.top > node.offsetHeight) + top = pos.top - node.offsetHeight; + else if (pos.bottom + node.offsetHeight <= vspace) + top = pos.bottom; + if (left + node.offsetWidth > hspace) + left = hspace - node.offsetWidth; + } + node.style.top = top + "px"; + node.style.left = node.style.right = ""; + if (horiz == "right") { + left = display.sizer.clientWidth - node.offsetWidth; + node.style.right = "0px"; + } else { + if (horiz == "left") left = 0; + else if (horiz == "middle") left = (display.sizer.clientWidth - node.offsetWidth) / 2; + node.style.left = left + "px"; + } + if (scroll) + scrollIntoView(this, left, top, left + node.offsetWidth, top + node.offsetHeight); + }, + + triggerOnKeyDown: methodOp(onKeyDown), + triggerOnKeyPress: methodOp(onKeyPress), + triggerOnKeyUp: onKeyUp, + + execCommand: function(cmd) { + if (commands.hasOwnProperty(cmd)) + return commands[cmd](this); + }, + + findPosH: function(from, amount, unit, visually) { + var dir = 1; + if (amount < 0) { dir = -1; amount = -amount; } + for (var i = 0, cur = clipPos(this.doc, from); i < amount; ++i) { + cur = findPosH(this.doc, cur, dir, unit, visually); + if (cur.hitSide) break; + } + return cur; + }, + + moveH: methodOp(function(dir, unit) { + var cm = this; + cm.extendSelectionsBy(function(range) { + if (cm.display.shift || cm.doc.extend || range.empty()) + return findPosH(cm.doc, range.head, dir, unit, cm.options.rtlMoveVisually); + else + return dir < 0 ? range.from() : range.to(); + }, sel_move); + }), + + deleteH: methodOp(function(dir, unit) { + var sel = this.doc.sel, doc = this.doc; + if (sel.somethingSelected()) + doc.replaceSelection("", null, "+delete"); + else + deleteNearSelection(this, function(range) { + var other = findPosH(doc, range.head, dir, unit, false); + return dir < 0 ? {from: other, to: range.head} : {from: range.head, to: other}; + }); + }), + + findPosV: function(from, amount, unit, goalColumn) { + var dir = 1, x = goalColumn; + if (amount < 0) { dir = -1; amount = -amount; } + for (var i = 0, cur = clipPos(this.doc, from); i < amount; ++i) { + var coords = cursorCoords(this, cur, "div"); + if (x == null) x = coords.left; + else coords.left = x; + cur = findPosV(this, coords, dir, unit); + if (cur.hitSide) break; + } + return cur; + }, + + moveV: methodOp(function(dir, unit) { + var cm = this, doc = this.doc, goals = []; + var collapse = !cm.display.shift && !doc.extend && doc.sel.somethingSelected(); + doc.extendSelectionsBy(function(range) { + if (collapse) + return dir < 0 ? range.from() : range.to(); + var headPos = cursorCoords(cm, range.head, "div"); + if (range.goalColumn != null) headPos.left = range.goalColumn; + goals.push(headPos.left); + var pos = findPosV(cm, headPos, dir, unit); + if (unit == "page" && range == doc.sel.primary()) + addToScrollPos(cm, null, charCoords(cm, pos, "div").top - headPos.top); + return pos; + }, sel_move); + if (goals.length) for (var i = 0; i < doc.sel.ranges.length; i++) + doc.sel.ranges[i].goalColumn = goals[i]; + }), + + // Find the word at the given position (as returned by coordsChar). + findWordAt: function(pos) { + var doc = this.doc, line = getLine(doc, pos.line).text; + var start = pos.ch, end = pos.ch; + if (line) { + var helper = this.getHelper(pos, "wordChars"); + if ((pos.xRel < 0 || end == line.length) && start) --start; else ++end; + var startChar = line.charAt(start); + var check = isWordChar(startChar, helper) + ? function(ch) { return isWordChar(ch, helper); } + : /\s/.test(startChar) ? function(ch) {return /\s/.test(ch);} + : function(ch) {return !/\s/.test(ch) && !isWordChar(ch);}; + while (start > 0 && check(line.charAt(start - 1))) --start; + while (end < line.length && check(line.charAt(end))) ++end; + } + return new Range(Pos(pos.line, start), Pos(pos.line, end)); + }, + + toggleOverwrite: function(value) { + if (value != null && value == this.state.overwrite) return; + if (this.state.overwrite = !this.state.overwrite) + addClass(this.display.cursorDiv, "CodeMirror-overwrite"); + else + rmClass(this.display.cursorDiv, "CodeMirror-overwrite"); + + signal(this, "overwriteToggle", this, this.state.overwrite); + }, + hasFocus: function() { return this.display.input.getField() == activeElt(); }, + + scrollTo: methodOp(function(x, y) { + if (x != null || y != null) resolveScrollToPos(this); + if (x != null) this.curOp.scrollLeft = x; + if (y != null) this.curOp.scrollTop = y; + }), + getScrollInfo: function() { + var scroller = this.display.scroller; + return {left: scroller.scrollLeft, top: scroller.scrollTop, + height: scroller.scrollHeight - scrollGap(this) - this.display.barHeight, + width: scroller.scrollWidth - scrollGap(this) - this.display.barWidth, + clientHeight: displayHeight(this), clientWidth: displayWidth(this)}; + }, + + scrollIntoView: methodOp(function(range, margin) { + if (range == null) { + range = {from: this.doc.sel.primary().head, to: null}; + if (margin == null) margin = this.options.cursorScrollMargin; + } else if (typeof range == "number") { + range = {from: Pos(range, 0), to: null}; + } else if (range.from == null) { + range = {from: range, to: null}; + } + if (!range.to) range.to = range.from; + range.margin = margin || 0; + + if (range.from.line != null) { + resolveScrollToPos(this); + this.curOp.scrollToPos = range; + } else { + var sPos = calculateScrollPos(this, Math.min(range.from.left, range.to.left), + Math.min(range.from.top, range.to.top) - range.margin, + Math.max(range.from.right, range.to.right), + Math.max(range.from.bottom, range.to.bottom) + range.margin); + this.scrollTo(sPos.scrollLeft, sPos.scrollTop); + } + }), + + setSize: methodOp(function(width, height) { + var cm = this; + function interpret(val) { + return typeof val == "number" || /^\d+$/.test(String(val)) ? val + "px" : val; + } + if (width != null) cm.display.wrapper.style.width = interpret(width); + if (height != null) cm.display.wrapper.style.height = interpret(height); + if (cm.options.lineWrapping) clearLineMeasurementCache(this); + var lineNo = cm.display.viewFrom; + cm.doc.iter(lineNo, cm.display.viewTo, function(line) { + if (line.widgets) for (var i = 0; i < line.widgets.length; i++) + if (line.widgets[i].noHScroll) { regLineChange(cm, lineNo, "widget"); break; } + ++lineNo; + }); + cm.curOp.forceUpdate = true; + signal(cm, "refresh", this); + }), + + operation: function(f){return runInOp(this, f);}, + + refresh: methodOp(function() { + var oldHeight = this.display.cachedTextHeight; + regChange(this); + this.curOp.forceUpdate = true; + clearCaches(this); + this.scrollTo(this.doc.scrollLeft, this.doc.scrollTop); + updateGutterSpace(this); + if (oldHeight == null || Math.abs(oldHeight - textHeight(this.display)) > .5) + estimateLineHeights(this); + signal(this, "refresh", this); + }), + + swapDoc: methodOp(function(doc) { + var old = this.doc; + old.cm = null; + attachDoc(this, doc); + clearCaches(this); + this.display.input.reset(); + this.scrollTo(doc.scrollLeft, doc.scrollTop); + this.curOp.forceScroll = true; + signalLater(this, "swapDoc", this, old); + return old; + }), + + getInputField: function(){return this.display.input.getField();}, + getWrapperElement: function(){return this.display.wrapper;}, + getScrollerElement: function(){return this.display.scroller;}, + getGutterElement: function(){return this.display.gutters;} + }; + eventMixin(CodeMirror); + + // OPTION DEFAULTS + + // The default configuration options. + var defaults = CodeMirror.defaults = {}; + // Functions to run when options are changed. + var optionHandlers = CodeMirror.optionHandlers = {}; + + function option(name, deflt, handle, notOnInit) { + CodeMirror.defaults[name] = deflt; + if (handle) optionHandlers[name] = + notOnInit ? function(cm, val, old) {if (old != Init) handle(cm, val, old);} : handle; + } + + // Passed to option handlers when there is no old value. + var Init = CodeMirror.Init = {toString: function(){return "CodeMirror.Init";}}; + + // These two are, on init, called from the constructor because they + // have to be initialized before the editor can start at all. + option("value", "", function(cm, val) { + cm.setValue(val); + }, true); + option("mode", null, function(cm, val) { + cm.doc.modeOption = val; + loadMode(cm); + }, true); + + option("indentUnit", 2, loadMode, true); + option("indentWithTabs", false); + option("smartIndent", true); + option("tabSize", 4, function(cm) { + resetModeState(cm); + clearCaches(cm); + regChange(cm); + }, true); + option("specialChars", /[\t\u0000-\u0019\u00ad\u200b-\u200f\u2028\u2029\ufeff]/g, function(cm, val, old) { + cm.state.specialChars = new RegExp(val.source + (val.test("\t") ? "" : "|\t"), "g"); + if (old != CodeMirror.Init) cm.refresh(); + }); + option("specialCharPlaceholder", defaultSpecialCharPlaceholder, function(cm) {cm.refresh();}, true); + option("electricChars", true); + option("inputStyle", mobile ? "contenteditable" : "textarea", function() { + throw new Error("inputStyle can not (yet) be changed in a running editor"); // FIXME + }, true); + option("rtlMoveVisually", !windows); + option("wholeLineUpdateBefore", true); + + option("theme", "default", function(cm) { + themeChanged(cm); + guttersChanged(cm); + }, true); + option("keyMap", "default", function(cm, val, old) { + var next = getKeyMap(val); + var prev = old != CodeMirror.Init && getKeyMap(old); + if (prev && prev.detach) prev.detach(cm, next); + if (next.attach) next.attach(cm, prev || null); + }); + option("extraKeys", null); + + option("lineWrapping", false, wrappingChanged, true); + option("gutters", [], function(cm) { + setGuttersForLineNumbers(cm.options); + guttersChanged(cm); + }, true); + option("fixedGutter", true, function(cm, val) { + cm.display.gutters.style.left = val ? compensateForHScroll(cm.display) + "px" : "0"; + cm.refresh(); + }, true); + option("coverGutterNextToScrollbar", false, function(cm) {updateScrollbars(cm);}, true); + option("scrollbarStyle", "native", function(cm) { + initScrollbars(cm); + updateScrollbars(cm); + cm.display.scrollbars.setScrollTop(cm.doc.scrollTop); + cm.display.scrollbars.setScrollLeft(cm.doc.scrollLeft); + }, true); + option("lineNumbers", false, function(cm) { + setGuttersForLineNumbers(cm.options); + guttersChanged(cm); + }, true); + option("firstLineNumber", 1, guttersChanged, true); + option("lineNumberFormatter", function(integer) {return integer;}, guttersChanged, true); + option("showCursorWhenSelecting", false, updateSelection, true); + + option("resetSelectionOnContextMenu", true); + option("lineWiseCopyCut", true); + + option("readOnly", false, function(cm, val) { + if (val == "nocursor") { + onBlur(cm); + cm.display.input.blur(); + cm.display.disabled = true; + } else { + cm.display.disabled = false; + if (!val) cm.display.input.reset(); + } + }); + option("disableInput", false, function(cm, val) {if (!val) cm.display.input.reset();}, true); + option("dragDrop", true, dragDropChanged); + + option("cursorBlinkRate", 530); + option("cursorScrollMargin", 0); + option("cursorHeight", 1, updateSelection, true); + option("singleCursorHeightPerLine", true, updateSelection, true); + option("workTime", 100); + option("workDelay", 100); + option("flattenSpans", true, resetModeState, true); + option("addModeClass", false, resetModeState, true); + option("pollInterval", 100); + option("undoDepth", 200, function(cm, val){cm.doc.history.undoDepth = val;}); + option("historyEventDelay", 1250); + option("viewportMargin", 10, function(cm){cm.refresh();}, true); + option("maxHighlightLength", 10000, resetModeState, true); + option("moveInputWithCursor", true, function(cm, val) { + if (!val) cm.display.input.resetPosition(); + }); + + option("tabindex", null, function(cm, val) { + cm.display.input.getField().tabIndex = val || ""; + }); + option("autofocus", null); + + // MODE DEFINITION AND QUERYING + + // Known modes, by name and by MIME + var modes = CodeMirror.modes = {}, mimeModes = CodeMirror.mimeModes = {}; + + // Extra arguments are stored as the mode's dependencies, which is + // used by (legacy) mechanisms like loadmode.js to automatically + // load a mode. (Preferred mechanism is the require/define calls.) + CodeMirror.defineMode = function(name, mode) { + if (!CodeMirror.defaults.mode && name != "null") CodeMirror.defaults.mode = name; + if (arguments.length > 2) + mode.dependencies = Array.prototype.slice.call(arguments, 2); + modes[name] = mode; + }; + + CodeMirror.defineMIME = function(mime, spec) { + mimeModes[mime] = spec; + }; + + // Given a MIME type, a {name, ...options} config object, or a name + // string, return a mode config object. + CodeMirror.resolveMode = function(spec) { + if (typeof spec == "string" && mimeModes.hasOwnProperty(spec)) { + spec = mimeModes[spec]; + } else if (spec && typeof spec.name == "string" && mimeModes.hasOwnProperty(spec.name)) { + var found = mimeModes[spec.name]; + if (typeof found == "string") found = {name: found}; + spec = createObj(found, spec); + spec.name = found.name; + } else if (typeof spec == "string" && /^[\w\-]+\/[\w\-]+\+xml$/.test(spec)) { + return CodeMirror.resolveMode("application/xml"); + } + if (typeof spec == "string") return {name: spec}; + else return spec || {name: "null"}; + }; + + // Given a mode spec (anything that resolveMode accepts), find and + // initialize an actual mode object. + CodeMirror.getMode = function(options, spec) { + var spec = CodeMirror.resolveMode(spec); + var mfactory = modes[spec.name]; + if (!mfactory) return CodeMirror.getMode(options, "text/plain"); + var modeObj = mfactory(options, spec); + if (modeExtensions.hasOwnProperty(spec.name)) { + var exts = modeExtensions[spec.name]; + for (var prop in exts) { + if (!exts.hasOwnProperty(prop)) continue; + if (modeObj.hasOwnProperty(prop)) modeObj["_" + prop] = modeObj[prop]; + modeObj[prop] = exts[prop]; + } + } + modeObj.name = spec.name; + if (spec.helperType) modeObj.helperType = spec.helperType; + if (spec.modeProps) for (var prop in spec.modeProps) + modeObj[prop] = spec.modeProps[prop]; + + return modeObj; + }; + + // Minimal default mode. + CodeMirror.defineMode("null", function() { + return {token: function(stream) {stream.skipToEnd();}}; + }); + CodeMirror.defineMIME("text/plain", "null"); + + // This can be used to attach properties to mode objects from + // outside the actual mode definition. + var modeExtensions = CodeMirror.modeExtensions = {}; + CodeMirror.extendMode = function(mode, properties) { + var exts = modeExtensions.hasOwnProperty(mode) ? modeExtensions[mode] : (modeExtensions[mode] = {}); + copyObj(properties, exts); + }; + + // EXTENSIONS + + CodeMirror.defineExtension = function(name, func) { + CodeMirror.prototype[name] = func; + }; + CodeMirror.defineDocExtension = function(name, func) { + Doc.prototype[name] = func; + }; + CodeMirror.defineOption = option; + + var initHooks = []; + CodeMirror.defineInitHook = function(f) {initHooks.push(f);}; + + var helpers = CodeMirror.helpers = {}; + CodeMirror.registerHelper = function(type, name, value) { + if (!helpers.hasOwnProperty(type)) helpers[type] = CodeMirror[type] = {_global: []}; + helpers[type][name] = value; + }; + CodeMirror.registerGlobalHelper = function(type, name, predicate, value) { + CodeMirror.registerHelper(type, name, value); + helpers[type]._global.push({pred: predicate, val: value}); + }; + + // MODE STATE HANDLING + + // Utility functions for working with state. Exported because nested + // modes need to do this for their inner modes. + + var copyState = CodeMirror.copyState = function(mode, state) { + if (state === true) return state; + if (mode.copyState) return mode.copyState(state); + var nstate = {}; + for (var n in state) { + var val = state[n]; + if (val instanceof Array) val = val.concat([]); + nstate[n] = val; + } + return nstate; + }; + + var startState = CodeMirror.startState = function(mode, a1, a2) { + return mode.startState ? mode.startState(a1, a2) : true; + }; + + // Given a mode and a state (for that mode), find the inner mode and + // state at the position that the state refers to. + CodeMirror.innerMode = function(mode, state) { + while (mode.innerMode) { + var info = mode.innerMode(state); + if (!info || info.mode == mode) break; + state = info.state; + mode = info.mode; + } + return info || {mode: mode, state: state}; + }; + + // STANDARD COMMANDS + + // Commands are parameter-less actions that can be performed on an + // editor, mostly used for keybindings. + var commands = CodeMirror.commands = { + selectAll: function(cm) {cm.setSelection(Pos(cm.firstLine(), 0), Pos(cm.lastLine()), sel_dontScroll);}, + singleSelection: function(cm) { + cm.setSelection(cm.getCursor("anchor"), cm.getCursor("head"), sel_dontScroll); + }, + killLine: function(cm) { + deleteNearSelection(cm, function(range) { + if (range.empty()) { + var len = getLine(cm.doc, range.head.line).text.length; + if (range.head.ch == len && range.head.line < cm.lastLine()) + return {from: range.head, to: Pos(range.head.line + 1, 0)}; + else + return {from: range.head, to: Pos(range.head.line, len)}; + } else { + return {from: range.from(), to: range.to()}; + } + }); + }, + deleteLine: function(cm) { + deleteNearSelection(cm, function(range) { + return {from: Pos(range.from().line, 0), + to: clipPos(cm.doc, Pos(range.to().line + 1, 0))}; + }); + }, + delLineLeft: function(cm) { + deleteNearSelection(cm, function(range) { + return {from: Pos(range.from().line, 0), to: range.from()}; + }); + }, + delWrappedLineLeft: function(cm) { + deleteNearSelection(cm, function(range) { + var top = cm.charCoords(range.head, "div").top + 5; + var leftPos = cm.coordsChar({left: 0, top: top}, "div"); + return {from: leftPos, to: range.from()}; + }); + }, + delWrappedLineRight: function(cm) { + deleteNearSelection(cm, function(range) { + var top = cm.charCoords(range.head, "div").top + 5; + var rightPos = cm.coordsChar({left: cm.display.lineDiv.offsetWidth + 100, top: top}, "div"); + return {from: range.from(), to: rightPos }; + }); + }, + undo: function(cm) {cm.undo();}, + redo: function(cm) {cm.redo();}, + undoSelection: function(cm) {cm.undoSelection();}, + redoSelection: function(cm) {cm.redoSelection();}, + goDocStart: function(cm) {cm.extendSelection(Pos(cm.firstLine(), 0));}, + goDocEnd: function(cm) {cm.extendSelection(Pos(cm.lastLine()));}, + goLineStart: function(cm) { + cm.extendSelectionsBy(function(range) { return lineStart(cm, range.head.line); }, + {origin: "+move", bias: 1}); + }, + goLineStartSmart: function(cm) { + cm.extendSelectionsBy(function(range) { + return lineStartSmart(cm, range.head); + }, {origin: "+move", bias: 1}); + }, + goLineEnd: function(cm) { + cm.extendSelectionsBy(function(range) { return lineEnd(cm, range.head.line); }, + {origin: "+move", bias: -1}); + }, + goLineRight: function(cm) { + cm.extendSelectionsBy(function(range) { + var top = cm.charCoords(range.head, "div").top + 5; + return cm.coordsChar({left: cm.display.lineDiv.offsetWidth + 100, top: top}, "div"); + }, sel_move); + }, + goLineLeft: function(cm) { + cm.extendSelectionsBy(function(range) { + var top = cm.charCoords(range.head, "div").top + 5; + return cm.coordsChar({left: 0, top: top}, "div"); + }, sel_move); + }, + goLineLeftSmart: function(cm) { + cm.extendSelectionsBy(function(range) { + var top = cm.charCoords(range.head, "div").top + 5; + var pos = cm.coordsChar({left: 0, top: top}, "div"); + if (pos.ch < cm.getLine(pos.line).search(/\S/)) return lineStartSmart(cm, range.head); + return pos; + }, sel_move); + }, + goLineUp: function(cm) {cm.moveV(-1, "line");}, + goLineDown: function(cm) {cm.moveV(1, "line");}, + goPageUp: function(cm) {cm.moveV(-1, "page");}, + goPageDown: function(cm) {cm.moveV(1, "page");}, + goCharLeft: function(cm) {cm.moveH(-1, "char");}, + goCharRight: function(cm) {cm.moveH(1, "char");}, + goColumnLeft: function(cm) {cm.moveH(-1, "column");}, + goColumnRight: function(cm) {cm.moveH(1, "column");}, + goWordLeft: function(cm) {cm.moveH(-1, "word");}, + goGroupRight: function(cm) {cm.moveH(1, "group");}, + goGroupLeft: function(cm) {cm.moveH(-1, "group");}, + goWordRight: function(cm) {cm.moveH(1, "word");}, + delCharBefore: function(cm) {cm.deleteH(-1, "char");}, + delCharAfter: function(cm) {cm.deleteH(1, "char");}, + delWordBefore: function(cm) {cm.deleteH(-1, "word");}, + delWordAfter: function(cm) {cm.deleteH(1, "word");}, + delGroupBefore: function(cm) {cm.deleteH(-1, "group");}, + delGroupAfter: function(cm) {cm.deleteH(1, "group");}, + indentAuto: function(cm) {cm.indentSelection("smart");}, + indentMore: function(cm) {cm.indentSelection("add");}, + indentLess: function(cm) {cm.indentSelection("subtract");}, + insertTab: function(cm) {cm.replaceSelection("\t");}, + insertSoftTab: function(cm) { + var spaces = [], ranges = cm.listSelections(), tabSize = cm.options.tabSize; + for (var i = 0; i < ranges.length; i++) { + var pos = ranges[i].from(); + var col = countColumn(cm.getLine(pos.line), pos.ch, tabSize); + spaces.push(new Array(tabSize - col % tabSize + 1).join(" ")); + } + cm.replaceSelections(spaces); + }, + defaultTab: function(cm) { + if (cm.somethingSelected()) cm.indentSelection("add"); + else cm.execCommand("insertTab"); + }, + transposeChars: function(cm) { + runInOp(cm, function() { + var ranges = cm.listSelections(), newSel = []; + for (var i = 0; i < ranges.length; i++) { + var cur = ranges[i].head, line = getLine(cm.doc, cur.line).text; + if (line) { + if (cur.ch == line.length) cur = new Pos(cur.line, cur.ch - 1); + if (cur.ch > 0) { + cur = new Pos(cur.line, cur.ch + 1); + cm.replaceRange(line.charAt(cur.ch - 1) + line.charAt(cur.ch - 2), + Pos(cur.line, cur.ch - 2), cur, "+transpose"); + } else if (cur.line > cm.doc.first) { + var prev = getLine(cm.doc, cur.line - 1).text; + if (prev) + cm.replaceRange(line.charAt(0) + "\n" + prev.charAt(prev.length - 1), + Pos(cur.line - 1, prev.length - 1), Pos(cur.line, 1), "+transpose"); + } + } + newSel.push(new Range(cur, cur)); + } + cm.setSelections(newSel); + }); + }, + newlineAndIndent: function(cm) { + runInOp(cm, function() { + var len = cm.listSelections().length; + for (var i = 0; i < len; i++) { + var range = cm.listSelections()[i]; + cm.replaceRange("\n", range.anchor, range.head, "+input"); + cm.indentLine(range.from().line + 1, null, true); + ensureCursorVisible(cm); + } + }); + }, + toggleOverwrite: function(cm) {cm.toggleOverwrite();} + }; + + + // STANDARD KEYMAPS + + var keyMap = CodeMirror.keyMap = {}; + + keyMap.basic = { + "Left": "goCharLeft", "Right": "goCharRight", "Up": "goLineUp", "Down": "goLineDown", + "End": "goLineEnd", "Home": "goLineStartSmart", "PageUp": "goPageUp", "PageDown": "goPageDown", + "Delete": "delCharAfter", "Backspace": "delCharBefore", "Shift-Backspace": "delCharBefore", + "Tab": "defaultTab", "Shift-Tab": "indentAuto", + "Enter": "newlineAndIndent", "Insert": "toggleOverwrite", + "Esc": "singleSelection" + }; + // Note that the save and find-related commands aren't defined by + // default. User code or addons can define them. Unknown commands + // are simply ignored. + keyMap.pcDefault = { + "Ctrl-A": "selectAll", "Ctrl-D": "deleteLine", "Ctrl-Z": "undo", "Shift-Ctrl-Z": "redo", "Ctrl-Y": "redo", + "Ctrl-Home": "goDocStart", "Ctrl-End": "goDocEnd", "Ctrl-Up": "goLineUp", "Ctrl-Down": "goLineDown", + "Ctrl-Left": "goGroupLeft", "Ctrl-Right": "goGroupRight", "Alt-Left": "goLineStart", "Alt-Right": "goLineEnd", + "Ctrl-Backspace": "delGroupBefore", "Ctrl-Delete": "delGroupAfter", "Ctrl-S": "save", "Ctrl-F": "find", + "Ctrl-G": "findNext", "Shift-Ctrl-G": "findPrev", "Shift-Ctrl-F": "replace", "Shift-Ctrl-R": "replaceAll", + "Ctrl-[": "indentLess", "Ctrl-]": "indentMore", + "Ctrl-U": "undoSelection", "Shift-Ctrl-U": "redoSelection", "Alt-U": "redoSelection", + fallthrough: "basic" + }; + // Very basic readline/emacs-style bindings, which are standard on Mac. + keyMap.emacsy = { + "Ctrl-F": "goCharRight", "Ctrl-B": "goCharLeft", "Ctrl-P": "goLineUp", "Ctrl-N": "goLineDown", + "Alt-F": "goWordRight", "Alt-B": "goWordLeft", "Ctrl-A": "goLineStart", "Ctrl-E": "goLineEnd", + "Ctrl-V": "goPageDown", "Shift-Ctrl-V": "goPageUp", "Ctrl-D": "delCharAfter", "Ctrl-H": "delCharBefore", + "Alt-D": "delWordAfter", "Alt-Backspace": "delWordBefore", "Ctrl-K": "killLine", "Ctrl-T": "transposeChars" + }; + keyMap.macDefault = { + "Cmd-A": "selectAll", "Cmd-D": "deleteLine", "Cmd-Z": "undo", "Shift-Cmd-Z": "redo", "Cmd-Y": "redo", + "Cmd-Home": "goDocStart", "Cmd-Up": "goDocStart", "Cmd-End": "goDocEnd", "Cmd-Down": "goDocEnd", "Alt-Left": "goGroupLeft", + "Alt-Right": "goGroupRight", "Cmd-Left": "goLineLeft", "Cmd-Right": "goLineRight", "Alt-Backspace": "delGroupBefore", + "Ctrl-Alt-Backspace": "delGroupAfter", "Alt-Delete": "delGroupAfter", "Cmd-S": "save", "Cmd-F": "find", + "Cmd-G": "findNext", "Shift-Cmd-G": "findPrev", "Cmd-Alt-F": "replace", "Shift-Cmd-Alt-F": "replaceAll", + "Cmd-[": "indentLess", "Cmd-]": "indentMore", "Cmd-Backspace": "delWrappedLineLeft", "Cmd-Delete": "delWrappedLineRight", + "Cmd-U": "undoSelection", "Shift-Cmd-U": "redoSelection", "Ctrl-Up": "goDocStart", "Ctrl-Down": "goDocEnd", + fallthrough: ["basic", "emacsy"] + }; + keyMap["default"] = mac ? keyMap.macDefault : keyMap.pcDefault; + + // KEYMAP DISPATCH + + function normalizeKeyName(name) { + var parts = name.split(/-(?!$)/), name = parts[parts.length - 1]; + var alt, ctrl, shift, cmd; + for (var i = 0; i < parts.length - 1; i++) { + var mod = parts[i]; + if (/^(cmd|meta|m)$/i.test(mod)) cmd = true; + else if (/^a(lt)?$/i.test(mod)) alt = true; + else if (/^(c|ctrl|control)$/i.test(mod)) ctrl = true; + else if (/^s(hift)$/i.test(mod)) shift = true; + else throw new Error("Unrecognized modifier name: " + mod); + } + if (alt) name = "Alt-" + name; + if (ctrl) name = "Ctrl-" + name; + if (cmd) name = "Cmd-" + name; + if (shift) name = "Shift-" + name; + return name; + } + + // This is a kludge to keep keymaps mostly working as raw objects + // (backwards compatibility) while at the same time support features + // like normalization and multi-stroke key bindings. It compiles a + // new normalized keymap, and then updates the old object to reflect + // this. + CodeMirror.normalizeKeyMap = function(keymap) { + var copy = {}; + for (var keyname in keymap) if (keymap.hasOwnProperty(keyname)) { + var value = keymap[keyname]; + if (/^(name|fallthrough|(de|at)tach)$/.test(keyname)) continue; + if (value == "...") { delete keymap[keyname]; continue; } + + var keys = map(keyname.split(" "), normalizeKeyName); + for (var i = 0; i < keys.length; i++) { + var val, name; + if (i == keys.length - 1) { + name = keyname; + val = value; + } else { + name = keys.slice(0, i + 1).join(" "); + val = "..."; + } + var prev = copy[name]; + if (!prev) copy[name] = val; + else if (prev != val) throw new Error("Inconsistent bindings for " + name); + } + delete keymap[keyname]; + } + for (var prop in copy) keymap[prop] = copy[prop]; + return keymap; + }; + + var lookupKey = CodeMirror.lookupKey = function(key, map, handle, context) { + map = getKeyMap(map); + var found = map.call ? map.call(key, context) : map[key]; + if (found === false) return "nothing"; + if (found === "...") return "multi"; + if (found != null && handle(found)) return "handled"; + + if (map.fallthrough) { + if (Object.prototype.toString.call(map.fallthrough) != "[object Array]") + return lookupKey(key, map.fallthrough, handle, context); + for (var i = 0; i < map.fallthrough.length; i++) { + var result = lookupKey(key, map.fallthrough[i], handle, context); + if (result) return result; + } + } + }; + + // Modifier key presses don't count as 'real' key presses for the + // purpose of keymap fallthrough. + var isModifierKey = CodeMirror.isModifierKey = function(value) { + var name = typeof value == "string" ? value : keyNames[value.keyCode]; + return name == "Ctrl" || name == "Alt" || name == "Shift" || name == "Mod"; + }; + + // Look up the name of a key as indicated by an event object. + var keyName = CodeMirror.keyName = function(event, noShift) { + if (presto && event.keyCode == 34 && event["char"]) return false; + var base = keyNames[event.keyCode], name = base; + if (name == null || event.altGraphKey) return false; + if (event.altKey && base != "Alt") name = "Alt-" + name; + if ((flipCtrlCmd ? event.metaKey : event.ctrlKey) && base != "Ctrl") name = "Ctrl-" + name; + if ((flipCtrlCmd ? event.ctrlKey : event.metaKey) && base != "Cmd") name = "Cmd-" + name; + if (!noShift && event.shiftKey && base != "Shift") name = "Shift-" + name; + return name; + }; + + function getKeyMap(val) { + return typeof val == "string" ? keyMap[val] : val; + } + + // FROMTEXTAREA + + CodeMirror.fromTextArea = function(textarea, options) { + options = options ? copyObj(options) : {}; + options.value = textarea.value; + if (!options.tabindex && textarea.tabIndex) + options.tabindex = textarea.tabIndex; + if (!options.placeholder && textarea.placeholder) + options.placeholder = textarea.placeholder; + // Set autofocus to true if this textarea is focused, or if it has + // autofocus and no other element is focused. + if (options.autofocus == null) { + var hasFocus = activeElt(); + options.autofocus = hasFocus == textarea || + textarea.getAttribute("autofocus") != null && hasFocus == document.body; + } + + function save() {textarea.value = cm.getValue();} + if (textarea.form) { + on(textarea.form, "submit", save); + // Deplorable hack to make the submit method do the right thing. + if (!options.leaveSubmitMethodAlone) { + var form = textarea.form, realSubmit = form.submit; + try { + var wrappedSubmit = form.submit = function() { + save(); + form.submit = realSubmit; + form.submit(); + form.submit = wrappedSubmit; + }; + } catch(e) {} + } + } + + options.finishInit = function(cm) { + cm.save = save; + cm.getTextArea = function() { return textarea; }; + cm.toTextArea = function() { + cm.toTextArea = isNaN; // Prevent this from being ran twice + save(); + textarea.parentNode.removeChild(cm.getWrapperElement()); + textarea.style.display = ""; + if (textarea.form) { + off(textarea.form, "submit", save); + if (typeof textarea.form.submit == "function") + textarea.form.submit = realSubmit; + } + }; + }; + + textarea.style.display = "none"; + var cm = CodeMirror(function(node) { + textarea.parentNode.insertBefore(node, textarea.nextSibling); + }, options); + return cm; + }; + + // STRING STREAM + + // Fed to the mode parsers, provides helper functions to make + // parsers more succinct. + + var StringStream = CodeMirror.StringStream = function(string, tabSize) { + this.pos = this.start = 0; + this.string = string; + this.tabSize = tabSize || 8; + this.lastColumnPos = this.lastColumnValue = 0; + this.lineStart = 0; + }; + + StringStream.prototype = { + eol: function() {return this.pos >= this.string.length;}, + sol: function() {return this.pos == this.lineStart;}, + peek: function() {return this.string.charAt(this.pos) || undefined;}, + next: function() { + if (this.pos < this.string.length) + return this.string.charAt(this.pos++); + }, + eat: function(match) { + var ch = this.string.charAt(this.pos); + if (typeof match == "string") var ok = ch == match; + else var ok = ch && (match.test ? match.test(ch) : match(ch)); + if (ok) {++this.pos; return ch;} + }, + eatWhile: function(match) { + var start = this.pos; + while (this.eat(match)){} + return this.pos > start; + }, + eatSpace: function() { + var start = this.pos; + while (/[\s\u00a0]/.test(this.string.charAt(this.pos))) ++this.pos; + return this.pos > start; + }, + skipToEnd: function() {this.pos = this.string.length;}, + skipTo: function(ch) { + var found = this.string.indexOf(ch, this.pos); + if (found > -1) {this.pos = found; return true;} + }, + backUp: function(n) {this.pos -= n;}, + column: function() { + if (this.lastColumnPos < this.start) { + this.lastColumnValue = countColumn(this.string, this.start, this.tabSize, this.lastColumnPos, this.lastColumnValue); + this.lastColumnPos = this.start; + } + return this.lastColumnValue - (this.lineStart ? countColumn(this.string, this.lineStart, this.tabSize) : 0); + }, + indentation: function() { + return countColumn(this.string, null, this.tabSize) - + (this.lineStart ? countColumn(this.string, this.lineStart, this.tabSize) : 0); + }, + match: function(pattern, consume, caseInsensitive) { + if (typeof pattern == "string") { + var cased = function(str) {return caseInsensitive ? str.toLowerCase() : str;}; + var substr = this.string.substr(this.pos, pattern.length); + if (cased(substr) == cased(pattern)) { + if (consume !== false) this.pos += pattern.length; + return true; + } + } else { + var match = this.string.slice(this.pos).match(pattern); + if (match && match.index > 0) return null; + if (match && consume !== false) this.pos += match[0].length; + return match; + } + }, + current: function(){return this.string.slice(this.start, this.pos);}, + hideFirstChars: function(n, inner) { + this.lineStart += n; + try { return inner(); } + finally { this.lineStart -= n; } + } + }; + + // TEXTMARKERS + + // Created with markText and setBookmark methods. A TextMarker is a + // handle that can be used to clear or find a marked position in the + // document. Line objects hold arrays (markedSpans) containing + // {from, to, marker} object pointing to such marker objects, and + // indicating that such a marker is present on that line. Multiple + // lines may point to the same marker when it spans across lines. + // The spans will have null for their from/to properties when the + // marker continues beyond the start/end of the line. Markers have + // links back to the lines they currently touch. + + var nextMarkerId = 0; + + var TextMarker = CodeMirror.TextMarker = function(doc, type) { + this.lines = []; + this.type = type; + this.doc = doc; + this.id = ++nextMarkerId; + }; + eventMixin(TextMarker); + + // Clear the marker. + TextMarker.prototype.clear = function() { + if (this.explicitlyCleared) return; + var cm = this.doc.cm, withOp = cm && !cm.curOp; + if (withOp) startOperation(cm); + if (hasHandler(this, "clear")) { + var found = this.find(); + if (found) signalLater(this, "clear", found.from, found.to); + } + var min = null, max = null; + for (var i = 0; i < this.lines.length; ++i) { + var line = this.lines[i]; + var span = getMarkedSpanFor(line.markedSpans, this); + if (cm && !this.collapsed) regLineChange(cm, lineNo(line), "text"); + else if (cm) { + if (span.to != null) max = lineNo(line); + if (span.from != null) min = lineNo(line); + } + line.markedSpans = removeMarkedSpan(line.markedSpans, span); + if (span.from == null && this.collapsed && !lineIsHidden(this.doc, line) && cm) + updateLineHeight(line, textHeight(cm.display)); + } + if (cm && this.collapsed && !cm.options.lineWrapping) for (var i = 0; i < this.lines.length; ++i) { + var visual = visualLine(this.lines[i]), len = lineLength(visual); + if (len > cm.display.maxLineLength) { + cm.display.maxLine = visual; + cm.display.maxLineLength = len; + cm.display.maxLineChanged = true; + } + } + + if (min != null && cm && this.collapsed) regChange(cm, min, max + 1); + this.lines.length = 0; + this.explicitlyCleared = true; + if (this.atomic && this.doc.cantEdit) { + this.doc.cantEdit = false; + if (cm) reCheckSelection(cm.doc); + } + if (cm) signalLater(cm, "markerCleared", cm, this); + if (withOp) endOperation(cm); + if (this.parent) this.parent.clear(); + }; + + // Find the position of the marker in the document. Returns a {from, + // to} object by default. Side can be passed to get a specific side + // -- 0 (both), -1 (left), or 1 (right). When lineObj is true, the + // Pos objects returned contain a line object, rather than a line + // number (used to prevent looking up the same line twice). + TextMarker.prototype.find = function(side, lineObj) { + if (side == null && this.type == "bookmark") side = 1; + var from, to; + for (var i = 0; i < this.lines.length; ++i) { + var line = this.lines[i]; + var span = getMarkedSpanFor(line.markedSpans, this); + if (span.from != null) { + from = Pos(lineObj ? line : lineNo(line), span.from); + if (side == -1) return from; + } + if (span.to != null) { + to = Pos(lineObj ? line : lineNo(line), span.to); + if (side == 1) return to; + } + } + return from && {from: from, to: to}; + }; + + // Signals that the marker's widget changed, and surrounding layout + // should be recomputed. + TextMarker.prototype.changed = function() { + var pos = this.find(-1, true), widget = this, cm = this.doc.cm; + if (!pos || !cm) return; + runInOp(cm, function() { + var line = pos.line, lineN = lineNo(pos.line); + var view = findViewForLine(cm, lineN); + if (view) { + clearLineMeasurementCacheFor(view); + cm.curOp.selectionChanged = cm.curOp.forceUpdate = true; + } + cm.curOp.updateMaxLine = true; + if (!lineIsHidden(widget.doc, line) && widget.height != null) { + var oldHeight = widget.height; + widget.height = null; + var dHeight = widgetHeight(widget) - oldHeight; + if (dHeight) + updateLineHeight(line, line.height + dHeight); + } + }); + }; + + TextMarker.prototype.attachLine = function(line) { + if (!this.lines.length && this.doc.cm) { + var op = this.doc.cm.curOp; + if (!op.maybeHiddenMarkers || indexOf(op.maybeHiddenMarkers, this) == -1) + (op.maybeUnhiddenMarkers || (op.maybeUnhiddenMarkers = [])).push(this); + } + this.lines.push(line); + }; + TextMarker.prototype.detachLine = function(line) { + this.lines.splice(indexOf(this.lines, line), 1); + if (!this.lines.length && this.doc.cm) { + var op = this.doc.cm.curOp; + (op.maybeHiddenMarkers || (op.maybeHiddenMarkers = [])).push(this); + } + }; + + // Collapsed markers have unique ids, in order to be able to order + // them, which is needed for uniquely determining an outer marker + // when they overlap (they may nest, but not partially overlap). + var nextMarkerId = 0; + + // Create a marker, wire it up to the right lines, and + function markText(doc, from, to, options, type) { + // Shared markers (across linked documents) are handled separately + // (markTextShared will call out to this again, once per + // document). + if (options && options.shared) return markTextShared(doc, from, to, options, type); + // Ensure we are in an operation. + if (doc.cm && !doc.cm.curOp) return operation(doc.cm, markText)(doc, from, to, options, type); + + var marker = new TextMarker(doc, type), diff = cmp(from, to); + if (options) copyObj(options, marker, false); + // Don't connect empty markers unless clearWhenEmpty is false + if (diff > 0 || diff == 0 && marker.clearWhenEmpty !== false) + return marker; + if (marker.replacedWith) { + // Showing up as a widget implies collapsed (widget replaces text) + marker.collapsed = true; + marker.widgetNode = elt("span", [marker.replacedWith], "CodeMirror-widget"); + if (!options.handleMouseEvents) marker.widgetNode.setAttribute("cm-ignore-events", "true"); + if (options.insertLeft) marker.widgetNode.insertLeft = true; + } + if (marker.collapsed) { + if (conflictingCollapsedRange(doc, from.line, from, to, marker) || + from.line != to.line && conflictingCollapsedRange(doc, to.line, from, to, marker)) + throw new Error("Inserting collapsed marker partially overlapping an existing one"); + sawCollapsedSpans = true; + } + + if (marker.addToHistory) + addChangeToHistory(doc, {from: from, to: to, origin: "markText"}, doc.sel, NaN); + + var curLine = from.line, cm = doc.cm, updateMaxLine; + doc.iter(curLine, to.line + 1, function(line) { + if (cm && marker.collapsed && !cm.options.lineWrapping && visualLine(line) == cm.display.maxLine) + updateMaxLine = true; + if (marker.collapsed && curLine != from.line) updateLineHeight(line, 0); + addMarkedSpan(line, new MarkedSpan(marker, + curLine == from.line ? from.ch : null, + curLine == to.line ? to.ch : null)); + ++curLine; + }); + // lineIsHidden depends on the presence of the spans, so needs a second pass + if (marker.collapsed) doc.iter(from.line, to.line + 1, function(line) { + if (lineIsHidden(doc, line)) updateLineHeight(line, 0); + }); + + if (marker.clearOnEnter) on(marker, "beforeCursorEnter", function() { marker.clear(); }); + + if (marker.readOnly) { + sawReadOnlySpans = true; + if (doc.history.done.length || doc.history.undone.length) + doc.clearHistory(); + } + if (marker.collapsed) { + marker.id = ++nextMarkerId; + marker.atomic = true; + } + if (cm) { + // Sync editor state + if (updateMaxLine) cm.curOp.updateMaxLine = true; + if (marker.collapsed) + regChange(cm, from.line, to.line + 1); + else if (marker.className || marker.title || marker.startStyle || marker.endStyle || marker.css) + for (var i = from.line; i <= to.line; i++) regLineChange(cm, i, "text"); + if (marker.atomic) reCheckSelection(cm.doc); + signalLater(cm, "markerAdded", cm, marker); + } + return marker; + } + + // SHARED TEXTMARKERS + + // A shared marker spans multiple linked documents. It is + // implemented as a meta-marker-object controlling multiple normal + // markers. + var SharedTextMarker = CodeMirror.SharedTextMarker = function(markers, primary) { + this.markers = markers; + this.primary = primary; + for (var i = 0; i < markers.length; ++i) + markers[i].parent = this; + }; + eventMixin(SharedTextMarker); + + SharedTextMarker.prototype.clear = function() { + if (this.explicitlyCleared) return; + this.explicitlyCleared = true; + for (var i = 0; i < this.markers.length; ++i) + this.markers[i].clear(); + signalLater(this, "clear"); + }; + SharedTextMarker.prototype.find = function(side, lineObj) { + return this.primary.find(side, lineObj); + }; + + function markTextShared(doc, from, to, options, type) { + options = copyObj(options); + options.shared = false; + var markers = [markText(doc, from, to, options, type)], primary = markers[0]; + var widget = options.widgetNode; + linkedDocs(doc, function(doc) { + if (widget) options.widgetNode = widget.cloneNode(true); + markers.push(markText(doc, clipPos(doc, from), clipPos(doc, to), options, type)); + for (var i = 0; i < doc.linked.length; ++i) + if (doc.linked[i].isParent) return; + primary = lst(markers); + }); + return new SharedTextMarker(markers, primary); + } + + function findSharedMarkers(doc) { + return doc.findMarks(Pos(doc.first, 0), doc.clipPos(Pos(doc.lastLine())), + function(m) { return m.parent; }); + } + + function copySharedMarkers(doc, markers) { + for (var i = 0; i < markers.length; i++) { + var marker = markers[i], pos = marker.find(); + var mFrom = doc.clipPos(pos.from), mTo = doc.clipPos(pos.to); + if (cmp(mFrom, mTo)) { + var subMark = markText(doc, mFrom, mTo, marker.primary, marker.primary.type); + marker.markers.push(subMark); + subMark.parent = marker; + } + } + } + + function detachSharedMarkers(markers) { + for (var i = 0; i < markers.length; i++) { + var marker = markers[i], linked = [marker.primary.doc];; + linkedDocs(marker.primary.doc, function(d) { linked.push(d); }); + for (var j = 0; j < marker.markers.length; j++) { + var subMarker = marker.markers[j]; + if (indexOf(linked, subMarker.doc) == -1) { + subMarker.parent = null; + marker.markers.splice(j--, 1); + } + } + } + } + + // TEXTMARKER SPANS + + function MarkedSpan(marker, from, to) { + this.marker = marker; + this.from = from; this.to = to; + } + + // Search an array of spans for a span matching the given marker. + function getMarkedSpanFor(spans, marker) { + if (spans) for (var i = 0; i < spans.length; ++i) { + var span = spans[i]; + if (span.marker == marker) return span; + } + } + // Remove a span from an array, returning undefined if no spans are + // left (we don't store arrays for lines without spans). + function removeMarkedSpan(spans, span) { + for (var r, i = 0; i < spans.length; ++i) + if (spans[i] != span) (r || (r = [])).push(spans[i]); + return r; + } + // Add a span to a line. + function addMarkedSpan(line, span) { + line.markedSpans = line.markedSpans ? line.markedSpans.concat([span]) : [span]; + span.marker.attachLine(line); + } + + // Used for the algorithm that adjusts markers for a change in the + // document. These functions cut an array of spans at a given + // character position, returning an array of remaining chunks (or + // undefined if nothing remains). + function markedSpansBefore(old, startCh, isInsert) { + if (old) for (var i = 0, nw; i < old.length; ++i) { + var span = old[i], marker = span.marker; + var startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= startCh : span.from < startCh); + if (startsBefore || span.from == startCh && marker.type == "bookmark" && (!isInsert || !span.marker.insertLeft)) { + var endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= startCh : span.to > startCh); + (nw || (nw = [])).push(new MarkedSpan(marker, span.from, endsAfter ? null : span.to)); + } + } + return nw; + } + function markedSpansAfter(old, endCh, isInsert) { + if (old) for (var i = 0, nw; i < old.length; ++i) { + var span = old[i], marker = span.marker; + var endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= endCh : span.to > endCh); + if (endsAfter || span.from == endCh && marker.type == "bookmark" && (!isInsert || span.marker.insertLeft)) { + var startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= endCh : span.from < endCh); + (nw || (nw = [])).push(new MarkedSpan(marker, startsBefore ? null : span.from - endCh, + span.to == null ? null : span.to - endCh)); + } + } + return nw; + } + + // Given a change object, compute the new set of marker spans that + // cover the line in which the change took place. Removes spans + // entirely within the change, reconnects spans belonging to the + // same marker that appear on both sides of the change, and cuts off + // spans partially within the change. Returns an array of span + // arrays with one element for each line in (after) the change. + function stretchSpansOverChange(doc, change) { + if (change.full) return null; + var oldFirst = isLine(doc, change.from.line) && getLine(doc, change.from.line).markedSpans; + var oldLast = isLine(doc, change.to.line) && getLine(doc, change.to.line).markedSpans; + if (!oldFirst && !oldLast) return null; + + var startCh = change.from.ch, endCh = change.to.ch, isInsert = cmp(change.from, change.to) == 0; + // Get the spans that 'stick out' on both sides + var first = markedSpansBefore(oldFirst, startCh, isInsert); + var last = markedSpansAfter(oldLast, endCh, isInsert); + + // Next, merge those two ends + var sameLine = change.text.length == 1, offset = lst(change.text).length + (sameLine ? startCh : 0); + if (first) { + // Fix up .to properties of first + for (var i = 0; i < first.length; ++i) { + var span = first[i]; + if (span.to == null) { + var found = getMarkedSpanFor(last, span.marker); + if (!found) span.to = startCh; + else if (sameLine) span.to = found.to == null ? null : found.to + offset; + } + } + } + if (last) { + // Fix up .from in last (or move them into first in case of sameLine) + for (var i = 0; i < last.length; ++i) { + var span = last[i]; + if (span.to != null) span.to += offset; + if (span.from == null) { + var found = getMarkedSpanFor(first, span.marker); + if (!found) { + span.from = offset; + if (sameLine) (first || (first = [])).push(span); + } + } else { + span.from += offset; + if (sameLine) (first || (first = [])).push(span); + } + } + } + // Make sure we didn't create any zero-length spans + if (first) first = clearEmptySpans(first); + if (last && last != first) last = clearEmptySpans(last); + + var newMarkers = [first]; + if (!sameLine) { + // Fill gap with whole-line-spans + var gap = change.text.length - 2, gapMarkers; + if (gap > 0 && first) + for (var i = 0; i < first.length; ++i) + if (first[i].to == null) + (gapMarkers || (gapMarkers = [])).push(new MarkedSpan(first[i].marker, null, null)); + for (var i = 0; i < gap; ++i) + newMarkers.push(gapMarkers); + newMarkers.push(last); + } + return newMarkers; + } + + // Remove spans that are empty and don't have a clearWhenEmpty + // option of false. + function clearEmptySpans(spans) { + for (var i = 0; i < spans.length; ++i) { + var span = spans[i]; + if (span.from != null && span.from == span.to && span.marker.clearWhenEmpty !== false) + spans.splice(i--, 1); + } + if (!spans.length) return null; + return spans; + } + + // Used for un/re-doing changes from the history. Combines the + // result of computing the existing spans with the set of spans that + // existed in the history (so that deleting around a span and then + // undoing brings back the span). + function mergeOldSpans(doc, change) { + var old = getOldSpans(doc, change); + var stretched = stretchSpansOverChange(doc, change); + if (!old) return stretched; + if (!stretched) return old; + + for (var i = 0; i < old.length; ++i) { + var oldCur = old[i], stretchCur = stretched[i]; + if (oldCur && stretchCur) { + spans: for (var j = 0; j < stretchCur.length; ++j) { + var span = stretchCur[j]; + for (var k = 0; k < oldCur.length; ++k) + if (oldCur[k].marker == span.marker) continue spans; + oldCur.push(span); + } + } else if (stretchCur) { + old[i] = stretchCur; + } + } + return old; + } + + // Used to 'clip' out readOnly ranges when making a change. + function removeReadOnlyRanges(doc, from, to) { + var markers = null; + doc.iter(from.line, to.line + 1, function(line) { + if (line.markedSpans) for (var i = 0; i < line.markedSpans.length; ++i) { + var mark = line.markedSpans[i].marker; + if (mark.readOnly && (!markers || indexOf(markers, mark) == -1)) + (markers || (markers = [])).push(mark); + } + }); + if (!markers) return null; + var parts = [{from: from, to: to}]; + for (var i = 0; i < markers.length; ++i) { + var mk = markers[i], m = mk.find(0); + for (var j = 0; j < parts.length; ++j) { + var p = parts[j]; + if (cmp(p.to, m.from) < 0 || cmp(p.from, m.to) > 0) continue; + var newParts = [j, 1], dfrom = cmp(p.from, m.from), dto = cmp(p.to, m.to); + if (dfrom < 0 || !mk.inclusiveLeft && !dfrom) + newParts.push({from: p.from, to: m.from}); + if (dto > 0 || !mk.inclusiveRight && !dto) + newParts.push({from: m.to, to: p.to}); + parts.splice.apply(parts, newParts); + j += newParts.length - 1; + } + } + return parts; + } + + // Connect or disconnect spans from a line. + function detachMarkedSpans(line) { + var spans = line.markedSpans; + if (!spans) return; + for (var i = 0; i < spans.length; ++i) + spans[i].marker.detachLine(line); + line.markedSpans = null; + } + function attachMarkedSpans(line, spans) { + if (!spans) return; + for (var i = 0; i < spans.length; ++i) + spans[i].marker.attachLine(line); + line.markedSpans = spans; + } + + // Helpers used when computing which overlapping collapsed span + // counts as the larger one. + function extraLeft(marker) { return marker.inclusiveLeft ? -1 : 0; } + function extraRight(marker) { return marker.inclusiveRight ? 1 : 0; } + + // Returns a number indicating which of two overlapping collapsed + // spans is larger (and thus includes the other). Falls back to + // comparing ids when the spans cover exactly the same range. + function compareCollapsedMarkers(a, b) { + var lenDiff = a.lines.length - b.lines.length; + if (lenDiff != 0) return lenDiff; + var aPos = a.find(), bPos = b.find(); + var fromCmp = cmp(aPos.from, bPos.from) || extraLeft(a) - extraLeft(b); + if (fromCmp) return -fromCmp; + var toCmp = cmp(aPos.to, bPos.to) || extraRight(a) - extraRight(b); + if (toCmp) return toCmp; + return b.id - a.id; + } + + // Find out whether a line ends or starts in a collapsed span. If + // so, return the marker for that span. + function collapsedSpanAtSide(line, start) { + var sps = sawCollapsedSpans && line.markedSpans, found; + if (sps) for (var sp, i = 0; i < sps.length; ++i) { + sp = sps[i]; + if (sp.marker.collapsed && (start ? sp.from : sp.to) == null && + (!found || compareCollapsedMarkers(found, sp.marker) < 0)) + found = sp.marker; + } + return found; + } + function collapsedSpanAtStart(line) { return collapsedSpanAtSide(line, true); } + function collapsedSpanAtEnd(line) { return collapsedSpanAtSide(line, false); } + + // Test whether there exists a collapsed span that partially + // overlaps (covers the start or end, but not both) of a new span. + // Such overlap is not allowed. + function conflictingCollapsedRange(doc, lineNo, from, to, marker) { + var line = getLine(doc, lineNo); + var sps = sawCollapsedSpans && line.markedSpans; + if (sps) for (var i = 0; i < sps.length; ++i) { + var sp = sps[i]; + if (!sp.marker.collapsed) continue; + var found = sp.marker.find(0); + var fromCmp = cmp(found.from, from) || extraLeft(sp.marker) - extraLeft(marker); + var toCmp = cmp(found.to, to) || extraRight(sp.marker) - extraRight(marker); + if (fromCmp >= 0 && toCmp <= 0 || fromCmp <= 0 && toCmp >= 0) continue; + if (fromCmp <= 0 && (cmp(found.to, from) > 0 || (sp.marker.inclusiveRight && marker.inclusiveLeft)) || + fromCmp >= 0 && (cmp(found.from, to) < 0 || (sp.marker.inclusiveLeft && marker.inclusiveRight))) + return true; + } + } + + // A visual line is a line as drawn on the screen. Folding, for + // example, can cause multiple logical lines to appear on the same + // visual line. This finds the start of the visual line that the + // given line is part of (usually that is the line itself). + function visualLine(line) { + var merged; + while (merged = collapsedSpanAtStart(line)) + line = merged.find(-1, true).line; + return line; + } + + // Returns an array of logical lines that continue the visual line + // started by the argument, or undefined if there are no such lines. + function visualLineContinued(line) { + var merged, lines; + while (merged = collapsedSpanAtEnd(line)) { + line = merged.find(1, true).line; + (lines || (lines = [])).push(line); + } + return lines; + } + + // Get the line number of the start of the visual line that the + // given line number is part of. + function visualLineNo(doc, lineN) { + var line = getLine(doc, lineN), vis = visualLine(line); + if (line == vis) return lineN; + return lineNo(vis); + } + // Get the line number of the start of the next visual line after + // the given line. + function visualLineEndNo(doc, lineN) { + if (lineN > doc.lastLine()) return lineN; + var line = getLine(doc, lineN), merged; + if (!lineIsHidden(doc, line)) return lineN; + while (merged = collapsedSpanAtEnd(line)) + line = merged.find(1, true).line; + return lineNo(line) + 1; + } + + // Compute whether a line is hidden. Lines count as hidden when they + // are part of a visual line that starts with another line, or when + // they are entirely covered by collapsed, non-widget span. + function lineIsHidden(doc, line) { + var sps = sawCollapsedSpans && line.markedSpans; + if (sps) for (var sp, i = 0; i < sps.length; ++i) { + sp = sps[i]; + if (!sp.marker.collapsed) continue; + if (sp.from == null) return true; + if (sp.marker.widgetNode) continue; + if (sp.from == 0 && sp.marker.inclusiveLeft && lineIsHiddenInner(doc, line, sp)) + return true; + } + } + function lineIsHiddenInner(doc, line, span) { + if (span.to == null) { + var end = span.marker.find(1, true); + return lineIsHiddenInner(doc, end.line, getMarkedSpanFor(end.line.markedSpans, span.marker)); + } + if (span.marker.inclusiveRight && span.to == line.text.length) + return true; + for (var sp, i = 0; i < line.markedSpans.length; ++i) { + sp = line.markedSpans[i]; + if (sp.marker.collapsed && !sp.marker.widgetNode && sp.from == span.to && + (sp.to == null || sp.to != span.from) && + (sp.marker.inclusiveLeft || span.marker.inclusiveRight) && + lineIsHiddenInner(doc, line, sp)) return true; + } + } + + // LINE WIDGETS + + // Line widgets are block elements displayed above or below a line. + + var LineWidget = CodeMirror.LineWidget = function(doc, node, options) { + if (options) for (var opt in options) if (options.hasOwnProperty(opt)) + this[opt] = options[opt]; + this.doc = doc; + this.node = node; + }; + eventMixin(LineWidget); + + function adjustScrollWhenAboveVisible(cm, line, diff) { + if (heightAtLine(line) < ((cm.curOp && cm.curOp.scrollTop) || cm.doc.scrollTop)) + addToScrollPos(cm, null, diff); + } + + LineWidget.prototype.clear = function() { + var cm = this.doc.cm, ws = this.line.widgets, line = this.line, no = lineNo(line); + if (no == null || !ws) return; + for (var i = 0; i < ws.length; ++i) if (ws[i] == this) ws.splice(i--, 1); + if (!ws.length) line.widgets = null; + var height = widgetHeight(this); + updateLineHeight(line, Math.max(0, line.height - height)); + if (cm) runInOp(cm, function() { + adjustScrollWhenAboveVisible(cm, line, -height); + regLineChange(cm, no, "widget"); + }); + }; + LineWidget.prototype.changed = function() { + var oldH = this.height, cm = this.doc.cm, line = this.line; + this.height = null; + var diff = widgetHeight(this) - oldH; + if (!diff) return; + updateLineHeight(line, line.height + diff); + if (cm) runInOp(cm, function() { + cm.curOp.forceUpdate = true; + adjustScrollWhenAboveVisible(cm, line, diff); + }); + }; + + function widgetHeight(widget) { + if (widget.height != null) return widget.height; + var cm = widget.doc.cm; + if (!cm) return 0; + if (!contains(document.body, widget.node)) { + var parentStyle = "position: relative;"; + if (widget.coverGutter) + parentStyle += "margin-left: -" + cm.display.gutters.offsetWidth + "px;"; + if (widget.noHScroll) + parentStyle += "width: " + cm.display.wrapper.clientWidth + "px;"; + removeChildrenAndAdd(cm.display.measure, elt("div", [widget.node], null, parentStyle)); + } + return widget.height = widget.node.offsetHeight; + } + + function addLineWidget(doc, handle, node, options) { + var widget = new LineWidget(doc, node, options); + var cm = doc.cm; + if (cm && widget.noHScroll) cm.display.alignWidgets = true; + changeLine(doc, handle, "widget", function(line) { + var widgets = line.widgets || (line.widgets = []); + if (widget.insertAt == null) widgets.push(widget); + else widgets.splice(Math.min(widgets.length - 1, Math.max(0, widget.insertAt)), 0, widget); + widget.line = line; + if (cm && !lineIsHidden(doc, line)) { + var aboveVisible = heightAtLine(line) < doc.scrollTop; + updateLineHeight(line, line.height + widgetHeight(widget)); + if (aboveVisible) addToScrollPos(cm, null, widget.height); + cm.curOp.forceUpdate = true; + } + return true; + }); + return widget; + } + + // LINE DATA STRUCTURE + + // Line objects. These hold state related to a line, including + // highlighting info (the styles array). + var Line = CodeMirror.Line = function(text, markedSpans, estimateHeight) { + this.text = text; + attachMarkedSpans(this, markedSpans); + this.height = estimateHeight ? estimateHeight(this) : 1; + }; + eventMixin(Line); + Line.prototype.lineNo = function() { return lineNo(this); }; + + // Change the content (text, markers) of a line. Automatically + // invalidates cached information and tries to re-estimate the + // line's height. + function updateLine(line, text, markedSpans, estimateHeight) { + line.text = text; + if (line.stateAfter) line.stateAfter = null; + if (line.styles) line.styles = null; + if (line.order != null) line.order = null; + detachMarkedSpans(line); + attachMarkedSpans(line, markedSpans); + var estHeight = estimateHeight ? estimateHeight(line) : 1; + if (estHeight != line.height) updateLineHeight(line, estHeight); + } + + // Detach a line from the document tree and its markers. + function cleanUpLine(line) { + line.parent = null; + detachMarkedSpans(line); + } + + function extractLineClasses(type, output) { + if (type) for (;;) { + var lineClass = type.match(/(?:^|\s+)line-(background-)?(\S+)/); + if (!lineClass) break; + type = type.slice(0, lineClass.index) + type.slice(lineClass.index + lineClass[0].length); + var prop = lineClass[1] ? "bgClass" : "textClass"; + if (output[prop] == null) + output[prop] = lineClass[2]; + else if (!(new RegExp("(?:^|\s)" + lineClass[2] + "(?:$|\s)")).test(output[prop])) + output[prop] += " " + lineClass[2]; + } + return type; + } + + function callBlankLine(mode, state) { + if (mode.blankLine) return mode.blankLine(state); + if (!mode.innerMode) return; + var inner = CodeMirror.innerMode(mode, state); + if (inner.mode.blankLine) return inner.mode.blankLine(inner.state); + } + + function readToken(mode, stream, state, inner) { + for (var i = 0; i < 10; i++) { + if (inner) inner[0] = CodeMirror.innerMode(mode, state).mode; + var style = mode.token(stream, state); + if (stream.pos > stream.start) return style; + } + throw new Error("Mode " + mode.name + " failed to advance stream."); + } + + // Utility for getTokenAt and getLineTokens + function takeToken(cm, pos, precise, asArray) { + function getObj(copy) { + return {start: stream.start, end: stream.pos, + string: stream.current(), + type: style || null, + state: copy ? copyState(doc.mode, state) : state}; + } + + var doc = cm.doc, mode = doc.mode, style; + pos = clipPos(doc, pos); + var line = getLine(doc, pos.line), state = getStateBefore(cm, pos.line, precise); + var stream = new StringStream(line.text, cm.options.tabSize), tokens; + if (asArray) tokens = []; + while ((asArray || stream.pos < pos.ch) && !stream.eol()) { + stream.start = stream.pos; + style = readToken(mode, stream, state); + if (asArray) tokens.push(getObj(true)); + } + return asArray ? tokens : getObj(); + } + + // Run the given mode's parser over a line, calling f for each token. + function runMode(cm, text, mode, state, f, lineClasses, forceToEnd) { + var flattenSpans = mode.flattenSpans; + if (flattenSpans == null) flattenSpans = cm.options.flattenSpans; + var curStart = 0, curStyle = null; + var stream = new StringStream(text, cm.options.tabSize), style; + var inner = cm.options.addModeClass && [null]; + if (text == "") extractLineClasses(callBlankLine(mode, state), lineClasses); + while (!stream.eol()) { + if (stream.pos > cm.options.maxHighlightLength) { + flattenSpans = false; + if (forceToEnd) processLine(cm, text, state, stream.pos); + stream.pos = text.length; + style = null; + } else { + style = extractLineClasses(readToken(mode, stream, state, inner), lineClasses); + } + if (inner) { + var mName = inner[0].name; + if (mName) style = "m-" + (style ? mName + " " + style : mName); + } + if (!flattenSpans || curStyle != style) { + while (curStart < stream.start) { + curStart = Math.min(stream.start, curStart + 50000); + f(curStart, curStyle); + } + curStyle = style; + } + stream.start = stream.pos; + } + while (curStart < stream.pos) { + // Webkit seems to refuse to render text nodes longer than 57444 characters + var pos = Math.min(stream.pos, curStart + 50000); + f(pos, curStyle); + curStart = pos; + } + } + + // Compute a style array (an array starting with a mode generation + // -- for invalidation -- followed by pairs of end positions and + // style strings), which is used to highlight the tokens on the + // line. + function highlightLine(cm, line, state, forceToEnd) { + // A styles array always starts with a number identifying the + // mode/overlays that it is based on (for easy invalidation). + var st = [cm.state.modeGen], lineClasses = {}; + // Compute the base array of styles + runMode(cm, line.text, cm.doc.mode, state, function(end, style) { + st.push(end, style); + }, lineClasses, forceToEnd); + + // Run overlays, adjust style array. + for (var o = 0; o < cm.state.overlays.length; ++o) { + var overlay = cm.state.overlays[o], i = 1, at = 0; + runMode(cm, line.text, overlay.mode, true, function(end, style) { + var start = i; + // Ensure there's a token end at the current position, and that i points at it + while (at < end) { + var i_end = st[i]; + if (i_end > end) + st.splice(i, 1, end, st[i+1], i_end); + i += 2; + at = Math.min(end, i_end); + } + if (!style) return; + if (overlay.opaque) { + st.splice(start, i - start, end, "cm-overlay " + style); + i = start + 2; + } else { + for (; start < i; start += 2) { + var cur = st[start+1]; + st[start+1] = (cur ? cur + " " : "") + "cm-overlay " + style; + } + } + }, lineClasses); + } + + return {styles: st, classes: lineClasses.bgClass || lineClasses.textClass ? lineClasses : null}; + } + + function getLineStyles(cm, line, updateFrontier) { + if (!line.styles || line.styles[0] != cm.state.modeGen) { + var result = highlightLine(cm, line, line.stateAfter = getStateBefore(cm, lineNo(line))); + line.styles = result.styles; + if (result.classes) line.styleClasses = result.classes; + else if (line.styleClasses) line.styleClasses = null; + if (updateFrontier === cm.doc.frontier) cm.doc.frontier++; + } + return line.styles; + } + + // Lightweight form of highlight -- proceed over this line and + // update state, but don't save a style array. Used for lines that + // aren't currently visible. + function processLine(cm, text, state, startAt) { + var mode = cm.doc.mode; + var stream = new StringStream(text, cm.options.tabSize); + stream.start = stream.pos = startAt || 0; + if (text == "") callBlankLine(mode, state); + while (!stream.eol() && stream.pos <= cm.options.maxHighlightLength) { + readToken(mode, stream, state); + stream.start = stream.pos; + } + } + + // Convert a style as returned by a mode (either null, or a string + // containing one or more styles) to a CSS style. This is cached, + // and also looks for line-wide styles. + var styleToClassCache = {}, styleToClassCacheWithMode = {}; + function interpretTokenStyle(style, options) { + if (!style || /^\s*$/.test(style)) return null; + var cache = options.addModeClass ? styleToClassCacheWithMode : styleToClassCache; + return cache[style] || + (cache[style] = style.replace(/\S+/g, "cm-$&")); + } + + // Render the DOM representation of the text of a line. Also builds + // up a 'line map', which points at the DOM nodes that represent + // specific stretches of text, and is used by the measuring code. + // The returned object contains the DOM node, this map, and + // information about line-wide styles that were set by the mode. + function buildLineContent(cm, lineView) { + // The padding-right forces the element to have a 'border', which + // is needed on Webkit to be able to get line-level bounding + // rectangles for it (in measureChar). + var content = elt("span", null, null, webkit ? "padding-right: .1px" : null); + var builder = {pre: elt("pre", [content]), content: content, + col: 0, pos: 0, cm: cm, + splitSpaces: (ie || webkit) && cm.getOption("lineWrapping")}; + lineView.measure = {}; + + // Iterate over the logical lines that make up this visual line. + for (var i = 0; i <= (lineView.rest ? lineView.rest.length : 0); i++) { + var line = i ? lineView.rest[i - 1] : lineView.line, order; + builder.pos = 0; + builder.addToken = buildToken; + // Optionally wire in some hacks into the token-rendering + // algorithm, to deal with browser quirks. + if (hasBadBidiRects(cm.display.measure) && (order = getOrder(line))) + builder.addToken = buildTokenBadBidi(builder.addToken, order); + builder.map = []; + var allowFrontierUpdate = lineView != cm.display.externalMeasured && lineNo(line); + insertLineContent(line, builder, getLineStyles(cm, line, allowFrontierUpdate)); + if (line.styleClasses) { + if (line.styleClasses.bgClass) + builder.bgClass = joinClasses(line.styleClasses.bgClass, builder.bgClass || ""); + if (line.styleClasses.textClass) + builder.textClass = joinClasses(line.styleClasses.textClass, builder.textClass || ""); + } + + // Ensure at least a single node is present, for measuring. + if (builder.map.length == 0) + builder.map.push(0, 0, builder.content.appendChild(zeroWidthElement(cm.display.measure))); + + // Store the map and a cache object for the current logical line + if (i == 0) { + lineView.measure.map = builder.map; + lineView.measure.cache = {}; + } else { + (lineView.measure.maps || (lineView.measure.maps = [])).push(builder.map); + (lineView.measure.caches || (lineView.measure.caches = [])).push({}); + } + } + + // See issue #2901 + if (webkit && /\bcm-tab\b/.test(builder.content.lastChild.className)) + builder.content.className = "cm-tab-wrap-hack"; + + signal(cm, "renderLine", cm, lineView.line, builder.pre); + if (builder.pre.className) + builder.textClass = joinClasses(builder.pre.className, builder.textClass || ""); + + return builder; + } + + function defaultSpecialCharPlaceholder(ch) { + var token = elt("span", "\u2022", "cm-invalidchar"); + token.title = "\\u" + ch.charCodeAt(0).toString(16); + token.setAttribute("aria-label", token.title); + return token; + } + + // Build up the DOM representation for a single token, and add it to + // the line map. Takes care to render special characters separately. + function buildToken(builder, text, style, startStyle, endStyle, title, css) { + if (!text) return; + var displayText = builder.splitSpaces ? text.replace(/ {3,}/g, splitSpaces) : text; + var special = builder.cm.state.specialChars, mustWrap = false; + if (!special.test(text)) { + builder.col += text.length; + var content = document.createTextNode(displayText); + builder.map.push(builder.pos, builder.pos + text.length, content); + if (ie && ie_version < 9) mustWrap = true; + builder.pos += text.length; + } else { + var content = document.createDocumentFragment(), pos = 0; + while (true) { + special.lastIndex = pos; + var m = special.exec(text); + var skipped = m ? m.index - pos : text.length - pos; + if (skipped) { + var txt = document.createTextNode(displayText.slice(pos, pos + skipped)); + if (ie && ie_version < 9) content.appendChild(elt("span", [txt])); + else content.appendChild(txt); + builder.map.push(builder.pos, builder.pos + skipped, txt); + builder.col += skipped; + builder.pos += skipped; + } + if (!m) break; + pos += skipped + 1; + if (m[0] == "\t") { + var tabSize = builder.cm.options.tabSize, tabWidth = tabSize - builder.col % tabSize; + var txt = content.appendChild(elt("span", spaceStr(tabWidth), "cm-tab")); + txt.setAttribute("role", "presentation"); + txt.setAttribute("cm-text", "\t"); + builder.col += tabWidth; + } else { + var txt = builder.cm.options.specialCharPlaceholder(m[0]); + txt.setAttribute("cm-text", m[0]); + if (ie && ie_version < 9) content.appendChild(elt("span", [txt])); + else content.appendChild(txt); + builder.col += 1; + } + builder.map.push(builder.pos, builder.pos + 1, txt); + builder.pos++; + } + } + if (style || startStyle || endStyle || mustWrap || css) { + var fullStyle = style || ""; + if (startStyle) fullStyle += startStyle; + if (endStyle) fullStyle += endStyle; + var token = elt("span", [content], fullStyle, css); + if (title) token.title = title; + return builder.content.appendChild(token); + } + builder.content.appendChild(content); + } + + function splitSpaces(old) { + var out = " "; + for (var i = 0; i < old.length - 2; ++i) out += i % 2 ? " " : "\u00a0"; + out += " "; + return out; + } + + // Work around nonsense dimensions being reported for stretches of + // right-to-left text. + function buildTokenBadBidi(inner, order) { + return function(builder, text, style, startStyle, endStyle, title, css) { + style = style ? style + " cm-force-border" : "cm-force-border"; + var start = builder.pos, end = start + text.length; + for (;;) { + // Find the part that overlaps with the start of this text + for (var i = 0; i < order.length; i++) { + var part = order[i]; + if (part.to > start && part.from <= start) break; + } + if (part.to >= end) return inner(builder, text, style, startStyle, endStyle, title, css); + inner(builder, text.slice(0, part.to - start), style, startStyle, null, title, css); + startStyle = null; + text = text.slice(part.to - start); + start = part.to; + } + }; + } + + function buildCollapsedSpan(builder, size, marker, ignoreWidget) { + var widget = !ignoreWidget && marker.widgetNode; + if (widget) builder.map.push(builder.pos, builder.pos + size, widget); + if (!ignoreWidget && builder.cm.display.input.needsContentAttribute) { + if (!widget) + widget = builder.content.appendChild(document.createElement("span")); + widget.setAttribute("cm-marker", marker.id); + } + if (widget) { + builder.cm.display.input.setUneditable(widget); + builder.content.appendChild(widget); + } + builder.pos += size; + } + + // Outputs a number of spans to make up a line, taking highlighting + // and marked text into account. + function insertLineContent(line, builder, styles) { + var spans = line.markedSpans, allText = line.text, at = 0; + if (!spans) { + for (var i = 1; i < styles.length; i+=2) + builder.addToken(builder, allText.slice(at, at = styles[i]), interpretTokenStyle(styles[i+1], builder.cm.options)); + return; + } + + var len = allText.length, pos = 0, i = 1, text = "", style, css; + var nextChange = 0, spanStyle, spanEndStyle, spanStartStyle, title, collapsed; + for (;;) { + if (nextChange == pos) { // Update current marker set + spanStyle = spanEndStyle = spanStartStyle = title = css = ""; + collapsed = null; nextChange = Infinity; + var foundBookmarks = []; + for (var j = 0; j < spans.length; ++j) { + var sp = spans[j], m = sp.marker; + if (sp.from <= pos && (sp.to == null || sp.to > pos)) { + if (sp.to != null && nextChange > sp.to) { nextChange = sp.to; spanEndStyle = ""; } + if (m.className) spanStyle += " " + m.className; + if (m.css) css = m.css; + if (m.startStyle && sp.from == pos) spanStartStyle += " " + m.startStyle; + if (m.endStyle && sp.to == nextChange) spanEndStyle += " " + m.endStyle; + if (m.title && !title) title = m.title; + if (m.collapsed && (!collapsed || compareCollapsedMarkers(collapsed.marker, m) < 0)) + collapsed = sp; + } else if (sp.from > pos && nextChange > sp.from) { + nextChange = sp.from; + } + if (m.type == "bookmark" && sp.from == pos && m.widgetNode) foundBookmarks.push(m); + } + if (collapsed && (collapsed.from || 0) == pos) { + buildCollapsedSpan(builder, (collapsed.to == null ? len + 1 : collapsed.to) - pos, + collapsed.marker, collapsed.from == null); + if (collapsed.to == null) return; + } + if (!collapsed && foundBookmarks.length) for (var j = 0; j < foundBookmarks.length; ++j) + buildCollapsedSpan(builder, 0, foundBookmarks[j]); + } + if (pos >= len) break; + + var upto = Math.min(len, nextChange); + while (true) { + if (text) { + var end = pos + text.length; + if (!collapsed) { + var tokenText = end > upto ? text.slice(0, upto - pos) : text; + builder.addToken(builder, tokenText, style ? style + spanStyle : spanStyle, + spanStartStyle, pos + tokenText.length == nextChange ? spanEndStyle : "", title, css); + } + if (end >= upto) {text = text.slice(upto - pos); pos = upto; break;} + pos = end; + spanStartStyle = ""; + } + text = allText.slice(at, at = styles[i++]); + style = interpretTokenStyle(styles[i++], builder.cm.options); + } + } + } + + // DOCUMENT DATA STRUCTURE + + // By default, updates that start and end at the beginning of a line + // are treated specially, in order to make the association of line + // widgets and marker elements with the text behave more intuitive. + function isWholeLineUpdate(doc, change) { + return change.from.ch == 0 && change.to.ch == 0 && lst(change.text) == "" && + (!doc.cm || doc.cm.options.wholeLineUpdateBefore); + } + + // Perform a change on the document data structure. + function updateDoc(doc, change, markedSpans, estimateHeight) { + function spansFor(n) {return markedSpans ? markedSpans[n] : null;} + function update(line, text, spans) { + updateLine(line, text, spans, estimateHeight); + signalLater(line, "change", line, change); + } + function linesFor(start, end) { + for (var i = start, result = []; i < end; ++i) + result.push(new Line(text[i], spansFor(i), estimateHeight)); + return result; + } + + var from = change.from, to = change.to, text = change.text; + var firstLine = getLine(doc, from.line), lastLine = getLine(doc, to.line); + var lastText = lst(text), lastSpans = spansFor(text.length - 1), nlines = to.line - from.line; + + // Adjust the line structure + if (change.full) { + doc.insert(0, linesFor(0, text.length)); + doc.remove(text.length, doc.size - text.length); + } else if (isWholeLineUpdate(doc, change)) { + // This is a whole-line replace. Treated specially to make + // sure line objects move the way they are supposed to. + var added = linesFor(0, text.length - 1); + update(lastLine, lastLine.text, lastSpans); + if (nlines) doc.remove(from.line, nlines); + if (added.length) doc.insert(from.line, added); + } else if (firstLine == lastLine) { + if (text.length == 1) { + update(firstLine, firstLine.text.slice(0, from.ch) + lastText + firstLine.text.slice(to.ch), lastSpans); + } else { + var added = linesFor(1, text.length - 1); + added.push(new Line(lastText + firstLine.text.slice(to.ch), lastSpans, estimateHeight)); + update(firstLine, firstLine.text.slice(0, from.ch) + text[0], spansFor(0)); + doc.insert(from.line + 1, added); + } + } else if (text.length == 1) { + update(firstLine, firstLine.text.slice(0, from.ch) + text[0] + lastLine.text.slice(to.ch), spansFor(0)); + doc.remove(from.line + 1, nlines); + } else { + update(firstLine, firstLine.text.slice(0, from.ch) + text[0], spansFor(0)); + update(lastLine, lastText + lastLine.text.slice(to.ch), lastSpans); + var added = linesFor(1, text.length - 1); + if (nlines > 1) doc.remove(from.line + 1, nlines - 1); + doc.insert(from.line + 1, added); + } + + signalLater(doc, "change", doc, change); + } + + // The document is represented as a BTree consisting of leaves, with + // chunk of lines in them, and branches, with up to ten leaves or + // other branch nodes below them. The top node is always a branch + // node, and is the document object itself (meaning it has + // additional methods and properties). + // + // All nodes have parent links. The tree is used both to go from + // line numbers to line objects, and to go from objects to numbers. + // It also indexes by height, and is used to convert between height + // and line object, and to find the total height of the document. + // + // See also http://marijnhaverbeke.nl/blog/codemirror-line-tree.html + + function LeafChunk(lines) { + this.lines = lines; + this.parent = null; + for (var i = 0, height = 0; i < lines.length; ++i) { + lines[i].parent = this; + height += lines[i].height; + } + this.height = height; + } + + LeafChunk.prototype = { + chunkSize: function() { return this.lines.length; }, + // Remove the n lines at offset 'at'. + removeInner: function(at, n) { + for (var i = at, e = at + n; i < e; ++i) { + var line = this.lines[i]; + this.height -= line.height; + cleanUpLine(line); + signalLater(line, "delete"); + } + this.lines.splice(at, n); + }, + // Helper used to collapse a small branch into a single leaf. + collapse: function(lines) { + lines.push.apply(lines, this.lines); + }, + // Insert the given array of lines at offset 'at', count them as + // having the given height. + insertInner: function(at, lines, height) { + this.height += height; + this.lines = this.lines.slice(0, at).concat(lines).concat(this.lines.slice(at)); + for (var i = 0; i < lines.length; ++i) lines[i].parent = this; + }, + // Used to iterate over a part of the tree. + iterN: function(at, n, op) { + for (var e = at + n; at < e; ++at) + if (op(this.lines[at])) return true; + } + }; + + function BranchChunk(children) { + this.children = children; + var size = 0, height = 0; + for (var i = 0; i < children.length; ++i) { + var ch = children[i]; + size += ch.chunkSize(); height += ch.height; + ch.parent = this; + } + this.size = size; + this.height = height; + this.parent = null; + } + + BranchChunk.prototype = { + chunkSize: function() { return this.size; }, + removeInner: function(at, n) { + this.size -= n; + for (var i = 0; i < this.children.length; ++i) { + var child = this.children[i], sz = child.chunkSize(); + if (at < sz) { + var rm = Math.min(n, sz - at), oldHeight = child.height; + child.removeInner(at, rm); + this.height -= oldHeight - child.height; + if (sz == rm) { this.children.splice(i--, 1); child.parent = null; } + if ((n -= rm) == 0) break; + at = 0; + } else at -= sz; + } + // If the result is smaller than 25 lines, ensure that it is a + // single leaf node. + if (this.size - n < 25 && + (this.children.length > 1 || !(this.children[0] instanceof LeafChunk))) { + var lines = []; + this.collapse(lines); + this.children = [new LeafChunk(lines)]; + this.children[0].parent = this; + } + }, + collapse: function(lines) { + for (var i = 0; i < this.children.length; ++i) this.children[i].collapse(lines); + }, + insertInner: function(at, lines, height) { + this.size += lines.length; + this.height += height; + for (var i = 0; i < this.children.length; ++i) { + var child = this.children[i], sz = child.chunkSize(); + if (at <= sz) { + child.insertInner(at, lines, height); + if (child.lines && child.lines.length > 50) { + while (child.lines.length > 50) { + var spilled = child.lines.splice(child.lines.length - 25, 25); + var newleaf = new LeafChunk(spilled); + child.height -= newleaf.height; + this.children.splice(i + 1, 0, newleaf); + newleaf.parent = this; + } + this.maybeSpill(); + } + break; + } + at -= sz; + } + }, + // When a node has grown, check whether it should be split. + maybeSpill: function() { + if (this.children.length <= 10) return; + var me = this; + do { + var spilled = me.children.splice(me.children.length - 5, 5); + var sibling = new BranchChunk(spilled); + if (!me.parent) { // Become the parent node + var copy = new BranchChunk(me.children); + copy.parent = me; + me.children = [copy, sibling]; + me = copy; + } else { + me.size -= sibling.size; + me.height -= sibling.height; + var myIndex = indexOf(me.parent.children, me); + me.parent.children.splice(myIndex + 1, 0, sibling); + } + sibling.parent = me.parent; + } while (me.children.length > 10); + me.parent.maybeSpill(); + }, + iterN: function(at, n, op) { + for (var i = 0; i < this.children.length; ++i) { + var child = this.children[i], sz = child.chunkSize(); + if (at < sz) { + var used = Math.min(n, sz - at); + if (child.iterN(at, used, op)) return true; + if ((n -= used) == 0) break; + at = 0; + } else at -= sz; + } + } + }; + + var nextDocId = 0; + var Doc = CodeMirror.Doc = function(text, mode, firstLine) { + if (!(this instanceof Doc)) return new Doc(text, mode, firstLine); + if (firstLine == null) firstLine = 0; + + BranchChunk.call(this, [new LeafChunk([new Line("", null)])]); + this.first = firstLine; + this.scrollTop = this.scrollLeft = 0; + this.cantEdit = false; + this.cleanGeneration = 1; + this.frontier = firstLine; + var start = Pos(firstLine, 0); + this.sel = simpleSelection(start); + this.history = new History(null); + this.id = ++nextDocId; + this.modeOption = mode; + + if (typeof text == "string") text = splitLines(text); + updateDoc(this, {from: start, to: start, text: text}); + setSelection(this, simpleSelection(start), sel_dontScroll); + }; + + Doc.prototype = createObj(BranchChunk.prototype, { + constructor: Doc, + // Iterate over the document. Supports two forms -- with only one + // argument, it calls that for each line in the document. With + // three, it iterates over the range given by the first two (with + // the second being non-inclusive). + iter: function(from, to, op) { + if (op) this.iterN(from - this.first, to - from, op); + else this.iterN(this.first, this.first + this.size, from); + }, + + // Non-public interface for adding and removing lines. + insert: function(at, lines) { + var height = 0; + for (var i = 0; i < lines.length; ++i) height += lines[i].height; + this.insertInner(at - this.first, lines, height); + }, + remove: function(at, n) { this.removeInner(at - this.first, n); }, + + // From here, the methods are part of the public interface. Most + // are also available from CodeMirror (editor) instances. + + getValue: function(lineSep) { + var lines = getLines(this, this.first, this.first + this.size); + if (lineSep === false) return lines; + return lines.join(lineSep || "\n"); + }, + setValue: docMethodOp(function(code) { + var top = Pos(this.first, 0), last = this.first + this.size - 1; + makeChange(this, {from: top, to: Pos(last, getLine(this, last).text.length), + text: splitLines(code), origin: "setValue", full: true}, true); + setSelection(this, simpleSelection(top)); + }), + replaceRange: function(code, from, to, origin) { + from = clipPos(this, from); + to = to ? clipPos(this, to) : from; + replaceRange(this, code, from, to, origin); + }, + getRange: function(from, to, lineSep) { + var lines = getBetween(this, clipPos(this, from), clipPos(this, to)); + if (lineSep === false) return lines; + return lines.join(lineSep || "\n"); + }, + + getLine: function(line) {var l = this.getLineHandle(line); return l && l.text;}, + + getLineHandle: function(line) {if (isLine(this, line)) return getLine(this, line);}, + getLineNumber: function(line) {return lineNo(line);}, + + getLineHandleVisualStart: function(line) { + if (typeof line == "number") line = getLine(this, line); + return visualLine(line); + }, + + lineCount: function() {return this.size;}, + firstLine: function() {return this.first;}, + lastLine: function() {return this.first + this.size - 1;}, + + clipPos: function(pos) {return clipPos(this, pos);}, + + getCursor: function(start) { + var range = this.sel.primary(), pos; + if (start == null || start == "head") pos = range.head; + else if (start == "anchor") pos = range.anchor; + else if (start == "end" || start == "to" || start === false) pos = range.to(); + else pos = range.from(); + return pos; + }, + listSelections: function() { return this.sel.ranges; }, + somethingSelected: function() {return this.sel.somethingSelected();}, + + setCursor: docMethodOp(function(line, ch, options) { + setSimpleSelection(this, clipPos(this, typeof line == "number" ? Pos(line, ch || 0) : line), null, options); + }), + setSelection: docMethodOp(function(anchor, head, options) { + setSimpleSelection(this, clipPos(this, anchor), clipPos(this, head || anchor), options); + }), + extendSelection: docMethodOp(function(head, other, options) { + extendSelection(this, clipPos(this, head), other && clipPos(this, other), options); + }), + extendSelections: docMethodOp(function(heads, options) { + extendSelections(this, clipPosArray(this, heads, options)); + }), + extendSelectionsBy: docMethodOp(function(f, options) { + extendSelections(this, map(this.sel.ranges, f), options); + }), + setSelections: docMethodOp(function(ranges, primary, options) { + if (!ranges.length) return; + for (var i = 0, out = []; i < ranges.length; i++) + out[i] = new Range(clipPos(this, ranges[i].anchor), + clipPos(this, ranges[i].head)); + if (primary == null) primary = Math.min(ranges.length - 1, this.sel.primIndex); + setSelection(this, normalizeSelection(out, primary), options); + }), + addSelection: docMethodOp(function(anchor, head, options) { + var ranges = this.sel.ranges.slice(0); + ranges.push(new Range(clipPos(this, anchor), clipPos(this, head || anchor))); + setSelection(this, normalizeSelection(ranges, ranges.length - 1), options); + }), + + getSelection: function(lineSep) { + var ranges = this.sel.ranges, lines; + for (var i = 0; i < ranges.length; i++) { + var sel = getBetween(this, ranges[i].from(), ranges[i].to()); + lines = lines ? lines.concat(sel) : sel; + } + if (lineSep === false) return lines; + else return lines.join(lineSep || "\n"); + }, + getSelections: function(lineSep) { + var parts = [], ranges = this.sel.ranges; + for (var i = 0; i < ranges.length; i++) { + var sel = getBetween(this, ranges[i].from(), ranges[i].to()); + if (lineSep !== false) sel = sel.join(lineSep || "\n"); + parts[i] = sel; + } + return parts; + }, + replaceSelection: function(code, collapse, origin) { + var dup = []; + for (var i = 0; i < this.sel.ranges.length; i++) + dup[i] = code; + this.replaceSelections(dup, collapse, origin || "+input"); + }, + replaceSelections: docMethodOp(function(code, collapse, origin) { + var changes = [], sel = this.sel; + for (var i = 0; i < sel.ranges.length; i++) { + var range = sel.ranges[i]; + changes[i] = {from: range.from(), to: range.to(), text: splitLines(code[i]), origin: origin}; + } + var newSel = collapse && collapse != "end" && computeReplacedSel(this, changes, collapse); + for (var i = changes.length - 1; i >= 0; i--) + makeChange(this, changes[i]); + if (newSel) setSelectionReplaceHistory(this, newSel); + else if (this.cm) ensureCursorVisible(this.cm); + }), + undo: docMethodOp(function() {makeChangeFromHistory(this, "undo");}), + redo: docMethodOp(function() {makeChangeFromHistory(this, "redo");}), + undoSelection: docMethodOp(function() {makeChangeFromHistory(this, "undo", true);}), + redoSelection: docMethodOp(function() {makeChangeFromHistory(this, "redo", true);}), + + setExtending: function(val) {this.extend = val;}, + getExtending: function() {return this.extend;}, + + historySize: function() { + var hist = this.history, done = 0, undone = 0; + for (var i = 0; i < hist.done.length; i++) if (!hist.done[i].ranges) ++done; + for (var i = 0; i < hist.undone.length; i++) if (!hist.undone[i].ranges) ++undone; + return {undo: done, redo: undone}; + }, + clearHistory: function() {this.history = new History(this.history.maxGeneration);}, + + markClean: function() { + this.cleanGeneration = this.changeGeneration(true); + }, + changeGeneration: function(forceSplit) { + if (forceSplit) + this.history.lastOp = this.history.lastSelOp = this.history.lastOrigin = null; + return this.history.generation; + }, + isClean: function (gen) { + return this.history.generation == (gen || this.cleanGeneration); + }, + + getHistory: function() { + return {done: copyHistoryArray(this.history.done), + undone: copyHistoryArray(this.history.undone)}; + }, + setHistory: function(histData) { + var hist = this.history = new History(this.history.maxGeneration); + hist.done = copyHistoryArray(histData.done.slice(0), null, true); + hist.undone = copyHistoryArray(histData.undone.slice(0), null, true); + }, + + addLineClass: docMethodOp(function(handle, where, cls) { + return changeLine(this, handle, where == "gutter" ? "gutter" : "class", function(line) { + var prop = where == "text" ? "textClass" + : where == "background" ? "bgClass" + : where == "gutter" ? "gutterClass" : "wrapClass"; + if (!line[prop]) line[prop] = cls; + else if (classTest(cls).test(line[prop])) return false; + else line[prop] += " " + cls; + return true; + }); + }), + removeLineClass: docMethodOp(function(handle, where, cls) { + return changeLine(this, handle, where == "gutter" ? "gutter" : "class", function(line) { + var prop = where == "text" ? "textClass" + : where == "background" ? "bgClass" + : where == "gutter" ? "gutterClass" : "wrapClass"; + var cur = line[prop]; + if (!cur) return false; + else if (cls == null) line[prop] = null; + else { + var found = cur.match(classTest(cls)); + if (!found) return false; + var end = found.index + found[0].length; + line[prop] = cur.slice(0, found.index) + (!found.index || end == cur.length ? "" : " ") + cur.slice(end) || null; + } + return true; + }); + }), + + addLineWidget: docMethodOp(function(handle, node, options) { + return addLineWidget(this, handle, node, options); + }), + removeLineWidget: function(widget) { widget.clear(); }, + + markText: function(from, to, options) { + return markText(this, clipPos(this, from), clipPos(this, to), options, "range"); + }, + setBookmark: function(pos, options) { + var realOpts = {replacedWith: options && (options.nodeType == null ? options.widget : options), + insertLeft: options && options.insertLeft, + clearWhenEmpty: false, shared: options && options.shared, + handleMouseEvents: options && options.handleMouseEvents}; + pos = clipPos(this, pos); + return markText(this, pos, pos, realOpts, "bookmark"); + }, + findMarksAt: function(pos) { + pos = clipPos(this, pos); + var markers = [], spans = getLine(this, pos.line).markedSpans; + if (spans) for (var i = 0; i < spans.length; ++i) { + var span = spans[i]; + if ((span.from == null || span.from <= pos.ch) && + (span.to == null || span.to >= pos.ch)) + markers.push(span.marker.parent || span.marker); + } + return markers; + }, + findMarks: function(from, to, filter) { + from = clipPos(this, from); to = clipPos(this, to); + var found = [], lineNo = from.line; + this.iter(from.line, to.line + 1, function(line) { + var spans = line.markedSpans; + if (spans) for (var i = 0; i < spans.length; i++) { + var span = spans[i]; + if (!(lineNo == from.line && from.ch > span.to || + span.from == null && lineNo != from.line|| + lineNo == to.line && span.from > to.ch) && + (!filter || filter(span.marker))) + found.push(span.marker.parent || span.marker); + } + ++lineNo; + }); + return found; + }, + getAllMarks: function() { + var markers = []; + this.iter(function(line) { + var sps = line.markedSpans; + if (sps) for (var i = 0; i < sps.length; ++i) + if (sps[i].from != null) markers.push(sps[i].marker); + }); + return markers; + }, + + posFromIndex: function(off) { + var ch, lineNo = this.first; + this.iter(function(line) { + var sz = line.text.length + 1; + if (sz > off) { ch = off; return true; } + off -= sz; + ++lineNo; + }); + return clipPos(this, Pos(lineNo, ch)); + }, + indexFromPos: function (coords) { + coords = clipPos(this, coords); + var index = coords.ch; + if (coords.line < this.first || coords.ch < 0) return 0; + this.iter(this.first, coords.line, function (line) { + index += line.text.length + 1; + }); + return index; + }, + + copy: function(copyHistory) { + var doc = new Doc(getLines(this, this.first, this.first + this.size), this.modeOption, this.first); + doc.scrollTop = this.scrollTop; doc.scrollLeft = this.scrollLeft; + doc.sel = this.sel; + doc.extend = false; + if (copyHistory) { + doc.history.undoDepth = this.history.undoDepth; + doc.setHistory(this.getHistory()); + } + return doc; + }, + + linkedDoc: function(options) { + if (!options) options = {}; + var from = this.first, to = this.first + this.size; + if (options.from != null && options.from > from) from = options.from; + if (options.to != null && options.to < to) to = options.to; + var copy = new Doc(getLines(this, from, to), options.mode || this.modeOption, from); + if (options.sharedHist) copy.history = this.history; + (this.linked || (this.linked = [])).push({doc: copy, sharedHist: options.sharedHist}); + copy.linked = [{doc: this, isParent: true, sharedHist: options.sharedHist}]; + copySharedMarkers(copy, findSharedMarkers(this)); + return copy; + }, + unlinkDoc: function(other) { + if (other instanceof CodeMirror) other = other.doc; + if (this.linked) for (var i = 0; i < this.linked.length; ++i) { + var link = this.linked[i]; + if (link.doc != other) continue; + this.linked.splice(i, 1); + other.unlinkDoc(this); + detachSharedMarkers(findSharedMarkers(this)); + break; + } + // If the histories were shared, split them again + if (other.history == this.history) { + var splitIds = [other.id]; + linkedDocs(other, function(doc) {splitIds.push(doc.id);}, true); + other.history = new History(null); + other.history.done = copyHistoryArray(this.history.done, splitIds); + other.history.undone = copyHistoryArray(this.history.undone, splitIds); + } + }, + iterLinkedDocs: function(f) {linkedDocs(this, f);}, + + getMode: function() {return this.mode;}, + getEditor: function() {return this.cm;} + }); + + // Public alias. + Doc.prototype.eachLine = Doc.prototype.iter; + + // Set up methods on CodeMirror's prototype to redirect to the editor's document. + var dontDelegate = "iter insert remove copy getEditor".split(" "); + for (var prop in Doc.prototype) if (Doc.prototype.hasOwnProperty(prop) && indexOf(dontDelegate, prop) < 0) + CodeMirror.prototype[prop] = (function(method) { + return function() {return method.apply(this.doc, arguments);}; + })(Doc.prototype[prop]); + + eventMixin(Doc); + + // Call f for all linked documents. + function linkedDocs(doc, f, sharedHistOnly) { + function propagate(doc, skip, sharedHist) { + if (doc.linked) for (var i = 0; i < doc.linked.length; ++i) { + var rel = doc.linked[i]; + if (rel.doc == skip) continue; + var shared = sharedHist && rel.sharedHist; + if (sharedHistOnly && !shared) continue; + f(rel.doc, shared); + propagate(rel.doc, doc, shared); + } + } + propagate(doc, null, true); + } + + // Attach a document to an editor. + function attachDoc(cm, doc) { + if (doc.cm) throw new Error("This document is already in use."); + cm.doc = doc; + doc.cm = cm; + estimateLineHeights(cm); + loadMode(cm); + if (!cm.options.lineWrapping) findMaxLine(cm); + cm.options.mode = doc.modeOption; + regChange(cm); + } + + // LINE UTILITIES + + // Find the line object corresponding to the given line number. + function getLine(doc, n) { + n -= doc.first; + if (n < 0 || n >= doc.size) throw new Error("There is no line " + (n + doc.first) + " in the document."); + for (var chunk = doc; !chunk.lines;) { + for (var i = 0;; ++i) { + var child = chunk.children[i], sz = child.chunkSize(); + if (n < sz) { chunk = child; break; } + n -= sz; + } + } + return chunk.lines[n]; + } + + // Get the part of a document between two positions, as an array of + // strings. + function getBetween(doc, start, end) { + var out = [], n = start.line; + doc.iter(start.line, end.line + 1, function(line) { + var text = line.text; + if (n == end.line) text = text.slice(0, end.ch); + if (n == start.line) text = text.slice(start.ch); + out.push(text); + ++n; + }); + return out; + } + // Get the lines between from and to, as array of strings. + function getLines(doc, from, to) { + var out = []; + doc.iter(from, to, function(line) { out.push(line.text); }); + return out; + } + + // Update the height of a line, propagating the height change + // upwards to parent nodes. + function updateLineHeight(line, height) { + var diff = height - line.height; + if (diff) for (var n = line; n; n = n.parent) n.height += diff; + } + + // Given a line object, find its line number by walking up through + // its parent links. + function lineNo(line) { + if (line.parent == null) return null; + var cur = line.parent, no = indexOf(cur.lines, line); + for (var chunk = cur.parent; chunk; cur = chunk, chunk = chunk.parent) { + for (var i = 0;; ++i) { + if (chunk.children[i] == cur) break; + no += chunk.children[i].chunkSize(); + } + } + return no + cur.first; + } + + // Find the line at the given vertical position, using the height + // information in the document tree. + function lineAtHeight(chunk, h) { + var n = chunk.first; + outer: do { + for (var i = 0; i < chunk.children.length; ++i) { + var child = chunk.children[i], ch = child.height; + if (h < ch) { chunk = child; continue outer; } + h -= ch; + n += child.chunkSize(); + } + return n; + } while (!chunk.lines); + for (var i = 0; i < chunk.lines.length; ++i) { + var line = chunk.lines[i], lh = line.height; + if (h < lh) break; + h -= lh; + } + return n + i; + } + + + // Find the height above the given line. + function heightAtLine(lineObj) { + lineObj = visualLine(lineObj); + + var h = 0, chunk = lineObj.parent; + for (var i = 0; i < chunk.lines.length; ++i) { + var line = chunk.lines[i]; + if (line == lineObj) break; + else h += line.height; + } + for (var p = chunk.parent; p; chunk = p, p = chunk.parent) { + for (var i = 0; i < p.children.length; ++i) { + var cur = p.children[i]; + if (cur == chunk) break; + else h += cur.height; + } + } + return h; + } + + // Get the bidi ordering for the given line (and cache it). Returns + // false for lines that are fully left-to-right, and an array of + // BidiSpan objects otherwise. + function getOrder(line) { + var order = line.order; + if (order == null) order = line.order = bidiOrdering(line.text); + return order; + } + + // HISTORY + + function History(startGen) { + // Arrays of change events and selections. Doing something adds an + // event to done and clears undo. Undoing moves events from done + // to undone, redoing moves them in the other direction. + this.done = []; this.undone = []; + this.undoDepth = Infinity; + // Used to track when changes can be merged into a single undo + // event + this.lastModTime = this.lastSelTime = 0; + this.lastOp = this.lastSelOp = null; + this.lastOrigin = this.lastSelOrigin = null; + // Used by the isClean() method + this.generation = this.maxGeneration = startGen || 1; + } + + // Create a history change event from an updateDoc-style change + // object. + function historyChangeFromChange(doc, change) { + var histChange = {from: copyPos(change.from), to: changeEnd(change), text: getBetween(doc, change.from, change.to)}; + attachLocalSpans(doc, histChange, change.from.line, change.to.line + 1); + linkedDocs(doc, function(doc) {attachLocalSpans(doc, histChange, change.from.line, change.to.line + 1);}, true); + return histChange; + } + + // Pop all selection events off the end of a history array. Stop at + // a change event. + function clearSelectionEvents(array) { + while (array.length) { + var last = lst(array); + if (last.ranges) array.pop(); + else break; + } + } + + // Find the top change event in the history. Pop off selection + // events that are in the way. + function lastChangeEvent(hist, force) { + if (force) { + clearSelectionEvents(hist.done); + return lst(hist.done); + } else if (hist.done.length && !lst(hist.done).ranges) { + return lst(hist.done); + } else if (hist.done.length > 1 && !hist.done[hist.done.length - 2].ranges) { + hist.done.pop(); + return lst(hist.done); + } + } + + // Register a change in the history. Merges changes that are within + // a single operation, ore are close together with an origin that + // allows merging (starting with "+") into a single event. + function addChangeToHistory(doc, change, selAfter, opId) { + var hist = doc.history; + hist.undone.length = 0; + var time = +new Date, cur; + + if ((hist.lastOp == opId || + hist.lastOrigin == change.origin && change.origin && + ((change.origin.charAt(0) == "+" && doc.cm && hist.lastModTime > time - doc.cm.options.historyEventDelay) || + change.origin.charAt(0) == "*")) && + (cur = lastChangeEvent(hist, hist.lastOp == opId))) { + // Merge this change into the last event + var last = lst(cur.changes); + if (cmp(change.from, change.to) == 0 && cmp(change.from, last.to) == 0) { + // Optimized case for simple insertion -- don't want to add + // new changesets for every character typed + last.to = changeEnd(change); + } else { + // Add new sub-event + cur.changes.push(historyChangeFromChange(doc, change)); + } + } else { + // Can not be merged, start a new event. + var before = lst(hist.done); + if (!before || !before.ranges) + pushSelectionToHistory(doc.sel, hist.done); + cur = {changes: [historyChangeFromChange(doc, change)], + generation: hist.generation}; + hist.done.push(cur); + while (hist.done.length > hist.undoDepth) { + hist.done.shift(); + if (!hist.done[0].ranges) hist.done.shift(); + } + } + hist.done.push(selAfter); + hist.generation = ++hist.maxGeneration; + hist.lastModTime = hist.lastSelTime = time; + hist.lastOp = hist.lastSelOp = opId; + hist.lastOrigin = hist.lastSelOrigin = change.origin; + + if (!last) signal(doc, "historyAdded"); + } + + function selectionEventCanBeMerged(doc, origin, prev, sel) { + var ch = origin.charAt(0); + return ch == "*" || + ch == "+" && + prev.ranges.length == sel.ranges.length && + prev.somethingSelected() == sel.somethingSelected() && + new Date - doc.history.lastSelTime <= (doc.cm ? doc.cm.options.historyEventDelay : 500); + } + + // Called whenever the selection changes, sets the new selection as + // the pending selection in the history, and pushes the old pending + // selection into the 'done' array when it was significantly + // different (in number of selected ranges, emptiness, or time). + function addSelectionToHistory(doc, sel, opId, options) { + var hist = doc.history, origin = options && options.origin; + + // A new event is started when the previous origin does not match + // the current, or the origins don't allow matching. Origins + // starting with * are always merged, those starting with + are + // merged when similar and close together in time. + if (opId == hist.lastSelOp || + (origin && hist.lastSelOrigin == origin && + (hist.lastModTime == hist.lastSelTime && hist.lastOrigin == origin || + selectionEventCanBeMerged(doc, origin, lst(hist.done), sel)))) + hist.done[hist.done.length - 1] = sel; + else + pushSelectionToHistory(sel, hist.done); + + hist.lastSelTime = +new Date; + hist.lastSelOrigin = origin; + hist.lastSelOp = opId; + if (options && options.clearRedo !== false) + clearSelectionEvents(hist.undone); + } + + function pushSelectionToHistory(sel, dest) { + var top = lst(dest); + if (!(top && top.ranges && top.equals(sel))) + dest.push(sel); + } + + // Used to store marked span information in the history. + function attachLocalSpans(doc, change, from, to) { + var existing = change["spans_" + doc.id], n = 0; + doc.iter(Math.max(doc.first, from), Math.min(doc.first + doc.size, to), function(line) { + if (line.markedSpans) + (existing || (existing = change["spans_" + doc.id] = {}))[n] = line.markedSpans; + ++n; + }); + } + + // When un/re-doing restores text containing marked spans, those + // that have been explicitly cleared should not be restored. + function removeClearedSpans(spans) { + if (!spans) return null; + for (var i = 0, out; i < spans.length; ++i) { + if (spans[i].marker.explicitlyCleared) { if (!out) out = spans.slice(0, i); } + else if (out) out.push(spans[i]); + } + return !out ? spans : out.length ? out : null; + } + + // Retrieve and filter the old marked spans stored in a change event. + function getOldSpans(doc, change) { + var found = change["spans_" + doc.id]; + if (!found) return null; + for (var i = 0, nw = []; i < change.text.length; ++i) + nw.push(removeClearedSpans(found[i])); + return nw; + } + + // Used both to provide a JSON-safe object in .getHistory, and, when + // detaching a document, to split the history in two + function copyHistoryArray(events, newGroup, instantiateSel) { + for (var i = 0, copy = []; i < events.length; ++i) { + var event = events[i]; + if (event.ranges) { + copy.push(instantiateSel ? Selection.prototype.deepCopy.call(event) : event); + continue; + } + var changes = event.changes, newChanges = []; + copy.push({changes: newChanges}); + for (var j = 0; j < changes.length; ++j) { + var change = changes[j], m; + newChanges.push({from: change.from, to: change.to, text: change.text}); + if (newGroup) for (var prop in change) if (m = prop.match(/^spans_(\d+)$/)) { + if (indexOf(newGroup, Number(m[1])) > -1) { + lst(newChanges)[prop] = change[prop]; + delete change[prop]; + } + } + } + } + return copy; + } + + // Rebasing/resetting history to deal with externally-sourced changes + + function rebaseHistSelSingle(pos, from, to, diff) { + if (to < pos.line) { + pos.line += diff; + } else if (from < pos.line) { + pos.line = from; + pos.ch = 0; + } + } + + // Tries to rebase an array of history events given a change in the + // document. If the change touches the same lines as the event, the + // event, and everything 'behind' it, is discarded. If the change is + // before the event, the event's positions are updated. Uses a + // copy-on-write scheme for the positions, to avoid having to + // reallocate them all on every rebase, but also avoid problems with + // shared position objects being unsafely updated. + function rebaseHistArray(array, from, to, diff) { + for (var i = 0; i < array.length; ++i) { + var sub = array[i], ok = true; + if (sub.ranges) { + if (!sub.copied) { sub = array[i] = sub.deepCopy(); sub.copied = true; } + for (var j = 0; j < sub.ranges.length; j++) { + rebaseHistSelSingle(sub.ranges[j].anchor, from, to, diff); + rebaseHistSelSingle(sub.ranges[j].head, from, to, diff); + } + continue; + } + for (var j = 0; j < sub.changes.length; ++j) { + var cur = sub.changes[j]; + if (to < cur.from.line) { + cur.from = Pos(cur.from.line + diff, cur.from.ch); + cur.to = Pos(cur.to.line + diff, cur.to.ch); + } else if (from <= cur.to.line) { + ok = false; + break; + } + } + if (!ok) { + array.splice(0, i + 1); + i = 0; + } + } + } + + function rebaseHist(hist, change) { + var from = change.from.line, to = change.to.line, diff = change.text.length - (to - from) - 1; + rebaseHistArray(hist.done, from, to, diff); + rebaseHistArray(hist.undone, from, to, diff); + } + + // EVENT UTILITIES + + // Due to the fact that we still support jurassic IE versions, some + // compatibility wrappers are needed. + + var e_preventDefault = CodeMirror.e_preventDefault = function(e) { + if (e.preventDefault) e.preventDefault(); + else e.returnValue = false; + }; + var e_stopPropagation = CodeMirror.e_stopPropagation = function(e) { + if (e.stopPropagation) e.stopPropagation(); + else e.cancelBubble = true; + }; + function e_defaultPrevented(e) { + return e.defaultPrevented != null ? e.defaultPrevented : e.returnValue == false; + } + var e_stop = CodeMirror.e_stop = function(e) {e_preventDefault(e); e_stopPropagation(e);}; + + function e_target(e) {return e.target || e.srcElement;} + function e_button(e) { + var b = e.which; + if (b == null) { + if (e.button & 1) b = 1; + else if (e.button & 2) b = 3; + else if (e.button & 4) b = 2; + } + if (mac && e.ctrlKey && b == 1) b = 3; + return b; + } + + // EVENT HANDLING + + // Lightweight event framework. on/off also work on DOM nodes, + // registering native DOM handlers. + + var on = CodeMirror.on = function(emitter, type, f) { + if (emitter.addEventListener) + emitter.addEventListener(type, f, false); + else if (emitter.attachEvent) + emitter.attachEvent("on" + type, f); + else { + var map = emitter._handlers || (emitter._handlers = {}); + var arr = map[type] || (map[type] = []); + arr.push(f); + } + }; + + var off = CodeMirror.off = function(emitter, type, f) { + if (emitter.removeEventListener) + emitter.removeEventListener(type, f, false); + else if (emitter.detachEvent) + emitter.detachEvent("on" + type, f); + else { + var arr = emitter._handlers && emitter._handlers[type]; + if (!arr) return; + for (var i = 0; i < arr.length; ++i) + if (arr[i] == f) { arr.splice(i, 1); break; } + } + }; + + var signal = CodeMirror.signal = function(emitter, type /*, values...*/) { + var arr = emitter._handlers && emitter._handlers[type]; + if (!arr) return; + var args = Array.prototype.slice.call(arguments, 2); + for (var i = 0; i < arr.length; ++i) arr[i].apply(null, args); + }; + + var orphanDelayedCallbacks = null; + + // Often, we want to signal events at a point where we are in the + // middle of some work, but don't want the handler to start calling + // other methods on the editor, which might be in an inconsistent + // state or simply not expect any other events to happen. + // signalLater looks whether there are any handlers, and schedules + // them to be executed when the last operation ends, or, if no + // operation is active, when a timeout fires. + function signalLater(emitter, type /*, values...*/) { + var arr = emitter._handlers && emitter._handlers[type]; + if (!arr) return; + var args = Array.prototype.slice.call(arguments, 2), list; + if (operationGroup) { + list = operationGroup.delayedCallbacks; + } else if (orphanDelayedCallbacks) { + list = orphanDelayedCallbacks; + } else { + list = orphanDelayedCallbacks = []; + setTimeout(fireOrphanDelayed, 0); + } + function bnd(f) {return function(){f.apply(null, args);};}; + for (var i = 0; i < arr.length; ++i) + list.push(bnd(arr[i])); + } + + function fireOrphanDelayed() { + var delayed = orphanDelayedCallbacks; + orphanDelayedCallbacks = null; + for (var i = 0; i < delayed.length; ++i) delayed[i](); + } + + // The DOM events that CodeMirror handles can be overridden by + // registering a (non-DOM) handler on the editor for the event name, + // and preventDefault-ing the event in that handler. + function signalDOMEvent(cm, e, override) { + if (typeof e == "string") + e = {type: e, preventDefault: function() { this.defaultPrevented = true; }}; + signal(cm, override || e.type, cm, e); + return e_defaultPrevented(e) || e.codemirrorIgnore; + } + + function signalCursorActivity(cm) { + var arr = cm._handlers && cm._handlers.cursorActivity; + if (!arr) return; + var set = cm.curOp.cursorActivityHandlers || (cm.curOp.cursorActivityHandlers = []); + for (var i = 0; i < arr.length; ++i) if (indexOf(set, arr[i]) == -1) + set.push(arr[i]); + } + + function hasHandler(emitter, type) { + var arr = emitter._handlers && emitter._handlers[type]; + return arr && arr.length > 0; + } + + // Add on and off methods to a constructor's prototype, to make + // registering events on such objects more convenient. + function eventMixin(ctor) { + ctor.prototype.on = function(type, f) {on(this, type, f);}; + ctor.prototype.off = function(type, f) {off(this, type, f);}; + } + + // MISC UTILITIES + + // Number of pixels added to scroller and sizer to hide scrollbar + var scrollerGap = 30; + + // Returned or thrown by various protocols to signal 'I'm not + // handling this'. + var Pass = CodeMirror.Pass = {toString: function(){return "CodeMirror.Pass";}}; + + // Reused option objects for setSelection & friends + var sel_dontScroll = {scroll: false}, sel_mouse = {origin: "*mouse"}, sel_move = {origin: "+move"}; + + function Delayed() {this.id = null;} + Delayed.prototype.set = function(ms, f) { + clearTimeout(this.id); + this.id = setTimeout(f, ms); + }; + + // Counts the column offset in a string, taking tabs into account. + // Used mostly to find indentation. + var countColumn = CodeMirror.countColumn = function(string, end, tabSize, startIndex, startValue) { + if (end == null) { + end = string.search(/[^\s\u00a0]/); + if (end == -1) end = string.length; + } + for (var i = startIndex || 0, n = startValue || 0;;) { + var nextTab = string.indexOf("\t", i); + if (nextTab < 0 || nextTab >= end) + return n + (end - i); + n += nextTab - i; + n += tabSize - (n % tabSize); + i = nextTab + 1; + } + }; + + // The inverse of countColumn -- find the offset that corresponds to + // a particular column. + function findColumn(string, goal, tabSize) { + for (var pos = 0, col = 0;;) { + var nextTab = string.indexOf("\t", pos); + if (nextTab == -1) nextTab = string.length; + var skipped = nextTab - pos; + if (nextTab == string.length || col + skipped >= goal) + return pos + Math.min(skipped, goal - col); + col += nextTab - pos; + col += tabSize - (col % tabSize); + pos = nextTab + 1; + if (col >= goal) return pos; + } + } + + var spaceStrs = [""]; + function spaceStr(n) { + while (spaceStrs.length <= n) + spaceStrs.push(lst(spaceStrs) + " "); + return spaceStrs[n]; + } + + function lst(arr) { return arr[arr.length-1]; } + + var selectInput = function(node) { node.select(); }; + if (ios) // Mobile Safari apparently has a bug where select() is broken. + selectInput = function(node) { node.selectionStart = 0; node.selectionEnd = node.value.length; }; + else if (ie) // Suppress mysterious IE10 errors + selectInput = function(node) { try { node.select(); } catch(_e) {} }; + + function indexOf(array, elt) { + for (var i = 0; i < array.length; ++i) + if (array[i] == elt) return i; + return -1; + } + function map(array, f) { + var out = []; + for (var i = 0; i < array.length; i++) out[i] = f(array[i], i); + return out; + } + + function nothing() {} + + function createObj(base, props) { + var inst; + if (Object.create) { + inst = Object.create(base); + } else { + nothing.prototype = base; + inst = new nothing(); + } + if (props) copyObj(props, inst); + return inst; + }; + + function copyObj(obj, target, overwrite) { + if (!target) target = {}; + for (var prop in obj) + if (obj.hasOwnProperty(prop) && (overwrite !== false || !target.hasOwnProperty(prop))) + target[prop] = obj[prop]; + return target; + } + + function bind(f) { + var args = Array.prototype.slice.call(arguments, 1); + return function(){return f.apply(null, args);}; + } + + var nonASCIISingleCaseWordChar = /[\u00df\u0587\u0590-\u05f4\u0600-\u06ff\u3040-\u309f\u30a0-\u30ff\u3400-\u4db5\u4e00-\u9fcc\uac00-\ud7af]/; + var isWordCharBasic = CodeMirror.isWordChar = function(ch) { + return /\w/.test(ch) || ch > "\x80" && + (ch.toUpperCase() != ch.toLowerCase() || nonASCIISingleCaseWordChar.test(ch)); + }; + function isWordChar(ch, helper) { + if (!helper) return isWordCharBasic(ch); + if (helper.source.indexOf("\\w") > -1 && isWordCharBasic(ch)) return true; + return helper.test(ch); + } + + function isEmpty(obj) { + for (var n in obj) if (obj.hasOwnProperty(n) && obj[n]) return false; + return true; + } + + // Extending unicode characters. A series of a non-extending char + + // any number of extending chars is treated as a single unit as far + // as editing and measuring is concerned. This is not fully correct, + // since some scripts/fonts/browsers also treat other configurations + // of code points as a group. + var extendingChars = /[\u0300-\u036f\u0483-\u0489\u0591-\u05bd\u05bf\u05c1\u05c2\u05c4\u05c5\u05c7\u0610-\u061a\u064b-\u065e\u0670\u06d6-\u06dc\u06de-\u06e4\u06e7\u06e8\u06ea-\u06ed\u0711\u0730-\u074a\u07a6-\u07b0\u07eb-\u07f3\u0816-\u0819\u081b-\u0823\u0825-\u0827\u0829-\u082d\u0900-\u0902\u093c\u0941-\u0948\u094d\u0951-\u0955\u0962\u0963\u0981\u09bc\u09be\u09c1-\u09c4\u09cd\u09d7\u09e2\u09e3\u0a01\u0a02\u0a3c\u0a41\u0a42\u0a47\u0a48\u0a4b-\u0a4d\u0a51\u0a70\u0a71\u0a75\u0a81\u0a82\u0abc\u0ac1-\u0ac5\u0ac7\u0ac8\u0acd\u0ae2\u0ae3\u0b01\u0b3c\u0b3e\u0b3f\u0b41-\u0b44\u0b4d\u0b56\u0b57\u0b62\u0b63\u0b82\u0bbe\u0bc0\u0bcd\u0bd7\u0c3e-\u0c40\u0c46-\u0c48\u0c4a-\u0c4d\u0c55\u0c56\u0c62\u0c63\u0cbc\u0cbf\u0cc2\u0cc6\u0ccc\u0ccd\u0cd5\u0cd6\u0ce2\u0ce3\u0d3e\u0d41-\u0d44\u0d4d\u0d57\u0d62\u0d63\u0dca\u0dcf\u0dd2-\u0dd4\u0dd6\u0ddf\u0e31\u0e34-\u0e3a\u0e47-\u0e4e\u0eb1\u0eb4-\u0eb9\u0ebb\u0ebc\u0ec8-\u0ecd\u0f18\u0f19\u0f35\u0f37\u0f39\u0f71-\u0f7e\u0f80-\u0f84\u0f86\u0f87\u0f90-\u0f97\u0f99-\u0fbc\u0fc6\u102d-\u1030\u1032-\u1037\u1039\u103a\u103d\u103e\u1058\u1059\u105e-\u1060\u1071-\u1074\u1082\u1085\u1086\u108d\u109d\u135f\u1712-\u1714\u1732-\u1734\u1752\u1753\u1772\u1773\u17b7-\u17bd\u17c6\u17c9-\u17d3\u17dd\u180b-\u180d\u18a9\u1920-\u1922\u1927\u1928\u1932\u1939-\u193b\u1a17\u1a18\u1a56\u1a58-\u1a5e\u1a60\u1a62\u1a65-\u1a6c\u1a73-\u1a7c\u1a7f\u1b00-\u1b03\u1b34\u1b36-\u1b3a\u1b3c\u1b42\u1b6b-\u1b73\u1b80\u1b81\u1ba2-\u1ba5\u1ba8\u1ba9\u1c2c-\u1c33\u1c36\u1c37\u1cd0-\u1cd2\u1cd4-\u1ce0\u1ce2-\u1ce8\u1ced\u1dc0-\u1de6\u1dfd-\u1dff\u200c\u200d\u20d0-\u20f0\u2cef-\u2cf1\u2de0-\u2dff\u302a-\u302f\u3099\u309a\ua66f-\ua672\ua67c\ua67d\ua6f0\ua6f1\ua802\ua806\ua80b\ua825\ua826\ua8c4\ua8e0-\ua8f1\ua926-\ua92d\ua947-\ua951\ua980-\ua982\ua9b3\ua9b6-\ua9b9\ua9bc\uaa29-\uaa2e\uaa31\uaa32\uaa35\uaa36\uaa43\uaa4c\uaab0\uaab2-\uaab4\uaab7\uaab8\uaabe\uaabf\uaac1\uabe5\uabe8\uabed\udc00-\udfff\ufb1e\ufe00-\ufe0f\ufe20-\ufe26\uff9e\uff9f]/; + function isExtendingChar(ch) { return ch.charCodeAt(0) >= 768 && extendingChars.test(ch); } + + // DOM UTILITIES + + function elt(tag, content, className, style) { + var e = document.createElement(tag); + if (className) e.className = className; + if (style) e.style.cssText = style; + if (typeof content == "string") e.appendChild(document.createTextNode(content)); + else if (content) for (var i = 0; i < content.length; ++i) e.appendChild(content[i]); + return e; + } + + var range; + if (document.createRange) range = function(node, start, end, endNode) { + var r = document.createRange(); + r.setEnd(endNode || node, end); + r.setStart(node, start); + return r; + }; + else range = function(node, start, end) { + var r = document.body.createTextRange(); + try { r.moveToElementText(node.parentNode); } + catch(e) { return r; } + r.collapse(true); + r.moveEnd("character", end); + r.moveStart("character", start); + return r; + }; + + function removeChildren(e) { + for (var count = e.childNodes.length; count > 0; --count) + e.removeChild(e.firstChild); + return e; + } + + function removeChildrenAndAdd(parent, e) { + return removeChildren(parent).appendChild(e); + } + + var contains = CodeMirror.contains = function(parent, child) { + if (child.nodeType == 3) // Android browser always returns false when child is a textnode + child = child.parentNode; + if (parent.contains) + return parent.contains(child); + do { + if (child.nodeType == 11) child = child.host; + if (child == parent) return true; + } while (child = child.parentNode); + }; + + function activeElt() { return document.activeElement; } + // Older versions of IE throws unspecified error when touching + // document.activeElement in some cases (during loading, in iframe) + if (ie && ie_version < 11) activeElt = function() { + try { return document.activeElement; } + catch(e) { return document.body; } + }; + + function classTest(cls) { return new RegExp("(^|\\s)" + cls + "(?:$|\\s)\\s*"); } + var rmClass = CodeMirror.rmClass = function(node, cls) { + var current = node.className; + var match = classTest(cls).exec(current); + if (match) { + var after = current.slice(match.index + match[0].length); + node.className = current.slice(0, match.index) + (after ? match[1] + after : ""); + } + }; + var addClass = CodeMirror.addClass = function(node, cls) { + var current = node.className; + if (!classTest(cls).test(current)) node.className += (current ? " " : "") + cls; + }; + function joinClasses(a, b) { + var as = a.split(" "); + for (var i = 0; i < as.length; i++) + if (as[i] && !classTest(as[i]).test(b)) b += " " + as[i]; + return b; + } + + // WINDOW-WIDE EVENTS + + // These must be handled carefully, because naively registering a + // handler for each editor will cause the editors to never be + // garbage collected. + + function forEachCodeMirror(f) { + if (!document.body.getElementsByClassName) return; + var byClass = document.body.getElementsByClassName("CodeMirror"); + for (var i = 0; i < byClass.length; i++) { + var cm = byClass[i].CodeMirror; + if (cm) f(cm); + } + } + + var globalsRegistered = false; + function ensureGlobalHandlers() { + if (globalsRegistered) return; + registerGlobalHandlers(); + globalsRegistered = true; + } + function registerGlobalHandlers() { + // When the window resizes, we need to refresh active editors. + var resizeTimer; + on(window, "resize", function() { + if (resizeTimer == null) resizeTimer = setTimeout(function() { + resizeTimer = null; + forEachCodeMirror(onResize); + }, 100); + }); + // When the window loses focus, we want to show the editor as blurred + on(window, "blur", function() { + forEachCodeMirror(onBlur); + }); + } + + // FEATURE DETECTION + + // Detect drag-and-drop + var dragAndDrop = function() { + // There is *some* kind of drag-and-drop support in IE6-8, but I + // couldn't get it to work yet. + if (ie && ie_version < 9) return false; + var div = elt('div'); + return "draggable" in div || "dragDrop" in div; + }(); + + var zwspSupported; + function zeroWidthElement(measure) { + if (zwspSupported == null) { + var test = elt("span", "\u200b"); + removeChildrenAndAdd(measure, elt("span", [test, document.createTextNode("x")])); + if (measure.firstChild.offsetHeight != 0) + zwspSupported = test.offsetWidth <= 1 && test.offsetHeight > 2 && !(ie && ie_version < 8); + } + var node = zwspSupported ? elt("span", "\u200b") : + elt("span", "\u00a0", null, "display: inline-block; width: 1px; margin-right: -1px"); + node.setAttribute("cm-text", ""); + return node; + } + + // Feature-detect IE's crummy client rect reporting for bidi text + var badBidiRects; + function hasBadBidiRects(measure) { + if (badBidiRects != null) return badBidiRects; + var txt = removeChildrenAndAdd(measure, document.createTextNode("A\u062eA")); + var r0 = range(txt, 0, 1).getBoundingClientRect(); + if (!r0 || r0.left == r0.right) return false; // Safari returns null in some cases (#2780) + var r1 = range(txt, 1, 2).getBoundingClientRect(); + return badBidiRects = (r1.right - r0.right < 3); + } + + // See if "".split is the broken IE version, if so, provide an + // alternative way to split lines. + var splitLines = CodeMirror.splitLines = "\n\nb".split(/\n/).length != 3 ? function(string) { + var pos = 0, result = [], l = string.length; + while (pos <= l) { + var nl = string.indexOf("\n", pos); + if (nl == -1) nl = string.length; + var line = string.slice(pos, string.charAt(nl - 1) == "\r" ? nl - 1 : nl); + var rt = line.indexOf("\r"); + if (rt != -1) { + result.push(line.slice(0, rt)); + pos += rt + 1; + } else { + result.push(line); + pos = nl + 1; + } + } + return result; + } : function(string){return string.split(/\r\n?|\n/);}; + + var hasSelection = window.getSelection ? function(te) { + try { return te.selectionStart != te.selectionEnd; } + catch(e) { return false; } + } : function(te) { + try {var range = te.ownerDocument.selection.createRange();} + catch(e) {} + if (!range || range.parentElement() != te) return false; + return range.compareEndPoints("StartToEnd", range) != 0; + }; + + var hasCopyEvent = (function() { + var e = elt("div"); + if ("oncopy" in e) return true; + e.setAttribute("oncopy", "return;"); + return typeof e.oncopy == "function"; + })(); + + var badZoomedRects = null; + function hasBadZoomedRects(measure) { + if (badZoomedRects != null) return badZoomedRects; + var node = removeChildrenAndAdd(measure, elt("span", "x")); + var normal = node.getBoundingClientRect(); + var fromRange = range(node, 0, 1).getBoundingClientRect(); + return badZoomedRects = Math.abs(normal.left - fromRange.left) > 1; + } + + // KEY NAMES + + var keyNames = {3: "Enter", 8: "Backspace", 9: "Tab", 13: "Enter", 16: "Shift", 17: "Ctrl", 18: "Alt", + 19: "Pause", 20: "CapsLock", 27: "Esc", 32: "Space", 33: "PageUp", 34: "PageDown", 35: "End", + 36: "Home", 37: "Left", 38: "Up", 39: "Right", 40: "Down", 44: "PrintScrn", 45: "Insert", + 46: "Delete", 59: ";", 61: "=", 91: "Mod", 92: "Mod", 93: "Mod", 107: "=", 109: "-", 127: "Delete", + 173: "-", 186: ";", 187: "=", 188: ",", 189: "-", 190: ".", 191: "/", 192: "`", 219: "[", 220: "\\", + 221: "]", 222: "'", 63232: "Up", 63233: "Down", 63234: "Left", 63235: "Right", 63272: "Delete", + 63273: "Home", 63275: "End", 63276: "PageUp", 63277: "PageDown", 63302: "Insert"}; + CodeMirror.keyNames = keyNames; + (function() { + // Number keys + for (var i = 0; i < 10; i++) keyNames[i + 48] = keyNames[i + 96] = String(i); + // Alphabetic keys + for (var i = 65; i <= 90; i++) keyNames[i] = String.fromCharCode(i); + // Function keys + for (var i = 1; i <= 12; i++) keyNames[i + 111] = keyNames[i + 63235] = "F" + i; + })(); + + // BIDI HELPERS + + function iterateBidiSections(order, from, to, f) { + if (!order) return f(from, to, "ltr"); + var found = false; + for (var i = 0; i < order.length; ++i) { + var part = order[i]; + if (part.from < to && part.to > from || from == to && part.to == from) { + f(Math.max(part.from, from), Math.min(part.to, to), part.level == 1 ? "rtl" : "ltr"); + found = true; + } + } + if (!found) f(from, to, "ltr"); + } + + function bidiLeft(part) { return part.level % 2 ? part.to : part.from; } + function bidiRight(part) { return part.level % 2 ? part.from : part.to; } + + function lineLeft(line) { var order = getOrder(line); return order ? bidiLeft(order[0]) : 0; } + function lineRight(line) { + var order = getOrder(line); + if (!order) return line.text.length; + return bidiRight(lst(order)); + } + + function lineStart(cm, lineN) { + var line = getLine(cm.doc, lineN); + var visual = visualLine(line); + if (visual != line) lineN = lineNo(visual); + var order = getOrder(visual); + var ch = !order ? 0 : order[0].level % 2 ? lineRight(visual) : lineLeft(visual); + return Pos(lineN, ch); + } + function lineEnd(cm, lineN) { + var merged, line = getLine(cm.doc, lineN); + while (merged = collapsedSpanAtEnd(line)) { + line = merged.find(1, true).line; + lineN = null; + } + var order = getOrder(line); + var ch = !order ? line.text.length : order[0].level % 2 ? lineLeft(line) : lineRight(line); + return Pos(lineN == null ? lineNo(line) : lineN, ch); + } + function lineStartSmart(cm, pos) { + var start = lineStart(cm, pos.line); + var line = getLine(cm.doc, start.line); + var order = getOrder(line); + if (!order || order[0].level == 0) { + var firstNonWS = Math.max(0, line.text.search(/\S/)); + var inWS = pos.line == start.line && pos.ch <= firstNonWS && pos.ch; + return Pos(start.line, inWS ? 0 : firstNonWS); + } + return start; + } + + function compareBidiLevel(order, a, b) { + var linedir = order[0].level; + if (a == linedir) return true; + if (b == linedir) return false; + return a < b; + } + var bidiOther; + function getBidiPartAt(order, pos) { + bidiOther = null; + for (var i = 0, found; i < order.length; ++i) { + var cur = order[i]; + if (cur.from < pos && cur.to > pos) return i; + if ((cur.from == pos || cur.to == pos)) { + if (found == null) { + found = i; + } else if (compareBidiLevel(order, cur.level, order[found].level)) { + if (cur.from != cur.to) bidiOther = found; + return i; + } else { + if (cur.from != cur.to) bidiOther = i; + return found; + } + } + } + return found; + } + + function moveInLine(line, pos, dir, byUnit) { + if (!byUnit) return pos + dir; + do pos += dir; + while (pos > 0 && isExtendingChar(line.text.charAt(pos))); + return pos; + } + + // This is needed in order to move 'visually' through bi-directional + // text -- i.e., pressing left should make the cursor go left, even + // when in RTL text. The tricky part is the 'jumps', where RTL and + // LTR text touch each other. This often requires the cursor offset + // to move more than one unit, in order to visually move one unit. + function moveVisually(line, start, dir, byUnit) { + var bidi = getOrder(line); + if (!bidi) return moveLogically(line, start, dir, byUnit); + var pos = getBidiPartAt(bidi, start), part = bidi[pos]; + var target = moveInLine(line, start, part.level % 2 ? -dir : dir, byUnit); + + for (;;) { + if (target > part.from && target < part.to) return target; + if (target == part.from || target == part.to) { + if (getBidiPartAt(bidi, target) == pos) return target; + part = bidi[pos += dir]; + return (dir > 0) == part.level % 2 ? part.to : part.from; + } else { + part = bidi[pos += dir]; + if (!part) return null; + if ((dir > 0) == part.level % 2) + target = moveInLine(line, part.to, -1, byUnit); + else + target = moveInLine(line, part.from, 1, byUnit); + } + } + } + + function moveLogically(line, start, dir, byUnit) { + var target = start + dir; + if (byUnit) while (target > 0 && isExtendingChar(line.text.charAt(target))) target += dir; + return target < 0 || target > line.text.length ? null : target; + } + + // Bidirectional ordering algorithm + // See http://unicode.org/reports/tr9/tr9-13.html for the algorithm + // that this (partially) implements. + + // One-char codes used for character types: + // L (L): Left-to-Right + // R (R): Right-to-Left + // r (AL): Right-to-Left Arabic + // 1 (EN): European Number + // + (ES): European Number Separator + // % (ET): European Number Terminator + // n (AN): Arabic Number + // , (CS): Common Number Separator + // m (NSM): Non-Spacing Mark + // b (BN): Boundary Neutral + // s (B): Paragraph Separator + // t (S): Segment Separator + // w (WS): Whitespace + // N (ON): Other Neutrals + + // Returns null if characters are ordered as they appear + // (left-to-right), or an array of sections ({from, to, level} + // objects) in the order in which they occur visually. + var bidiOrdering = (function() { + // Character types for codepoints 0 to 0xff + var lowTypes = "bbbbbbbbbtstwsbbbbbbbbbbbbbbssstwNN%%%NNNNNN,N,N1111111111NNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNbbbbbbsbbbbbbbbbbbbbbbbbbbbbbbbbb,N%%%%NNNNLNNNNN%%11NLNNN1LNNNNNLLLLLLLLLLLLLLLLLLLLLLLNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLN"; + // Character types for codepoints 0x600 to 0x6ff + var arabicTypes = "rrrrrrrrrrrr,rNNmmmmmmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmmmmmmmmrrrrrrrnnnnnnnnnn%nnrrrmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmmmmmmmmmmmmmNmmmm"; + function charType(code) { + if (code <= 0xf7) return lowTypes.charAt(code); + else if (0x590 <= code && code <= 0x5f4) return "R"; + else if (0x600 <= code && code <= 0x6ed) return arabicTypes.charAt(code - 0x600); + else if (0x6ee <= code && code <= 0x8ac) return "r"; + else if (0x2000 <= code && code <= 0x200b) return "w"; + else if (code == 0x200c) return "b"; + else return "L"; + } + + var bidiRE = /[\u0590-\u05f4\u0600-\u06ff\u0700-\u08ac]/; + var isNeutral = /[stwN]/, isStrong = /[LRr]/, countsAsLeft = /[Lb1n]/, countsAsNum = /[1n]/; + // Browsers seem to always treat the boundaries of block elements as being L. + var outerType = "L"; + + function BidiSpan(level, from, to) { + this.level = level; + this.from = from; this.to = to; + } + + return function(str) { + if (!bidiRE.test(str)) return false; + var len = str.length, types = []; + for (var i = 0, type; i < len; ++i) + types.push(type = charType(str.charCodeAt(i))); + + // W1. Examine each non-spacing mark (NSM) in the level run, and + // change the type of the NSM to the type of the previous + // character. If the NSM is at the start of the level run, it will + // get the type of sor. + for (var i = 0, prev = outerType; i < len; ++i) { + var type = types[i]; + if (type == "m") types[i] = prev; + else prev = type; + } + + // W2. Search backwards from each instance of a European number + // until the first strong type (R, L, AL, or sor) is found. If an + // AL is found, change the type of the European number to Arabic + // number. + // W3. Change all ALs to R. + for (var i = 0, cur = outerType; i < len; ++i) { + var type = types[i]; + if (type == "1" && cur == "r") types[i] = "n"; + else if (isStrong.test(type)) { cur = type; if (type == "r") types[i] = "R"; } + } + + // W4. A single European separator between two European numbers + // changes to a European number. A single common separator between + // two numbers of the same type changes to that type. + for (var i = 1, prev = types[0]; i < len - 1; ++i) { + var type = types[i]; + if (type == "+" && prev == "1" && types[i+1] == "1") types[i] = "1"; + else if (type == "," && prev == types[i+1] && + (prev == "1" || prev == "n")) types[i] = prev; + prev = type; + } + + // W5. A sequence of European terminators adjacent to European + // numbers changes to all European numbers. + // W6. Otherwise, separators and terminators change to Other + // Neutral. + for (var i = 0; i < len; ++i) { + var type = types[i]; + if (type == ",") types[i] = "N"; + else if (type == "%") { + for (var end = i + 1; end < len && types[end] == "%"; ++end) {} + var replace = (i && types[i-1] == "!") || (end < len && types[end] == "1") ? "1" : "N"; + for (var j = i; j < end; ++j) types[j] = replace; + i = end - 1; + } + } + + // W7. Search backwards from each instance of a European number + // until the first strong type (R, L, or sor) is found. If an L is + // found, then change the type of the European number to L. + for (var i = 0, cur = outerType; i < len; ++i) { + var type = types[i]; + if (cur == "L" && type == "1") types[i] = "L"; + else if (isStrong.test(type)) cur = type; + } + + // N1. A sequence of neutrals takes the direction of the + // surrounding strong text if the text on both sides has the same + // direction. European and Arabic numbers act as if they were R in + // terms of their influence on neutrals. Start-of-level-run (sor) + // and end-of-level-run (eor) are used at level run boundaries. + // N2. Any remaining neutrals take the embedding direction. + for (var i = 0; i < len; ++i) { + if (isNeutral.test(types[i])) { + for (var end = i + 1; end < len && isNeutral.test(types[end]); ++end) {} + var before = (i ? types[i-1] : outerType) == "L"; + var after = (end < len ? types[end] : outerType) == "L"; + var replace = before || after ? "L" : "R"; + for (var j = i; j < end; ++j) types[j] = replace; + i = end - 1; + } + } + + // Here we depart from the documented algorithm, in order to avoid + // building up an actual levels array. Since there are only three + // levels (0, 1, 2) in an implementation that doesn't take + // explicit embedding into account, we can build up the order on + // the fly, without following the level-based algorithm. + var order = [], m; + for (var i = 0; i < len;) { + if (countsAsLeft.test(types[i])) { + var start = i; + for (++i; i < len && countsAsLeft.test(types[i]); ++i) {} + order.push(new BidiSpan(0, start, i)); + } else { + var pos = i, at = order.length; + for (++i; i < len && types[i] != "L"; ++i) {} + for (var j = pos; j < i;) { + if (countsAsNum.test(types[j])) { + if (pos < j) order.splice(at, 0, new BidiSpan(1, pos, j)); + var nstart = j; + for (++j; j < i && countsAsNum.test(types[j]); ++j) {} + order.splice(at, 0, new BidiSpan(2, nstart, j)); + pos = j; + } else ++j; + } + if (pos < i) order.splice(at, 0, new BidiSpan(1, pos, i)); + } + } + if (order[0].level == 1 && (m = str.match(/^\s+/))) { + order[0].from = m[0].length; + order.unshift(new BidiSpan(0, 0, m[0].length)); + } + if (lst(order).level == 1 && (m = str.match(/\s+$/))) { + lst(order).to -= m[0].length; + order.push(new BidiSpan(0, len - m[0].length, len)); + } + if (order[0].level != lst(order).level) + order.push(new BidiSpan(order[0].level, len, len)); + + return order; + }; + })(); + + // THE END + + CodeMirror.version = "5.1.1"; + + return CodeMirror; +}); \ No newline at end of file diff --git a/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.policy.wizard/public/js/policy-create.js b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.policy.wizard/public/js/policy-create.js new file mode 100644 index 0000000000..84e9886436 --- /dev/null +++ b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.policy.wizard/public/js/policy-create.js @@ -0,0 +1,670 @@ +/* + * Copyright (c) 2016, 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. + */ + +var validateStep = {}; +var stepForwardFrom = {}; +var stepBackFrom = {}; +var policy = {}; +var configuredOperations = []; +var deviceTypeLabel; + +/** + * Method to update the visibility of grouped input. + * @param domElement HTML grouped-input element with class name "grouped-input" + */ +var updateGroupedInputVisibility = function (domElement) { + if ($(".parent-input:first", domElement).is(":checked")) { + if ($(".grouped-child-input:first", domElement).hasClass("disabled")) { + $(".grouped-child-input:first", domElement).removeClass("disabled"); + } + $(".child-input", domElement).each(function () { + $(this).prop('disabled', false); + }); + } else { + if (!$(".grouped-child-input:first", domElement).hasClass("disabled")) { + $(".grouped-child-input:first", domElement).addClass("disabled"); + } + $(".child-input", domElement).each(function () { + $(this).prop('disabled', true); + }); + } +}; + +/** + * Checks if provided number is valid against a range. + * + * @param numberInput Number Input + * @param min Minimum Limit + * @param max Maximum Limit + * @returns {boolean} Returns true if input is within the specified range + */ +var inputIsValidAgainstRange = function (numberInput, min, max) { + return (numberInput == min || (numberInput > min && numberInput < max) || numberInput == max); +}; + +/** + * Checks if provided input is valid against RegEx input. + * + * @param regExp Regular expression + * @param input Input string to check + * @returns {boolean} Returns true if input matches RegEx + */ +var inputIsValidAgainstRegExp = function (regExp, input) { + return regExp.test(input); +}; + +validateStep["policy-profile"] = function () { + return true; +}; + +stepForwardFrom["policy-profile"] = function () { + policy["profile"] = operationModule.generateProfile(policy["platform"], configuredOperations); + // updating next-page wizard title with selected platform + $("#policy-criteria-page-wizard-title").text("ADD " + deviceTypeLabel + " POLICY"); +}; + +stepBackFrom["policy-profile"] = function () { + // reinitialize configuredOperations + configuredOperations = []; + // clearing already-loaded platform specific hidden-operations html content from the relevant div + // so that, the wrong content would not be shown at the first glance, in case + // the user selects a different platform + $(".wr-advance-operations").html( + "
      " + + "
      " + + "     " + + "" + + "    " + + "Loading Platform Features . . ." + + "
      " + + "
      " + + "
      " + ); +}; + +stepForwardFrom["policy-criteria"] = function () { + $("input[type='radio'].select-users-radio").each(function () { + if ($(this).is(':radio')) { + if ($(this).is(":checked")) { + if ($(this).attr("id") == "users-radio-btn") { + policy["selectedUsers"] = $("#users-input").val(); + } else if ($(this).attr("id") == "user-roles-radio-btn") { + policy["selectedUserRoles"] = $("#user-roles-input").val(); + } + } + } + }); + policy["selectedNonCompliantAction"] = $("#action-input").find(":selected").data("action"); + policy["selectedOwnership"] = $("#ownership-input").val(); + // updating next-page wizard title with selected platform + $("#policy-naming-page-wizard-title").text("ADD " + deviceTypeLabel + " POLICY"); +}; + +/** + * Checks if provided input is valid against provided length range. + * + * @param input Alphanumeric or non-alphanumeric input + * @param minLength Minimum Required Length + * @param maxLength Maximum Required Length + * @returns {boolean} Returns true if input matches the provided minimum length and maximum length + */ +var inputIsValidAgainstLength = function (input, minLength, maxLength) { + var length = input.length; + return (length == minLength || (length > minLength && length < maxLength) || length == maxLength); +}; + +validateStep["policy-naming"] = function () { + var validationStatus = {}; + + // taking values of inputs to be validated + var policyName = $("input#policy-name-input").val(); + // starting validation process and updating validationStatus + if (!policyName) { + validationStatus["error"] = true; + validationStatus["mainErrorMsg"] = "Policy name is empty. You cannot proceed."; + } else if (!inputIsValidAgainstLength(policyName, 1, 30)) { + validationStatus["error"] = true; + validationStatus["mainErrorMsg"] = + "Policy name exceeds maximum allowed length. Please check."; + } else { + validationStatus["error"] = false; + } + // ending validation process + + // start taking specific actions upon validation + var wizardIsToBeContinued; + if (validationStatus["error"]) { + wizardIsToBeContinued = false; + var mainErrorMsgWrapper = "#policy-naming-main-error-msg"; + var mainErrorMsg = mainErrorMsgWrapper + " span"; + $(mainErrorMsg).text(validationStatus["mainErrorMsg"]); + $(mainErrorMsgWrapper).removeClass("hidden"); + } else { + wizardIsToBeContinued = true; + } + + return wizardIsToBeContinued; +}; + +validateStep["policy-naming-publish"] = function () { + var validationStatus = {}; + + // taking values of inputs to be validated + var policyName = $("input#policy-name-input").val(); + // starting validation process and updating validationStatus + if (!policyName) { + validationStatus["error"] = true; + validationStatus["mainErrorMsg"] = "Policy name is empty. You cannot proceed."; + } else if (!inputIsValidAgainstLength(policyName, 1, 30)) { + validationStatus["error"] = true; + validationStatus["mainErrorMsg"] = + "Policy name exceeds maximum allowed length. Please check."; + } else { + validationStatus["error"] = false; + } + // ending validation process + + // start taking specific actions upon validation + var wizardIsToBeContinued; + if (validationStatus["error"]) { + wizardIsToBeContinued = false; + var mainErrorMsgWrapper = "#policy-naming-main-error-msg"; + var mainErrorMsg = mainErrorMsgWrapper + " span"; + $(mainErrorMsg).text(validationStatus["mainErrorMsg"]); + $(mainErrorMsgWrapper).removeClass("hidden"); + } else { + wizardIsToBeContinued = true; + } + + return wizardIsToBeContinued; +}; + +stepForwardFrom["policy-naming-publish"] = function () { + policy["policyName"] = $("#policy-name-input").val(); + policy["description"] = $("#policy-description-input").val(); + //All data is collected. Policy can now be updated. + savePolicy(policy, "publish"); +}; +stepForwardFrom["policy-naming"] = function () { + policy["policyName"] = $("#policy-name-input").val(); + policy["description"] = $("#policy-description-input").val(); + //All data is collected. Policy can now be updated. + savePolicy(policy, "save"); +}; + +var savePolicy = function (policy, state) { + var profilePayloads = [{ + "featureCode": "CONFIG", + "deviceTypeId": policy["platformId"], + "content": {"policyDefinition": window.queryEditor.getValue()} + }]; + + var payload = { + "policyName": policy["policyName"], + "description": policy["description"], + "compliance": policy["selectedNonCompliantAction"], + "ownershipType": "ANY", + "profile": { + "profileName": policy["policyName"], + "deviceType": { + "id": policy["platformId"], + "name": policy["platform"] + }, + "profileFeaturesList": profilePayloads + } + }; + + if (policy["selectedUsers"]) { + payload["users"] = policy["selectedUsers"]; + } else if (policy["selectedUserRoles"]) { + payload["roles"] = policy["selectedUserRoles"]; + } else { + payload["users"] = []; + payload["roles"] = []; + } + + var serviceURL; + if (state == "save") { + serviceURL = "/devicemgt_admin/policies/inactive-policy" + } else if (state == "publish") { + serviceURL = "/devicemgt_admin/policies/active-policy" + } + invokerUtil.post( + serviceURL, + payload, + function () { + $(".add-policy").addClass("hidden"); + $(".policy-naming").addClass("hidden"); + $(".policy-message").removeClass("hidden"); + if (state == "publish") { + publishToDevice(); + } + }, + function (err) { + console.log(err); + } + ); +}; + +function publishToDevice() { + var payload = { + "policyName": policy["policyName"], + "description": policy["description"], + "compliance": policy["selectedNonCompliantAction"], + "ownershipType": "ANY", + "deviceId": getParameterByName('deviceId'), + "profile": { + "profileName": policy["policyName"], + "deviceType": { + "id": policy["platformId"], + "name": policy["platform"] + }, + "policyDefinition": window.queryEditor.getValue(), + "policyDescription": policy["description"] + } + }; + + var successCallback = function (data, status) { + console.log("Data: " + data + "\nStatus: " + status); + }; + + var data = { + url: "/devicemgt/api/policies/add", + type: "POST", + contentType: "application/json", + accept: "application/json", + success: successCallback, + data: JSON.stringify(payload) + }; + + $.ajax(data).fail(function (jqXHR) { + console.log("Error: " + jqXHR); + }); + +} + +// Start of functions related to grid-input-view + +/** + * Method to set count id to cloned elements. + * @param {object} addFormContainer + */ +var setId = function (addFormContainer) { + $(addFormContainer).find("[data-add-form-clone]").each(function (i) { + $(this).attr("id", $(this).attr("data-add-form-clone").slice(1) + "-" + (i + 1)); + if ($(this).find(".index").length > 0) { + $(this).find(".index").html(i + 1); + } + }); +}; + +/** + * Method to set count id to cloned elements. + * @param {object} addFormContainer + */ +var showHideHelpText = function (addFormContainer) { + var helpText = "[data-help-text=add-form]"; + if ($(addFormContainer).find("[data-add-form-clone]").length > 0) { + $(addFormContainer).find(helpText).hide(); + } else { + $(addFormContainer).find(helpText).show(); + } +}; + +function formatRepo(user) { + if (user.loading) { + return user.text + } + if (!user.username) { + return; + } + var markup = '
      ' + + '
      ' + + '
      ' + + '
      ' + user.username + '
      '; + if (user.firstname) { + markup += '
      ' + user.firstname + '
      '; + } + if (user.emailAddress) { + markup += '
      ' + user.emailAddress + '
      '; + } + markup += '
      '; + return markup; +} + +function formatRepoSelection(user) { + return user.username || user.text; +} + +// End of functions related to grid-input-view + + +$(document).ready(function () { + window.queryEditor = CodeMirror.fromTextArea(document.getElementById('policy-definition-input'), { + mode: MIME_TYPE_SIDDHI_QL, + indentWithTabs: true, + smartIndent: true, + lineNumbers: true, + matchBrackets: true, + autofocus: true, + extraKeys: { + "Shift-2": function (cm) { + insertStr(cm, cm.getCursor(), '@'); + CodeMirror.showHint(cm, getAnnotationHints); + }, + "Ctrl-Space": "autocomplete" + } + }); + + $("#users-input").select2({ + multiple: true, + tags: true, + ajax: { + url: window.location.origin + "/devicemgt/api/invoker/execute/", + method: "POST", + dataType: 'json', + delay: 250, + id: function (user) { + return user.username; + }, + data: function (params) { + var postData = {}; + postData.actionMethod = "GET"; + postData.actionUrl = "/devicemgt_admin/users"; + postData.actionPayload = JSON.stringify({ + q: params.term, // search term + page: params.page + }); + + return JSON.stringify(postData); + }, + processResults: function (data, page) { + var newData = []; + $.each(data.responseContent, function (index, value) { + value.id = value.username; + newData.push(value); + }); + return { + results: newData + }; + }, + cache: true + }, + escapeMarkup: function (markup) { + return markup; + }, // let our custom formatter work + minimumInputLength: 1, + templateResult: formatRepo, // omitted for brevity, see the source of this page + templateSelection: formatRepoSelection // omitted for brevity, see the source of this page + }); + + // Adding initial state of wizard-steps. + $("#policy-profile-wizard-steps").html($(".wr-steps").html()); + + policy["platform"] = $("#platform").data("platform"); + policy["platformId"] = $("#platform").data("platform-id"); + deviceTypeLabel = $("#platform").data("platform-label"); + // updating next-page wizard title with selected platform + $("#policy-profile-page-wizard-title").text("ADD " + deviceTypeLabel + " POLICY"); + + $("select.select2[multiple=multiple]").select2({ + "tags": true + }); + + $("#users-select-field").hide(); + $("#user-roles-select-field").show(); + + $("input[type='radio'].select-users-radio").change(function () { + if ($("#users-radio-btn").is(":checked")) { + $("#user-roles-select-field").hide(); + $("#users-select-field").show(); + } + if ($("#user-roles-radio-btn").is(":checked")) { + $("#users-select-field").hide(); + $("#user-roles-select-field").show(); + } + }); + + // Support for special input type "ANY" on user(s) & user-role(s) selection + $("#user-roles-input").select2({ + "tags": true + }).on("select2:select", function (e) { + if (e.params.data.id == "ANY") { + $(this).val("ANY").trigger("change"); + } else { + $("option[value=ANY]", this).prop("selected", false).parent().trigger("change"); + } + }); + + // Maintains an array of configured features of the profile + var advanceOperations = ".wr-advance-operations"; + $(advanceOperations).on("click", ".wr-input-control.switch", function (event) { + var operationCode = $(this).parents(".operation-data").data("operation-code"); + var operation = $(this).parents(".operation-data").data("operation"); + var operationDataWrapper = $(this).data("target"); + // prevents event bubbling by figuring out what element it's being called from. + if (event.target.tagName == "INPUT") { + var featureConfiguredIcon; + if ($("input[type='checkbox']", this).is(":checked")) { + configuredOperations.push(operationCode); + // when a feature is enabled, if "zero-configured-features" msg is available, hide that. + var zeroConfiguredOperationsErrorMsg = "#policy-profile-main-error-msg"; + if (!$(zeroConfiguredOperationsErrorMsg).hasClass("hidden")) { + $(zeroConfiguredOperationsErrorMsg).addClass("hidden"); + } + // add configured-state-icon to the feature + featureConfiguredIcon = "#" + operation + "-configured"; + if ($(featureConfiguredIcon).hasClass("hidden")) { + $(featureConfiguredIcon).removeClass("hidden"); + } + } else { + //splicing the array if operation is present. + var index = $.inArray(operationCode, configuredOperations); + if (index != -1) { + configuredOperations.splice(index, 1); + } + // when a feature is disabled, clearing all its current configured, error or success states + var subErrorMsgWrapper = "#" + operation + "-feature-error-msg"; + var subErrorIcon = "#" + operation + "-error"; + var subOkIcon = "#" + operation + "-ok"; + featureConfiguredIcon = "#" + operation + "-configured"; + + if (!$(subErrorMsgWrapper).hasClass("hidden")) { + $(subErrorMsgWrapper).addClass("hidden"); + } + if (!$(subErrorIcon).hasClass("hidden")) { + $(subErrorIcon).addClass("hidden"); + } + if (!$(subOkIcon).hasClass("hidden")) { + $(subOkIcon).addClass("hidden"); + } + if (!$(featureConfiguredIcon).hasClass("hidden")) { + $(featureConfiguredIcon).addClass("hidden"); + } + // reinitializing input fields into the defaults + $(operationDataWrapper + " input").each( + function () { + if ($(this).is("input:text")) { + $(this).val($(this).data("default")); + } else if ($(this).is("input:password")) { + $(this).val(""); + } else if ($(this).is("input:checkbox")) { + $(this).prop("checked", $(this).data("default")); + // if this checkbox is the parent input of a grouped-input + if ($(this).hasClass("parent-input")) { + var groupedInput = $(this).parent().parent().parent(); + updateGroupedInputVisibility(groupedInput); + } + } + } + ); + // reinitializing select fields into the defaults + $(operationDataWrapper + " select").each( + function () { + var defaultOption = $(this).data("default"); + $("option:eq(" + defaultOption + ")", this).prop("selected", "selected"); + } + ); + // collapsing expanded-panes (upon the selection of html-select-options) if any + $(operationDataWrapper + " .expanded").each( + function () { + if ($(this).hasClass("expanded")) { + $(this).removeClass("expanded"); + } + $(this).slideUp(); + } + ); + // removing all entries of grid-input elements if exist + $(operationDataWrapper + " .grouped-array-input").each( + function () { + var gridInputs = $(this).find("[data-add-form-clone]"); + if (gridInputs.length > 0) { + gridInputs.remove(); + } + var helpTexts = $(this).find("[data-help-text=add-form]"); + if (helpTexts.length > 0) { + helpTexts.show(); + } + } + ); + } + } + }); + + // adding support for cloning multiple profiles per feature with cloneable class definitions + $(advanceOperations).on("click", ".multi-view.add.enabled", function () { + // get a copy of .cloneable and create new .cloned div element + var cloned = "

      " + $(".cloneable", $(this).parent().parent()).html() + "
      "; + // append newly created .cloned div element to panel-body + $(this).parent().parent().append(cloned); + // enable remove action of newly cloned div element + $(".cloned", $(this).parent().parent()).each( + function () { + if ($(".multi-view.remove", this).hasClass("disabled")) { + $(".multi-view.remove", this).removeClass("disabled"); + } + if (!$(".multi-view.remove", this).hasClass("enabled")) { + $(".multi-view.remove", this).addClass("enabled"); + } + } + ); + }); + + $(advanceOperations).on("click", ".multi-view.remove.enabled", function () { + $(this).parent().remove(); + }); + + // enabling or disabling grouped-input based on the status of a parent check-box + $(advanceOperations).on("click", ".grouped-input", function () { + updateGroupedInputVisibility(this); + }); + + // add form entry click function for grid inputs + $(advanceOperations).on("click", "[data-click-event=add-form]", function () { + var addFormContainer = $("[data-add-form-container=" + $(this).attr("href") + "]"); + var clonedForm = $("[data-add-form=" + $(this).attr("href") + "]").clone(). + find("[data-add-form-element=clone]").attr("data-add-form-clone", $(this).attr("href")); + + // adding class .child-input to capture text-input-array-values + $("input, select", clonedForm).addClass("child-input"); + + $(addFormContainer).append(clonedForm); + setId(addFormContainer); + showHideHelpText(addFormContainer); + }); + + // remove form entry click function for grid inputs + $(advanceOperations).on("click", "[data-click-event=remove-form]", function () { + var addFormContainer = $("[data-add-form-container=" + $(this).attr("href") + "]"); + + $(this).closest("[data-add-form-element=clone]").remove(); + setId(addFormContainer); + showHideHelpText(addFormContainer); + }); + + $(".wizard-stepper").click(function () { + // button clicked here can be either a continue button or a back button. + var currentStep = $(this).data("current"); + var validationIsRequired = $(this).data("validate"); + var wizardIsToBeContinued; + + if (validationIsRequired) { + wizardIsToBeContinued = validateStep[currentStep](); + } else { + wizardIsToBeContinued = true; + } + + if (wizardIsToBeContinued) { + // When moving back and forth, following code segment will + // remove if there are any visible error-messages. + var errorMsgWrappers = ".alert.alert-danger"; + $(errorMsgWrappers).each( + function () { + if (!$(this).hasClass("hidden")) { + $(this).addClass("hidden"); + } + } + ); + + var nextStep = $(this).data("next"); + var isBackBtn = $(this).data("is-back-btn"); + + // if current button is a continuation... + if (!isBackBtn) { + // initiate stepForwardFrom[*] functions to gather form data. + if (stepForwardFrom[currentStep]) { + stepForwardFrom[currentStep](this); + } + } else { + // initiate stepBackFrom[*] functions to rollback. + if (stepBackFrom[currentStep]) { + stepBackFrom[currentStep](); + } + } + + // following step occurs only at the last stage of the wizard. + if (!nextStep) { + window.location.href = $(this).data("direct"); + } + + // updating next wizard step as current. + $(".itm-wiz").each(function () { + var step = $(this).data("step"); + if (step == nextStep) { + $(this).addClass("itm-wiz-current"); + } else { + $(this).removeClass("itm-wiz-current"); + } + }); + + // adding next update of wizard-steps. + $("#" + nextStep + "-wizard-steps").html($(".wr-steps").html()); + + // hiding current section of the wizard and showing next section. + $("." + currentStep).addClass("hidden"); + $("." + nextStep).removeClass("hidden"); + } + }); +}); + +function getParameterByName(name) { + name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]"); + var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"), + results = regex.exec(location.search); + return results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " ")); +} \ No newline at end of file diff --git a/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.policy.wizard/public/js/sql.js b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.policy.wizard/public/js/sql.js new file mode 100644 index 0000000000..9f92fbc938 --- /dev/null +++ b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.policy.wizard/public/js/sql.js @@ -0,0 +1,310 @@ +/* + * Copyright (c) 2016, 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. + */ + +/*Annotations, Annotation Names and relevant tokens*/ +var ANNOTATION_IMPORT = "Import"; +var ANNOTATION_EXPORT = "Export"; + +var ANNOTATION_TOKEN_AT = "@"; +var ANNOTATION_TOKEN_OPENING_BRACKET = "("; +var ANNOTATION_TOKEN_CLOSING_BRACKET = ")"; + +var REGEX_LINE_STARTING_WITH_PLAN = /^@Plan.*/g; +var REGEX_LINE_STARTING_WITH_SINGLE_LINE_COMMENT = /^--.*/g; +var REGEX_LINE_STARTING_WITH_MULTI_LINE_COMMENT = /^\/\*.*\*\//g; +var REGEX_LINE_STARTING_WITH_IMPORT_STATEMENT = /^@Import.*/g; + +var SIDDHI_STATEMENT_DELIMETER = ";"; +var SIDDHI_LINE_BREAK = "\n"; +var SIDDHI_LINE_BREAK_CHARACTER = '\n'; +var SIDDHI_SINGLE_QUOTE = "'"; +var SIDDHI_SPACE_LITERAL = " "; + +var SIDDHI_LITERAL_DEFINE_STREAM = "define stream"; + +var MIME_TYPE_SIDDHI_QL = "text/siddhi-ql"; + + +CodeMirror.defineMode("sql", function (config, parserConfig) { + "use strict"; + + var client = parserConfig.client || {}, + atoms = parserConfig.atoms || {"false":true, "true":true, "null":true}, + builtin = parserConfig.builtin || {}, + keywords = parserConfig.keywords || {}, + operatorChars = parserConfig.operatorChars || /^[*+\-%<>!=&|~^]/, + support = parserConfig.support || {}, + hooks = parserConfig.hooks || {}, + dateSQL = parserConfig.dateSQL || {"date":true, "time":true, "timestamp":true}; + + function tokenBase(stream, state) { + var ch = stream.next(); + + // call hooks from the mime type + if (hooks[ch]) { + var result = hooks[ch](stream, state); + if (result !== false) return result; + } + + if (ch.charCodeAt(0) > 47 && ch.charCodeAt(0) < 58) { + // numbers + // ref: http://dev.mysql.com/doc/refman/5.5/en/number-literals.html + stream.match(/^[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?/); + support.decimallessFloat == true && stream.eat('.'); + return "number"; + } else if (ch == "'" || (ch == '"' && support.doubleQuote)) { + // strings + // ref: http://dev.mysql.com/doc/refman/5.5/en/string-literals.html + state.tokenize = tokenLiteral(ch); + return state.tokenize(stream, state); + } else if (/^[\(\),\;\[\]]/.test(ch)) { + // no highlightning + return null; + } else if ((ch == "-" && stream.eat("-") && (!support.commentSpaceRequired || stream.eat(" ")))) { + // 1-line comments + // ref: https://kb.askmonty.org/en/comment-syntax/ + stream.skipToEnd(); + return "comment"; + } else if (ch == "/" && stream.eat("*")) { + // multi-line comments + // ref: https://kb.askmonty.org/en/comment-syntax/ + state.tokenize = tokenComment; + return state.tokenize(stream, state); + } else if (ch == ".") { + // .1 for 0.1 + if (support.zerolessFloat == true && stream.match(/^(?:\d+(?:e[+-]?\d+)?)/i)) { + return "number"; + } + } else { + stream.eatWhile(/^[_\-\w\d]/); /* Character '-' will also be eaten, to prevent the highlight happening in keywords being embedded in non-keyword strings. For example, 'all' in 'all-nonkeyword' */ + var word = stream.current().toLowerCase(); // Added toLowerCase() to highlight keywords in a case insensitive manner. + // dates (standard SQL syntax) + // ref: http://dev.mysql.com/doc/refman/5.5/en/date-and-time-literals.html + if (dateSQL.hasOwnProperty(word) && (stream.match(/^( )+'[^']*'/) || stream.match(/^( )+"[^"]*"/))) + return "number"; + if (atoms.hasOwnProperty(word)) return "atom"; + if (builtin.hasOwnProperty(word)) return "builtin"; + if (keywords.hasOwnProperty(word)) return "keyword"; + if (client.hasOwnProperty(word)) return "string-2"; + return null; + } + } + + // 'string', with char specified in quote escaped by '\' + function tokenLiteral(quote) { + return function (stream, state) { + var escaped = false, ch; + while ((ch = stream.next()) != null) { + if (ch == quote && !escaped) { + state.tokenize = tokenBase; + break; + } + escaped = !escaped && ch == "\\"; + } + return "string"; + }; + } + + function tokenComment(stream, state) { + while (true) { + if (stream.skipTo("*")) { + stream.next(); + if (stream.eat("/")) { + state.tokenize = tokenBase; + break; + } + } else { + stream.skipToEnd(); + break; + } + } + return "comment"; + } + + function pushContext(stream, state, type) { + state.context = { + prev:state.context, + indent:stream.indentation(), + col:stream.column(), + type:type + }; + } + + function popContext(state) { + state.indent = state.context.indent; + state.context = state.context.prev; + } + + return { + startState:function () { + return {tokenize:tokenBase, context:null}; + }, + + token:function (stream, state) { + if (stream.sol()) { + if (state.context && state.context.align == null) + state.context.align = false; + } + if (stream.eatSpace()) return null; + + var style = state.tokenize(stream, state); + if (style == "comment") return style; + + if (state.context && state.context.align == null) + state.context.align = true; + + var tok = stream.current(); + if (tok == "(") + pushContext(stream, state, ")"); + else if (tok == "[") + pushContext(stream, state, "]"); + else if (state.context && state.context.type == tok) + popContext(state); + return style; + }, + + indent:function (state, textAfter) { + var cx = state.context; + if (!cx) return CodeMirror.Pass; + var closing = textAfter.charAt(0) == cx.type; + if (cx.align) return cx.col + (closing ? 0 : 1); + else return cx.indent + (closing ? 0 : config.indentUnit); + }, + + blockCommentStart: "/*", + blockCommentEnd: "*/", + lineComment: "--" + }; +}); + +(function () { + "use strict"; + + // `identifier` + function hookIdentifier(stream) { + // MySQL/MariaDB identifiers + // ref: http://dev.mysql.com/doc/refman/5.6/en/identifier-qualifiers.html + var ch; + while ((ch = stream.next()) != null) { + if (ch == "`" && !stream.eat("`")) return "variable-2"; + } + stream.backUp(stream.current().length - 1); + return stream.eatWhile(/\w/) ? "variable-2" : null; + } + + // variable token + function hookVar(stream) { + // variables + // @@prefix.varName @varName + // varName can be quoted with ` or ' or " + // ref: http://dev.mysql.com/doc/refman/5.5/en/user-variables.html + if (stream.eat("@")) { + stream.match(/^session\./); + stream.match(/^local\./); + stream.match(/^global\./); + } + + if (stream.eat("'")) { + stream.match(/^.*'/); + return "variable-2"; + } else if (stream.eat('"')) { + stream.match(/^.*"/); + return "variable-2"; + } else if (stream.eat("`")) { + stream.match(/^.*`/); + return "variable-2"; + } else if (stream.match(/^[0-9a-zA-Z$\.\_]+/)) { + return "variable-2"; + } + return null; + } + + ; + + // short client keyword token + function hookClient(stream) { + // \N means NULL + // ref: http://dev.mysql.com/doc/refman/5.5/en/null-values.html + if (stream.eat("N")) { + return "atom"; + } + // \g, etc + // ref: http://dev.mysql.com/doc/refman/5.5/en/mysql-commands.html + return stream.match(/^[a-zA-Z.#!?]/) ? "variable-2" : null; + } + + // these keywords are used by all SQL dialects (however, a mode can still overwrite it) + var sqlKeywordsWithoutSymbols = "all and as begin by contains define delete end events " + + "every first for from full group having inner insert into join last " + + "left not of on or outer output partition raw return right select snapshot stream table "; + var sqlKeywords = ", : ? # ( ) " + sqlKeywordsWithoutSymbols; + var builtIn = "bool double float int long object string "; + var atoms = "false true null "; + var dateSQL = "days hours milliseconds minutes months seconds "; + var allSqlSuggestions = sqlKeywordsWithoutSymbols + builtIn + atoms + dateSQL; + + // turn a space-separated list into an array + function set(str) { + var obj = {}, words = str.split(" "); + for (var i = 0; i < words.length; ++i) obj[words[i]] = true; + return obj; + } + + // A generic SQL Mode. It's not a standard, it just try to support what is generally supported + CodeMirror.defineMIME(MIME_TYPE_SIDDHI_QL, { + name:"sql", + keywords:set(sqlKeywords), + builtin:set(builtIn), + atoms:set(atoms), + operatorChars:/^[*+%<>!=/]/, + dateSQL:set(dateSQL), + support:set("doubleQuote "), + allSqlSuggestions:set(allSqlSuggestions) + }); +}()); + +/* + How Properties of Mime Types are used by SQL Mode + ================================================= + + keywords: + A list of keywords you want to be highlighted. + functions: + A list of function names you want to be highlighted. + builtin: + A list of builtin types you want to be highlighted (if you want types to be of class "builtin" instead of "keyword"). + operatorChars: + All characters that must be handled as operators. + client: + Commands parsed and executed by the client (not the server). + support: + A list of supported syntaxes which are not common, but are supported by more than 1 DBMS. + * ODBCdotTable: .tableName + * zerolessFloat: .1 + * doubleQuote + * nCharCast: N'string' + * charsetCast: _utf8'string' + * commentHash: use # char for comments + * commentSlashSlash: use // for comments + * commentSpaceRequired: require a space after -- for comments + atoms: + Keywords that must be highlighted as atoms,. Some DBMS's support more atoms than others: + UNKNOWN, INFINITY, UNDERFLOW, NaN... + dateSQL: + Used for date/time SQL standard syntax, because not all DBMS's support same temporal types. + */ \ No newline at end of file diff --git a/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.policy.wizard/wizard.hbs b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.policy.wizard/wizard.hbs new file mode 100644 index 0000000000..4cc6fa2726 --- /dev/null +++ b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.policy.wizard/wizard.hbs @@ -0,0 +1,255 @@ +{{#zone "topCss"}} + {{css "css/codemirror.css"}} +{{/zone}} + +
      +
      + + + + + + + + +
      +
      +

      ADD POLICY

      +
      +
      +
      +
      +
      +

      Step 2: Configure profile

      +
      + + +
      +
      +
      + +
      +
      +
      +
      + +
      +
      +
      +
      + + +
      +
      +{{#zone "bottomJs"}} + {{js "js/codemirror.js"}} + {{js "js/sql.js"}} + {{js "js/policy-create.js"}} +{{/zone}} \ No newline at end of file diff --git a/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.policy.wizard/wizard.js b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.policy.wizard/wizard.js new file mode 100644 index 0000000000..925a97ea47 --- /dev/null +++ b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.policy.wizard/wizard.js @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2016, 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("wizard.js"); + var DTYPE_CONF_DEVICE_TYPE_KEY = "deviceType"; + var DTYPE_CONF_DEVICE_TYPE_LABEL_KEY = "label"; + + var userModule = require("/app/modules/user.js")["userModule"]; + var utility = require('/app/modules/utility.js').utility; + var response = userModule.getRoles(); + var wizardPage = {}; + if (response["status"] == "success") { + wizardPage["roles"] = response["content"]; + } + var deviceType = context.uriParams.deviceType; + var typesListResponse = userModule.getPlatforms(); + if (typesListResponse["status"] == "success") { + for (var type in typesListResponse["content"]) { + if (deviceType == typesListResponse["content"][type]["name"]) { + wizardPage["type"] = typesListResponse["content"][type]; + var deviceTypeLabel = deviceType; + var configs = utility.getDeviceTypeConfig(deviceType); + if (configs && configs[DTYPE_CONF_DEVICE_TYPE_KEY][DTYPE_CONF_DEVICE_TYPE_LABEL_KEY]) { + deviceTypeLabel = configs[DTYPE_CONF_DEVICE_TYPE_KEY][DTYPE_CONF_DEVICE_TYPE_LABEL_KEY]; + } + wizardPage["type"]["label"] = deviceTypeLabel; + } + } + } + return wizardPage; +} \ No newline at end of file diff --git a/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.policy.wizard/wizard.json b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.policy.wizard/wizard.json new file mode 100644 index 0000000000..fd25901297 --- /dev/null +++ b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.policy.wizard/wizard.json @@ -0,0 +1,3 @@ +{ + "version" : "1.0.0" +} \ No newline at end of file diff --git a/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.ui.header.logo/logo.hbs b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.ui.header.logo/logo.hbs new file mode 100644 index 0000000000..b1841e1864 --- /dev/null +++ b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.ui.header.logo/logo.hbs @@ -0,0 +1 @@ +{{#zone "productName"}}{{@app.conf.appName}}{{/zone}} \ No newline at end of file diff --git a/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.ui.header.logo/logo.json b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.ui.header.logo/logo.json new file mode 100644 index 0000000000..4e30bc0747 --- /dev/null +++ b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot.ui/src/main/resources/jaggeryapps/devicemgt/app/units/iot.unit.ui.header.logo/logo.json @@ -0,0 +1,5 @@ +{ + "version": "1.0.0", + "index": 29, + "extends": "mdm.unit.ui.header.logo" +} \ No newline at end of file diff --git a/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot/pom.xml b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot/pom.xml new file mode 100644 index 0000000000..cfdc32dbce --- /dev/null +++ b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot/pom.xml @@ -0,0 +1,213 @@ + + + + + + + iot-base-plugin + org.wso2.carbon.devicemgt-plugins + 2.1.0-SNAPSHOT + ../pom.xml + + + 4.0.0 + org.wso2.carbon.device.mgt.iot + bundle + WSO2 Carbon - IoT Device Management Common Impl + WSO2 Carbon - IoT Device Management and Control Implementation + http://wso2.org + + + + + org.apache.felix + maven-scr-plugin + + + maven-compiler-plugin + + 1.7 + 1.7 + + 2.3.2 + + + org.apache.felix + maven-bundle-plugin + 1.4.0 + true + + + ${project.artifactId} + ${project.artifactId} + ${carbon.devicemgt.plugins.version} + IoT Server Impl Bundle + org.wso2.carbon.device.mgt.iot.internal + + org.jivesoftware.smack.*, + javax.xml.namespace;resolution:=optional, + javax.xml.validation;resolution:=optional, + org.apache.commons.codec.binary, + org.apache.commons.collections.map, + org.apache.http.*, + org.apache.commons.io.*, + org.apache.commons.logging.*, + org.json;version="${commons-json.version}", + org.wso2.carbon.base.*, + org.wso2.carbon.databridge.*, + org.wso2.carbon.user.api, + org.wso2.carbon.user.core.service, + org.osgi.framework, + org.osgi.service.component, + javax.xml.bind.*;resolution:=optional, + javax.naming;resolution:=optional, + javax.sql;resolution:=optional, + javax.xml.bind.annotation.*;resolution:=optional, + javax.xml.parsers.*;resolution:=optional, + javax.net;resolution:=optional, + javax.net.ssl;resolution:=optional, + javax.crypto, + org.apache.tomcat.util.codec.binary, + org.w3c.dom;resolution:=optional, + org.wso2.carbon.core;version="${carbon.kernel.version.range}", + org.wso2.carbon.utils.*;version="${carbon.kernel.version.range}", + org.wso2.carbon.device.mgt.common.*, + org.wso2.carbon.device.mgt.core.*, + org.wso2.carbon.context.*;version="${carbon.kernel.version.range}", + org.wso2.carbon.ndatasource.core;version="${carbon.kernel.version.range}", + org.eclipse.paho.client.mqttv3.*;version="${eclipse.paho.version}" + + + !org.wso2.carbon.device.mgt.iot.internal, + org.wso2.carbon.device.mgt.iot.*;version="${project.version}" + + + + + + + + + + + + + + + + + + + + + + + + + org.eclipse.osgi + org.eclipse.osgi + + + org.eclipse.osgi + org.eclipse.osgi.services + + + org.wso2.carbon + org.wso2.carbon.logging + + + org.wso2.carbon + org.wso2.carbon.utils + + + org.wso2.carbon.devicemgt + org.wso2.carbon.device.mgt.common + + + org.wso2.carbon.devicemgt + org.wso2.carbon.device.mgt.core + + + org.wso2.carbon + org.wso2.carbon.ndatasource.core + + + org.wso2.carbon.devicemgt + org.wso2.carbon.policy.mgt.core + provided + + + org.wso2.carbon.devicemgt + org.wso2.carbon.policy.mgt.common + provided + + + + org.json.wso2 + json + + + org.wso2.carbon + org.wso2.carbon.core + + + commons-collections + commons-collections + + + commons-configuration + commons-configuration + + + org.wso2.carbon.analytics-common + org.wso2.carbon.databridge.agent + + + org.wso2.carbon.analytics-common + org.wso2.carbon.databridge.core + + + org.wso2.carbon.commons + org.wso2.carbon.databridge.commons + + + org.eclipse.paho + org.eclipse.paho.client.mqttv3 + + + org.wso2.carbon.devicemgt + org.wso2.carbon.device.mgt.analytics + + + + org.igniterealtime.smack.wso2 + smack + + + org.igniterealtime.smack.wso2 + smackx + + + commons-codec.wso2 + commons-codec + + + diff --git a/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot/src/main/java/org/wso2/carbon/device/mgt/iot/config/server/DeviceManagementConfigurationManager.java b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot/src/main/java/org/wso2/carbon/device/mgt/iot/config/server/DeviceManagementConfigurationManager.java new file mode 100644 index 0000000000..00774a3cf5 --- /dev/null +++ b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot/src/main/java/org/wso2/carbon/device/mgt/iot/config/server/DeviceManagementConfigurationManager.java @@ -0,0 +1,118 @@ +/* + * 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.wso2.carbon.device.mgt.iot.config.server; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.w3c.dom.Document; +import org.wso2.carbon.device.mgt.iot.config.server.datasource.ControlQueue; +import org.wso2.carbon.device.mgt.iot.config.server.datasource.DeviceManagementConfiguration; +import org.wso2.carbon.device.mgt.iot.exception.DeviceControllerException; +import org.wso2.carbon.device.mgt.iot.util.IotDeviceManagementUtil; +import org.wso2.carbon.utils.CarbonUtils; +import javax.xml.XMLConstants; +import javax.xml.bind.JAXBContext; +import javax.xml.bind.Unmarshaller; +import javax.xml.bind.ValidationEvent; +import javax.xml.bind.ValidationEventHandler; +import javax.xml.validation.Schema; +import javax.xml.validation.SchemaFactory; +import java.io.File; +import java.util.List; + +/** + * Class responsible for the iot device manager configuration initialization. + */ +public class DeviceManagementConfigurationManager { + + private static final Log log = LogFactory.getLog(DeviceManagementConfigurationManager.class); + private static final String DEVICE_MGT_CONFIG_XML_NAME = "devicemgt-config.xml"; + private static final String DEVICE_MGT_ROOT_DIRECTORY = "iot"; + private final String XMLCONFIGS_FILE_LOCATION = + CarbonUtils.getCarbonConfigDirPath() + File.separator + + DEVICE_MGT_ROOT_DIRECTORY + File.separator + DEVICE_MGT_CONFIG_XML_NAME; + private static final String IOT_DEVICE_CONFIG_XSD_NAME = "devicemgt-config.xsd"; + private final String XSDCONFIGS_FILE_LOCATION = + CarbonUtils.getCarbonConfigDirPath() + File.separator + + DEVICE_MGT_ROOT_DIRECTORY + File.separator + IOT_DEVICE_CONFIG_XSD_NAME; + private DeviceManagementConfiguration currentDeviceManagementConfiguration; + private static DeviceManagementConfigurationManager deviceConfigurationManager = + new DeviceManagementConfigurationManager(); + + private DeviceManagementConfigurationManager() { + } + + public static DeviceManagementConfigurationManager getInstance() { + return deviceConfigurationManager; + } + + public void initConfig() throws DeviceControllerException { + try { + SchemaFactory sf = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); + Schema schema = sf.newSchema(new File(XSDCONFIGS_FILE_LOCATION)); + + File deviceCloudMgtConfig = new File(XMLCONFIGS_FILE_LOCATION); + Document doc = IotDeviceManagementUtil.convertToDocument(deviceCloudMgtConfig); + JAXBContext deviceCloudContext = JAXBContext.newInstance(DeviceManagementConfiguration.class); + Unmarshaller unmarshaller = deviceCloudContext.createUnmarshaller(); + unmarshaller.setSchema(schema); + unmarshaller.setEventHandler(new IotConfigValidationEventHandler()); + this.currentDeviceManagementConfiguration = (DeviceManagementConfiguration) unmarshaller.unmarshal(doc); + } catch (Exception e) { + String error = "Error occurred while initializing DeviceController configurations"; + log.error(error); + throw new DeviceControllerException(error, e); + } + } + + public DeviceManagementConfiguration getDeviceCloudMgtConfig() { + return currentDeviceManagementConfiguration; + } + + public ControlQueue getControlQueue(String name) { + List controlQueues = currentDeviceManagementConfiguration.getControlQueues().getControlQueue(); + if (controlQueues != null) { + for (ControlQueue controlQueue : controlQueues) { + if (controlQueue.getName().equals(name)) { + return controlQueue; + } + } + } + return null; + } + + private class IotConfigValidationEventHandler implements ValidationEventHandler { + @Override + public boolean handleEvent(ValidationEvent event) { + String error = "\nEVENT" + "\nSEVERITY: " + event.getSeverity() + + "\n MESSAGE: " + event.getMessage() + + "\n LINKED EXCEPTION: " + event.getLinkedException() + + "\n LOCATOR" + + "\n LINE NUMBER: " + event.getLocator().getLineNumber() + + "\n COLUMN NUMBER: " + event.getLocator().getColumnNumber() + + "\n OFFSET: " + event.getLocator().getOffset() + + "\n OBJECT: " + event.getLocator().getObject() + + "\n NODE: " + event.getLocator().getNode() + + "\n URL: " + event.getLocator().getURL(); + log.error(error); + return true; + } + } + +} diff --git a/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot/src/main/java/org/wso2/carbon/device/mgt/iot/config/server/datasource/ControlQueue.java b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot/src/main/java/org/wso2/carbon/device/mgt/iot/config/server/datasource/ControlQueue.java new file mode 100644 index 0000000000..145763bfab --- /dev/null +++ b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot/src/main/java/org/wso2/carbon/device/mgt/iot/config/server/datasource/ControlQueue.java @@ -0,0 +1,229 @@ + +/* + * 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.wso2.carbon.device.mgt.iot.config.server.datasource; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlType; + +/** + *

      Java class for ControlQueue complex type. + *

      + *

      The following schema fragment specifies the expected content contained within this class. + *

      + *

      + * <complexType name="ControlQueue">
      + *   <complexContent>
      + *     <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
      + *       <sequence>
      + *         <element name="Name" type="{http://www.w3.org/2001/XMLSchema}string"/>
      + *         <element name="Enabled" type="{http://www.w3.org/2001/XMLSchema}boolean"/>
      + *         <element name="ControlClass" type="{http://www.w3.org/2001/XMLSchema}string"/>
      + *         <element name="Protocol" type="{http://www.w3.org/2001/XMLSchema}string"/>
      + *         <element name="ServerURL" type="{http://www.w3.org/2001/XMLSchema}string"/>
      + *         <element name="Port" type="{http://www.w3.org/2001/XMLSchema}short"/>
      + *         <element name="Username" type="{http://www.w3.org/2001/XMLSchema}string"/>
      + *         <element name="Password" type="{http://www.w3.org/2001/XMLSchema}string"/>
      + *       </sequence>
      + *     </restriction>
      + *   </complexContent>
      + * </complexType>
      + * 
      + */ +@XmlAccessorType(XmlAccessType.FIELD) +@XmlType(name = "ControlQueue", propOrder = { + "name", + "enabled", + "controlClass", + "protocol", + "serverURL", + "port", + "username", + "password" +}) +public class ControlQueue { + + @XmlElement(name = "Name", required = true) + protected String name; + @XmlElement(name = "Enabled") + protected boolean enabled; + @XmlElement(name = "ControlClass", required = true) + protected String controlClass; + @XmlElement(name = "Protocol", required = true) + protected String protocol; + @XmlElement(name = "ServerURL", required = true) + protected String serverURL; + @XmlElement(name = "Port") + protected short port; + @XmlElement(name = "Username", required = true) + protected String username; + @XmlElement(name = "Password", required = true) + protected String password; + + /** + * Gets the value of the name property. + * + * @return possible object is + * {@link String } + */ + public String getName() { + return name; + } + + /** + * Sets the value of the name property. + * + * @param value allowed object is + * {@link String } + */ + public void setName(String value) { + this.name = value; + } + + /** + * Gets the value of the enabled property. + */ + public boolean isEnabled() { + return enabled; + } + + /** + * Sets the value of the enabled property. + */ + public void setEnabled(boolean value) { + this.enabled = value; + } + + /** + * Gets the value of the controlClass property. + * + * @return possible object is + * {@link String } + */ + public String getControlClass() { + return controlClass; + } + + /** + * Sets the value of the controlClass property. + * + * @param value allowed object is + * {@link String } + */ + public void setControlClass(String value) { + this.controlClass = value; + } + + /** + * Gets the value of the protocol property. + * + * @return possible object is + * {@link String } + */ + public String getProtocol() { + return protocol; + } + + /** + * Sets the value of the protocol property. + * + * @param value allowed object is + * {@link String } + */ + public void setProtocol(String value) { + this.protocol = value; + } + + /** + * Gets the value of the serverURL property. + * + * @return possible object is + * {@link String } + */ + public String getServerURL() { + return serverURL; + } + + /** + * Sets the value of the serverURL property. + * + * @param value allowed object is + * {@link String } + */ + public void setServerURL(String value) { + this.serverURL = value; + } + + /** + * Gets the value of the port property. + */ + public short getPort() { + return port; + } + + /** + * Sets the value of the port property. + */ + public void setPort(short value) { + this.port = value; + } + + /** + * Gets the value of the username property. + * + * @return possible object is + * {@link String } + */ + public String getUsername() { + return username; + } + + /** + * Sets the value of the username property. + * + * @param value allowed object is + * {@link String } + */ + public void setUsername(String value) { + this.username = value; + } + + /** + * Gets the value of the password property. + * + * @return possible object is + * {@link String } + */ + public String getPassword() { + return password; + } + + /** + * Sets the value of the password property. + * + * @param value allowed object is + * {@link String } + */ + public void setPassword(String value) { + this.password = value; + } + +} diff --git a/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot/src/main/java/org/wso2/carbon/device/mgt/iot/config/server/datasource/ControlQueuesConfig.java b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot/src/main/java/org/wso2/carbon/device/mgt/iot/config/server/datasource/ControlQueuesConfig.java new file mode 100644 index 0000000000..e7b82ce0b5 --- /dev/null +++ b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot/src/main/java/org/wso2/carbon/device/mgt/iot/config/server/datasource/ControlQueuesConfig.java @@ -0,0 +1,82 @@ + +/* + * 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.wso2.carbon.device.mgt.iot.config.server.datasource; + +import java.util.ArrayList; +import java.util.List; +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlType; + +/** + *

      Java class for ControlQueuesConfig complex type. + *

      + *

      The following schema fragment specifies the expected content contained within this class. + *

      + *

      + * <complexType name="ControlQueuesConfig">
      + *   <complexContent>
      + *     <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
      + *       <sequence>
      + *         <element name="ControlQueue" type="{}ControlQueue" maxOccurs="unbounded" minOccurs="0"/>
      + *       </sequence>
      + *     </restriction>
      + *   </complexContent>
      + * </complexType>
      + * 
      + */ +@XmlAccessorType(XmlAccessType.FIELD) +@XmlType(name = "ControlQueuesConfig", propOrder = { + "controlQueue" +}) +public class ControlQueuesConfig { + + @XmlElement(name = "ControlQueue") + protected List controlQueue; + + /** + * Gets the value of the controlQueue property. + *

      + *

      + * This accessor method returns a reference to the live list, + * not a snapshot. Therefore any modification you make to the + * returned list will be present inside the JAXB object. + * This is why there is not a set method for the controlQueue property. + *

      + *

      + * For example, to add a new item, do as follows: + *

      +     *    getControlQueue().add(newItem);
      +     * 
      + *

      + *

      + *

      + * Objects of the following type(s) are allowed in the list + * {@link ControlQueue } + */ + public List getControlQueue() { + if (controlQueue == null) { + controlQueue = new ArrayList(); + } + return this.controlQueue; + } + +} diff --git a/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot/src/main/java/org/wso2/carbon/device/mgt/iot/config/server/datasource/DeviceManagementConfiguration.java b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot/src/main/java/org/wso2/carbon/device/mgt/iot/config/server/datasource/DeviceManagementConfiguration.java new file mode 100644 index 0000000000..6194049c41 --- /dev/null +++ b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot/src/main/java/org/wso2/carbon/device/mgt/iot/config/server/datasource/DeviceManagementConfiguration.java @@ -0,0 +1,74 @@ + +/* + * 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.wso2.carbon.device.mgt.iot.config.server.datasource; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.XmlType; + +/** + *

      Java class for DeviceCloudConfig complex type. + *

      + *

      The following schema fragment specifies the expected content contained within this class. + *

      + *

      + * <complexType name="DeviceManagementConfigurations">
      + *   <complexContent>
      + *     <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
      + *       <sequence>
      + *         <element name="ControlQueues" type="{}ControlQueuesConfig"/>
      + *       </sequence>
      + *     </restriction>
      + *   </complexContent>
      + * </complexType>
      + * 
      + */ +@XmlAccessorType(XmlAccessType.FIELD) +@XmlType(name = "DeviceManagementConfigurations", propOrder = { + "controlQueues" +}) + +@XmlRootElement(name = "DeviceManagementConfigurations") +public class DeviceManagementConfiguration { + @XmlElement(name = "ControlQueues", required = true) + protected ControlQueuesConfig controlQueues; + + /** + * Gets the value of the controlQueues property. + * + * @return possible object is + * {@link ControlQueuesConfig } + */ + public ControlQueuesConfig getControlQueues() { + return controlQueues; + } + + /** + * Sets the value of the controlQueues property. + * + * @param value allowed object is + * {@link ControlQueuesConfig } + */ + public void setControlQueues(ControlQueuesConfig value) { + this.controlQueues = value; + } +} diff --git a/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot/src/main/java/org/wso2/carbon/device/mgt/iot/config/server/datasource/ObjectFactory.java b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot/src/main/java/org/wso2/carbon/device/mgt/iot/config/server/datasource/ObjectFactory.java new file mode 100644 index 0000000000..9bc3135799 --- /dev/null +++ b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot/src/main/java/org/wso2/carbon/device/mgt/iot/config/server/datasource/ObjectFactory.java @@ -0,0 +1,69 @@ + +/* + * 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.wso2.carbon.device.mgt.iot.config.server.datasource; + +import javax.xml.bind.JAXBElement; +import javax.xml.bind.annotation.XmlElementDecl; +import javax.xml.bind.annotation.XmlRegistry; +import javax.xml.namespace.QName; + +/** + * This object contains factory methods for each + * Java content interface and Java element interface + * generated in the org.wso2.carbon.device.mgt.iot.common.config.server.configs package. + *

      An ObjectFactory allows you to programatically + * construct new instances of the Java representation + * for XML content. The Java representation of XML + * content can consist of schema derived interfaces + * and classes representing the binding of schema + * type definitions, element declarations and model + * groups. Factory methods for each of these are + * provided in this class. + */ +@XmlRegistry +public class ObjectFactory { + + private final static QName _DeviceCloudConfiguration_QNAME = new QName("", "DeviceManagementConfigurations"); + + /** + * Create a new ObjectFactory that can be used to create new instances of schema derived classes for package: + * org.wso2.carbon.device.mgt.iot.common.config.server.configs + */ + public ObjectFactory() { + } + + /** + * Create an instance of {@link DeviceManagementConfiguration } + */ + public DeviceManagementConfiguration createDeviceCloudConfig() { + return new DeviceManagementConfiguration(); + } + + /** + * Create an instance of {@link JAXBElement }{@code <}{@link DeviceManagementConfiguration }{@code >}} + */ + @XmlElementDecl(namespace = "", name = "DeviceManagementConfigurations") + public JAXBElement createDeviceCloudConfiguration( + DeviceManagementConfiguration value) { + return new JAXBElement(_DeviceCloudConfiguration_QNAME, + DeviceManagementConfiguration.class, null, value); + } + +} diff --git a/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot/src/main/java/org/wso2/carbon/device/mgt/iot/controlqueue/mqtt/MqttConfig.java b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot/src/main/java/org/wso2/carbon/device/mgt/iot/controlqueue/mqtt/MqttConfig.java new file mode 100644 index 0000000000..26abefe495 --- /dev/null +++ b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot/src/main/java/org/wso2/carbon/device/mgt/iot/controlqueue/mqtt/MqttConfig.java @@ -0,0 +1,81 @@ +/* + * 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.wso2.carbon.device.mgt.iot.controlqueue.mqtt; + +import org.wso2.carbon.device.mgt.iot.config.server.DeviceManagementConfigurationManager; +import org.wso2.carbon.device.mgt.iot.config.server.datasource.ControlQueue; + +public class MqttConfig { + private String mqttQueueEndpoint; + private String mqttQueueUsername; + private String mqttQueuePassword; + private boolean isEnabled; + private static final String MQTT_QUEUE_CONFIG_NAME = "MQTT"; + private static final String LOCALHOST = "localhost"; + private static final String PORT_OFFSET_PROPERTY = "portOffset"; + private ControlQueue mqttControlQueue; + private static MqttConfig mqttConfig = new MqttConfig(); + + public String getMqttQueueEndpoint() { + return mqttQueueEndpoint; + } + + public String getMqttQueueUsername() { + return mqttQueueUsername; + } + + public String getMqttQueuePassword() { + return mqttQueuePassword; + } + + public ControlQueue getMqttControlQueue() { + return mqttControlQueue; + } + + public boolean isEnabled() { + return isEnabled; + } + + public static String getMqttQueueConfigName() { + return MQTT_QUEUE_CONFIG_NAME; + } + + private MqttConfig() { + + mqttControlQueue = DeviceManagementConfigurationManager.getInstance().getControlQueue(MQTT_QUEUE_CONFIG_NAME); + int portOffset = Integer.parseInt(System.getProperty(PORT_OFFSET_PROPERTY)); + String brokerURL = mqttControlQueue.getServerURL(); + + if (portOffset != 0 && brokerURL.contains(LOCALHOST)) { + // if using the internal MB (meaning URL is localhost and there is a portOffset) + // then increment port accordingly + int mqttPort = mqttControlQueue.getPort(); + mqttPort = mqttPort + portOffset; + mqttQueueEndpoint = mqttControlQueue.getServerURL() + ":" + mqttPort; + } else { + mqttQueueEndpoint = mqttControlQueue.getServerURL() + ":" + mqttControlQueue.getPort(); + } + mqttQueueUsername = mqttControlQueue.getUsername(); + mqttQueuePassword = mqttControlQueue.getPassword(); + isEnabled = mqttControlQueue.isEnabled(); + } + public static MqttConfig getInstance() { + return mqttConfig; + } +} diff --git a/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot/src/main/java/org/wso2/carbon/device/mgt/iot/controlqueue/xmpp/XmppAccount.java b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot/src/main/java/org/wso2/carbon/device/mgt/iot/controlqueue/xmpp/XmppAccount.java new file mode 100644 index 0000000000..94c6dd6d50 --- /dev/null +++ b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot/src/main/java/org/wso2/carbon/device/mgt/iot/controlqueue/xmpp/XmppAccount.java @@ -0,0 +1,60 @@ +/* + * 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.wso2.carbon.device.mgt.iot.controlqueue.xmpp; + +public class XmppAccount { + + private String username; + private String password; + private String accountName; + private String email; + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public String getAccountName() { + return accountName; + } + + public void setAccountName(String accountName) { + this.accountName = accountName; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + +} diff --git a/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot/src/main/java/org/wso2/carbon/device/mgt/iot/controlqueue/xmpp/XmppConfig.java b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot/src/main/java/org/wso2/carbon/device/mgt/iot/controlqueue/xmpp/XmppConfig.java new file mode 100644 index 0000000000..e06ee66f07 --- /dev/null +++ b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot/src/main/java/org/wso2/carbon/device/mgt/iot/controlqueue/xmpp/XmppConfig.java @@ -0,0 +1,93 @@ +/* + * 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.wso2.carbon.device.mgt.iot.controlqueue.xmpp; + +import org.wso2.carbon.device.mgt.iot.config.server.DeviceManagementConfigurationManager; +import org.wso2.carbon.device.mgt.iot.config.server.datasource.ControlQueue; + +public class XmppConfig { + + private String xmppServerIP; + private int xmppServerPort; + private String xmppEndpoint; + private String xmppUsername; + private String xmppPassword; + private boolean isEnabled; + private static final String XMPP_QUEUE_CONFIG_NAME = "XMPP"; + private final int SERVER_CONNECTION_PORT = 5222; + private ControlQueue xmppControlQueue; + private static XmppConfig xmppConfig = new XmppConfig(); + + public String getXmppServerIP() { + return xmppServerIP; + } + + public int getXmppServerPort() { + return xmppServerPort; + } + + public String getXmppEndpoint() { + return xmppEndpoint; + } + + public String getXmppUsername() { + return xmppUsername; + } + + public String getXmppPassword() { + return xmppPassword; + } + + public ControlQueue getXmppControlQueue() { + return xmppControlQueue; + } + + public boolean isEnabled() { + return isEnabled; + } + + public static String getXmppQueueConfigName() { + return XMPP_QUEUE_CONFIG_NAME; + } + + private XmppConfig() { + xmppControlQueue = DeviceManagementConfigurationManager.getInstance().getControlQueue( + XMPP_QUEUE_CONFIG_NAME); + xmppServerIP = xmppControlQueue.getServerURL(); + int indexOfChar = xmppServerIP.lastIndexOf('/'); + + if (indexOfChar != -1) { + xmppServerIP = xmppServerIP.substring((indexOfChar + 1), xmppServerIP.length()); + } + + xmppServerPort = xmppControlQueue.getPort(); + xmppEndpoint = xmppControlQueue.getServerURL() + ":" + xmppServerPort; + xmppUsername = xmppControlQueue.getUsername(); + xmppPassword = xmppControlQueue.getPassword(); + isEnabled = xmppControlQueue.isEnabled(); + } + + public static XmppConfig getInstance() { + return xmppConfig; + } + + public int getSERVER_CONNECTION_PORT() { + return SERVER_CONNECTION_PORT; + } +} diff --git a/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot/src/main/java/org/wso2/carbon/device/mgt/iot/controlqueue/xmpp/XmppServerClient.java b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot/src/main/java/org/wso2/carbon/device/mgt/iot/controlqueue/xmpp/XmppServerClient.java new file mode 100644 index 0000000000..55c86707d8 --- /dev/null +++ b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot/src/main/java/org/wso2/carbon/device/mgt/iot/controlqueue/xmpp/XmppServerClient.java @@ -0,0 +1,352 @@ +/* + * 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.wso2.carbon.device.mgt.iot.controlqueue.xmpp; + +import org.apache.commons.codec.binary.Base64; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.http.HttpHeaders; +import org.apache.http.HttpResponse; +import org.apache.http.HttpStatus; +import org.apache.http.client.HttpClient; +import org.apache.http.client.methods.HttpDelete; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.StringEntity; +import org.apache.http.util.EntityUtils; +import org.json.JSONArray; +import org.json.JSONObject; +import org.wso2.carbon.device.mgt.iot.exception.DeviceControllerException; +import org.wso2.carbon.device.mgt.iot.exception.IoTException; +import org.wso2.carbon.device.mgt.iot.util.IoTUtil; + +import javax.ws.rs.core.MediaType; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.MalformedURLException; +import java.net.URL; +import java.nio.charset.StandardCharsets; + +public class XmppServerClient { + + private static final Log log = LogFactory.getLog(XmppServerClient.class); + private static final String XMPP_SERVER_API_CONTEXT = "/plugins/restapi/v1"; + private static final String XMPP_USERS_API = "/users"; + private static final String XMPP_SESSIONS_API = "/sessions"; + @SuppressWarnings("unused") + private static final String XMPP_GROUPS_API = "/groups"; + @SuppressWarnings("unused") + private static final String APPLICATION_JSON_MT = "application/json"; + private static final String DEVICEMGT_CONFIG_FILE = "devicemgt-config.xml"; + private String xmppEndpoint; + private String xmppUsername; + private String xmppPassword; + private boolean xmppEnabled = false; + + public XmppServerClient() { + } + + public void initControlQueue() { + xmppEndpoint = XmppConfig.getInstance().getXmppEndpoint(); + xmppUsername = XmppConfig.getInstance().getXmppUsername(); + xmppPassword = XmppConfig.getInstance().getXmppPassword(); + xmppEnabled = XmppConfig.getInstance().isEnabled(); + } + + public boolean createXMPPAccount(XmppAccount newUserAccount) throws DeviceControllerException { + if (xmppEnabled) { + String xmppUsersAPIEndpoint = xmppEndpoint + XMPP_SERVER_API_CONTEXT + XMPP_USERS_API; + if (log.isDebugEnabled()) { + log.debug("The Create-UserAccount Endpoint URL of the XMPP Server is set to: " + xmppUsersAPIEndpoint); + } + + String encodedString = xmppUsername + ":" + xmppPassword; + encodedString = new String(Base64.encodeBase64(encodedString.getBytes(StandardCharsets.UTF_8))); + String authorizationHeader = "Basic " + encodedString; + String jsonRequest = "{\n" + + " \"username\": \"" + newUserAccount.getUsername() + "\"," + + " \"password\": \"" + newUserAccount.getPassword() + "\"," + + " \"name\": \"" + newUserAccount.getAccountName() + "\"," + + " \"email\": \"" + newUserAccount.getEmail() + "\"," + + " \"properties\": {" + + " \"property\": [" + + " {" + + " \"@key\": \"console.rows_per_page\"," + + " \"@value\": \"user-summary=8\"" + + " }," + + " {" + + " \"@key\": \"console.order\"," + + " \"@value\": \"session-summary=1\"" + + " }" + + " ]" + + " }" + + "}"; + + StringEntity requestEntity; + try { + requestEntity = new StringEntity(jsonRequest, MediaType.APPLICATION_JSON, + StandardCharsets.UTF_8.toString()); + } catch (UnsupportedEncodingException e) { + return false; + } + + URL xmppUserApiUrl; + try { + xmppUserApiUrl = new URL(xmppUsersAPIEndpoint); + } catch (MalformedURLException e) { + String errMsg = "Malformed XMPP URL + " + xmppUsersAPIEndpoint; + log.error(errMsg); + throw new DeviceControllerException(errMsg); + } + HttpClient httpClient; + try { + httpClient = IoTUtil.getHttpClient(xmppUserApiUrl.getPort(), xmppUserApiUrl.getProtocol()); + } catch (Exception e) { + log.error("Error on getting a http client for port :" + xmppUserApiUrl.getPort() + " protocol :" + + xmppUserApiUrl.getProtocol()); + return false; + } + + HttpPost httpPost = new HttpPost(xmppUsersAPIEndpoint); + httpPost.addHeader(HttpHeaders.AUTHORIZATION, authorizationHeader); + httpPost.setEntity(requestEntity); + + try { + HttpResponse httpResponse = httpClient.execute(httpPost); + + if (httpResponse.getStatusLine().getStatusCode() != HttpStatus.SC_CREATED) { + String response = IoTUtil.getResponseString(httpResponse); + String errorMsg = "XMPP Server returned status: '" + httpResponse.getStatusLine().getStatusCode() + + "' for account creation with error:\n" + response; + log.error(errorMsg); + throw new DeviceControllerException(errorMsg); + } else { + EntityUtils.consume(httpResponse.getEntity()); + return true; + } + } catch (IOException | IoTException e) { + String errorMsg = "Error occured whilst trying a 'POST' at : " + xmppUsersAPIEndpoint; + log.error(errorMsg); + throw new DeviceControllerException(errorMsg, e); + } + + } else { + log.warn(String.format("XMPP set to false in [%s]", DEVICEMGT_CONFIG_FILE)); + return false; + } + } + + public boolean doesXMPPUserAccountExist(String username) throws DeviceControllerException { + if (xmppEnabled) { + String xmppCheckUserAPIEndpoint = xmppEndpoint + XMPP_SERVER_API_CONTEXT + XMPP_USERS_API + "/" + username; + if (log.isDebugEnabled()) { + log.debug("The Check-User-Account Endpoint URL of the XMPP Server is set to: " + + xmppCheckUserAPIEndpoint); + } + + String encodedString = xmppUsername + ":" + xmppPassword; + encodedString = new String(Base64.encodeBase64(encodedString.getBytes(StandardCharsets.UTF_8))); + String authorizationHeader = "Basic " + encodedString; + + URL xmppUserApiUrl; + try { + xmppUserApiUrl = new URL(xmppCheckUserAPIEndpoint); + } catch (MalformedURLException e) { + String errMsg = "Malformed XMPP URL + " + xmppCheckUserAPIEndpoint; + log.error(errMsg); + throw new DeviceControllerException(errMsg, e); + } + + HttpClient httpClient; + try { + httpClient = IoTUtil.getHttpClient(xmppUserApiUrl.getPort(), xmppUserApiUrl.getProtocol()); + } catch (Exception e) { + String errorMsg = "Error on getting a http client for port :" + xmppUserApiUrl.getPort() + + " protocol :" + xmppUserApiUrl.getProtocol(); + log.error(errorMsg); + throw new DeviceControllerException(errorMsg, e); + } + + HttpGet httpGet = new HttpGet(xmppCheckUserAPIEndpoint); + httpGet.addHeader(HttpHeaders.AUTHORIZATION, authorizationHeader); + + try { + HttpResponse httpResponse = httpClient.execute(httpGet); + + if (httpResponse.getStatusLine().getStatusCode() != HttpStatus.SC_OK) { + String response = IoTUtil.getResponseString(httpResponse); + if (log.isDebugEnabled()) { + log.debug("XMPP Server returned status: '" + httpResponse.getStatusLine().getStatusCode() + + "' for checking existence of account [" + username + "] with message:\n" + + response + "\nProbably, an account with this username does not exist."); + } + return false; + } + + } catch (IOException | IoTException e) { + String errorMsg = "Error occured whilst trying a 'GET' at : " + xmppCheckUserAPIEndpoint; + log.error(errorMsg); + throw new DeviceControllerException(errorMsg, e); + } + + if (log.isDebugEnabled()) { + log.debug("XMPP Server already has an account for the username - [" + username + "]."); + } + return true; + } else { + String warnMsg = String.format("XMPP set to false in [%s]", DEVICEMGT_CONFIG_FILE); + log.warn(warnMsg); + throw new DeviceControllerException(warnMsg); + } + } + + public JSONArray getAllCurrentUserSessions() throws DeviceControllerException { + if (xmppEnabled) { + JSONArray xmppSessions; + String xmppSessionsAPIEndpoint = xmppEndpoint + XMPP_SERVER_API_CONTEXT + XMPP_SESSIONS_API; + + if (log.isDebugEnabled()) { + log.debug("The Get-Sessions Endpoint URL of the XMPP Server is set to: " + xmppSessionsAPIEndpoint); + } + + String encodedString = xmppUsername + ":" + xmppPassword; + encodedString = new String(Base64.encodeBase64(encodedString.getBytes(StandardCharsets.UTF_8))); + String authorizationHeader = "Basic " + encodedString; + + URL xmppUserApiUrl; + try { + xmppUserApiUrl = new URL(xmppSessionsAPIEndpoint); + } catch (MalformedURLException e) { + String errMsg = "Malformed XMPP URL + " + xmppSessionsAPIEndpoint; + log.error(errMsg); + throw new DeviceControllerException(errMsg, e); + } + + HttpClient httpClient; + try { + httpClient = IoTUtil.getHttpClient(xmppUserApiUrl.getPort(), xmppUserApiUrl.getProtocol()); + } catch (Exception e) { + String errorMsg = "Error on getting a http client for port :" + xmppUserApiUrl.getPort() + + " protocol :" + xmppUserApiUrl.getProtocol(); + log.error(errorMsg); + throw new DeviceControllerException(errorMsg, e); + } + + HttpGet httpGet = new HttpGet(xmppSessionsAPIEndpoint); + httpGet.addHeader(HttpHeaders.AUTHORIZATION, authorizationHeader); + httpGet.addHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON); + + try { + HttpResponse httpResponse = httpClient.execute(httpGet); + + if (httpResponse.getStatusLine().getStatusCode() != HttpStatus.SC_OK) { + String errorMsg = "XMPP Server returned status: '" + httpResponse.getStatusLine().getStatusCode() + + "' for checking current XMPP Sessions."; + log.error(errorMsg); + throw new DeviceControllerException(errorMsg); + } + + String response = IoTUtil.getResponseString(httpResponse); + xmppSessions = new JSONObject(response).getJSONArray("session"); + return xmppSessions; + + } catch (IOException | IoTException e) { + String errorMsg = "Error occured whilst trying a 'GET' at : " + xmppSessionsAPIEndpoint; + log.error(errorMsg); + throw new DeviceControllerException(errorMsg, e); + } + + } else { + String warnMsg = String.format("XMPP set to false in [%s]", DEVICEMGT_CONFIG_FILE); + log.warn(warnMsg); + throw new DeviceControllerException(warnMsg); + } + } + + public void deleteCurrentXmppSessions() throws DeviceControllerException { + JSONArray xmppSessionsArray; + try { + xmppSessionsArray = getAllCurrentUserSessions(); + } catch (DeviceControllerException e) { + if (e.getMessage().contains(DEVICEMGT_CONFIG_FILE)) { + log.warn(String.format("XMPP set to false in [%s]", DEVICEMGT_CONFIG_FILE)); + return; + } else { + throw e; + } + } + + if (xmppSessionsArray.length() != 0) { + String xmppSessionsAPIEndpoint = xmppEndpoint + XMPP_SERVER_API_CONTEXT + XMPP_SESSIONS_API; + String encodedString = xmppUsername + ":" + xmppPassword; + encodedString = new String(Base64.encodeBase64(encodedString.getBytes(StandardCharsets.UTF_8))); + String authorizationHeader = "Basic " + encodedString; + + if (log.isDebugEnabled()) { + log.debug("The Get-Sessions Endpoint URL of the XMPP Server is set to: " + xmppSessionsAPIEndpoint); + } + + URL xmppUserApiUrl; + try { + xmppUserApiUrl = new URL(xmppSessionsAPIEndpoint); + } catch (MalformedURLException e) { + String errMsg = "Malformed XMPP URL + " + xmppSessionsAPIEndpoint; + log.error(errMsg); + throw new DeviceControllerException(errMsg, e); + } + + HttpClient httpClient; + try { + httpClient = IoTUtil.getHttpClient(xmppUserApiUrl.getPort(), xmppUserApiUrl.getProtocol()); + } catch (Exception e) { + String errorMsg = "Error on getting a http client for port :" + xmppUserApiUrl.getPort() + + " protocol :" + xmppUserApiUrl.getProtocol(); + log.error(errorMsg); + throw new DeviceControllerException(errorMsg, e); + } + + for (int i = 0; i < xmppSessionsArray.length(); i++) { + + String sessionName = xmppSessionsArray.getJSONObject(i).getString("username"); + String xmppUserSessionsAPIEndpoint = xmppSessionsAPIEndpoint + "/" + sessionName; + + HttpDelete httpDelete = new HttpDelete(xmppUserSessionsAPIEndpoint); + httpDelete.addHeader(HttpHeaders.AUTHORIZATION, authorizationHeader); + + try { + HttpResponse httpResponse = httpClient.execute(httpDelete); + + if (httpResponse.getStatusLine().getStatusCode() != HttpStatus.SC_OK) { + String errorMsg = + "XMPP Server returned status: '" + httpResponse.getStatusLine().getStatusCode() + + "' for checking current XMPP Sessions."; + log.error(errorMsg); + throw new DeviceControllerException(errorMsg); + } + + } catch (IOException e) { + String errorMsg = "Error occured whilst trying a 'DELETE' user-session [" + sessionName + "] " + + "at : " + xmppUserSessionsAPIEndpoint; + log.error(errorMsg); + throw new DeviceControllerException(errorMsg, e); + } + } + } + } +} diff --git a/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot/src/main/java/org/wso2/carbon/device/mgt/iot/exception/DeviceControllerException.java b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot/src/main/java/org/wso2/carbon/device/mgt/iot/exception/DeviceControllerException.java new file mode 100644 index 0000000000..c3bce48486 --- /dev/null +++ b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot/src/main/java/org/wso2/carbon/device/mgt/iot/exception/DeviceControllerException.java @@ -0,0 +1,42 @@ +/* + * 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.wso2.carbon.device.mgt.iot.exception; + +public class DeviceControllerException extends Exception { + + public DeviceControllerException(String message, Throwable cause, boolean enableSuppression, + boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } + + public DeviceControllerException(Throwable cause) { + super(cause); + } + + public DeviceControllerException(String message, Throwable cause) { + super(message, cause); + } + + public DeviceControllerException(String message) { + super(message); + } + + public DeviceControllerException() { + } +} diff --git a/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot/src/main/java/org/wso2/carbon/device/mgt/iot/exception/IoTException.java b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot/src/main/java/org/wso2/carbon/device/mgt/iot/exception/IoTException.java new file mode 100644 index 0000000000..85639cb63f --- /dev/null +++ b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot/src/main/java/org/wso2/carbon/device/mgt/iot/exception/IoTException.java @@ -0,0 +1,43 @@ +/* + * 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.wso2.carbon.device.mgt.iot.exception; + +public class IoTException extends Exception { + + public IoTException() { + super(); + } + + public IoTException(String message) { + super(message); + } + + public IoTException(String message, Throwable cause) { + super(message, cause); + } + + public IoTException(Throwable cause) { + super(cause); + } + + protected IoTException(String message, Throwable cause, boolean enableSuppression, + boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } +} diff --git a/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot/src/main/java/org/wso2/carbon/device/mgt/iot/exception/NotImplementedException.java b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot/src/main/java/org/wso2/carbon/device/mgt/iot/exception/NotImplementedException.java new file mode 100644 index 0000000000..7ce9b3f807 --- /dev/null +++ b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot/src/main/java/org/wso2/carbon/device/mgt/iot/exception/NotImplementedException.java @@ -0,0 +1,43 @@ +/* + * 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.wso2.carbon.device.mgt.iot.exception; + +public class NotImplementedException extends RuntimeException { + + public NotImplementedException() { + super(); + } + + public NotImplementedException(String message) { + super(message); + } + + public NotImplementedException(String message, Throwable cause) { + super(message, cause); + } + + public NotImplementedException(Throwable cause) { + super(cause); + } + + protected NotImplementedException(String message, Throwable cause, boolean enableSuppression, + boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } +} diff --git a/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot/src/main/java/org/wso2/carbon/device/mgt/iot/exception/UnauthorizedException.java b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot/src/main/java/org/wso2/carbon/device/mgt/iot/exception/UnauthorizedException.java new file mode 100644 index 0000000000..1af199b174 --- /dev/null +++ b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot/src/main/java/org/wso2/carbon/device/mgt/iot/exception/UnauthorizedException.java @@ -0,0 +1,39 @@ +/* + * 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.wso2.carbon.device.mgt.iot.exception; + +public class UnauthorizedException extends Exception { + + public UnauthorizedException(String message) { + super(message); + } + + public UnauthorizedException(String message, Throwable cause) { + super(message, cause); + } + + public UnauthorizedException(Throwable cause) { + super(cause); + } + + public UnauthorizedException(String message, Throwable cause, boolean enableSuppression, + boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } +} diff --git a/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot/src/main/java/org/wso2/carbon/device/mgt/iot/internal/IoTCommonDataHolder.java b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot/src/main/java/org/wso2/carbon/device/mgt/iot/internal/IoTCommonDataHolder.java new file mode 100644 index 0000000000..6ed3743501 --- /dev/null +++ b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot/src/main/java/org/wso2/carbon/device/mgt/iot/internal/IoTCommonDataHolder.java @@ -0,0 +1,54 @@ +/* + * 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.wso2.carbon.device.mgt.iot.internal; + +import org.wso2.carbon.base.ServerConfiguration; +import org.wso2.carbon.databridge.core.DataBridgeReceiverService; + +public class IoTCommonDataHolder { + + private static IoTCommonDataHolder thisInstance = new IoTCommonDataHolder(); + String trustStoreLocaiton; + String trustStorePassword; + private IoTCommonDataHolder() { + + } + + public void initialize(){ + setTrustStore(); + } + + public static IoTCommonDataHolder getInstance() { + return thisInstance; + } + + private void setTrustStore(){ + this.trustStoreLocaiton = ServerConfiguration.getInstance().getFirstProperty("Security.TrustStore.Location"); + this.trustStorePassword = ServerConfiguration.getInstance().getFirstProperty("Security.TrustStore.Password"); + } + + public String getTrustStoreLocation(){ + return trustStoreLocaiton; + } + + public String getTrustStorePassword(){ + return trustStorePassword; + } + +} diff --git a/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot/src/main/java/org/wso2/carbon/device/mgt/iot/internal/IotDeviceManagementServiceComponent.java b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot/src/main/java/org/wso2/carbon/device/mgt/iot/internal/IotDeviceManagementServiceComponent.java new file mode 100644 index 0000000000..dadc99f974 --- /dev/null +++ b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot/src/main/java/org/wso2/carbon/device/mgt/iot/internal/IotDeviceManagementServiceComponent.java @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2016, 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.wso2.carbon.device.mgt.iot.internal; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.osgi.framework.BundleContext; +import org.osgi.service.component.ComponentContext; +import org.wso2.carbon.core.ServerStartupObserver; +import org.wso2.carbon.device.mgt.iot.config.server.DeviceManagementConfigurationManager; +import org.wso2.carbon.device.mgt.iot.controlqueue.xmpp.XmppConfig; +import org.wso2.carbon.device.mgt.iot.controlqueue.xmpp.XmppServerClient; +import org.wso2.carbon.device.mgt.iot.exception.DeviceControllerException; +import org.wso2.carbon.device.mgt.iot.service.IoTServerStartupListener; +import org.wso2.carbon.utils.ConfigurationContextService; + +/** + * @scr.component name="org.wso2.carbon.device.mgt.iot.internal.IotDeviceManagementServiceComponent" + * immediate="true" + * @scr.reference name="config.context.service" + * interface="org.wso2.carbon.utils.ConfigurationContextService" + * cardinality="0..1" + * policy="dynamic" + * bind="setConfigurationContextService" + * unbind="unsetConfigurationContextService" + */ +public class IotDeviceManagementServiceComponent { + + private static final Log log = LogFactory.getLog(IotDeviceManagementServiceComponent.class); + public static ConfigurationContextService configurationContextService; + + protected void activate(ComponentContext ctx) { + if (log.isDebugEnabled()) { + log.debug("Activating Iot Device Management Service Component"); + } + try { + BundleContext bundleContext = ctx.getBundleContext(); + /* Initialize the data source configuration */ + DeviceManagementConfigurationManager.getInstance().initConfig(); + bundleContext.registerService(ServerStartupObserver.class.getName(), new IoTServerStartupListener(), null); + IoTCommonDataHolder.getInstance().initialize(); + if (log.isDebugEnabled()) { + log.debug("Iot Device Management Service Component has been successfully activated"); + } + } catch (Throwable e) { + log.error("Error occurred while activating Iot Device Management Service Component", e); + } + } + + protected void deactivate(ComponentContext ctx) { + XmppConfig xmppConfig = XmppConfig.getInstance(); + try { + if (xmppConfig.isEnabled()) { + XmppServerClient xmppServerClient = new XmppServerClient(); + xmppServerClient.initControlQueue(); + xmppServerClient.deleteCurrentXmppSessions(); + } + } catch (DeviceControllerException e) { + String errorMsg = "An error occurred whilst trying to delete all existing XMPP login sessions at " + + "[" + xmppConfig.getXmppEndpoint() + "]."; + log.error(errorMsg, e); + } + + if (log.isDebugEnabled()) { + log.debug("De-activating Iot Device Management Service Component"); + } + } + + protected void setConfigurationContextService(ConfigurationContextService configurationContextService) { + if (log.isDebugEnabled()) { + log.debug("Setting ConfigurationContextService"); + } + + IotDeviceManagementServiceComponent.configurationContextService = configurationContextService; + + } + + protected void unsetConfigurationContextService(ConfigurationContextService configurationContextService) { + if (log.isDebugEnabled()) { + log.debug("Un-setting ConfigurationContextService"); + } + IotDeviceManagementServiceComponent.configurationContextService = null; + } +} diff --git a/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot/src/main/java/org/wso2/carbon/device/mgt/iot/mqtt/PolicyPush.java b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot/src/main/java/org/wso2/carbon/device/mgt/iot/mqtt/PolicyPush.java new file mode 100644 index 0000000000..9fc4fddd9f --- /dev/null +++ b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot/src/main/java/org/wso2/carbon/device/mgt/iot/mqtt/PolicyPush.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2016, 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.wso2.carbon.device.mgt.iot.mqtt; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.eclipse.paho.client.mqttv3.MqttClient; +import org.eclipse.paho.client.mqttv3.MqttConnectOptions; +import org.eclipse.paho.client.mqttv3.MqttException; +import org.eclipse.paho.client.mqttv3.MqttMessage; +import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence; + +public class PolicyPush { + + private static final Log log = LogFactory.getLog(PolicyPush.class); + + public boolean pushToMQTT(String topic, String content, String broker, String clientId) { + + byte qos = 2; + MemoryPersistence persistence = new MemoryPersistence(); + + try { + MqttClient me = new MqttClient(broker, clientId, persistence); + MqttConnectOptions connOpts = new MqttConnectOptions(); + connOpts.setCleanSession(true); + if (log.isDebugEnabled()) { + log.debug("Connecting to broker: " + broker); + } + me.connect(connOpts); + if (log.isDebugEnabled()) { + log.debug("Connected"); + log.debug("Publishing message: " + content); + } + MqttMessage message = new MqttMessage(content.getBytes()); + message.setQos(qos); + me.publish(topic, message); + if (log.isDebugEnabled()) { + log.debug("Message published"); + } + me.disconnect(); + if (log.isDebugEnabled()) { + log.debug("Disconnected"); + } + return true; + } catch (MqttException ex) { + log.error("Error occurred when trying to publish to MQTT Queue", ex); + return false; + } + } +} diff --git a/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot/src/main/java/org/wso2/carbon/device/mgt/iot/sensormgt/DeviceRecord.java b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot/src/main/java/org/wso2/carbon/device/mgt/iot/sensormgt/DeviceRecord.java new file mode 100644 index 0000000000..5f4d6eb903 --- /dev/null +++ b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot/src/main/java/org/wso2/carbon/device/mgt/iot/sensormgt/DeviceRecord.java @@ -0,0 +1,43 @@ +/* + * 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.wso2.carbon.device.mgt.iot.sensormgt; + +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; + +@XmlRootElement +public class DeviceRecord implements Serializable { + private Map sensorDataList = new HashMap<>(); + + public DeviceRecord(String sensorName, String sensorValue, long time) { + sensorDataList.put(sensorName, new SensorRecord(sensorValue, time)); + } + + @XmlElement + public Map getSensorDataList() { + return sensorDataList; + } + + public void addDeviceRecord(String sensorName, String sensorValue, long time) { + sensorDataList.put(sensorName, new SensorRecord(sensorValue, time)); + } +} \ No newline at end of file diff --git a/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot/src/main/java/org/wso2/carbon/device/mgt/iot/sensormgt/SensorDataManager.java b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot/src/main/java/org/wso2/carbon/device/mgt/iot/sensormgt/SensorDataManager.java new file mode 100644 index 0000000000..fa1cc7c153 --- /dev/null +++ b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot/src/main/java/org/wso2/carbon/device/mgt/iot/sensormgt/SensorDataManager.java @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2016, 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.wso2.carbon.device.mgt.iot.sensormgt; + +import org.wso2.carbon.device.mgt.iot.exception.DeviceControllerException; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +/** + * This class is used to store latest sensor value readings against a device id in an in-memory map. + */ +public class SensorDataManager { + + private static final SensorDataManager instance = new SensorDataManager(); + private Map deviceMap = new HashMap<>(); + + private SensorDataManager() { + } + + public static SensorDataManager getInstance() { + return instance; + } + + /** + * Store sensor record in a map. + * + * @param deviceId + * @param sensorName + * @param sensorValue + * @param time + * @return if success returns true + */ + public boolean setSensorRecord(String deviceId, String sensorName, String sensorValue, long time) { + DeviceRecord deviceRecord = deviceMap.get(deviceId); + if (deviceRecord == null) { + deviceRecord = new DeviceRecord(sensorName, sensorValue, time); + } else { + deviceRecord.addDeviceRecord(sensorName, sensorValue, time); + } + deviceMap.put(deviceId, deviceRecord); + return true; + } + + /** + * Returns last updated sensor records list for a device + * + * @param deviceId + * @return list of sensor records + */ + public SensorRecord[] getSensorRecords(String deviceId) throws DeviceControllerException { + DeviceRecord deviceRecord = deviceMap.get(deviceId); + if (deviceRecord != null) { + Collection list = deviceRecord.getSensorDataList().values(); + return list.toArray(new SensorRecord[list.size()]); + } + throw new DeviceControllerException("Error: No records found for the device ID: " + deviceId); + } + + /** + * Returns last updated sensor record for a device's sensor + * + * @param deviceId + * @param sensorName + * @return sensor record + */ + public SensorRecord getSensorRecord(String deviceId, String sensorName) throws + DeviceControllerException { + DeviceRecord deviceRecord = deviceMap.get(deviceId); + if (deviceRecord != null) { + SensorRecord sensorRecord = deviceRecord.getSensorDataList().get(sensorName); + if (sensorRecord != null) { + return sensorRecord; + } + throw new DeviceControllerException("Error: No records found for the Device ID: " + deviceId + + " Sensor Name: " + sensorName); + } + throw new DeviceControllerException("Error: No records found for the device ID: " + deviceId); + } + + /** + * Returns last updated sensor value for a device's sensor + * + * @param deviceId + * @param sensorName + * @return sensor reading + */ + public String getSensorRecordValue(String deviceId, String sensorName) throws DeviceControllerException { + DeviceRecord deviceRecord = deviceMap.get(deviceId); + if (deviceRecord != null) { + SensorRecord sensorRecord = deviceRecord.getSensorDataList().get(sensorName); + if (sensorRecord != null) { + return sensorRecord.getSensorValue(); + } + throw new DeviceControllerException("Error: No records found for the Device ID: " + deviceId + + " Sensor Name: " + sensorName); + } + throw new DeviceControllerException("Error: No records found for the device ID: " + deviceId); + } + + /** + * Returns last updated sensor value reading time for a device's sensor + * + * @param deviceId + * @param sensorName + * @return time in millis + */ + public long getSensorRecordTime(String deviceId, String sensorName) throws DeviceControllerException { + DeviceRecord deviceRecord = deviceMap.get(deviceId); + if (deviceRecord != null) { + SensorRecord sensorRecord = deviceRecord.getSensorDataList().get(sensorName); + if (sensorRecord != null) { + return sensorRecord.getTime(); + } + throw new DeviceControllerException("Error: No records found for the Device ID: " + deviceId + + " Sensor Name: " + sensorName); + } + throw new DeviceControllerException("Error: No records found for the device ID: " + deviceId); + } +} diff --git a/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot/src/main/java/org/wso2/carbon/device/mgt/iot/sensormgt/SensorRecord.java b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot/src/main/java/org/wso2/carbon/device/mgt/iot/sensormgt/SensorRecord.java new file mode 100644 index 0000000000..88b9916fd8 --- /dev/null +++ b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot/src/main/java/org/wso2/carbon/device/mgt/iot/sensormgt/SensorRecord.java @@ -0,0 +1,46 @@ +/* + * 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.wso2.carbon.device.mgt.iot.sensormgt; + +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; +import java.io.Serializable; + +@XmlRootElement +public class SensorRecord implements Serializable { + //sensor value float, int, boolean all should be converted into string + private String sensorValue; + private long time; + + public SensorRecord(String sensorValue, long time) { + this.sensorValue = sensorValue; + this.time = time; + } + + @XmlElement + public String getSensorValue() { + return sensorValue; + } + + @XmlElement + public long getTime() { + return time; + } + +} \ No newline at end of file diff --git a/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot/src/main/java/org/wso2/carbon/device/mgt/iot/service/IoTServerStartupListener.java b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot/src/main/java/org/wso2/carbon/device/mgt/iot/service/IoTServerStartupListener.java new file mode 100644 index 0000000000..22fd657cc3 --- /dev/null +++ b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot/src/main/java/org/wso2/carbon/device/mgt/iot/service/IoTServerStartupListener.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2016, 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.wso2.carbon.device.mgt.iot.service; + +import org.wso2.carbon.core.ServerStartupObserver; + +public class IoTServerStartupListener implements ServerStartupObserver { + private static volatile boolean serverReady = false; + @Override + public void completingServerStartup() { + } + + @Override + public void completedServerStartup() { + IoTServerStartupListener.setServerReady(true); + } + + public static boolean isServerReady() { + return IoTServerStartupListener.serverReady; + } + + public static void setServerReady(boolean serverReady) { + IoTServerStartupListener.serverReady = serverReady; + } +} diff --git a/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot/src/main/java/org/wso2/carbon/device/mgt/iot/transport/CommunicationUtils.java b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot/src/main/java/org/wso2/carbon/device/mgt/iot/transport/CommunicationUtils.java new file mode 100644 index 0000000000..59cd7813df --- /dev/null +++ b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot/src/main/java/org/wso2/carbon/device/mgt/iot/transport/CommunicationUtils.java @@ -0,0 +1,219 @@ +/* + * 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.wso2.carbon.device.mgt.iot.transport; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.tomcat.util.codec.binary.Base64; + +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; +import java.nio.charset.StandardCharsets; +import java.security.InvalidKeyException; +import java.security.Key; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.Signature; +import java.security.SignatureException; + +/** + * This is a utility class which contains methods common to the communication process of a client and the server. The + * methods include encryption/decryption of payloads and signing/verification of payloads received and to be sent. + */ +public class CommunicationUtils { + private static final Log log = LogFactory.getLog(TransportUtils.class); + // The Signature Algorithm used. + private static final String SIGNATURE_ALG = "SHA1withRSA"; + // The Encryption Algorithm and the Padding used. + private static final String CIPHER_PADDING = "RSA/ECB/PKCS1Padding"; + + /** + * Encrypts the message with the key that's passed in. + * + * @param message the message to be encrypted. + * @param encryptionKey the key to use for the encryption of the message. + * @return the encrypted message in String format. + * @throws TransportHandlerException if an error occurs with the encryption flow which can be due to Padding + * issues, encryption key being invalid or the algorithm used is unrecognizable. + */ + public static String encryptMessage(String message, Key encryptionKey) throws TransportHandlerException { + Cipher encrypter; + byte[] cipherData; + + try { + encrypter = Cipher.getInstance(CIPHER_PADDING); + encrypter.init(Cipher.ENCRYPT_MODE, encryptionKey); + cipherData = encrypter.doFinal(message.getBytes(StandardCharsets.UTF_8)); + + } catch (NoSuchAlgorithmException e) { + String errorMsg = "Algorithm not found exception occurred for Cipher instance of [" + CIPHER_PADDING + "]"; + log.error(errorMsg); + throw new TransportHandlerException(errorMsg, e); + } catch (NoSuchPaddingException e) { + String errorMsg = "No Padding error occurred for Cipher instance of [" + CIPHER_PADDING + "]"; + log.error(errorMsg); + throw new TransportHandlerException(errorMsg, e); + } catch (InvalidKeyException e) { + String errorMsg = "InvalidKey exception occurred for encryptionKey \n[\n" + encryptionKey + "\n]\n"; + log.error(errorMsg); + throw new TransportHandlerException(errorMsg, e); + } catch (BadPaddingException e) { + String errorMsg = "Bad Padding error occurred for Cipher instance of [" + CIPHER_PADDING + "]"; + log.error(errorMsg); + throw new TransportHandlerException(errorMsg, e); + } catch (IllegalBlockSizeException e) { + String errorMsg = "Illegal blockSize error occurred for Cipher instance of [" + CIPHER_PADDING + "]"; + log.error(errorMsg); + throw new TransportHandlerException(errorMsg, e); + } + + return Base64.encodeBase64String(cipherData); + } + +///TODO:: Exception needs to change according to the common package + + /** + * Signed a given message using the PrivateKey that's passes in. + * + * @param message the message to be signed. Ideally some encrypted payload. + * @param signatureKey the PrivateKey with which the message is to be signed. + * @return the Base64Encoded String of the signed payload. + * @throws TransportHandlerException if some error occurs with the signing process which may be related to the + * signature algorithm used or the key used for signing. + */ + public static String signMessage(String message, PrivateKey signatureKey) throws TransportHandlerException { + + Signature signature; + String signedEncodedString; + + try { + signature = Signature.getInstance(SIGNATURE_ALG); + signature.initSign(signatureKey); + signature.update(Base64.decodeBase64(message)); + + byte[] signatureBytes = signature.sign(); + signedEncodedString = Base64.encodeBase64String(signatureBytes); + + } catch (NoSuchAlgorithmException e) { + String errorMsg = + "Algorithm not found exception occurred for Signature instance of [" + SIGNATURE_ALG + "]"; + log.error(errorMsg); + throw new TransportHandlerException(errorMsg, e); + } catch (SignatureException e) { + String errorMsg = "Signature exception occurred for Signature instance of [" + SIGNATURE_ALG + "]"; + log.error(errorMsg); + throw new TransportHandlerException(errorMsg, e); + } catch (InvalidKeyException e) { + String errorMsg = "InvalidKey exception occurred for signatureKey \n[\n" + signatureKey + "\n]\n"; + log.error(errorMsg); + throw new TransportHandlerException(errorMsg, e); + } + return signedEncodedString; + } + + /** + * Verifies some signed-data against the a Public-Key to ensure that it was produced by the holder of the + * corresponding Private Key. + * + * @param data the actual payoad which was signed by some Private Key. + * @param signedData the signed data produced by signing the payload using a Private Key. + * @param verificationKey the corresponding Public Key which is an exact pair of the Private-Key with we expect + * the data to be signed by. + * @return true if the signed data verifies to be signed by the corresponding Private Key. + * @throws TransportHandlerException if some error occurs with the verification process which may be related to + * the signature algorithm used or the key used for signing. + */ + public static boolean verifySignature(String data, String signedData, PublicKey verificationKey) + throws TransportHandlerException { + + Signature signature; + boolean verified; + + try { + signature = Signature.getInstance(SIGNATURE_ALG); + signature.initVerify(verificationKey); + signature.update(Base64.decodeBase64(data)); + + verified = signature.verify(Base64.decodeBase64(signedData)); + + } catch (NoSuchAlgorithmException e) { + String errorMsg = + "Algorithm not found exception occurred for Signature instance of [" + SIGNATURE_ALG + "]"; + log.error(errorMsg); + throw new TransportHandlerException(errorMsg, e); + } catch (SignatureException e) { + String errorMsg = "Signature exception occurred for Signature instance of [" + SIGNATURE_ALG + "]"; + log.error(errorMsg); + throw new TransportHandlerException(errorMsg, e); + } catch (InvalidKeyException e) { + String errorMsg = "InvalidKey exception occurred for signatureKey \n[\n" + verificationKey + "\n]\n"; + log.error(errorMsg); + throw new TransportHandlerException(errorMsg, e); + } + return verified; + } + + /** + * Encrypts the message with the key that's passed in. + * + * @param encryptedMessage the encrypted message that is supposed to be decrypted. + * @param decryptKey the key to use in the decryption process. + * @return the decrypted message in String format. + * @throws TransportHandlerException if an error occurs with the encryption flow which can be due to Padding + * issues, encryption key being invalid or the algorithm used is unrecognizable. + */ + public static String decryptMessage(String encryptedMessage, Key decryptKey) throws TransportHandlerException { + + Cipher decrypter; + String decryptedMessage; + + try { + + decrypter = Cipher.getInstance(CIPHER_PADDING); + decrypter.init(Cipher.DECRYPT_MODE, decryptKey); + decryptedMessage = new String(decrypter.doFinal(Base64.decodeBase64(encryptedMessage)), + StandardCharsets.UTF_8); + + } catch (NoSuchAlgorithmException e) { + String errorMsg = "Algorithm not found exception occurred for Cipher instance of [" + CIPHER_PADDING + "]"; + log.error(errorMsg); + throw new TransportHandlerException(errorMsg, e); + } catch (NoSuchPaddingException e) { + String errorMsg = "No Padding error occurred for Cipher instance of [" + CIPHER_PADDING + "]"; + log.error(errorMsg); + throw new TransportHandlerException(errorMsg, e); + } catch (InvalidKeyException e) { + String errorMsg = "InvalidKey exception occurred for encryptionKey \n[\n" + decryptKey + "\n]\n"; + log.error(errorMsg); + throw new TransportHandlerException(errorMsg, e); + } catch (BadPaddingException e) { + String errorMsg = "Bad Padding error occurred for Cipher instance of [" + CIPHER_PADDING + "]"; + log.error(errorMsg); + throw new TransportHandlerException(errorMsg, e); + } catch (IllegalBlockSizeException e) { + String errorMsg = "Illegal blockSize error occurred for Cipher instance of [" + CIPHER_PADDING + "]"; + log.error(errorMsg); + throw new TransportHandlerException(errorMsg, e); + } + return decryptedMessage; + } +} diff --git a/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot/src/main/java/org/wso2/carbon/device/mgt/iot/transport/TransportHandler.java b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot/src/main/java/org/wso2/carbon/device/mgt/iot/transport/TransportHandler.java new file mode 100644 index 0000000000..b81bdba4f2 --- /dev/null +++ b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot/src/main/java/org/wso2/carbon/device/mgt/iot/transport/TransportHandler.java @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2016, 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.wso2.carbon.device.mgt.iot.transport; + +/** + * This interface consists of the core functionality related to the transport between any device and the server. The + * interface is an abstraction, regardless of the underlying protocol used for the transport. Implementation of this + * interface by any class that caters a specific protocol (ex: HTTP, XMPP, MQTT, CoAP) would ideally have methods + * specific to the protocol used for communication and other methods that implement the logic related to the devices + * using the protocol. The methods of the interface are identified as generic ones for implementing transport + * protocols for device communication. The implementation can utilize the appropriate method signatures applicable for + * intended protocol. + * + * @param an object of the message type specific to the protocol implemented. To be set to 'String' if there + * isn't anything specific. + */ +public interface TransportHandler { + // a default timeout interval to be used for the protocol specific connections + int DEFAULT_TIMEOUT_INTERVAL = 5000; // millis ~ 5 sec + + /** + * Implements the underlying connect mechanism specific to the protocol enabled by the interface. An object of a + * class that implements this interface would call this method before any communication is started via the + * intended protocol. + */ + void connect(); + + /** + * Used to check whether a connection (via the implemented protocol) to the external-endpoint exists. Ideally + * used to verify that the connection persists and to spawn a reconnection attempt if not. + * + * @return 'true' if connection is already made & exists, else 'false'. + */ + boolean isConnected(); + + /** + * @throws TransportHandlerException in the event of any exceptions that occur whilst processing the message. + * @see TransportHandler#processIncomingMessage(Object, String...) + */ + void processIncomingMessage() throws TransportHandlerException; + + /** + * @param message the message (of the type specific to the protocol) received from the device. + * @throws TransportHandlerException + * @see TransportHandler#processIncomingMessage(Object, String...) + */ + void processIncomingMessage(T message) throws TransportHandlerException; + + /** + * This is an overloaded method with three different method-signatures. This method is used to process any + * incoming messages via the implemented protocol. It would ideally be invoked at a point where a message + * received event is activated (Ex: `MessageArrived` callback in Eclipse-Paho-MQTT Client & `PacketListener`(s) + * in XMPP). + *

      + * + * @param message the message (of the type specific to the protocol) received from the device. + * @param messageParams one or more other parameters received as part-of & relevant-to the message (Ex: MQTT Topic). + * @throws TransportHandlerException in the event of any exceptions that occur whilst processing the message. + */ + void processIncomingMessage(T message, String... messageParams) throws TransportHandlerException; + + /** + * @throws TransportHandlerException in the event of any exceptions that occur whilst sending the message. + * @see TransportHandler#publishDeviceData(String...) + */ + void publishDeviceData() throws TransportHandlerException; + + /** + * @param publishData the message (of the type specific to the protocol) to be sent to the device. + * @throws TransportHandlerException in the event of any exceptions that occur whilst sending the message. + * @see TransportHandler#publishDeviceData(String...) + */ + void publishDeviceData(T publishData) throws TransportHandlerException; + + /** + * This is an overloaded method with three different method-signatures. This method is used to publish messages + * to an external-endpoint/device via the implemented protocol. It could in itself call the (communicating) + * external-endpoint or invoke any method provided by the protocol specific library. + *

      + * + * @param publishData one or more parameters specific to the message and the data to be sent. + * @throws TransportHandlerException in the event of any exceptions that occur whilst sending the message. + */ + void publishDeviceData(String... publishData) throws TransportHandlerException; + + /** + * Implements the underlying disconnect mechanism specific to the protocol enabled by the interface. An object of a + * class that implements this interface would call this method upon completion of all communication. In the case of + * the IoT-Server invoking this would only be required if the server shuts-down. + */ + void disconnect(); +} diff --git a/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot/src/main/java/org/wso2/carbon/device/mgt/iot/transport/TransportHandlerException.java b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot/src/main/java/org/wso2/carbon/device/mgt/iot/transport/TransportHandlerException.java new file mode 100644 index 0000000000..c862e56d85 --- /dev/null +++ b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot/src/main/java/org/wso2/carbon/device/mgt/iot/transport/TransportHandlerException.java @@ -0,0 +1,56 @@ +/* + * 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.wso2.carbon.device.mgt.iot.transport; + +public class TransportHandlerException extends Exception { + + private static final long serialVersionUID = 2736466230451105440L; + private String errorMessage; + + public String getErrorMessage() { + return errorMessage; + } + + public void setErrorMessage(String errorMessage) { + this.errorMessage = errorMessage; + } + + public TransportHandlerException(String msg, Exception nestedEx) { + super(msg, nestedEx); + setErrorMessage(msg); + } + + public TransportHandlerException(String message, Throwable cause) { + super(message, cause); + setErrorMessage(message); + } + + public TransportHandlerException(String msg) { + super(msg); + setErrorMessage(msg); + } + + public TransportHandlerException() { + super(); + } + + public TransportHandlerException(Throwable cause) { + super(cause); + } +} diff --git a/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot/src/main/java/org/wso2/carbon/device/mgt/iot/transport/TransportUtils.java b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot/src/main/java/org/wso2/carbon/device/mgt/iot/transport/TransportUtils.java new file mode 100644 index 0000000000..b6f32778b2 --- /dev/null +++ b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot/src/main/java/org/wso2/carbon/device/mgt/iot/transport/TransportUtils.java @@ -0,0 +1,296 @@ +/* + * 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.wso2.carbon.device.mgt.iot.transport; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.DatagramSocket; +import java.net.HttpURLConnection; +import java.net.InetAddress; +import java.net.MalformedURLException; +import java.net.NetworkInterface; +import java.net.ServerSocket; +import java.net.SocketException; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; +import java.util.Random; + +public class TransportUtils { + private static final Log log = LogFactory.getLog(TransportUtils.class); + + public static final int MIN_PORT_NUMBER = 9000; + public static final int MAX_PORT_NUMBER = 11000; + + /** + * Given a server endpoint as a String, this method splits it into Protocol, Host and Port + * + * @param ipString a network endpoint in the format - '://:' + * @return a map with keys "Protocol", "Host" & "Port" for the related values from the ipString + * @throws TransportHandlerException + */ + public static Map getHostAndPort(String ipString) + throws TransportHandlerException { + Map ipPortMap = new HashMap(); + String[] ipPortArray = ipString.split(":"); + + if (ipPortArray.length != 3) { + String errorMsg = + "The IP String - '" + ipString + + "' is invalid. It needs to be in format '://:'."; + log.info(errorMsg); + throw new TransportHandlerException(errorMsg); + } + + ipPortMap.put("Protocol", ipPortArray[0]); + ipPortMap.put("Host", ipPortArray[1].replace(File.separator, "")); + ipPortMap.put("Port", ipPortArray[2]); + return ipPortMap; + } + + /** + * This method validates whether a specific IP Address is of IPv4 type + * + * @param ipAddress the IP Address which needs to be validated + * @return true if it is of IPv4 type and false otherwise + */ + public static boolean validateIPv4(String ipAddress) { + try { + if (ipAddress == null || ipAddress.isEmpty()) { + return false; + } + + String[] parts = ipAddress.split("\\."); + if (parts.length != 4) { + return false; + } + + for (String s : parts) { + int i = Integer.parseInt(s); + if ((i < 0) || (i > 255)) { + return false; + } + } + return !ipAddress.endsWith("."); + + } catch (NumberFormatException nfe) { + log.warn("The IP Address: " + ipAddress + " could not " + + "be validated against IPv4-style"); + return false; + } + } + + public static Map getInterfaceIPMap() throws TransportHandlerException { + + Map interfaceToIPMap = new HashMap(); + Enumeration networkInterfaces; + String networkInterfaceName = ""; + String ipAddress; + + try { + networkInterfaces = NetworkInterface.getNetworkInterfaces(); + } catch (SocketException exception) { + String errorMsg = + "Error encountered whilst trying to get the list of network-interfaces"; + log.error(errorMsg); + throw new TransportHandlerException(errorMsg, exception); + } + + try { + for (; networkInterfaces.hasMoreElements(); ) { + networkInterfaceName = networkInterfaces.nextElement().getName(); + + if (log.isDebugEnabled()) { + log.debug("Network Interface: " + networkInterfaceName); + log.debug("------------------------------------------"); + } + + Enumeration interfaceIPAddresses = NetworkInterface.getByName( + networkInterfaceName).getInetAddresses(); + + for (; interfaceIPAddresses.hasMoreElements(); ) { + ipAddress = interfaceIPAddresses.nextElement().getHostAddress(); + + if (log.isDebugEnabled()) { + log.debug("IP Address: " + ipAddress); + } + + if (TransportUtils.validateIPv4(ipAddress)) { + interfaceToIPMap.put(networkInterfaceName, ipAddress); + } + } + + if (log.isDebugEnabled()) { + log.debug("------------------------------------------"); + } + } + } catch (SocketException exception) { + String errorMsg = + "Error encountered whilst trying to get the IP Addresses of the network " + + "interface: " + networkInterfaceName; + log.error(errorMsg); + throw new TransportHandlerException(errorMsg, exception); + } + + return interfaceToIPMap; + } + + /** + * Attempts to find a free port between the MIN_PORT_NUMBER(9000) and MAX_PORT_NUMBER(11000). + * Tries 'RANDOMLY picked' port numbers between this range up-until "randomAttempts" number of + * times. If still fails, then tries each port in descending order from the MAX_PORT_NUMBER + * whilst skipping already attempted ones via random selection. + * + * @param randomAttempts no of times to TEST port numbers picked randomly over the given range + * @return an available/free port + */ + public static synchronized int getAvailablePort(int randomAttempts) { + ArrayList failedPorts = new ArrayList(randomAttempts); + + Random randomNum = new Random(); + int randomPort = MAX_PORT_NUMBER; + + while (randomAttempts > 0) { + randomPort = randomNum.nextInt(MAX_PORT_NUMBER - MIN_PORT_NUMBER) + MIN_PORT_NUMBER; + + if (checkIfPortAvailable(randomPort)) { + return randomPort; + } + failedPorts.add(randomPort); + randomAttempts--; + } + + randomPort = MAX_PORT_NUMBER; + + while (true) { + if (!failedPorts.contains(randomPort) && checkIfPortAvailable(randomPort)) { + return randomPort; + } + randomPort--; + } + } + + private static boolean checkIfPortAvailable(int port) { + ServerSocket tcpSocket = null; + DatagramSocket udpSocket = null; + + try { + tcpSocket = new ServerSocket(port); + tcpSocket.setReuseAddress(true); + + udpSocket = new DatagramSocket(port); + udpSocket.setReuseAddress(true); + return true; + } catch (IOException ex) { + // denotes the port is in use + } finally { + if (tcpSocket != null) { + try { + tcpSocket.close(); + } catch (IOException e) { + /* not to be thrown */ + } + } + + if (udpSocket != null) { + udpSocket.close(); + } + } + + return false; + } + + /** + * This is a utility method that creates and returns a HTTP connection object. + * + * @param urlString the URL pattern to which the connection needs to be created + * @return an HTTPConnection object which cn be used to send HTTP requests + * @throws TransportHandlerException if errors occur when creating the HTTP connection with + * the given URL string + */ + public static HttpURLConnection getHttpConnection(String urlString) throws + TransportHandlerException { + URL connectionUrl; + 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 TransportHandlerException(errorMsg, e); + } catch (IOException exception) { + String errorMsg = "Error occured whilst trying to open a connection to: " + urlString; + log.error(errorMsg); + throw new TransportHandlerException(errorMsg, exception); + } + return httpConnection; + } + + /** + * This is a utility method that reads and returns the response from a HTTP connection + * + * @param httpConnection the connection from which a response is expected + * @return the response (as a string) from the given HTTP connection + * @throws TransportHandlerException if any errors occur whilst reading the response from + * the connection stream + */ + public static String readResponseFromHttpRequest(HttpURLConnection httpConnection) + throws TransportHandlerException { + BufferedReader bufferedReader; + try { + bufferedReader = new BufferedReader(new InputStreamReader( + httpConnection.getInputStream(), StandardCharsets.UTF_8)); + } catch (IOException exception) { + String errorMsg = "There is an issue with connecting the reader to the input stream at: " + + httpConnection.getURL(); + log.error(errorMsg); + throw new TransportHandlerException(errorMsg, exception); + } + + String responseLine; + StringBuilder completeResponse = new StringBuilder(); + + try { + while ((responseLine = bufferedReader.readLine()) != null) { + completeResponse.append(responseLine); + } + } catch (IOException exception) { + String errorMsg = "Error occured whilst trying read from the connection stream at: " + + httpConnection.getURL(); + log.error(errorMsg); + throw new TransportHandlerException(errorMsg, exception); + } + try { + bufferedReader.close(); + } catch (IOException exception) { + log.error("Could not succesfully close the bufferedReader to the connection at: " + httpConnection.getURL()); + } + return completeResponse.toString(); + } +} diff --git a/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot/src/main/java/org/wso2/carbon/device/mgt/iot/transport/mqtt/MQTTTransportHandler.java b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot/src/main/java/org/wso2/carbon/device/mgt/iot/transport/mqtt/MQTTTransportHandler.java new file mode 100644 index 0000000000..a413bbf9c7 --- /dev/null +++ b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot/src/main/java/org/wso2/carbon/device/mgt/iot/transport/mqtt/MQTTTransportHandler.java @@ -0,0 +1,402 @@ +/* + * Copyright (c) 2016, 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.wso2.carbon.device.mgt.iot.transport.mqtt; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken; +import org.eclipse.paho.client.mqttv3.MqttCallback; +import org.eclipse.paho.client.mqttv3.MqttClient; +import org.eclipse.paho.client.mqttv3.MqttConnectOptions; +import org.eclipse.paho.client.mqttv3.MqttException; +import org.eclipse.paho.client.mqttv3.MqttMessage; +import org.wso2.carbon.device.mgt.iot.transport.TransportHandler; +import org.wso2.carbon.device.mgt.iot.transport.TransportHandlerException; + +import java.io.File; +import java.nio.charset.StandardCharsets; + +/** + * This is an abstract class that implements the "TransportHandler" interface. The interface is an abstraction for + * the core functionality with regards to device-server communication regardless of the Transport protocol. This + * specific class contains the MQTT-Transport specific implementations. The class implements utility methods for the + * case of a MQTT communication. However, this "abstract class", even-though it implements the "TransportHandler" + * interface, does not contain the logic relevant to the interface methods. The specific functionality of the + * interface methods are intended to be implemented by the concrete class that extends this abstract class and + * utilizes the MQTT specific functionality (ideally a device API writer who would like to communicate to the device + * via MQTT Protocol). + *

      + * This class contains the Device-Management specific implementation for all the MQTT functionality. This includes + * connecting to a MQTT Broker & subscribing to the appropriate MQTT-topic, action plan upon losing connection or + * successfully delivering a message to the broker and upon receiving a MQTT message. Makes use of the 'Paho-MQTT' + * library provided by Eclipse Org. + */ +public abstract class MQTTTransportHandler implements MqttCallback, TransportHandler { + + private static final Log log = LogFactory.getLog(MQTTTransportHandler.class); + private MqttClient client; + private String clientId; + private MqttConnectOptions options; // options to be set to the client-connection. + // topic to which a will-message is automatically published by the broker upon the device losing its connection. + private String clientWillTopic; + protected String mqttBrokerEndPoint; + protected int timeoutInterval; // interval to use for reconnection attempts etc. + protected String subscribeTopic; + // Quality of Service Levels for MQTT Subscription and Publishing. + public static final int QoS_0 = 0; // At-Most Once + @SuppressWarnings("unused") + public static final int QoS_1 = 1; // At-Least Once + public static final int QoS_2 = 2; // Exactly Once + public static final int DEFAULT_MQTT_QUALITY_OF_SERVICE = QoS_0; + // Prefix to the Will-Topic to which a message is published if client loses its connection. + private static final String DISCONNECTION_WILL_TOPIC_PREFIX = "Disconnection/"; + // Will-Message of the client to be published if connection is lost. + private static final String DISCONNECTION_WILL_MSG = "Lost-Connection"; + + /** + * Constructor for the MQTTTransportHandler which takes in the owner, type of the device and the MQTT Broker URL + * and the topic to subscribe. + * + * @param deviceOwner the owner of the device. + * @param deviceType the CDMF Device-Type of the device. + * @param mqttBrokerEndPoint the IP/URL of the MQTT broker endpoint. + * @param subscribeTopic the MQTT topic to which the client is to be subscribed. + */ + protected MQTTTransportHandler(String deviceOwner, String deviceType, + String mqttBrokerEndPoint, String subscribeTopic) { + this.clientId = deviceOwner + ":" + deviceType; + this.subscribeTopic = subscribeTopic; + this.clientWillTopic = DISCONNECTION_WILL_TOPIC_PREFIX + deviceType; + this.mqttBrokerEndPoint = mqttBrokerEndPoint; + this.timeoutInterval = DEFAULT_TIMEOUT_INTERVAL; + this.initMQTTClient(); + } + + /** + * Constructor for the MQTTTransportHandler which takes in the owner, type of the device and the MQTT Broker URL + * and the topic to subscribe. Additionally this constructor takes in the reconnection-time interval between + * successive attempts to connect to the broker. + * + * @param deviceOwner the owner of the device. + * @param deviceType the CDMF Device-Type of the device. + * @param mqttBrokerEndPoint the IP/URL of the MQTT broker endpoint. + * @param subscribeTopic the MQTT topic to which the client is to be subscribed + * @param intervalInMillis the time interval in MILLI-SECONDS between attempts to connect to the broker. + */ + protected MQTTTransportHandler(String deviceOwner, String deviceType, + String mqttBrokerEndPoint, String subscribeTopic, int intervalInMillis) { + this.clientId = deviceOwner + ":" + deviceType; + this.subscribeTopic = subscribeTopic; + this.clientWillTopic = DISCONNECTION_WILL_TOPIC_PREFIX + deviceType; + this.mqttBrokerEndPoint = mqttBrokerEndPoint; + this.timeoutInterval = intervalInMillis; + this.initMQTTClient(); + } + + /** + * Initializes the MQTT-Client. Creates a client using the given MQTT-broker endpoint and the clientId (which is + * constructed by a concatenation of [deviceOwner]:[deviceType]). Also sets the client's options parameter with + * the clientWillTopic (in-case of connection failure) and other info. Also sets the callback to this current class. + */ + private void initMQTTClient() { + try { + client = new MqttClient(this.mqttBrokerEndPoint, clientId, null); + log.info("MQTT client was created with ClientID : " + clientId); + } catch (MqttException ex) { + String errorMsg = "Initializing the MQTT Client failed."; + log.error(errorMsg, ex); + //TODO:: Throw the error out + } + options = new MqttConnectOptions(); + options.setKeepAliveInterval(120); // set the keep alive interval to 120 seconds by default. + options.setCleanSession(true); // sets clean session to true by default. + setDisconnectionWillForClient(QoS_2, true); // sets default will-topic & msg with QoS 2 and retained true. + client.setCallback(this); // callback for MQTT events are set to `this` object. + } + + /** + * @param qos the Quality of Service at which the last-will-message is to be published. + * @param isRetained indicate whether to retain the last-will-message. + * @see MQTTTransportHandler#setDisconnectionWillForClient(String, String, int, boolean). Uses the default values + * for Will-Topic and Will-Message. + */ + protected void setDisconnectionWillForClient(int qos, boolean isRetained) { + this.setDisconnectionWillForClient(clientWillTopic, DISCONNECTION_WILL_MSG, qos, isRetained); + } + + /** + * Sets the [Will] option in the default options-set of the MQTT Client. A will-topic, will-message is parsed + * along with the QoS and the retained flag. When the client loses its connection to the broker, the broker + * publishes the will-message to the will-topic, to itself. + * + * @param willTopic the topic to which the last will message is to be published when client exists ungracefully. + * @param willMsg the message to be published upon client's ungraceful exit from the broker. + * @param qos the Quality of Service at which the last-will-message is to be published. + * @param isRetained indicate whether to retain the last-will-message. + */ + protected void setDisconnectionWillForClient(String willTopic, String willMsg, int qos, boolean isRetained) { + this.options.setWill(willTopic, willMsg.getBytes(StandardCharsets.UTF_8), qos, isRetained); + } + + /** + * Sets the [Clean-Session] option in the default options-set of the MQTT Client. It is set to `true` by default. + * + * @param setCleanSession `true` indicates that the session details can be cleared/cleaned upon disconnection, + * `false` indicates that the session details are to be persisted if the client disconnects. + */ + @SuppressWarnings("unused") + protected void setClientCleanSession(boolean setCleanSession) { + this.options.setCleanSession(setCleanSession); + } + + /** + * Sets the [Username] & [Password] options in the default options-set of the MQTT Client. By default these + * values are not set. + * + * @param username the username to be used by the client to connect to the broker. + * @param password the password to be used by the client to connect to the broker. + */ + @SuppressWarnings("unused") + protected void setUsernameAndPassword(String username, String password) { + this.options.setUserName(username); + this.options.setPassword(password.toCharArray()); + } + + /** + * Connects to the MQTT-Broker at the endpoint specified in the constructor to this class using default the + * MQTT-options. + * + * @throws TransportHandlerException in the event of 'Connecting to' the MQTT broker fails. + */ + protected void connectToQueue() throws TransportHandlerException { + this.connectToQueue(options); + } + + /** + * Connects to the MQTT-Broker at the endpoint specified in the constructor to this class using the MQTT-Options + * passed. + * + * @param options options to be used by the client for this connection. (username, password, clean-session, etc) + * @throws TransportHandlerException in the event of 'Connecting to' the MQTT broker fails. + */ + protected void connectToQueue(MqttConnectOptions options) throws TransportHandlerException { + try { + client.connect(options); + if (log.isDebugEnabled()) { + log.debug("MQTT Client connected to queue at: " + this.mqttBrokerEndPoint); + } + } catch (MqttException ex) { + String errorMsg = "MQTT Exception occured whilst connecting to queue at [" + this.mqttBrokerEndPoint + "]"; + log.error(errorMsg); + throw new TransportHandlerException(errorMsg, ex); + } + } + + /** + * @throws TransportHandlerException in the event of 'Subscribing to' the MQTT broker fails. + * @see MQTTTransportHandler#subscribeToQueue(int). Uses default QoS of 1. + */ + protected void subscribeToQueue() throws TransportHandlerException { + this.subscribeToQueue(QoS_0); + } + + /** + * Subscribes to the MQTT-Topic specified in the constructor to this class. + * + * @throws TransportHandlerException in the event of 'Subscribing to' the MQTT broker fails. + */ + protected void subscribeToQueue(int qos) throws TransportHandlerException { + try { + client.subscribe(subscribeTopic, qos); + if (log.isDebugEnabled()) { + log.debug("Client [" + clientId + "] subscribed to topic: " + subscribeTopic); + } + } catch (MqttException ex) { + String errorMsg = "MQTT Exception occurred whilst client [" + clientId + "] tried to subscribe to " + + "topic: [" + subscribeTopic + "]"; + log.error(errorMsg); + throw new TransportHandlerException(errorMsg, ex); + } + } + + /** + * @param topic the topic to which the message is to be published. + * @param payLoad the message (payload) of the MQTT publish action. + * @see MQTTTransportHandler#publishToQueue(String, String, int, boolean) + */ + @SuppressWarnings("unused") + protected void publishToQueue(String topic, String payLoad) throws TransportHandlerException { + publishToQueue(topic, payLoad, DEFAULT_MQTT_QUALITY_OF_SERVICE, false); + } + + /** + * @param topic the topic to which the message is to be published. + * @param message the message (payload) of the MQTT publish action as a `MQTTMessage`. + * @throws TransportHandlerException if any error occurs whilst trying to publish to the MQTT Queue. + * @see MQTTTransportHandler#publishToQueue(String, String, int, boolean) + */ + protected void publishToQueue(String topic, MqttMessage message) throws TransportHandlerException { + try { + client.publish(topic, message); + if (log.isDebugEnabled()) { + log.debug("Message: " + message.toString() + " to MQTT topic [" + topic + "] published successfully"); + } + } catch (MqttException ex) { + String errorMsg = "MQTT Client Error whilst client [" + clientId + "] tried to publish to queue at " + + "[" + mqttBrokerEndPoint + "] under topic [" + topic + "]"; + log.info(errorMsg); + throw new TransportHandlerException(errorMsg, ex); + } + } + + /** + * This method is used to publish messages to the MQTT-Endpoint to which this client is connected to. It is via + * publishing to this broker that the messages are communicated to the device. This is an overloaded method with + * different parameter combinations. This method invokes the publish method provided by the MQTT-Client library. + * + * @param topic the topic to which the message is to be published. + * @param payLoad the message (payload) of the MQTT publish action. + * @param qos the Quality-of-Service of the current publish action. + * Could be 0(At-most once), 1(At-least once) or 2(Exactly once) + * @param retained indicate whether to retain the publish-message in the event of no subscribers. + * @throws TransportHandlerException if any error occurs whilst trying to publish to the MQTT Queue. + */ + protected void publishToQueue(String topic, String payLoad, int qos, boolean retained) + throws TransportHandlerException { + try { + client.publish(topic, payLoad.getBytes(StandardCharsets.UTF_8), qos, retained); + if (log.isDebugEnabled()) { + log.debug("Message: " + payLoad + " to MQTT topic [" + topic + "] published successfully"); + } + } catch (MqttException ex) { + String errorMsg = "MQTT Client Error whilst client [" + clientId + "] tried to publish to queue at " + + "[" + mqttBrokerEndPoint + "] under topic [" + topic + "]"; + log.info(errorMsg); + throw new TransportHandlerException(errorMsg, ex); + } + } + + /** + * Checks whether the connection to the MQTT-Broker exists. + * + * @return `true` if the client is connected to the MQTT-Broker, else `false`. + */ + @Override + public boolean isConnected() { + return client.isConnected(); + } + + /** + * Callback method which is triggered once the MQTT client losers its connection to the broker. Spawns a new + * thread that executes necessary actions to try and reconnect to the endpoint. + * + * @param throwable a Throwable Object containing the details as to why the failure occurred. + */ + @Override + public void connectionLost(Throwable throwable) { + if (log.isDebugEnabled()) { + log.warn("Connection for client: " + this.clientId + " to " + this.mqttBrokerEndPoint + " was lost." + + "\nThis was due to - " + throwable.getMessage()); + } + + Thread reconnectThread = new Thread() { + public void run() { + connect(); + } + }; + reconnectThread.setDaemon(true); + reconnectThread.start(); + } + + /** + * Callback method which is triggered upon receiving a MQTT Message from the broker. Spawns a new thread that + * executes any actions to be taken with the received message. + * + * @param topic the MQTT-Topic to which the received message was published to and the client subscribed to. + * @param mqttMessage the actual MQTT-Message that was received from the broker. + */ + @Override + public void messageArrived(final String topic, final MqttMessage mqttMessage) { + if (log.isDebugEnabled()) { + log.debug("Got an MQTT message '" + mqttMessage.toString() + "' for topic '" + topic + "'."); + } + + Thread messageProcessorThread = new Thread() { + public void run() { + try { + processIncomingMessage(mqttMessage, topic); + } catch (TransportHandlerException e) { + log.error("An error occurred when trying to process received MQTT message [" + mqttMessage + "] " + + "for topic [" + topic + "].", e); + } + } + }; + messageProcessorThread.setDaemon(true); + messageProcessorThread.start(); + } + + /** + * Callback method which gets triggered upon successful completion of a message delivery to the broker. + * + * @param iMqttDeliveryToken the MQTT-DeliveryToken which includes the details about the specific message delivery. + */ + @Override + public void deliveryComplete(IMqttDeliveryToken iMqttDeliveryToken) { + String topic = iMqttDeliveryToken.getTopics()[0]; + String client = iMqttDeliveryToken.getClient().getClientId(); + + try { + if (iMqttDeliveryToken.isComplete()) { + if (log.isDebugEnabled()) { + if (iMqttDeliveryToken.getMessage() != null) { + String message = iMqttDeliveryToken.getMessage().toString(); + log.debug("Message to client [" + client + "] under topic (" + topic + + ") was delivered successfully with the delivery message: '" + message + "'"); + } else { + log.debug("Message to client [" + client + "] under topic (" + topic + + ") was delivered successfully."); + } + } + } else { + log.warn("FAILED: Delivery of MQTT message to [" + client + "] under topic [" + topic + "] failed."); + } + } catch (MqttException e) { + log.warn("Error occurred whilst trying to read the message from the MQTT delivery token."); + } + } + + /** + * Closes the connection to the MQTT Broker. + */ + public void closeConnection() throws MqttException { + if (client != null && isConnected()) { + client.disconnect(); + } + } + + /** + * Fetches the default options set for the MQTT Client + * + * @return the options that are currently set for the client. + */ + public MqttConnectOptions getOptions() { + return options; + } +} + diff --git a/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot/src/main/java/org/wso2/carbon/device/mgt/iot/transport/xmpp/XMPPTransportHandler.java b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot/src/main/java/org/wso2/carbon/device/mgt/iot/transport/xmpp/XMPPTransportHandler.java new file mode 100644 index 0000000000..1c57897f83 --- /dev/null +++ b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot/src/main/java/org/wso2/carbon/device/mgt/iot/transport/xmpp/XMPPTransportHandler.java @@ -0,0 +1,376 @@ +/* + * 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.wso2.carbon.device.mgt.iot.transport.xmpp; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.jivesoftware.smack.ConnectionConfiguration; +import org.jivesoftware.smack.PacketListener; +import org.jivesoftware.smack.SmackConfiguration; +import org.jivesoftware.smack.XMPPConnection; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smack.filter.AndFilter; +import org.jivesoftware.smack.filter.FromContainsFilter; +import org.jivesoftware.smack.filter.OrFilter; +import org.jivesoftware.smack.filter.PacketFilter; +import org.jivesoftware.smack.filter.PacketTypeFilter; +import org.jivesoftware.smack.filter.ToContainsFilter; +import org.jivesoftware.smack.packet.Message; +import org.jivesoftware.smack.packet.Packet; +import org.wso2.carbon.device.mgt.iot.transport.TransportHandler; +import org.wso2.carbon.device.mgt.iot.transport.TransportHandlerException; + +/** + * This is an abstract class that implements the "TransportHandler" interface. The interface is an abstraction for + * the core functionality with regards to device-server communication regardless of the Transport protocol. This + * specific class contains the HTTP-Transport specific implementations. The class implements utility methods for the + * case of a HTTP communication. However, this "abstract class", even-though it implements the "TransportHandler" + * interface, does not contain the logic relevant to the interface methods. The specific functionality of the + * interface methods are intended to be implemented by the concrete class that extends this abstract class and + * utilizes the HTTP specific functionality (ideally a device API writer who would like to communicate to the device + * via HTTP Protocol). + *

      + * This class contains the IoT-Server specific implementation for all the XMPP functionality. This includes + * connecting to a XMPP Server & Login-In using the device's/server's XMPP-Account, Setting listeners and filters on + * incoming XMPP messages and Sending XMPP replies for messages received. Makes use of the 'Smack-XMPP' library + * provided by jivesoftware/igniterealtime. + */ +public abstract class XMPPTransportHandler implements TransportHandler { + + private static final Log log = LogFactory.getLog(XMPPTransportHandler.class); + protected String server; + protected int timeoutInterval; // millis + //TODO:: Shouldnt be hard-coded. Need to be read from configs + private static final int DEFAULT_XMPP_PORT = 5222; + private XMPPConnection connection; + private int port; + private PacketFilter filter; + private PacketListener listener; + + /** + * Constructor for XMPPTransportHandler passing only the server-IP. + * + * @param server the IP of the XMPP server. + */ + @SuppressWarnings("unused") + protected XMPPTransportHandler(String server) { + this.server = server; + this.port = DEFAULT_XMPP_PORT; + this.timeoutInterval = DEFAULT_TIMEOUT_INTERVAL; + initXMPPClient(); + } + + /** + * Constructor for XMPPTransportHandler passing server-IP and the XMPP-port. + * + * @param server the IP of the XMPP server. + * @param port the XMPP server's port to connect to. (default - 5222) + */ + protected XMPPTransportHandler(String server, int port) { + this.server = server; + this.port = port; + this.timeoutInterval = DEFAULT_TIMEOUT_INTERVAL; + initXMPPClient(); + } + + /** + * Constructor for XMPPTransportHandler passing server-IP, the XMPP-port and the + * timeoutInterval used by listeners to the server and for reconnection schedules. + * + * @param server the IP of the XMPP server. + * @param port the XMPP server's port to connect to. (default - 5222) + * @param timeoutInterval the timeout interval to use for the connection and reconnection + */ + @SuppressWarnings("unused") + protected XMPPTransportHandler(String server, int port, int timeoutInterval) { + this.server = server; + this.port = port; + this.timeoutInterval = timeoutInterval; + initXMPPClient(); + } + + /** + * Sets the client's time-out-limit whilst waiting for XMPP-replies from server. + * + * @param millis the time in millis to be set as the time-out-limit whilst waiting for a + * XMPP-reply. + */ + @SuppressWarnings("unused") + public void setTimeoutInterval(int millis) { + this.timeoutInterval = millis; + } + + /** + * Checks whether the connection to the XMPP-Server persists. + * + * @return true if the client is connected to the XMPP-Server, else false. + */ + @Override + public boolean isConnected() { + return connection.isConnected(); + } + + /** + * Initializes the XMPP Client. Sets the time-out-limit whilst waiting for XMPP-replies from + * server. Sets the XMPP configurations to connect to the server and creates the + * XMPPConnection object used for connecting and Logging-In. + */ + private void initXMPPClient() { + log.info(String.format("Initializing connection to XMPP Server at %1$s via port " + + "%2$d.", server, port)); + SmackConfiguration.setPacketReplyTimeout(timeoutInterval); + ConnectionConfiguration config = new ConnectionConfiguration(server, port); +// TODO:: Need to enable SASL-Authentication appropriately + config.setSASLAuthenticationEnabled(false); + config.setSecurityMode(ConnectionConfiguration.SecurityMode.disabled); + connection = new XMPPConnection(config); + } + +//TODO:: Re-check all exception handling + + /** + * Connects to the XMPP-Server and if attempt unsuccessful, then throws exception. + * + * @throws TransportHandlerException in the event of 'Connecting to' the XMPP server fails. + */ + protected void connectToServer() throws TransportHandlerException { + try { + connection.connect(); + log.info(String.format( + "Connection to XMPP Server at %1$s established successfully......", server)); + + } catch (XMPPException xmppExcepion) { + String errorMsg = + "Connection attempt to the XMPP Server at " + server + " via port " + port + + " failed."; + log.info(errorMsg); + throw new TransportHandlerException(errorMsg, xmppExcepion); + } + } + + /** + * If successfully established connection, then tries to Log in using the device's XMPP + * Account credentials. + * + * @param username the username of the device's XMPP-Account. + * @param password the password of the device's XMPP-Account. + * @param resource the resource the resource, specific to the XMPP-Account to which the login + * is made to + * @throws TransportHandlerException in the event of 'Logging into' the XMPP server fails. + */ + protected void loginToServer(String username, String password, String resource) + throws TransportHandlerException { + if (isConnected()) { + try { + if (resource == null) { + connection.login(username, password); + log.info(String.format("Logged into XMPP Server at %1$s as user %2$s......", + server, username)); + } else { + connection.login(username, password, resource); + log.info(String.format( + "Logged into XMPP Server at %1$s as user %2$s on resource %3$s......", + server, username, resource)); + } + } catch (XMPPException xmppException) { + String errorMsg = + "Login attempt to the XMPP Server at " + server + " with username - " + + username + " failed."; + log.info(errorMsg); + throw new TransportHandlerException(errorMsg, xmppException); + } + } else {//TODO:: Log not required + String errorMsg = + "Not connected to XMPP-Server to attempt Login. Please 'connectToServer' " + + "before Login"; + if (log.isDebugEnabled()) { + log.debug(errorMsg); + } + throw new TransportHandlerException(errorMsg); + } + } + + /** + * Sets a filter for all the incoming XMPP-Messages on the Sender's JID (XMPP-Account ID). + * Also creates a listener for the incoming messages and connects the listener to the + * XMPPConnection alongside the set filter. + * + * @param senderJID the JID (XMPP-Account ID of the sender) to which the filter is to be set. + */ + @SuppressWarnings("unused") + protected void setFilterOnSender(String senderJID) { + filter = new AndFilter(new PacketTypeFilter(Message.class), new FromContainsFilter( + senderJID)); + listener = new PacketListener() { + @Override + public void processPacket(Packet packet) { + if (packet instanceof Message) { + final Message xmppMessage = (Message) packet; + Thread msgProcessThread = new Thread() { + public void run() { + try { + processIncomingMessage(xmppMessage); + } catch (TransportHandlerException e) { + log.error("An error occurred when trying to process received XMPP message " + + "[" + xmppMessage.getBody() + "].", e); + } + } + }; + msgProcessThread.setDaemon(true); + msgProcessThread.start(); + } + } + }; + connection.addPacketListener(listener, filter); + } + + /** + * Sets a filter for all the incoming XMPP-Messages on the Receiver's JID (XMPP-Account ID). + * Also creates a listener for the incoming messages and connects the listener to the + * XMPPConnection alongside the set filter. + * + * @param receiverJID the JID (XMPP-Account ID of the receiver) to which the filter is to be + * set. + */ + protected void setFilterOnReceiver(String receiverJID) { + filter = new AndFilter(new PacketTypeFilter(Message.class), new ToContainsFilter( + receiverJID)); + listener = new PacketListener() { + @Override + public void processPacket(Packet packet) { + if (packet instanceof Message) { + final Message xmppMessage = (Message) packet; + Thread msgProcessThread = new Thread() { + public void run() { + try { + processIncomingMessage(xmppMessage); + } catch (TransportHandlerException e) { + log.error("An error occurred when trying to process received XMPP message " + + "[" + xmppMessage.getBody() + "].", e); + } + } + }; + msgProcessThread.setDaemon(true); + msgProcessThread.start(); + } + } + }; + connection.addPacketListener(listener, filter); + } + + /** + * Sets a filter for all the incoming XMPP-Messages on the From-JID & To-JID (XMPP-Account IDs) + * passed in. Also creates a listener for the incoming messages and connects the listener to + * the XMPPConnection alongside the set filter. + * + * @param senderJID the From-JID (XMPP-Account ID) to which the filter is to be set. + * @param receiverJID the To-JID (XMPP-Account ID) to which the filter is to be set. + * @param andCondition if true: then filter is set with 'AND' operator (senderJID && + * receiverJID), + * if false: then the filter is set with 'OR' operator (senderJID | + * receiverJID) + */ + @SuppressWarnings("unused") + protected void setMessageFilterAndListener(String senderJID, String receiverJID, boolean + andCondition) { + PacketFilter jidFilter; + + if (andCondition) { + jidFilter = new AndFilter(new FromContainsFilter(senderJID), new ToContainsFilter( + receiverJID)); + } else { + jidFilter = new OrFilter(new FromContainsFilter(senderJID), new ToContainsFilter( + receiverJID)); + } + + filter = new AndFilter(new PacketTypeFilter(Message.class), jidFilter); + listener = new PacketListener() { + @Override + public void processPacket(Packet packet) { + if (packet instanceof Message) { + final Message xmppMessage = (Message) packet; + Thread msgProcessThread = new Thread() { + public void run() { + try { + processIncomingMessage(xmppMessage); + } catch (TransportHandlerException e) { + log.error("An error occurred when trying to process received XMPP message " + + "[" + xmppMessage.getBody() + "].", e); + } + } + }; + msgProcessThread.setDaemon(true); + msgProcessThread.start(); + } + } + }; + connection.addPacketListener(listener, filter); + } + + /** + * Sends an XMPP message. Calls the overloaded method with Subject set to "Reply-From-Device" + * + * @param JID the JID (XMPP Account ID) to which the message is to be sent to. + * @param message the XMPP-Message that is to be sent. + */ + @SuppressWarnings("unused") + protected void sendXMPPMessage(String JID, String message) { + sendXMPPMessage(JID, message, "XMPP-Message"); + } + + /** + * Overloaded method to send an XMPP message. Includes the subject to be mentioned in the + * message that is sent. + * + * @param JID the JID (XMPP Account ID) to which the message is to be sent to. + * @param message the XMPP-Message that is to be sent. + * @param subject the subject that the XMPP-Message would carry. + */ + protected void sendXMPPMessage(String JID, String message, String subject) { + Message xmppMessage = new Message(); + xmppMessage.setTo(JID); + xmppMessage.setSubject(subject); + xmppMessage.setBody(message); + xmppMessage.setType(Message.Type.chat); + sendXMPPMessage(JID, xmppMessage); + } + + /** + * Sends an XMPP message. + * + * @param JID the JID (XMPP Account ID) to which the message is to be sent to. + * @param xmppMessage the XMPP-Message that is to be sent. + */ + protected void sendXMPPMessage(String JID, Message xmppMessage) { + connection.sendPacket(xmppMessage); + if (log.isDebugEnabled()) { + log.debug("Message: '" + xmppMessage.getBody() + "' sent to XMPP JID [" + JID + "] sent successfully."); + } + } + + /** + * Closes the connection to the XMPP Server. + */ + public void closeConnection() { + if (connection != null && isConnected()) { + connection.disconnect(); + } + } + +} diff --git a/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot/src/main/java/org/wso2/carbon/device/mgt/iot/util/IoTUtil.java b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot/src/main/java/org/wso2/carbon/device/mgt/iot/util/IoTUtil.java new file mode 100644 index 0000000000..76e375e274 --- /dev/null +++ b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot/src/main/java/org/wso2/carbon/device/mgt/iot/util/IoTUtil.java @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2016, 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.wso2.carbon.device.mgt.iot.util; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.http.HttpResponse; +import org.apache.http.client.HttpClient; +import org.apache.http.conn.scheme.PlainSocketFactory; +import org.apache.http.conn.scheme.Scheme; +import org.apache.http.conn.scheme.SchemeRegistry; +import org.apache.http.conn.ssl.SSLSocketFactory; +import org.apache.http.impl.client.DefaultHttpClient; +import org.apache.http.impl.conn.PoolingClientConnectionManager; +import org.apache.http.params.BasicHttpParams; +import org.apache.http.params.HttpParams; +import org.apache.http.util.EntityUtils; +import org.wso2.carbon.base.ServerConfiguration; +import org.wso2.carbon.device.mgt.iot.exception.IoTException; +import org.wso2.carbon.device.mgt.iot.internal.IoTCommonDataHolder; +import org.wso2.carbon.utils.NetworkUtils; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.SocketException; +import java.security.KeyManagementException; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.UnrecoverableKeyException; + +public class IoTUtil { + + public static final String HOST_NAME = "HostName"; + private static final Log log = LogFactory.getLog(IoTUtil.class); + + /** + * Return a http client instance + * + * @param port - server port + * @param protocol- service endpoint protocol http/https + * @return + */ + public static HttpClient getHttpClient(int port, String protocol) + throws UnrecoverableKeyException, NoSuchAlgorithmException, KeyStoreException, + KeyManagementException { + SchemeRegistry registry = new SchemeRegistry(); + + if ("https".equals(protocol)) { + System.setProperty("javax.net.ssl.trustStrore", IoTCommonDataHolder.getInstance().getTrustStoreLocation()); + System.setProperty("javax.net.ssl.trustStorePassword", + IoTCommonDataHolder.getInstance().getTrustStorePassword()); + + if (port >= 0) { + registry.register(new Scheme("https", port, SSLSocketFactory.getSocketFactory())); + } else { + registry.register(new Scheme("https", 443, SSLSocketFactory.getSocketFactory())); + } + } else if ("http".equals(protocol)) { + if (port >= 0) { + registry.register(new Scheme("http", port, PlainSocketFactory.getSocketFactory())); + } else { + registry.register(new Scheme("http", 80, PlainSocketFactory.getSocketFactory())); + } + } + HttpParams params = new BasicHttpParams(); + PoolingClientConnectionManager tcm = new PoolingClientConnectionManager(registry); + HttpClient client = new DefaultHttpClient(tcm, params); + return client; + } + + public static String getResponseString(HttpResponse httpResponse) throws IoTException { + BufferedReader br = null; + try { + br = new BufferedReader(new InputStreamReader(httpResponse.getEntity().getContent())); + String readLine; + String response = ""; + while (((readLine = br.readLine()) != null)) { + response += readLine; + } + return response; + } catch (IOException e) { + throw new IoTException("Error while reading the response from the remote. " + + e.getMessage(), e); + } finally { + EntityUtils.consumeQuietly(httpResponse.getEntity()); + if (br != null) { + try { + br.close(); + } catch (IOException e) { + log.warn("Error while closing the connection! " + e.getMessage()); + } + } + } + } + + public static String getHostName() throws IoTException { + String hostName = ServerConfiguration.getInstance().getFirstProperty(HOST_NAME); + + try { + if (hostName == null) { + hostName = NetworkUtils.getLocalHostname(); + } + } catch (SocketException e) { + throw new IoTException("Error while trying to read hostname.", e); + } + + return hostName; + } + +} diff --git a/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot/src/main/java/org/wso2/carbon/device/mgt/iot/util/IotDeviceManagementUtil.java b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot/src/main/java/org/wso2/carbon/device/mgt/iot/util/IotDeviceManagementUtil.java new file mode 100644 index 0000000000..f1d14ff76c --- /dev/null +++ b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot/src/main/java/org/wso2/carbon/device/mgt/iot/util/IotDeviceManagementUtil.java @@ -0,0 +1,346 @@ +/* + * 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.wso2.carbon.device.mgt.iot.util; + +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.IOUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.w3c.dom.Document; +import org.wso2.carbon.device.mgt.common.Device; +import org.wso2.carbon.device.mgt.common.DeviceManagementException; +import org.wso2.carbon.utils.CarbonUtils; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import java.io.*; +import java.nio.charset.StandardCharsets; +import java.util.*; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; + +/** + * Provides utility methods required by the iot device management bundle. + */ +public class IotDeviceManagementUtil { + + private static final Log log = LogFactory.getLog(IotDeviceManagementUtil.class.getName()); + + public static Document convertToDocument(File file) throws DeviceManagementException { + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + factory.setNamespaceAware(true); + try { + DocumentBuilder docBuilder = factory.newDocumentBuilder(); + return docBuilder.parse(file); + } catch (Exception e) { + throw new DeviceManagementException("Error occurred while parsing file, while converting " + + "to a org.w3c.dom.Document : " + e.getMessage(), e); + } + } + + private static Device.Property getProperty(String property, String value) { + if (property != null) { + Device.Property prop = new Device.Property(); + prop.setName(property); + prop.setValue(value); + return prop; + } + return null; + } + + public static ZipArchive getSketchArchive(String archivesPath, String templateSketchPath, Map contextParams) + throws DeviceManagementException, IOException { + + String sep = File.separator; + String sketchPath = CarbonUtils.getCarbonHome() + sep + templateSketchPath; + + FileUtils.deleteDirectory(new File(archivesPath));//clear directory + FileUtils.deleteDirectory(new File(archivesPath + ".zip"));//clear zip + if (!new File(archivesPath).mkdirs()) { //new dir + String message = "Could not create directory at path: " + archivesPath; + log.error(message); + throw new DeviceManagementException(message); + } + + String zipFileName = "zipFile.zip"; + + try { + Map> properties = getProperties(sketchPath + sep + "sketch" + ".properties"); + List templateFiles = properties.get("templates"); + +// zipFileName = properties.get("zipfilename").get(0); + zipFileName = contextParams.get("DEVICE_NAME") + ".zip"; + + for (String templateFile : templateFiles) { + parseTemplate(templateSketchPath + sep + templateFile, archivesPath + sep + templateFile, + contextParams); + } + + templateFiles.add("sketch.properties"); // ommit copying the props file + copyFolder(new File(sketchPath), new File(archivesPath), templateFiles); + + } catch (IOException ex) { + throw new DeviceManagementException( + "Error occurred when trying to read property " + "file sketch.properties", ex); + } + + try { + createZipArchive(archivesPath); + } catch (IOException e) { + String message = "Zip file for the specific device agent not found at path: " + archivesPath; + log.error(message); + log.error(e); + throw new DeviceManagementException(message, e); + } + FileUtils.deleteDirectory(new File(archivesPath));//clear folder + + /* now get the zip file */ + File zip = new File(archivesPath + ".zip"); + return new ZipArchive(zipFileName, zip); + } + + private static Map> getProperties(String propertyFilePath) throws IOException { + Properties prop = new Properties(); + InputStream input = null; + + try { + + input = new FileInputStream(propertyFilePath); + + // load a properties file + prop.load(input); + Map> properties = new HashMap>(); + + String templates = prop.getProperty("templates"); + List list = new ArrayList(Arrays.asList(templates.split(","))); + properties.put("templates", list); + + final String filename = prop.getProperty("zipfilename"); + list = new ArrayList() {{ + add(filename); + }}; + properties.put("zipfilename", list); + return properties; + + } finally { + if (input != null) { + try { + input.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + } + + private static void parseTemplate(String srcFile, String dstFile, Map contextParams) throws IOException { + //TODO add velocity 1.7, currently commented + //TODO conflicting when calling in CXF environment with the opensaml orbit + + // /* create a context and add data */ + // VelocityContext context = new VelocityContext(contextParams); + // + // /* first, get and initialize an engine */ + // VelocityEngine ve = new VelocityEngine(); + // ve.setProperty( RuntimeConstants.RUNTIME_LOG_LOGSYSTEM_CLASS, + // "org.apache.velocity.runtime.log.Log4JLogChute" ); + // ve.setProperty("runtime.log.logsystem.log4j.logger", IotDeviceManagementUtil.class.getName()); + // ve.init(); + // + // String sep = File.separator; + // Template t = ve.getTemplate(srcFile); + // FileWriter writer = null; + // try { + // writer = new FileWriter(dstFile); + // t.merge(context, writer); + // } finally { + // if (writer != null) { + // writer.flush(); + // writer.close(); + // } + // } + + //read from file + FileInputStream inputStream = new FileInputStream(srcFile); + String content = IOUtils.toString(inputStream, StandardCharsets.UTF_8.toString()); + Iterator iterator = contextParams.entrySet().iterator(); + while (iterator.hasNext()) { + Map.Entry mapEntry = (Map.Entry) iterator.next(); + content = content.replaceAll("\\$\\{" + mapEntry.getKey() + "\\}", mapEntry.getValue().toString()); + } + if (inputStream != null) { + inputStream.close(); + } + //write to file + FileOutputStream outputStream = new FileOutputStream(dstFile); + IOUtils.write(content, outputStream, StandardCharsets.UTF_8.toString()); + if (outputStream != null) { + outputStream.close(); + } + } + + private static void copyFolder(File src, File dest, List excludeFileNames) throws IOException { + + if (src.isDirectory()) { + + //if directory not exists, create it + if (!dest.exists() && !dest.mkdirs()) { + String message = "Could not create directory at path: " + dest; + log.error(message); + throw new IOException(message); + } + + //list all the directory contents + String files[] = src.list(); + + if (files == null) { + log.warn("There are no files insides the directory " + src.getAbsolutePath()); + return; + } + + for (String file : files) { + //construct the src and dest file structure + File srcFile = new File(src, file); + File destFile = new File(dest, file); + //recursive copy + copyFolder(srcFile, destFile, excludeFileNames); + } + + } else { + for (String fileName : excludeFileNames) { + if (src.getName().equals(fileName)) { + return; + } + } + //if file, then copy it + //Use bytes stream to support all file types + InputStream in = null; + OutputStream out = null; + + try { + in = new FileInputStream(src); + out = new FileOutputStream(dest); + + byte[] buffer = new byte[1024]; + + int length; + //copy the file content in bytes + while ((length = in.read(buffer)) > 0) { + out.write(buffer, 0, length); + } + } finally { + silentClose(in); + silentClose(out); + } + } + } + + private static void silentClose(InputStream is) { + if (is == null) { + return; + } + + try { + is.close(); + } catch (IOException e) { + // do nothing + } + + } + + private static void silentClose(OutputStream os) { + if (os == null) { + return; + } + + try { + + os.close(); + } catch (IOException e) { + // do nothing + } + } + + private static boolean createZipArchive(String srcFolder) throws IOException { + BufferedInputStream origin = null; + ZipOutputStream out = null; + + try { + final int BUFFER = 2048; + + FileOutputStream dest = new FileOutputStream(new File(srcFolder + ".zip")); + + out = new ZipOutputStream(new BufferedOutputStream(dest)); + byte data[] = new byte[BUFFER]; + + File subDir = new File(srcFolder); + String subdirList[] = subDir.list(); + + if (subdirList == null) { + log.warn("The sub directory " + subDir.getAbsolutePath() + " is empty"); + return false; + } + + for (String sd : subdirList) { + // get a list of files from current directory + File f = new File(srcFolder + "/" + sd); + if (f.isDirectory()) { + String files[] = f.list(); + + if (files == null) { + log.warn("The current directory " + f.getAbsolutePath() + " is empty. Has no files"); + return false; + } + + for (int i = 0; i < files.length; i++) { + FileInputStream fi = new FileInputStream(srcFolder + "/" + sd + "/" + files[i]); + origin = new BufferedInputStream(fi, BUFFER); + ZipEntry entry = new ZipEntry(sd + "/" + files[i]); + out.putNextEntry(entry); + int count; + while ((count = origin.read(data, 0, BUFFER)) != -1) { + out.write(data, 0, count); + out.flush(); + } + + } + } else //it is just a file + { + FileInputStream fi = new FileInputStream(f); + origin = new BufferedInputStream(fi, BUFFER); + ZipEntry entry = new ZipEntry(sd); + out.putNextEntry(entry); + int count; + while ((count = origin.read(data, 0, BUFFER)) != -1) { + out.write(data, 0, count); + out.flush(); + } + + } + } + + out.flush(); + } finally { + silentClose(origin); + silentClose(out); + } + return true; + } + +} diff --git a/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot/src/main/java/org/wso2/carbon/device/mgt/iot/util/ZipArchive.java b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot/src/main/java/org/wso2/carbon/device/mgt/iot/util/ZipArchive.java new file mode 100644 index 0000000000..b9720d112b --- /dev/null +++ b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot/src/main/java/org/wso2/carbon/device/mgt/iot/util/ZipArchive.java @@ -0,0 +1,49 @@ +/* + * 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.wso2.carbon.device.mgt.iot.util; + +import java.io.File; + +public class ZipArchive { + + private File zipFile = null; + private String fileName = null; + private String deviceId = null; + + public ZipArchive(String fileName, File zipFile) { + this.fileName = fileName; + this.zipFile = zipFile; + } + + public String getDeviceId() { + return deviceId; + } + + public void setDeviceId(String deviceId) { + this.deviceId = deviceId; + } + + public File getZipFile() { + return zipFile; + } + + public String getFileName() { + return fileName; + } +} diff --git a/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot/src/main/java/org/wso2/carbon/device/mgt/iot/util/ZipUtil.java b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot/src/main/java/org/wso2/carbon/device/mgt/iot/util/ZipUtil.java new file mode 100644 index 0000000000..8ea3396d64 --- /dev/null +++ b/components/iot-plugins/iot-base-plugin/org.wso2.carbon.device.mgt.iot/src/main/java/org/wso2/carbon/device/mgt/iot/util/ZipUtil.java @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2016, 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.wso2.carbon.device.mgt.iot.util; + +import org.wso2.carbon.device.mgt.common.DeviceManagementException; +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.controlqueue.xmpp.XmppConfig; +import org.wso2.carbon.device.mgt.iot.exception.IoTException; +import org.wso2.carbon.utils.CarbonUtils; +import java.io.File; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +public class ZipUtil { + + private static final String HTTPS_PORT_PROPERTY = "httpsPort"; + private static final String HTTP_PORT_PROPERTY = "httpPort"; + + private static final String LOCALHOST = "localhost"; + private static final String HTTPS_PROTOCOL_APPENDER = "https://"; + private static final String HTTP_PROTOCOL_APPENDER = "http://"; + + public ZipArchive createZipFile(String owner, String tenantDomain, String deviceType, + String deviceId, String deviceName, String token, + String refreshToken) + throws DeviceManagementException { + + String sep = File.separator; + String sketchFolder = "repository" + sep + "resources" + sep + "sketches"; + String archivesPath = CarbonUtils.getCarbonHome() + sep + sketchFolder + sep + "archives" + sep + deviceId; + String templateSketchPath = sketchFolder + sep + deviceType; + String iotServerIP; + + try { + iotServerIP = IoTUtil.getHostName(); + } catch (IoTException e) { + throw new DeviceManagementException(e.getMessage()); + } + String httpsServerPort = System.getProperty(HTTPS_PORT_PROPERTY); + String httpServerPort = System.getProperty(HTTP_PORT_PROPERTY); + + String httpsServerEP = HTTPS_PROTOCOL_APPENDER + iotServerIP + ":" + httpsServerPort; + String httpServerEP = HTTP_PROTOCOL_APPENDER + iotServerIP + ":" + httpServerPort; + String apimEndpoint = httpsServerEP; + String mqttEndpoint = MqttConfig.getInstance().getMqttQueueEndpoint(); + if (mqttEndpoint.contains(LOCALHOST)) { + mqttEndpoint = mqttEndpoint.replace(LOCALHOST, iotServerIP); + } + + String xmppEndpoint = XmppConfig.getInstance().getXmppEndpoint(); + + int indexOfChar = xmppEndpoint.lastIndexOf(":"); + if (indexOfChar != -1) { + xmppEndpoint = xmppEndpoint.substring(0, indexOfChar); + } + + xmppEndpoint = xmppEndpoint + ":" + XmppConfig.getInstance().getSERVER_CONNECTION_PORT(); + + Map contextParams = new HashMap<>(); + //TODO:refactor remove and move to device type impl + contextParams.put("SERVER_NAME", "wso2"); + contextParams.put("DEVICE_OWNER", owner); + contextParams.put("DEVICE_ID", deviceId); + contextParams.put("DEVICE_NAME", deviceName); + contextParams.put("HTTPS_EP", httpsServerEP); + contextParams.put("HTTP_EP", httpServerEP); + contextParams.put("APIM_EP", apimEndpoint); + contextParams.put("MQTT_EP", mqttEndpoint); + contextParams.put("XMPP_EP", xmppEndpoint); + contextParams.put("DEVICE_TOKEN", token); + contextParams.put("DEVICE_REFRESH_TOKEN", refreshToken); + + ZipArchive zipFile; + try { + zipFile = IotDeviceManagementUtil.getSketchArchive(archivesPath, templateSketchPath, contextParams); + } catch (IOException e) { + throw new DeviceManagementException("Zip File Creation Failed", e); + } + + return zipFile; + } +} diff --git a/components/iot-plugins/iot-base-plugin/pom.xml b/components/iot-plugins/iot-base-plugin/pom.xml new file mode 100644 index 0000000000..759eb40776 --- /dev/null +++ b/components/iot-plugins/iot-base-plugin/pom.xml @@ -0,0 +1,60 @@ + + + + + + + org.wso2.carbon.devicemgt-plugins + iot-plugins + 2.1.0-SNAPSHOT + ../pom.xml + + + 4.0.0 + iot-base-plugin + pom + WSO2 Carbon - Arduino Plugin + http://wso2.org + + + org.wso2.carbon.device.mgt.iot + org.wso2.carbon.device.mgt.iot.api + org.wso2.carbon.device.mgt.iot.ui + + + + + + + org.apache.felix + maven-scr-plugin + 1.7.2 + + + generate-scr-scrdescriptor + + scr + + + + + + + + diff --git a/components/iot-plugins/pom.xml b/components/iot-plugins/pom.xml new file mode 100644 index 0000000000..0020ee7f1f --- /dev/null +++ b/components/iot-plugins/pom.xml @@ -0,0 +1,67 @@ + + + + + + + org.wso2.carbon.devicemgt-plugins + carbon-device-mgt-plugins-parent + 2.1.0-SNAPSHOT + ../../pom.xml + + + 4.0.0 + org.wso2.carbon.devicemgt-plugins + iot-plugins + pom + WSO2 Carbon - IoT Plugins + http://wso2.org + + + androidsense-plugin + arduino-plugin + camera-plugin + digital-display-plugin + drone-analyzer-plugin + raspberrypi-plugin + virtual-fire-alarm-plugin + iot-base-plugin + + + + + + + org.apache.felix + maven-scr-plugin + 1.7.2 + + + generate-scr-scrdescriptor + + scr + + + + + + + + + diff --git a/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.analytics/build.xml b/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.analytics/build.xml new file mode 100644 index 0000000000..a877b11413 --- /dev/null +++ b/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.analytics/build.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.analytics/pom.xml b/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.analytics/pom.xml new file mode 100644 index 0000000000..cfe0fb2a27 --- /dev/null +++ b/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.analytics/pom.xml @@ -0,0 +1,92 @@ + + + + + + + + raspberrypi-plugin + org.wso2.carbon.devicemgt-plugins + 2.1.0-SNAPSHOT + ../pom.xml + + + 4.0.0 + org.wso2.carbon.device.mgt.iot.raspberrypi.analytics + WSO2 Carbon - IoT Server RaspberryPi Analytics capp + pom + + + + + maven-clean-plugin + 2.4.1 + + + auto-clean + initialize + + clean + + + + + + maven-antrun-plugin + 1.7 + + + process-resources + + + + + + + run + + + + + + maven-assembly-plugin + 2.5.5 + + ${project.artifactId}-${carbon.device.mgt.version} + false + + src/assembly/src.xml + + + + + create-archive + package + + single + + + + + + + + \ No newline at end of file diff --git a/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.analytics/src/assembly/src.xml b/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.analytics/src/assembly/src.xml new file mode 100644 index 0000000000..a5a375010e --- /dev/null +++ b/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.analytics/src/assembly/src.xml @@ -0,0 +1,36 @@ + + + + src + + zip + + false + ${basedir}/src + + + ${basedir}/target/carbonapps + / + true + + + \ No newline at end of file diff --git a/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.analytics/src/main/resources/carbonapps/Temperature_Sensor/Eventreceiver_temperature_1.0.0/EventReceiver_temperature.xml b/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.analytics/src/main/resources/carbonapps/Temperature_Sensor/Eventreceiver_temperature_1.0.0/EventReceiver_temperature.xml new file mode 100644 index 0000000000..28b710c27f --- /dev/null +++ b/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.analytics/src/main/resources/carbonapps/Temperature_Sensor/Eventreceiver_temperature_1.0.0/EventReceiver_temperature.xml @@ -0,0 +1,26 @@ + + + + + + false + + + + diff --git a/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.analytics/src/main/resources/carbonapps/Temperature_Sensor/Eventreceiver_temperature_1.0.0/artifact.xml b/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.analytics/src/main/resources/carbonapps/Temperature_Sensor/Eventreceiver_temperature_1.0.0/artifact.xml new file mode 100644 index 0000000000..25df56734b --- /dev/null +++ b/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.analytics/src/main/resources/carbonapps/Temperature_Sensor/Eventreceiver_temperature_1.0.0/artifact.xml @@ -0,0 +1,22 @@ + + + + + EventReceiver_temperature.xml + diff --git a/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.analytics/src/main/resources/carbonapps/Temperature_Sensor/Eventstore_temperature_1.0.0/artifact.xml b/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.analytics/src/main/resources/carbonapps/Temperature_Sensor/Eventstore_temperature_1.0.0/artifact.xml new file mode 100644 index 0000000000..ccfb3b3140 --- /dev/null +++ b/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.analytics/src/main/resources/carbonapps/Temperature_Sensor/Eventstore_temperature_1.0.0/artifact.xml @@ -0,0 +1,22 @@ + + + + + org_wso2_iot_devices_temperature.xml + diff --git a/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.analytics/src/main/resources/carbonapps/Temperature_Sensor/Eventstore_temperature_1.0.0/org_wso2_iot_devices_temperature.xml b/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.analytics/src/main/resources/carbonapps/Temperature_Sensor/Eventstore_temperature_1.0.0/org_wso2_iot_devices_temperature.xml new file mode 100644 index 0000000000..d06f73b14e --- /dev/null +++ b/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.analytics/src/main/resources/carbonapps/Temperature_Sensor/Eventstore_temperature_1.0.0/org_wso2_iot_devices_temperature.xml @@ -0,0 +1,62 @@ + + + + + + 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/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.analytics/src/main/resources/carbonapps/Temperature_Sensor/Eventstream_temperature_1.0.0/artifact.xml b/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.analytics/src/main/resources/carbonapps/Temperature_Sensor/Eventstream_temperature_1.0.0/artifact.xml new file mode 100644 index 0000000000..27ec69702e --- /dev/null +++ b/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.analytics/src/main/resources/carbonapps/Temperature_Sensor/Eventstream_temperature_1.0.0/artifact.xml @@ -0,0 +1,23 @@ + + + + + org.wso2.iot.devices.temperature_1.0.0.json + + diff --git a/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.analytics/src/main/resources/carbonapps/Temperature_Sensor/Eventstream_temperature_1.0.0/org.wso2.iot.devices.temperature_1.0.0.json b/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.analytics/src/main/resources/carbonapps/Temperature_Sensor/Eventstream_temperature_1.0.0/org.wso2.iot.devices.temperature_1.0.0.json new file mode 100644 index 0000000000..5d94b9821b --- /dev/null +++ b/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.analytics/src/main/resources/carbonapps/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/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.analytics/src/main/resources/carbonapps/Temperature_Sensor/Sparkscripts_1.0.0/Temperature_Sensor_Script.xml b/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.analytics/src/main/resources/carbonapps/Temperature_Sensor/Sparkscripts_1.0.0/Temperature_Sensor_Script.xml new file mode 100644 index 0000000000..41938dd4ff --- /dev/null +++ b/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.analytics/src/main/resources/carbonapps/Temperature_Sensor/Sparkscripts_1.0.0/Temperature_Sensor_Script.xml @@ -0,0 +1,31 @@ + + + + + IoTServer_Sensor_Script + + 0 * * * * ? + diff --git a/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.analytics/src/main/resources/carbonapps/Temperature_Sensor/Sparkscripts_1.0.0/artifact.xml b/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.analytics/src/main/resources/carbonapps/Temperature_Sensor/Sparkscripts_1.0.0/artifact.xml new file mode 100644 index 0000000000..9b4228e30c --- /dev/null +++ b/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.analytics/src/main/resources/carbonapps/Temperature_Sensor/Sparkscripts_1.0.0/artifact.xml @@ -0,0 +1,22 @@ + + + + + Temperature_Sensor_Script.xml + diff --git a/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.analytics/src/main/resources/carbonapps/Temperature_Sensor/artifacts.xml b/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.analytics/src/main/resources/carbonapps/Temperature_Sensor/artifacts.xml new file mode 100644 index 0000000000..c4580f909d --- /dev/null +++ b/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.analytics/src/main/resources/carbonapps/Temperature_Sensor/artifacts.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + diff --git a/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.controller.service.impl/pom.xml b/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.controller.service.impl/pom.xml new file mode 100644 index 0000000000..4173e23e18 --- /dev/null +++ b/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.controller.service.impl/pom.xml @@ -0,0 +1,158 @@ + + + + + + + raspberrypi-plugin + org.wso2.carbon.devicemgt-plugins + 2.1.0-SNAPSHOT + ../pom.xml + + + 4.0.0 + org.wso2.carbon.device.mgt.iot.raspberrypi.controller.service.impl + war + WSO2 Carbon - IoT Server RaspberryPi Controller API + WSO2 Carbon - RaspberryPi Controller Service API Implementation + http://wso2.org + + + + + org.wso2.carbon.devicemgt + org.wso2.carbon.device.mgt.common + provided + + + org.wso2.carbon.devicemgt + org.wso2.carbon.device.mgt.core + provided + + + org.apache.axis2.wso2 + axis2-client + + + + + + + org.apache.cxf + cxf-rt-frontend-jaxws + provided + + + org.apache.cxf + cxf-rt-frontend-jaxrs + provided + + + org.apache.cxf + cxf-rt-transports-http + provided + + + + + org.eclipse.paho + org.eclipse.paho.client.mqttv3 + provided + + + + + org.apache.httpcomponents + httpasyncclient + 4.1 + + + org.wso2.carbon.devicemgt-plugins + org.wso2.carbon.device.mgt.iot + provided + + + org.wso2.carbon.devicemgt-plugins + org.wso2.carbon.device.mgt.iot.raspberrypi.plugin.impl + provided + + + + + org.codehaus.jackson + jackson-core-asl + + + org.codehaus.jackson + jackson-jaxrs + + + javax + javaee-web-api + provided + + + javax.ws.rs + jsr311-api + provided + + + + org.wso2.carbon.devicemgt + org.wso2.carbon.device.mgt.analytics + provided + + + org.wso2.carbon.devicemgt + org.wso2.carbon.apimgt.annotations + provided + + + + org.wso2.carbon.devicemgt + org.wso2.carbon.device.mgt.extensions + provided + + + + + + + + + + maven-compiler-plugin + + UTF-8 + ${wso2.maven.compiler.source} + ${wso2.maven.compiler.target} + + + + maven-war-plugin + + raspberrypi + + + + + + \ No newline at end of file diff --git a/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.controller.service.impl/src/main/java/org/wso2/carbon/device/mgt/iot/raspberrypi/controller/service/impl/RaspberryPiControllerService.java b/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.controller.service.impl/src/main/java/org/wso2/carbon/device/mgt/iot/raspberrypi/controller/service/impl/RaspberryPiControllerService.java new file mode 100644 index 0000000000..1af824749f --- /dev/null +++ b/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.controller.service.impl/src/main/java/org/wso2/carbon/device/mgt/iot/raspberrypi/controller/service/impl/RaspberryPiControllerService.java @@ -0,0 +1,295 @@ +/* + * Copyright (c) 2016, 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.wso2.carbon.device.mgt.iot.raspberrypi.controller.service.impl; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.apimgt.annotations.api.API; +import org.wso2.carbon.context.PrivilegedCarbonContext; +import org.wso2.carbon.device.mgt.analytics.common.AnalyticsDataRecord; +import org.wso2.carbon.device.mgt.analytics.exception.DeviceManagementAnalyticsException; +import org.wso2.carbon.device.mgt.analytics.service.DeviceAnalyticsService; +import org.wso2.carbon.device.mgt.common.DeviceManagementException; +import org.wso2.carbon.device.mgt.extensions.feature.mgt.annotations.DeviceType; +import org.wso2.carbon.device.mgt.extensions.feature.mgt.annotations.Feature; +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.raspberrypi.controller.service.impl.dto.DeviceData; +import org.wso2.carbon.device.mgt.iot.raspberrypi.controller.service.impl.dto.SensorData; +import org.wso2.carbon.device.mgt.iot.raspberrypi.controller.service.impl.transport.RaspberryPiMQTTConnector; +import org.wso2.carbon.device.mgt.iot.raspberrypi.controller.service.impl.util.RaspberrypiServiceUtils; +import org.wso2.carbon.device.mgt.iot.raspberrypi.plugin.constants.RaspberrypiConstants; +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.service.IoTServerStartupListener; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.ws.rs.Consumes; +import javax.ws.rs.FormParam; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +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.MediaType; +import javax.ws.rs.core.Response; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; + +@API(name = "raspberrypi", version = "1.0.0", context = "/raspberrypi", tags = {"raspberrypi"}) +@DeviceType(value = "raspberrypi") +public class RaspberryPiControllerService { + + private static Log log = LogFactory.getLog(RaspberryPiControllerService.class); + @Context //injected response proxy supporting multiple thread + private HttpServletResponse response; + private ConcurrentHashMap deviceToIpMap = new ConcurrentHashMap<>(); + private RaspberryPiMQTTConnector raspberryPiMQTTConnector; + + private boolean waitForServerStartup() { + while (!IoTServerStartupListener.isServerReady()) { + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + return true; + } + } + return false; + } + + public RaspberryPiMQTTConnector getRaspberryPiMQTTConnector() { + return raspberryPiMQTTConnector; + } + + public void setRaspberryPiMQTTConnector( + final RaspberryPiMQTTConnector raspberryPiMQTTConnector) { + Runnable connector = new Runnable() { + public void run() { + if (waitForServerStartup()) { + return; + } + RaspberryPiControllerService.this.raspberryPiMQTTConnector = raspberryPiMQTTConnector; + if (MqttConfig.getInstance().isEnabled()) { + raspberryPiMQTTConnector.connect(); + } else { + log.warn("MQTT disabled in 'devicemgt-config.xml'. Hence, RaspberryPiMQTTConnector not started."); + } + } + }; + Thread connectorThread = new Thread(connector); + connectorThread.setDaemon(true); + connectorThread.start(); + } + + @Path("controller/register/{deviceId}/{ip}/{port}") + @POST + public String registerDeviceIP(@PathParam("deviceId") String deviceId, @PathParam("ip") String deviceIP, + @PathParam("port") String devicePort, @Context HttpServletResponse response, + @Context HttpServletRequest request) { + try { + String result; + if (log.isDebugEnabled()) { + log.debug("Got register call from IP: " + deviceIP + " for Device ID: " + deviceId); + } + String deviceHttpEndpoint = deviceIP + ":" + devicePort; + deviceToIpMap.put(deviceId, deviceHttpEndpoint); + result = "Device-IP Registered"; + response.setStatus(Response.Status.OK.getStatusCode()); + if (log.isDebugEnabled()) { + log.debug(result); + } + return result; + } finally { + PrivilegedCarbonContext.endTenantFlow(); + } + } + + + @Path("controller/device/{deviceId}/bulb") + @POST + @Feature(code = "bulb", name = "Bulb On / Off", type = "operation", + description = "Switch on/off Raspberry Pi agent's bulb. (On / Off)") + public void switchBulb(@PathParam("deviceId") String deviceId, @FormParam("state") String state, + @Context HttpServletResponse response) { + try { + String switchToState = state.toUpperCase(); + if (!switchToState.equals(RaspberrypiConstants.STATE_ON) && !switchToState.equals( + RaspberrypiConstants.STATE_OFF)) { + log.error("The requested state change shoud be either - 'ON' or 'OFF'"); + response.setStatus(Response.Status.BAD_REQUEST.getStatusCode()); + return; + } + String callUrlPattern = RaspberrypiConstants.BULB_CONTEXT + switchToState; + try { + String deviceHTTPEndpoint = deviceToIpMap.get(deviceId); + if (deviceHTTPEndpoint == null) { + response.setStatus(Response.Status.PRECONDITION_FAILED.getStatusCode()); + return; + } + RaspberrypiServiceUtils.sendCommandViaHTTP(deviceHTTPEndpoint, callUrlPattern, true); + } catch (DeviceManagementException e) { + log.error("Failed to send switch-bulb request to device [" + deviceId + "] via "); + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + return; + } + response.setStatus(Response.Status.OK.getStatusCode()); + } finally { + PrivilegedCarbonContext.endTenantFlow(); + } + } + + @Path("controller/device/{deviceId}/readtemperature") + @GET + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + @Feature(code = "readtemperature", name = "Temperature", type = "monitor", + description = "Request temperature reading from Raspberry Pi agent") + public SensorRecord requestTemperature(@PathParam("deviceId") String deviceId, + @Context HttpServletResponse response) { + try { + SensorRecord sensorRecord = null; + if (log.isDebugEnabled()) { + log.debug("Sending request to read raspberrypi-temperature of device [" + deviceId + "] via "); + } + try { + String deviceHTTPEndpoint = deviceToIpMap.get(deviceId); + if (deviceHTTPEndpoint == null) { + response.setStatus(Response.Status.PRECONDITION_FAILED.getStatusCode()); + } + String temperatureValue = RaspberrypiServiceUtils.sendCommandViaHTTP(deviceHTTPEndpoint, + RaspberrypiConstants + .TEMPERATURE_CONTEXT, + + false); + SensorDataManager.getInstance().setSensorRecord(deviceId, RaspberrypiConstants.SENSOR_TEMPERATURE, + temperatureValue, Calendar.getInstance() + .getTimeInMillis + ()); + sensorRecord = SensorDataManager.getInstance().getSensorRecord(deviceId, + RaspberrypiConstants + .SENSOR_TEMPERATURE); + } catch (DeviceManagementException | DeviceControllerException e) { + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + } + response.setStatus(Response.Status.OK.getStatusCode()); + return sensorRecord; + } finally { + PrivilegedCarbonContext.endTenantFlow(); + } + } + + @Path("controller/push_temperature") + @POST + @Consumes(MediaType.APPLICATION_JSON) + public void pushTemperatureData(final DeviceData dataMsg, @Context HttpServletResponse response, + @Context HttpServletRequest request) { + try { + String owner = dataMsg.owner; + String deviceId = dataMsg.deviceId; + String deviceIp = dataMsg.reply; + float temperature = dataMsg.value; + String registeredIp = deviceToIpMap.get(deviceId); + if (registeredIp == null) { + log.warn("Unregistered IP: Temperature Data Received from an un-registered IP " + deviceIp + + " for device ID - " + deviceId); + response.setStatus(Response.Status.PRECONDITION_FAILED.getStatusCode()); + return; + } else if (!registeredIp.equals(deviceIp)) { + log.warn("Conflicting IP: Received IP is " + deviceIp + ". Device with ID " + deviceId + + " is already registered under some other IP. Re-registration required"); + response.setStatus(Response.Status.CONFLICT.getStatusCode()); + return; + } + if (log.isDebugEnabled()) { + log.debug("Received Pin Data Value: " + temperature + " degrees C"); + } + SensorDataManager.getInstance().setSensorRecord(deviceId, RaspberrypiConstants.SENSOR_TEMPERATURE, + String.valueOf(temperature), + Calendar.getInstance().getTimeInMillis()); + if (!RaspberrypiServiceUtils.publishToDAS(dataMsg.deviceId, dataMsg.value)) { + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + log.warn("An error occured whilst trying to publish temperature data of raspberrypi with ID [" + + deviceId + "] of owner [" + owner + "]"); + } + } finally { + PrivilegedCarbonContext.endTenantFlow(); + } + } + + /** + * Retreive Sensor data for the device type + */ + @Path("controller/stats/device/{deviceId}/sensors/temperature") + @GET + @Consumes("application/json") + @Produces("application/json") + public SensorData[] getArduinoTemperatureStats(@PathParam("deviceId") String deviceId, + @QueryParam("username") String user, + @QueryParam("from") long from, + @QueryParam("to") long to) { + + String fromDate = String.valueOf(from); + String toDate = String.valueOf(to); + List sensorDatas = new ArrayList<>(); + PrivilegedCarbonContext ctx = PrivilegedCarbonContext.getThreadLocalCarbonContext(); + DeviceAnalyticsService deviceAnalyticsService = (DeviceAnalyticsService) ctx + .getOSGiService(DeviceAnalyticsService.class, null); + String query = "owner:" + user + " AND deviceId:" + deviceId + " AND deviceType:" + + RaspberrypiConstants.DEVICE_TYPE + " AND time : [" + fromDate + " TO " + toDate + "]"; + String sensorTableName = RaspberrypiConstants.TEMPERATURE_EVENT_TABLE; + try { + List records = deviceAnalyticsService.getAllEventsForDevice(sensorTableName, query); + Collections.sort(records, new Comparator() { + @Override + public int compare(AnalyticsDataRecord o1, AnalyticsDataRecord o2) { + long t1 = (Long) o1.getValue("time"); + long t2 = (Long) o2.getValue("time"); + if (t1 < t2) { + return -1; + } else if (t1 > t2) { + return 1; + } else { + return 0; + } + } + }); + for (AnalyticsDataRecord record : records) { + SensorData sensorData = new SensorData(); + sensorData.setTime((long) record.getValue("time")); + sensorData.setValue("" + (float) record.getValue(RaspberrypiConstants.SENSOR_TEMPERATURE)); + sensorDatas.add(sensorData); + } + return sensorDatas.toArray(new SensorData[sensorDatas.size()]); + } catch (DeviceManagementAnalyticsException e) { + String errorMsg = "Error on retrieving stats on table " + sensorTableName + " with query " + query; + log.error(errorMsg); + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + return sensorDatas.toArray(new SensorData[sensorDatas.size()]); + } finally { + PrivilegedCarbonContext.endTenantFlow(); + } + } +} diff --git a/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.controller.service.impl/src/main/java/org/wso2/carbon/device/mgt/iot/raspberrypi/controller/service/impl/dto/DeviceData.java b/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.controller.service.impl/src/main/java/org/wso2/carbon/device/mgt/iot/raspberrypi/controller/service/impl/dto/DeviceData.java new file mode 100644 index 0000000000..6482d1e018 --- /dev/null +++ b/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.controller.service.impl/src/main/java/org/wso2/carbon/device/mgt/iot/raspberrypi/controller/service/impl/dto/DeviceData.java @@ -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. + */ + +package org.wso2.carbon.device.mgt.iot.raspberrypi.controller.service.impl.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 DeviceData { + @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/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.controller.service.impl/src/main/java/org/wso2/carbon/device/mgt/iot/raspberrypi/controller/service/impl/dto/SensorData.java b/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.controller.service.impl/src/main/java/org/wso2/carbon/device/mgt/iot/raspberrypi/controller/service/impl/dto/SensorData.java new file mode 100644 index 0000000000..4709b54f6c --- /dev/null +++ b/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.controller.service.impl/src/main/java/org/wso2/carbon/device/mgt/iot/raspberrypi/controller/service/impl/dto/SensorData.java @@ -0,0 +1,44 @@ +package org.wso2.carbon.device.mgt.iot.raspberrypi.controller.service.impl.dto; + +import org.codehaus.jackson.annotate.JsonIgnoreProperties; + +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; + +@XmlRootElement +/** + * This stores sensor event data for the device type. + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class SensorData { + + @XmlElement public Long time; + @XmlElement public String key; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } + + public Long getTime() { + return time; + } + + public void setTime(Long time) { + this.time = time; + } + + @XmlElement public String value; + +} diff --git a/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.controller.service.impl/src/main/java/org/wso2/carbon/device/mgt/iot/raspberrypi/controller/service/impl/exception/RaspberrypiException.java b/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.controller.service.impl/src/main/java/org/wso2/carbon/device/mgt/iot/raspberrypi/controller/service/impl/exception/RaspberrypiException.java new file mode 100644 index 0000000000..26fa4d538f --- /dev/null +++ b/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.controller.service.impl/src/main/java/org/wso2/carbon/device/mgt/iot/raspberrypi/controller/service/impl/exception/RaspberrypiException.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.wso2.carbon.device.mgt.iot.raspberrypi.controller.service.impl.exception; + +public class RaspberrypiException extends Exception { + private static final long serialVersionUID = 118512086957330189L; + + public RaspberrypiException(String errorMessage) { + super(errorMessage); + } + + public RaspberrypiException(String errorMessage, Throwable throwable) { + super(errorMessage, throwable); + } +} diff --git a/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.controller.service.impl/src/main/java/org/wso2/carbon/device/mgt/iot/raspberrypi/controller/service/impl/transport/RaspberryPiMQTTConnector.java b/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.controller.service.impl/src/main/java/org/wso2/carbon/device/mgt/iot/raspberrypi/controller/service/impl/transport/RaspberryPiMQTTConnector.java new file mode 100644 index 0000000000..d00da115b6 --- /dev/null +++ b/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.controller.service.impl/src/main/java/org/wso2/carbon/device/mgt/iot/raspberrypi/controller/service/impl/transport/RaspberryPiMQTTConnector.java @@ -0,0 +1,195 @@ +/* + * 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.wso2.carbon.device.mgt.iot.raspberrypi.controller.service.impl.transport; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.eclipse.paho.client.mqttv3.MqttException; +import org.eclipse.paho.client.mqttv3.MqttMessage; +import org.wso2.carbon.context.PrivilegedCarbonContext; +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.core.service.DeviceManagementProviderService; +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.raspberrypi.controller.service.impl.util.RaspberrypiServiceUtils; +import org.wso2.carbon.device.mgt.iot.raspberrypi.plugin.constants.RaspberrypiConstants; +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 org.wso2.carbon.utils.multitenancy.MultitenantUtils; + +import java.io.File; +import java.util.Calendar; +import java.util.UUID; + +public class RaspberryPiMQTTConnector extends MQTTTransportHandler { + private static Log log = LogFactory.getLog(RaspberryPiMQTTConnector.class); + private static final String subscribeTopic = "wso2/" + RaspberrypiConstants.DEVICE_TYPE + "/+/publisher"; + + private static final String iotServerSubscriber = UUID.randomUUID().toString().substring(0, 5); + + private RaspberryPiMQTTConnector() { + super(iotServerSubscriber, RaspberrypiConstants.DEVICE_TYPE, + MqttConfig.getInstance().getMqttQueueEndpoint(), subscribeTopic); + } + + @Override + public void connect() { + Runnable connector = new Runnable() { + public void run() { + while (!isConnected()) { + try { + String brokerUsername = MqttConfig.getInstance().getMqttQueueUsername(); + String brokerPassword = MqttConfig.getInstance().getMqttQueuePassword(); + setUsernameAndPassword(brokerUsername, brokerPassword); + connectToQueue(); + } catch (TransportHandlerException e) { + log.error("Connection to MQTT Broker at: " + mqttBrokerEndPoint + " failed", e); + try { + Thread.sleep(timeoutInterval); + } catch (InterruptedException ex) { + log.error("MQTT-Connector: Thread Sleep Interrupt Exception.", ex); + } + } + + try { + subscribeToQueue(); + } catch (TransportHandlerException e) { + log.warn("Subscription to MQTT Broker at: " + mqttBrokerEndPoint + " failed", e); + } + } + } + }; + + Thread connectorThread = new Thread(connector); + connectorThread.setDaemon(true); + connectorThread.start(); + } + + @Override + public void processIncomingMessage(MqttMessage message, String... messageParams) throws TransportHandlerException { + if(messageParams.length != 0) { + // owner and the deviceId are extracted from the MQTT topic to which the message was received. + // = [ServerName/Owner/DeviceType/DeviceId/"publisher"] + String topic = messageParams[0]; + String[] topicParams = topic.split("/"); + String deviceId = topicParams[2]; + String receivedMessage = message.toString(); + + if (log.isDebugEnabled()) { + log.debug("Received MQTT message for: [DEVICE.ID-" + deviceId + "]"); + log.debug("Message [" + receivedMessage + "] topic: [" + topic + "]"); + } + + if (receivedMessage.contains("PUBLISHER")) { + float temperature = Float.parseFloat(receivedMessage.split(":")[2]); + + try { + PrivilegedCarbonContext.startTenantFlow(); + PrivilegedCarbonContext ctx = PrivilegedCarbonContext.getThreadLocalCarbonContext(); + DeviceManagementProviderService deviceManagementProviderService = + (DeviceManagementProviderService) ctx.getOSGiService(DeviceManagementProviderService.class, null); + if (deviceManagementProviderService != null) { + DeviceIdentifier identifier = new DeviceIdentifier(deviceId, RaspberrypiConstants.DEVICE_TYPE); + Device device = deviceManagementProviderService.getDevice(identifier); + if (device != null) { + String owner = device.getEnrolmentInfo().getOwner(); + ctx.setTenantDomain(MultitenantUtils.getTenantDomain(owner), true); + ctx.setUsername(owner); + if (!RaspberrypiServiceUtils.publishToDAS(deviceId, temperature)) { + log.error("MQTT Subscriber: Publishing data to DAS failed."); + } + } + } + } catch (DeviceManagementException e) { + log.error("Failed to retreive the device managment service for device type " + + RaspberrypiConstants.DEVICE_TYPE, e); + } finally { + PrivilegedCarbonContext.endTenantFlow(); + } + + if (log.isDebugEnabled()) { + log.debug("MQTT Subscriber: Published data to DAS successfully."); + } + + } else if (receivedMessage.contains("TEMPERATURE")) { + String temperatureValue = receivedMessage.split(":")[1]; + SensorDataManager.getInstance().setSensorRecord(deviceId, RaspberrypiConstants.SENSOR_TEMPERATURE, + temperatureValue, Calendar.getInstance().getTimeInMillis()); + } + } + } + + @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 - " + RaspberrypiConstants.DEVICE_TYPE, e); + } + + try { + Thread.sleep(timeoutInterval); + } catch (InterruptedException e1) { + log.error("MQTT-Terminator: Thread Sleep Interrupt Exception at device-type - " + + RaspberrypiConstants.DEVICE_TYPE, e1); + } + } + } + } + }; + + Thread terminatorThread = new Thread(stopConnection); + terminatorThread.setDaemon(true); + terminatorThread.start(); + } + + @Override + public void processIncomingMessage() throws TransportHandlerException { + + } + + @Override + public void processIncomingMessage(MqttMessage message) throws TransportHandlerException { + + } + + @Override + public void publishDeviceData() throws TransportHandlerException { + + } + + @Override + public void publishDeviceData(MqttMessage publishData) throws TransportHandlerException { + + } + + @Override + public void publishDeviceData(String... publishData) throws TransportHandlerException { + + } +} + diff --git a/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.controller.service.impl/src/main/java/org/wso2/carbon/device/mgt/iot/raspberrypi/controller/service/impl/util/RaspberrypiServiceUtils.java b/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.controller.service.impl/src/main/java/org/wso2/carbon/device/mgt/iot/raspberrypi/controller/service/impl/util/RaspberrypiServiceUtils.java new file mode 100644 index 0000000000..a641ab09d2 --- /dev/null +++ b/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.controller.service.impl/src/main/java/org/wso2/carbon/device/mgt/iot/raspberrypi/controller/service/impl/util/RaspberrypiServiceUtils.java @@ -0,0 +1,228 @@ +/* + * 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.wso2.carbon.device.mgt.iot.raspberrypi.controller.service.impl.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.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 org.wso2.carbon.device.mgt.iot.raspberrypi.plugin.constants.RaspberrypiConstants; + +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 RaspberrypiServiceUtils { + private static final Log log = LogFactory.getLog(RaspberrypiServiceUtils.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 = RaspberrypiConstants.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; + } + + + /*public static boolean sendCommandViaMQTT(String deviceOwner, String deviceId, String resource, + String state) throws DeviceManagementException { + + boolean result; + DeviceController deviceController = new DeviceController(); + + try { + result = deviceController.publishMqttControl(deviceOwner, RaspberrypiConstants.DEVICE_TYPE, deviceId, resource, state); + } catch (DeviceControllerException e) { + String errorMsg = "Error whilst trying to publish to MQTT Queue"; + log.error(errorMsg); + throw new DeviceManagementException(errorMsg, e); + } + return result; + }*/ + + /* --------------------------------------------------------------------------------------- + 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 deviceId, float temperature) { + PrivilegedCarbonContext ctx = PrivilegedCarbonContext.getThreadLocalCarbonContext(); + DeviceAnalyticsService deviceAnalyticsService = (DeviceAnalyticsService) ctx.getOSGiService( + DeviceAnalyticsService.class, null); + String owner = PrivilegedCarbonContext.getThreadLocalCarbonContext().getUsername(); + Object metdaData[] = {owner, RaspberrypiConstants.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; + } + return true; + } +} diff --git a/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.controller.service.impl/src/main/webapp/META-INF/webapp-classloading.xml b/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.controller.service.impl/src/main/webapp/META-INF/webapp-classloading.xml new file mode 100644 index 0000000000..fa44619195 --- /dev/null +++ b/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.controller.service.impl/src/main/webapp/META-INF/webapp-classloading.xml @@ -0,0 +1,33 @@ + + + + + + + + + false + + + CXF,Carbon + diff --git a/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.controller.service.impl/src/main/webapp/WEB-INF/cxf-servlet.xml b/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.controller.service.impl/src/main/webapp/WEB-INF/cxf-servlet.xml new file mode 100644 index 0000000000..0ccfb9745e --- /dev/null +++ b/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.controller.service.impl/src/main/webapp/WEB-INF/cxf-servlet.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.controller.service.impl/src/main/webapp/WEB-INF/web.xml b/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.controller.service.impl/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 0000000000..f2fe934b8d --- /dev/null +++ b/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.controller.service.impl/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,54 @@ + + + RaspberryPi + RaspberryPi + + + CXFServlet + org.apache.cxf.transport.servlet.CXFServlet + 1 + + + + + CXFServlet + /* + + + + isAdminService + false + + + doAuthentication + true + + + + + managed-api-enabled + true + + + managed-api-owner + admin + + + managed-api-context-template + /raspberrypi/{version} + + + managed-api-application + raspberrypi + + + managed-api-isSecured + true + + + + diff --git a/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.manager.service.impl/pom.xml b/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.manager.service.impl/pom.xml new file mode 100644 index 0000000000..9a2750ba33 --- /dev/null +++ b/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.manager.service.impl/pom.xml @@ -0,0 +1,166 @@ + + + + + + + raspberrypi-plugin + org.wso2.carbon.devicemgt-plugins + 2.1.0-SNAPSHOT + ../pom.xml + + + 4.0.0 + org.wso2.carbon.device.mgt.iot.raspberrypi.manager.service.impl + war + WSO2 Carbon - IoT Server RaspberryPi Manager API + WSO2 Carbon - RaspberryPi Manager Service API Implementation + http://wso2.org + + + + + org.wso2.carbon.devicemgt + org.wso2.carbon.device.mgt.common + provided + + + org.wso2.carbon.devicemgt + org.wso2.carbon.device.mgt.core + provided + + + org.apache.axis2.wso2 + axis2-client + + + + + + + org.apache.cxf + cxf-rt-frontend-jaxws + provided + + + org.apache.cxf + cxf-rt-frontend-jaxrs + provided + + + org.apache.cxf + cxf-rt-transports-http + provided + + + + + org.eclipse.paho + org.eclipse.paho.client.mqttv3 + provided + + + + + org.apache.httpcomponents + httpasyncclient + 4.1 + provided + + + org.wso2.carbon.devicemgt-plugins + org.wso2.carbon.device.mgt.iot + provided + + + org.wso2.carbon.devicemgt-plugins + org.wso2.carbon.device.mgt.iot.raspberrypi.plugin.impl + provided + + + + + org.codehaus.jackson + jackson-core-asl + + + org.codehaus.jackson + jackson-jaxrs + + + javax + javaee-web-api + provided + + + javax.ws.rs + jsr311-api + provided + + + + org.wso2.carbon.devicemgt + org.wso2.carbon.device.mgt.analytics + provided + + + org.wso2.carbon.devicemgt + org.wso2.carbon.apimgt.annotations + provided + + + org.wso2.carbon.devicemgt + org.wso2.carbon.apimgt.webapp.publisher + provided + + + org.wso2.carbon.devicemgt + org.wso2.carbon.device.mgt.jwt.client.extension + provided + + + org.wso2.carbon.devicemgt + org.wso2.carbon.apimgt.application.extension + provided + + + + + + + + maven-compiler-plugin + + UTF-8 + ${wso2.maven.compiler.source} + ${wso2.maven.compiler.target} + + + + maven-war-plugin + + raspberrypi_mgt + + + + + + \ No newline at end of file diff --git a/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.manager.service.impl/src/main/java/org/wso2/carbon/device/mgt/iot/raspberrypi/manager/service/impl/RaspberryPiManagerService.java b/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.manager.service.impl/src/main/java/org/wso2/carbon/device/mgt/iot/raspberrypi/manager/service/impl/RaspberryPiManagerService.java new file mode 100644 index 0000000000..abc970e89e --- /dev/null +++ b/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.manager.service.impl/src/main/java/org/wso2/carbon/device/mgt/iot/raspberrypi/manager/service/impl/RaspberryPiManagerService.java @@ -0,0 +1,323 @@ +/* + * Copyright (c) 2016, 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.wso2.carbon.device.mgt.iot.raspberrypi.manager.service.impl; + +import org.apache.commons.io.FileUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.apimgt.application.extension.APIManagementProviderService; +import org.wso2.carbon.apimgt.application.extension.dto.ApiApplicationKey; +import org.wso2.carbon.apimgt.application.extension.exception.APIManagerException; +import org.wso2.carbon.context.PrivilegedCarbonContext; +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.controlqueue.xmpp.XmppAccount; +import org.wso2.carbon.device.mgt.iot.controlqueue.xmpp.XmppConfig; +import org.wso2.carbon.device.mgt.iot.controlqueue.xmpp.XmppServerClient; +import org.wso2.carbon.device.mgt.iot.exception.DeviceControllerException; +import org.wso2.carbon.device.mgt.iot.raspberrypi.manager.service.impl.util.APIUtil; +import org.wso2.carbon.device.mgt.iot.raspberrypi.plugin.constants.RaspberrypiConstants; +import org.wso2.carbon.device.mgt.iot.util.ZipArchive; +import org.wso2.carbon.device.mgt.iot.util.ZipUtil; +import org.wso2.carbon.device.mgt.jwt.client.extension.JWTClient; +import org.wso2.carbon.device.mgt.jwt.client.extension.JWTClientManager; +import org.wso2.carbon.device.mgt.jwt.client.extension.dto.AccessTokenInfo; +import org.wso2.carbon.device.mgt.jwt.client.extension.exception.JWTClientException; +import org.wso2.carbon.user.api.UserStoreException; + +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.MediaType; +import javax.ws.rs.core.Response; +import java.io.IOException; +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; + +public class RaspberryPiManagerService { + + private static Log log = LogFactory.getLog(RaspberryPiManagerService.class); + + @Context //injected response proxy supporting multiple thread + private HttpServletResponse response; + private static final String KEY_TYPE = "PRODUCTION"; + private static ApiApplicationKey apiApplicationKey; + + @Path("manager/device/register") + @POST + public boolean register(@QueryParam("deviceId") String deviceId, + @QueryParam("name") String name) { + try { + DeviceIdentifier deviceIdentifier = new DeviceIdentifier(); + deviceIdentifier.setId(deviceId); + deviceIdentifier.setType(RaspberrypiConstants.DEVICE_TYPE); + if (APIUtil.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); + device.setName(name); + device.setType(RaspberrypiConstants.DEVICE_TYPE); + enrolmentInfo.setOwner(APIUtil.getAuthenticatedUser()); + device.setEnrolmentInfo(enrolmentInfo); + boolean added = APIUtil.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; + } finally { + PrivilegedCarbonContext.endTenantFlow(); + } + } + + @Path("manager/device/{device_id}") + @DELETE + public void removeDevice(@PathParam("device_id") String deviceId, + @Context HttpServletResponse response) { + try { + DeviceIdentifier deviceIdentifier = new DeviceIdentifier(); + deviceIdentifier.setId(deviceId); + deviceIdentifier.setType(RaspberrypiConstants.DEVICE_TYPE); + boolean removed = APIUtil.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 { + PrivilegedCarbonContext.endTenantFlow(); + } + } + + @Path("manager/device/{device_id}") + @PUT + public boolean updateDevice(@PathParam("device_id") String deviceId, + @QueryParam("name") String name, + @Context HttpServletResponse response) { + DeviceIdentifier deviceIdentifier = new DeviceIdentifier(); + deviceIdentifier.setId(deviceId); + deviceIdentifier.setType(RaspberrypiConstants.DEVICE_TYPE); + try { + Device device = APIUtil.getDeviceManagementService().getDevice(deviceIdentifier); + device.setDeviceIdentifier(deviceId); + device.getEnrolmentInfo().setDateOfLastUpdate(new Date().getTime()); + device.setName(name); + device.setType(RaspberrypiConstants.DEVICE_TYPE); + boolean updated = APIUtil.getDeviceManagementService().modifyEnrollment(device); + if (updated) { + response.setStatus(Response.Status.OK.getStatusCode()); + } else { + response.setStatus(Response.Status.NOT_ACCEPTABLE.getStatusCode()); + } + return updated; + } catch (DeviceManagementException e) { + log.error(e.getErrorMessage()); + return false; + } finally { + PrivilegedCarbonContext.endTenantFlow(); + } + } + + @Path("manager/device/{device_id}") + @GET + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public Device getDevice(@PathParam("device_id") String deviceId) { + DeviceIdentifier deviceIdentifier = new DeviceIdentifier(); + deviceIdentifier.setId(deviceId); + deviceIdentifier.setType(RaspberrypiConstants.DEVICE_TYPE); + try { + return APIUtil.getDeviceManagementService().getDevice(deviceIdentifier); + } catch (DeviceManagementException ex) { + log.error("Error occurred while retrieving device with Id " + deviceId + "\n" + ex); + return null; + } finally { + PrivilegedCarbonContext.endTenantFlow(); + } + } + + @Path("manager/devices") + @GET + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public Device[] getRaspberrypiDevices() { + try { + List userDevices = APIUtil.getDeviceManagementService().getDevicesOfUser( + APIUtil.getAuthenticatedUser()); + ArrayList usersRaspberrypiDevices = new ArrayList<>(); + for (Device device : userDevices) { + if (device.getType().equals(RaspberrypiConstants.DEVICE_TYPE) && + device.getEnrolmentInfo().getStatus().equals(EnrolmentInfo.Status.ACTIVE)) { + usersRaspberrypiDevices.add(device); + } + } + return usersRaspberrypiDevices.toArray(new Device[]{}); + + } catch (DeviceManagementException e) { + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + return null; + } finally { + PrivilegedCarbonContext.endTenantFlow(); + } + } + + @Path("manager/device/{sketch_type}/download") + @GET + @Produces(MediaType.APPLICATION_JSON) + public Response downloadSketch(@QueryParam("deviceName") String deviceName, @PathParam("sketch_type") String + sketchType) { + try { + ZipArchive zipFile = createDownloadFile(APIUtil.getAuthenticatedUser(), deviceName, sketchType); + Response.ResponseBuilder response = Response.ok(FileUtils.readFileToByteArray(zipFile.getZipFile())); + response.type("application/zip"); + response.header("Content-Disposition", "attachment; filename=\"" + zipFile.getFileName() + "\""); + return response.build(); + } catch (IllegalArgumentException ex) { + return Response.status(400).entity(ex.getMessage()).build();//bad request + } catch (DeviceManagementException ex) { + return Response.status(500).entity(ex.getMessage()).build(); + } catch (JWTClientException ex) { + return Response.status(500).entity(ex.getMessage()).build(); + } catch (DeviceControllerException ex) { + return Response.status(500).entity(ex.getMessage()).build(); + } catch (IOException ex) { + return Response.status(500).entity(ex.getMessage()).build(); + } catch (APIManagerException ex) { + return Response.status(500).entity(ex.getMessage()).build(); + } catch (UserStoreException ex) { + return Response.status(500).entity(ex.getMessage()).build(); + } finally { + PrivilegedCarbonContext.endTenantFlow(); + } + } + + + @Path("manager/device/{sketch_type}/generate_link") + @GET + public Response generateSketchLink(@QueryParam("deviceName") String deviceName, + @PathParam("sketch_type") String sketchType) { + + try { + ZipArchive zipFile = createDownloadFile(APIUtil.getAuthenticatedUser(), deviceName, sketchType); + Response.ResponseBuilder rb = Response.ok(zipFile.getDeviceId()); + return rb.build(); + } catch (IllegalArgumentException ex) { + return Response.status(400).entity(ex.getMessage()).build();//bad request + } catch (DeviceManagementException ex) { + return Response.status(500).entity(ex.getMessage()).build(); + } catch (JWTClientException ex) { + return Response.status(500).entity(ex.getMessage()).build(); + } catch (DeviceControllerException ex) { + return Response.status(500).entity(ex.getMessage()).build(); + } catch (APIManagerException ex) { + return Response.status(500).entity(ex.getMessage()).build(); + } catch (UserStoreException ex) { + return Response.status(500).entity(ex.getMessage()).build(); + } finally { + PrivilegedCarbonContext.endTenantFlow(); + } + } + + + private ZipArchive createDownloadFile(String owner, String deviceName, String sketchType) + throws DeviceManagementException, JWTClientException, APIManagerException, DeviceControllerException, + UserStoreException { + //create new device id + String deviceId = shortUUID(); + if (apiApplicationKey == null) { + String applicationUsername = PrivilegedCarbonContext.getThreadLocalCarbonContext().getUserRealm().getRealmConfiguration() + .getAdminUserName(); + APIManagementProviderService apiManagementProviderService = APIUtil.getAPIManagementProviderService(); + String[] tags = {RaspberrypiConstants.DEVICE_TYPE}; + apiApplicationKey = apiManagementProviderService.generateAndRetrieveApplicationKeys( + RaspberrypiConstants.DEVICE_TYPE, tags, KEY_TYPE, applicationUsername, true); + } + JWTClient jwtClient = JWTClientManager.getInstance().getJWTClient(); + String scopes = "device_type_" + RaspberrypiConstants.DEVICE_TYPE + " device_" + deviceId; + AccessTokenInfo accessTokenInfo = jwtClient.getAccessToken(apiApplicationKey.getConsumerKey(), + apiApplicationKey.getConsumerSecret(), owner, scopes); + //create token + String accessToken = accessTokenInfo.getAccess_token(); + String refreshToken = accessTokenInfo.getRefresh_token(); + //adding registering data + XmppAccount newXmppAccount = new XmppAccount(); + newXmppAccount.setAccountName(owner + "_" + deviceId); + newXmppAccount.setUsername(deviceId); + newXmppAccount.setPassword(accessToken); + newXmppAccount.setEmail(deviceId + "@wso2.com"); + XmppServerClient xmppServerClient = new XmppServerClient(); + xmppServerClient.initControlQueue(); + boolean status; + if (XmppConfig.getInstance().isEnabled()) { + status = xmppServerClient.createXMPPAccount(newXmppAccount); + if (!status) { + String msg = "XMPP Account was not created for device - " + deviceId + " of owner - " + owner + + ".XMPP might have been disabled in org.wso2.carbon.device.mgt.iot.common.config" + + ".server.configs"; + log.warn(msg); + throw new DeviceManagementException(msg); + } + } + //Register the device with CDMF + status = register(deviceId, deviceName); + if (!status) { + String msg = "Error occurred while registering the device with " + "id: " + deviceId + " owner:" + owner; + throw new DeviceManagementException(msg); + } + ZipUtil ziputil = new ZipUtil(); + ZipArchive zipFile = ziputil.createZipFile(owner, APIUtil.getTenantDomainOftheUser(), sketchType, + deviceId, deviceName, accessToken, refreshToken); + zipFile.setDeviceId(deviceId); + return zipFile; + } + + 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); + } + +} diff --git a/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.manager.service.impl/src/main/java/org/wso2/carbon/device/mgt/iot/raspberrypi/manager/service/impl/util/APIUtil.java b/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.manager.service.impl/src/main/java/org/wso2/carbon/device/mgt/iot/raspberrypi/manager/service/impl/util/APIUtil.java new file mode 100644 index 0000000000..4126294438 --- /dev/null +++ b/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.manager.service.impl/src/main/java/org/wso2/carbon/device/mgt/iot/raspberrypi/manager/service/impl/util/APIUtil.java @@ -0,0 +1,55 @@ +package org.wso2.carbon.device.mgt.iot.raspberrypi.manager.service.impl.util; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.apimgt.application.extension.APIManagementProviderService; +import org.wso2.carbon.context.PrivilegedCarbonContext; +import org.wso2.carbon.device.mgt.core.service.DeviceManagementProviderService; + +/** + * This class provides utility functions used by REST-API. + */ +public class APIUtil { + + private static Log log = LogFactory.getLog(APIUtil.class); + + public static String getAuthenticatedUser() { + PrivilegedCarbonContext threadLocalCarbonContext = PrivilegedCarbonContext.getThreadLocalCarbonContext(); + String username = threadLocalCarbonContext.getUsername(); + String tenantDomain = threadLocalCarbonContext.getTenantDomain(); + if (username.endsWith(tenantDomain)) { + return username.substring(0, username.lastIndexOf("@")); + } + return username; + } + + public static String getTenantDomainOftheUser() { + PrivilegedCarbonContext threadLocalCarbonContext = PrivilegedCarbonContext.getThreadLocalCarbonContext(); + String tenantDomain = threadLocalCarbonContext.getTenantDomain(); + return tenantDomain; + } + + public static DeviceManagementProviderService getDeviceManagementService() { + PrivilegedCarbonContext ctx = PrivilegedCarbonContext.getThreadLocalCarbonContext(); + DeviceManagementProviderService deviceManagementProviderService = + (DeviceManagementProviderService) ctx.getOSGiService(DeviceManagementProviderService.class, null); + if (deviceManagementProviderService == null) { + String msg = "Device Management service has not initialized."; + log.error(msg); + throw new IllegalStateException(msg); + } + return deviceManagementProviderService; + } + + public static APIManagementProviderService getAPIManagementProviderService() { + PrivilegedCarbonContext ctx = PrivilegedCarbonContext.getThreadLocalCarbonContext(); + APIManagementProviderService apiManagementProviderService = + (APIManagementProviderService) ctx.getOSGiService(DeviceManagementProviderService.class, null); + if (apiManagementProviderService == null) { + String msg = "API management provider service has not initialized."; + log.error(msg); + throw new IllegalStateException(msg); + } + return apiManagementProviderService; + } +} diff --git a/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.manager.service.impl/src/main/webapp/META-INF/webapp-classloading.xml b/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.manager.service.impl/src/main/webapp/META-INF/webapp-classloading.xml new file mode 100644 index 0000000000..fa44619195 --- /dev/null +++ b/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.manager.service.impl/src/main/webapp/META-INF/webapp-classloading.xml @@ -0,0 +1,33 @@ + + + + + + + + + false + + + CXF,Carbon + diff --git a/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.manager.service.impl/src/main/webapp/WEB-INF/cxf-servlet.xml b/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.manager.service.impl/src/main/webapp/WEB-INF/cxf-servlet.xml new file mode 100644 index 0000000000..48216c9b82 --- /dev/null +++ b/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.manager.service.impl/src/main/webapp/WEB-INF/cxf-servlet.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.manager.service.impl/src/main/webapp/WEB-INF/web.xml b/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.manager.service.impl/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 0000000000..abda0a6a42 --- /dev/null +++ b/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.manager.service.impl/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,50 @@ + + + RaspberryPi + RaspberryPi + + + CXFServlet + org.apache.cxf.transport.servlet.CXFServlet + 1 + + + + CXFServlet + /* + + + isAdminService + false + + + doAuthentication + true + + + + + managed-api-enabled + false + + + managed-api-owner + admin + + + managed-api-context-template + /raspberrypi/{version} + + + managed-api-application + raspberrypi + + + managed-api-isSecured + true + + diff --git a/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.plugin.impl/pom.xml b/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.plugin.impl/pom.xml new file mode 100644 index 0000000000..8d96e651aa --- /dev/null +++ b/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.plugin.impl/pom.xml @@ -0,0 +1,124 @@ + + + + + + + + raspberrypi-plugin + org.wso2.carbon.devicemgt-plugins + 2.1.0-SNAPSHOT + ../pom.xml + + + 4.0.0 + org.wso2.carbon.device.mgt.iot.raspberrypi.plugin.impl + bundle + WSO2 Carbon - IoT Server RaspberryPi Management Plugin + WSO2 Carbon - RaspberryPi Management/Control Plugin Implementation + http://wso2.org + + + + + org.apache.felix + maven-scr-plugin + + + maven-compiler-plugin + + 1.7 + 1.7 + + 2.3.2 + + + org.apache.felix + maven-bundle-plugin + 1.4.0 + true + + + ${project.artifactId} + ${project.artifactId} + ${carbon.devicemgt.plugins.version} + IoT Server Impl Bundle + org.wso2.carbon.device.mgt.iot.raspberrypi.plugin.internal + + org.osgi.framework, + org.osgi.service.component, + org.apache.commons.logging, + javax.xml.bind.*, + javax.naming;resolution:=optional,, + javax.sql;resolution:=optional, + javax.xml.bind.annotation.*;resolution:=optional, + javax.xml.parsers;resolution:=optional, + javax.net;resolution:=optional, + javax.net.ssl;resolution:=optional, + org.w3c.dom;resolution:=optional, + org.wso2.carbon.device.mgt.common.*, + org.wso2.carbon.device.mgt.common, + org.wso2.carbon.context.*, + org.wso2.carbon.ndatasource.core, + org.wso2.carbon.device.mgt.iot.*, + org.wso2.carbon.device.mgt.extensions.feature.mgt.*, + org.wso2.carbon.utils.* + + + !org.wso2.carbon.device.mgt.iot.raspberrypi.plugin.internal, + org.wso2.carbon.device.mgt.iot.raspberrypi.plugin.* + + + + + + + + + + org.eclipse.osgi + org.eclipse.osgi + + + org.eclipse.osgi + org.eclipse.osgi.services + + + org.wso2.carbon + org.wso2.carbon.logging + + + org.wso2.carbon.devicemgt + org.wso2.carbon.device.mgt.common + + + org.wso2.carbon + org.wso2.carbon.ndatasource.core + + + org.wso2.carbon.devicemgt + org.wso2.carbon.device.mgt.extensions + + + org.wso2.carbon + org.wso2.carbon.utils + + + \ No newline at end of file diff --git a/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.plugin.impl/src/main/java/org/wso2/carbon/device/mgt/iot/raspberrypi/plugin/constants/RaspberrypiConstants.java b/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.plugin.impl/src/main/java/org/wso2/carbon/device/mgt/iot/raspberrypi/plugin/constants/RaspberrypiConstants.java new file mode 100644 index 0000000000..c6534e46fb --- /dev/null +++ b/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.plugin.impl/src/main/java/org/wso2/carbon/device/mgt/iot/raspberrypi/plugin/constants/RaspberrypiConstants.java @@ -0,0 +1,39 @@ +/* + * 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.wso2.carbon.device.mgt.iot.raspberrypi.plugin.constants; + +public class RaspberrypiConstants { + + public final static String DEVICE_TYPE = "raspberrypi"; + public final static String DEVICE_PLUGIN_DEVICE_NAME = "DEVICE_NAME"; + public final static String DEVICE_PLUGIN_DEVICE_ID = "RASPBERRYPI_DEVICE_ID"; + public final static String STATE_ON = "ON"; + public final static String STATE_OFF = "OFF"; + + public static final String URL_PREFIX = "http://"; + public static final String BULB_CONTEXT = "/BULB/"; + public static final String TEMPERATURE_CONTEXT = "/TEMPERATURE/"; + + //type of the sensor + public static final String SENSOR_TEMPERATURE = "temperature"; + //sensor events summerized table name + public static final String TEMPERATURE_EVENT_TABLE = "ORG_WSO2_IOT_DEVICES_TEMPERATURE"; + public static final String DATA_SOURCE_NAME = "jdbc/RaspberryPiDM_DB"; + +} diff --git a/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.plugin.impl/src/main/java/org/wso2/carbon/device/mgt/iot/raspberrypi/plugin/exception/RaspberrypiDeviceMgtPluginException.java b/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.plugin.impl/src/main/java/org/wso2/carbon/device/mgt/iot/raspberrypi/plugin/exception/RaspberrypiDeviceMgtPluginException.java new file mode 100644 index 0000000000..805775195b --- /dev/null +++ b/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.plugin.impl/src/main/java/org/wso2/carbon/device/mgt/iot/raspberrypi/plugin/exception/RaspberrypiDeviceMgtPluginException.java @@ -0,0 +1,56 @@ +/* + * 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.wso2.carbon.device.mgt.iot.raspberrypi.plugin.exception; + + +public class RaspberrypiDeviceMgtPluginException extends Exception{ + + private String errorMessage; + + public String getErrorMessage() { + return errorMessage; + } + + public void setErrorMessage(String errorMessage) { + this.errorMessage = errorMessage; + } + + public RaspberrypiDeviceMgtPluginException(String msg, Exception nestedEx) { + super(msg, nestedEx); + setErrorMessage(msg); + } + + public RaspberrypiDeviceMgtPluginException(String message, Throwable cause) { + super(message, cause); + setErrorMessage(message); + } + + public RaspberrypiDeviceMgtPluginException(String msg) { + super(msg); + setErrorMessage(msg); + } + + public RaspberrypiDeviceMgtPluginException() { + super(); + } + + public RaspberrypiDeviceMgtPluginException(Throwable cause) { + super(cause); + } + +} diff --git a/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.plugin.impl/src/main/java/org/wso2/carbon/device/mgt/iot/raspberrypi/plugin/impl/RaspberrypiManager.java b/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.plugin.impl/src/main/java/org/wso2/carbon/device/mgt/iot/raspberrypi/plugin/impl/RaspberrypiManager.java new file mode 100644 index 0000000000..098438d464 --- /dev/null +++ b/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.plugin.impl/src/main/java/org/wso2/carbon/device/mgt/iot/raspberrypi/plugin/impl/RaspberrypiManager.java @@ -0,0 +1,252 @@ +/* + * 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.wso2.carbon.device.mgt.iot.raspberrypi.plugin.impl; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +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.DeviceManager; +import org.wso2.carbon.device.mgt.common.EnrolmentInfo; +import org.wso2.carbon.device.mgt.common.FeatureManager; +import org.wso2.carbon.device.mgt.common.configuration.mgt.TenantConfiguration; +import org.wso2.carbon.device.mgt.common.license.mgt.License; +import org.wso2.carbon.device.mgt.common.license.mgt.LicenseManagementException; +import org.wso2.carbon.device.mgt.iot.raspberrypi.plugin.exception.RaspberrypiDeviceMgtPluginException; +import org.wso2.carbon.device.mgt.iot.raspberrypi.plugin.impl.dao.RaspberrypiDAO; +import java.util.List; + +/** + * This represents the Raspberrypi implementation of DeviceManagerService. + */ +public class RaspberrypiManager implements DeviceManager { + + private static final RaspberrypiDAO raspberrypiDAO = new RaspberrypiDAO(); + private static final Log log = LogFactory.getLog(RaspberrypiManager.class); + + @Override + public FeatureManager getFeatureManager() { + return null; + } + + @Override + public boolean saveConfiguration(TenantConfiguration tenantConfiguration) + throws DeviceManagementException { + return false; + } + + @Override + public TenantConfiguration getConfiguration() throws DeviceManagementException { + return null; + } + + @Override + public boolean enrollDevice(Device device) throws DeviceManagementException { + boolean status; + try { + if (log.isDebugEnabled()) { + log.debug("Enrolling a new Raspberrypi device : " + device.getDeviceIdentifier()); + } + RaspberrypiDAO.beginTransaction(); + status = raspberrypiDAO.getDeviceDAO().addDevice(device); + RaspberrypiDAO.commitTransaction(); + } catch (RaspberrypiDeviceMgtPluginException e) { + try { + RaspberrypiDAO.rollbackTransaction(); + } catch (RaspberrypiDeviceMgtPluginException iotDAOEx) { + String msg = "Error occurred while roll back the device enrol transaction :" + device.toString(); + log.warn(msg, iotDAOEx); + } + String msg = "Error while enrolling the Raspberrypi device : " + device.getDeviceIdentifier(); + log.error(msg, e); + throw new DeviceManagementException(msg, e); + } + return status; + } + + @Override + public boolean modifyEnrollment(Device device) throws DeviceManagementException { + boolean status; + try { + if (log.isDebugEnabled()) { + log.debug("Modifying the Raspberrypi device enrollment data"); + } + RaspberrypiDAO.beginTransaction(); + status = raspberrypiDAO.getDeviceDAO().updateDevice(device); + RaspberrypiDAO.commitTransaction(); + } catch (RaspberrypiDeviceMgtPluginException e) { + try { + RaspberrypiDAO.rollbackTransaction(); + } catch (RaspberrypiDeviceMgtPluginException iotDAOEx) { + String msg = "Error occurred while roll back the update device transaction :" + device.toString(); + log.warn(msg, iotDAOEx); + } + String msg = "Error while updating the enrollment of the Raspberrypi device : " + + device.getDeviceIdentifier(); + log.error(msg, e); + throw new DeviceManagementException(msg, e); + } + return status; + } + + @Override + public boolean disenrollDevice(DeviceIdentifier deviceId) throws DeviceManagementException { + boolean status; + try { + if (log.isDebugEnabled()) { + log.debug("Dis-enrolling Raspberrypi device : " + deviceId); + } + RaspberrypiDAO.beginTransaction(); + status = raspberrypiDAO.getDeviceDAO().deleteDevice(deviceId.getId()); + RaspberrypiDAO.commitTransaction(); + } catch (RaspberrypiDeviceMgtPluginException e) { + try { + RaspberrypiDAO.rollbackTransaction(); + } catch (RaspberrypiDeviceMgtPluginException iotDAOEx) { + String msg = "Error occurred while roll back the device dis enrol transaction :" + deviceId.toString(); + log.warn(msg, iotDAOEx); + } + String msg = "Error while removing the Raspberrypi device : " + deviceId.getId(); + log.error(msg, e); + throw new DeviceManagementException(msg, e); + } + return status; + } + + @Override + public boolean isEnrolled(DeviceIdentifier deviceId) throws DeviceManagementException { + boolean isEnrolled = false; + try { + if (log.isDebugEnabled()) { + log.debug("Checking the enrollment of Raspberrypi device : " + deviceId.getId()); + } + Device iotDevice = raspberrypiDAO.getDeviceDAO().getDevice(deviceId.getId()); + if (iotDevice != null) { + isEnrolled = true; + } + } catch (RaspberrypiDeviceMgtPluginException e) { + String msg = "Error while checking the enrollment status of Raspberrypi device : " + + deviceId.getId(); + log.error(msg, e); + throw new DeviceManagementException(msg, e); + } + return isEnrolled; + } + + @Override + public boolean isActive(DeviceIdentifier deviceId) throws DeviceManagementException { + return true; + } + + @Override + public boolean setActive(DeviceIdentifier deviceId, boolean status) + throws DeviceManagementException { + return true; + } + + @Override + public Device getDevice(DeviceIdentifier deviceId) throws DeviceManagementException { + Device device; + try { + if (log.isDebugEnabled()) { + log.debug("Getting the details of Raspberrypi device : " + deviceId.getId()); + } + device = raspberrypiDAO.getDeviceDAO().getDevice(deviceId.getId()); + } catch (RaspberrypiDeviceMgtPluginException e) { + String msg = "Error while fetching the Raspberrypi device : " + deviceId.getId(); + log.error(msg, e); + throw new DeviceManagementException(msg, e); + } + return device; + } + + @Override + public boolean setOwnership(DeviceIdentifier deviceId, String ownershipType) + throws DeviceManagementException { + return true; + } + + public boolean isClaimable(DeviceIdentifier deviceIdentifier) throws DeviceManagementException { + return false; + } + + @Override + public boolean setStatus(DeviceIdentifier deviceId, String currentOwner, + EnrolmentInfo.Status status) throws DeviceManagementException { + return false; + } + + @Override + public License getLicense(String s) throws LicenseManagementException { + return null; + } + + @Override + public void addLicense(License license) throws LicenseManagementException { + + } + + @Override + public boolean requireDeviceAuthorization() { + return false; + } + + @Override + public boolean updateDeviceInfo(DeviceIdentifier deviceIdentifier, Device device) throws DeviceManagementException { + boolean status; + try { + if (log.isDebugEnabled()) { + log.debug( + "updating the details of Raspberrypi device : " + deviceIdentifier); + } + RaspberrypiDAO.beginTransaction(); + status = raspberrypiDAO.getDeviceDAO().updateDevice(device); + RaspberrypiDAO.commitTransaction(); + } catch (RaspberrypiDeviceMgtPluginException e) { + try { + RaspberrypiDAO.rollbackTransaction(); + } catch (RaspberrypiDeviceMgtPluginException iotDAOEx) { + String msg = "Error occurred while roll back the update device info transaction :" + device.toString(); + log.warn(msg, iotDAOEx); + } + String msg = + "Error while updating the Raspberrypi device : " + deviceIdentifier; + log.error(msg, e); + throw new DeviceManagementException(msg, e); + } + return status; + } + + @Override + public List getAllDevices() throws DeviceManagementException { + List devices = null; + try { + if (log.isDebugEnabled()) { + log.debug("Fetching the details of all Raspberrypi devices"); + } + List iotDevices = raspberrypiDAO.getDeviceDAO().getAllDevices(); + } catch (RaspberrypiDeviceMgtPluginException e) { + String msg = "Error while fetching all Raspberrypi devices."; + log.error(msg, e); + throw new DeviceManagementException(msg, e); + } + return devices; + } +} \ No newline at end of file diff --git a/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.plugin.impl/src/main/java/org/wso2/carbon/device/mgt/iot/raspberrypi/plugin/impl/RaspberrypiManagerService.java b/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.plugin.impl/src/main/java/org/wso2/carbon/device/mgt/iot/raspberrypi/plugin/impl/RaspberrypiManagerService.java new file mode 100644 index 0000000000..96ce170ef5 --- /dev/null +++ b/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.plugin.impl/src/main/java/org/wso2/carbon/device/mgt/iot/raspberrypi/plugin/impl/RaspberrypiManagerService.java @@ -0,0 +1,108 @@ +/* + * 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.wso2.carbon.device.mgt.iot.raspberrypi.plugin.impl; + +import org.wso2.carbon.device.mgt.common.DeviceIdentifier; +import org.wso2.carbon.device.mgt.common.DeviceManagementException; +import org.wso2.carbon.device.mgt.common.DeviceManager; +import org.wso2.carbon.device.mgt.common.app.mgt.Application; +import org.wso2.carbon.device.mgt.common.app.mgt.ApplicationManagementException; +import org.wso2.carbon.device.mgt.common.app.mgt.ApplicationManager; +import org.wso2.carbon.device.mgt.common.operation.mgt.Operation; +import org.wso2.carbon.device.mgt.common.spi.DeviceManagementService; +import org.wso2.carbon.device.mgt.iot.raspberrypi.plugin.constants.RaspberrypiConstants; + +import java.util.List; + +public class RaspberrypiManagerService implements DeviceManagementService { + + private DeviceManager deviceManager; + + @Override + public String getType() { + return RaspberrypiConstants.DEVICE_TYPE; + } + + @Override + public String getProviderTenantDomain() { + return "carbon.super"; + } + + @Override + public boolean isSharedWithAllTenants() { + return false; + } + + @Override + public void init() throws DeviceManagementException { + deviceManager = new RaspberrypiManager(); + } + + @Override + public DeviceManager getDeviceManager() { + return deviceManager; + } + + @Override + public ApplicationManager getApplicationManager() { + return null; + } + + @Override + public void notifyOperationToDevices(Operation operation, List deviceIds) + throws DeviceManagementException { + + } + + @Override + public Application[] getApplications(String domain, int pageNumber, int size) + throws ApplicationManagementException { + return new Application[0]; + } + + @Override + public void updateApplicationStatus(DeviceIdentifier deviceId, Application application, + String status) throws ApplicationManagementException { + + } + + @Override + public String getApplicationStatus(DeviceIdentifier deviceId, Application application) + throws ApplicationManagementException { + return null; + } + + @Override + public void installApplicationForDevices(Operation operation, List deviceIdentifiers) + throws ApplicationManagementException { + + } + + @Override + public void installApplicationForUsers(Operation operation, List userNameList) + throws ApplicationManagementException { + + } + + @Override + public void installApplicationForUserRoles(Operation operation, List userRoleList) + throws ApplicationManagementException { + + } +} diff --git a/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.plugin.impl/src/main/java/org/wso2/carbon/device/mgt/iot/raspberrypi/plugin/impl/dao/RaspberrypiDAO.java b/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.plugin.impl/src/main/java/org/wso2/carbon/device/mgt/iot/raspberrypi/plugin/impl/dao/RaspberrypiDAO.java new file mode 100644 index 0000000000..cdd3177431 --- /dev/null +++ b/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.plugin.impl/src/main/java/org/wso2/carbon/device/mgt/iot/raspberrypi/plugin/impl/dao/RaspberrypiDAO.java @@ -0,0 +1,124 @@ +/* + * 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.wso2.carbon.device.mgt.iot.raspberrypi.plugin.impl.dao; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.device.mgt.iot.raspberrypi.plugin.exception.RaspberrypiDeviceMgtPluginException; +import org.wso2.carbon.device.mgt.iot.raspberrypi.plugin.constants.RaspberrypiConstants; +import org.wso2.carbon.device.mgt.iot.raspberrypi.plugin.impl.dao.impl.RaspberrypiDeviceDAOImpl; +import javax.naming.Context; +import javax.naming.InitialContext; +import javax.naming.NamingException; +import javax.sql.DataSource; +import java.sql.Connection; +import java.sql.SQLException; + +public class RaspberrypiDAO { + + private static final Log log = LogFactory.getLog(RaspberrypiDAO.class); + static DataSource dataSource; + private static ThreadLocal currentConnection = new ThreadLocal(); + + public RaspberrypiDAO() { + initRaspberrypiDAO(); + } + + public RaspberrypiDeviceDAOImpl getDeviceDAO() { + return new RaspberrypiDeviceDAOImpl(); + } + + public static void initRaspberrypiDAO() { + try { + Context ctx = new InitialContext(); + dataSource = (DataSource) ctx.lookup(RaspberrypiConstants.DATA_SOURCE_NAME); + } catch (NamingException e) { + log.error("Error while looking up the data source: " + RaspberrypiConstants.DATA_SOURCE_NAME); + } + } + + public static void beginTransaction() throws RaspberrypiDeviceMgtPluginException { + try { + Connection conn = dataSource.getConnection(); + conn.setAutoCommit(false); + currentConnection.set(conn); + } catch (SQLException e) { + throw new RaspberrypiDeviceMgtPluginException("Error occurred while retrieving datasource connection", e); + } + } + + public static Connection getConnection() throws RaspberrypiDeviceMgtPluginException { + if (currentConnection.get() == null) { + try { + currentConnection.set(dataSource.getConnection()); + } catch (SQLException e) { + throw new RaspberrypiDeviceMgtPluginException("Error occurred while retrieving data source connection", e); + } + } + return currentConnection.get(); + } + + public static void commitTransaction() throws RaspberrypiDeviceMgtPluginException { + 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 RaspberrypiDeviceMgtPluginException("Error occurred while committing the transaction", e); + } finally { + closeConnection(); + } + } + + public static void closeConnection() throws RaspberrypiDeviceMgtPluginException { + 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 RaspberrypiDeviceMgtPluginException { + 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 RaspberrypiDeviceMgtPluginException("Error occurred while rollback the transaction", e); + } finally { + closeConnection(); + } + } +} \ No newline at end of file diff --git a/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.plugin.impl/src/main/java/org/wso2/carbon/device/mgt/iot/raspberrypi/plugin/impl/dao/impl/RaspberrypiDeviceDAOImpl.java b/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.plugin.impl/src/main/java/org/wso2/carbon/device/mgt/iot/raspberrypi/plugin/impl/dao/impl/RaspberrypiDeviceDAOImpl.java new file mode 100644 index 0000000000..ae9c22620c --- /dev/null +++ b/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.plugin.impl/src/main/java/org/wso2/carbon/device/mgt/iot/raspberrypi/plugin/impl/dao/impl/RaspberrypiDeviceDAOImpl.java @@ -0,0 +1,191 @@ +/* + * 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.wso2.carbon.device.mgt.iot.raspberrypi.plugin.impl.dao.impl; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.device.mgt.common.Device; +import org.wso2.carbon.device.mgt.iot.raspberrypi.plugin.exception.RaspberrypiDeviceMgtPluginException; +import org.wso2.carbon.device.mgt.iot.raspberrypi.plugin.impl.util.RaspberrypiUtils; +import org.wso2.carbon.device.mgt.iot.raspberrypi.plugin.impl.dao.RaspberrypiDAO; +import org.wso2.carbon.device.mgt.iot.raspberrypi.plugin.constants.RaspberrypiConstants; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; + +/** + * Implements CRUD for Raspberrypi Devices. + */ +public class RaspberrypiDeviceDAOImpl { + + private static final Log log = LogFactory.getLog(RaspberrypiDeviceDAOImpl.class); + + public Device getDevice(String iotDeviceId) throws RaspberrypiDeviceMgtPluginException { + Connection conn; + PreparedStatement stmt = null; + Device device = null; + ResultSet resultSet = null; + try { + conn = RaspberrypiDAO.getConnection(); + String selectDBQuery = + "SELECT RASPBERRYPI_DEVICE_ID, DEVICE_NAME FROM RASPBERRYPI_DEVICE WHERE RASPBERRYPI_DEVICE_ID = ?"; + stmt = conn.prepareStatement(selectDBQuery); + stmt.setString(1, iotDeviceId); + resultSet = stmt.executeQuery(); + + if (resultSet.next()) { + device = new Device(); + device.setName(resultSet.getString(RaspberrypiConstants.DEVICE_PLUGIN_DEVICE_NAME)); + if (log.isDebugEnabled()) { + log.debug("Raspberrypi device " + iotDeviceId + " data has been fetched from " + + "Raspberrypi database."); + } + } + } catch (SQLException e) { + String msg = "Error occurred while fetching Raspberrypi device : '" + iotDeviceId + "'"; + log.error(msg, e); + throw new RaspberrypiDeviceMgtPluginException(msg, e); + } finally { + RaspberrypiUtils.cleanupResources(stmt, resultSet); + RaspberrypiDAO.closeConnection(); + } + return device; + } + + public boolean addDevice(Device device) throws RaspberrypiDeviceMgtPluginException { + boolean status = false; + Connection conn; + PreparedStatement stmt = null; + try { + conn = RaspberrypiDAO.getConnection(); + String createDBQuery = + "INSERT INTO RASPBERRYPI_DEVICE(RASPBERRYPI_DEVICE_ID, DEVICE_NAME) VALUES (?, ?)"; + + stmt = conn.prepareStatement(createDBQuery); + stmt.setString(1, device.getDeviceIdentifier()); + stmt.setString(2, device.getName()); + int rows = stmt.executeUpdate(); + if (rows > 0) { + status = true; + if (log.isDebugEnabled()) { + log.debug("Raspberrypi device " + device.getDeviceIdentifier() + " data has been" + + " added to the Raspberrypi database."); + } + } + } catch (SQLException e) { + String msg = "Error occurred while adding the Raspberrypi device '" + + device.getDeviceIdentifier() + "' to the Raspberrypi db."; + log.error(msg, e); + throw new RaspberrypiDeviceMgtPluginException(msg, e); + } finally { + RaspberrypiUtils.cleanupResources(stmt, null); + } + return status; + } + + public boolean updateDevice(Device device) throws RaspberrypiDeviceMgtPluginException { + boolean status = false; + Connection conn; + PreparedStatement stmt = null; + try { + conn = RaspberrypiDAO.getConnection(); + String updateDBQuery = "UPDATE RASPBERRYPI_DEVICE SET DEVICE_NAME = ? WHERE RASPBERRYPI_DEVICE_ID = ?"; + stmt = conn.prepareStatement(updateDBQuery); + stmt.setString(1, device.getName()); + stmt.setString(2, device.getDeviceIdentifier()); + int rows = stmt.executeUpdate(); + if (rows > 0) { + status = true; + if (log.isDebugEnabled()) { + log.debug("Raspberrypi device " + device.getDeviceIdentifier() + " data has been" + + " modified."); + } + } + } catch (SQLException e) { + String msg = "Error occurred while modifying the Raspberrypi device '" + device.getDeviceIdentifier() + + "' data."; + log.error(msg, e); + throw new RaspberrypiDeviceMgtPluginException(msg, e); + } finally { + RaspberrypiUtils.cleanupResources(stmt, null); + } + return status; + } + + public boolean deleteDevice(String iotDeviceId) throws RaspberrypiDeviceMgtPluginException { + boolean status = false; + Connection conn; + PreparedStatement stmt = null; + try { + conn = RaspberrypiDAO.getConnection(); + String deleteDBQuery = "DELETE FROM RASPBERRYPI_DEVICE WHERE RASPBERRYPI_DEVICE_ID = ?"; + stmt = conn.prepareStatement(deleteDBQuery); + stmt.setString(1, iotDeviceId); + int rows = stmt.executeUpdate(); + if (rows > 0) { + status = true; + if (log.isDebugEnabled()) { + log.debug("Raspberrypi device " + iotDeviceId + " data has deleted" + + " from the Raspberrypi database."); + } + } + } catch (SQLException e) { + String msg = "Error occurred while deleting Raspberrypi device " + iotDeviceId; + log.error(msg, e); + throw new RaspberrypiDeviceMgtPluginException(msg, e); + } finally { + RaspberrypiUtils.cleanupResources(stmt, null); + } + return status; + } + + public List getAllDevices() throws RaspberrypiDeviceMgtPluginException { + Connection conn; + PreparedStatement stmt = null; + ResultSet resultSet = null; + Device device; + List devices = new ArrayList<>(); + try { + conn = RaspberrypiDAO.getConnection(); + String selectDBQuery = "SELECT RASPBERRYPI_DEVICE_ID, DEVICE_NAME FROM RASPBERRYPI_DEVICE"; + stmt = conn.prepareStatement(selectDBQuery); + resultSet = stmt.executeQuery(); + while (resultSet.next()) { + device = new Device(); + device.setDeviceIdentifier(resultSet.getString(RaspberrypiConstants.DEVICE_PLUGIN_DEVICE_ID)); + device.setName(resultSet.getString(RaspberrypiConstants.DEVICE_PLUGIN_DEVICE_NAME)); + devices.add(device); + } + if (log.isDebugEnabled()) { + log.debug("All Raspberrypi device details have fetched from Raspberrypi database."); + } + return devices; + } catch (SQLException e) { + String msg = "Error occurred while fetching all Raspberrypi device data'"; + log.error(msg, e); + throw new RaspberrypiDeviceMgtPluginException(msg, e); + } finally { + RaspberrypiUtils.cleanupResources(stmt, resultSet); + RaspberrypiDAO.closeConnection(); + } + } +} \ No newline at end of file diff --git a/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.plugin.impl/src/main/java/org/wso2/carbon/device/mgt/iot/raspberrypi/plugin/impl/feature/RaspberrypiFeatureManager.java b/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.plugin.impl/src/main/java/org/wso2/carbon/device/mgt/iot/raspberrypi/plugin/impl/feature/RaspberrypiFeatureManager.java new file mode 100644 index 0000000000..755b7ae0d5 --- /dev/null +++ b/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.plugin.impl/src/main/java/org/wso2/carbon/device/mgt/iot/raspberrypi/plugin/impl/feature/RaspberrypiFeatureManager.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2016, 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. + */ +package org.wso2.carbon.device.mgt.iot.raspberrypi.plugin.impl.feature; + +import org.wso2.carbon.device.mgt.common.DeviceManagementException; +import org.wso2.carbon.device.mgt.common.Feature; +import org.wso2.carbon.device.mgt.common.FeatureManager; +import org.wso2.carbon.device.mgt.extensions.feature.mgt.GenericFeatureManager; +import org.wso2.carbon.device.mgt.iot.raspberrypi.plugin.constants.RaspberrypiConstants; + +import java.util.List; + +public class RaspberrypiFeatureManager implements FeatureManager { + @Override + public boolean addFeature(Feature feature) throws DeviceManagementException { + return false; + } + + @Override + public boolean addFeatures(List features) throws DeviceManagementException { + return false; + } + + @Override + public Feature getFeature(String name) throws DeviceManagementException { + GenericFeatureManager genericFeatureManager = GenericFeatureManager.getInstance(); + return genericFeatureManager.getFeature(RaspberrypiConstants.DEVICE_TYPE, name); + } + + @Override + public List getFeatures() throws DeviceManagementException { + GenericFeatureManager genericFeatureManager = GenericFeatureManager.getInstance(); + return genericFeatureManager.getFeatures(RaspberrypiConstants.DEVICE_TYPE); + } + + @Override + public boolean removeFeature(String name) throws DeviceManagementException { + return false; + } + + @Override + public boolean addSupportedFeaturesToDB() throws DeviceManagementException { + return false; + } +} diff --git a/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.plugin.impl/src/main/java/org/wso2/carbon/device/mgt/iot/raspberrypi/plugin/impl/util/DeviceSchemaInitializer.java b/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.plugin.impl/src/main/java/org/wso2/carbon/device/mgt/iot/raspberrypi/plugin/impl/util/DeviceSchemaInitializer.java new file mode 100644 index 0000000000..7919c4d764 --- /dev/null +++ b/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.plugin.impl/src/main/java/org/wso2/carbon/device/mgt/iot/raspberrypi/plugin/impl/util/DeviceSchemaInitializer.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2016, 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.wso2.carbon.device.mgt.iot.raspberrypi.plugin.impl.util; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.utils.CarbonUtils; +import org.wso2.carbon.utils.dbcreator.DatabaseCreator; + +import javax.sql.DataSource; +import java.io.File; + +/** + * Provides methods for initializing the database script. + */ +public class DeviceSchemaInitializer extends DatabaseCreator{ + + private static final Log log = LogFactory.getLog(DeviceSchemaInitializer.class); + private static final String setupSQLScriptBaseLocation = CarbonUtils.getCarbonHome() + File.separator + "dbscripts" + + File.separator + "cdm" + File.separator + "plugins" + File.separator; + + public DeviceSchemaInitializer(DataSource dataSource) { + super(dataSource); + } + + @Override + protected String getDbScriptLocation(String databaseType) { + String scriptName = databaseType + ".sql"; + if (log.isDebugEnabled()) { + log.debug("Loading database script from :" + scriptName); + } + return setupSQLScriptBaseLocation.replaceFirst("DBTYPE", databaseType) + scriptName; + } +} diff --git a/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.plugin.impl/src/main/java/org/wso2/carbon/device/mgt/iot/raspberrypi/plugin/impl/util/RaspberrypiUtils.java b/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.plugin.impl/src/main/java/org/wso2/carbon/device/mgt/iot/raspberrypi/plugin/impl/util/RaspberrypiUtils.java new file mode 100644 index 0000000000..bd08581417 --- /dev/null +++ b/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.plugin.impl/src/main/java/org/wso2/carbon/device/mgt/iot/raspberrypi/plugin/impl/util/RaspberrypiUtils.java @@ -0,0 +1,111 @@ +/* + * 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.wso2.carbon.device.mgt.iot.raspberrypi.plugin.impl.util; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.device.mgt.common.Device; +import org.wso2.carbon.device.mgt.iot.raspberrypi.plugin.constants.RaspberrypiConstants; +import org.wso2.carbon.device.mgt.iot.raspberrypi.plugin.exception.RaspberrypiDeviceMgtPluginException; + +import javax.naming.Context; +import javax.naming.InitialContext; +import javax.naming.NamingException; +import javax.sql.DataSource; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.List; +import java.util.Map; + +/** + * Contains utility methods used by Raspberrypi plugin. + */ +public class RaspberrypiUtils { + + private static Log log = LogFactory.getLog(RaspberrypiUtils.class); + + public static String getDeviceProperty(List deviceProperties, String propertyKey) { + String deviceProperty = ""; + for(Device.Property property :deviceProperties){ + if(propertyKey.equals(property.getName())){ + deviceProperty = property.getValue(); + } + } + return deviceProperty; + } + + public static Device.Property getProperty(String property, String value) { + if (property != null) { + Device.Property prop = new Device.Property(); + prop.setName(property); + prop.setValue(value); + return prop; + } + return null; + } + + public static void cleanupResources(Connection conn, PreparedStatement stmt, ResultSet rs) { + if (rs != null) { + try { + rs.close(); + } catch (SQLException e) { + log.warn("Error occurred while closing result set", e); + } + } + if (stmt != null) { + try { + stmt.close(); + } catch (SQLException e) { + log.warn("Error occurred while closing prepared statement", e); + } + } + if (conn != null) { + try { + conn.close(); + } catch (SQLException e) { + log.warn("Error occurred while closing database connection", e); + } + } + } + + public static void cleanupResources(PreparedStatement stmt, ResultSet rs) { + cleanupResources(null, stmt, rs); + } + + /** + * Creates the device management schema. + */ + public static void setupDeviceManagementSchema() throws RaspberrypiDeviceMgtPluginException { + try { + Context ctx = new InitialContext(); + DataSource dataSource = (DataSource) ctx.lookup(RaspberrypiConstants.DATA_SOURCE_NAME); + DeviceSchemaInitializer initializer = + new DeviceSchemaInitializer(dataSource); + log.info("Initializing device management repository database schema"); + initializer.createRegistryDatabase(); + } catch (NamingException e) { + log.error("Error while looking up the data source: " + RaspberrypiConstants.DATA_SOURCE_NAME); + } catch (Exception e) { + throw new RaspberrypiDeviceMgtPluginException("Error occurred while initializing Iot Device " + + "Management database schema", e); + } + } +} diff --git a/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.plugin.impl/src/main/java/org/wso2/carbon/device/mgt/iot/raspberrypi/plugin/internal/RaspberrypiManagementServiceComponent.java b/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.plugin.impl/src/main/java/org/wso2/carbon/device/mgt/iot/raspberrypi/plugin/internal/RaspberrypiManagementServiceComponent.java new file mode 100644 index 0000000000..0b78f2d852 --- /dev/null +++ b/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.plugin.impl/src/main/java/org/wso2/carbon/device/mgt/iot/raspberrypi/plugin/internal/RaspberrypiManagementServiceComponent.java @@ -0,0 +1,85 @@ +/* + * 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.wso2.carbon.device.mgt.iot.raspberrypi.plugin.internal; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.osgi.framework.BundleContext; +import org.osgi.framework.ServiceRegistration; +import org.osgi.service.component.ComponentContext; +import org.wso2.carbon.device.mgt.common.spi.DeviceManagementService; +import org.wso2.carbon.device.mgt.iot.raspberrypi.plugin.exception.RaspberrypiDeviceMgtPluginException; +import org.wso2.carbon.device.mgt.iot.raspberrypi.plugin.impl.RaspberrypiManagerService; +import org.wso2.carbon.device.mgt.iot.raspberrypi.plugin.impl.util.RaspberrypiUtils; + +/** + * @scr.component name="org.wso2.carbon.device.mgt.iot.raspberrypi.internal.RaspberrypiManagementServiceComponent" + * immediate="true" + */ +public class RaspberrypiManagementServiceComponent { + + private ServiceRegistration raspberrypiServiceRegRef; + private static final Log log = LogFactory.getLog(RaspberrypiManagementServiceComponent.class); + + protected void activate(ComponentContext ctx) { + if (log.isDebugEnabled()) { + log.debug("Activating Raspberrypi Device Management Service Component"); + } + try { + BundleContext bundleContext = ctx.getBundleContext(); + raspberrypiServiceRegRef = + bundleContext.registerService(DeviceManagementService.class.getName(), + new RaspberrypiManagerService(), null); + String setupOption = System.getProperty("setup"); + if (setupOption != null) { + if (log.isDebugEnabled()) { + log.debug("-Dsetup is enabled. Iot Device management repository schema initialization is about " + + "to begin"); + } + try { + RaspberrypiUtils.setupDeviceManagementSchema(); + } catch (RaspberrypiDeviceMgtPluginException e) { + log.error("Exception occurred while initializing device management database schema", e); + } + } + if (log.isDebugEnabled()) { + log.debug("Raspberrypi Device Management Service Component has been successfully activated"); + } + } catch (Throwable e) { + log.error( + "Error occurred while activating Raspberrypi Device Management Service Component", e); + } + } + + protected void deactivate(ComponentContext ctx) { + if (log.isDebugEnabled()) { + log.debug("De-activating Raspberrypi Device Management Service Component"); + } + try { + if (raspberrypiServiceRegRef != null) { + raspberrypiServiceRegRef.unregister(); + } + if (log.isDebugEnabled()) { + log.debug("Raspberrypi Device Management Service Component has been successfully de-activated"); + } + } catch (Throwable e) { + log.error("Error occurred while de-activating Raspberrypi Device Management bundle", e); + } + } +} diff --git a/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.ui/pom.xml b/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.ui/pom.xml new file mode 100644 index 0000000000..29e8c9e80d --- /dev/null +++ b/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.ui/pom.xml @@ -0,0 +1,62 @@ + + + + + + + + raspberrypi-plugin + org.wso2.carbon.devicemgt-plugins + 2.1.0-SNAPSHOT + ../pom.xml + + + 4.0.0 + org.wso2.carbon.device.mgt.iot.raspberrypi.ui + WSO2 Carbon - IoT Server RaspberryPi UI + pom + + + + + maven-assembly-plugin + 2.5.5 + + ${project.artifactId}-${carbon.device.mgt.version} + false + + src/assembly/src.xml + + + + + create-archive + package + + single + + + + + + + + \ No newline at end of file diff --git a/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.ui/src/assembly/src.xml b/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.ui/src/assembly/src.xml new file mode 100644 index 0000000000..2797034e07 --- /dev/null +++ b/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.ui/src/assembly/src.xml @@ -0,0 +1,36 @@ + + + + src + + zip + + false + ${basedir}/src + + + ${basedir}/src/main/resources/jaggeryapps/devicemgt + / + true + + + \ No newline at end of file diff --git a/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.raspberrypi.device-view/device-view.hbs b/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.raspberrypi.device-view/device-view.hbs new file mode 100644 index 0000000000..d3c6c39b21 --- /dev/null +++ b/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.raspberrypi.device-view/device-view.hbs @@ -0,0 +1,68 @@ +{{#zone "topCss"}} + +{{/zone}} + +{{#zone "device-thumbnail"}} + +{{/zone}} + +{{#zone "device-opetations"}} +

      + Operations +
      +
      + {{unit "iot.unit.device.operation-bar" device=device}} +
      +{{/zone}} + +{{#zone "device-detail-properties"}} +
      + +
      +
      + +
      +
      Device Statistics
      + {{unit "iot.unit.device.stats" device=device}} +
      +
      +
      Operations Log
      +
      + +
      +
      + Not available yet +
      +
      +
      +
      +
      +
      +
      +
      +{{/zone}} \ No newline at end of file diff --git a/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.raspberrypi.device-view/device-view.js b/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.raspberrypi.device-view/device-view.js new file mode 100644 index 0000000000..ea9439d4e2 --- /dev/null +++ b/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.raspberrypi.device-view/device-view.js @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2016, 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") { + return {"device": device}; + } else { + response.sendError(404, "Device Id " + deviceId + "of type " + deviceType + " cannot be found!"); + exit(); + } + } +} \ No newline at end of file diff --git a/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.raspberrypi.device-view/device-view.json b/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.raspberrypi.device-view/device-view.json new file mode 100644 index 0000000000..9eecd8f5bf --- /dev/null +++ b/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.raspberrypi.device-view/device-view.json @@ -0,0 +1,3 @@ +{ + "version": "1.0.0" +} \ No newline at end of file diff --git a/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.raspberrypi.device-view/public/images/respberry-icon.png b/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.raspberrypi.device-view/public/images/respberry-icon.png new file mode 100644 index 0000000000..fe7065142f Binary files /dev/null and b/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.raspberrypi.device-view/public/images/respberry-icon.png differ diff --git a/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.raspberrypi.device-view/public/images/thumb.png b/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.raspberrypi.device-view/public/images/thumb.png new file mode 100644 index 0000000000..510859d86c Binary files /dev/null and b/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.raspberrypi.device-view/public/images/thumb.png differ diff --git a/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.raspberrypi.type-view/public/images/myDevices_analytics.png b/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.raspberrypi.type-view/public/images/myDevices_analytics.png new file mode 100644 index 0000000000..51d63966a4 Binary files /dev/null and b/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.raspberrypi.type-view/public/images/myDevices_analytics.png differ diff --git a/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.raspberrypi.type-view/public/images/respberry-icon.png b/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.raspberrypi.type-view/public/images/respberry-icon.png new file mode 100644 index 0000000000..fe7065142f Binary files /dev/null and b/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.raspberrypi.type-view/public/images/respberry-icon.png differ diff --git a/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.raspberrypi.type-view/public/images/schematicsGuide.png b/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.raspberrypi.type-view/public/images/schematicsGuide.png new file mode 100644 index 0000000000..e2a8c544c8 Binary files /dev/null and b/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.raspberrypi.type-view/public/images/schematicsGuide.png differ diff --git a/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.raspberrypi.type-view/public/images/thumb.png b/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.raspberrypi.type-view/public/images/thumb.png new file mode 100644 index 0000000000..510859d86c Binary files /dev/null and b/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.raspberrypi.type-view/public/images/thumb.png differ diff --git a/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.raspberrypi.type-view/public/js/download.js b/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.raspberrypi.type-view/public/js/download.js new file mode 100644 index 0000000000..c8901643b3 --- /dev/null +++ b/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.raspberrypi.type-view/public/js/download.js @@ -0,0 +1,183 @@ +/* + * Copyright (c) 2016, 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. + */ + +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; + } + }); +} + +/* + * 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() { + var deviceName; + $('.new-device-name').each(function () { + if (this.value != "") { + deviceName = this.value; + } + }); + var deviceNameFormat = /^[^~?!#$:;%^*`+={}\[\]\\()|<>,'"]{1,30}$/; + if (deviceName && deviceNameFormat.test(deviceName)) { + $('#downloadForm').submit(); + hidePopup(); + $(modalPopupContent).html($('#device-agent-downloading-content').html()); + showPopup(); + setTimeout(function () { + hidePopup(); + }, 1000); + }else { + $("#invalid-username-error-msg span").text("Invalid device name"); + $("#invalid-username-error-msg").removeClass("hidden"); + } +} + +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/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.raspberrypi.type-view/public/js/jquery.validate.js b/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.raspberrypi.type-view/public/js/jquery.validate.js new file mode 100644 index 0000000000..fe7ecf07a6 --- /dev/null +++ b/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.raspberrypi.type-view/public/js/jquery.validate.js @@ -0,0 +1,1227 @@ +/* + * Copyright (c) 2016, 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($) { + +$.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/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.raspberrypi.type-view/type-view.hbs b/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.raspberrypi.type-view/type-view.hbs new file mode 100644 index 0000000000..67e4d0225d --- /dev/null +++ b/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.raspberrypi.type-view/type-view.hbs @@ -0,0 +1,324 @@ +
      +

      Raspberry Pi

      +
      +
      + +
      + +
      + +
      + +
      + +
      +

      What it Does

      +
      +

      Connect a RaspberryPi to WSO2 IoT Server and manage it.

      +
      +

      What You Need

      +
      +
        +
      • + ITEM 01 +    Raspberry Pi with SD Card(Internet Enabled [Wifi or Ethernet]). +
      • +
      • + ITEM 02 +    Adafruit DHT11 Temperature Sensor. +
      • +
      • + ITEM 03 +    LED Bulb. +
      • +
      • + ITEM 04 +    Resister (eg : 470 ohm). +
      • +
      • + STEP 05 +    Proceed to [Prepare] section. +
      +
      + View API   + + + Download Agent + +
      + +
      +
      + +
      +
      + +
      +
      + +
      +
      + +
      +
      + +
      +
      + +
      +

      +
      +
      +

      Prepare

      +
      +
        +
      • + 01 +    Connect RaspberryPi, DHT11 Temperature sensor and LED as per the + schematic below. +
      • +
      • + 02 +    Ensure your RaspberryPi Board can connect to Internet. +
      • +
      • + 03 +    Download RaspberryPi Agent via [Download Agent] button above. + (Alternatively you can use the "curl" command to directly download the Agent to your + RaspberryPi.) +
      • +
      • + 04 +    Copy downloaded Agent into your RaspberryPi. +
      • +
      • + 05 +    Unzip the downloaded Agent and start terminal to run below commands. +
      • +
      • +                + + + + +    [sudo ./testAgent.sh]    -    Script to test agent + functionality. +
      • +
      • +                + + + + +    [sudo ./startService.sh]    -    Use this to start + agent as Daemon. +
      • +
      +
      +
      +
      +

      Schematic Diagram

      +
      +

      Click on the image to zoom

      +
      + + + +
      +
      +
      +
      +

      Try Out

      +
      +
        +
      • + 01 +    You can view all your connected devices at + [Device Management] page. +
      • +
      • + 02 +    Select one of connected devices and check for available control + operations and monitor Real-Time data. +
      • +
      • + 03 +    You can also view analytics of the data published to IoT-Server by + navigating to Device Analytics page. +
      • +
      +
      +

      Click on the image to zoom

      +
      + + + +
      +
      + +{{#zone "topCss"}} + +{{/zone}} + +{{#zone "bottomJs"}} + {{js "/js/download.js"}} + {{js "/js/jquery.validate.js"}} +{{/zone}} \ No newline at end of file diff --git a/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.raspberrypi.type-view/type-view.json b/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.raspberrypi.type-view/type-view.json new file mode 100644 index 0000000000..9eecd8f5bf --- /dev/null +++ b/components/iot-plugins/raspberrypi-plugin/org.wso2.carbon.device.mgt.iot.raspberrypi.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.raspberrypi.type-view/type-view.json @@ -0,0 +1,3 @@ +{ + "version": "1.0.0" +} \ No newline at end of file diff --git a/components/iot-plugins/raspberrypi-plugin/pom.xml b/components/iot-plugins/raspberrypi-plugin/pom.xml new file mode 100644 index 0000000000..3b85d06d0f --- /dev/null +++ b/components/iot-plugins/raspberrypi-plugin/pom.xml @@ -0,0 +1,62 @@ + + + + + + + org.wso2.carbon.devicemgt-plugins + iot-plugins + 2.1.0-SNAPSHOT + ../pom.xml + + + 4.0.0 + raspberrypi-plugin + pom + WSO2 Carbon - Arduino Plugin + http://wso2.org + + + org.wso2.carbon.device.mgt.iot.raspberrypi.manager.service.impl + org.wso2.carbon.device.mgt.iot.raspberrypi.plugin.impl + org.wso2.carbon.device.mgt.iot.raspberrypi.ui + org.wso2.carbon.device.mgt.iot.raspberrypi.analytics + org.wso2.carbon.device.mgt.iot.raspberrypi.controller.service.impl + + + + + + + org.apache.felix + maven-scr-plugin + 1.7.2 + + + generate-scr-scrdescriptor + + scr + + + + + + + + diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.impl/pom.xml b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.impl/pom.xml new file mode 100644 index 0000000000..99bbf77dba --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.impl/pom.xml @@ -0,0 +1,255 @@ + + + + + 4.0.0 + + + virtual-fire-alarm-plugin + org.wso2.carbon.devicemgt-plugins + 2.1.0-SNAPSHOT + ../pom.xml + + + org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.impl + WSO2 Carbon - IoT Server VirtualFireAlarm Advanced Agent + WSO2 Carbon - VirtualFireAlarm Device Advanced Agent Implementation + http://wso2.org + + + + + org.apache.maven.plugins + maven-compiler-plugin + + UTF-8 + ${wso2.maven.compiler.source} + ${wso2.maven.compiler.target} + + 2.3.2 + + + + org.apache.maven.plugins + maven-assembly-plugin + + + + + org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.Bootstrap + + + + + jar-with-dependencies + + wso2-firealarm-virtual-agent-advanced + false + + + + make-assembly + + package + + + single + + + + + + + + + + + + org.eclipse.jetty + jetty-server + ${jetty.version} + + + + + org.eclipse.paho + org.eclipse.paho.client.mqttv3 + ${paho.mqtt.version} + + + + + org.igniterealtime.smack.wso2 + smack + ${smack.wso2.version} + + + org.igniterealtime.smack.wso2 + smackx + ${smackx.wso2.version} + + + + + org.bouncycastle.wso2 + bcprov-jdk15on + ${bcprov.wso2.version} + + + org.bouncycastle.wso2 + bcpkix-jdk15on + ${bcpkix.wso2.version} + + + + + com.google.code.jscep.wso2 + jscep + ${jscep.version} + + + + commons-codec.wso2 + commons-codec + + + + commons-lang + commons-lang + ${commons-lang.version} + + + + commons-logging + commons-logging + ${common-logging.version} + + + + commons-io + commons-io + ${commons.io} + + + + + org.slf4j + slf4j-api + ${slf4j.version} + + + org.slf4j + slf4j-simple + ${slf4j.version} + + + + org.json.wso2 + json + + + + + org.wso2.siddhi + siddhi-query + ${sidhdhi.version} + + + org.wso2.siddhi + siddhi-core + ${sidhdhi.version} + + + + org.apache.httpcomponents + httpclient + ${apache-httpclient.version} + + + + com.google.code.gson + gson + ${gson.version} + + + + + + + + + wso2-nexus + WSO2 internal Repository + http://maven.wso2.org/nexus/content/groups/wso2-public/ + + true + daily + ignore + + + + wso2-maven2-repository + http://dist.wso2.org/maven2 + + + + + + 1.7 + 1.7 + + + 8.1.3.v20120416 + + + 1.0.2 + + + 3.0.4.wso2v1 + 3.0.4.wso2v1 + + + 1.49.wso2v1 + 1.49.wso2v1 + + + 2.0.2.wso2v2 + + + 1.2.17 + 1.2 + 2.4 + 1.7 + 2.6 + + + 4.5 + + + 1.7.13 + + + 2.1.0-wso2v1 + + 2.3.1 + + + diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/advanced/Bootstrap.java b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/advanced/Bootstrap.java new file mode 100644 index 0000000000..663a207f9c --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/advanced/Bootstrap.java @@ -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. + */ + +package org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced; + +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.core.AgentManager; + +public class Bootstrap { + + /** + * @param args the command line arguments + */ + public static void main(String[] args) { + System.setProperty("org.apache.commons.logging.Log", "org.apache.commons.logging.impl.SimpleLog"); + System.setProperty("org.apache.commons.logging.simplelog.defaultlog", "info"); + System.setProperty("org.apache.commons.logging.simplelog.showdatetime", "true"); + System.setProperty("org.apache.commons.logging.simplelog.dateTimeFormat", "HH:mm:ss"); + AgentManager.getInstance().init(); + } + +} diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/advanced/communication/http/FireAlarmHTTPCommunicator.java b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/advanced/communication/http/FireAlarmHTTPCommunicator.java new file mode 100644 index 0000000000..bc4a7bfb77 --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/advanced/communication/http/FireAlarmHTTPCommunicator.java @@ -0,0 +1,495 @@ +/* + * 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.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.communication.http; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.handler.AbstractHandler; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.core.AgentConstants; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.core.AgentManager; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.exception.AgentCoreOperationException; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.transport.TransportHandlerException; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.transport.TransportUtils; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.transport.http.HTTPTransportHandler; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.DataOutputStream; +import java.io.File; +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.Inet4Address; +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.net.ProtocolException; +import java.net.SocketException; +import java.net.UnknownHostException; +import java.util.Enumeration; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +public class FireAlarmHTTPCommunicator extends HTTPTransportHandler { + private static final Log log = LogFactory.getLog(FireAlarmHTTPCommunicator.class); + + private ScheduledExecutorService service = Executors.newScheduledThreadPool(2); + private ScheduledFuture dataPushServiceHandler; + private ScheduledFuture connectorServiceHandler; + + public FireAlarmHTTPCommunicator() { + super(); + } + + public FireAlarmHTTPCommunicator(int port) { + super(port); + } + + public FireAlarmHTTPCommunicator(int port, int reconnectionInterval) { + super(port, reconnectionInterval); + } + + public ScheduledFuture getDataPushServiceHandler() { + return dataPushServiceHandler; + } + + public void connect() { + Runnable connect = new Runnable() { + public void run() { + if (!isConnected()) { + try { + processIncomingMessage(); + server.start(); + registerThisDevice(); + publishDeviceData(); + log.info("HTTP Server started at port: " + port); + + } catch (Exception e) { + if (log.isDebugEnabled()) { + log.warn("Unable to 'START' HTTP server. Will retry after " + + timeoutInterval / 1000 + " seconds."); + } + } + } + } + }; + + connectorServiceHandler = service.scheduleAtFixedRate(connect, 0, timeoutInterval, + TimeUnit.MILLISECONDS); + } + + + @Override + public void processIncomingMessage() { + server.setHandler(new AbstractHandler() { + public void handle(String s, Request request, HttpServletRequest + httpServletRequest, + HttpServletResponse httpServletResponse) + throws IOException, ServletException { + httpServletResponse.setContentType("text/html;charset=utf-8"); + httpServletResponse.setStatus(HttpServletResponse.SC_OK); + request.setHandled(true); + + AgentManager agentManager = AgentManager.getInstance(); + String pathContext = request.getPathInfo(); + String separator = (File.separatorChar == '\\') ? ("\\\\") : (File.separator); + + if (pathContext.toUpperCase().contains( + separator + AgentConstants.TEMPERATURE_CONTROL)) { + httpServletResponse.getWriter().println( + agentManager.getTemperature()); + + } else if (pathContext.toUpperCase().contains( + separator + AgentConstants.HUMIDITY_CONTROL)) { + httpServletResponse.getWriter().println( + agentManager.getHumidity()); + + } else if (pathContext.toUpperCase().contains( + separator + AgentConstants.BULB_CONTROL)) { + String[] pathVariables = pathContext.split(separator); + + if (pathVariables.length != 3) { + httpServletResponse.getWriter().println( + "Invalid BULB-control received by the device. Need to be in " + + "'{host}:{port}/BULB/{ON|OFF}' format."); + return; + } + + String switchState = pathVariables[2]; + + if (switchState == null) { + httpServletResponse.getWriter().println( + "Please specify switch-status of the BULB."); + } else { + boolean status = switchState.toUpperCase().equals( + AgentConstants.CONTROL_ON); + agentManager.changeAlarmStatus(status); + httpServletResponse.getWriter().println("Bulb is " + (status ? + AgentConstants.CONTROL_ON : AgentConstants.CONTROL_OFF)); + } + } else { + httpServletResponse.getWriter().println( + "Invalid control command received by the device."); + } + } + }); + } + + @Override + public void publishDeviceData() { + final AgentManager agentManager = AgentManager.getInstance(); + int publishInterval = agentManager.getPushInterval(); + final String deviceOwner = agentManager.getAgentConfigs().getDeviceOwner(); + final String deviceID = agentManager.getAgentConfigs().getDeviceId(); + boolean simulationMode = false; + int duration = 2 * 60; + int frequency = 5; + + Runnable pushDataRunnable = new Runnable() { + @Override + public void run() { + + String pushDataPayload = String.format(AgentConstants.PUSH_DATA_PAYLOAD, deviceOwner, + deviceID, (agentManager.getDeviceIP() + ":" + port), + agentManager.getTemperature()); + executeDataPush(pushDataPayload); + } + }; + + if (!simulationMode) { + dataPushServiceHandler = service.scheduleAtFixedRate(pushDataRunnable, publishInterval, + publishInterval, + TimeUnit.SECONDS); + } else { + String pushDataPayload = String.format(AgentConstants.PUSH_SIMULATION_DATA_PAYLOAD, deviceOwner, + deviceID, (agentManager.getDeviceIP() + ":" + port), + agentManager.getTemperature(), true, duration, frequency); + executeDataPush(pushDataPayload); + + } + } + + + private void executeDataPush(String pushDataPayload) { + AgentManager agentManager = AgentManager.getInstance(); + int responseCode = -1; + String pushDataEndPointURL = agentManager.getPushDataAPIEP(); + HttpURLConnection httpConnection = null; + + try { + httpConnection = TransportUtils.getHttpConnection(agentManager.getPushDataAPIEP()); + httpConnection.setRequestMethod(AgentConstants.HTTP_POST); + httpConnection.setRequestProperty("Authorization", "Bearer " + + agentManager.getAgentConfigs().getAuthToken()); + httpConnection.setRequestProperty("Content-Type", + AgentConstants.APPLICATION_JSON_TYPE); + + httpConnection.setDoOutput(true); + DataOutputStream dataOutPutWriter = new DataOutputStream( + httpConnection.getOutputStream()); + dataOutPutWriter.writeBytes(pushDataPayload); + dataOutPutWriter.flush(); + dataOutPutWriter.close(); + + responseCode = httpConnection.getResponseCode(); + httpConnection.disconnect(); + + log.info(AgentConstants.LOG_APPENDER + "Message - '" + pushDataPayload + + "' was published to server at: " + httpConnection.getURL()); + + } catch (ProtocolException exception) { + String errorMsg = + "Protocol specific error occurred when trying to set method to " + + AgentConstants.HTTP_POST + " for:" + pushDataEndPointURL; + log.error(AgentConstants.LOG_APPENDER + errorMsg); + + } catch (IOException exception) { + String errorMsg = + "An IO error occurred whilst trying to get the response code from: " + + pushDataEndPointURL + " for a " + AgentConstants.HTTP_POST + + " " + "method."; + log.error(AgentConstants.LOG_APPENDER + errorMsg); + + } catch (TransportHandlerException exception) { + log.error(AgentConstants.LOG_APPENDER + + "Error encountered whilst trying to create HTTP-Connection " + + "to IoT-Server EP at: " + + pushDataEndPointURL); + } + + if (responseCode == HttpStatus.CONFLICT_409 || + responseCode == HttpStatus.PRECONDITION_FAILED_412) { + log.warn(AgentConstants.LOG_APPENDER + + "DeviceIP is being Re-Registered due to Push-Data failure " + + "with response code: " + + responseCode); + registerThisDevice(); + + } else if (responseCode != HttpStatus.NO_CONTENT_204) { + if (log.isDebugEnabled()) { + log.error(AgentConstants.LOG_APPENDER + "Status Code: " + responseCode + + " encountered whilst trying to Push-Device-Data to IoT " + + "Server at: " + + agentManager.getPushDataAPIEP()); + } + agentManager.updateAgentStatus(AgentConstants.SERVER_NOT_RESPONDING); + } + + if (log.isDebugEnabled()) { + log.debug(AgentConstants.LOG_APPENDER + "Push-Data call with payload - " + + pushDataPayload + ", to IoT Server returned status " + + responseCode); + } + } + + @Override + public void disconnect() { + Runnable stopConnection = new Runnable() { + public void run() { + while (isConnected()) { + try { + dataPushServiceHandler.cancel(true); + connectorServiceHandler.cancel(true); + closeConnection(); + } catch (Exception e) { + if (log.isDebugEnabled()) { + log.warn(AgentConstants.LOG_APPENDER + + "Unable to 'STOP' HTTP server at port: " + port); + } + + try { + Thread.sleep(timeoutInterval); + } catch (InterruptedException e1) { + log.error(AgentConstants.LOG_APPENDER + + "HTTP-Termination: Thread Sleep Interrupt " + + "Exception"); + } + } + } + } + }; + + Thread terminatorThread = new Thread(stopConnection); + terminatorThread.setDaemon(true); + terminatorThread.start(); + } + + @Override + public void processIncomingMessage(Object message, String... messageParams) { + + } + + @Override + public void publishDeviceData(String... publishData) { + + } + + public void registerThisDevice() { + final AgentManager agentManager = AgentManager.getInstance(); + agentManager.updateAgentStatus("Registering..."); + + final Runnable ipRegistration = new Runnable() { + @Override + public void run() { + while (isConnected()) { + try { + int responseCode = registerDeviceIP( + agentManager.getAgentConfigs().getDeviceOwner(), + agentManager.getAgentConfigs().getDeviceId()); + + if (responseCode == HttpStatus.OK_200) { + agentManager.updateAgentStatus(AgentConstants.REGISTERED); + break; + } else { + log.error(AgentConstants.LOG_APPENDER + + "Device Registration with IoT Server at:" + " " + + agentManager.getIpRegistrationEP() + + " failed with response - '" + responseCode + ":" + + HttpStatus.getMessage(responseCode) + "'"); + agentManager.updateAgentStatus(AgentConstants.RETRYING_TO_REGISTER); + } + } catch (AgentCoreOperationException exception) { + log.error(AgentConstants.LOG_APPENDER + + "Error encountered whilst trying to register the " + + "Device's IP at: " + + agentManager.getIpRegistrationEP() + + ".\nCheck whether the network-interface provided is " + + "accurate"); + agentManager.updateAgentStatus(AgentConstants.REGISTRATION_FAILED); + } + + try { + Thread.sleep(timeoutInterval); + } catch (InterruptedException e1) { + log.error(AgentConstants.LOG_APPENDER + + "Device Registration: Thread Sleep Interrupt Exception"); + } + } + } + }; + + Thread ipRegisterThread = new Thread(ipRegistration); + ipRegisterThread.setDaemon(true); + ipRegisterThread.start(); + } + + + /** + * This method calls the "Register-API" of the IoT Server in order to register the device's IP + * against its ID. + * + * @param deviceOwner the owner of the device by whose name the agent was downloaded. + * (Read from configuration file) + * @param deviceID the deviceId that is auto-generated whilst downloading the agent. + * (Read from configuration file) + * @return the status code of the HTTP-Post call to the Register-API of the IoT-Server + * @throws AgentCoreOperationException if any errors occur when an HTTPConnection session is + * created + */ + private int registerDeviceIP(String deviceOwner, String deviceID) + throws AgentCoreOperationException { + int responseCode = -1; + final AgentManager agentManager = AgentManager.getInstance(); + + String networkInterface = agentManager.getNetworkInterface(); + String deviceIPAddress = getDeviceIP(networkInterface); + + if (deviceIPAddress == null) { + throw new AgentCoreOperationException( + "An IP address could not be retrieved for the selected network interface - '" + + networkInterface + "."); + } + + agentManager.setDeviceIP(deviceIPAddress); + log.info(AgentConstants.LOG_APPENDER + "Device IP Address: " + deviceIPAddress); + + String deviceIPRegistrationEP = agentManager.getIpRegistrationEP(); + String registerEndpointURLString = + deviceIPRegistrationEP + File.separator + deviceOwner + File.separator + deviceID + + File.separator + deviceIPAddress + File.separator + port; + + if (log.isDebugEnabled()) { + log.debug(AgentConstants.LOG_APPENDER + "DeviceIP Registration EndPoint: " + + registerEndpointURLString); + } + + HttpURLConnection httpConnection; + try { + httpConnection = TransportUtils.getHttpConnection(registerEndpointURLString); + } catch (TransportHandlerException e) { + String errorMsg = + "Protocol specific error occurred when trying to fetch an HTTPConnection to:" + + " " + + registerEndpointURLString; + log.error(AgentConstants.LOG_APPENDER + errorMsg); + throw new AgentCoreOperationException(); + } + + try { + httpConnection.setRequestMethod(AgentConstants.HTTP_POST); + httpConnection.setRequestProperty("Authorization", "Bearer " + + agentManager.getAgentConfigs().getAuthToken()); + httpConnection.setDoOutput(true); + responseCode = httpConnection.getResponseCode(); + + } catch (ProtocolException exception) { + String errorMsg = "Protocol specific error occurred when trying to set method to " + + AgentConstants.HTTP_POST + " for:" + registerEndpointURLString; + log.error(AgentConstants.LOG_APPENDER + errorMsg); + throw new AgentCoreOperationException(errorMsg, exception); + + } catch (IOException exception) { + String errorMsg = "An IO error occurred whilst trying to get the response code from:" + + " " + + registerEndpointURLString + " for a " + AgentConstants.HTTP_POST + " method."; + log.error(AgentConstants.LOG_APPENDER + errorMsg); + throw new AgentCoreOperationException(errorMsg, exception); + } + + log.info(AgentConstants.LOG_APPENDER + "DeviceIP - " + deviceIPAddress + + ", registration with IoT Server at : " + + agentManager.getAgentConfigs().getHTTPS_ServerEndpoint() + + " returned status " + + responseCode); + + return responseCode; + } + + /*------------------------------------------------------------------------------------------*/ + /* Utility methods relevant to creating and sending HTTP requests to the Iot-Server */ + /*------------------------------------------------------------------------------------------*/ + + /** + * This method is used to get the IP of the device in which the agent is run on. + * + * @return the IP Address of the device + * @throws AgentCoreOperationException if any errors occur whilst trying to get the IP address + */ + private String getDeviceIP() throws AgentCoreOperationException { + try { + return Inet4Address.getLocalHost().getHostAddress(); + } catch (UnknownHostException e) { + String errorMsg = "Error encountered whilst trying to get the device IP address."; + log.error(AgentConstants.LOG_APPENDER + errorMsg); + throw new AgentCoreOperationException(errorMsg, e); + } + } + + /** + * This is an overloaded method that fetches the public IPv4 address of the given network + * interface + * + * @param networkInterfaceName the network-interface of whose IPv4 address is to be retrieved + * @return the IP Address iof the device + * @throws AgentCoreOperationException if any errors occur whilst trying to get details of the + * given network interface + */ + private String getDeviceIP(String networkInterfaceName) throws + AgentCoreOperationException { + String ipAddress = null; + try { + Enumeration interfaceIPAddresses = NetworkInterface.getByName( + networkInterfaceName).getInetAddresses(); + for (; interfaceIPAddresses.hasMoreElements(); ) { + InetAddress ip = interfaceIPAddresses.nextElement(); + ipAddress = ip.getHostAddress(); + if (log.isDebugEnabled()) { + log.debug(AgentConstants.LOG_APPENDER + "IP Address: " + ipAddress); + } + + if (TransportUtils.validateIPv4(ipAddress)) { + return ipAddress; + } + } + } catch (SocketException | NullPointerException exception) { + String errorMsg = + "Error encountered whilst trying to get IP Addresses of the network interface: " + + networkInterfaceName + + ".\nPlease check whether the name of the network interface used is correct"; + log.error(AgentConstants.LOG_APPENDER + errorMsg); + throw new AgentCoreOperationException(errorMsg, exception); + } + + return ipAddress; + } + +} diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/advanced/communication/mqtt/FireAlarmMQTTCommunicator.java b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/advanced/communication/mqtt/FireAlarmMQTTCommunicator.java new file mode 100644 index 0000000000..50e7f384ea --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/advanced/communication/mqtt/FireAlarmMQTTCommunicator.java @@ -0,0 +1,294 @@ +/* + * 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.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.communication.mqtt; + +import com.google.gson.Gson; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.eclipse.paho.client.mqttv3.MqttException; +import org.eclipse.paho.client.mqttv3.MqttMessage; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.core.AgentConstants; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.core.AgentManager; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.core.AgentUtilOperations; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.exception.AgentCoreOperationException; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.transport.TransportHandlerException; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.transport.mqtt.MQTTTransportHandler; + +import java.nio.charset.StandardCharsets; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +public class FireAlarmMQTTCommunicator extends MQTTTransportHandler { + + private static final Log log = LogFactory.getLog(FireAlarmMQTTCommunicator.class); + private static final Gson gson = new Gson(); + + private ScheduledExecutorService service = Executors.newScheduledThreadPool(2); + private ScheduledFuture dataPushServiceHandler; + + public FireAlarmMQTTCommunicator(String deviceOwner, String deviceType, + String mqttBrokerEndPoint, String subscribeTopic) { + super(deviceOwner, deviceType, mqttBrokerEndPoint, subscribeTopic); + } + + @SuppressWarnings("unused") + public FireAlarmMQTTCommunicator(String deviceOwner, String deviceType, + String mqttBrokerEndPoint, String subscribeTopic, + int intervalInMillis) { + super(deviceOwner, deviceType, mqttBrokerEndPoint, subscribeTopic, intervalInMillis); + } + + public ScheduledFuture getDataPushServiceHandler() { + return dataPushServiceHandler; + } + + //TODO:: Terminate logs with a period + //TODO: Need to print exceptions + @Override + public void connect() { + final AgentManager agentManager = AgentManager.getInstance(); + Runnable connector = new Runnable() { + public void run() { + while (!isConnected()) { + try { + connectToQueue(); + agentManager.updateAgentStatus("Connected to MQTT Queue"); + } catch (TransportHandlerException e) { + log.warn(AgentConstants.LOG_APPENDER + "Connection to MQTT Broker at: " + mqttBrokerEndPoint + + " failed.\n Will retry in " + timeoutInterval + " milli-seconds."); + } + + try { + subscribeToQueue(); + agentManager.updateAgentStatus("Subscribed to MQTT Queue"); + publishDeviceData(); + + } catch (TransportHandlerException e) { + log.warn(AgentConstants.LOG_APPENDER + "Subscription to MQTT Broker at: " + + mqttBrokerEndPoint + " failed"); + agentManager.updateAgentStatus("Subscription to broker failed."); + } + + try { + Thread.sleep(timeoutInterval); + } catch (InterruptedException ex) { + log.error(AgentConstants.LOG_APPENDER + "MQTT: Connect-Thread Sleep Interrupt Exception."); + } + } + } + }; + + Thread connectorThread = new Thread(connector); + connectorThread.setDaemon(true); + connectorThread.start(); + } + + + @Override + public void processIncomingMessage(MqttMessage message, String... messageParams) { + final AgentManager agentManager = AgentManager.getInstance(); + String serverName = agentManager.getAgentConfigs().getServerName(); + String deviceOwner = agentManager.getAgentConfigs().getDeviceOwner(); + String deviceID = agentManager.getAgentConfigs().getDeviceId(); + String receivedMessage; + String replyMessage; + String securePayLoad; + + try { + receivedMessage = message.toString(); + if(!receivedMessage.contains("POLICY")){ + receivedMessage = AgentUtilOperations.extractMessageFromPayload(receivedMessage); + } + log.info(AgentConstants.LOG_APPENDER + "Message [" + receivedMessage + "] was received"); + } catch (AgentCoreOperationException e) { + log.warn(AgentConstants.LOG_APPENDER + "Could not extract message from payload.", e); + return; + } + + + String[] controlSignal = receivedMessage.split(":"); + // message- ":" format.(ex: "BULB:ON", "TEMPERATURE", "HUMIDITY") + + try { + switch (controlSignal[0].toUpperCase()) { + case AgentConstants.BULB_CONTROL: + boolean stateToSwitch = controlSignal[1].equals(AgentConstants.CONTROL_ON); + agentManager.changeAlarmStatus(stateToSwitch); + log.info( + AgentConstants.LOG_APPENDER + "Bulb was switched to state: '" + controlSignal[1] + "'"); + break; + + case AgentConstants.TEMPERATURE_CONTROL: + int currentTemperature = agentManager.getTemperature(); + + String replyTemperature = "Current temperature was read as: '" + currentTemperature + "C'"; + log.info(AgentConstants.LOG_APPENDER + replyTemperature); + + String tempPublishTopic = String.format(AgentConstants.MQTT_PUBLISH_TOPIC, + serverName, deviceOwner, deviceID); + + replyMessage = AgentConstants.TEMPERATURE_CONTROL + ":" + currentTemperature; + securePayLoad = AgentUtilOperations.prepareSecurePayLoad(replyMessage); + publishToQueue(tempPublishTopic, securePayLoad); + break; + + case AgentConstants.HUMIDITY_CONTROL: + int currentHumidity = agentManager.getHumidity(); + + String replyHumidity = "Current humidity was read as: '" + currentHumidity + "%'"; + log.info(AgentConstants.LOG_APPENDER + replyHumidity); + + String humidPublishTopic = String.format( + AgentConstants.MQTT_PUBLISH_TOPIC, serverName, deviceOwner, deviceID); + + replyMessage = AgentConstants.HUMIDITY_CONTROL + ":" + currentHumidity; + securePayLoad = AgentUtilOperations.prepareSecurePayLoad(replyMessage); + publishToQueue(humidPublishTopic, securePayLoad); + break; + + case AgentConstants.POLICY_SIGNAL: + String policy = controlSignal[1]; + updateCEPPolicy(policy); + + default: + log.warn(AgentConstants.LOG_APPENDER + "'" + controlSignal[0] + + "' is invalid and not-supported for this device-type"); + break; + } + } catch (AgentCoreOperationException e) { + log.warn(AgentConstants.LOG_APPENDER + "Preparing Secure payload failed", e); + } catch (TransportHandlerException e) { + log.error(AgentConstants.LOG_APPENDER + + "MQTT - Publishing, reply message to the MQTT Queue at: " + + agentManager.getAgentConfigs().getMqttBrokerEndpoint() + " failed"); + } + + } + + @Override + public void publishDeviceData() { + final AgentManager agentManager = AgentManager.getInstance(); + int publishInterval = agentManager.getPushInterval(); + Runnable pushDataRunnable = new Runnable() { + @Override + public void run() { + int currentTemperature = agentManager.getTemperature(); + String message = "PUBLISHER:" + AgentConstants.TEMPERATURE_CONTROL + ":" + currentTemperature; + + try { + String payLoad = AgentUtilOperations.prepareSecurePayLoad(message); + + MqttMessage pushMessage = new MqttMessage(); + pushMessage.setPayload(payLoad.getBytes(StandardCharsets.UTF_8)); + pushMessage.setQos(DEFAULT_MQTT_QUALITY_OF_SERVICE); + pushMessage.setRetained(false); + + String topic = String.format(AgentConstants.MQTT_PUBLISH_TOPIC, + agentManager.getAgentConfigs().getServerName(), + agentManager.getAgentConfigs().getDeviceOwner(), + agentManager.getAgentConfigs().getDeviceId()); + + publishToQueue(topic, pushMessage); + log.info(AgentConstants.LOG_APPENDER + "Message: '" + message + "' published to MQTT Queue at [" + + agentManager.getAgentConfigs().getMqttBrokerEndpoint() + "] under topic [" + + topic + "]"); + + } catch (TransportHandlerException e) { + log.warn(AgentConstants.LOG_APPENDER + "Data Publish attempt to topic - [" + + AgentConstants.MQTT_PUBLISH_TOPIC + "] failed for payload [" + message + "]"); + } catch (AgentCoreOperationException e) { + log.warn(AgentConstants.LOG_APPENDER + "Preparing Secure payload failed", e); + } + } + }; + + dataPushServiceHandler = service.scheduleAtFixedRate(pushDataRunnable, publishInterval, publishInterval, + TimeUnit.SECONDS); + } + + + @Override + public void disconnect() { + Runnable stopConnection = new Runnable() { + public void run() { + while (isConnected()) { + + if (dataPushServiceHandler != null) { + dataPushServiceHandler.cancel(true); + } + + try { + closeConnection(); + + } catch (MqttException e) { + if (log.isDebugEnabled()) { + log.warn(AgentConstants.LOG_APPENDER + + "Unable to 'STOP' MQTT connection at broker at: " + + mqttBrokerEndPoint); + } + + try { + Thread.sleep(timeoutInterval); + } catch (InterruptedException e1) { + log.error(AgentConstants.LOG_APPENDER + + "MQTT-Terminator: Thread Sleep Interrupt Exception"); + } + } + } + } + }; + + Thread terminatorThread = new Thread(stopConnection); + terminatorThread.setDaemon(true); + terminatorThread.start(); + } + + @Override + public void processIncomingMessage() { + + } + + @Override + public void publishDeviceData(String... publishData) { + + } + + private boolean isJSONValid(String JSON_STRING) { + try { + gson.fromJson(JSON_STRING, Object.class); + return true; + } catch (com.google.gson.JsonSyntaxException ex) { + return false; + } + } + + + private void updateCEPPolicy(String message) { + AgentManager agentManager = AgentManager.getInstance(); + System.out.println(" Message : " + message); + String fileLocation = agentManager.getRootPath() + AgentConstants.CEP_FILE_NAME; + message = AgentUtilOperations.formatMessage(message); + AgentUtilOperations.writeToFile(message, fileLocation); + agentManager.addToPolicyLog(message); + } + + +} diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/advanced/communication/xmpp/FireAlarmXMPPCommunicator.java b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/advanced/communication/xmpp/FireAlarmXMPPCommunicator.java new file mode 100644 index 0000000000..55c3cf1d08 --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/advanced/communication/xmpp/FireAlarmXMPPCommunicator.java @@ -0,0 +1,265 @@ +/* + * 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.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.communication.xmpp; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.jivesoftware.smack.packet.Message; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.core.AgentConstants; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.core.AgentManager; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.core.AgentUtilOperations; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.exception.AgentCoreOperationException; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.transport.TransportHandlerException; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.transport.xmpp.XMPPTransportHandler; + +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +public class FireAlarmXMPPCommunicator extends XMPPTransportHandler { + + private static final Log log = LogFactory.getLog(FireAlarmXMPPCommunicator.class); + + private ScheduledExecutorService service = Executors.newScheduledThreadPool(2); + private ScheduledFuture dataPushServiceHandler; + private ScheduledFuture connectorServiceHandler; + + private String username; + private String password; + private String resource; + private String xmppAdminJID; + private String xmppDeviceJID; + + public FireAlarmXMPPCommunicator(String server) { + super(server); + } + + public FireAlarmXMPPCommunicator(String server, int port) { + super(server, port); + } + + public FireAlarmXMPPCommunicator(String server, int port, int timeout) { + super(server, port, timeout); + } + + public ScheduledFuture getDataPushServiceHandler() { + return dataPushServiceHandler; + } + + @Override + public void connect() { + final AgentManager agentManager = AgentManager.getInstance(); + username = agentManager.getAgentConfigs().getDeviceId(); + password = agentManager.getAgentConfigs().getAuthToken(); + resource = agentManager.getAgentConfigs().getDeviceOwner(); + + xmppDeviceJID = username + "@" + server; + xmppAdminJID = agentManager.getAgentConfigs().getServerName() + "_" + AgentConstants.DEVICE_TYPE + "@" + server; + + + Runnable connect = new Runnable() { + public void run() { + if (!isConnected()) { + try { + connectToServer(); + } catch (TransportHandlerException e) { + log.warn(AgentConstants.LOG_APPENDER + "Connection to XMPP server at: " + server + " failed"); + } + + try { + loginToServer(username, password, resource); + agentManager.updateAgentStatus("Connected to XMPP Server"); + setMessageFilterAndListener(xmppAdminJID, xmppDeviceJID, true); + publishDeviceData(); + } catch (TransportHandlerException e) { + log.warn(AgentConstants.LOG_APPENDER + "Login to XMPP server at: " + server + " failed"); + agentManager.updateAgentStatus("No XMPP Account for Device"); + } + } + } + }; + + connectorServiceHandler = service.scheduleAtFixedRate(connect, 0, timeoutInterval, TimeUnit.MILLISECONDS); + } + + /** + * This is an abstract method used for post processing the received XMPP-message. This + * method will be implemented as per requirement at the time of creating an object of this + * class. + * + * @param xmppMessage the xmpp message received by the listener. + */ + @Override + public void processIncomingMessage(Message xmppMessage, String... messageParams) { + final AgentManager agentManager = AgentManager.getInstance(); + String from = xmppMessage.getFrom(); + String message = xmppMessage.getBody(); + String receivedMessage; + String replyMessage; + String securePayLoad; + + try { + receivedMessage = AgentUtilOperations.extractMessageFromPayload(message); + log.info(AgentConstants.LOG_APPENDER + "Message [" + receivedMessage + "] was received"); + } catch (AgentCoreOperationException e) { + log.warn(AgentConstants.LOG_APPENDER + "Could not extract message from payload.", e); + return; + } + + String[] controlSignal = receivedMessage.split(":"); + //message- ":" format. (ex: "BULB:ON", "TEMPERATURE", "HUMIDITY") + + try { + switch (controlSignal[0].toUpperCase()) { + case AgentConstants.BULB_CONTROL: + if (controlSignal.length != 2) { + replyMessage = "BULB controls need to be in the form - 'BULB:{ON|OFF}'"; + log.warn(replyMessage); + securePayLoad = AgentUtilOperations.prepareSecurePayLoad(replyMessage); + sendXMPPMessage(xmppAdminJID, securePayLoad, "CONTROL-REPLY"); + break; + } + + agentManager.changeAlarmStatus(controlSignal[1].equals(AgentConstants.CONTROL_ON)); + log.info(AgentConstants.LOG_APPENDER + "Bulb was switched to state: '" + controlSignal[1] + "'"); + break; + + case AgentConstants.TEMPERATURE_CONTROL: + int currentTemperature = agentManager.getTemperature(); + + String replyTemperature = + "The current temperature was read to be: '" + currentTemperature + + "C'"; + log.info(AgentConstants.LOG_APPENDER + replyTemperature); + + replyMessage = AgentConstants.TEMPERATURE_CONTROL + ":" + currentTemperature; + securePayLoad = AgentUtilOperations.prepareSecurePayLoad(replyMessage); + sendXMPPMessage(xmppAdminJID, securePayLoad, "CONTROL-REPLY"); + break; + + case AgentConstants.HUMIDITY_CONTROL: + int currentHumidity = agentManager.getHumidity(); + + String replyHumidity = "The current humidity was read to be: '" + currentHumidity + "%'"; + log.info(AgentConstants.LOG_APPENDER + replyHumidity); + + replyMessage = AgentConstants.HUMIDITY_CONTROL + ":" + currentHumidity; + securePayLoad = AgentUtilOperations.prepareSecurePayLoad(replyMessage); + sendXMPPMessage(xmppAdminJID, securePayLoad, "CONTROL-REPLY"); + break; + + default: + replyMessage = "'" + controlSignal[0] + "' is invalid and not-supported for this device-type"; + log.warn(replyMessage); + securePayLoad = AgentUtilOperations.prepareSecurePayLoad(replyMessage); + sendXMPPMessage(xmppAdminJID, securePayLoad, "CONTROL-ERROR"); + break; + } + } catch (AgentCoreOperationException e) { + log.warn(AgentConstants.LOG_APPENDER + "Preparing Secure payload failed", e); + } + + } + + + @Override + public void publishDeviceData() { + final AgentManager agentManager = AgentManager.getInstance(); + int publishInterval = agentManager.getPushInterval(); + + Runnable pushDataRunnable = new Runnable() { + @Override + public void run() { + Message xmppMessage = new Message(); + + try { + int currentTemperature = agentManager.getTemperature(); + + String message = AgentConstants.TEMPERATURE_CONTROL + ":" + currentTemperature; + String payLoad = AgentUtilOperations.prepareSecurePayLoad(message); + + xmppMessage.setTo(xmppAdminJID); + xmppMessage.setSubject("PUBLISHER"); + xmppMessage.setBody(payLoad); + xmppMessage.setType(Message.Type.chat); + + sendXMPPMessage(xmppAdminJID, xmppMessage); + log.info(AgentConstants.LOG_APPENDER + "Message: '" + message + "' sent to XMPP JID - " + + "[" + xmppAdminJID + "] under subject [" + xmppMessage.getSubject() + "]."); + } catch (AgentCoreOperationException e) { + log.warn(AgentConstants.LOG_APPENDER + "Preparing Secure payload failed for XMPP JID - " + + "[" + xmppAdminJID + "] with subject - [" + xmppMessage.getSubject() + "]."); + } + } + }; + + dataPushServiceHandler = service.scheduleAtFixedRate(pushDataRunnable, publishInterval, + publishInterval, TimeUnit.SECONDS); + } + + + @Override + public void disconnect() { + Runnable stopConnection = new Runnable() { + public void run() { + + if (dataPushServiceHandler != null) { + dataPushServiceHandler.cancel(true); + } + + if (connectorServiceHandler != null) { + connectorServiceHandler.cancel(true); + } + + while (isConnected()) { + closeConnection(); + + if (log.isDebugEnabled()) { + log.warn(AgentConstants.LOG_APPENDER + + "Unable to 'STOP' connection to XMPP server at: " + server); + } + + try { + Thread.sleep(timeoutInterval); + } catch (InterruptedException e1) { + log.error(AgentConstants.LOG_APPENDER + "XMPP-Terminator: Thread Sleep Interrupt Exception"); + } + + } + } + }; + + Thread terminatorThread = new Thread(stopConnection); + terminatorThread.setDaemon(true); + terminatorThread.start(); + } + + + @Override + public void processIncomingMessage() { + + } + + @Override + public void publishDeviceData(String... publishData) { + + } + +} diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/advanced/core/AgentConfiguration.java b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/advanced/core/AgentConfiguration.java new file mode 100644 index 0000000000..49e437cc16 --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/advanced/core/AgentConfiguration.java @@ -0,0 +1,155 @@ +/* + * 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.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.core; + +/** + * A Configuration class that holds all the Agent specific details that are read from the + * 'deviceConfig.properties' file. This file is generated by the IoT-Server at the time of + * downloading the device agent from the IoT-Server. + */ +public class AgentConfiguration { + private String serverName; + private String deviceOwner; + private String deviceId; + private String deviceName; + private String controllerContext; + private String HTTPS_ServerEndpoint; + private String HTTP_ServerEndpoint; + private String apimGatewayEndpoint; + private String mqttBrokerEndpoint; + private String xmppServerEndpoint; + private String authMethod; + private String authToken; + private String refreshToken; + private int dataPushInterval; + + public String getServerName() { + return serverName; + } + + public void setServerName(String serverName) { + this.serverName = serverName; + } + + public String getDeviceOwner() { + return deviceOwner; + } + + public void setDeviceOwner(String deviceOwner) { + this.deviceOwner = deviceOwner; + } + + public String getDeviceId() { + return deviceId; + } + + public void setDeviceId(String deviceId) { + this.deviceId = deviceId; + } + + public String getDeviceName() { + return deviceName; + } + + public void setDeviceName(String deviceName) { + this.deviceName = deviceName; + } + + public String getControllerContext() { + return controllerContext; + } + + public void setControllerContext(String controllerContext) { + this.controllerContext = controllerContext; + } + + public String getHTTPS_ServerEndpoint() { + return HTTPS_ServerEndpoint; + } + + public void setHTTPS_ServerEndpoint(String HTTPS_ServerEndpoint) { + this.HTTPS_ServerEndpoint = HTTPS_ServerEndpoint; + } + + public String getHTTP_ServerEndpoint() { + return HTTP_ServerEndpoint; + } + + public void setHTTP_ServerEndpoint(String HTTP_ServerEndpoint) { + this.HTTP_ServerEndpoint = HTTP_ServerEndpoint; + } + + public String getApimGatewayEndpoint() { + return apimGatewayEndpoint; + } + + public void setApimGatewayEndpoint(String apimGatewayEndpoint) { + this.apimGatewayEndpoint = apimGatewayEndpoint; + } + + public String getMqttBrokerEndpoint() { + return mqttBrokerEndpoint; + } + + public void setMqttBrokerEndpoint(String mqttBrokerEndpoint) { + this.mqttBrokerEndpoint = mqttBrokerEndpoint; + } + + public String getXmppServerEndpoint() { + return xmppServerEndpoint; + } + + public void setXmppServerEndpoint(String xmppServerEndpoint) { + this.xmppServerEndpoint = xmppServerEndpoint; + } + + public String getAuthMethod() { + return authMethod; + } + + public void setAuthMethod(String authMethod) { + this.authMethod = authMethod; + } + + public String getAuthToken() { + return authToken; + } + + public void setAuthToken(String authToken) { + this.authToken = authToken; + } + + public String getRefreshToken() { + return refreshToken; + } + + public void setRefreshToken(String refreshToken) { + this.refreshToken = refreshToken; + } + + public int getDataPushInterval() { + return dataPushInterval; + } + + public void setDataPushInterval(int dataPushInterval) { + this.dataPushInterval = dataPushInterval; + } +} + + diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/advanced/core/AgentConstants.java b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/advanced/core/AgentConstants.java new file mode 100644 index 0000000000..038a522403 --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/advanced/core/AgentConstants.java @@ -0,0 +1,135 @@ +/* + * 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.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.core; + +public class AgentConstants { + public static final String DEVICE_TYPE = "virtual_firealarm"; + public static final String LOG_APPENDER = "AGENT_LOG:: "; + public static final String PROPERTIES_FILE_PATH = ""; + public static final int DEFAULT_RETRY_THREAD_INTERVAL = 5000; // time in millis + /* --------------------------------------------------------------------------------------- + IoT-Server specific information + --------------------------------------------------------------------------------------- */ + public static final String DEVICE_CONTROLLER_API_EP = "/virtual_firealarm/controller"; + public static final String DEVICE_ENROLLMENT_API_EP = "/scep"; + public static final String DEVICE_REGISTER_API_EP = "/register"; + public static final String DEVICE_PUSH_TEMPERATURE_API_EP = "/push_temperature"; + public static final String PUSH_DATA_PAYLOAD = + "{\"owner\":\"%s\",\"deviceId\":\"%s\",\"reply\":\"%s\",\"value\":\"%s\"}"; + + public static final String PUSH_SIMULATION_DATA_PAYLOAD = + "{\"owner\":\"%s\",\"deviceId\":\"%s\",\"reply\":\"%s\",\"value\":\"%s\",\"isSimulated\":\"%s\",\"duration\":\"%s\",\"frequency\":\"%s\"}"; + + public static final String AGENT_CONTROL_APP_EP = "/devicemgt/device/%s?id=%s"; + public static final String DEVICE_DETAILS_PAGE_EP = "/devicemgt/device/%s?id=%s"; + public static final String DEVICE_ANALYTICS_PAGE_URL = "/devicemgt/analytics?deviceId=%s&deviceType=%s&deviceName=%s"; + /* --------------------------------------------------------------------------------------- + HTTP Connection specific information for communicating with IoT-Server + --------------------------------------------------------------------------------------- */ + public static final String HTTP_POST = "POST"; + public static final String HTTP_GET = "GET"; + public static final String APPLICATION_JSON_TYPE = "application/json"; + public static final String REGISTERED = "Registered"; + public static final String NOT_REGISTERED = "Not-Registered"; + public static final String REGISTRATION_FAILED = "Registration Failed"; + public static final String RETRYING_TO_REGISTER = "Registration Failed. Re-trying.."; + public static final String SERVER_NOT_RESPONDING = "Server not responding.."; + + /* --------------------------------------------------------------------------------------- + MQTT Connection specific information + --------------------------------------------------------------------------------------- */ + public static final int DEFAULT_MQTT_RECONNECTION_INTERVAL = 2; // time in seconds + public static final int DEFAULT_MQTT_QUALITY_OF_SERVICE = 0; + public static final String MQTT_SUBSCRIBE_TOPIC = "%s/%s/" + DEVICE_TYPE + "/%s"; + public static final String MQTT_PUBLISH_TOPIC = "%s/%s/" + DEVICE_TYPE + "/%s/publisher"; + /* --------------------------------------------------------------------------------------- + XMPP Connection specific information + --------------------------------------------------------------------------------------- */ + public static final String XMPP_ADMIN_ACCOUNT_UNAME = "admin"; + /* --------------------------------------------------------------------------------------- + Device/Agent specific properties to be read from the 'deviceConfig.properties' file + --------------------------------------------------------------------------------------- */ + public static final String AGENT_PROPERTIES_FILE_NAME = "deviceConfig.properties"; + public static final String SERVER_NAME_PROPERTY = "server-name"; + public static final String DEVICE_OWNER_PROPERTY = "owner"; + public static final String DEVICE_ID_PROPERTY = "deviceId"; + public static final String DEVICE_NAME_PROPERTY = "device-name"; + public static final String DEVICE_CONTROLLER_CONTEXT_PROPERTY = "controller-context"; + public static final String SERVER_HTTPS_EP_PROPERTY = "https-ep"; + public static final String SERVER_HTTP_EP_PROPERTY = "http-ep"; + public static final String APIM_GATEWAY_EP_PROPERTY = "apim-ep"; + public static final String MQTT_BROKER_EP_PROPERTY = "mqtt-ep"; + public static final String XMPP_SERVER_EP_PROPERTY = "xmpp-ep"; + public static final String AUTH_METHOD_PROPERTY = "auth-method"; + public static final String AUTH_TOKEN_PROPERTY = "auth-token"; + public static final String REFRESH_TOKEN_PROPERTY = "refresh-token"; + public static final String NETWORK_INTERFACE_PROPERTY = "network-interface"; + public static final String PUSH_INTERVAL_PROPERTY = "push-interval"; + /* --------------------------------------------------------------------------------------- + Default values for the Device/Agent specific configurations listed above + --------------------------------------------------------------------------------------- */ + public static final String DEFAULT_SERVER_NAME = "WSO2IoTServer"; + public static final String DEFAULT_DEVICE_OWNER = "admin"; + public static final String DEFAULT_DEVICE_ID = "1234567890"; + public static final String DEFAULT_DEVICE_NAME = "admin_1234567890"; + public static final String DEFAULT_HTTPS_SERVER_EP = "https://localhost:9443"; + public static final String DEFAULT_HTTP_SERVER_EP = "http://localhost:9763"; + public static final String DEFAULT_APIM_GATEWAY_EP = "http://127.0.0.1:8281"; + public static final String DEFAULT_MQTT_BROKER_EP = "tcp://127.0.0.1:1883"; + public static final String DEFAULT_XMPP_SERVER_EP = "http://127.0.0.1:9061"; + public static final String DEFAULT_AUTH_METHOD = "token"; + public static final String DEFAULT_AUTH_TOKEN = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0987654321"; + public static final String DEFAULT_REFRESH_TOKEN = "1234567890ZYXWVUTSRQPONMKLJIHGFEDCBA"; + public static final String DEFAULT_NETWORK_INTERFACE = "en0"; + public static final int DEFAULT_DATA_PUBLISH_INTERVAL = 15; // seconds + public static final String DEFAULT_PROTOCOL = "MQTT"; + /* --------------------------------------------------------------------------------------- + Control Signal specific constants to match the request context + --------------------------------------------------------------------------------------- */ + public static final String BULB_CONTROL = "BULB"; + public static final String TEMPERATURE_CONTROL = "TEMPERATURE"; + public static final String POLICY_SIGNAL = "POLICY"; + public static final String HUMIDITY_CONTROL = "HUMIDITY"; + public static final String CONTROL_ON = "ON"; + public static final String CONTROL_OFF = "OFF"; + public static final String AUDIO_FILE_NAME = "fireAlarmSound.mid"; + /* --------------------------------------------------------------------------------------- + Communication protocol specific Strings + --------------------------------------------------------------------------------------- */ + public static final String TCP_PREFIX = "tcp://"; + public static final String HTTP_PREFIX = "http://"; + public static final String HTTPS_PREFIX = "https://"; + public static final String HTTP_PROTOCOL = "HTTP"; + public static final String MQTT_PROTOCOL = "MQTT"; + public static final String XMPP_PROTOCOL = "XMPP"; + + public static final String CEP_FILE_NAME = "cep_query.txt"; + public static final String CEP_QUERY = "define stream fireAlarmEventStream (deviceID string, temp int);\n" + + "from fireAlarmEventStream#window.time(30 sec)\n" + + "select deviceID, max(temp) as maxValue\n" + + "group by deviceID\n" + + "insert into analyzeStream for expired-events;\n" + + "from analyzeStream[maxValue > 50]\n" + + "select maxValue\n" + + "insert into bulbOnStream;\n" + + "from fireAlarmEventStream[temp < 50]\n" + + "select deviceID, temp\n" + + "insert into bulbOffStream;"; + +} diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/advanced/core/AgentManager.java b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/advanced/core/AgentManager.java new file mode 100644 index 0000000000..aba3809c39 --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/advanced/core/AgentManager.java @@ -0,0 +1,377 @@ +/* + * 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.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.core; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.communication.http.FireAlarmHTTPCommunicator; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.communication.mqtt.FireAlarmMQTTCommunicator; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.communication.xmpp.FireAlarmXMPPCommunicator; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.enrollment.EnrollmentManager; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.exception.AgentCoreOperationException; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.sidhdhi.SidhdhiQuery; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.transport.TransportHandler; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.transport.TransportHandlerException; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.transport.TransportUtils; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.virtual.VirtualHardwareManager; + +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class AgentManager { + + private static final Log log = LogFactory.getLog(AgentManager.class); + private static final Object lock = new Object(); + private static AgentManager agentManager; + private static Boolean policyUpdated = false; + private String rootPath = ""; + private boolean deviceReady = false; + private boolean isAlarmOn = false; + private String initialPolicy; + + private String deviceMgtControlUrl, deviceMgtAnalyticUrl; + private String deviceName, agentStatus; + + private int pushInterval; // seconds + private String prevProtocol, protocol; + + private String networkInterface; + private List interfaceList, protocolList; + private Map agentCommunicator; + + private AgentConfiguration agentConfigs; + + private String deviceIP; + private String enrollmentEP; + private String ipRegistrationEP; + private String pushDataAPIEP; + + private AgentManager() { + } + + public static synchronized AgentManager getInstance() { + if (agentManager == null) { + agentManager = new AgentManager(); + } + return agentManager; + } + + public static void setUpdated(Boolean isUpdated) { + synchronized (lock) { + policyUpdated = isUpdated; + } + } + + public static Boolean isUpdated() { + synchronized (lock) { + Boolean temp = policyUpdated; + policyUpdated = false; + return temp; + } + } + + public void init() { + + agentCommunicator = new HashMap<>(); + + // Read IoT-Server specific configurations from the 'deviceConfig.properties' file + this.agentConfigs = AgentUtilOperations.readIoTServerConfigs(); + + // Initialise IoT-Server URL endpoints from the configuration read from file + AgentUtilOperations.initializeServerEndPoints(); + + String analyticsPageContext = String.format(AgentConstants.DEVICE_ANALYTICS_PAGE_URL, + agentConfigs.getDeviceId(), + AgentConstants.DEVICE_TYPE, + agentConfigs.getDeviceName()); + + String controlPageContext = String.format(AgentConstants.DEVICE_DETAILS_PAGE_EP, + AgentConstants.DEVICE_TYPE, + agentConfigs.getDeviceId()); + + this.deviceMgtAnalyticUrl = agentConfigs.getHTTPS_ServerEndpoint() + analyticsPageContext; + this.deviceMgtControlUrl = agentConfigs.getHTTPS_ServerEndpoint() + controlPageContext; + + this.agentStatus = AgentConstants.NOT_REGISTERED; + this.deviceName = this.agentConfigs.getDeviceName(); + + this.pushInterval = this.agentConfigs.getDataPushInterval(); + this.networkInterface = AgentConstants.DEFAULT_NETWORK_INTERFACE; + + this.protocol = AgentConstants.DEFAULT_PROTOCOL; + this.prevProtocol = protocol; + + Map xmppIPPortMap; + try { + xmppIPPortMap = TransportUtils.getHostAndPort(agentConfigs.getXmppServerEndpoint()); + String xmppServer = xmppIPPortMap.get("Host"); + int xmppPort = Integer.parseInt(xmppIPPortMap.get("Port")); + + TransportHandler xmppCommunicator = new FireAlarmXMPPCommunicator(xmppServer, xmppPort); + agentCommunicator.put(AgentConstants.XMPP_PROTOCOL, xmppCommunicator); + + } catch (TransportHandlerException e) { + log.error("XMPP Endpoint String - " + agentConfigs.getXmppServerEndpoint() + + ", provided in the configuration file is invalid."); + } + String mqttTopic = String.format(AgentConstants.MQTT_SUBSCRIBE_TOPIC, + agentConfigs.getServerName(), + agentConfigs.getDeviceOwner(), + agentConfigs.getDeviceId()); + +// TransportHandler httpCommunicator = new FireAlarmHTTPCommunicator(); + TransportHandler mqttCommunicator = new FireAlarmMQTTCommunicator(agentConfigs.getDeviceOwner(), + agentConfigs.getDeviceId(), + agentConfigs.getMqttBrokerEndpoint(), + mqttTopic); + +// agentCommunicator.put(AgentConstants.HTTP_PROTOCOL, httpCommunicator); + agentCommunicator.put(AgentConstants.MQTT_PROTOCOL, mqttCommunicator); + + try { + interfaceList = new ArrayList<>(TransportUtils.getInterfaceIPMap().keySet()); + protocolList = new ArrayList<>(agentCommunicator.keySet()); + } catch (TransportHandlerException e) { + log.error("An error occurred whilst retrieving all NetworkInterface-IP mappings"); + } + + String siddhiQueryFilePath = rootPath + AgentConstants.CEP_FILE_NAME; + (new Thread(new SidhdhiQuery())).start(); + initialPolicy = SidhdhiQuery.readFile(siddhiQueryFilePath, StandardCharsets.UTF_8); + + //Initializing hardware at that point + //AgentManger.setDeviceReady() method should invoked from hardware after initialization + VirtualHardwareManager.getInstance().init(); + + //Wait till hardware get ready + while (!deviceReady) { + try { + Thread.sleep(500); + } catch (InterruptedException e) { + log.info(AgentConstants.LOG_APPENDER + "Sleep error in 'device ready-flag' checking thread"); + } + } + + try { + EnrollmentManager.getInstance().beginEnrollmentFlow(); + } catch (AgentCoreOperationException e) { + log.error("Device Enrollment Failed:\n"); + e.printStackTrace(); + System.exit(0); + } + + //Start agent communication + agentCommunicator.get(protocol).connect(); + } + + private void switchCommunicator(String stopProtocol, String startProtocol) { + agentCommunicator.get(stopProtocol).disconnect(); + + while (agentCommunicator.get(stopProtocol).isConnected()) { + try { + Thread.sleep(250); + } catch (InterruptedException e) { + log.info(AgentConstants.LOG_APPENDER + + "Sleep error in 'Switch-Communicator' Thread's shutdown wait."); + } + } + + agentCommunicator.get(startProtocol).connect(); + } + + public void setInterface(int interfaceId) { + if (interfaceId != -1) { + String newInterface = interfaceList.get(interfaceId); + + if (!newInterface.equals(networkInterface)) { + networkInterface = newInterface; + + if (protocol.equals(AgentConstants.HTTP_PROTOCOL) && !protocol.equals( + prevProtocol)) { + switchCommunicator(protocol, protocol); + } + } + } + } + + public void setProtocol(int protocolId) { + if (protocolId != -1) { + String newProtocol = protocolList.get(protocolId); + + if (!protocol.equals(newProtocol)) { + prevProtocol = protocol; + protocol = newProtocol; + switchCommunicator(prevProtocol, protocol); + } + } + } + + public void changeAlarmStatus(boolean isOn) { + VirtualHardwareManager.getInstance().changeAlarmStatus(isOn); + isAlarmOn = isOn; + } + + public void updateAgentStatus(String status) { + this.agentStatus = status; + } + + public void addToPolicyLog(String policy) { + VirtualHardwareManager.getInstance().addToPolicyLog(policy); + } + + public String getRootPath() { + return rootPath; + } + + /*------------------------------------------------------------------------------------------*/ + /* Getter and Setter Methods for the private variables */ + /*------------------------------------------------------------------------------------------*/ + + public void setRootPath(String rootPath) { + this.rootPath = rootPath; + } + + public void setDeviceReady(boolean deviceReady) { + this.deviceReady = deviceReady; + } + + public String getInitialPolicy() { + return initialPolicy; + } + + public String getDeviceMgtControlUrl() { + return deviceMgtControlUrl; + } + + public String getDeviceMgtAnalyticUrl() { + return deviceMgtAnalyticUrl; + } + + public AgentConfiguration getAgentConfigs() { + return agentConfigs; + } + + public String getDeviceIP() { + return deviceIP; + } + + public void setDeviceIP(String deviceIP) { + this.deviceIP = deviceIP; + } + + public String getEnrollmentEP() { + return enrollmentEP; + } + + public void setEnrollmentEP(String enrollmentEP) { + this.enrollmentEP = enrollmentEP; + } + + public String getIpRegistrationEP() { + return ipRegistrationEP; + } + + public void setIpRegistrationEP(String ipRegistrationEP) { + this.ipRegistrationEP = ipRegistrationEP; + } + + public String getPushDataAPIEP() { + return pushDataAPIEP; + } + + public void setPushDataAPIEP(String pushDataAPIEP) { + this.pushDataAPIEP = pushDataAPIEP; + } + + public String getDeviceName() { + return deviceName; + } + + public String getNetworkInterface() { + return networkInterface; + } + + public String getAgentStatus() { + return agentStatus; + } + + public int getPushInterval() { + return pushInterval; + } + + public void setPushInterval(int pushInterval) { + this.pushInterval = pushInterval; + TransportHandler transportHandler = agentCommunicator.get(protocol); + + switch (protocol) { + case AgentConstants.HTTP_PROTOCOL: + ((FireAlarmHTTPCommunicator) transportHandler).getDataPushServiceHandler() + .cancel(true); + break; + case AgentConstants.MQTT_PROTOCOL: + ((FireAlarmMQTTCommunicator) transportHandler).getDataPushServiceHandler() + .cancel(true); + break; + case AgentConstants.XMPP_PROTOCOL: + ((FireAlarmXMPPCommunicator) transportHandler).getDataPushServiceHandler() + .cancel(true); + break; + default: + log.warn("Unknown protocol " + protocol); + } + transportHandler.publishDeviceData(); + + if (log.isDebugEnabled()) { + log.debug("The Data Publish Interval was changed to: " + pushInterval); + } + } + + public List getInterfaceList() { + return interfaceList; + } + + public List getProtocolList() { + return protocolList; + } + + /** + * Get temperature reading from device + * + * @return Temperature + */ + public int getTemperature() { + return VirtualHardwareManager.getInstance().getTemperature(); + } + + /** + * Get humidity reading from device + * + * @return Humidity + */ + public int getHumidity() { + return VirtualHardwareManager.getInstance().getHumidity(); + } + + public boolean isAlarmOn() { + return isAlarmOn; + } +} diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/advanced/core/AgentUtilOperations.java b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/advanced/core/AgentUtilOperations.java new file mode 100644 index 0000000000..cb2dbf4d1e --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/advanced/core/AgentUtilOperations.java @@ -0,0 +1,371 @@ +/* + * 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.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.core; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.json.JSONObject; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.enrollment.EnrollmentManager; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.exception.AgentCoreOperationException; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.transport.CommunicationUtils; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.transport.TransportHandlerException; + +import java.io.*; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.util.ArrayList; +import java.util.Properties; + +/** + * This class contains all the core operations of the FireAlarm agent that are common to both + * Virtual and Real Scenarios. These operations include, connecting to and subscribing to an MQTT + * queue and to a XMPP Server. Pushing temperature data to the IoT-Server at timely intervals. + * Reading device specific configuration from a configs file etc.... + */ +public class AgentUtilOperations { + + private static final Log log = LogFactory.getLog(AgentUtilOperations.class); + private static final String JSON_MESSAGE_KEY = "Msg"; + private static final String JSON_SIGNATURE_KEY = "Sig"; + + /** + * This method reads the agent specific configurations for the device from the + * "deviceConfigs.properties" file found at /repository/conf folder. + * If the properties file is not found in the specified path, then the configuration values + * are set to the default ones in the 'AgentConstants' class. + * + * @return an object of type 'AgentConfiguration' which contains all the necessary + * configuration attributes + */ + public static AgentConfiguration readIoTServerConfigs() { + AgentManager agentManager = AgentManager.getInstance(); + AgentConfiguration iotServerConfigs = new AgentConfiguration(); + Properties properties = new Properties(); + InputStream propertiesInputStream = null; + String propertiesFileName = AgentConstants.AGENT_PROPERTIES_FILE_NAME; + + try { + ClassLoader loader = AgentUtilOperations.class.getClassLoader(); + URL path = loader.getResource(propertiesFileName); + System.out.println(path); + String root = path.getPath().replace( + "wso2-firealarm-virtual-agent-advanced.jar!/deviceConfig.properties", + "").replace("jar:", "").replace("file:", ""); + + agentManager.setRootPath(root); + + propertiesInputStream = new FileInputStream( + root + AgentConstants.AGENT_PROPERTIES_FILE_NAME); + + //load a properties file from class path, inside static method + properties.load(propertiesInputStream); + + iotServerConfigs.setServerName(properties.getProperty( + AgentConstants.SERVER_NAME_PROPERTY)); + iotServerConfigs.setDeviceOwner(properties.getProperty( + AgentConstants.DEVICE_OWNER_PROPERTY)); + iotServerConfigs.setDeviceId(properties.getProperty( + AgentConstants.DEVICE_ID_PROPERTY)); + iotServerConfigs.setDeviceName(properties.getProperty( + AgentConstants.DEVICE_NAME_PROPERTY)); + iotServerConfigs.setControllerContext(properties.getProperty( + AgentConstants.DEVICE_CONTROLLER_CONTEXT_PROPERTY)); + iotServerConfigs.setHTTPS_ServerEndpoint(properties.getProperty( + AgentConstants.SERVER_HTTPS_EP_PROPERTY)); + iotServerConfigs.setHTTP_ServerEndpoint(properties.getProperty( + AgentConstants.SERVER_HTTP_EP_PROPERTY)); + iotServerConfigs.setApimGatewayEndpoint(properties.getProperty( + AgentConstants.APIM_GATEWAY_EP_PROPERTY)); + iotServerConfigs.setMqttBrokerEndpoint(properties.getProperty( + AgentConstants.MQTT_BROKER_EP_PROPERTY)); + iotServerConfigs.setXmppServerEndpoint(properties.getProperty( + AgentConstants.XMPP_SERVER_EP_PROPERTY)); + iotServerConfigs.setAuthMethod(properties.getProperty( + AgentConstants.AUTH_METHOD_PROPERTY)); + iotServerConfigs.setAuthToken(properties.getProperty( + AgentConstants.AUTH_TOKEN_PROPERTY)); + iotServerConfigs.setRefreshToken(properties.getProperty( + AgentConstants.REFRESH_TOKEN_PROPERTY)); + iotServerConfigs.setDataPushInterval(Integer.parseInt(properties.getProperty( + AgentConstants.PUSH_INTERVAL_PROPERTY))); + + log.info(AgentConstants.LOG_APPENDER + "Server name: " + + iotServerConfigs.getServerName()); + log.info(AgentConstants.LOG_APPENDER + "Device Owner: " + + iotServerConfigs.getDeviceOwner()); + log.info(AgentConstants.LOG_APPENDER + "Device ID: " + iotServerConfigs.getDeviceId()); + log.info(AgentConstants.LOG_APPENDER + "Device Name: " + + iotServerConfigs.getDeviceName()); + log.info(AgentConstants.LOG_APPENDER + "Device Controller Context: " + + iotServerConfigs.getControllerContext()); + log.info(AgentConstants.LOG_APPENDER + "IoT Server HTTPS EndPoint: " + + iotServerConfigs.getHTTPS_ServerEndpoint()); + log.info(AgentConstants.LOG_APPENDER + "IoT Server HTTP EndPoint: " + + iotServerConfigs.getHTTP_ServerEndpoint()); + log.info(AgentConstants.LOG_APPENDER + "API-Manager Gateway EndPoint: " + + iotServerConfigs.getApimGatewayEndpoint()); + log.info(AgentConstants.LOG_APPENDER + "MQTT Broker EndPoint: " + + iotServerConfigs.getMqttBrokerEndpoint()); + log.info(AgentConstants.LOG_APPENDER + "XMPP Server EndPoint: " + + iotServerConfigs.getXmppServerEndpoint()); + log.info(AgentConstants.LOG_APPENDER + "Authentication Method: " + + iotServerConfigs.getAuthMethod()); + log.info(AgentConstants.LOG_APPENDER + "Authentication Token: " + + iotServerConfigs.getAuthToken()); + log.info(AgentConstants.LOG_APPENDER + "Refresh Token: " + + iotServerConfigs.getRefreshToken()); + log.info(AgentConstants.LOG_APPENDER + "Data Push Interval: " + + iotServerConfigs.getDataPushInterval()); + + } catch (FileNotFoundException ex) { + log.error(AgentConstants.LOG_APPENDER + "Unable to find " + propertiesFileName + + " file at: " + AgentConstants.PROPERTIES_FILE_PATH); + iotServerConfigs = setDefaultDeviceConfigs(); + + } catch (IOException ex) { + log.error(AgentConstants.LOG_APPENDER + "Error occurred whilst trying to fetch '" + + propertiesFileName + "' from: " + + AgentConstants.PROPERTIES_FILE_PATH); + iotServerConfigs = setDefaultDeviceConfigs(); + + } finally { + if (propertiesInputStream != null) { + try { + propertiesInputStream.close(); + } catch (IOException e) { + log.error(AgentConstants.LOG_APPENDER + + "Error occurred whilst trying to close InputStream resource used to read the '" + + propertiesFileName + "' file"); + } + } + } + return iotServerConfigs; + } + + /** + * Sets the default Device specific configurations listed in the 'AgentConstants' class. + * + * @return an object of AgentConfiguration class including all default device specific configs. + */ + private static AgentConfiguration setDefaultDeviceConfigs() { + log.warn(AgentConstants.LOG_APPENDER + + "Default Values are being set to all Agent specific configurations"); + + AgentConfiguration iotServerConfigs = new AgentConfiguration(); + + iotServerConfigs.setDeviceOwner(AgentConstants.DEFAULT_SERVER_NAME); + iotServerConfigs.setDeviceOwner(AgentConstants.DEFAULT_DEVICE_OWNER); + iotServerConfigs.setDeviceId(AgentConstants.DEFAULT_DEVICE_ID); + iotServerConfigs.setDeviceName(AgentConstants.DEFAULT_DEVICE_NAME); + iotServerConfigs.setControllerContext(AgentConstants.DEVICE_CONTROLLER_API_EP); + iotServerConfigs.setHTTPS_ServerEndpoint(AgentConstants.DEFAULT_HTTPS_SERVER_EP); + iotServerConfigs.setHTTP_ServerEndpoint(AgentConstants.DEFAULT_HTTP_SERVER_EP); + iotServerConfigs.setApimGatewayEndpoint(AgentConstants.DEFAULT_APIM_GATEWAY_EP); + iotServerConfigs.setMqttBrokerEndpoint(AgentConstants.DEFAULT_MQTT_BROKER_EP); + iotServerConfigs.setXmppServerEndpoint(AgentConstants.DEFAULT_XMPP_SERVER_EP); + iotServerConfigs.setAuthMethod(AgentConstants.DEFAULT_AUTH_METHOD); + iotServerConfigs.setAuthToken(AgentConstants.DEFAULT_AUTH_TOKEN); + iotServerConfigs.setRefreshToken(AgentConstants.DEFAULT_REFRESH_TOKEN); + iotServerConfigs.setDataPushInterval(AgentConstants.DEFAULT_DATA_PUBLISH_INTERVAL); + + return iotServerConfigs; + } + + + /** + * This method constructs the URLs for each of the API Endpoints called by the device agent + * Ex: Register API, Push-Data API + * + * @throws AgentCoreOperationException if any error occurs at socket level whilst trying to + * retrieve the deviceIP of the network-interface read + * from the configs file + */ + public static void initializeServerEndPoints() { + AgentManager agentManager = AgentManager.getInstance(); + String serverSecureEndpoint = agentManager.getAgentConfigs().getHTTPS_ServerEndpoint(); + String serverUnSecureEndpoint = agentManager.getAgentConfigs().getHTTP_ServerEndpoint(); + String backEndContext = agentManager.getAgentConfigs().getControllerContext(); + + String deviceControllerAPIEndpoint = serverSecureEndpoint + backEndContext; + + String deviceEnrollmentEndpoint = + serverUnSecureEndpoint + backEndContext + AgentConstants.DEVICE_ENROLLMENT_API_EP; + agentManager.setEnrollmentEP(deviceEnrollmentEndpoint); + + String registerEndpointURL = + deviceControllerAPIEndpoint + AgentConstants.DEVICE_REGISTER_API_EP; + agentManager.setIpRegistrationEP(registerEndpointURL); + + String pushDataEndPointURL = + deviceControllerAPIEndpoint + AgentConstants.DEVICE_PUSH_TEMPERATURE_API_EP; + agentManager.setPushDataAPIEP(pushDataEndPointURL); + + log.info(AgentConstants.LOG_APPENDER + "IoT Server's Device Controller API Endpoint: " + + deviceControllerAPIEndpoint); + log.info(AgentConstants.LOG_APPENDER + "Device Enrollment EndPoint: " + + registerEndpointURL); + log.info(AgentConstants.LOG_APPENDER + "DeviceIP Registration EndPoint: " + + registerEndpointURL); + log.info(AgentConstants.LOG_APPENDER + "Push-Data API EndPoint: " + pushDataEndPointURL); + } + + + public static String prepareSecurePayLoad(String message) throws AgentCoreOperationException { + PublicKey serverPublicKey = EnrollmentManager.getInstance().getServerPublicKey(); + PrivateKey devicePrivateKey = EnrollmentManager.getInstance().getPrivateKey(); + + String encryptedMsg; + try { + encryptedMsg = CommunicationUtils.encryptMessage(message, serverPublicKey); + } catch (TransportHandlerException e) { + String errorMsg = "Error occurred whilst trying to encryptMessage: [" + message + "]"; + log.error(errorMsg); + throw new AgentCoreOperationException(errorMsg, e); + } + + String signedPayload; + try { + signedPayload = CommunicationUtils.signMessage(encryptedMsg, devicePrivateKey); + } catch (TransportHandlerException e) { + String errorMsg = "Error occurred whilst trying to sign encrypted message of: [" + message + "]"; + log.error(errorMsg); + throw new AgentCoreOperationException(errorMsg, e); + } + + JSONObject jsonPayload = new JSONObject(); + jsonPayload.put(JSON_MESSAGE_KEY, encryptedMsg); + jsonPayload.put(JSON_SIGNATURE_KEY, signedPayload); + + return jsonPayload.toString(); + } + + + public static String extractMessageFromPayload(String message) throws AgentCoreOperationException { + String actualMessage; + + PublicKey serverPublicKey = EnrollmentManager.getInstance().getServerPublicKey(); + PrivateKey devicePrivateKey = EnrollmentManager.getInstance().getPrivateKey(); + + JSONObject jsonPayload = new JSONObject(message); + Object encryptedMessage = jsonPayload.get(JSON_MESSAGE_KEY); + Object signedPayload = jsonPayload.get(JSON_SIGNATURE_KEY); + boolean verification; + + if (encryptedMessage != null && signedPayload != null) { + try { + verification = CommunicationUtils.verifySignature( + encryptedMessage.toString(), signedPayload.toString(), serverPublicKey); + } catch (TransportHandlerException e) { + String errorMsg = + "Error occurred whilst trying to verify signature on received message: [" + message + "]"; + log.error(errorMsg); + throw new AgentCoreOperationException(errorMsg, e); + } + } else { + String errorMsg = "The received message is in an INVALID format. " + + "Need to be JSON - {\"Msg\":\"\", \"Sig\":\"\"}."; + throw new AgentCoreOperationException(errorMsg); + } + + try { + if (verification) { + actualMessage = CommunicationUtils.decryptMessage(encryptedMessage.toString(), devicePrivateKey); + } else { + String errorMsg = "Could not verify payload signature. The message was not signed by a valid client"; + log.error(errorMsg); + throw new AgentCoreOperationException(errorMsg); + } + } catch (TransportHandlerException e) { + String errorMsg = "Error occurred whilst trying to decrypt received message: [" + encryptedMessage + "]"; + log.error(errorMsg); + throw new AgentCoreOperationException(errorMsg, e); + } + + return actualMessage; + } + + public static String formatMessage(String message) { + StringBuilder formattedMsg = new StringBuilder(message); + + ArrayList keyWordList = new ArrayList(); + keyWordList.add("define"); + keyWordList.add("from"); + keyWordList.add("select"); + keyWordList.add("group"); + keyWordList.add("insert"); + keyWordList.add(";"); + + + for (String keyWord : keyWordList) { + int startIndex = 0; + + while (true) { + int keyWordIndex = formattedMsg.indexOf(keyWord, startIndex); + + if (keyWordIndex == -1) { + break; + } + + if (keyWord.equals(";")) { + if (keyWordIndex != 0 && (keyWordIndex + 1) != formattedMsg.length() && + formattedMsg.charAt(keyWordIndex + 1) == ' ') { + formattedMsg.setCharAt((keyWordIndex + 1), '\n'); + } + } else { + if (keyWordIndex != 0 && formattedMsg.charAt(keyWordIndex - 1) == ' ') { + formattedMsg.setCharAt((keyWordIndex - 1), '\n'); + } + } + startIndex = keyWordIndex + 1; + } + } + return formattedMsg.toString(); + } + + public static boolean writeToFile(String content, String fileLocation) { + File file = new File(fileLocation); + + try (FileOutputStream fop = new FileOutputStream(file)) { + + // if file doesn't exists, then create it + if (!file.exists()) { + file.createNewFile(); + } + + // get the content in bytes + byte[] contentInBytes = content.getBytes(StandardCharsets.UTF_8); + + fop.write(contentInBytes); + fop.flush(); + fop.close(); + + System.out.println("Done"); + AgentManager.setUpdated(true); + return true; + + } catch (IOException e) { + e.printStackTrace(); + return false; + } + } + +} + diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/advanced/enrollment/EnrollmentManager.java b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/advanced/enrollment/EnrollmentManager.java new file mode 100644 index 0000000000..b6f26ad057 --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/advanced/enrollment/EnrollmentManager.java @@ -0,0 +1,440 @@ +/* + * 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.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.enrollment; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.bouncycastle.asn1.x500.X500Name; +import org.bouncycastle.asn1.x500.X500NameBuilder; +import org.bouncycastle.asn1.x500.style.BCStyle; +import org.bouncycastle.cert.X509v3CertificateBuilder; +import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.operator.ContentSigner; +import org.bouncycastle.operator.OperatorCreationException; +import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; +import org.bouncycastle.pkcs.PKCS10CertificationRequest; +import org.bouncycastle.pkcs.PKCS10CertificationRequestBuilder; +import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequestBuilder; +import org.jscep.client.Client; +import org.jscep.client.ClientException; +import org.jscep.client.EnrollmentResponse; +import org.jscep.client.verification.CertificateVerifier; +import org.jscep.client.verification.OptimisticCertificateVerifier; +import org.jscep.transaction.TransactionException; +import org.jscep.transport.response.Capabilities; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.core.AgentConstants; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.core.AgentManager; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.exception.AgentCoreOperationException; +import sun.security.x509.X509CertImpl; + +import java.math.BigInteger; +import java.net.MalformedURLException; +import java.net.URL; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.SecureRandom; +import java.security.Security; +import java.security.cert.CertStore; +import java.security.cert.CertStoreException; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.Date; + +/** + * This class controls the entire SCEP enrolment process of the client. It is a singleton for any single client which + * has the agent code running in it. The main functionality of this class includes generating a Private-Public Key + * Pair for the enrollment flow, creating the Certificate-Sign-Request using the generated Public-Key to send to the + * SEP server, Contacting the SCEP server to receive the Signed Certificate and requesting for the server's public + * key for encrypting the payloads. + * The provider for all Cryptographic functions used in this class are "BouncyCastle" and the Asymmetric-Key pair + * algorithm used is "RSA" with a key size of 2048. The signature algorithm used is "SHA1withRSA". + * This class also holds the "SCEPUrl" (Server Url read from the configs file), the Private-Public Keys of the + * client, Signed SCEP certificate and the server's public certificate. + */ + +//TODO: Need to save cert and keys to file after initial enrollment... +public class EnrollmentManager { + private static final Log log = LogFactory.getLog(EnrollmentManager.class); + private static EnrollmentManager enrollmentManager; + + private static final String KEY_PAIR_ALGORITHM = "RSA"; + private static final String PROVIDER = "BC"; + private static final String SIGNATURE_ALG = "SHA1withRSA"; + private static final int KEY_SIZE = 2048; + + // Seed to our PRNG. Make sure this is initialised randomly, NOT LIKE THIS + private static final byte[] SEED = ")(*&^%$#@!".getBytes(); + private static final int CERT_VALIDITY = 730; + + // URL of our SCEP server + private String SCEPUrl; + private PrivateKey privateKey; + private PublicKey publicKey; + private PublicKey serverPublicKey; + private X509Certificate SCEPCertificate; + + + /** + * Constructor of the EnrollmentManager. Initializes the SCEPUrl as read from the configuration file by the + * AgentManager. + */ + private EnrollmentManager() { + this.SCEPUrl = AgentManager.getInstance().getEnrollmentEP(); + } + + /** + * Method to return the current singleton instance of the EnrollmentManager. + * + * @return the current singleton instance if available and if not initializes a new instance and returns it. + */ + public static EnrollmentManager getInstance() { + if (enrollmentManager == null) { + enrollmentManager = new EnrollmentManager(); + } + return enrollmentManager; + } + + + /** + * Method to control the entire enrollment flow. This method calls the method to create the Private-Public Key + * Pair, calls the specific method to generate the Certificate-Sign-Request, creates a one time self signed + * certificate to present to the SCEP server with the initial CSR, calls the specific method to connect to the + * SCEP Server and to get the SCEP Certificate and also calls the method that requests the SCEP Server for its + * PublicKey for future payload encryption. + * + * @throws AgentCoreOperationException if the private method generateCertSignRequest() fails with an error or if + * there is an error creating a self-sign certificate to present to the + * server (whilst trying to get the CSR signed) + */ + public void beginEnrollmentFlow() throws AgentCoreOperationException { + Security.addProvider(new BouncyCastleProvider()); + + KeyPair keyPair = generateKeyPair(); + this.privateKey = keyPair.getPrivate(); + this.publicKey = keyPair.getPublic(); + + if (log.isDebugEnabled()) { + log.info(AgentConstants.LOG_APPENDER + "DevicePrivateKey:\n[\n" + privateKey + "\n]\n"); + log.info(AgentConstants.LOG_APPENDER + "DevicePublicKey:\n[\n" + publicKey + "\n]\n"); + } + + PKCS10CertificationRequest certSignRequest = generateCertSignRequest(); + + /** + * ----------------------------------------------------------------------------------------------- + * Generate an ephemeral self-signed certificate. This is needed to present to the CA in the SCEP request. + * In the future, add proper EKU and attributes in the request. The CA does NOT have to honour any of this. + * ----------------------------------------------------------------------------------------------- + */ + X500Name issuer = new X500Name("CN=Temporary Issuer"); + BigInteger serial = new BigInteger(32, new SecureRandom()); + Date fromDate = new Date(); + Date toDate = new Date(System.currentTimeMillis() + (CERT_VALIDITY * 86400000L)); + + // Build the self-signed cert using BC, sign it with our private key (self-signed) + X509v3CertificateBuilder certBuilder = new X509v3CertificateBuilder(issuer, serial, fromDate, toDate, + certSignRequest.getSubject(), + certSignRequest.getSubjectPublicKeyInfo()); + ContentSigner sigGen; + X509Certificate tmpCert; + + try { + sigGen = new JcaContentSignerBuilder(SIGNATURE_ALG).setProvider(PROVIDER).build(keyPair.getPrivate()); + tmpCert = new JcaX509CertificateConverter().setProvider(PROVIDER).getCertificate(certBuilder.build(sigGen)); + } catch (OperatorCreationException e) { + String errorMsg = "Error occurred whilst creating a ContentSigner for the Temp-Self-Signed Certificate."; + log.error(errorMsg); + throw new AgentCoreOperationException(errorMsg, e); + } catch (CertificateException e) { + String errorMsg = "Error occurred whilst trying to create Temp-Self-Signed Certificate."; + log.error(errorMsg); + throw new AgentCoreOperationException(errorMsg, e); + } + /** + * ----------------------------------------------------------------------------------------------- + */ + + this.SCEPCertificate = getSignedCertificateFromServer(tmpCert, certSignRequest); + this.serverPublicKey = initPublicKeyOfServer(); + + if (log.isDebugEnabled()) { + log.info(AgentConstants.LOG_APPENDER + "TemporaryCertPublicKey:\n[\n" + tmpCert.getPublicKey() + "\n]\n"); + log.info(AgentConstants.LOG_APPENDER + "ServerPublicKey:\n[\n" + serverPublicKey + "\n]\n"); + } + + } + + + /** + * This method creates the Public-Private Key pair for the current client. + * + * @return the generated KeyPair object + * @throws AgentCoreOperationException when the given Security Provider does not exist or the Algorithmn used to + * generate the key pair is invalid. + */ + private KeyPair generateKeyPair() throws AgentCoreOperationException { + + // Generate key pair + KeyPairGenerator keyPairGenerator; + try { + keyPairGenerator = KeyPairGenerator.getInstance(KEY_PAIR_ALGORITHM, PROVIDER); + keyPairGenerator.initialize(KEY_SIZE, new SecureRandom(SEED)); + } catch (NoSuchAlgorithmException e) { + String errorMsg = "Algorithm [" + KEY_PAIR_ALGORITHM + "] provided for KeyPairGenerator is invalid."; + log.error(errorMsg); + throw new AgentCoreOperationException(errorMsg, e); + } catch (NoSuchProviderException e) { + String errorMsg = "Provider [" + PROVIDER + "] provided for KeyPairGenerator does not exist."; + log.error(errorMsg); + throw new AgentCoreOperationException(errorMsg, e); + } + + return keyPairGenerator.genKeyPair(); + } + + + /** + * This method creates the PKCS10 Certificate Sign Request which is to be sent to the SCEP Server using the + * generated PublicKey of the client. The certificate parameters used here are the ones from the AgentManager + * which are the values read from the configurations file. + * + * @return the PKCS10CertificationRequest object created using the client specific configs and the generated + * PublicKey + * @throws AgentCoreOperationException if an error occurs when creating a content signer to sign the CSR. + */ + private PKCS10CertificationRequest generateCertSignRequest() throws AgentCoreOperationException { + // Build the CN for the cert that's being requested. + X500NameBuilder nameBld = new X500NameBuilder(BCStyle.INSTANCE); + nameBld.addRDN(BCStyle.CN, AgentManager.getInstance().getAgentConfigs().getServerName()); + nameBld.addRDN(BCStyle.O, AgentManager.getInstance().getAgentConfigs().getDeviceOwner()); + nameBld.addRDN(BCStyle.OU, AgentManager.getInstance().getAgentConfigs().getDeviceOwner()); + nameBld.addRDN(BCStyle.UNIQUE_IDENTIFIER, AgentManager.getInstance().getAgentConfigs().getDeviceId()); + nameBld.addRDN(BCStyle.SERIALNUMBER, AgentManager.getInstance().getAgentConfigs().getDeviceId()); + X500Name principal = nameBld.build(); + + JcaContentSignerBuilder contentSignerBuilder = new JcaContentSignerBuilder(SIGNATURE_ALG).setProvider(PROVIDER); + ContentSigner contentSigner; + + try { + contentSigner = contentSignerBuilder.build(this.privateKey); + } catch (OperatorCreationException e) { + String errorMsg = "Could not create content signer with private key."; + log.error(errorMsg); + throw new AgentCoreOperationException(errorMsg, e); + } + + // Generate the certificate signing request (csr = PKCS10) + PKCS10CertificationRequestBuilder reqBuilder = new JcaPKCS10CertificationRequestBuilder(principal, + this.publicKey); + return reqBuilder.build(contentSigner); + } + + + /** + * This method connects to the SCEP Server to fetch the signed SCEP Certificate. + * + * @param tempCert the temporary self-signed certificate of the client required for the initial CSR + * request against the SCEP Server. + * @param certSignRequest the PKCS10 Certificate-Sign-Request that is to be sent to the SCEP Server. + * @return the SCEP-Certificate for the client signed by the SCEP-Server. + * @throws AgentCoreOperationException if the SCEPUrl is invalid or if the flow of sending the CSR and getting + * the signed certificate fails or if the signed certificate cannot be + * retrieved from the reply from the server. + */ + private X509Certificate getSignedCertificateFromServer(X509Certificate tempCert, + PKCS10CertificationRequest certSignRequest) + throws AgentCoreOperationException { + + X509Certificate signedSCEPCertificate = null; + URL url; + EnrollmentResponse enrolResponse; + CertStore certStore; + + try { + // The URL where we are going to request our cert from + url = new URL(this.SCEPUrl); + + /* // This is called when we get the certificate for our CSR signed by CA + // Implement this handler to check the CA cert in prod. We can do cert pinning here + CallbackHandler cb = new CallbackHandler() { + @Override + public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { + //throw new UnsupportedOperationException("Not supported yet."); //To change body of generated + methods, choose Tools | Templates. + } + };*/ + + // Implement verification of the CA cert. VERIFY the CA + CertificateVerifier ocv = new OptimisticCertificateVerifier(); + + // Instantiate our SCEP client + Client scepClient = new Client(url, ocv); + + // Submit our cert for signing. SCEP server should allow the client to specify + // the SCEP CA to issue the request against, if there are multiple CAs + enrolResponse = scepClient.enrol(tempCert, this.privateKey, certSignRequest); + + // Verify we got what we want, and just print out the cert. + certStore = enrolResponse.getCertStore(); + + for (Certificate x509Certificate : certStore.getCertificates(null)) { + if (log.isDebugEnabled()) { + log.debug(x509Certificate.toString()); + } + signedSCEPCertificate = (X509Certificate) x509Certificate; + } + + } catch (MalformedURLException ex) { + String errorMsg = "Could not create valid URL from given SCEP URI: " + SCEPUrl; + log.error(errorMsg); + throw new AgentCoreOperationException(errorMsg, ex); + } catch (TransactionException | ClientException e) { + String errorMsg = "Enrollment process to SCEP Server at: " + SCEPUrl + " failed."; + log.error(errorMsg); + throw new AgentCoreOperationException(errorMsg, e); + } catch (CertStoreException e) { + String errorMsg = "Could not retrieve [Signed-Certificate] from the response message from SCEP-Server."; + log.error(errorMsg); + throw new AgentCoreOperationException(errorMsg, e); + } + + return signedSCEPCertificate; + } + + + /** + * Gets the Public Key of the SCEP-Server and initializes it for later use. This method contacts the SCEP Server + * and fetches its CA Cert and extracts the Public Key of the server from the received reply. + * + * @return the public key of the SCEP Server which is to be used to encrypt pyloads. + * @throws AgentCoreOperationException if the SCEPUrl is invalid or if the flow of sending the CSR and getting + * the signed certificate fails or if the signed certificate cannot be + * retrieved from the reply from the server. + */ + private PublicKey initPublicKeyOfServer() throws AgentCoreOperationException { + URL url; + CertStore certStore; + PublicKey serverCertPublicKey = null; + + try { + // The URL where we are going to request our cert from + url = new URL(this.SCEPUrl); + + /* // This is called when we get the certificate for our CSR signed by CA + // Implement this handler to check the CA cert in prod. We can do cert pinning here + CallbackHandler cb = new CallbackHandler() { + @Override + public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { + //throw new UnsupportedOperationException("Not supported yet."); //To change body of generated + methods, choose Tools | Templates. + } + };*/ + + // Implement verification of the CA cert. VERIFY the CA + CertificateVerifier ocv = new OptimisticCertificateVerifier(); + + // Instantiate our SCEP client + Client scepClient = new Client(url, ocv); + + // Get the CA capabilities. Should return SHA1withRSA for strongest hash and sig. Returns MD5. + if (log.isDebugEnabled()) { + Capabilities cap = scepClient.getCaCapabilities(); + log.debug(String.format( + "\nStrongestCipher: %s,\nStrongestMessageDigest: %s,\nStrongestSignatureAlgorithm: %s," + + "\nIsRenewalSupported: %s,\nIsRolloverSupported: %s", + cap.getStrongestCipher(), cap.getStrongestMessageDigest(), cap.getStrongestSignatureAlgorithm(), + cap.isRenewalSupported(), cap.isRolloverSupported())); + } + + certStore = scepClient.getCaCertificate(); + + for (Certificate cert : certStore.getCertificates(null)) { + if (cert instanceof X509Certificate) { + if (log.isDebugEnabled()) { + log.debug(((X509Certificate) cert).getIssuerDN().getName()); + } + + //TODO: Need to identify the correct certificate. + // I have chosen the CA cert based on its BasicConstraint criticality being set to "true" + if (((X509CertImpl) cert).getBasicConstraintsExtension().isCritical()) { + serverCertPublicKey = cert.getPublicKey(); + } + } + } + + } catch (MalformedURLException ex) { + String errorMsg = "Could not create valid URL from given SCEP URI: " + SCEPUrl; + log.error(errorMsg); + throw new AgentCoreOperationException(errorMsg, ex); + } catch (ClientException e) { + String errorMsg = "Could not retrieve [Server-Certificate] from the SCEP-Server."; + log.error(errorMsg); + throw new AgentCoreOperationException(errorMsg, e); + } catch (CertStoreException e) { + String errorMsg = "Could not retrieve [Server-Certificates] from the response message from SCEP-Server."; + log.error(errorMsg); + throw new AgentCoreOperationException(errorMsg, e); + } + + return serverCertPublicKey; + } + + + /** + * Gets the Public-Key of the client. + * + * @return the public key of the client. + */ + public PublicKey getPublicKey() { + return publicKey; + } + + /** + * Gets the Private-Key of the client. + * + * @return the private key of the client. + */ + public PrivateKey getPrivateKey() { + return privateKey; + } + + /** + * Gets the SCEP-Certificate of the client. + * + * @return the SCEP Certificate of the client. + */ + public X509Certificate getSCEPCertificate() { + return SCEPCertificate; + } + + /** + * Gets the Public-Key of the Server. + * + * @return the pubic key of the server. + */ + public PublicKey getServerPublicKey() { + return serverPublicKey; + } +} diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/advanced/exception/AgentCoreOperationException.java b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/advanced/exception/AgentCoreOperationException.java new file mode 100644 index 0000000000..7aa1020427 --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/advanced/exception/AgentCoreOperationException.java @@ -0,0 +1,57 @@ +/* + * 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.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.exception; + + +public class AgentCoreOperationException extends Exception { + private static final long serialVersionUID = 2736466230451105710L; + + private String errorMessage; + + public String getErrorMessage() { + return errorMessage; + } + + public void setErrorMessage(String errorMessage) { + this.errorMessage = errorMessage; + } + + public AgentCoreOperationException(String msg, Exception nestedEx) { + super(msg, nestedEx); + setErrorMessage(msg); + } + + public AgentCoreOperationException(String message, Throwable cause) { + super(message, cause); + setErrorMessage(message); + } + + public AgentCoreOperationException(String msg) { + super(msg); + setErrorMessage(msg); + } + + public AgentCoreOperationException() { + super(); + } + + public AgentCoreOperationException(Throwable cause) { + super(cause); + } +} diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/advanced/sidhdhi/SidhdhiQuery.java b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/advanced/sidhdhi/SidhdhiQuery.java new file mode 100644 index 0000000000..94e6bd525f --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/advanced/sidhdhi/SidhdhiQuery.java @@ -0,0 +1,205 @@ +/* + * 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.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.sidhdhi; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.http.HttpResponse; +import org.apache.http.client.HttpClient; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.impl.client.DefaultHttpClient; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.core.AgentConstants; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.core.AgentManager; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.core.AgentUtilOperations; +import org.wso2.siddhi.core.SiddhiManager; +import org.wso2.siddhi.core.event.Event; +import org.wso2.siddhi.core.stream.input.InputHandler; +import org.wso2.siddhi.core.stream.output.StreamCallback; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; + +/** + * This class reads the humidity reading and injects values + * to the siddhiEngine for processing on a routine basis + * also if the siddhiquery is updated the class takes + * care of re-initializing same. + */ +public class SidhdhiQuery implements Runnable { + private static final Log log = LogFactory.getLog(SidhdhiQuery.class); + public static final String sidhdhiQueryPath = AgentManager.getInstance().getRootPath() + AgentConstants.CEP_FILE_NAME; + + //Bam data push client + private static SiddhiManager siddhiManager = new SiddhiManager(); + + public static SiddhiManager getSiddhiManager() { + return siddhiManager; + } + + public static void setSiddhiManager(SiddhiManager siddhiManager) { + SidhdhiQuery.siddhiManager = siddhiManager; + } + + public void run() { + + //Start the execution plan with pre-defined or previously persisted Siddhi query + File f = new File(sidhdhiQueryPath); + + if (!f.exists()) { + AgentUtilOperations.writeToFile(AgentConstants.CEP_QUERY, sidhdhiQueryPath); + } + + StartExecutionPlan startExecutionPlan = new StartExecutionPlan().invoke(); + + while (true) { + + //Check if there is new policy update available + if (AgentManager.isUpdated()) { + System.out.print("### Policy Update Detected!"); + //Restart execution plan with new query + restartSiddhi(); + startExecutionPlan = new StartExecutionPlan().invoke(); + } + InputHandler inputHandler = startExecutionPlan.getInputHandler(); + + //Sending events to Siddhi + try { + int humidityReading = AgentManager.getInstance().getTemperature(); + inputHandler.send(new Object[]{"FIRE_1", humidityReading}); + Thread.sleep(3000); + } catch (InterruptedException e) { + e.printStackTrace(); + break; + } + } + } + + /** + * Re-Initialize SiddhiManager + */ + private void restartSiddhi() { + siddhiManager.shutdown(); + siddhiManager = new SiddhiManager(); + } + + + /** + * Read content from a given file and return as a string + * + * @param path + * @param encoding + * @return + */ + public static String readFile(String path, Charset encoding) { + byte[] encoded = new byte[0]; + try { + encoded = Files.readAllBytes(Paths.get(path)); + } catch (IOException e) { + log.error("Error reading Sidhdhi query from file."); + } + return new String(encoded, encoding); + } + + + /** + * Read humidity data from API URL + * + * @param humidityAPIUrl + * @return + */ + private String readHumidityData(String humidityAPIUrl) { + HttpClient client = new DefaultHttpClient(); + HttpGet request = new HttpGet(humidityAPIUrl); + String responseStr = null; + try { + HttpResponse response = client.execute(request); + log.debug("Response Code : " + response); + InputStream input = response.getEntity().getContent(); + BufferedReader br = new BufferedReader(new InputStreamReader(input, "UTF-8")); + responseStr = String.valueOf(br.readLine()); + br.close(); + + } catch (IOException e) { + //log.error("Exception encountered while trying to make get request."); + log.error("Error while reading humidity reading from file!"); + return responseStr; + } + return responseStr; + } + + /** + * Initialize SiddhiExecution plan + */ + private static class StartExecutionPlan { + private InputHandler inputHandler; + + public InputHandler getInputHandler() { + return inputHandler; + } + + public StartExecutionPlan invoke() { + String executionPlan; + + executionPlan = readFile(sidhdhiQueryPath, StandardCharsets.UTF_8); + + //Generating runtime + siddhiManager.addExecutionPlan(executionPlan); + + siddhiManager.addCallback("bulbOnStream", new StreamCallback() { + @Override + public void receive(Event[] events) { + System.out.println("Bulb on Event Fired!"); + if (events.length > 0) { + if (!AgentManager.getInstance().isAlarmOn()) { + AgentManager.getInstance().changeAlarmStatus(true); + System.out.println("#### Performed HTTP call! ON."); + } + } + } + }); + + siddhiManager.addCallback("bulbOffStream", new StreamCallback() { + @Override + public void receive(Event[] inEvents) { + System.out.println("Bulb off Event Fired"); + if (AgentManager.getInstance().isAlarmOn()) { + AgentManager.getInstance().changeAlarmStatus(false); + System.out.println("#### Performed HTTP call! OFF."); + } + } + + }); + + //Retrieving InputHandler to push events into Siddhi + inputHandler = siddhiManager.getInputHandler("fireAlarmEventStream"); + + //Starting event processing + System.out.println("Execution Plan Started!"); + return this; + } + } +} diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/advanced/transport/CommunicationUtils.java b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/advanced/transport/CommunicationUtils.java new file mode 100644 index 0000000000..e6e16ba52b --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/advanced/transport/CommunicationUtils.java @@ -0,0 +1,226 @@ +/* + * 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.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.transport; + +import org.apache.commons.codec.binary.Base64; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; +import java.nio.charset.StandardCharsets; +import java.security.InvalidKeyException; +import java.security.Key; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.Signature; +import java.security.SignatureException; + +/** + * This is a utility class which contains methods common to the communication process of a client and the server. The + * methods include encryption/decryption of payloads and signing/verification of payloads received and to be sent. + */ +public class CommunicationUtils { + private static final Log log = LogFactory.getLog(TransportUtils.class); + + // The Signature Algorithm used. + private static final String SIGNATURE_ALG = "SHA1withRSA"; + // The Encryption Algorithm and the Padding used. + private static final String CIPHER_PADDING = "RSA/ECB/PKCS1Padding"; + + + /** + * Encrypts the message with the key that's passed in. + * + * @param message the message to be encrypted. + * @param encryptionKey the key to use for the encryption of the message. + * @return the encrypted message in String format. + * @throws TransportHandlerException if an error occurs with the encryption flow which can be due to Padding + * issues, encryption key being invalid or the algorithm used is unrecognizable. + */ + public static String encryptMessage(String message, Key encryptionKey) throws TransportHandlerException { + Cipher encrypter; + byte[] cipherData; + + try { + encrypter = Cipher.getInstance(CIPHER_PADDING); + encrypter.init(Cipher.ENCRYPT_MODE, encryptionKey); + cipherData = encrypter.doFinal(message.getBytes(StandardCharsets.UTF_8)); + + } catch (NoSuchAlgorithmException e) { + String errorMsg = "Algorithm not found exception occurred for Cipher instance of [" + CIPHER_PADDING + "]"; + log.error(errorMsg); + throw new TransportHandlerException(errorMsg, e); + } catch (NoSuchPaddingException e) { + String errorMsg = "No Padding error occurred for Cipher instance of [" + CIPHER_PADDING + "]"; + log.error(errorMsg); + throw new TransportHandlerException(errorMsg, e); + } catch (InvalidKeyException e) { + String errorMsg = "InvalidKey exception occurred for encryptionKey \n[\n" + encryptionKey + "\n]\n"; + log.error(errorMsg); + throw new TransportHandlerException(errorMsg, e); + } catch (BadPaddingException e) { + String errorMsg = "Bad Padding error occurred for Cipher instance of [" + CIPHER_PADDING + "]"; + log.error(errorMsg); + throw new TransportHandlerException(errorMsg, e); + } catch (IllegalBlockSizeException e) { + String errorMsg = "Illegal blockSize error occurred for Cipher instance of [" + CIPHER_PADDING + "]"; + log.error(errorMsg); + throw new TransportHandlerException(errorMsg, e); + } + + return Base64.encodeBase64String(cipherData); + } + +///TODO:: Exception needs to change according to the common package + + /** + * Signed a given message using the PrivateKey that's passes in. + * + * @param message the message to be signed. Ideally some encrypted payload. + * @param signatureKey the PrivateKey with which the message is to be signed. + * @return the Base64Encoded String of the signed payload. + * @throws TransportHandlerException if some error occurs with the signing process which may be related to the + * signature algorithm used or the key used for signing. + */ + public static String signMessage(String message, PrivateKey signatureKey) throws TransportHandlerException { + + Signature signature; + String signedEncodedString; + + try { + signature = Signature.getInstance(SIGNATURE_ALG); + signature.initSign(signatureKey); + signature.update(Base64.decodeBase64(message)); + + byte[] signatureBytes = signature.sign(); + signedEncodedString = Base64.encodeBase64String(signatureBytes); + + } catch (NoSuchAlgorithmException e) { + String errorMsg = + "Algorithm not found exception occurred for Signature instance of [" + SIGNATURE_ALG + "]"; + log.error(errorMsg); + throw new TransportHandlerException(errorMsg, e); + } catch (SignatureException e) { + String errorMsg = "Signature exception occurred for Signature instance of [" + SIGNATURE_ALG + "]"; + log.error(errorMsg); + throw new TransportHandlerException(errorMsg, e); + } catch (InvalidKeyException e) { + String errorMsg = "InvalidKey exception occurred for signatureKey \n[\n" + signatureKey + "\n]\n"; + log.error(errorMsg); + throw new TransportHandlerException(errorMsg, e); + } + + return signedEncodedString; + } + + + /** + * Verifies some signed-data against the a Public-Key to ensure that it was produced by the holder of the + * corresponding Private Key. + * + * @param data the actual payoad which was signed by some Private Key. + * @param signedData the signed data produced by signing the payload using a Private Key. + * @param verificationKey the corresponding Public Key which is an exact pair of the Private-Key with we expect + * the data to be signed by. + * @return true if the signed data verifies to be signed by the corresponding Private Key. + * @throws TransportHandlerException if some error occurs with the verification process which may be related to + * the signature algorithm used or the key used for signing. + */ + public static boolean verifySignature(String data, String signedData, PublicKey verificationKey) + throws TransportHandlerException { + + Signature signature; + boolean verified; + + try { + signature = Signature.getInstance(SIGNATURE_ALG); + signature.initVerify(verificationKey); + signature.update(Base64.decodeBase64(data)); + + verified = signature.verify(Base64.decodeBase64(signedData)); + + } catch (NoSuchAlgorithmException e) { + String errorMsg = + "Algorithm not found exception occurred for Signature instance of [" + SIGNATURE_ALG + "]"; + log.error(errorMsg); + throw new TransportHandlerException(errorMsg, e); + } catch (SignatureException e) { + String errorMsg = "Signature exception occurred for Signature instance of [" + SIGNATURE_ALG + "]"; + log.error(errorMsg); + throw new TransportHandlerException(errorMsg, e); + } catch (InvalidKeyException e) { + String errorMsg = "InvalidKey exception occurred for signatureKey \n[\n" + verificationKey + "\n]\n"; + log.error(errorMsg); + throw new TransportHandlerException(errorMsg, e); + } + + return verified; + } + + + /** + * Encrypts the message with the key that's passed in. + * + * @param encryptedMessage the encrypted message that is supposed to be decrypted. + * @param decryptKey the key to use in the decryption process. + * @return the decrypted message in String format. + * @throws TransportHandlerException if an error occurs with the encryption flow which can be due to Padding + * issues, encryption key being invalid or the algorithm used is unrecognizable. + */ + public static String decryptMessage(String encryptedMessage, Key decryptKey) throws TransportHandlerException { + + Cipher decrypter; + String decryptedMessage; + + try { + + decrypter = Cipher.getInstance(CIPHER_PADDING); + decrypter.init(Cipher.DECRYPT_MODE, decryptKey); + decryptedMessage = new String(decrypter.doFinal(Base64.decodeBase64(encryptedMessage)), + StandardCharsets.UTF_8); + + } catch (NoSuchAlgorithmException e) { + String errorMsg = "Algorithm not found exception occurred for Cipher instance of [" + CIPHER_PADDING + "]"; + log.error(errorMsg); + throw new TransportHandlerException(errorMsg, e); + } catch (NoSuchPaddingException e) { + String errorMsg = "No Padding error occurred for Cipher instance of [" + CIPHER_PADDING + "]"; + log.error(errorMsg); + throw new TransportHandlerException(errorMsg, e); + } catch (InvalidKeyException e) { + String errorMsg = "InvalidKey exception occurred for encryptionKey \n[\n" + decryptKey + "\n]\n"; + log.error(errorMsg); + throw new TransportHandlerException(errorMsg, e); + } catch (BadPaddingException e) { + String errorMsg = "Bad Padding error occurred for Cipher instance of [" + CIPHER_PADDING + "]"; + log.error(errorMsg); + throw new TransportHandlerException(errorMsg, e); + } catch (IllegalBlockSizeException e) { + String errorMsg = "Illegal blockSize error occurred for Cipher instance of [" + CIPHER_PADDING + "]"; + log.error(errorMsg); + throw new TransportHandlerException(errorMsg, e); + } + + return decryptedMessage; + } +} diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/advanced/transport/TransportHandler.java b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/advanced/transport/TransportHandler.java new file mode 100644 index 0000000000..8034f66e27 --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/advanced/transport/TransportHandler.java @@ -0,0 +1,47 @@ +/* + * 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.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.transport; + +/** + * This interface consists of the core functionality related to the transport between any device and the server. The + * interface is an abstraction, regardless of the underlying protocol used for the transport. Implementation of this + * interface by any class that caters a specific protocol (ex: HTTP, XMPP, MQTT, CoAP) would ideally have methods + * specific to the protocol used for communication and thees methods that implement the logic related to the devices + * using the protocol. + * + * @param a message type specific to the protocol implemented + */ +public interface TransportHandler { + int DEFAULT_TIMEOUT_INTERVAL = 5000; // millis ~ 10 sec + + void connect(); + + boolean isConnected(); + + //TODO:: Any errors needs to be thrown ahead + void processIncomingMessage(T message, String... messageParams); + + void processIncomingMessage(); + + void publishDeviceData(String... publishData); + + void publishDeviceData(); + + void disconnect(); +} diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/advanced/transport/TransportHandlerException.java b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/advanced/transport/TransportHandlerException.java new file mode 100644 index 0000000000..aeccc0fbce --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/advanced/transport/TransportHandlerException.java @@ -0,0 +1,56 @@ +/* + * 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.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.transport; + +public class TransportHandlerException extends Exception { + private static final long serialVersionUID = 2736466230451105440L; + + private String errorMessage; + + public String getErrorMessage() { + return errorMessage; + } + + public void setErrorMessage(String errorMessage) { + this.errorMessage = errorMessage; + } + + public TransportHandlerException(String msg, Exception nestedEx) { + super(msg, nestedEx); + setErrorMessage(msg); + } + + public TransportHandlerException(String message, Throwable cause) { + super(message, cause); + setErrorMessage(message); + } + + public TransportHandlerException(String msg) { + super(msg); + setErrorMessage(msg); + } + + public TransportHandlerException() { + super(); + } + + public TransportHandlerException(Throwable cause) { + super(cause); + } +} diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/advanced/transport/TransportUtils.java b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/advanced/transport/TransportUtils.java new file mode 100644 index 0000000000..a9b6676a3c --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/advanced/transport/TransportUtils.java @@ -0,0 +1,302 @@ +/* + * 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.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.transport; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.DatagramSocket; +import java.net.HttpURLConnection; +import java.net.InetAddress; +import java.net.MalformedURLException; +import java.net.NetworkInterface; +import java.net.ServerSocket; +import java.net.SocketException; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; +import java.util.Random; + +public class TransportUtils { + private static final Log log = LogFactory.getLog(TransportUtils.class); + + public static final int MIN_PORT_NUMBER = 9000; + public static final int MAX_PORT_NUMBER = 11000; + + /** + * Given a server endpoint as a String, this method splits it into Protocol, Host and Port + * + * @param ipString a network endpoint in the format - '://:' + * @return a map with keys "Protocol", "Host" & "Port" for the related values from the ipString + * @throws TransportHandlerException + */ + public static Map getHostAndPort(String ipString) + throws TransportHandlerException { + Map ipPortMap = new HashMap(); + String[] ipPortArray = ipString.split(":"); + + if (ipPortArray.length != 3) { + String errorMsg = + "The IP String - '" + ipString + + "' is invalid. It needs to be in format '://:'."; + log.info(errorMsg); + throw new TransportHandlerException(errorMsg); + } + + ipPortMap.put("Protocol", ipPortArray[0]); + ipPortMap.put("Host", ipPortArray[1].replace(File.separator, "")); + ipPortMap.put("Port", ipPortArray[2]); + return ipPortMap; + } + + /** + * This method validates whether a specific IP Address is of IPv4 type + * + * @param ipAddress the IP Address which needs to be validated + * @return true if it is of IPv4 type and false otherwise + */ + public static boolean validateIPv4(String ipAddress) { + try { + if (ipAddress == null || ipAddress.isEmpty()) { + return false; + } + + String[] parts = ipAddress.split("\\."); + if (parts.length != 4) { + return false; + } + + for (String s : parts) { + int i = Integer.parseInt(s); + if ((i < 0) || (i > 255)) { + return false; + } + } + return !ipAddress.endsWith("."); + + } catch (NumberFormatException nfe) { + log.warn("The IP Address: " + ipAddress + " could not " + + "be validated against IPv4-style"); + return false; + } + } + + + public static Map getInterfaceIPMap() throws TransportHandlerException { + + Map interfaceToIPMap = new HashMap(); + Enumeration networkInterfaces; + String networkInterfaceName = ""; + String ipAddress; + + try { + networkInterfaces = NetworkInterface.getNetworkInterfaces(); + } catch (SocketException exception) { + String errorMsg = + "Error encountered whilst trying to get the list of network-interfaces"; + log.error(errorMsg); + throw new TransportHandlerException(errorMsg, exception); + } + + try { + for (; networkInterfaces.hasMoreElements(); ) { + networkInterfaceName = networkInterfaces.nextElement().getName(); + + if (log.isDebugEnabled()) { + log.debug("Network Interface: " + networkInterfaceName); + log.debug("------------------------------------------"); + } + + Enumeration interfaceIPAddresses = NetworkInterface.getByName( + networkInterfaceName).getInetAddresses(); + + for (; interfaceIPAddresses.hasMoreElements(); ) { + ipAddress = interfaceIPAddresses.nextElement().getHostAddress(); + + if (log.isDebugEnabled()) { + log.debug("IP Address: " + ipAddress); + } + + if (TransportUtils.validateIPv4(ipAddress)) { + interfaceToIPMap.put(networkInterfaceName, ipAddress); + } + } + + if (log.isDebugEnabled()) { + log.debug("------------------------------------------"); + } + } + } catch (SocketException exception) { + String errorMsg = + "Error encountered whilst trying to get the IP Addresses of the network " + + "interface: " + networkInterfaceName; + log.error(errorMsg); + throw new TransportHandlerException(errorMsg, exception); + } + + return interfaceToIPMap; + } + + + /** + * Attempts to find a free port between the MIN_PORT_NUMBER(9000) and MAX_PORT_NUMBER(11000). + * Tries 'RANDOMLY picked' port numbers between this range up-until "randomAttempts" number of + * times. If still fails, then tries each port in descending order from the MAX_PORT_NUMBER + * whilst skipping already attempted ones via random selection. + * + * @param randomAttempts no of times to TEST port numbers picked randomly over the given range + * @return an available/free port + */ + public static synchronized int getAvailablePort(int randomAttempts) { + ArrayList failedPorts = new ArrayList(randomAttempts); + + Random randomNum = new Random(); + int randomPort = MAX_PORT_NUMBER; + + while (randomAttempts > 0) { + randomPort = randomNum.nextInt(MAX_PORT_NUMBER - MIN_PORT_NUMBER) + MIN_PORT_NUMBER; + + if (checkIfPortAvailable(randomPort)) { + return randomPort; + } + failedPorts.add(randomPort); + randomAttempts--; + } + + randomPort = MAX_PORT_NUMBER; + + while (true) { + if (!failedPorts.contains(randomPort) && checkIfPortAvailable(randomPort)) { + return randomPort; + } + randomPort--; + } + } + + + private static boolean checkIfPortAvailable(int port) { + ServerSocket tcpSocket = null; + DatagramSocket udpSocket = null; + + try { + tcpSocket = new ServerSocket(port); + tcpSocket.setReuseAddress(true); + + udpSocket = new DatagramSocket(port); + udpSocket.setReuseAddress(true); + return true; + } catch (IOException ex) { + // denotes the port is in use + } finally { + if (tcpSocket != null) { + try { + tcpSocket.close(); + } catch (IOException e) { + /* not to be thrown */ + } + } + + if (udpSocket != null) { + udpSocket.close(); + } + } + + return false; + } + + + /** + * This is a utility method that creates and returns a HTTP connection object. + * + * @param urlString the URL pattern to which the connection needs to be created + * @return an HTTPConnection object which cn be used to send HTTP requests + * @throws TransportHandlerException if errors occur when creating the HTTP connection with + * the given URL string + */ + public static HttpURLConnection getHttpConnection(String urlString) throws + TransportHandlerException { + URL connectionUrl; + 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 TransportHandlerException(errorMsg, e); + } catch (IOException exception) { + String errorMsg = "Error occured whilst trying to open a connection to: " + urlString; + log.error(errorMsg); + throw new TransportHandlerException(errorMsg, exception); + } + return httpConnection; + } + + /** + * This is a utility method that reads and returns the response from a HTTP connection + * + * @param httpConnection the connection from which a response is expected + * @return the response (as a string) from the given HTTP connection + * @throws TransportHandlerException if any errors occur whilst reading the response from + * the connection stream + */ + public static String readResponseFromHttpRequest(HttpURLConnection httpConnection) + throws TransportHandlerException { + BufferedReader bufferedReader; + try { + bufferedReader = new BufferedReader(new InputStreamReader( + httpConnection.getInputStream(), StandardCharsets.UTF_8)); + } catch (IOException exception) { + String errorMsg = "There is an issue with connecting the reader to the input stream at: " + + httpConnection.getURL(); + log.error(errorMsg); + throw new TransportHandlerException(errorMsg, exception); + } + + String responseLine; + StringBuilder completeResponse = new StringBuilder(); + + try { + while ((responseLine = bufferedReader.readLine()) != null) { + completeResponse.append(responseLine); + } + } catch (IOException exception) { + String errorMsg = "Error occured whilst trying read from the connection stream at: " + + httpConnection.getURL(); + log.error(errorMsg); + throw new TransportHandlerException(errorMsg, exception); + } + try { + bufferedReader.close(); + } catch (IOException exception) { + log.error( + "Could not succesfully close the bufferedReader to the connection at: " + httpConnection.getURL()); + } + return completeResponse.toString(); + } + +} diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/advanced/transport/http/HTTPTransportHandler.java b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/advanced/transport/http/HTTPTransportHandler.java new file mode 100644 index 0000000000..6abee0b09b --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/advanced/transport/http/HTTPTransportHandler.java @@ -0,0 +1,91 @@ +/* + * 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.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.transport.http; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.eclipse.jetty.server.Server; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.transport.TransportHandler; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.transport.TransportUtils; + +/** + * This is an abstract class that implements the "TransportHandler" interface. The interface is an abstraction for + * the core functionality with regards to device-server communication regardless of the Transport protocol. This + * specific class contains the HTTP-Transport specific implementations. The class implements utility methods for the + * case of a HTTP communication. However, this "abstract class", even-though it implements the "TransportHandler" + * interface, does not contain the logic relevant to the interface methods. The specific functionality of the + * interface methods are intended to be implemented by the concrete class that extends this abstract class and + * utilizes the HTTP specific functionality (ideally a device API writer who would like to communicate to the device + * via HTTP Protocol). + */ +public abstract class HTTPTransportHandler implements TransportHandler { + private static final Log log = LogFactory.getLog(HTTPTransportHandler.class); + + protected Server server; + protected int port; + protected int timeoutInterval; + + protected HTTPTransportHandler() { + this.port = TransportUtils.getAvailablePort(10); + this.server = new Server(port); + timeoutInterval = DEFAULT_TIMEOUT_INTERVAL; + } + + protected HTTPTransportHandler(int port) { + this.port = port; + this.server = new Server(this.port); + timeoutInterval = DEFAULT_TIMEOUT_INTERVAL; + } + + protected HTTPTransportHandler(int port, int timeoutInterval) { + this.port = port; + this.server = new Server(this.port); + this.timeoutInterval = timeoutInterval; + } + + public void setTimeoutInterval(int timeoutInterval) { + this.timeoutInterval = timeoutInterval; + } + + /** + * Checks whether the HTTP server is up and listening for incoming requests. + * + * @return true if the server is up & listening for requests, else false. + */ + public boolean isConnected() { + return server.isStarted(); + } + + + protected void incrementPort() { + this.port = this.port + 1; + server = new Server(port); + } + + /** + * Shuts-down the HTTP Server. + */ + public void closeConnection() throws Exception { + if (server != null && isConnected()) { + server.stop(); + } + } + + +} diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/advanced/transport/mqtt/MQTTTransportHandler.java b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/advanced/transport/mqtt/MQTTTransportHandler.java new file mode 100644 index 0000000000..d4626f6dff --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/advanced/transport/mqtt/MQTTTransportHandler.java @@ -0,0 +1,360 @@ +/* + * 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.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.transport.mqtt; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken; +import org.eclipse.paho.client.mqttv3.MqttCallback; +import org.eclipse.paho.client.mqttv3.MqttClient; +import org.eclipse.paho.client.mqttv3.MqttConnectOptions; +import org.eclipse.paho.client.mqttv3.MqttException; +import org.eclipse.paho.client.mqttv3.MqttMessage; +import org.eclipse.paho.client.mqttv3.MqttSecurityException; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.transport.TransportHandler; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.transport.TransportHandlerException; + +import java.io.File; +import java.nio.charset.StandardCharsets; + +/** + * This is an abstract class that implements the "TransportHandler" interface. The interface is an abstraction for + * the core functionality with regards to device-server communication regardless of the Transport protocol. This + * specific class contains the HTTP-Transport specific implementations. The class implements utility methods for the + * case of a HTTP communication. However, this "abstract class", even-though it implements the "TransportHandler" + * interface, does not contain the logic relevant to the interface methods. The specific functionality of the + * interface methods are intended to be implemented by the concrete class that extends this abstract class and + * utilizes the HTTP specific functionality (ideally a device API writer who would like to communicate to the device + * via HTTP Protocol). + *

      + * This class contains the Device-Management specific implementation for all the MQTT functionality. This includes + * connecting to a MQTT Broker & subscribing to the appropriate MQTT-topic, action plan upon losing connection or + * successfully delivering a message to the broker and processing incoming messages. Makes use of the 'Paho-MQTT' + * library provided by Eclipse Org. + */ +public abstract class MQTTTransportHandler + implements MqttCallback, TransportHandler { + private static final Log log = LogFactory.getLog(MQTTTransportHandler.class); + + public static final int DEFAULT_MQTT_QUALITY_OF_SERVICE = 0; + + private MqttClient client; + private String clientId; + private MqttConnectOptions options; + private String clientWillTopic; + + protected String mqttBrokerEndPoint; + protected int timeoutInterval; + protected String subscribeTopic; + + /** + * Constructor for the MQTTTransportHandler which takes in the owner, type of the device + * and the MQTT Broker URL and the topic to subscribe. + * + * @param deviceOwner the owner of the device. + * @param deviceType the CDMF Device-Type of the device. + * @param mqttBrokerEndPoint the IP/URL of the MQTT broker endpoint. + * @param subscribeTopic the MQTT topic to which the client is to be subscribed + */ + protected MQTTTransportHandler(String deviceOwner, String deviceType, + String mqttBrokerEndPoint, + String subscribeTopic) { + this.clientId = deviceOwner + ":" + deviceType; + this.subscribeTopic = subscribeTopic; + this.clientWillTopic = deviceType + File.separator + "disconnection"; + this.mqttBrokerEndPoint = mqttBrokerEndPoint; + this.timeoutInterval = DEFAULT_TIMEOUT_INTERVAL; + this.initSubscriber(); + } + + /** + * Constructor for the MQTTTransportHandler which takes in the owner, type of the device + * and the MQTT Broker URL and the topic to subscribe. Additionally this constructor takes in + * the reconnection-time interval between successive attempts to connect to the broker. + * + * @param deviceOwner the owner of the device. + * @param deviceType the CDMF Device-Type of the device. + * @param mqttBrokerEndPoint the IP/URL of the MQTT broker endpoint. + * @param subscribeTopic the MQTT topic to which the client is to be subscribed + * @param intervalInMillis the time interval in MILLI-SECONDS between successive + * attempts to connect to the broker. + */ + protected MQTTTransportHandler(String deviceOwner, String deviceType, + String mqttBrokerEndPoint, String subscribeTopic, + int intervalInMillis) { + this.clientId = deviceOwner + ":" + deviceType; + this.subscribeTopic = subscribeTopic; + //TODO:: Use constant strings + this.clientWillTopic = deviceType + File.separator + "disconnection"; + this.mqttBrokerEndPoint = mqttBrokerEndPoint; + this.timeoutInterval = intervalInMillis; + this.initSubscriber(); + } + + public void setTimeoutInterval(int timeoutInterval) { + this.timeoutInterval = timeoutInterval; + } + + /** + * Initializes the MQTT-Client. Creates a client using the given MQTT-broker endpoint and the + * clientId (which is constructed by a concatenation of [deviceOwner]:[deviceType]). Also sets + * the client's options parameter with the clientWillTopic (in-case of connection failure) and + * other info. Also sets the call-back this current class. + */ + private void initSubscriber() { + try { + client = new MqttClient(this.mqttBrokerEndPoint, clientId, null); + //TODO:: Need to check for debug + log.info("MQTT subscriber was created with ClientID : " + clientId); + } catch (MqttException ex) { + //TODO:: Remove unnecessary formatting and print exception + String errorMsg = "MQTT Client Error\n" + "\tReason: " + ex.getReasonCode() + + "\n\tMessage: " + ex.getMessage() + "\n\tLocalMsg: " + + ex.getLocalizedMessage() + "\n\tCause: " + ex.getCause() + + "\n\tException: " + ex; + log.error(errorMsg); + //TODO:: Throw the error out + } + + options = new MqttConnectOptions(); + options.setCleanSession(false); + //TODO:: Use constant strings + options.setWill(clientWillTopic, "Connection-Lost".getBytes(StandardCharsets.UTF_8), 2, + true); + client.setCallback(this); + } + + /** + * Checks whether the connection to the MQTT-Broker persists. + * + * @return true if the client is connected to the MQTT-Broker, else false. + */ + @Override + public boolean isConnected() { + return client.isConnected(); + } + + + /** + * Connects to the MQTT-Broker and if successfully established connection. + * + * @throws TransportHandlerException in the event of 'Connecting to' the MQTT broker fails. + */ + protected void connectToQueue() throws TransportHandlerException { + try { + client.connect(options); + + if (log.isDebugEnabled()) { + log.debug("Subscriber connected to queue at: " + this.mqttBrokerEndPoint); + } + } catch (MqttSecurityException ex) { + String errorMsg = "MQTT Security Exception when connecting to queue\n" + "\tReason: " + + " " + + ex.getReasonCode() + "\n\tMessage: " + ex.getMessage() + + "\n\tLocalMsg: " + ex.getLocalizedMessage() + "\n\tCause: " + + ex.getCause() + "\n\tException: " + ex; + //TODO:: Compulsory log of errors and remove formatted error + if (log.isDebugEnabled()) { + log.debug(errorMsg); + } + throw new TransportHandlerException(errorMsg, ex); + + } catch (MqttException ex) { + //TODO:: Compulsory log of errors and remove formatted error + String errorMsg = "MQTT Exception when connecting to queue\n" + "\tReason: " + + ex.getReasonCode() + "\n\tMessage: " + ex.getMessage() + + "\n\tLocalMsg: " + ex.getLocalizedMessage() + "\n\tCause: " + + ex.getCause() + "\n\tException: " + ex; + if (log.isDebugEnabled()) { + log.debug(errorMsg); + } + throw new TransportHandlerException(errorMsg, ex); + } + } + + /** + * Subscribes to the MQTT-Topic specific to this MQTT Client. (The MQTT-Topic specific to the + * device is taken in as a constructor parameter of this class) . + * + * @throws TransportHandlerException in the event of 'Subscribing to' the MQTT broker + * fails. + */ + protected void subscribeToQueue() throws TransportHandlerException { + try { + //TODO:: QoS Level take it from a variable + client.subscribe(subscribeTopic, 0); + log.info("Subscriber '" + clientId + "' subscribed to topic: " + subscribeTopic); + } catch (MqttException ex) { + //TODO:: Compulsory log of errors and remove formatted error + String errorMsg = "MQTT Exception when trying to subscribe to topic: " + + subscribeTopic + "\n\tReason: " + ex.getReasonCode() + + "\n\tMessage: " + ex.getMessage() + "\n\tLocalMsg: " + + ex.getLocalizedMessage() + "\n\tCause: " + ex.getCause() + + "\n\tException: " + ex; + if (log.isDebugEnabled()) { + log.debug(errorMsg); + } + + throw new TransportHandlerException(errorMsg, ex); + } + } + + + /** + * This method is used to publish reply-messages for the control signals received. + * Invocation of this method calls its overloaded-method with a QoS equal to that of the + * default value. + * + * @param topic the topic to which the reply message is to be published. + * @param payLoad the reply-message (payload) of the MQTT publish action. + */ + protected void publishToQueue(String topic, String payLoad) + throws TransportHandlerException { + publishToQueue(topic, payLoad, DEFAULT_MQTT_QUALITY_OF_SERVICE, false); + } + + /** + * This is an overloaded method that publishes MQTT reply-messages for control signals + * received form the IoT-Server. + * + * @param topic the topic to which the reply message is to be published + * @param payLoad the reply-message (payload) of the MQTT publish action. + * @param qos the Quality-of-Service of the current publish action. + * Could be 0(At-most once), 1(At-least once) or 2(Exactly once) + */ + protected void publishToQueue(String topic, String payLoad, int qos, boolean retained) + throws TransportHandlerException { + try { + client.publish(topic, payLoad.getBytes(StandardCharsets.UTF_8), qos, retained); + if (log.isDebugEnabled()) { + log.debug("Message: " + payLoad + " to MQTT topic [" + topic + + "] published successfully"); + } + } catch (MqttException ex) { + String errorMsg = + "MQTT Client Error" + "\n\tReason: " + ex.getReasonCode() + "\n\tMessage: " + + ex.getMessage() + "\n\tLocalMsg: " + ex.getLocalizedMessage() + + "\n\tCause: " + ex.getCause() + "\n\tException: " + ex; + log.info(errorMsg); + throw new TransportHandlerException(errorMsg, ex); + } + } + + + protected void publishToQueue(String topic, MqttMessage message) + throws TransportHandlerException { + try { + client.publish(topic, message); + if (log.isDebugEnabled()) { + log.debug("Message: " + message.toString() + " to MQTT topic [" + topic + + "] published successfully"); + } + } catch (MqttException ex) { + //TODO:: Compulsory log of errors and remove formatted error + String errorMsg = + "MQTT Client Error" + "\n\tReason: " + ex.getReasonCode() + "\n\tMessage: " + + ex.getMessage() + "\n\tLocalMsg: " + ex.getLocalizedMessage() + + "\n\tCause: " + ex.getCause() + "\n\tException: " + ex; + log.info(errorMsg); + throw new TransportHandlerException(errorMsg, ex); + } + } + + + /** + * Callback method which is triggered once the MQTT client losers its connection to the broker. + * Spawns a new thread that executes necessary actions to try and reconnect to the endpoint. + * + * @param throwable a Throwable Object containing the details as to why the failure occurred. + */ + @Override + public void connectionLost(Throwable throwable) { + log.warn("Lost Connection for client: " + this.clientId + + " to " + this.mqttBrokerEndPoint + ".\nThis was due to - " + + throwable.getMessage()); + + Thread reconnectThread = new Thread() { + public void run() { + connect(); + } + }; + reconnectThread.setDaemon(true); + reconnectThread.start(); + } + + /** + * Callback method which is triggered upon receiving a MQTT Message from the broker. Spawns a + * new thread that executes any actions to be taken with the received message. + * + * @param topic the MQTT-Topic to which the received message was published to and the + * client was subscribed to. + * @param mqttMessage the actual MQTT-Message that was received from the broker. + */ + @Override + public void messageArrived(final String topic, final MqttMessage mqttMessage) { + if (log.isDebugEnabled()) { + log.info("Got an MQTT message '" + mqttMessage.toString() + "' for topic '" + topic + "'."); + } + + Thread messageProcessorThread = new Thread() { + public void run() { + processIncomingMessage(mqttMessage, topic); + } + }; + messageProcessorThread.setDaemon(true); + messageProcessorThread.start(); + } + + /** + * Callback method which gets triggered upon successful completion of a message delivery to + * the broker. + * + * @param iMqttDeliveryToken the MQTT-DeliveryToken which includes the details about the + * specific message delivery. + */ + @Override + public void deliveryComplete(IMqttDeliveryToken iMqttDeliveryToken) { + String message = ""; + try { + message = iMqttDeliveryToken.getMessage().toString(); + } catch (MqttException e) { + //TODO:: Throw errors + log.error( + "Error occurred whilst trying to read the message from the MQTT delivery " + + "token."); + } + String topic = iMqttDeliveryToken.getTopics()[0]; + String client = iMqttDeliveryToken.getClient().getClientId(); + + if (log.isDebugEnabled()) { + log.debug("Message - '" + message + "' of client [" + client + "] for the topic (" + + topic + ") was delivered successfully."); + } + } + + /** + * Closes the connection to the MQTT Broker. + */ + public void closeConnection() throws MqttException { + if (client != null && isConnected()) { + client.disconnect(); + } + } +} + diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/advanced/transport/xmpp/XMPPTransportHandler.java b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/advanced/transport/xmpp/XMPPTransportHandler.java new file mode 100644 index 0000000000..ba8320c7cf --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/advanced/transport/xmpp/XMPPTransportHandler.java @@ -0,0 +1,366 @@ +/* + * 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.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.transport.xmpp; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.jivesoftware.smack.ConnectionConfiguration; +import org.jivesoftware.smack.PacketListener; +import org.jivesoftware.smack.SmackConfiguration; +import org.jivesoftware.smack.XMPPConnection; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smack.filter.AndFilter; +import org.jivesoftware.smack.filter.FromContainsFilter; +import org.jivesoftware.smack.filter.OrFilter; +import org.jivesoftware.smack.filter.PacketFilter; +import org.jivesoftware.smack.filter.PacketTypeFilter; +import org.jivesoftware.smack.filter.ToContainsFilter; +import org.jivesoftware.smack.packet.Message; +import org.jivesoftware.smack.packet.Packet; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.transport.TransportHandler; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.transport.TransportHandlerException; + +/** + * This is an abstract class that implements the "TransportHandler" interface. The interface is an abstraction for + * the core functionality with regards to device-server communication regardless of the Transport protocol. This + * specific class contains the HTTP-Transport specific implementations. The class implements utility methods for the + * case of a HTTP communication. However, this "abstract class", even-though it implements the "TransportHandler" + * interface, does not contain the logic relevant to the interface methods. The specific functionality of the + * interface methods are intended to be implemented by the concrete class that extends this abstract class and + * utilizes the HTTP specific functionality (ideally a device API writer who would like to communicate to the device + * via HTTP Protocol). + *

      + * This class contains the IoT-Server specific implementation for all the XMPP functionality. This includes + * connecting to a XMPP Server & Login-In using the device's/server's XMPP-Account, Setting listeners and filters on + * incoming XMPP messages and Sending XMPP replies for messages received. Makes use of the 'Smack-XMPP' library + * provided by jivesoftware/igniterealtime. + */ +public abstract class XMPPTransportHandler implements TransportHandler { + private static final Log log = LogFactory.getLog(XMPPTransportHandler.class); + + protected String server; + protected int timeoutInterval; // millis + + //TODO:: Shouldnt be hard-coded. Need to be read from configs + private static final int DEFAULT_XMPP_PORT = 5222; + private XMPPConnection connection; + private int port; + private ConnectionConfiguration config; + private PacketFilter filter; + private PacketListener listener; + + + /** + * Constructor for XMPPTransportHandler passing only the server-IP. + * + * @param server the IP of the XMPP server. + */ + protected XMPPTransportHandler(String server) { + this.server = server; + this.port = DEFAULT_XMPP_PORT; + this.timeoutInterval = DEFAULT_TIMEOUT_INTERVAL; + initXMPPClient(); + } + + /** + * Constructor for XMPPTransportHandler passing server-IP and the XMPP-port. + * + * @param server the IP of the XMPP server. + * @param port the XMPP server's port to connect to. (default - 5222) + */ + protected XMPPTransportHandler(String server, int port) { + this.server = server; + this.port = port; + this.timeoutInterval = DEFAULT_TIMEOUT_INTERVAL; + initXMPPClient(); + } + + /** + * Constructor for XMPPTransportHandler passing server-IP, the XMPP-port and the + * timeoutInterval used by listeners to the server and for reconnection schedules. + * + * @param server the IP of the XMPP server. + * @param port the XMPP server's port to connect to. (default - 5222) + * @param timeoutInterval the timeout interval to use for the connection and reconnection + */ + protected XMPPTransportHandler(String server, int port, int timeoutInterval) { + this.server = server; + this.port = port; + this.timeoutInterval = timeoutInterval; + initXMPPClient(); + } + + /** + * Sets the client's time-out-limit whilst waiting for XMPP-replies from server. + * + * @param millis the time in millis to be set as the time-out-limit whilst waiting for a + * XMPP-reply. + */ + public void setTimeoutInterval(int millis) { + this.timeoutInterval = millis; + } + + /** + * Checks whether the connection to the XMPP-Server persists. + * + * @return true if the client is connected to the XMPP-Server, else false. + */ + @Override + public boolean isConnected() { + return connection.isConnected(); + } + + /** + * Initializes the XMPP Client. Sets the time-out-limit whilst waiting for XMPP-replies from + * server. Sets the XMPP configurations to connect to the server and creates the + * XMPPConnection object used for connecting and Logging-In. + */ + private void initXMPPClient() { + log.info(String.format("Initializing connection to XMPP Server at %1$s via port " + + "%2$d.", server, port)); + SmackConfiguration.setPacketReplyTimeout(timeoutInterval); + config = new ConnectionConfiguration(server, port); +// TODO:: Need to enable SASL-Authentication appropriately + config.setSASLAuthenticationEnabled(false); + config.setSecurityMode(ConnectionConfiguration.SecurityMode.disabled); + connection = new XMPPConnection(config); + } + +//TODO:: Re-check all exception handling + + /** + * Connects to the XMPP-Server and if attempt unsuccessful, then throws exception. + * + * @throws TransportHandlerException in the event of 'Connecting to' the XMPP server fails. + */ + protected void connectToServer() throws TransportHandlerException { + try { + connection.connect(); + log.info(String.format("Connection to XMPP Server at %1$s established successfully......", server)); + + } catch (XMPPException xmppExcepion) { + String errorMsg = "Connection attempt to the XMPP Server at " + server + " via port " + port + " failed."; + log.info(errorMsg); + throw new TransportHandlerException(errorMsg, xmppExcepion); + } + } + + /** + * If successfully established connection, then tries to Log in using the device's XMPP + * Account credentials. + * + * @param username the username of the device's XMPP-Account. + * @param password the password of the device's XMPP-Account. + * @param resource the resource the resource, specific to the XMPP-Account to which the login + * is made to + * @throws TransportHandlerException in the event of 'Logging into' the XMPP server fails. + */ + protected void loginToServer(String username, String password, String resource) + throws TransportHandlerException { + if (isConnected()) { + try { + if (resource == null) { + connection.login(username, password); + log.info(String.format("Logged into XMPP Server at %1$s as user %2$s......", server, username)); + } else { + connection.login(username, password, resource); + log.info(String.format("Logged into XMPP Server at %1$s as user %2$s on resource %3$s......", + server, username, resource)); + } + } catch (XMPPException xmppException) { + String errorMsg = + "Login attempt to the XMPP Server at " + server + " with username - " + username + " failed."; + log.error(errorMsg); + throw new TransportHandlerException(errorMsg, xmppException); + } + } else { + String errorMsg = "Not connected to XMPP-Server to attempt Login. Please 'connectToServer' before Login"; + throw new TransportHandlerException(errorMsg); + } + } + + + /** + * Sets a filter for all the incoming XMPP-Messages on the Sender's JID (XMPP-Account ID). + * Also creates a listener for the incoming messages and connects the listener to the + * XMPPConnection alongside the set filter. + * + * @param senderJID the JID (XMPP-Account ID of the sender) to which the filter is to be set. + */ + protected void setFilterOnSender(String senderJID) { + filter = new AndFilter(new PacketTypeFilter(Message.class), new FromContainsFilter( + senderJID)); + listener = new PacketListener() { + @Override + public void processPacket(Packet packet) { + if (packet instanceof Message) { + final Message xmppMessage = (Message) packet; + Thread msgProcessThread = new Thread() { + public void run() { + processIncomingMessage(xmppMessage); + } + }; + msgProcessThread.setDaemon(true); + msgProcessThread.start(); + } + } + }; + + connection.addPacketListener(listener, filter); + } + + + /** + * Sets a filter for all the incoming XMPP-Messages on the Receiver's JID (XMPP-Account ID). + * Also creates a listener for the incoming messages and connects the listener to the + * XMPPConnection alongside the set filter. + * + * @param receiverJID the JID (XMPP-Account ID of the receiver) to which the filter is to be + * set. + */ + protected void setFilterOnReceiver(String receiverJID) { + filter = new AndFilter(new PacketTypeFilter(Message.class), new ToContainsFilter( + receiverJID)); + listener = new PacketListener() { + @Override + public void processPacket(Packet packet) { + if (packet instanceof Message) { + final Message xmppMessage = (Message) packet; + Thread msgProcessThread = new Thread() { + public void run() { + processIncomingMessage(xmppMessage); + } + }; + msgProcessThread.setDaemon(true); + msgProcessThread.start(); + } + } + }; + + connection.addPacketListener(listener, filter); + } + + + /** + * Sets a filter for all the incoming XMPP-Messages on the From-JID & To-JID (XMPP-Account IDs) + * passed in. Also creates a listener for the incoming messages and connects the listener to + * the XMPPConnection alongside the set filter. + * + * @param senderJID the From-JID (XMPP-Account ID) to which the filter is to be set. + * @param receiverJID the To-JID (XMPP-Account ID) to which the filter is to be set. + * @param andCondition if true: then filter is set with 'AND' operator (senderJID && + * receiverJID), + * if false: then the filter is set with 'OR' operator (senderJID | + * receiverJID) + */ + protected void setMessageFilterAndListener(String senderJID, String receiverJID, boolean + andCondition) { + PacketFilter jidFilter; + + if (andCondition) { + jidFilter = new AndFilter(new FromContainsFilter(senderJID), new ToContainsFilter( + receiverJID)); + } else { + jidFilter = new OrFilter(new FromContainsFilter(senderJID), new ToContainsFilter( + receiverJID)); + } + + filter = new AndFilter(new PacketTypeFilter(Message.class), jidFilter); + listener = new PacketListener() { + @Override + public void processPacket(Packet packet) { + if (packet instanceof Message) { + final Message xmppMessage = (Message) packet; + Thread msgProcessThread = new Thread() { + public void run() { + processIncomingMessage(xmppMessage); + } + }; + msgProcessThread.setDaemon(true); + msgProcessThread.start(); + } + } + }; + + connection.addPacketListener(listener, filter); + } + + + /** + * Sends an XMPP message. Calls the overloaded method with Subject set to "Reply-From-Device" + * + * @param JID the JID (XMPP Account ID) to which the message is to be sent to. + * @param message the XMPP-Message that is to be sent. + */ + protected void sendXMPPMessage(String JID, String message) { + sendXMPPMessage(JID, message, "XMPP-Message"); + } + + + /** + * Overloaded method to send an XMPP message. Includes the subject to be mentioned in the + * message that is sent. + * + * @param JID the JID (XMPP Account ID) to which the message is to be sent to. + * @param message the XMPP-Message that is to be sent. + * @param subject the subject that the XMPP-Message would carry. + */ + protected void sendXMPPMessage(String JID, String message, String subject) { + Message xmppMessage = new Message(); + xmppMessage.setTo(JID); + xmppMessage.setSubject(subject); + xmppMessage.setBody(message); + xmppMessage.setType(Message.Type.chat); + sendXMPPMessage(JID, xmppMessage); + } + + + /** + * Sends an XMPP message. + * + * @param JID the JID (XMPP Account ID) to which the message is to be sent to. + * @param xmppMessage the XMPP-Message that is to be sent. + */ + protected void sendXMPPMessage(String JID, Message xmppMessage) { + connection.sendPacket(xmppMessage); + if (log.isDebugEnabled()) { + log.debug("Message: '" + xmppMessage.getBody() + "' sent to XMPP JID [" + JID + + "] sent successfully."); + } + } + + + /** + * Disables default debugger provided by the XMPPConnection. + */ + protected void disableDebugger() { + connection.DEBUG_ENABLED = false; + } + + + /** + * Closes the connection to the XMPP Server. + */ + public void closeConnection() { + if (connection != null && isConnected()) { + connection.disconnect(); + } + } + +} diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/advanced/virtual/VirtualHardwareManager.java b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/advanced/virtual/VirtualHardwareManager.java new file mode 100644 index 0000000000..59a13bb87f --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/advanced/virtual/VirtualHardwareManager.java @@ -0,0 +1,218 @@ +/* + * 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.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.virtual; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.core.AgentConstants; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.core.AgentUtilOperations; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.virtual.ui.AgentUI; + +import javax.sound.midi.InvalidMidiDataException; +import javax.sound.midi.MidiSystem; +import javax.sound.midi.MidiUnavailableException; +import javax.sound.midi.Sequence; +import javax.sound.midi.Sequencer; +import javax.sound.sampled.Clip; +import javax.swing.*; +import java.io.IOException; +import java.io.InputStream; + +/** + * This class use to emulate virtual hardware functionality + */ +public class VirtualHardwareManager { + + private static final Log log = LogFactory.getLog(VirtualHardwareManager.class); + + private static VirtualHardwareManager virtualHardwareManager; + + private AgentUI agentUI; + private Sequencer sequencer = null; + + private int temperature = 30, humidity = 30; + private int temperatureMin = 20, temperatureMax = 50, humidityMin = 20, humidityMax = 50; + private int temperatureSVF = 50, humiditySVF = 50; + private boolean isTemperatureRandomized, isHumidityRandomized; + private boolean isTemperatureSmoothed, isHumiditySmoothed; + + private VirtualHardwareManager(){ + } + + public static VirtualHardwareManager getInstance(){ + if (virtualHardwareManager == null){ + virtualHardwareManager = new VirtualHardwareManager(); + } + return virtualHardwareManager; + } + + public void init(){ + try { + // Set System L&F for Device UI + UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); + } catch (UnsupportedLookAndFeelException e) { + log.error( + "'UnsupportedLookAndFeelException' error occurred whilst initializing the" + + " Agent UI."); + } catch (ClassNotFoundException e) { + log.error( + "'ClassNotFoundException' error occurred whilst initializing the Agent UI."); + } catch (InstantiationException e) { + log.error( + "'InstantiationException' error occurred whilst initializing the Agent UI."); + } catch (IllegalAccessException e) { + log.error( + "'IllegalAccessException' error occurred whilst initializing the Agent UI."); + } + java.awt.EventQueue.invokeLater(new Runnable() { + public void run() { + agentUI = new AgentUI(); + agentUI.setVisible(true); + } + }); + setAudioSequencer(); + } + + /** + * Get temperature from emulated device + * @return Temperature + */ + public int getTemperature() { + if (isTemperatureRandomized) { + temperature = getRandom(temperatureMax, temperatureMin, temperature, + isTemperatureSmoothed, temperatureSVF); + agentUI.updateTemperature(temperature); + } + return temperature; + } + + public void setTemperature(int temperature) { + this.temperature = temperature; + } + + /** + * Get humidity from emulated device + * @return Humidity + */ + public int getHumidity() { + if (isHumidityRandomized) { + humidity = getRandom(humidityMax, humidityMin, humidity, isHumiditySmoothed, + humiditySVF); + agentUI.updateHumidity(humidity); + } + return humidity; + } + + public void setHumidity(int humidity) { + this.humidity = humidity; + } + + public void setTemperatureMin(int temperatureMin) { + this.temperatureMin = temperatureMin; + } + + public void setTemperatureMax(int temperatureMax) { + this.temperatureMax = temperatureMax; + } + + public void setHumidityMin(int humidityMin) { + this.humidityMin = humidityMin; + } + + public void setHumidityMax(int humidityMax) { + this.humidityMax = humidityMax; + } + + public void setIsHumidityRandomized(boolean isHumidityRandomized) { + this.isHumidityRandomized = isHumidityRandomized; + } + + public void setIsTemperatureRandomized(boolean isTemperatureRandomized) { + this.isTemperatureRandomized = isTemperatureRandomized; + } + + public void setTemperatureSVF(int temperatureSVF) { + this.temperatureSVF = temperatureSVF; + } + + public void setHumiditySVF(int humiditySVF) { + this.humiditySVF = humiditySVF; + } + + public void setIsTemperatureSmoothed(boolean isTemperatureSmoothed) { + this.isTemperatureSmoothed = isTemperatureSmoothed; + } + + public void setIsHumiditySmoothed(boolean isHumiditySmoothed) { + this.isHumiditySmoothed = isHumiditySmoothed; + } + + public void changeAlarmStatus(boolean isOn) { + agentUI.setAlarmStatus(isOn); + + if (isOn) { + sequencer.start(); + } else { + sequencer.stop(); + } + } + + public void addToPolicyLog(String policy) { + agentUI.addToPolicyLog(policy); + } + + + private int getRandom(int max, int min, int current, boolean isSmoothed, int svf) { + + if (isSmoothed) { + int offset = (max - min) * svf / 100; + double mx = current + offset; + max = (mx > max) ? max : (int) Math.round(mx); + + double mn = current - offset; + min = (mn < min) ? min : (int) Math.round(mn); + } + + double rnd = Math.random() * (max - min) + min; + return (int) Math.round(rnd); + + } + + private void setAudioSequencer() { + InputStream audioSrc = AgentUtilOperations.class.getResourceAsStream( + "/" + AgentConstants.AUDIO_FILE_NAME); + Sequence sequence; + + try { + sequence = MidiSystem.getSequence(audioSrc); + sequencer = MidiSystem.getSequencer(); + sequencer.open(); + sequencer.setSequence(sequence); + } catch (InvalidMidiDataException e) { + log.error("AudioReader: Error whilst setting MIDI Audio reader sequence"); + } catch (IOException e) { + log.error("AudioReader: Error whilst getting audio sequence from stream"); + } catch (MidiUnavailableException e) { + log.error("AudioReader: Error whilst openning MIDI Audio reader sequencer"); + } + + sequencer.setLoopCount(Clip.LOOP_CONTINUOUSLY); + } + +} diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/advanced/virtual/ui/AgentUI.java b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/advanced/virtual/ui/AgentUI.java new file mode 100644 index 0000000000..96546e5b6f --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/advanced/virtual/ui/AgentUI.java @@ -0,0 +1,1085 @@ +/* + * 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.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.virtual.ui; + +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.core.AgentConstants; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.core.AgentManager; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.core.AgentUtilOperations; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.virtual.VirtualHardwareManager; + +import javax.swing.*; +import java.awt.*; +import java.net.URI; +import java.net.URL; +import java.util.ArrayList; + +public class AgentUI extends JFrame { + + private boolean isTemperatureRandomized, isHumidityRandomized; + private boolean isTemperatureSmoothed, isHumiditySmoothed; + + private volatile boolean isAlarmOn = false; + + private final Object _lock = new Object(); + + private JLabel picLabelBulbOn, picLabelBulbOff; + + private volatile java.util.List policyLogs = new ArrayList<>(); + + // Variables declaration - do not modify + private JButton btnControl; + private JButton btnView; + private JCheckBox chkbxEmulate; + private JCheckBox chkbxHumidityRandom; + private JCheckBox chkbxHumiditySmooth; + private JCheckBox chkbxTemperatureRandom; + private JCheckBox chkbxTemperatureSmooth; + private JComboBox cmbInterface; + private JComboBox cmbPeriod; + private JComboBox cmbProtocol; + private JLabel jLabel1; + private JLabel jLabel10; + private JLabel jLabel11; + private JLabel jLabel12; + private JLabel jLabel2; + private JLabel jLabel20; + private JLabel jLabel23; + private JLabel jLabel24; + private JLabel jLabel25; + private JLabel jLabel3; + private JLabel jLabel4; + private JLabel jLabel5; + private JLabel jLabel6; + private JLabel jLabel7; + private JLabel jLabel8; + private JLabel jLabel9; + private JPanel jPanel1; + private JPanel jPanel2; + private JPanel jPanel3; + private JPanel jPanel4; + private JPanel jPanel6; + private JPanel jPanel7; + private JPanel jPanel8; + private JPanel jPanel9; + private JScrollPane jScrollPane1; + private JSeparator jSeparator1; + private JSeparator jSeparator5; + private JLabel lblAgentName; + private JLabel lblStatus; + private JPanel pnlBulbStatus; + private JSpinner spinnerHumidity; + private JSpinner spinnerInterval; + private JSpinner spinnerTemperature; + private JTextArea txtAreaLogs; + private JTextField txtHumidityMax; + private JTextField txtHumidityMin; + private JTextField txtHumiditySVF; + private JTextField txtTemperatureMax; + private JTextField txtTemperatureMin; + private JTextField txtTemperatureSVF; + // End of variables declaration + + //Update UI from AgentManager changes + private Runnable uiUpdater = new Runnable() { + @Override + public void run() { + while (true) { + EventQueue.invokeLater(new Runnable() { + @Override + public void run() { + pnlBulbStatus.removeAll(); + pnlBulbStatus.add(isAlarmOn ? picLabelBulbOn : picLabelBulbOff); + pnlBulbStatus.updateUI(); + lblStatus.setText(AgentManager.getInstance().getAgentStatus()); + String policy = getPolicyLog(); + if (policy != null){ + txtAreaLogs.append("\n" + policy); + txtAreaLogs.append("\n--------------------------------------------------\n"); + } + if (isTemperatureRandomized) { + txtTemperatureMinActionPerformed(null); + txtTemperatureMaxActionPerformed(null); + if (isTemperatureSmoothed) { + txtTemperatureSVFActionPerformed(null); + } + } + if (isHumidityRandomized) { + txtHumidityMinActionPerformed(null); + txtHumidityMaxActionPerformed(null); + if (isHumiditySmoothed) { + txtHumiditySVFActionPerformed(null); + } + } + } + }); + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + break; + } + } + } + }; + + + /** + * Creates new form AgentUI + */ + public AgentUI() { + initComponents(); + } + + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + private void initComponents() { + + lblAgentName = new JLabel(); + jLabel2 = new JLabel(); + jPanel1 = new JPanel(); + jLabel3 = new JLabel(); + pnlBulbStatus = new JPanel(); + jPanel2 = new JPanel(); + jLabel4 = new JLabel(); + chkbxTemperatureRandom = new JCheckBox(); + jSeparator1 = new JSeparator(); + jPanel7 = new JPanel(); + jLabel5 = new JLabel(); + txtTemperatureMin = new JTextField(); + jLabel6 = new JLabel(); + txtTemperatureMax = new JTextField(); + jLabel10 = new JLabel(); + txtTemperatureSVF = new JTextField(); + spinnerTemperature = new JSpinner(); + chkbxTemperatureSmooth = new JCheckBox(); + jPanel6 = new JPanel(); + jLabel20 = new JLabel(); + btnView = new JButton(); + btnControl = new JButton(); + lblStatus = new JLabel(); + jPanel8 = new JPanel(); + jLabel23 = new JLabel(); + chkbxHumidityRandom = new JCheckBox(); + jSeparator5 = new JSeparator(); + jPanel9 = new JPanel(); + jLabel24 = new JLabel(); + txtHumidityMin = new JTextField(); + jLabel25 = new JLabel(); + txtHumidityMax = new JTextField(); + txtHumiditySVF = new JTextField(); + jLabel11 = new JLabel(); + spinnerHumidity = new JSpinner(); + chkbxHumiditySmooth = new JCheckBox(); + jPanel3 = new JPanel(); + jLabel7 = new JLabel(); + spinnerInterval = new JSpinner(); + jLabel8 = new JLabel(); + jLabel9 = new JLabel(); + cmbProtocol = new JComboBox(); + jLabel12 = new JLabel(); + cmbInterface = new JComboBox(); + jScrollPane1 = new JScrollPane(); + txtAreaLogs = new JTextArea(); + jPanel4 = new JPanel(); + chkbxEmulate = new JCheckBox(); + cmbPeriod = new JComboBox(); + jLabel1 = new JLabel(); + + setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); + setTitle("Fire Alarm Emulator"); + setResizable(false); + Dimension dim = Toolkit.getDefaultToolkit().getScreenSize(); + setLocation(dim.width / 2 - 650 / 2, dim.height / 2 - 440 / 2); + + lblAgentName.setFont(new Font("Cantarell", 1, 24)); // NOI18N + lblAgentName.setHorizontalAlignment(SwingConstants.LEFT); + lblAgentName.setText("Device Name: " + AgentManager.getInstance().getDeviceName()); + + jLabel2.setHorizontalAlignment(SwingConstants.CENTER); + jLabel2.setText("Copyright (c) 2015, WSO2 Inc."); + + jPanel1.setBackground(new Color(220, 220, 220)); + + jLabel3.setFont(new Font("Cantarell", 0, 18)); // NOI18N + jLabel3.setHorizontalAlignment(SwingConstants.CENTER); + jLabel3.setText("Alarm Status"); + + pnlBulbStatus.setBackground(new Color(220, 220, 220)); + + GroupLayout pnlBulbStatusLayout = new GroupLayout(pnlBulbStatus); + pnlBulbStatus.setLayout(pnlBulbStatusLayout); + pnlBulbStatusLayout.setHorizontalGroup( + pnlBulbStatusLayout.createParallelGroup(GroupLayout.Alignment.LEADING) + .addGap(0, 0, Short.MAX_VALUE) + ); + pnlBulbStatusLayout.setVerticalGroup( + pnlBulbStatusLayout.createParallelGroup(GroupLayout.Alignment.LEADING) + .addGap(0, 167, Short.MAX_VALUE) + ); + + GroupLayout jPanel1Layout = new GroupLayout(jPanel1); + jPanel1.setLayout(jPanel1Layout); + jPanel1Layout.setHorizontalGroup( + jPanel1Layout.createParallelGroup(GroupLayout.Alignment.LEADING) + .addGroup(GroupLayout.Alignment.TRAILING, jPanel1Layout.createSequentialGroup() + .addContainerGap() + .addGroup(jPanel1Layout.createParallelGroup( + GroupLayout.Alignment.TRAILING) + .addComponent(pnlBulbStatus, + GroupLayout + .DEFAULT_SIZE, + GroupLayout.DEFAULT_SIZE, + Short.MAX_VALUE) + .addComponent(jLabel3, + GroupLayout.DEFAULT_SIZE, + 190, Short.MAX_VALUE)) + .addContainerGap()) + ); + jPanel1Layout.setVerticalGroup( + jPanel1Layout.createParallelGroup(GroupLayout.Alignment.LEADING) + .addGroup(jPanel1Layout.createSequentialGroup() + .addContainerGap() + .addComponent(jLabel3) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addComponent(pnlBulbStatus, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addContainerGap()) + ); + + jPanel2.setBackground(new Color(220, 220, 220)); + + jLabel4.setFont(new Font("Cantarell", 0, 18)); // NOI18N + jLabel4.setHorizontalAlignment(SwingConstants.CENTER); + jLabel4.setText("Temperature"); + + chkbxTemperatureRandom.setText("Randomize Data"); + chkbxTemperatureRandom.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + chkbxTemperatureRandomActionPerformed(evt); + } + }); + + jSeparator1.setOrientation(SwingConstants.VERTICAL); + + jPanel7.setBackground(new Color(220, 220, 220)); + + jLabel5.setHorizontalAlignment(SwingConstants.LEFT); + jLabel5.setText("Min"); + + txtTemperatureMin.setHorizontalAlignment(JTextField.CENTER); + txtTemperatureMin.setText("20"); + txtTemperatureMin.setEnabled(false); + txtTemperatureMin.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + txtTemperatureMinActionPerformed(evt); + } + }); + + jLabel6.setHorizontalAlignment(SwingConstants.RIGHT); + jLabel6.setText("Max"); + + txtTemperatureMax.setHorizontalAlignment(JTextField.CENTER); + txtTemperatureMax.setText("50"); + txtTemperatureMax.setEnabled(false); + txtTemperatureMax.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + txtTemperatureMaxActionPerformed(evt); + } + }); + + jLabel10.setHorizontalAlignment(SwingConstants.RIGHT); + jLabel10.setText("SV %"); + + txtTemperatureSVF.setHorizontalAlignment(JTextField.CENTER); + txtTemperatureSVF.setText("50"); + txtTemperatureSVF.setEnabled(false); + txtTemperatureSVF.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + txtTemperatureSVFActionPerformed(evt); + } + }); + + GroupLayout jPanel7Layout = new GroupLayout(jPanel7); + jPanel7.setLayout(jPanel7Layout); + jPanel7Layout.setHorizontalGroup( + jPanel7Layout.createParallelGroup(GroupLayout.Alignment.LEADING) + .addGroup(jPanel7Layout.createSequentialGroup() + .addComponent(jLabel5) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addComponent(txtTemperatureMin, GroupLayout.PREFERRED_SIZE, 45, GroupLayout.PREFERRED_SIZE) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jLabel6) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addComponent(txtTemperatureMax, GroupLayout.PREFERRED_SIZE, 45, GroupLayout.PREFERRED_SIZE) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jLabel10) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addComponent(txtTemperatureSVF, GroupLayout.PREFERRED_SIZE, 45, GroupLayout.PREFERRED_SIZE) + .addContainerGap(GroupLayout.DEFAULT_SIZE, + Short.MAX_VALUE)) + ); + jPanel7Layout.setVerticalGroup( + jPanel7Layout.createParallelGroup(GroupLayout.Alignment.LEADING) + .addGroup(GroupLayout.Alignment.TRAILING, jPanel7Layout + .createSequentialGroup() + .addContainerGap(GroupLayout.DEFAULT_SIZE, + Short.MAX_VALUE) + .addGroup(jPanel7Layout.createParallelGroup( + GroupLayout.Alignment.BASELINE) + .addComponent(txtTemperatureMin, + GroupLayout + .PREFERRED_SIZE, + GroupLayout + .DEFAULT_SIZE, + GroupLayout.PREFERRED_SIZE) + .addComponent(txtTemperatureMax, + GroupLayout.PREFERRED_SIZE, + GroupLayout.DEFAULT_SIZE, + GroupLayout.PREFERRED_SIZE) + .addComponent(jLabel6) + .addComponent(jLabel5) + .addComponent(jLabel10) + .addComponent(txtTemperatureSVF, + GroupLayout.PREFERRED_SIZE, + GroupLayout.DEFAULT_SIZE, + GroupLayout.PREFERRED_SIZE)) + .addGap(35, 35, 35)) + ); + + spinnerTemperature.setFont(new Font("Cantarell", 1, 24)); // NOI18N + spinnerTemperature.setModel(new SpinnerNumberModel(30, 0, 100, 1)); + spinnerTemperature.addChangeListener(new javax.swing.event.ChangeListener() { + public void stateChanged(javax.swing.event.ChangeEvent evt) { + spinnerTemperatureStateChanged(evt); + } + }); + + chkbxTemperatureSmooth.setText("Smooth Variation"); + chkbxTemperatureSmooth.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + chkbxTemperatureSmoothActionPerformed(evt); + } + }); + + GroupLayout jPanel2Layout = new GroupLayout(jPanel2); + jPanel2.setLayout(jPanel2Layout); + jPanel2Layout.setHorizontalGroup( + jPanel2Layout.createParallelGroup(GroupLayout.Alignment.LEADING) + .addGroup(jPanel2Layout.createSequentialGroup() + .addContainerGap() + .addGroup(jPanel2Layout.createParallelGroup(GroupLayout.Alignment.LEADING, false) + .addComponent(jLabel4, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(spinnerTemperature)) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(jSeparator1, GroupLayout.PREFERRED_SIZE, 6, GroupLayout.PREFERRED_SIZE) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addGroup(jPanel2Layout.createParallelGroup(GroupLayout.Alignment.LEADING) + .addComponent(jPanel7, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) + .addGroup(jPanel2Layout.createSequentialGroup() + .addComponent(chkbxTemperatureRandom) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(chkbxTemperatureSmooth))) + .addContainerGap()) + ); + jPanel2Layout.setVerticalGroup( + jPanel2Layout.createParallelGroup(GroupLayout.Alignment.LEADING) + .addGroup(jPanel2Layout.createSequentialGroup() + .addContainerGap() + .addGroup(jPanel2Layout.createParallelGroup(GroupLayout.Alignment.LEADING) + .addComponent(jSeparator1) + .addGroup(jPanel2Layout.createSequentialGroup() + .addGroup(jPanel2Layout.createParallelGroup( + GroupLayout.Alignment.BASELINE) + .addComponent( + chkbxTemperatureRandom) + .addComponent( + chkbxTemperatureSmooth)) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jPanel7, GroupLayout.PREFERRED_SIZE, 51, GroupLayout.PREFERRED_SIZE) + .addGap(0, 0, Short.MAX_VALUE)) + .addGroup(jPanel2Layout.createSequentialGroup() + .addComponent(jLabel4, GroupLayout.PREFERRED_SIZE, 23, GroupLayout.PREFERRED_SIZE) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addComponent(spinnerTemperature))) + .addContainerGap()) + ); + + jPanel6.setBackground(new Color(253, 254, 209)); + + jLabel20.setText("Connection Status:"); + jLabel20.setVerticalTextPosition(SwingConstants.TOP); + + btnView.setText("View Device Data"); + btnView.addMouseListener(new java.awt.event.MouseAdapter() { + public void mouseClicked(java.awt.event.MouseEvent evt) { + btnViewMouseClicked(evt); + } + }); + + btnControl.setText("Control Device"); + btnControl.addMouseListener(new java.awt.event.MouseAdapter() { + public void mouseClicked(java.awt.event.MouseEvent evt) { + btnControlMouseClicked(evt); + } + }); + + lblStatus.setFont(new Font("Cantarell", 1, 15)); // NOI18N + lblStatus.setText("Not Connected"); + + GroupLayout jPanel6Layout = new GroupLayout(jPanel6); + jPanel6.setLayout(jPanel6Layout); + jPanel6Layout.setHorizontalGroup( + jPanel6Layout.createParallelGroup(GroupLayout.Alignment.LEADING) + .addGroup(jPanel6Layout.createSequentialGroup() + .addContainerGap() + .addComponent(jLabel20) + .addPreferredGap(LayoutStyle + .ComponentPlacement.RELATED) + .addComponent(lblStatus, GroupLayout + .DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addComponent(btnControl) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addComponent(btnView) + .addContainerGap()) + ); + jPanel6Layout.setVerticalGroup( + jPanel6Layout.createParallelGroup(GroupLayout.Alignment.LEADING) + .addGroup(GroupLayout.Alignment.TRAILING, jPanel6Layout.createSequentialGroup() + .addContainerGap(GroupLayout.DEFAULT_SIZE, + Short.MAX_VALUE) + .addGroup(jPanel6Layout.createParallelGroup( + GroupLayout.Alignment.LEADING, false) + .addComponent(jLabel20, + GroupLayout.DEFAULT_SIZE, + GroupLayout.DEFAULT_SIZE, + Short.MAX_VALUE) + .addGroup(jPanel6Layout.createParallelGroup( + GroupLayout.Alignment.BASELINE) + .addComponent(btnView, + GroupLayout.DEFAULT_SIZE, + GroupLayout.DEFAULT_SIZE, + Short.MAX_VALUE) + .addComponent(btnControl) + .addComponent(lblStatus))) + .addContainerGap()) + ); + + jPanel8.setBackground(new Color(220, 220, 220)); + + jLabel23.setFont(new Font("Cantarell", 0, 18)); // NOI18N + jLabel23.setHorizontalAlignment(SwingConstants.CENTER); + jLabel23.setText("Humidity"); + + chkbxHumidityRandom.setText("Randomize Data"); + chkbxHumidityRandom.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + chkbxHumidityRandomActionPerformed(evt); + } + }); + + jSeparator5.setOrientation(SwingConstants.VERTICAL); + + jPanel9.setBackground(new Color(220, 220, 220)); + + jLabel24.setHorizontalAlignment(SwingConstants.LEFT); + jLabel24.setText("Min"); + + txtHumidityMin.setHorizontalAlignment(JTextField.CENTER); + txtHumidityMin.setText("20"); + txtHumidityMin.setEnabled(false); + txtHumidityMin.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + txtHumidityMinActionPerformed(evt); + } + }); + + jLabel25.setHorizontalAlignment(SwingConstants.RIGHT); + jLabel25.setText("Max"); + + txtHumidityMax.setHorizontalAlignment(JTextField.CENTER); + txtHumidityMax.setText("50"); + txtHumidityMax.setEnabled(false); + txtHumidityMax.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + txtHumidityMaxActionPerformed(evt); + } + }); + + txtHumiditySVF.setHorizontalAlignment(JTextField.CENTER); + txtHumiditySVF.setText("50"); + txtHumiditySVF.setEnabled(false); + txtHumiditySVF.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + txtHumiditySVFActionPerformed(evt); + } + }); + + jLabel11.setHorizontalAlignment(SwingConstants.RIGHT); + jLabel11.setText("SV %"); + + GroupLayout jPanel9Layout = new GroupLayout(jPanel9); + jPanel9.setLayout(jPanel9Layout); + jPanel9Layout.setHorizontalGroup( + jPanel9Layout.createParallelGroup(GroupLayout.Alignment.LEADING) + .addGroup(jPanel9Layout.createSequentialGroup() + .addComponent(jLabel24) + .addPreferredGap(LayoutStyle + .ComponentPlacement.RELATED) + .addComponent(txtHumidityMin, GroupLayout + .PREFERRED_SIZE, 45, GroupLayout + .PREFERRED_SIZE) + .addPreferredGap(LayoutStyle + .ComponentPlacement.RELATED) + .addComponent(jLabel25) + .addPreferredGap(LayoutStyle + .ComponentPlacement.RELATED) + .addComponent(txtHumidityMax, GroupLayout.PREFERRED_SIZE, 45, GroupLayout.PREFERRED_SIZE) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jLabel11) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addComponent(txtHumiditySVF, GroupLayout.PREFERRED_SIZE, 45, GroupLayout.PREFERRED_SIZE) + .addContainerGap(GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + ); + jPanel9Layout.setVerticalGroup( + jPanel9Layout.createParallelGroup(GroupLayout.Alignment.LEADING) + .addGroup(GroupLayout.Alignment.TRAILING, jPanel9Layout.createSequentialGroup() + .addContainerGap(GroupLayout.DEFAULT_SIZE, + Short.MAX_VALUE) + .addGroup(jPanel9Layout.createParallelGroup( + GroupLayout.Alignment.LEADING) + .addGroup(jPanel9Layout.createParallelGroup( + GroupLayout.Alignment.BASELINE) + .addComponent(jLabel11) + .addComponent(txtHumiditySVF, + GroupLayout.PREFERRED_SIZE, + GroupLayout.DEFAULT_SIZE, + GroupLayout.PREFERRED_SIZE)) + .addGroup(jPanel9Layout.createParallelGroup( + GroupLayout.Alignment.BASELINE) + .addComponent(txtHumidityMin, + GroupLayout.PREFERRED_SIZE, + GroupLayout.DEFAULT_SIZE, + GroupLayout.PREFERRED_SIZE) + .addComponent(txtHumidityMax, + GroupLayout.PREFERRED_SIZE, + GroupLayout.DEFAULT_SIZE, + GroupLayout.PREFERRED_SIZE) + .addComponent(jLabel25) + .addComponent(jLabel24))) + .addGap(35, 35, 35)) + ); + + spinnerHumidity.setFont(new Font("Cantarell", 1, 24)); // NOI18N + spinnerHumidity.setModel(new SpinnerNumberModel(30, 0, 100, 1)); + spinnerHumidity.addChangeListener(new javax.swing.event.ChangeListener() { + public void stateChanged(javax.swing.event.ChangeEvent evt) { + spinnerHumidityStateChanged(evt); + } + }); + + chkbxHumiditySmooth.setText("Smooth Variation"); + chkbxHumiditySmooth.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + chkbxHumiditySmoothActionPerformed(evt); + } + }); + + GroupLayout jPanel8Layout = new GroupLayout(jPanel8); + jPanel8.setLayout(jPanel8Layout); + jPanel8Layout.setHorizontalGroup( + jPanel8Layout.createParallelGroup(GroupLayout.Alignment.LEADING) + .addGroup(jPanel8Layout.createSequentialGroup() + .addContainerGap() + .addGroup(jPanel8Layout.createParallelGroup(GroupLayout.Alignment.LEADING) + .addComponent(jLabel23, GroupLayout.PREFERRED_SIZE, 100, GroupLayout.PREFERRED_SIZE) + .addComponent(spinnerHumidity)) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(jSeparator5, GroupLayout.PREFERRED_SIZE, 6, GroupLayout.PREFERRED_SIZE) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addGroup(jPanel8Layout.createParallelGroup(GroupLayout.Alignment.LEADING, false) + .addComponent(jPanel9, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) + .addGroup(jPanel8Layout.createSequentialGroup() + .addComponent(chkbxHumidityRandom) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(chkbxHumiditySmooth))) + .addContainerGap()) + ); + jPanel8Layout.setVerticalGroup( + jPanel8Layout.createParallelGroup(GroupLayout.Alignment.LEADING) + .addGroup(jPanel8Layout.createSequentialGroup() + .addContainerGap() + .addGroup(jPanel8Layout.createParallelGroup(GroupLayout.Alignment.LEADING) + .addComponent(jSeparator5) + .addGroup(jPanel8Layout.createSequentialGroup() + .addGroup(jPanel8Layout.createParallelGroup( + GroupLayout.Alignment.BASELINE) + .addComponent( + chkbxHumidityRandom) + .addComponent( + chkbxHumiditySmooth)) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jPanel9, GroupLayout.PREFERRED_SIZE, 51, GroupLayout.PREFERRED_SIZE) + .addGap(0, 1, Short.MAX_VALUE)) + .addGroup(jPanel8Layout.createSequentialGroup() + .addComponent(jLabel23, GroupLayout.PREFERRED_SIZE, 23, GroupLayout.PREFERRED_SIZE) + .addPreferredGap + (LayoutStyle.ComponentPlacement.RELATED) + .addComponent + (spinnerHumidity))) + .addContainerGap()) + ); + + jPanel3.setBackground(new Color(207, 233, 234)); + + jLabel7.setText("Data Push Interval:"); + + spinnerInterval.setModel(new SpinnerNumberModel(Integer.valueOf(5), Integer + .valueOf(1), null, Integer.valueOf(1))); + spinnerInterval.addChangeListener(new javax.swing.event.ChangeListener() { + public void stateChanged(javax.swing.event.ChangeEvent evt) { + spinnerIntervalStateChanged(evt); + } + }); + + jLabel8.setText("Seconds"); + + jLabel9.setText("Protocol:"); + + cmbProtocol.setModel(new DefaultComboBoxModel(new String[] { "MQTT", "XMPP", + "HTTP" })); + cmbProtocol.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + cmbProtocolActionPerformed(evt); + } + }); + + jLabel12.setText("Interface:"); + + cmbInterface.setModel(new DefaultComboBoxModel(new String[] { "eth0" })); + cmbInterface.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + cmbInterfaceActionPerformed(evt); + } + }); + + GroupLayout jPanel3Layout = new GroupLayout(jPanel3); + jPanel3.setLayout(jPanel3Layout); + jPanel3Layout.setHorizontalGroup( + jPanel3Layout.createParallelGroup(GroupLayout.Alignment.LEADING) + .addGroup(jPanel3Layout.createSequentialGroup() + .addContainerGap() + .addComponent(jLabel7) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addComponent(spinnerInterval, GroupLayout.PREFERRED_SIZE, 55, GroupLayout.PREFERRED_SIZE) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jLabel8) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(jLabel12) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addComponent(cmbInterface, GroupLayout.PREFERRED_SIZE, 100, GroupLayout.PREFERRED_SIZE) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jLabel9) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addComponent(cmbProtocol, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) + .addContainerGap()) + ); + jPanel3Layout.setVerticalGroup( + jPanel3Layout.createParallelGroup(GroupLayout.Alignment.LEADING) + .addGroup(GroupLayout.Alignment.TRAILING, jPanel3Layout + .createSequentialGroup() + .addContainerGap(GroupLayout.DEFAULT_SIZE, + Short.MAX_VALUE) + .addGroup(jPanel3Layout.createParallelGroup( + GroupLayout.Alignment.LEADING) + .addGroup(jPanel3Layout.createParallelGroup( + GroupLayout.Alignment.BASELINE) + .addComponent(jLabel12) + .addComponent(cmbInterface, + GroupLayout.PREFERRED_SIZE, + GroupLayout.DEFAULT_SIZE, + GroupLayout.PREFERRED_SIZE)) + .addGroup(jPanel3Layout.createParallelGroup( + GroupLayout.Alignment.BASELINE) + .addComponent(jLabel7) + .addComponent(spinnerInterval, + GroupLayout.PREFERRED_SIZE, + GroupLayout.DEFAULT_SIZE, + GroupLayout.PREFERRED_SIZE) + .addComponent(jLabel8) + .addComponent(jLabel9) + .addComponent(cmbProtocol, + GroupLayout.PREFERRED_SIZE, + GroupLayout.DEFAULT_SIZE, + GroupLayout.PREFERRED_SIZE))) + .addContainerGap()) + ); + + txtAreaLogs.setBackground(new Color(1, 1, 1)); + txtAreaLogs.setColumns(20); + txtAreaLogs.setFont(new Font("Courier 10 Pitch", Font.BOLD, 9)); // NOI18N + txtAreaLogs.setForeground(new Color(0, 255, 0)); + txtAreaLogs.setRows(5); + jScrollPane1.setViewportView(txtAreaLogs); + + jPanel4.setBackground(new Color(169, 253, 173)); + + chkbxEmulate.setText("Emulate data"); + chkbxEmulate.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + chkbxEmulateActionPerformed(evt); + } + }); + + cmbPeriod.setModel(new DefaultComboBoxModel(new String[] { "1 hour", "1 day", "1 week", "1 month " })); + cmbPeriod.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + cmbPeriodActionPerformed(evt); + } + }); + + jLabel1.setText("Emulation Period"); + + GroupLayout jPanel4Layout = new GroupLayout(jPanel4); + jPanel4.setLayout(jPanel4Layout); + jPanel4Layout.setHorizontalGroup( + jPanel4Layout.createParallelGroup(GroupLayout.Alignment.LEADING) + .addGroup(jPanel4Layout.createSequentialGroup() + .addContainerGap() + .addComponent(chkbxEmulate) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(jLabel1) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addComponent(cmbPeriod, GroupLayout.PREFERRED_SIZE, 162, GroupLayout.PREFERRED_SIZE) + .addContainerGap()) + ); + jPanel4Layout.setVerticalGroup( + jPanel4Layout.createParallelGroup(GroupLayout.Alignment.LEADING) + .addGroup(GroupLayout.Alignment.TRAILING, jPanel4Layout.createSequentialGroup() + .addContainerGap(GroupLayout.DEFAULT_SIZE, + Short.MAX_VALUE) + .addGroup(jPanel4Layout.createParallelGroup( + GroupLayout.Alignment.BASELINE) + .addComponent(chkbxEmulate) + .addComponent(cmbPeriod, + GroupLayout.PREFERRED_SIZE, + GroupLayout.DEFAULT_SIZE, + GroupLayout.PREFERRED_SIZE) + .addComponent(jLabel1)) + .addContainerGap()) + ); + + GroupLayout layout = new GroupLayout(getContentPane()); + getContentPane().setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING) + .addComponent(jScrollPane1) + .addComponent(lblAgentName, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(jPanel6, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGroup(layout.createSequentialGroup() + .addComponent(jPanel1, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup( + GroupLayout.Alignment.LEADING) + .addComponent( + jPanel8, + GroupLayout.DEFAULT_SIZE, + GroupLayout.DEFAULT_SIZE, + Short.MAX_VALUE) + .addComponent( + jPanel2, + GroupLayout.DEFAULT_SIZE, + GroupLayout.DEFAULT_SIZE, + Short.MAX_VALUE))) + .addComponent(jLabel2, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(jPanel4, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(jPanel3, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addContainerGap()) + ); + layout.setVerticalGroup( + layout.createParallelGroup(GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addComponent(lblAgentName, GroupLayout.PREFERRED_SIZE, 53, GroupLayout.PREFERRED_SIZE) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jPanel6, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addComponent(jPanel2, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jPanel8, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)) + .addComponent(jPanel1, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jScrollPane1, GroupLayout.PREFERRED_SIZE, 115, GroupLayout.PREFERRED_SIZE) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jPanel3, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jPanel4, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jLabel2, GroupLayout.PREFERRED_SIZE, 28, GroupLayout.PREFERRED_SIZE) + .addContainerGap()) + ); + + pack(); + + chkbxTemperatureSmooth.setEnabled(false); + chkbxTemperatureSmooth.setEnabled(false); + + cmbInterface.removeAllItems(); + for (String item : AgentManager.getInstance().getInterfaceList()){ + cmbInterface.addItem(item); + } + cmbInterface.setEnabled(false); + + cmbProtocol.removeAllItems(); + for (String item : AgentManager.getInstance().getProtocolList()){ + cmbProtocol.addItem(item); + } + cmbProtocol.setSelectedItem(AgentConstants.DEFAULT_PROTOCOL); + + URL urlAlarmOn = this.getClass().getResource("/alarm-on.gif"); + ImageIcon imageIconAlarmOn = new ImageIcon(urlAlarmOn); + + URL urlAlarmOff = this.getClass().getResource("/alarm-off.gif"); + ImageIcon imageIconAlarmOff = new ImageIcon(urlAlarmOff); + + picLabelBulbOn = new JLabel(imageIconAlarmOn); + picLabelBulbOn.setSize(pnlBulbStatus.getSize()); + + picLabelBulbOff = new JLabel(imageIconAlarmOff); + picLabelBulbOff.setSize(pnlBulbStatus.getSize()); + + addToPolicyLog(AgentUtilOperations.formatMessage(AgentManager.getInstance().getInitialPolicy())); + new Thread(uiUpdater).start(); + + AgentManager.getInstance().setDeviceReady(true); + } + + private void btnControlMouseClicked(java.awt.event.MouseEvent evt) { + Desktop desktop = Desktop.isDesktopSupported() ? Desktop.getDesktop() : null; + if (desktop != null && desktop.isSupported(Desktop.Action.BROWSE)) { + try { + URI uri = new URI(AgentManager.getInstance().getDeviceMgtControlUrl()); + desktop.browse(uri); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + private void btnViewMouseClicked(java.awt.event.MouseEvent evt) { + Desktop desktop = Desktop.isDesktopSupported() ? Desktop.getDesktop() : null; + if (desktop != null && desktop.isSupported(Desktop.Action.BROWSE)) { + try { + URI uri = new URI(AgentManager.getInstance().getDeviceMgtAnalyticUrl()); + desktop.browse(uri); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + private void chkbxTemperatureRandomActionPerformed(java.awt.event.ActionEvent evt) { + isTemperatureRandomized = chkbxTemperatureRandom.isSelected(); + VirtualHardwareManager.getInstance().setIsTemperatureRandomized(isTemperatureRandomized); + spinnerTemperature.setEnabled(!isTemperatureRandomized); + txtTemperatureMax.setEnabled(isTemperatureRandomized); + txtTemperatureMin.setEnabled(isTemperatureRandomized); + chkbxTemperatureSmooth.setEnabled(isTemperatureRandomized); + txtTemperatureSVF.setEnabled(isTemperatureRandomized && isTemperatureSmoothed); + } + + private void chkbxHumidityRandomActionPerformed(java.awt.event.ActionEvent evt) { + isHumidityRandomized = chkbxHumidityRandom.isSelected(); + VirtualHardwareManager.getInstance().setIsHumidityRandomized(isHumidityRandomized); + spinnerHumidity.setEnabled(!isHumidityRandomized); + txtHumidityMax.setEnabled(isHumidityRandomized); + txtHumidityMin.setEnabled(isHumidityRandomized); + chkbxHumiditySmooth.setEnabled(isHumidityRandomized); + txtTemperatureSVF.setEnabled(isHumidityRandomized && isHumiditySmoothed); + } + + private void spinnerTemperatureStateChanged(javax.swing.event.ChangeEvent evt) { + if (!isTemperatureRandomized) { + try { + int temperature = Integer.parseInt(spinnerTemperature.getValue().toString()); + VirtualHardwareManager.getInstance().setTemperature(temperature); + } catch (NumberFormatException e) { + JOptionPane.showMessageDialog(this, "Invalid temperature value", "Error", JOptionPane.ERROR_MESSAGE); + spinnerTemperature.setValue(VirtualHardwareManager.getInstance().getTemperature()); + } + } + } + + private void spinnerHumidityStateChanged(javax.swing.event.ChangeEvent evt) { + if (!isHumidityRandomized) { + try { + int humidity = Integer.parseInt(spinnerHumidity.getValue().toString()); + VirtualHardwareManager.getInstance().setHumidity(humidity); + } catch (NumberFormatException e) { + JOptionPane.showMessageDialog(this, "Invalid humidity value", "Error", JOptionPane.ERROR_MESSAGE); + spinnerHumidity.setValue(VirtualHardwareManager.getInstance().getHumidity()); + } + } + } + + private void txtTemperatureMinActionPerformed(java.awt.event.ActionEvent evt) { + try { + int temperature = Integer.parseInt(txtTemperatureMin.getText()); + VirtualHardwareManager.getInstance().setTemperatureMin(temperature); + } catch (NumberFormatException e) { + JOptionPane.showMessageDialog(this, "Invalid temperature value", "Error", JOptionPane.ERROR_MESSAGE); + txtTemperatureMin.setText("20"); + } + } + + private void txtTemperatureMaxActionPerformed(java.awt.event.ActionEvent evt) { + try { + int temperature = Integer.parseInt(txtTemperatureMax.getText()); + VirtualHardwareManager.getInstance().setTemperatureMax(temperature); + } catch (NumberFormatException e) { + JOptionPane.showMessageDialog(this, "Invalid temperature value", "Error", JOptionPane.ERROR_MESSAGE); + txtTemperatureMax.setText("50"); + } + } + + private void txtHumidityMinActionPerformed(java.awt.event.ActionEvent evt) { + try { + int humidity = Integer.parseInt(txtHumidityMin.getText()); + VirtualHardwareManager.getInstance().setHumidityMin(humidity); + } catch (NumberFormatException e) { + JOptionPane.showMessageDialog(this, "Invalid humidity value", "Error", JOptionPane.ERROR_MESSAGE); + txtHumidityMin.setText("20"); + } + } + + private void txtHumidityMaxActionPerformed(java.awt.event.ActionEvent evt) { + try { + int humidity = Integer.parseInt(txtHumidityMax.getText()); + VirtualHardwareManager.getInstance().setHumidityMax(humidity); + } catch (NumberFormatException e) { + JOptionPane.showMessageDialog(this, "Invalid humidity value", "Error", JOptionPane.ERROR_MESSAGE); + txtHumidityMax.setText("50"); + } + } + + private void spinnerIntervalStateChanged(javax.swing.event.ChangeEvent evt) { + try { + int interval = Integer.parseInt(spinnerInterval.getValue().toString()); + AgentManager.getInstance().setPushInterval(interval); + } catch (NumberFormatException e) { + JOptionPane.showMessageDialog(this, "Invalid time interval value", "Error", JOptionPane.ERROR_MESSAGE); + spinnerInterval.setValue(5); + } + } + + private void cmbInterfaceActionPerformed(java.awt.event.ActionEvent evt) { + AgentManager.getInstance().setInterface(cmbInterface.getSelectedIndex()); + } + + private void cmbProtocolActionPerformed(java.awt.event.ActionEvent evt) { + if (cmbProtocol.getSelectedIndex() != -1 && cmbProtocol.getItemAt( + cmbProtocol.getSelectedIndex()).equals(AgentConstants.HTTP_PROTOCOL)) { + cmbInterface.setEnabled(true); + } else { + cmbInterface.setEnabled(false); + } + + AgentManager.getInstance().setProtocol(cmbProtocol.getSelectedIndex()); + + } + + private void txtTemperatureSVFActionPerformed(java.awt.event.ActionEvent evt) { + try { + int temperatureSVF = Integer.parseInt(txtTemperatureSVF.getText()); + VirtualHardwareManager.getInstance().setTemperatureSVF(temperatureSVF); + } catch (NumberFormatException e) { + JOptionPane.showMessageDialog(this, "Invalid value", "Error", JOptionPane.ERROR_MESSAGE); + txtTemperatureSVF.setText("50"); + } + } + + private void txtHumiditySVFActionPerformed(java.awt.event.ActionEvent evt) { + try { + int humiditySVF = Integer.parseInt(txtHumiditySVF.getText()); + VirtualHardwareManager.getInstance().setHumiditySVF(humiditySVF); + } catch (NumberFormatException e) { + JOptionPane.showMessageDialog(this, "Invalid value", "Error", JOptionPane.ERROR_MESSAGE); + txtHumiditySVF.setText("50"); + } + } + + private void chkbxTemperatureSmoothActionPerformed(java.awt.event.ActionEvent evt) { + isTemperatureSmoothed = chkbxTemperatureSmooth.isSelected(); + txtTemperatureSVF.setEnabled(isTemperatureSmoothed); + VirtualHardwareManager.getInstance().setIsTemperatureSmoothed(isTemperatureSmoothed); + } + + private void chkbxHumiditySmoothActionPerformed(java.awt.event.ActionEvent evt) { + isHumiditySmoothed = chkbxHumiditySmooth.isSelected(); + txtHumiditySVF.setEnabled(isHumiditySmoothed); + VirtualHardwareManager.getInstance().setIsHumiditySmoothed(isHumiditySmoothed); + } + + private void cmbPeriodActionPerformed(java.awt.event.ActionEvent evt) { + // TODO add your handling code here: + } + + private void chkbxEmulateActionPerformed(java.awt.event.ActionEvent evt) { + // TODO add your handling code here: + } + + public void setAlarmStatus(boolean isAlarmOn) { + this.isAlarmOn = isAlarmOn; + } + + public void updateTemperature(int temperature) { + spinnerTemperature.setValue(temperature); + spinnerTemperature.updateUI(); + } + + public void updateHumidity(int humidity) { + spinnerHumidity.setValue(humidity); + spinnerHumidity.updateUI(); + } + + public void addToPolicyLog(String policy) { + synchronized (this._lock) { + policyLogs.add(policy); + } + } + + private String getPolicyLog() { + synchronized (this._lock) { + if (policyLogs.size() > 0) { + String policy = policyLogs.get(0); + policyLogs.remove(0); + return policy; + } else { + return null; + } + } + } + +} diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.impl/src/main/resources/alarm-off.gif b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.impl/src/main/resources/alarm-off.gif new file mode 100644 index 0000000000..c346605ad0 Binary files /dev/null and b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.impl/src/main/resources/alarm-off.gif differ diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.impl/src/main/resources/alarm-on.gif b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.impl/src/main/resources/alarm-on.gif new file mode 100644 index 0000000000..d7c83f6aa8 Binary files /dev/null and b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.impl/src/main/resources/alarm-on.gif differ diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.impl/src/main/resources/cep_query.txt b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.impl/src/main/resources/cep_query.txt new file mode 100644 index 0000000000..17546ece2c --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.impl/src/main/resources/cep_query.txt @@ -0,0 +1,11 @@ +define stream fireAlarmEventStream (deviceID string, temp int); +from fireAlarmEventStream#window.time(7886776 sec) +select deviceID, max(temp) as maxValue +group by deviceID +insert into analyzeStream for expired-events; +from analyzeStream[maxValue < 88] +select maxValue +insert into bulbOnStream; +from fireAlarmEventStream[temp > 1093] +select deviceID, temp +insert into bulbOffStream; \ No newline at end of file diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.impl/src/main/resources/deviceConfig.properties b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.impl/src/main/resources/deviceConfig.properties new file mode 100644 index 0000000000..930742a2aa --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.impl/src/main/resources/deviceConfig.properties @@ -0,0 +1,33 @@ +# +# 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] +server-name=WSO2IoTServer +owner=shabirmean +deviceId=t4ctwq8qfl11 +device-name=SMEAN_t4ctwq8qfl11 +controller-context=/virtual_firealarm/controller +https-ep=https://localhost:9443 +http-ep=http://localhost:9763 +apim-ep=http://192.168.67.21:8281 +mqtt-ep=tcp://192.168.67.21:1883 +xmpp-ep=http://204.232.188.215:5222 +auth-method=token +auth-token=79d68b50ae5f5a06e812889979b3453 +refresh-token=8bdda6359dddad218cff3354d5a8cb3b +network-interface=en0 +push-interval=14 diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.impl/src/main/resources/fireAlarmSound.mid b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.impl/src/main/resources/fireAlarmSound.mid new file mode 100644 index 0000000000..d1a2241b2d Binary files /dev/null and b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.impl/src/main/resources/fireAlarmSound.mid differ diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.impl/src/main/ui/build.xml b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.impl/src/main/ui/build.xml new file mode 100644 index 0000000000..bc3a404447 --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.impl/src/main/ui/build.xml @@ -0,0 +1,73 @@ + + + + + + + + + + + Builds, tests, and runs the project VirtualAgentUI. + + + diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.impl/src/main/ui/manifest.mf b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.impl/src/main/ui/manifest.mf new file mode 100644 index 0000000000..328e8e5bc3 --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.impl/src/main/ui/manifest.mf @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +X-COMMENT: Main-Class will be added automatically by build + diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.impl/src/main/ui/nbproject/build-impl.xml b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.impl/src/main/ui/nbproject/build-impl.xml new file mode 100644 index 0000000000..1124542077 --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.impl/src/main/ui/nbproject/build-impl.xml @@ -0,0 +1,1413 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must set src.dir + Must set test.src.dir + Must set build.dir + Must set dist.dir + Must set build.classes.dir + Must set dist.javadoc.dir + Must set build.test.classes.dir + Must set build.test.results.dir + Must set build.classes.excludes + Must set dist.jar + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must set javac.includes + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + No tests executed. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must set JVM to use for profiling in profiler.info.jvm + Must set profiler agent JVM arguments in profiler.info.jvmargs.agent + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must select some files in the IDE or set javac.includes + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + To run this application from the command line without Ant, try: + + java -jar "${dist.jar.resolved}" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must select one file in the IDE or set run.class + + + + Must select one file in the IDE or set run.class + + + + + + + + + + + + + + + + + + + + + + + Must select one file in the IDE or set debug.class + + + + + Must select one file in the IDE or set debug.class + + + + + Must set fix.includes + + + + + + + + + + This target only works when run from inside the NetBeans IDE. + + + + + + + + + Must select one file in the IDE or set profile.class + This target only works when run from inside the NetBeans IDE. + + + + + + + + + This target only works when run from inside the NetBeans IDE. + + + + + + + + + + + + + This target only works when run from inside the NetBeans IDE. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must select one file in the IDE or set run.class + + + + + + Must select some files in the IDE or set test.includes + + + + + Must select one file in the IDE or set run.class + + + + + Must select one file in the IDE or set applet.url + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must select some files in the IDE or set javac.includes + + + + + + + + + + + + + + + + + + + + Some tests failed; see details above. + + + + + + + + + Must select some files in the IDE or set test.includes + + + + Some tests failed; see details above. + + + + Must select some files in the IDE or set test.class + Must select some method in the IDE or set test.method + + + + Some tests failed; see details above. + + + + + Must select one file in the IDE or set test.class + + + + Must select one file in the IDE or set test.class + Must select some method in the IDE or set test.method + + + + + + + + + + + + + + Must select one file in the IDE or set applet.url + + + + + + + + + Must select one file in the IDE or set applet.url + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.impl/src/main/ui/nbproject/genfiles.properties b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.impl/src/main/ui/nbproject/genfiles.properties new file mode 100644 index 0000000000..a6df38fd89 --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.impl/src/main/ui/nbproject/genfiles.properties @@ -0,0 +1,8 @@ +build.xml.data.CRC32=e60df945 +build.xml.script.CRC32=7c331eea +build.xml.stylesheet.CRC32=8064a381@1.75.2.48 +# This file is used by a NetBeans-based IDE to track changes in generated files such as build-impl.xml. +# Do not edit this file. You may delete it but then the IDE will never regenerate such files for you. +nbproject/build-impl.xml.data.CRC32=e60df945 +nbproject/build-impl.xml.script.CRC32=4fa004f7 +nbproject/build-impl.xml.stylesheet.CRC32=876e7a8f@1.75.2.48 diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.impl/src/main/ui/nbproject/private/private.properties b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.impl/src/main/ui/nbproject/private/private.properties new file mode 100644 index 0000000000..e59ac1df68 --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.impl/src/main/ui/nbproject/private/private.properties @@ -0,0 +1,2 @@ +compile.on.save=true +user.properties.file=/home/charitha/.netbeans/8.0.2/build.properties diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.impl/src/main/ui/nbproject/private/private.xml b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.impl/src/main/ui/nbproject/private/private.xml new file mode 100644 index 0000000000..97b7e92bd1 --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.impl/src/main/ui/nbproject/private/private.xml @@ -0,0 +1,9 @@ + + + + + + file:/home/charitha/NetBeansProjects/VirtualAgentUI/src/org/wso2/carbon/device/mgt/iot/agent/virtual/ui/AgentUI.java + + + diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.impl/src/main/ui/nbproject/project.properties b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.impl/src/main/ui/nbproject/project.properties new file mode 100644 index 0000000000..fb798f6142 --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.impl/src/main/ui/nbproject/project.properties @@ -0,0 +1,73 @@ +annotation.processing.enabled=true +annotation.processing.enabled.in.editor=false +annotation.processing.processor.options= +annotation.processing.processors.list= +annotation.processing.run.all.processors=true +annotation.processing.source.output=${build.generated.sources.dir}/ap-source-output +build.classes.dir=${build.dir}/classes +build.classes.excludes=**/*.java,**/*.form +# This directory is removed when the project is cleaned: +build.dir=build +build.generated.dir=${build.dir}/generated +build.generated.sources.dir=${build.dir}/generated-sources +# Only compile against the classpath explicitly listed here: +build.sysclasspath=ignore +build.test.classes.dir=${build.dir}/test/classes +build.test.results.dir=${build.dir}/test/results +# Uncomment to specify the preferred debugger connection transport: +#debug.transport=dt_socket +debug.classpath=\ + ${run.classpath} +debug.test.classpath=\ + ${run.test.classpath} +# Files in build.classes.dir which should be excluded from distribution jar +dist.archive.excludes= +# This directory is removed when the project is cleaned: +dist.dir=dist +dist.jar=${dist.dir}/VirtualAgentUI.jar +dist.javadoc.dir=${dist.dir}/javadoc +excludes= +includes=** +jar.compress=false +javac.classpath= +# Space-separated list of extra javac options +javac.compilerargs= +javac.deprecation=false +javac.processorpath=\ + ${javac.classpath} +javac.source=1.8 +javac.target=1.8 +javac.test.classpath=\ + ${javac.classpath}:\ + ${build.classes.dir} +javac.test.processorpath=\ + ${javac.test.classpath} +javadoc.additionalparam= +javadoc.author=false +javadoc.encoding=${source.encoding} +javadoc.noindex=false +javadoc.nonavbar=false +javadoc.notree=false +javadoc.private=false +javadoc.splitindex=true +javadoc.use=true +javadoc.version=false +javadoc.windowtitle= +main.class=org.wso2.carbon.device.mgt.iot.agent.virtual.VirtualAgentUI +manifest.file=manifest.mf +meta.inf.dir=${src.dir}/META-INF +mkdist.disabled=false +platform.active=default_platform +run.classpath=\ + ${javac.classpath}:\ + ${build.classes.dir} +# Space-separated list of JVM arguments used when running the project. +# You may also define separate properties like run-sys-prop.name=value instead of -Dname=value. +# To set system properties for unit tests define test-sys-prop.name=value: +run.jvmargs= +run.test.classpath=\ + ${javac.test.classpath}:\ + ${build.test.classes.dir} +source.encoding=UTF-8 +src.dir=src +test.src.dir=test diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.impl/src/main/ui/nbproject/project.xml b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.impl/src/main/ui/nbproject/project.xml new file mode 100644 index 0000000000..438f1e6a38 --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.impl/src/main/ui/nbproject/project.xml @@ -0,0 +1,15 @@ + + + org.netbeans.modules.java.j2seproject + + + VirtualAgentUI + + + + + + + + + diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.impl/src/main/ui/src/bulb-on.jpg b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.impl/src/main/ui/src/bulb-on.jpg new file mode 100644 index 0000000000..51d40cd834 Binary files /dev/null and b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.impl/src/main/ui/src/bulb-on.jpg differ diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.impl/src/main/ui/src/org/wso2/carbon/device/mgt/iot/agent/virtual/VirtualAgentUI.java b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.impl/src/main/ui/src/org/wso2/carbon/device/mgt/iot/agent/virtual/VirtualAgentUI.java new file mode 100644 index 0000000000..a2aa2116a5 --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.impl/src/main/ui/src/org/wso2/carbon/device/mgt/iot/agent/virtual/VirtualAgentUI.java @@ -0,0 +1,37 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package org.wso2.carbon.device.mgt.iot.agent.virtual; + +import javax.swing.UIManager; +import javax.swing.UnsupportedLookAndFeelException; +import org.wso2.carbon.device.mgt.iot.agent.virtual.ui.AgentUI; + +/** + * + * @author charitha + */ +public class VirtualAgentUI { + + /** + * @param args the command line arguments + */ + public static void main(String[] args) { + try { + // Set System L&F + UIManager.setLookAndFeel( + UIManager.getSystemLookAndFeelClassName()); + } catch (UnsupportedLookAndFeelException | ClassNotFoundException | InstantiationException | IllegalAccessException e) { + // handle exception + } + + java.awt.EventQueue.invokeLater(new Runnable() { + public void run() { + new AgentUI().setVisible(true); + } + }); + } + +} diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.impl/src/main/ui/src/org/wso2/carbon/device/mgt/iot/agent/virtual/ui/AgentUI.form b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.impl/src/main/ui/src/org/wso2/carbon/device/mgt/iot/agent/virtual/ui/AgentUI.form new file mode 100644 index 0000000000..0778bef15e --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.impl/src/main/ui/src/org/wso2/carbon/device/mgt/iot/agent/virtual/ui/AgentUI.form @@ -0,0 +1,830 @@ + + +

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.impl/src/main/ui/src/org/wso2/carbon/device/mgt/iot/agent/virtual/ui/AgentUI.java b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.impl/src/main/ui/src/org/wso2/carbon/device/mgt/iot/agent/virtual/ui/AgentUI.java new file mode 100644 index 0000000000..27a1774924 --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.impl/src/main/ui/src/org/wso2/carbon/device/mgt/iot/agent/virtual/ui/AgentUI.java @@ -0,0 +1,758 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package org.wso2.carbon.device.mgt.iot.agent.virtual.ui; + +/** + * + * @author charitha + */ +public class AgentUI extends javax.swing.JFrame { + + /** + * Creates new form AgentUI + */ + public AgentUI() { + initComponents(); + } + + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + + lblAgentName = new javax.swing.JLabel(); + jLabel2 = new javax.swing.JLabel(); + jPanel1 = new javax.swing.JPanel(); + jLabel3 = new javax.swing.JLabel(); + pnlBulbStatus = new javax.swing.JPanel(); + jPanel2 = new javax.swing.JPanel(); + jLabel4 = new javax.swing.JLabel(); + chkbxTemperatureRandom = new javax.swing.JCheckBox(); + jSeparator1 = new javax.swing.JSeparator(); + jPanel7 = new javax.swing.JPanel(); + jLabel5 = new javax.swing.JLabel(); + txtTemperatureMin = new javax.swing.JTextField(); + jLabel6 = new javax.swing.JLabel(); + txtTemperatureMax = new javax.swing.JTextField(); + jLabel10 = new javax.swing.JLabel(); + txtTemperatureSVF = new javax.swing.JTextField(); + spinnerTemperature = new javax.swing.JSpinner(); + chkbxTemperatureSmooth = new javax.swing.JCheckBox(); + jPanel6 = new javax.swing.JPanel(); + jLabel20 = new javax.swing.JLabel(); + btnView = new javax.swing.JButton(); + btnControl = new javax.swing.JButton(); + lblStatus = new javax.swing.JLabel(); + jPanel8 = new javax.swing.JPanel(); + jLabel23 = new javax.swing.JLabel(); + chkbxHumidityRandom = new javax.swing.JCheckBox(); + jSeparator5 = new javax.swing.JSeparator(); + jPanel9 = new javax.swing.JPanel(); + jLabel24 = new javax.swing.JLabel(); + txtHumidityMin = new javax.swing.JTextField(); + jLabel25 = new javax.swing.JLabel(); + txtHumidityMax = new javax.swing.JTextField(); + txtHumiditySVF = new javax.swing.JTextField(); + jLabel11 = new javax.swing.JLabel(); + spinnerHumidity = new javax.swing.JSpinner(); + chkbxHumiditySmooth = new javax.swing.JCheckBox(); + jPanel3 = new javax.swing.JPanel(); + jLabel7 = new javax.swing.JLabel(); + spinnerInterval = new javax.swing.JSpinner(); + jLabel8 = new javax.swing.JLabel(); + jLabel9 = new javax.swing.JLabel(); + cmbProtocol = new javax.swing.JComboBox(); + jLabel12 = new javax.swing.JLabel(); + cmbInterface = new javax.swing.JComboBox(); + jScrollPane1 = new javax.swing.JScrollPane(); + txtAreaLogs = new javax.swing.JTextArea(); + jPanel4 = new javax.swing.JPanel(); + chkbxEmulate = new javax.swing.JCheckBox(); + cmbPeriod = new javax.swing.JComboBox(); + jLabel1 = new javax.swing.JLabel(); + + setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE); + setTitle("Fire Alarm Emulator"); + setResizable(false); + + lblAgentName.setFont(new java.awt.Font("Cantarell", 1, 24)); // NOI18N + lblAgentName.setHorizontalAlignment(javax.swing.SwingConstants.LEFT); + lblAgentName.setText("Device Name: WSO2 IoT Virtual Agent"); + + jLabel2.setHorizontalAlignment(javax.swing.SwingConstants.CENTER); + jLabel2.setText("Copyright (c) 2015, WSO2 Inc."); + + jPanel1.setBackground(new java.awt.Color(220, 220, 220)); + + jLabel3.setFont(new java.awt.Font("Cantarell", 0, 18)); // NOI18N + jLabel3.setHorizontalAlignment(javax.swing.SwingConstants.CENTER); + jLabel3.setText("Bulb Status"); + + pnlBulbStatus.setBackground(new java.awt.Color(220, 220, 220)); + + javax.swing.GroupLayout pnlBulbStatusLayout = new javax.swing.GroupLayout(pnlBulbStatus); + pnlBulbStatus.setLayout(pnlBulbStatusLayout); + pnlBulbStatusLayout.setHorizontalGroup( + pnlBulbStatusLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGap(0, 0, Short.MAX_VALUE) + ); + pnlBulbStatusLayout.setVerticalGroup( + pnlBulbStatusLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGap(0, 167, Short.MAX_VALUE) + ); + + javax.swing.GroupLayout jPanel1Layout = new javax.swing.GroupLayout(jPanel1); + jPanel1.setLayout(jPanel1Layout); + jPanel1Layout.setHorizontalGroup( + jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, jPanel1Layout.createSequentialGroup() + .addContainerGap() + .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) + .addComponent(pnlBulbStatus, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(jLabel3, javax.swing.GroupLayout.DEFAULT_SIZE, 190, Short.MAX_VALUE)) + .addContainerGap()) + ); + jPanel1Layout.setVerticalGroup( + jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel1Layout.createSequentialGroup() + .addContainerGap() + .addComponent(jLabel3) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(pnlBulbStatus, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addContainerGap()) + ); + + jPanel2.setBackground(new java.awt.Color(220, 220, 220)); + + jLabel4.setFont(new java.awt.Font("Cantarell", 0, 18)); // NOI18N + jLabel4.setHorizontalAlignment(javax.swing.SwingConstants.CENTER); + jLabel4.setText("Temperature"); + + chkbxTemperatureRandom.setText("Randomize Data"); + chkbxTemperatureRandom.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + chkbxTemperatureRandomActionPerformed(evt); + } + }); + + jSeparator1.setOrientation(javax.swing.SwingConstants.VERTICAL); + + jPanel7.setBackground(new java.awt.Color(220, 220, 220)); + + jLabel5.setHorizontalAlignment(javax.swing.SwingConstants.LEFT); + jLabel5.setText("Min"); + + txtTemperatureMin.setHorizontalAlignment(javax.swing.JTextField.CENTER); + txtTemperatureMin.setText("20"); + txtTemperatureMin.setEnabled(false); + txtTemperatureMin.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + txtTemperatureMinActionPerformed(evt); + } + }); + + jLabel6.setHorizontalAlignment(javax.swing.SwingConstants.RIGHT); + jLabel6.setText("Max"); + + txtTemperatureMax.setHorizontalAlignment(javax.swing.JTextField.CENTER); + txtTemperatureMax.setText("50"); + txtTemperatureMax.setEnabled(false); + txtTemperatureMax.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + txtTemperatureMaxActionPerformed(evt); + } + }); + + jLabel10.setHorizontalAlignment(javax.swing.SwingConstants.RIGHT); + jLabel10.setText("SV %"); + + txtTemperatureSVF.setHorizontalAlignment(javax.swing.JTextField.CENTER); + txtTemperatureSVF.setText("50"); + txtTemperatureSVF.setEnabled(false); + txtTemperatureSVF.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + txtTemperatureSVFActionPerformed(evt); + } + }); + + javax.swing.GroupLayout jPanel7Layout = new javax.swing.GroupLayout(jPanel7); + jPanel7.setLayout(jPanel7Layout); + jPanel7Layout.setHorizontalGroup( + jPanel7Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel7Layout.createSequentialGroup() + .addComponent(jLabel5) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(txtTemperatureMin, javax.swing.GroupLayout.PREFERRED_SIZE, 45, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jLabel6) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(txtTemperatureMax, javax.swing.GroupLayout.PREFERRED_SIZE, 45, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jLabel10) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(txtTemperatureSVF, javax.swing.GroupLayout.PREFERRED_SIZE, 45, javax.swing.GroupLayout.PREFERRED_SIZE) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + ); + jPanel7Layout.setVerticalGroup( + jPanel7Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, jPanel7Layout.createSequentialGroup() + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGroup(jPanel7Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(txtTemperatureMin, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(txtTemperatureMax, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(jLabel6) + .addComponent(jLabel5) + .addComponent(jLabel10) + .addComponent(txtTemperatureSVF, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addGap(35, 35, 35)) + ); + + spinnerTemperature.setFont(new java.awt.Font("Cantarell", 1, 24)); // NOI18N + spinnerTemperature.setModel(new javax.swing.SpinnerNumberModel(30, 0, 100, 1)); + spinnerTemperature.addChangeListener(new javax.swing.event.ChangeListener() { + public void stateChanged(javax.swing.event.ChangeEvent evt) { + spinnerTemperatureStateChanged(evt); + } + }); + + chkbxTemperatureSmooth.setText("Smooth Variation"); + chkbxTemperatureSmooth.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + chkbxTemperatureSmoothActionPerformed(evt); + } + }); + + javax.swing.GroupLayout jPanel2Layout = new javax.swing.GroupLayout(jPanel2); + jPanel2.setLayout(jPanel2Layout); + jPanel2Layout.setHorizontalGroup( + jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel2Layout.createSequentialGroup() + .addContainerGap() + .addGroup(jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) + .addComponent(jLabel4, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(spinnerTemperature)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(jSeparator1, javax.swing.GroupLayout.PREFERRED_SIZE, 6, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(jPanel7, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGroup(jPanel2Layout.createSequentialGroup() + .addComponent(chkbxTemperatureRandom) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(chkbxTemperatureSmooth))) + .addContainerGap()) + ); + jPanel2Layout.setVerticalGroup( + jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel2Layout.createSequentialGroup() + .addContainerGap() + .addGroup(jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(jSeparator1) + .addGroup(jPanel2Layout.createSequentialGroup() + .addGroup(jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(chkbxTemperatureRandom) + .addComponent(chkbxTemperatureSmooth)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jPanel7, javax.swing.GroupLayout.PREFERRED_SIZE, 51, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(0, 0, Short.MAX_VALUE)) + .addGroup(jPanel2Layout.createSequentialGroup() + .addComponent(jLabel4, javax.swing.GroupLayout.PREFERRED_SIZE, 23, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(spinnerTemperature))) + .addContainerGap()) + ); + + jPanel6.setBackground(new java.awt.Color(253, 254, 209)); + + jLabel20.setText("Connection Status:"); + jLabel20.setVerticalTextPosition(javax.swing.SwingConstants.TOP); + + btnView.setText("View Device Data"); + btnView.addMouseListener(new java.awt.event.MouseAdapter() { + public void mouseClicked(java.awt.event.MouseEvent evt) { + btnViewMouseClicked(evt); + } + }); + + btnControl.setText("Control Device"); + btnControl.addMouseListener(new java.awt.event.MouseAdapter() { + public void mouseClicked(java.awt.event.MouseEvent evt) { + btnControlMouseClicked(evt); + } + }); + + lblStatus.setFont(new java.awt.Font("Cantarell", 1, 15)); // NOI18N + lblStatus.setText("Not Connected"); + + javax.swing.GroupLayout jPanel6Layout = new javax.swing.GroupLayout(jPanel6); + jPanel6.setLayout(jPanel6Layout); + jPanel6Layout.setHorizontalGroup( + jPanel6Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel6Layout.createSequentialGroup() + .addContainerGap() + .addComponent(jLabel20) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(lblStatus, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(btnControl) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(btnView) + .addContainerGap()) + ); + jPanel6Layout.setVerticalGroup( + jPanel6Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, jPanel6Layout.createSequentialGroup() + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGroup(jPanel6Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) + .addComponent(jLabel20, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGroup(jPanel6Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(btnView, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(btnControl) + .addComponent(lblStatus))) + .addContainerGap()) + ); + + jPanel8.setBackground(new java.awt.Color(220, 220, 220)); + + jLabel23.setFont(new java.awt.Font("Cantarell", 0, 18)); // NOI18N + jLabel23.setHorizontalAlignment(javax.swing.SwingConstants.CENTER); + jLabel23.setText("Humidity"); + + chkbxHumidityRandom.setText("Randomize Data"); + chkbxHumidityRandom.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + chkbxHumidityRandomActionPerformed(evt); + } + }); + + jSeparator5.setOrientation(javax.swing.SwingConstants.VERTICAL); + + jPanel9.setBackground(new java.awt.Color(220, 220, 220)); + + jLabel24.setHorizontalAlignment(javax.swing.SwingConstants.LEFT); + jLabel24.setText("Min"); + + txtHumidityMin.setHorizontalAlignment(javax.swing.JTextField.CENTER); + txtHumidityMin.setText("20"); + txtHumidityMin.setEnabled(false); + txtHumidityMin.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + txtHumidityMinActionPerformed(evt); + } + }); + + jLabel25.setHorizontalAlignment(javax.swing.SwingConstants.RIGHT); + jLabel25.setText("Max"); + + txtHumidityMax.setHorizontalAlignment(javax.swing.JTextField.CENTER); + txtHumidityMax.setText("50"); + txtHumidityMax.setEnabled(false); + txtHumidityMax.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + txtHumidityMaxActionPerformed(evt); + } + }); + + txtHumiditySVF.setHorizontalAlignment(javax.swing.JTextField.CENTER); + txtHumiditySVF.setText("50"); + txtHumiditySVF.setEnabled(false); + txtHumiditySVF.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + txtHumiditySVFActionPerformed(evt); + } + }); + + jLabel11.setHorizontalAlignment(javax.swing.SwingConstants.RIGHT); + jLabel11.setText("SV %"); + + javax.swing.GroupLayout jPanel9Layout = new javax.swing.GroupLayout(jPanel9); + jPanel9.setLayout(jPanel9Layout); + jPanel9Layout.setHorizontalGroup( + jPanel9Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel9Layout.createSequentialGroup() + .addComponent(jLabel24) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(txtHumidityMin, javax.swing.GroupLayout.PREFERRED_SIZE, 45, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jLabel25) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(txtHumidityMax, javax.swing.GroupLayout.PREFERRED_SIZE, 45, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jLabel11) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(txtHumiditySVF, javax.swing.GroupLayout.PREFERRED_SIZE, 45, javax.swing.GroupLayout.PREFERRED_SIZE) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + ); + jPanel9Layout.setVerticalGroup( + jPanel9Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, jPanel9Layout.createSequentialGroup() + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGroup(jPanel9Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel9Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(jLabel11) + .addComponent(txtHumiditySVF, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addGroup(jPanel9Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(txtHumidityMin, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(txtHumidityMax, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(jLabel25) + .addComponent(jLabel24))) + .addGap(35, 35, 35)) + ); + + spinnerHumidity.setFont(new java.awt.Font("Cantarell", 1, 24)); // NOI18N + spinnerHumidity.setModel(new javax.swing.SpinnerNumberModel(30, 0, 100, 1)); + spinnerHumidity.addChangeListener(new javax.swing.event.ChangeListener() { + public void stateChanged(javax.swing.event.ChangeEvent evt) { + spinnerHumidityStateChanged(evt); + } + }); + + chkbxHumiditySmooth.setText("Smooth Variation"); + chkbxHumiditySmooth.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + chkbxHumiditySmoothActionPerformed(evt); + } + }); + + javax.swing.GroupLayout jPanel8Layout = new javax.swing.GroupLayout(jPanel8); + jPanel8.setLayout(jPanel8Layout); + jPanel8Layout.setHorizontalGroup( + jPanel8Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel8Layout.createSequentialGroup() + .addContainerGap() + .addGroup(jPanel8Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(jLabel23, javax.swing.GroupLayout.PREFERRED_SIZE, 100, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(spinnerHumidity)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(jSeparator5, javax.swing.GroupLayout.PREFERRED_SIZE, 6, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(jPanel8Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) + .addComponent(jPanel9, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGroup(jPanel8Layout.createSequentialGroup() + .addComponent(chkbxHumidityRandom) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(chkbxHumiditySmooth))) + .addContainerGap()) + ); + jPanel8Layout.setVerticalGroup( + jPanel8Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel8Layout.createSequentialGroup() + .addContainerGap() + .addGroup(jPanel8Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(jSeparator5) + .addGroup(jPanel8Layout.createSequentialGroup() + .addGroup(jPanel8Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(chkbxHumidityRandom) + .addComponent(chkbxHumiditySmooth)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jPanel9, javax.swing.GroupLayout.PREFERRED_SIZE, 51, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(0, 1, Short.MAX_VALUE)) + .addGroup(jPanel8Layout.createSequentialGroup() + .addComponent(jLabel23, javax.swing.GroupLayout.PREFERRED_SIZE, 23, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(spinnerHumidity))) + .addContainerGap()) + ); + + jPanel3.setBackground(new java.awt.Color(207, 233, 234)); + + jLabel7.setText("Data Push Interval:"); + + spinnerInterval.setModel(new javax.swing.SpinnerNumberModel(Integer.valueOf(5), Integer.valueOf(1), null, Integer.valueOf(1))); + spinnerInterval.addChangeListener(new javax.swing.event.ChangeListener() { + public void stateChanged(javax.swing.event.ChangeEvent evt) { + spinnerIntervalStateChanged(evt); + } + }); + + jLabel8.setText("Seconds"); + + jLabel9.setText("Protocol:"); + + cmbProtocol.setModel(new javax.swing.DefaultComboBoxModel(new String[] { "MQTT", "XMPP", "HTTP" })); + cmbProtocol.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + cmbProtocolActionPerformed(evt); + } + }); + + jLabel12.setText("Interface:"); + + cmbInterface.setModel(new javax.swing.DefaultComboBoxModel(new String[] { "eth0" })); + cmbInterface.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + cmbInterfaceActionPerformed(evt); + } + }); + + javax.swing.GroupLayout jPanel3Layout = new javax.swing.GroupLayout(jPanel3); + jPanel3.setLayout(jPanel3Layout); + jPanel3Layout.setHorizontalGroup( + jPanel3Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel3Layout.createSequentialGroup() + .addContainerGap() + .addComponent(jLabel7) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(spinnerInterval, javax.swing.GroupLayout.PREFERRED_SIZE, 55, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jLabel8) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(jLabel12) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(cmbInterface, javax.swing.GroupLayout.PREFERRED_SIZE, 100, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jLabel9) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(cmbProtocol, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addContainerGap()) + ); + jPanel3Layout.setVerticalGroup( + jPanel3Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, jPanel3Layout.createSequentialGroup() + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGroup(jPanel3Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel3Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(jLabel12) + .addComponent(cmbInterface, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addGroup(jPanel3Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(jLabel7) + .addComponent(spinnerInterval, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(jLabel8) + .addComponent(jLabel9) + .addComponent(cmbProtocol, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))) + .addContainerGap()) + ); + + txtAreaLogs.setBackground(new java.awt.Color(1, 1, 1)); + txtAreaLogs.setColumns(20); + txtAreaLogs.setFont(new java.awt.Font("Courier 10 Pitch", 1, 18)); // NOI18N + txtAreaLogs.setForeground(new java.awt.Color(0, 255, 0)); + txtAreaLogs.setRows(5); + jScrollPane1.setViewportView(txtAreaLogs); + + jPanel4.setBackground(new java.awt.Color(169, 253, 173)); + + chkbxEmulate.setText("Emulate data"); + chkbxEmulate.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + chkbxEmulateActionPerformed(evt); + } + }); + + cmbPeriod.setModel(new javax.swing.DefaultComboBoxModel(new String[] { "1 hour", "1 day", "1 week", "1 month " })); + cmbPeriod.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + cmbPeriodActionPerformed(evt); + } + }); + + jLabel1.setText("Emulation Period"); + + javax.swing.GroupLayout jPanel4Layout = new javax.swing.GroupLayout(jPanel4); + jPanel4.setLayout(jPanel4Layout); + jPanel4Layout.setHorizontalGroup( + jPanel4Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel4Layout.createSequentialGroup() + .addContainerGap() + .addComponent(chkbxEmulate) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(jLabel1) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(cmbPeriod, javax.swing.GroupLayout.PREFERRED_SIZE, 162, javax.swing.GroupLayout.PREFERRED_SIZE) + .addContainerGap()) + ); + jPanel4Layout.setVerticalGroup( + jPanel4Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, jPanel4Layout.createSequentialGroup() + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGroup(jPanel4Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(chkbxEmulate) + .addComponent(cmbPeriod, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(jLabel1)) + .addContainerGap()) + ); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane()); + getContentPane().setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(jScrollPane1) + .addComponent(lblAgentName, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(jPanel6, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGroup(layout.createSequentialGroup() + .addComponent(jPanel1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(jPanel8, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(jPanel2, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))) + .addComponent(jLabel2, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(jPanel4, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(jPanel3, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addContainerGap()) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addComponent(lblAgentName, javax.swing.GroupLayout.PREFERRED_SIZE, 53, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jPanel6, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addComponent(jPanel2, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jPanel8, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addComponent(jPanel1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 115, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jPanel3, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jPanel4, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jLabel2, javax.swing.GroupLayout.PREFERRED_SIZE, 28, javax.swing.GroupLayout.PREFERRED_SIZE) + .addContainerGap()) + ); + + pack(); + }// //GEN-END:initComponents + + private void btnControlMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_btnControlMouseClicked + // TODO add your handling code here: + }//GEN-LAST:event_btnControlMouseClicked + + private void btnViewMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_btnViewMouseClicked + // TODO add your handling code here: + }//GEN-LAST:event_btnViewMouseClicked + + private void chkbxTemperatureRandomActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_chkbxTemperatureRandomActionPerformed + // TODO add your handling code here: + }//GEN-LAST:event_chkbxTemperatureRandomActionPerformed + + private void chkbxHumidityRandomActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_chkbxHumidityRandomActionPerformed + // TODO add your handling code here: + }//GEN-LAST:event_chkbxHumidityRandomActionPerformed + + private void spinnerTemperatureStateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_spinnerTemperatureStateChanged + // TODO add your handling code here: + }//GEN-LAST:event_spinnerTemperatureStateChanged + + private void spinnerHumidityStateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_spinnerHumidityStateChanged + // TODO add your handling code here: + }//GEN-LAST:event_spinnerHumidityStateChanged + + private void txtTemperatureMinActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_txtTemperatureMinActionPerformed + // TODO add your handling code here: + }//GEN-LAST:event_txtTemperatureMinActionPerformed + + private void txtTemperatureMaxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_txtTemperatureMaxActionPerformed + // TODO add your handling code here: + }//GEN-LAST:event_txtTemperatureMaxActionPerformed + + private void txtHumidityMinActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_txtHumidityMinActionPerformed + // TODO add your handling code here: + }//GEN-LAST:event_txtHumidityMinActionPerformed + + private void txtHumidityMaxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_txtHumidityMaxActionPerformed + // TODO add your handling code here: + }//GEN-LAST:event_txtHumidityMaxActionPerformed + + private void spinnerIntervalStateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_spinnerIntervalStateChanged + // TODO add your handling code here: + }//GEN-LAST:event_spinnerIntervalStateChanged + + private void cmbInterfaceActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cmbInterfaceActionPerformed + // TODO add your handling code here: + }//GEN-LAST:event_cmbInterfaceActionPerformed + + private void cmbProtocolActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cmbProtocolActionPerformed + // TODO add your handling code here: + }//GEN-LAST:event_cmbProtocolActionPerformed + + private void txtTemperatureSVFActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_txtTemperatureSVFActionPerformed + // TODO add your handling code here: + }//GEN-LAST:event_txtTemperatureSVFActionPerformed + + private void txtHumiditySVFActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_txtHumiditySVFActionPerformed + // TODO add your handling code here: + }//GEN-LAST:event_txtHumiditySVFActionPerformed + + private void chkbxTemperatureSmoothActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_chkbxTemperatureSmoothActionPerformed + // TODO add your handling code here: + }//GEN-LAST:event_chkbxTemperatureSmoothActionPerformed + + private void chkbxHumiditySmoothActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_chkbxHumiditySmoothActionPerformed + // TODO add your handling code here: + }//GEN-LAST:event_chkbxHumiditySmoothActionPerformed + + private void cmbPeriodActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cmbPeriodActionPerformed + // TODO add your handling code here: + }//GEN-LAST:event_cmbPeriodActionPerformed + + private void chkbxEmulateActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_chkbxEmulateActionPerformed + // TODO add your handling code here: + }//GEN-LAST:event_chkbxEmulateActionPerformed + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JButton btnControl; + private javax.swing.JButton btnView; + private javax.swing.JCheckBox chkbxEmulate; + private javax.swing.JCheckBox chkbxHumidityRandom; + private javax.swing.JCheckBox chkbxHumiditySmooth; + private javax.swing.JCheckBox chkbxTemperatureRandom; + private javax.swing.JCheckBox chkbxTemperatureSmooth; + private javax.swing.JComboBox cmbInterface; + private javax.swing.JComboBox cmbPeriod; + private javax.swing.JComboBox cmbProtocol; + private javax.swing.JLabel jLabel1; + private javax.swing.JLabel jLabel10; + private javax.swing.JLabel jLabel11; + private javax.swing.JLabel jLabel12; + private javax.swing.JLabel jLabel2; + private javax.swing.JLabel jLabel20; + private javax.swing.JLabel jLabel23; + private javax.swing.JLabel jLabel24; + private javax.swing.JLabel jLabel25; + private javax.swing.JLabel jLabel3; + private javax.swing.JLabel jLabel4; + private javax.swing.JLabel jLabel5; + private javax.swing.JLabel jLabel6; + private javax.swing.JLabel jLabel7; + private javax.swing.JLabel jLabel8; + private javax.swing.JLabel jLabel9; + private javax.swing.JPanel jPanel1; + private javax.swing.JPanel jPanel2; + private javax.swing.JPanel jPanel3; + private javax.swing.JPanel jPanel4; + private javax.swing.JPanel jPanel6; + private javax.swing.JPanel jPanel7; + private javax.swing.JPanel jPanel8; + private javax.swing.JPanel jPanel9; + private javax.swing.JScrollPane jScrollPane1; + private javax.swing.JSeparator jSeparator1; + private javax.swing.JSeparator jSeparator5; + private javax.swing.JLabel lblAgentName; + private javax.swing.JLabel lblStatus; + private javax.swing.JPanel pnlBulbStatus; + private javax.swing.JSpinner spinnerHumidity; + private javax.swing.JSpinner spinnerInterval; + private javax.swing.JSpinner spinnerTemperature; + private javax.swing.JTextArea txtAreaLogs; + private javax.swing.JTextField txtHumidityMax; + private javax.swing.JTextField txtHumidityMin; + private javax.swing.JTextField txtHumiditySVF; + private javax.swing.JTextField txtTemperatureMax; + private javax.swing.JTextField txtTemperatureMin; + private javax.swing.JTextField txtTemperatureSVF; + // End of variables declaration//GEN-END:variables +} diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.impl/pom.xml b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.impl/pom.xml new file mode 100644 index 0000000000..4ec1070718 --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.impl/pom.xml @@ -0,0 +1,232 @@ + + + + + 4.0.0 + + + virtual-fire-alarm-plugin + org.wso2.carbon.devicemgt-plugins + 2.1.0-SNAPSHOT + ../pom.xml + + + org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.impl + WSO2 Carbon - IoT Server VirtualFireAlarm Agent + WSO2 Carbon - VirtualFireAlarm Device Agent Implementation + http://wso2.org + + + + + org.apache.maven.plugins + maven-compiler-plugin + + UTF-8 + ${wso2.maven.compiler.source} + ${wso2.maven.compiler.target} + + 2.3.2 + + + + org.apache.maven.plugins + maven-assembly-plugin + + + + + org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.Bootstrap + + + + + jar-with-dependencies + + wso2-firealarm-virtual-agent + false + + + + make-assembly + + package + + + single + + + + + + + + + + + + + log4j + log4j + ${log4j.version} + + + + + + org.eclipse.jetty + jetty-server + ${jetty.version} + + + + + org.eclipse.paho + org.eclipse.paho.client.mqttv3 + ${paho.mqtt.version} + + + + + org.igniterealtime.smack.wso2 + smack + ${smack.wso2.version} + + + org.igniterealtime.smack.wso2 + smackx + ${smackx.wso2.version} + + + + + org.bouncycastle.wso2 + bcprov-jdk15on + ${bcprov.wso2.version} + + + org.bouncycastle.wso2 + bcpkix-jdk15on + ${bcpkix.wso2.version} + + + + + com.google.code.jscep.wso2 + jscep + ${jscep.version} + + + + commons-codec.wso2 + commons-codec + + + + commons-lang + commons-lang + ${commons-lang.version} + + + + commons-logging + commons-logging + ${common-logging.version} + + + + commons-io + commons-io + ${commons.io} + + + + + org.slf4j + slf4j-api + ${slf4j.version} + + + org.slf4j + slf4j-simple + ${slf4j.version} + + + + org.json.wso2 + json + + + + + + + + wso2-nexus + WSO2 internal Repository + http://maven.wso2.org/nexus/content/groups/wso2-public/ + + true + daily + ignore + + + + wso2-maven2-repository + http://dist.wso2.org/maven2 + + + + + + + 1.7 + 1.7 + + + 8.1.3.v20120416 + + + 1.0.2 + + + 3.0.4.wso2v1 + 3.0.4.wso2v1 + + + 1.49.wso2v1 + 1.49.wso2v1 + + + 2.0.2.wso2v2 + + + 1.2.17 + 1.2 + 2.4 + 1.7 + 2.6 + + + 1.7.13 + + + diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/Bootstrap.java b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/Bootstrap.java new file mode 100644 index 0000000000..63d4b2595c --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/Bootstrap.java @@ -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. + */ + +package org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent; + +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.core.AgentManager; + +public class Bootstrap { + + /** + * @param args the command line arguments + */ + public static void main(String[] args) { + System.setProperty("org.apache.commons.logging.Log", "org.apache.commons.logging.impl.SimpleLog"); + System.setProperty("org.apache.commons.logging.simplelog.defaultlog", "info"); + System.setProperty("org.apache.commons.logging.simplelog.showdatetime", "true"); + System.setProperty("org.apache.commons.logging.simplelog.dateTimeFormat", "HH:mm:ss"); + AgentManager.getInstance().init(); + } + +} diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/communication/http/FireAlarmHTTPCommunicator.java b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/communication/http/FireAlarmHTTPCommunicator.java new file mode 100644 index 0000000000..0f920e0d09 --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/communication/http/FireAlarmHTTPCommunicator.java @@ -0,0 +1,495 @@ +/* + * 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.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.communication.http; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.handler.AbstractHandler; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.transport.TransportHandlerException; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.transport.TransportUtils; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.transport.http.HTTPTransportHandler; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.core.AgentConstants; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.core.AgentManager; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.exception.AgentCoreOperationException; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.DataOutputStream; +import java.io.File; +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.Inet4Address; +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.net.ProtocolException; +import java.net.SocketException; +import java.net.UnknownHostException; +import java.util.Enumeration; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +public class FireAlarmHTTPCommunicator extends HTTPTransportHandler { + private static final Log log = LogFactory.getLog(FireAlarmHTTPCommunicator.class); + + private ScheduledExecutorService service = Executors.newScheduledThreadPool(2); + private ScheduledFuture dataPushServiceHandler; + private ScheduledFuture connectorServiceHandler; + + public FireAlarmHTTPCommunicator() { + super(); + } + + public FireAlarmHTTPCommunicator(int port) { + super(port); + } + + public FireAlarmHTTPCommunicator(int port, int reconnectionInterval) { + super(port, reconnectionInterval); + } + + public ScheduledFuture getDataPushServiceHandler() { + return dataPushServiceHandler; + } + + public void connect() { + Runnable connect = new Runnable() { + public void run() { + if (!isConnected()) { + try { + processIncomingMessage(); + server.start(); + registerThisDevice(); + publishDeviceData(); + log.info("HTTP Server started at port: " + port); + + } catch (Exception e) { + if (log.isDebugEnabled()) { + log.warn("Unable to 'START' HTTP server. Will retry after " + + timeoutInterval / 1000 + " seconds."); + } + } + } + } + }; + + connectorServiceHandler = service.scheduleAtFixedRate(connect, 0, timeoutInterval, + TimeUnit.MILLISECONDS); + } + + + @Override + public void processIncomingMessage() { + server.setHandler(new AbstractHandler() { + public void handle(String s, Request request, HttpServletRequest + httpServletRequest, + HttpServletResponse httpServletResponse) + throws IOException, ServletException { + httpServletResponse.setContentType("text/html;charset=utf-8"); + httpServletResponse.setStatus(HttpServletResponse.SC_OK); + request.setHandled(true); + + AgentManager agentManager = AgentManager.getInstance(); + String pathContext = request.getPathInfo(); + String separator = File.separator; + + if (pathContext.toUpperCase().contains( + separator + AgentConstants.TEMPERATURE_CONTROL)) { + httpServletResponse.getWriter().println( + agentManager.getTemperature()); + + } else if (pathContext.toUpperCase().contains( + separator + AgentConstants.HUMIDITY_CONTROL)) { + httpServletResponse.getWriter().println( + agentManager.getHumidity()); + + } else if (pathContext.toUpperCase().contains( + separator + AgentConstants.BULB_CONTROL)) { + String[] pathVariables = pathContext.split(separator); + + if (pathVariables.length != 3) { + httpServletResponse.getWriter().println( + "Invalid BULB-control received by the device. Need to be in " + + "'{host}:{port}/BULB/{ON|OFF}' format."); + return; + } + + String switchState = pathVariables[2]; + + if (switchState == null) { + httpServletResponse.getWriter().println( + "Please specify switch-status of the BULB."); + } else { + boolean status = switchState.toUpperCase().equals( + AgentConstants.CONTROL_ON); + agentManager.changeAlarmStatus(status); + httpServletResponse.getWriter().println("Bulb is " + (status ? + AgentConstants.CONTROL_ON : AgentConstants.CONTROL_OFF)); + } + } else { + httpServletResponse.getWriter().println( + "Invalid control command received by the device."); + } + } + }); + } + + @Override + public void publishDeviceData() { + final AgentManager agentManager = AgentManager.getInstance(); + int publishInterval = agentManager.getPushInterval(); + final String deviceOwner = agentManager.getAgentConfigs().getDeviceOwner(); + final String deviceID = agentManager.getAgentConfigs().getDeviceId(); + boolean simulationMode = false; + int duration = 2 * 60; + int frequency = 5; + + Runnable pushDataRunnable = new Runnable() { + @Override + public void run() { + + String pushDataPayload = String.format(AgentConstants.PUSH_DATA_PAYLOAD, deviceOwner, + deviceID, (agentManager.getDeviceIP() + ":" + port), + agentManager.getTemperature()); + executeDataPush(pushDataPayload); + } + }; + + if (!simulationMode) { + dataPushServiceHandler = service.scheduleAtFixedRate(pushDataRunnable, publishInterval, + publishInterval, + TimeUnit.SECONDS); + } else { + String pushDataPayload = String.format(AgentConstants.PUSH_SIMULATION_DATA_PAYLOAD, deviceOwner, + deviceID, (agentManager.getDeviceIP() + ":" + port), + agentManager.getTemperature(), true, duration, frequency); + executeDataPush(pushDataPayload); + + } + } + + + private void executeDataPush(String pushDataPayload) { + AgentManager agentManager = AgentManager.getInstance(); + int responseCode = -1; + String pushDataEndPointURL = agentManager.getPushDataAPIEP(); + HttpURLConnection httpConnection = null; + + try { + httpConnection = TransportUtils.getHttpConnection(agentManager.getPushDataAPIEP()); + httpConnection.setRequestMethod(AgentConstants.HTTP_POST); + httpConnection.setRequestProperty("Authorization", "Bearer " + + agentManager.getAgentConfigs().getAuthToken()); + httpConnection.setRequestProperty("Content-Type", + AgentConstants.APPLICATION_JSON_TYPE); + + httpConnection.setDoOutput(true); + DataOutputStream dataOutPutWriter = new DataOutputStream( + httpConnection.getOutputStream()); + dataOutPutWriter.writeBytes(pushDataPayload); + dataOutPutWriter.flush(); + dataOutPutWriter.close(); + + responseCode = httpConnection.getResponseCode(); + httpConnection.disconnect(); + + log.info(AgentConstants.LOG_APPENDER + "Message - '" + pushDataPayload + + "' was published to server at: " + httpConnection.getURL()); + + } catch (ProtocolException exception) { + String errorMsg = + "Protocol specific error occurred when trying to set method to " + + AgentConstants.HTTP_POST + " for:" + pushDataEndPointURL; + log.error(AgentConstants.LOG_APPENDER + errorMsg); + + } catch (IOException exception) { + String errorMsg = + "An IO error occurred whilst trying to get the response code from: " + + pushDataEndPointURL + " for a " + AgentConstants.HTTP_POST + + " " + "method."; + log.error(AgentConstants.LOG_APPENDER + errorMsg); + + } catch (TransportHandlerException exception) { + log.error(AgentConstants.LOG_APPENDER + + "Error encountered whilst trying to create HTTP-Connection " + + "to IoT-Server EP at: " + + pushDataEndPointURL); + } + + if (responseCode == HttpStatus.CONFLICT_409 || + responseCode == HttpStatus.PRECONDITION_FAILED_412) { + log.warn(AgentConstants.LOG_APPENDER + + "DeviceIP is being Re-Registered due to Push-Data failure " + + "with response code: " + + responseCode); + registerThisDevice(); + + } else if (responseCode != HttpStatus.NO_CONTENT_204) { + if (log.isDebugEnabled()) { + log.error(AgentConstants.LOG_APPENDER + "Status Code: " + responseCode + + " encountered whilst trying to Push-Device-Data to IoT " + + "Server at: " + + agentManager.getPushDataAPIEP()); + } + agentManager.updateAgentStatus(AgentConstants.SERVER_NOT_RESPONDING); + } + + if (log.isDebugEnabled()) { + log.debug(AgentConstants.LOG_APPENDER + "Push-Data call with payload - " + + pushDataPayload + ", to IoT Server returned status " + + responseCode); + } + } + + @Override + public void disconnect() { + Runnable stopConnection = new Runnable() { + public void run() { + while (isConnected()) { + try { + dataPushServiceHandler.cancel(true); + connectorServiceHandler.cancel(true); + closeConnection(); + } catch (Exception e) { + if (log.isDebugEnabled()) { + log.warn(AgentConstants.LOG_APPENDER + + "Unable to 'STOP' HTTP server at port: " + port); + } + + try { + Thread.sleep(timeoutInterval); + } catch (InterruptedException e1) { + log.error(AgentConstants.LOG_APPENDER + + "HTTP-Termination: Thread Sleep Interrupt " + + "Exception"); + } + } + } + } + }; + + Thread terminatorThread = new Thread(stopConnection); + terminatorThread.setDaemon(true); + terminatorThread.start(); + } + + @Override + public void processIncomingMessage(Object message, String... messageParams) { + + } + + @Override + public void publishDeviceData(String... publishData) { + + } + + public void registerThisDevice() { + final AgentManager agentManager = AgentManager.getInstance(); + agentManager.updateAgentStatus("Registering..."); + + final Runnable ipRegistration = new Runnable() { + @Override + public void run() { + while (isConnected()) { + try { + int responseCode = registerDeviceIP( + agentManager.getAgentConfigs().getDeviceOwner(), + agentManager.getAgentConfigs().getDeviceId()); + + if (responseCode == HttpStatus.OK_200) { + agentManager.updateAgentStatus(AgentConstants.REGISTERED); + break; + } else { + log.error(AgentConstants.LOG_APPENDER + + "Device Registration with IoT Server at:" + " " + + agentManager.getIpRegistrationEP() + + " failed with response - '" + responseCode + ":" + + HttpStatus.getMessage(responseCode) + "'"); + agentManager.updateAgentStatus(AgentConstants.RETRYING_TO_REGISTER); + } + } catch (AgentCoreOperationException exception) { + log.error(AgentConstants.LOG_APPENDER + + "Error encountered whilst trying to register the " + + "Device's IP at: " + + agentManager.getIpRegistrationEP() + + ".\nCheck whether the network-interface provided is " + + "accurate"); + agentManager.updateAgentStatus(AgentConstants.REGISTRATION_FAILED); + } + + try { + Thread.sleep(timeoutInterval); + } catch (InterruptedException e1) { + log.error(AgentConstants.LOG_APPENDER + + "Device Registration: Thread Sleep Interrupt Exception"); + } + } + } + }; + + Thread ipRegisterThread = new Thread(ipRegistration); + ipRegisterThread.setDaemon(true); + ipRegisterThread.start(); + } + + + /** + * This method calls the "Register-API" of the IoT Server in order to register the device's IP + * against its ID. + * + * @param deviceOwner the owner of the device by whose name the agent was downloaded. + * (Read from configuration file) + * @param deviceID the deviceId that is auto-generated whilst downloading the agent. + * (Read from configuration file) + * @return the status code of the HTTP-Post call to the Register-API of the IoT-Server + * @throws AgentCoreOperationException if any errors occur when an HTTPConnection session is + * created + */ + private int registerDeviceIP(String deviceOwner, String deviceID) + throws AgentCoreOperationException { + int responseCode = -1; + final AgentManager agentManager = AgentManager.getInstance(); + + String networkInterface = agentManager.getNetworkInterface(); + String deviceIPAddress = getDeviceIP(networkInterface); + + if (deviceIPAddress == null) { + throw new AgentCoreOperationException( + "An IP address could not be retrieved for the selected network interface - '" + + networkInterface + "."); + } + + agentManager.setDeviceIP(deviceIPAddress); + log.info(AgentConstants.LOG_APPENDER + "Device IP Address: " + deviceIPAddress); + + String deviceIPRegistrationEP = agentManager.getIpRegistrationEP(); + String registerEndpointURLString = + deviceIPRegistrationEP + File.separator + deviceOwner + File.separator + deviceID + + File.separator + deviceIPAddress + File.separator + port; + + if (log.isDebugEnabled()) { + log.debug(AgentConstants.LOG_APPENDER + "DeviceIP Registration EndPoint: " + + registerEndpointURLString); + } + + HttpURLConnection httpConnection; + try { + httpConnection = TransportUtils.getHttpConnection(registerEndpointURLString); + } catch (TransportHandlerException e) { + String errorMsg = + "Protocol specific error occurred when trying to fetch an HTTPConnection to:" + + " " + + registerEndpointURLString; + log.error(AgentConstants.LOG_APPENDER + errorMsg); + throw new AgentCoreOperationException(); + } + + try { + httpConnection.setRequestMethod(AgentConstants.HTTP_POST); + httpConnection.setRequestProperty("Authorization", "Bearer " + + agentManager.getAgentConfigs().getAuthToken()); + httpConnection.setDoOutput(true); + responseCode = httpConnection.getResponseCode(); + + } catch (ProtocolException exception) { + String errorMsg = "Protocol specific error occurred when trying to set method to " + + AgentConstants.HTTP_POST + " for:" + registerEndpointURLString; + log.error(AgentConstants.LOG_APPENDER + errorMsg); + throw new AgentCoreOperationException(errorMsg, exception); + + } catch (IOException exception) { + String errorMsg = "An IO error occurred whilst trying to get the response code from:" + + " " + + registerEndpointURLString + " for a " + AgentConstants.HTTP_POST + " method."; + log.error(AgentConstants.LOG_APPENDER + errorMsg); + throw new AgentCoreOperationException(errorMsg, exception); + } + + log.info(AgentConstants.LOG_APPENDER + "DeviceIP - " + deviceIPAddress + + ", registration with IoT Server at : " + + agentManager.getAgentConfigs().getHTTPS_ServerEndpoint() + + " returned status " + + responseCode); + + return responseCode; + } + + /*------------------------------------------------------------------------------------------*/ + /* Utility methods relevant to creating and sending HTTP requests to the Iot-Server */ + /*------------------------------------------------------------------------------------------*/ + + /** + * This method is used to get the IP of the device in which the agent is run on. + * + * @return the IP Address of the device + * @throws AgentCoreOperationException if any errors occur whilst trying to get the IP address + */ + private String getDeviceIP() throws AgentCoreOperationException { + try { + return Inet4Address.getLocalHost().getHostAddress(); + } catch (UnknownHostException e) { + String errorMsg = "Error encountered whilst trying to get the device IP address."; + log.error(AgentConstants.LOG_APPENDER + errorMsg); + throw new AgentCoreOperationException(errorMsg, e); + } + } + + /** + * This is an overloaded method that fetches the public IPv4 address of the given network + * interface + * + * @param networkInterfaceName the network-interface of whose IPv4 address is to be retrieved + * @return the IP Address iof the device + * @throws AgentCoreOperationException if any errors occur whilst trying to get details of the + * given network interface + */ + private String getDeviceIP(String networkInterfaceName) throws + AgentCoreOperationException { + String ipAddress = null; + try { + Enumeration interfaceIPAddresses = NetworkInterface.getByName( + networkInterfaceName).getInetAddresses(); + for (; interfaceIPAddresses.hasMoreElements(); ) { + InetAddress ip = interfaceIPAddresses.nextElement(); + ipAddress = ip.getHostAddress(); + if (log.isDebugEnabled()) { + log.debug(AgentConstants.LOG_APPENDER + "IP Address: " + ipAddress); + } + + if (TransportUtils.validateIPv4(ipAddress)) { + return ipAddress; + } + } + } catch (SocketException | NullPointerException exception) { + String errorMsg = + "Error encountered whilst trying to get IP Addresses of the network interface: " + + networkInterfaceName + + ".\nPlease check whether the name of the network interface used is correct"; + log.error(AgentConstants.LOG_APPENDER + errorMsg); + throw new AgentCoreOperationException(errorMsg, exception); + } + + return ipAddress; + } + +} diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/communication/mqtt/FireAlarmMQTTCommunicator.java b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/communication/mqtt/FireAlarmMQTTCommunicator.java new file mode 100644 index 0000000000..cb74b048b5 --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/communication/mqtt/FireAlarmMQTTCommunicator.java @@ -0,0 +1,265 @@ +/* + * 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.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.communication.mqtt; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.eclipse.paho.client.mqttv3.MqttException; +import org.eclipse.paho.client.mqttv3.MqttMessage; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.core.AgentConstants; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.core.AgentManager; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.core.AgentUtilOperations; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.exception.AgentCoreOperationException; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.transport.TransportHandlerException; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.transport.mqtt.MQTTTransportHandler; + +import java.nio.charset.StandardCharsets; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +//TODO:: Lincense heade, comments and SPECIFIC class name since its not generic +public class FireAlarmMQTTCommunicator extends MQTTTransportHandler { + + private static final Log log = LogFactory.getLog(FireAlarmMQTTCommunicator.class); + + private ScheduledExecutorService service = Executors.newScheduledThreadPool(2); + private ScheduledFuture dataPushServiceHandler; + + public FireAlarmMQTTCommunicator(String deviceOwner, String deviceType, + String mqttBrokerEndPoint, String subscribeTopic) { + super(deviceOwner, deviceType, mqttBrokerEndPoint, subscribeTopic); + } + + @SuppressWarnings("unused") + public FireAlarmMQTTCommunicator(String deviceOwner, String deviceType, + String mqttBrokerEndPoint, String subscribeTopic, + int intervalInMillis) { + super(deviceOwner, deviceType, mqttBrokerEndPoint, subscribeTopic, intervalInMillis); + } + + public ScheduledFuture getDataPushServiceHandler() { + return dataPushServiceHandler; + } + + //TODO:: Terminate logs with a period + //TODO: Need to print exceptions + @Override + public void connect() { + final AgentManager agentManager = AgentManager.getInstance(); + Runnable connector = new Runnable() { + public void run() { + while (!isConnected()) { + try { + connectToQueue(); + agentManager.updateAgentStatus("Connected to MQTT Queue"); + } catch (TransportHandlerException e) { + log.warn(AgentConstants.LOG_APPENDER + "Connection to MQTT Broker at: " + mqttBrokerEndPoint + + " failed.\n Will retry in " + timeoutInterval + " milli-seconds."); + } + + try{ + subscribeToQueue(); + agentManager.updateAgentStatus("Subscribed to MQTT Queue"); + publishDeviceData(); + + } catch (TransportHandlerException e) { + log.warn(AgentConstants.LOG_APPENDER + "Subscription to MQTT Broker at: " + + mqttBrokerEndPoint + " failed"); + agentManager.updateAgentStatus("Subscription to broker failed."); + } + + try { + Thread.sleep(timeoutInterval); + } catch (InterruptedException ex) { + log.error(AgentConstants.LOG_APPENDER + "MQTT: Connect-Thread Sleep Interrupt Exception."); + } + } + } + }; + + Thread connectorThread = new Thread(connector); + connectorThread.setDaemon(true); + connectorThread.start(); + } + + + @Override + public void processIncomingMessage(MqttMessage message, String... messageParams) { + final AgentManager agentManager = AgentManager.getInstance(); + String serverName = agentManager.getAgentConfigs().getServerName(); + String deviceOwner = agentManager.getAgentConfigs().getDeviceOwner(); + String deviceID = agentManager.getAgentConfigs().getDeviceId(); + String receivedMessage; + String replyMessage; + String securePayLoad; + + try { + receivedMessage = AgentUtilOperations.extractMessageFromPayload(message.toString()); + log.info(AgentConstants.LOG_APPENDER + "Message [" + receivedMessage + "] was received"); + } catch (AgentCoreOperationException e) { + log.warn(AgentConstants.LOG_APPENDER + "Could not extract message from payload.", e); + return; + } + + + String[] controlSignal = receivedMessage.split(":"); + // message- ":" format.(ex: "BULB:ON", "TEMPERATURE", "HUMIDITY") + + try { + switch (controlSignal[0].toUpperCase()) { + case AgentConstants.BULB_CONTROL: + boolean stateToSwitch = controlSignal[1].equals(AgentConstants.CONTROL_ON); + agentManager.changeAlarmStatus(stateToSwitch); + log.info(AgentConstants.LOG_APPENDER + "Bulb was switched to state: '" + controlSignal[1] + "'"); + break; + + case AgentConstants.TEMPERATURE_CONTROL: + int currentTemperature = agentManager.getTemperature(); + + String replyTemperature = "Current temperature was read as: '" + currentTemperature + "C'"; + log.info(AgentConstants.LOG_APPENDER + replyTemperature); + + String tempPublishTopic = String.format(AgentConstants.MQTT_PUBLISH_TOPIC, + serverName, deviceOwner, deviceID); + + replyMessage = AgentConstants.TEMPERATURE_CONTROL + ":" + currentTemperature; + securePayLoad = AgentUtilOperations.prepareSecurePayLoad(replyMessage); + publishToQueue(tempPublishTopic, securePayLoad); + break; + + case AgentConstants.HUMIDITY_CONTROL: + int currentHumidity = agentManager.getHumidity(); + + String replyHumidity = "Current humidity was read as: '" + currentHumidity + "%'"; + log.info(AgentConstants.LOG_APPENDER + replyHumidity); + + String humidPublishTopic = String.format( + AgentConstants.MQTT_PUBLISH_TOPIC, serverName, deviceOwner, deviceID); + + replyMessage = AgentConstants.HUMIDITY_CONTROL + ":" + currentHumidity; + securePayLoad = AgentUtilOperations.prepareSecurePayLoad(replyMessage); + publishToQueue(humidPublishTopic, securePayLoad); + break; + + default: + log.warn(AgentConstants.LOG_APPENDER + "'" + controlSignal[0] + + "' is invalid and not-supported for this device-type"); + break; + } + } catch (AgentCoreOperationException e) { + log.warn(AgentConstants.LOG_APPENDER + "Preparing Secure payload failed", e); + } catch (TransportHandlerException e) { + log.error(AgentConstants.LOG_APPENDER + + "MQTT - Publishing, reply message to the MQTT Queue at: " + + agentManager.getAgentConfigs().getMqttBrokerEndpoint() + " failed"); + } + + } + + @Override + public void publishDeviceData() { + final AgentManager agentManager = AgentManager.getInstance(); + int publishInterval = agentManager.getPushInterval(); + Runnable pushDataRunnable = new Runnable() { + @Override + public void run() { + int currentTemperature = agentManager.getTemperature(); + String message = "PUBLISHER:" + AgentConstants.TEMPERATURE_CONTROL + ":" + currentTemperature; + + try { + String payLoad = AgentUtilOperations.prepareSecurePayLoad(message); + + MqttMessage pushMessage = new MqttMessage(); + pushMessage.setPayload(payLoad.getBytes(StandardCharsets.UTF_8)); + pushMessage.setQos(DEFAULT_MQTT_QUALITY_OF_SERVICE); + pushMessage.setRetained(false); + + String topic = String.format(AgentConstants.MQTT_PUBLISH_TOPIC, + agentManager.getAgentConfigs().getServerName(), + agentManager.getAgentConfigs().getDeviceOwner(), + agentManager.getAgentConfigs().getDeviceId()); + + publishToQueue(topic, pushMessage); + log.info(AgentConstants.LOG_APPENDER + "Message: '" + message + "' published to MQTT Queue at [" + + agentManager.getAgentConfigs().getMqttBrokerEndpoint() + "] under topic [" + + topic + "]"); + + } catch (TransportHandlerException e) { + log.warn(AgentConstants.LOG_APPENDER + "Data Publish attempt to topic - [" + + AgentConstants.MQTT_PUBLISH_TOPIC + "] failed for payload [" + message + "]"); + } catch (AgentCoreOperationException e) { + log.warn(AgentConstants.LOG_APPENDER + "Preparing Secure payload failed", e); + } + } + }; + + dataPushServiceHandler = service.scheduleAtFixedRate(pushDataRunnable, publishInterval, publishInterval, + TimeUnit.SECONDS); + } + + + @Override + public void disconnect() { + Runnable stopConnection = new Runnable() { + public void run() { + while (isConnected()) { + + if (dataPushServiceHandler != null) { + dataPushServiceHandler.cancel(true); + } + + try { + closeConnection(); + + } catch (MqttException e) { + if (log.isDebugEnabled()) { + log.warn(AgentConstants.LOG_APPENDER + + "Unable to 'STOP' MQTT connection at broker at: " + + mqttBrokerEndPoint); + } + + try { + Thread.sleep(timeoutInterval); + } catch (InterruptedException e1) { + log.error(AgentConstants.LOG_APPENDER + + "MQTT-Terminator: Thread Sleep Interrupt Exception"); + } + } + } + } + }; + + Thread terminatorThread = new Thread(stopConnection); + terminatorThread.setDaemon(true); + terminatorThread.start(); + } + + @Override + public void processIncomingMessage() { + + } + + @Override + public void publishDeviceData(String... publishData) { + + } + +} diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/communication/xmpp/FireAlarmXMPPCommunicator.java b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/communication/xmpp/FireAlarmXMPPCommunicator.java new file mode 100644 index 0000000000..86c4a0f7bf --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/communication/xmpp/FireAlarmXMPPCommunicator.java @@ -0,0 +1,265 @@ +/* + * 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.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.communication.xmpp; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.jivesoftware.smack.packet.Message; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.core.AgentConstants; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.core.AgentManager; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.core.AgentUtilOperations; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.exception.AgentCoreOperationException; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.transport.TransportHandlerException; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.transport.xmpp.XMPPTransportHandler; + +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +public class FireAlarmXMPPCommunicator extends XMPPTransportHandler { + + private static final Log log = LogFactory.getLog(FireAlarmXMPPCommunicator.class); + + private ScheduledExecutorService service = Executors.newScheduledThreadPool(2); + private ScheduledFuture dataPushServiceHandler; + private ScheduledFuture connectorServiceHandler; + + private String username; + private String password; + private String resource; + private String xmppAdminJID; + private String xmppDeviceJID; + + public FireAlarmXMPPCommunicator(String server) { + super(server); + } + + public FireAlarmXMPPCommunicator(String server, int port) { + super(server, port); + } + + public FireAlarmXMPPCommunicator(String server, int port, int timeout) { + super(server, port, timeout); + } + + public ScheduledFuture getDataPushServiceHandler() { + return dataPushServiceHandler; + } + + @Override + public void connect() { + final AgentManager agentManager = AgentManager.getInstance(); + username = agentManager.getAgentConfigs().getDeviceId(); + password = agentManager.getAgentConfigs().getAuthToken(); + resource = agentManager.getAgentConfigs().getDeviceOwner(); + + xmppDeviceJID = username + "@" + server; + xmppAdminJID = agentManager.getAgentConfigs().getServerName() + "_" + AgentConstants.DEVICE_TYPE + "@" + server; + + + Runnable connect = new Runnable() { + public void run() { + if (!isConnected()) { + try { + connectToServer(); + } catch (TransportHandlerException e) { + log.warn(AgentConstants.LOG_APPENDER + "Connection to XMPP server at: " + server + " failed"); + } + + try { + loginToServer(username, password, resource); + agentManager.updateAgentStatus("Connected to XMPP Server"); + setMessageFilterAndListener(xmppAdminJID, xmppDeviceJID, true); + publishDeviceData(); + } catch (TransportHandlerException e) { + log.warn(AgentConstants.LOG_APPENDER + "Login to XMPP server at: " + server + " failed"); + agentManager.updateAgentStatus("No XMPP Account for Device"); + } + } + } + }; + + connectorServiceHandler = service.scheduleAtFixedRate(connect, 0, timeoutInterval, TimeUnit.MILLISECONDS); + } + + /** + * This is an abstract method used for post processing the received XMPP-message. This + * method will be implemented as per requirement at the time of creating an object of this + * class. + * + * @param xmppMessage the xmpp message received by the listener. + */ + @Override + public void processIncomingMessage(Message xmppMessage, String... messageParams) { + final AgentManager agentManager = AgentManager.getInstance(); + String from = xmppMessage.getFrom(); + String message = xmppMessage.getBody(); + String receivedMessage; + String replyMessage; + String securePayLoad; + + try { + receivedMessage = AgentUtilOperations.extractMessageFromPayload(message); + log.info(AgentConstants.LOG_APPENDER + "Message [" + receivedMessage + "] was received"); + } catch (AgentCoreOperationException e) { + log.warn(AgentConstants.LOG_APPENDER + "Could not extract message from payload.", e); + return; + } + + String[] controlSignal = receivedMessage.split(":"); + //message- ":" format. (ex: "BULB:ON", "TEMPERATURE", "HUMIDITY") + + try { + switch (controlSignal[0].toUpperCase()) { + case AgentConstants.BULB_CONTROL: + if (controlSignal.length != 2) { + replyMessage = "BULB controls need to be in the form - 'BULB:{ON|OFF}'"; + log.warn(replyMessage); + securePayLoad = AgentUtilOperations.prepareSecurePayLoad(replyMessage); + sendXMPPMessage(xmppAdminJID, securePayLoad, "CONTROL-REPLY"); + break; + } + + agentManager.changeAlarmStatus(controlSignal[1].equals(AgentConstants.CONTROL_ON)); + log.info(AgentConstants.LOG_APPENDER + "Bulb was switched to state: '" + controlSignal[1] + "'"); + break; + + case AgentConstants.TEMPERATURE_CONTROL: + int currentTemperature = agentManager.getTemperature(); + + String replyTemperature = + "The current temperature was read to be: '" + currentTemperature + + "C'"; + log.info(AgentConstants.LOG_APPENDER + replyTemperature); + + replyMessage = AgentConstants.TEMPERATURE_CONTROL + ":" + currentTemperature; + securePayLoad = AgentUtilOperations.prepareSecurePayLoad(replyMessage); + sendXMPPMessage(xmppAdminJID, securePayLoad, "CONTROL-REPLY"); + break; + + case AgentConstants.HUMIDITY_CONTROL: + int currentHumidity = agentManager.getHumidity(); + + String replyHumidity = "The current humidity was read to be: '" + currentHumidity + "%'"; + log.info(AgentConstants.LOG_APPENDER + replyHumidity); + + replyMessage = AgentConstants.HUMIDITY_CONTROL + ":" + currentHumidity; + securePayLoad = AgentUtilOperations.prepareSecurePayLoad(replyMessage); + sendXMPPMessage(xmppAdminJID, securePayLoad, "CONTROL-REPLY"); + break; + + default: + replyMessage = "'" + controlSignal[0] + "' is invalid and not-supported for this device-type"; + log.warn(replyMessage); + securePayLoad = AgentUtilOperations.prepareSecurePayLoad(replyMessage); + sendXMPPMessage(xmppAdminJID, securePayLoad, "CONTROL-ERROR"); + break; + } + } catch (AgentCoreOperationException e) { + log.warn(AgentConstants.LOG_APPENDER + "Preparing Secure payload failed", e); + } + + } + + + @Override + public void publishDeviceData() { + final AgentManager agentManager = AgentManager.getInstance(); + int publishInterval = agentManager.getPushInterval(); + + Runnable pushDataRunnable = new Runnable() { + @Override + public void run() { + Message xmppMessage = new Message(); + + try { + int currentTemperature = agentManager.getTemperature(); + + String message = AgentConstants.TEMPERATURE_CONTROL + ":" + currentTemperature; + String payLoad = AgentUtilOperations.prepareSecurePayLoad(message); + + xmppMessage.setTo(xmppAdminJID); + xmppMessage.setSubject("PUBLISHER"); + xmppMessage.setBody(payLoad); + xmppMessage.setType(Message.Type.chat); + + sendXMPPMessage(xmppAdminJID, xmppMessage); + log.info(AgentConstants.LOG_APPENDER + "Message: '" + message + "' sent to XMPP JID - " + + "[" + xmppAdminJID + "] under subject [" + xmppMessage.getSubject() + "]."); + } catch (AgentCoreOperationException e) { + log.warn(AgentConstants.LOG_APPENDER + "Preparing Secure payload failed for XMPP JID - " + + "[" + xmppAdminJID + "] with subject - [" + xmppMessage.getSubject() + "]."); + } + } + }; + + dataPushServiceHandler = service.scheduleAtFixedRate(pushDataRunnable, publishInterval, + publishInterval, TimeUnit.SECONDS); + } + + + @Override + public void disconnect() { + Runnable stopConnection = new Runnable() { + public void run() { + + if (dataPushServiceHandler != null) { + dataPushServiceHandler.cancel(true); + } + + if (connectorServiceHandler != null) { + connectorServiceHandler.cancel(true); + } + + while (isConnected()) { + closeConnection(); + + if (log.isDebugEnabled()) { + log.warn(AgentConstants.LOG_APPENDER + + "Unable to 'STOP' connection to XMPP server at: " + server); + } + + try { + Thread.sleep(timeoutInterval); + } catch (InterruptedException e1) { + log.error(AgentConstants.LOG_APPENDER + "XMPP-Terminator: Thread Sleep Interrupt Exception"); + } + + } + } + }; + + Thread terminatorThread = new Thread(stopConnection); + terminatorThread.setDaemon(true); + terminatorThread.start(); + } + + + @Override + public void processIncomingMessage() { + + } + + @Override + public void publishDeviceData(String... publishData) { + + } + +} diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/core/AgentConfiguration.java b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/core/AgentConfiguration.java new file mode 100644 index 0000000000..e36b161cbb --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/core/AgentConfiguration.java @@ -0,0 +1,155 @@ +/* + * 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.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.core; + +/** + * A Configuration class that holds all the Agent specific details that are read from the + * 'deviceConfig.properties' file. This file is generated by the IoT-Server at the time of + * downloading the device agent from the IoT-Server. + */ +public class AgentConfiguration { + private String serverName; + private String deviceOwner; + private String deviceId; + private String deviceName; + private String controllerContext; + private String HTTPS_ServerEndpoint; + private String HTTP_ServerEndpoint; + private String apimGatewayEndpoint; + private String mqttBrokerEndpoint; + private String xmppServerEndpoint; + private String authMethod; + private String authToken; + private String refreshToken; + private int dataPushInterval; + + public String getServerName() { + return serverName; + } + + public void setServerName(String serverName) { + this.serverName = serverName; + } + + public String getDeviceOwner() { + return deviceOwner; + } + + public void setDeviceOwner(String deviceOwner) { + this.deviceOwner = deviceOwner; + } + + public String getDeviceId() { + return deviceId; + } + + public void setDeviceId(String deviceId) { + this.deviceId = deviceId; + } + + public String getDeviceName() { + return deviceName; + } + + public void setDeviceName(String deviceName) { + this.deviceName = deviceName; + } + + public String getControllerContext() { + return controllerContext; + } + + public void setControllerContext(String controllerContext) { + this.controllerContext = controllerContext; + } + + public String getHTTPS_ServerEndpoint() { + return HTTPS_ServerEndpoint; + } + + public void setHTTPS_ServerEndpoint(String HTTPS_ServerEndpoint) { + this.HTTPS_ServerEndpoint = HTTPS_ServerEndpoint; + } + + public String getHTTP_ServerEndpoint() { + return HTTP_ServerEndpoint; + } + + public void setHTTP_ServerEndpoint(String HTTP_ServerEndpoint) { + this.HTTP_ServerEndpoint = HTTP_ServerEndpoint; + } + + public String getApimGatewayEndpoint() { + return apimGatewayEndpoint; + } + + public void setApimGatewayEndpoint(String apimGatewayEndpoint) { + this.apimGatewayEndpoint = apimGatewayEndpoint; + } + + public String getMqttBrokerEndpoint() { + return mqttBrokerEndpoint; + } + + public void setMqttBrokerEndpoint(String mqttBrokerEndpoint) { + this.mqttBrokerEndpoint = mqttBrokerEndpoint; + } + + public String getXmppServerEndpoint() { + return xmppServerEndpoint; + } + + public void setXmppServerEndpoint(String xmppServerEndpoint) { + this.xmppServerEndpoint = xmppServerEndpoint; + } + + public String getAuthMethod() { + return authMethod; + } + + public void setAuthMethod(String authMethod) { + this.authMethod = authMethod; + } + + public String getAuthToken() { + return authToken; + } + + public void setAuthToken(String authToken) { + this.authToken = authToken; + } + + public String getRefreshToken() { + return refreshToken; + } + + public void setRefreshToken(String refreshToken) { + this.refreshToken = refreshToken; + } + + public int getDataPushInterval() { + return dataPushInterval; + } + + public void setDataPushInterval(int dataPushInterval) { + this.dataPushInterval = dataPushInterval; + } +} + + diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/core/AgentConstants.java b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/core/AgentConstants.java new file mode 100644 index 0000000000..f415ef9e00 --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/core/AgentConstants.java @@ -0,0 +1,109 @@ +/* + * 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.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.core; + +public class AgentConstants { + public static final String DEVICE_TYPE = "virtual_firealarm"; + public static final String LOG_APPENDER = "AGENT_LOG:: "; + public static final String PROPERTIES_FILE_PATH = ""; + public static final int DEFAULT_RETRY_THREAD_INTERVAL = 5000; // time in millis + /* --------------------------------------------------------------------------------------- + IoT-Server specific information + --------------------------------------------------------------------------------------- */ + public static final String DEVICE_CONTROLLER_API_EP = "/virtual_firealarm/controller"; + public static final String DEVICE_ENROLLMENT_API_EP = "/scep"; + public static final String DEVICE_REGISTER_API_EP = "/register"; + public static final String DEVICE_PUSH_TEMPERATURE_API_EP = "/temperature"; + public static final String PUSH_DATA_PAYLOAD = + "{\"owner\":\"%s\",\"deviceId\":\"%s\",\"reply\":\"%s\",\"value\":\"%s\"}"; + + public static final String PUSH_SIMULATION_DATA_PAYLOAD = + "{\"owner\":\"%s\",\"deviceId\":\"%s\",\"reply\":\"%s\",\"value\":\"%s\",\"isSimulated\":\"%s\",\"duration\":\"%s\",\"frequency\":\"%s\"}"; + + public static final String AGENT_CONTROL_APP_EP = "/devicemgt/device/%s?id=%s"; + public static final String DEVICE_DETAILS_PAGE_EP = "/devicemgt/device/%s?id=%s"; + public static final String DEVICE_ANALYTICS_PAGE_URL = "/devicemgt/analytics?deviceId=%s&deviceType=%s&deviceName=%s"; + /* --------------------------------------------------------------------------------------- + HTTP Connection specific information for communicating with IoT-Server + --------------------------------------------------------------------------------------- */ + public static final String HTTP_POST = "POST"; + public static final String HTTP_GET = "GET"; + public static final String APPLICATION_JSON_TYPE = "application/json"; + public static final String REGISTERED = "Registered"; + public static final String NOT_REGISTERED = "Not-Registered"; + public static final String REGISTRATION_FAILED = "Registration Failed"; + public static final String RETRYING_TO_REGISTER = "Registration Failed. Re-trying.."; + public static final String SERVER_NOT_RESPONDING = "Server not responding.."; + + /* --------------------------------------------------------------------------------------- + MQTT Connection specific information + --------------------------------------------------------------------------------------- */ + public static final int DEFAULT_MQTT_RECONNECTION_INTERVAL = 2; // time in seconds + public static final int DEFAULT_MQTT_QUALITY_OF_SERVICE = 0; + public static final String MQTT_SUBSCRIBE_TOPIC = "%s/%s/" + DEVICE_TYPE + "/%s"; + public static final String MQTT_PUBLISH_TOPIC = "%s/%s/" + DEVICE_TYPE + "/%s/publisher"; + /* --------------------------------------------------------------------------------------- + XMPP Connection specific information + --------------------------------------------------------------------------------------- */ + public static final String XMPP_ADMIN_ACCOUNT_UNAME = "admin"; + /* --------------------------------------------------------------------------------------- + Device/Agent specific properties to be read from the 'deviceConfig.properties' file + --------------------------------------------------------------------------------------- */ + public static final String AGENT_PROPERTIES_FILE_NAME = "deviceConfig.properties"; + public static final String SERVER_NAME_PROPERTY = "server-name"; + public static final String DEVICE_OWNER_PROPERTY = "owner"; + public static final String DEVICE_ID_PROPERTY = "deviceId"; + public static final String DEVICE_NAME_PROPERTY = "device-name"; + public static final String DEVICE_CONTROLLER_CONTEXT_PROPERTY = "controller-context"; + public static final String SERVER_HTTPS_EP_PROPERTY = "https-ep"; + public static final String SERVER_HTTP_EP_PROPERTY = "http-ep"; + public static final String APIM_GATEWAY_EP_PROPERTY = "apim-ep"; + public static final String MQTT_BROKER_EP_PROPERTY = "mqtt-ep"; + public static final String XMPP_SERVER_EP_PROPERTY = "xmpp-ep"; + public static final String AUTH_METHOD_PROPERTY = "auth-method"; + public static final String AUTH_TOKEN_PROPERTY = "auth-token"; + public static final String REFRESH_TOKEN_PROPERTY = "refresh-token"; + public static final String NETWORK_INTERFACE_PROPERTY = "network-interface"; + public static final String PUSH_INTERVAL_PROPERTY = "push-interval"; + /* --------------------------------------------------------------------------------------- + Default values for the Device/Agent specific configurations listed above + --------------------------------------------------------------------------------------- */ + public static final String DEFAULT_NETWORK_INTERFACE = "en0"; + public static final int DEFAULT_DATA_PUBLISH_INTERVAL = 15; // seconds + public static final String DEFAULT_PROTOCOL = "MQTT"; + /* --------------------------------------------------------------------------------------- + Control Signal specific constants to match the request context + --------------------------------------------------------------------------------------- */ + public static final String BULB_CONTROL = "BULB"; + public static final String TEMPERATURE_CONTROL = "TEMPERATURE"; + public static final String POLICY_SIGNAL = "POLICY"; + public static final String HUMIDITY_CONTROL = "HUMIDITY"; + public static final String CONTROL_ON = "ON"; + public static final String CONTROL_OFF = "OFF"; + public static final String AUDIO_FILE_NAME = "fireAlarmSound.mid"; + /* --------------------------------------------------------------------------------------- + Communication protocol specific Strings + --------------------------------------------------------------------------------------- */ + public static final String TCP_PREFIX = "tcp://"; + public static final String HTTP_PREFIX = "http://"; + public static final String HTTPS_PREFIX = "https://"; + public static final String HTTP_PROTOCOL = "HTTP"; + public static final String MQTT_PROTOCOL = "MQTT"; + public static final String XMPP_PROTOCOL = "XMPP"; +} diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/core/AgentManager.java b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/core/AgentManager.java new file mode 100644 index 0000000000..b1d204d62d --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/core/AgentManager.java @@ -0,0 +1,347 @@ +/* + * 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.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.core; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.enrollment.EnrollmentManager; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.exception.AgentCoreOperationException; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.transport.TransportHandler; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.transport.TransportHandlerException; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.transport.TransportUtils; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.communication.http.FireAlarmHTTPCommunicator; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.communication.mqtt.FireAlarmMQTTCommunicator; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.communication.xmpp.FireAlarmXMPPCommunicator; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.virtual.VirtualHardwareManager; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class AgentManager { + + private static final Log log = LogFactory.getLog(AgentManager.class); + private static AgentManager agentManager; + private String rootPath = ""; + + private boolean deviceReady = false; + private boolean isAlarmOn = false; + + private String deviceMgtControlUrl, deviceMgtAnalyticUrl; + private String deviceName, agentStatus; + + private int pushInterval; // seconds + private String prevProtocol, protocol; + + private String networkInterface; + private List interfaceList, protocolList; + private Map agentCommunicator; + + private AgentConfiguration agentConfigs; + + private String deviceIP; + private String enrollmentEP; + private String ipRegistrationEP; + private String pushDataAPIEP; + + private AgentManager() { + } + + public static AgentManager getInstance() { + if (agentManager == null) { + agentManager = new AgentManager(); + } + return agentManager; + } + + public void init() { + + agentCommunicator = new HashMap<>(); + + // Read IoT-Server specific configurations from the 'deviceConfig.properties' file + try { + this.agentConfigs = AgentUtilOperations.readIoTServerConfigs(); + } catch (AgentCoreOperationException e) { + log.error("Reading device configuration from configd file failed:\n"); + log.error(e); + System.exit(0); + } + + // Initialise IoT-Server URL endpoints from the configuration read from file + AgentUtilOperations.initializeServerEndPoints(); + + String analyticsPageContext = String.format(AgentConstants.DEVICE_ANALYTICS_PAGE_URL, + agentConfigs.getDeviceId(), + AgentConstants.DEVICE_TYPE, + agentConfigs.getDeviceName()); + + String controlPageContext = String.format(AgentConstants.DEVICE_DETAILS_PAGE_EP, + AgentConstants.DEVICE_TYPE, + agentConfigs.getDeviceId()); + + this.deviceMgtAnalyticUrl = agentConfigs.getHTTPS_ServerEndpoint() + analyticsPageContext; + this.deviceMgtControlUrl = agentConfigs.getHTTPS_ServerEndpoint() + controlPageContext; + + this.agentStatus = AgentConstants.NOT_REGISTERED; + this.deviceName = this.agentConfigs.getDeviceName(); + + this.pushInterval = this.agentConfigs.getDataPushInterval(); + this.networkInterface = AgentConstants.DEFAULT_NETWORK_INTERFACE; + + this.protocol = AgentConstants.DEFAULT_PROTOCOL; + this.prevProtocol = protocol; + + Map xmppIPPortMap; + try { + xmppIPPortMap = TransportUtils.getHostAndPort(agentConfigs.getXmppServerEndpoint()); + String xmppServer = xmppIPPortMap.get("Host"); + int xmppPort = Integer.parseInt(xmppIPPortMap.get("Port")); + + TransportHandler xmppCommunicator = new FireAlarmXMPPCommunicator(xmppServer, xmppPort); + agentCommunicator.put(AgentConstants.XMPP_PROTOCOL, xmppCommunicator); + + } catch (TransportHandlerException e) { + log.error("XMPP Endpoint String - " + agentConfigs.getXmppServerEndpoint() + + ", provided in the configuration file is invalid."); + } + String mqttTopic = String.format(AgentConstants.MQTT_SUBSCRIBE_TOPIC, + agentConfigs.getServerName(), + agentConfigs.getDeviceOwner(), + agentConfigs.getDeviceId()); + +// TransportHandler httpCommunicator = new FireAlarmHTTPCommunicator(); + TransportHandler mqttCommunicator = new FireAlarmMQTTCommunicator(agentConfigs.getDeviceOwner(), + agentConfigs.getDeviceId(), + agentConfigs.getMqttBrokerEndpoint(), + mqttTopic); + +// agentCommunicator.put(AgentConstants.HTTP_PROTOCOL, httpCommunicator); + agentCommunicator.put(AgentConstants.MQTT_PROTOCOL, mqttCommunicator); + + try { + interfaceList = new ArrayList<>(TransportUtils.getInterfaceIPMap().keySet()); + protocolList = new ArrayList<>(agentCommunicator.keySet()); + } catch (TransportHandlerException e) { + log.error("An error occurred whilst retrieving all NetworkInterface-IP mappings"); + } + + //Initializing hardware at that point + //AgentManger.setDeviceReady() method should invoked from hardware after initialization + VirtualHardwareManager.getInstance().init(); + + //Wait till hardware get ready + while (!deviceReady) { + try { + Thread.sleep(500); + } catch (InterruptedException e) { + log.info(AgentConstants.LOG_APPENDER + "Sleep error in 'device ready-flag' checking thread"); + } + } + + try { + EnrollmentManager.getInstance().beginEnrollmentFlow(); + } catch (AgentCoreOperationException e) { + log.error("Device Enrollment Failed:\n"); + log.error(e); + System.exit(0); + } + + //Start agent communication + agentCommunicator.get(protocol).connect(); + } + + private void switchCommunicator(String stopProtocol, String startProtocol) { + agentCommunicator.get(stopProtocol).disconnect(); + + while (agentCommunicator.get(stopProtocol).isConnected()) { + try { + Thread.sleep(250); + } catch (InterruptedException e) { + log.info(AgentConstants.LOG_APPENDER + + "Sleep error in 'Switch-Communicator' Thread's shutdown wait."); + } + } + + agentCommunicator.get(startProtocol).connect(); + } + + public void setInterface(int interfaceId) { + if (interfaceId != -1) { + String newInterface = interfaceList.get(interfaceId); + + if (!newInterface.equals(networkInterface)) { + networkInterface = newInterface; + + if (protocol.equals(AgentConstants.HTTP_PROTOCOL) && !protocol.equals( + prevProtocol)) { + switchCommunicator(protocol, protocol); + } + } + } + } + + public void setProtocol(int protocolId) { + if (protocolId != -1) { + String newProtocol = protocolList.get(protocolId); + + if (!protocol.equals(newProtocol)) { + prevProtocol = protocol; + protocol = newProtocol; + switchCommunicator(prevProtocol, protocol); + } + } + } + + public void changeAlarmStatus(boolean isOn) { + VirtualHardwareManager.getInstance().changeAlarmStatus(isOn); + isAlarmOn = isOn; + } + + public void updateAgentStatus(String status) { + this.agentStatus = status; + } + + /*------------------------------------------------------------------------------------------*/ + /* Getter and Setter Methods for the private variables */ + /*------------------------------------------------------------------------------------------*/ + + public void setRootPath(String rootPath) { + this.rootPath = rootPath; + } + + public void setDeviceReady(boolean deviceReady) { + this.deviceReady = deviceReady; + } + + public String getDeviceMgtControlUrl() { + return deviceMgtControlUrl; + } + + public String getDeviceMgtAnalyticUrl() { + return deviceMgtAnalyticUrl; + } + + public AgentConfiguration getAgentConfigs() { + return agentConfigs; + } + + public String getDeviceIP() { + return deviceIP; + } + + public void setDeviceIP(String deviceIP) { + this.deviceIP = deviceIP; + } + + public String getEnrollmentEP() { + return enrollmentEP; + } + + public void setEnrollmentEP(String enrollmentEP) { + this.enrollmentEP = enrollmentEP; + } + + public String getIpRegistrationEP() { + return ipRegistrationEP; + } + + public void setIpRegistrationEP(String ipRegistrationEP) { + this.ipRegistrationEP = ipRegistrationEP; + } + + public String getPushDataAPIEP() { + return pushDataAPIEP; + } + + public void setPushDataAPIEP(String pushDataAPIEP) { + this.pushDataAPIEP = pushDataAPIEP; + } + + public String getDeviceName() { + return deviceName; + } + + public String getNetworkInterface() { + return networkInterface; + } + + public String getAgentStatus() { + return agentStatus; + } + + public int getPushInterval() { + return pushInterval; + } + + public void setPushInterval(int pushInterval) { + this.pushInterval = pushInterval; + TransportHandler transportHandler = agentCommunicator.get(protocol); + + switch (protocol) { + case AgentConstants.HTTP_PROTOCOL: + ((FireAlarmHTTPCommunicator) transportHandler).getDataPushServiceHandler() + .cancel(true); + break; + case AgentConstants.MQTT_PROTOCOL: + ((FireAlarmMQTTCommunicator) transportHandler).getDataPushServiceHandler() + .cancel(true); + break; + case AgentConstants.XMPP_PROTOCOL: + ((FireAlarmXMPPCommunicator) transportHandler).getDataPushServiceHandler() + .cancel(true); + break; + default: + log.warn("Unknown protocol " + protocol); + } + transportHandler.publishDeviceData(); + + if (log.isDebugEnabled()) { + log.debug("The Data Publish Interval was changed to: " + pushInterval); + } + } + + public List getInterfaceList() { + return interfaceList; + } + + public List getProtocolList() { + return protocolList; + } + + /** + * Get temperature reading from device + * @return Temperature + */ + public int getTemperature() { + return VirtualHardwareManager.getInstance().getTemperature(); + } + + /** + * Get humidity reading from device + * @return Humidity + */ + public int getHumidity(){ + return VirtualHardwareManager.getInstance().getHumidity(); + } + + public boolean isAlarmOn() { + return isAlarmOn; + } +} diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/core/AgentUtilOperations.java b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/core/AgentUtilOperations.java new file mode 100644 index 0000000000..d12baaa2b3 --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/core/AgentUtilOperations.java @@ -0,0 +1,280 @@ +/* + * 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.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.core; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.json.JSONObject; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.enrollment.EnrollmentManager; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.exception.AgentCoreOperationException; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.transport.CommunicationUtils; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.transport.TransportHandlerException; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.util.Properties; + +/** + * This class contains all the core operations of the FireAlarm agent that are common to both + * Virtual and Real Scenarios. These operations include, connecting to and subscribing to an MQTT + * queue and to a XMPP Server. Pushing temperature data to the IoT-Server at timely intervals. + * Reading device specific configuration from a configs file etc.... + */ +public class AgentUtilOperations { + + private static final Log log = LogFactory.getLog(AgentUtilOperations.class); + private static final String JSON_MESSAGE_KEY = "Msg"; + private static final String JSON_SIGNATURE_KEY = "Sig"; + + /** + * This method reads the agent specific configurations for the device from the + * "deviceConfigs.properties" file found at /repository/conf folder. + * If the properties file is not found in the specified path, then the configuration values + * are set to the default ones in the 'AgentConstants' class. + * + * @return an object of type 'AgentConfiguration' which contains all the necessary + * configuration attributes + */ + public static AgentConfiguration readIoTServerConfigs() throws AgentCoreOperationException { + AgentManager agentManager = AgentManager.getInstance(); + AgentConfiguration iotServerConfigs = new AgentConfiguration(); + Properties properties = new Properties(); + InputStream propertiesInputStream = null; + String propertiesFileName = AgentConstants.AGENT_PROPERTIES_FILE_NAME; + + try { + ClassLoader loader = AgentUtilOperations.class.getClassLoader(); + URL path = loader.getResource(propertiesFileName); + System.out.println(path); + String root = path.getPath().replace("wso2-firealarm-virtual-agent.jar!/deviceConfig.properties", "") + .replace("jar:", "").replace("file:", ""); + + root = URLDecoder.decode(root, StandardCharsets.UTF_8.toString()); + agentManager.setRootPath(root); + + String deviceConfigFilePath = root + AgentConstants.AGENT_PROPERTIES_FILE_NAME; + propertiesInputStream = new FileInputStream(deviceConfigFilePath); + + //load a properties file from class path, inside static method + properties.load(propertiesInputStream); + + iotServerConfigs.setServerName(properties.getProperty( + AgentConstants.SERVER_NAME_PROPERTY)); + iotServerConfigs.setDeviceOwner(properties.getProperty( + AgentConstants.DEVICE_OWNER_PROPERTY)); + iotServerConfigs.setDeviceId(properties.getProperty( + AgentConstants.DEVICE_ID_PROPERTY)); + iotServerConfigs.setDeviceName(properties.getProperty( + AgentConstants.DEVICE_NAME_PROPERTY)); + iotServerConfigs.setControllerContext(properties.getProperty( + AgentConstants.DEVICE_CONTROLLER_CONTEXT_PROPERTY)); + iotServerConfigs.setHTTPS_ServerEndpoint(properties.getProperty( + AgentConstants.SERVER_HTTPS_EP_PROPERTY)); + iotServerConfigs.setHTTP_ServerEndpoint(properties.getProperty( + AgentConstants.SERVER_HTTP_EP_PROPERTY)); + iotServerConfigs.setApimGatewayEndpoint(properties.getProperty( + AgentConstants.APIM_GATEWAY_EP_PROPERTY)); + iotServerConfigs.setMqttBrokerEndpoint(properties.getProperty( + AgentConstants.MQTT_BROKER_EP_PROPERTY)); + iotServerConfigs.setXmppServerEndpoint(properties.getProperty( + AgentConstants.XMPP_SERVER_EP_PROPERTY)); + iotServerConfigs.setAuthMethod(properties.getProperty( + AgentConstants.AUTH_METHOD_PROPERTY)); + iotServerConfigs.setAuthToken(properties.getProperty( + AgentConstants.AUTH_TOKEN_PROPERTY)); + iotServerConfigs.setRefreshToken(properties.getProperty( + AgentConstants.REFRESH_TOKEN_PROPERTY)); + iotServerConfigs.setDataPushInterval(Integer.parseInt(properties.getProperty( + AgentConstants.PUSH_INTERVAL_PROPERTY))); + + log.info(AgentConstants.LOG_APPENDER + "Server name: " + + iotServerConfigs.getServerName()); + log.info(AgentConstants.LOG_APPENDER + "Device Owner: " + + iotServerConfigs.getDeviceOwner()); + log.info(AgentConstants.LOG_APPENDER + "Device ID: " + iotServerConfigs.getDeviceId()); + log.info(AgentConstants.LOG_APPENDER + "Device Name: " + + iotServerConfigs.getDeviceName()); + log.info(AgentConstants.LOG_APPENDER + "Device Controller Context: " + + iotServerConfigs.getControllerContext()); + log.info(AgentConstants.LOG_APPENDER + "IoT Server HTTPS EndPoint: " + + iotServerConfigs.getHTTPS_ServerEndpoint()); + log.info(AgentConstants.LOG_APPENDER + "IoT Server HTTP EndPoint: " + + iotServerConfigs.getHTTP_ServerEndpoint()); + log.info(AgentConstants.LOG_APPENDER + "API-Manager Gateway EndPoint: " + + iotServerConfigs.getApimGatewayEndpoint()); + log.info(AgentConstants.LOG_APPENDER + "MQTT Broker EndPoint: " + + iotServerConfigs.getMqttBrokerEndpoint()); + log.info(AgentConstants.LOG_APPENDER + "XMPP Server EndPoint: " + + iotServerConfigs.getXmppServerEndpoint()); + log.info(AgentConstants.LOG_APPENDER + "Authentication Method: " + + iotServerConfigs.getAuthMethod()); + log.info(AgentConstants.LOG_APPENDER + "Authentication Token: " + + iotServerConfigs.getAuthToken()); + log.info(AgentConstants.LOG_APPENDER + "Refresh Token: " + + iotServerConfigs.getRefreshToken()); + log.info(AgentConstants.LOG_APPENDER + "Data Push Interval: " + + iotServerConfigs.getDataPushInterval()); + + } catch (FileNotFoundException ex) { + String errorMsg = "[" + propertiesFileName + "] file not found at: " + AgentConstants.PROPERTIES_FILE_PATH; + log.error(AgentConstants.LOG_APPENDER + errorMsg); + throw new AgentCoreOperationException(errorMsg); + + } catch (IOException ex) { + String errorMsg = "Error occurred whilst trying to fetch [" + propertiesFileName + "] from: " + + AgentConstants.PROPERTIES_FILE_PATH; + log.error(AgentConstants.LOG_APPENDER + errorMsg); + throw new AgentCoreOperationException(errorMsg); + } finally { + if (propertiesInputStream != null) { + try { + propertiesInputStream.close(); + } catch (IOException e) { + log.error(AgentConstants.LOG_APPENDER + + "Error occurred whilst trying to close InputStream resource used to read the '" + + propertiesFileName + "' file"); + } + } + } + return iotServerConfigs; + } + + /** + * This method constructs the URLs for each of the API Endpoints called by the device agent + * Ex: Register API, Push-Data API + * + * @throws AgentCoreOperationException if any error occurs at socket level whilst trying to + * retrieve the deviceIP of the network-interface read + * from the configs file + */ + public static void initializeServerEndPoints() { + AgentManager agentManager = AgentManager.getInstance(); + String serverSecureEndpoint = agentManager.getAgentConfigs().getHTTPS_ServerEndpoint(); + String serverUnSecureEndpoint = agentManager.getAgentConfigs().getHTTP_ServerEndpoint(); + String backEndContext = agentManager.getAgentConfigs().getControllerContext(); + + String deviceControllerAPIEndpoint = serverSecureEndpoint + backEndContext; + + String deviceEnrollmentEndpoint = + serverUnSecureEndpoint + backEndContext + AgentConstants.DEVICE_ENROLLMENT_API_EP; + agentManager.setEnrollmentEP(deviceEnrollmentEndpoint); + + String registerEndpointURL = + deviceControllerAPIEndpoint + AgentConstants.DEVICE_REGISTER_API_EP; + agentManager.setIpRegistrationEP(registerEndpointURL); + + String pushDataEndPointURL = + deviceControllerAPIEndpoint + AgentConstants.DEVICE_PUSH_TEMPERATURE_API_EP; + agentManager.setPushDataAPIEP(pushDataEndPointURL); + + log.info(AgentConstants.LOG_APPENDER + "IoT Server's Device Controller API Endpoint: " + + deviceControllerAPIEndpoint); + log.info(AgentConstants.LOG_APPENDER + "Device Enrollment EndPoint: " + + registerEndpointURL); + log.info(AgentConstants.LOG_APPENDER + "DeviceIP Registration EndPoint: " + + registerEndpointURL); + log.info(AgentConstants.LOG_APPENDER + "Push-Data API EndPoint: " + pushDataEndPointURL); + } + + + public static String prepareSecurePayLoad(String message) throws AgentCoreOperationException { + PublicKey serverPublicKey = EnrollmentManager.getInstance().getServerPublicKey(); + PrivateKey devicePrivateKey = EnrollmentManager.getInstance().getPrivateKey(); + + String encryptedMsg; + try { + encryptedMsg = CommunicationUtils.encryptMessage(message, serverPublicKey); + } catch (TransportHandlerException e) { + String errorMsg = "Error occurred whilst trying to encryptMessage: [" + message + "]"; + log.error(errorMsg); + throw new AgentCoreOperationException(errorMsg, e); + } + + String signedPayload; + try { + signedPayload = CommunicationUtils.signMessage(encryptedMsg, devicePrivateKey); + } catch (TransportHandlerException e) { + String errorMsg = "Error occurred whilst trying to sign encrypted message of: [" + message + "]"; + log.error(errorMsg); + throw new AgentCoreOperationException(errorMsg, e); + } + + JSONObject jsonPayload = new JSONObject(); + jsonPayload.put(JSON_MESSAGE_KEY, encryptedMsg); + jsonPayload.put(JSON_SIGNATURE_KEY, signedPayload); + + return jsonPayload.toString(); + } + + + public static String extractMessageFromPayload(String message) throws AgentCoreOperationException { + String actualMessage; + + PublicKey serverPublicKey = EnrollmentManager.getInstance().getServerPublicKey(); + PrivateKey devicePrivateKey = EnrollmentManager.getInstance().getPrivateKey(); + + JSONObject jsonPayload = new JSONObject(message); + Object encryptedMessage = jsonPayload.get(JSON_MESSAGE_KEY); + Object signedPayload = jsonPayload.get(JSON_SIGNATURE_KEY); + boolean verification; + + if (encryptedMessage != null && signedPayload != null) { + try { + verification = CommunicationUtils.verifySignature( + encryptedMessage.toString(), signedPayload.toString(), serverPublicKey); + } catch (TransportHandlerException e) { + String errorMsg = + "Error occurred whilst trying to verify signature on received message: [" + message + "]"; + log.error(errorMsg); + throw new AgentCoreOperationException(errorMsg, e); + } + } else { + String errorMsg = "The received message is in an INVALID format. " + + "Need to be JSON - {\"Msg\":\"\", \"Sig\":\"\"}."; + throw new AgentCoreOperationException(errorMsg); + } + + try { + if (verification) { + actualMessage = CommunicationUtils.decryptMessage(encryptedMessage.toString(), devicePrivateKey); + } else { + String errorMsg = "Could not verify payload signature. The message was not signed by a valid client"; + log.error(errorMsg); + throw new AgentCoreOperationException(errorMsg); + } + } catch (TransportHandlerException e) { + String errorMsg = "Error occurred whilst trying to decrypt received message: [" + encryptedMessage + "]"; + log.error(errorMsg); + throw new AgentCoreOperationException(errorMsg, e); + } + + return actualMessage; + } + + +} + diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/enrollment/EnrollmentManager.java b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/enrollment/EnrollmentManager.java new file mode 100644 index 0000000000..a4347cbae0 --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/enrollment/EnrollmentManager.java @@ -0,0 +1,436 @@ +/* + * 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.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.enrollment; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.bouncycastle.asn1.x500.X500Name; +import org.bouncycastle.asn1.x500.X500NameBuilder; +import org.bouncycastle.asn1.x500.style.BCStyle; +import org.bouncycastle.cert.X509v3CertificateBuilder; +import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.operator.ContentSigner; +import org.bouncycastle.operator.OperatorCreationException; +import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; +import org.bouncycastle.pkcs.PKCS10CertificationRequest; +import org.bouncycastle.pkcs.PKCS10CertificationRequestBuilder; +import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequestBuilder; +import org.jscep.client.Client; +import org.jscep.client.ClientException; +import org.jscep.client.EnrollmentResponse; +import org.jscep.client.verification.CertificateVerifier; +import org.jscep.client.verification.OptimisticCertificateVerifier; +import org.jscep.transaction.TransactionException; +import org.jscep.transport.response.Capabilities; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.core.AgentConstants; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.core.AgentManager; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.exception.AgentCoreOperationException; +import sun.security.x509.X509CertImpl; + +import java.math.BigInteger; +import java.net.MalformedURLException; +import java.net.URL; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.SecureRandom; +import java.security.Security; +import java.security.cert.CertStore; +import java.security.cert.CertStoreException; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.Date; + +/** + * This class controls the entire SCEP enrolment process of the client. It is a singleton for any single client which + * has the agent code running in it. The main functionality of this class includes generating a Private-Public Key + * Pair for the enrollment flow, creating the Certificate-Sign-Request using the generated Public-Key to send to the + * SEP server, Contacting the SCEP server to receive the Signed Certificate and requesting for the server's public + * key for encrypting the payloads. + * The provider for all Cryptographic functions used in this class are "BouncyCastle" and the Asymmetric-Key pair + * algorithm used is "RSA" with a key size of 2048. The signature algorithm used is "SHA1withRSA". + * This class also holds the "SCEPUrl" (Server Url read from the configs file), the Private-Public Keys of the + * client, Signed SCEP certificate and the server's public certificate. + */ + +//TODO: Need to save cert and keys to file after initial enrollment... +public class EnrollmentManager { + private static final Log log = LogFactory.getLog(EnrollmentManager.class); + private static EnrollmentManager enrollmentManager; + + private static final String KEY_PAIR_ALGORITHM = "RSA"; + private static final String PROVIDER = "BC"; + private static final String SIGNATURE_ALG = "SHA1withRSA"; + private static final int KEY_SIZE = 2048; + + // Seed to our PRNG. Make sure this is initialised randomly, NOT LIKE THIS + private static final byte[] SEED = ")(*&^%$#@!".getBytes(); + private static final int CERT_VALIDITY = 730; + + // URL of our SCEP server + private String SCEPUrl; + private PrivateKey privateKey; + private PublicKey publicKey; + private PublicKey serverPublicKey; + private X509Certificate SCEPCertificate; + + + /** + * Constructor of the EnrollmentManager. Initializes the SCEPUrl as read from the configuration file by the + * AgentManager. + */ + private EnrollmentManager() { + this.SCEPUrl = AgentManager.getInstance().getEnrollmentEP(); + } + + /** + * Method to return the current singleton instance of the EnrollmentManager. + * + * @return the current singleton instance if available and if not initializes a new instance and returns it. + */ + public static EnrollmentManager getInstance() { + if (enrollmentManager == null) { + enrollmentManager = new EnrollmentManager(); + } + return enrollmentManager; + } + + + /** + * Method to control the entire enrollment flow. This method calls the method to create the Private-Public Key + * Pair, calls the specific method to generate the Certificate-Sign-Request, creates a one time self signed + * certificate to present to the SCEP server with the initial CSR, calls the specific method to connect to the + * SCEP Server and to get the SCEP Certificate and also calls the method that requests the SCEP Server for its + * PublicKey for future payload encryption. + * + * @throws AgentCoreOperationException if the private method generateCertSignRequest() fails with an error or if + * there is an error creating a self-sign certificate to present to the + * server (whilst trying to get the CSR signed) + */ + public void beginEnrollmentFlow() throws AgentCoreOperationException { + Security.addProvider(new BouncyCastleProvider()); + + KeyPair keyPair = generateKeyPair(); + this.privateKey = keyPair.getPrivate(); + this.publicKey = keyPair.getPublic(); + + if (log.isDebugEnabled()) { + log.info(AgentConstants.LOG_APPENDER + "DevicePrivateKey:\n[\n" + privateKey + "\n]\n"); + log.info(AgentConstants.LOG_APPENDER + "DevicePublicKey:\n[\n" + publicKey + "\n]\n"); + } + + PKCS10CertificationRequest certSignRequest = generateCertSignRequest(); + + /** + * ----------------------------------------------------------------------------------------------- + * Generate an ephemeral self-signed certificate. This is needed to present to the CA in the SCEP request. + * In the future, add proper EKU and attributes in the request. The CA does NOT have to honour any of this. + * ----------------------------------------------------------------------------------------------- + */ + X500Name issuer = new X500Name("CN=Temporary Issuer"); + BigInteger serial = new BigInteger(32, new SecureRandom()); + Date fromDate = new Date(); + Date toDate = new Date(System.currentTimeMillis() + (CERT_VALIDITY * 86400000L)); + + // Build the self-signed cert using BC, sign it with our private key (self-signed) + X509v3CertificateBuilder certBuilder = new X509v3CertificateBuilder(issuer, serial, fromDate, toDate, + certSignRequest.getSubject(), + certSignRequest.getSubjectPublicKeyInfo()); + ContentSigner sigGen; + X509Certificate tmpCert; + + try { + sigGen = new JcaContentSignerBuilder(SIGNATURE_ALG).setProvider(PROVIDER).build(keyPair.getPrivate()); + tmpCert = new JcaX509CertificateConverter().setProvider(PROVIDER).getCertificate(certBuilder.build(sigGen)); + } catch (OperatorCreationException e) { + String errorMsg = "Error occurred whilst creating a ContentSigner for the Temp-Self-Signed Certificate."; + log.error(errorMsg); + throw new AgentCoreOperationException(errorMsg, e); + } catch (CertificateException e) { + String errorMsg = "Error occurred whilst trying to create Temp-Self-Signed Certificate."; + log.error(errorMsg); + throw new AgentCoreOperationException(errorMsg, e); + } + /** + * ----------------------------------------------------------------------------------------------- + */ + + this.SCEPCertificate = getSignedCertificateFromServer(tmpCert, certSignRequest); + this.serverPublicKey = initPublicKeyOfServer(); + + if (log.isDebugEnabled()) { + log.info(AgentConstants.LOG_APPENDER + "TemporaryCertPublicKey:\n[\n" + tmpCert.getPublicKey() + "\n]\n"); + log.info(AgentConstants.LOG_APPENDER + "ServerPublicKey:\n[\n" + serverPublicKey + "\n]\n"); + } + + } + + + /** + * This method creates the Public-Private Key pair for the current client. + * + * @return the generated KeyPair object + * @throws AgentCoreOperationException when the given Security Provider does not exist or the Algorithmn used to + * generate the key pair is invalid. + */ + private KeyPair generateKeyPair() throws AgentCoreOperationException { + + // Generate key pair + KeyPairGenerator keyPairGenerator; + try { + keyPairGenerator = KeyPairGenerator.getInstance(KEY_PAIR_ALGORITHM, PROVIDER); + keyPairGenerator.initialize(KEY_SIZE, new SecureRandom(SEED)); + } catch (NoSuchAlgorithmException e) { + String errorMsg = "Algorithm [" + KEY_PAIR_ALGORITHM + "] provided for KeyPairGenerator is invalid."; + log.error(errorMsg); + throw new AgentCoreOperationException(errorMsg, e); + } catch (NoSuchProviderException e) { + String errorMsg = "Provider [" + PROVIDER + "] provided for KeyPairGenerator does not exist."; + log.error(errorMsg); + throw new AgentCoreOperationException(errorMsg, e); + } + + return keyPairGenerator.genKeyPair(); + } + + + /** + * This method creates the PKCS10 Certificate Sign Request which is to be sent to the SCEP Server using the + * generated PublicKey of the client. The certificate parameters used here are the ones from the AgentManager + * which are the values read from the configurations file. + * + * @return the PKCS10CertificationRequest object created using the client specific configs and the generated + * PublicKey + * @throws AgentCoreOperationException if an error occurs when creating a content signer to sign the CSR. + */ + private PKCS10CertificationRequest generateCertSignRequest() throws AgentCoreOperationException { + // Build the CN for the cert that's being requested. + X500NameBuilder nameBld = new X500NameBuilder(BCStyle.INSTANCE); + nameBld.addRDN(BCStyle.CN, AgentManager.getInstance().getAgentConfigs().getServerName()); + nameBld.addRDN(BCStyle.O, AgentManager.getInstance().getAgentConfigs().getDeviceOwner()); + nameBld.addRDN(BCStyle.OU, AgentManager.getInstance().getAgentConfigs().getDeviceOwner()); + nameBld.addRDN(BCStyle.UNIQUE_IDENTIFIER, AgentManager.getInstance().getAgentConfigs().getDeviceId()); + nameBld.addRDN(BCStyle.SERIALNUMBER, AgentManager.getInstance().getAgentConfigs().getDeviceId()); + X500Name principal = nameBld.build(); + + JcaContentSignerBuilder contentSignerBuilder = new JcaContentSignerBuilder(SIGNATURE_ALG).setProvider(PROVIDER); + ContentSigner contentSigner; + + try { + contentSigner = contentSignerBuilder.build(this.privateKey); + } catch (OperatorCreationException e) { + String errorMsg = "Could not create content signer with private key."; + log.error(errorMsg); + throw new AgentCoreOperationException(errorMsg, e); + } + + // Generate the certificate signing request (csr = PKCS10) + PKCS10CertificationRequestBuilder reqBuilder = new JcaPKCS10CertificationRequestBuilder(principal, + this.publicKey); + return reqBuilder.build(contentSigner); + } + + + /** + * This method connects to the SCEP Server to fetch the signed SCEP Certificate. + * + * @param tempCert the temporary self-signed certificate of the client required for the initial CSR + * request against the SCEP Server. + * @param certSignRequest the PKCS10 Certificate-Sign-Request that is to be sent to the SCEP Server. + * @return the SCEP-Certificate for the client signed by the SCEP-Server. + * @throws AgentCoreOperationException if the SCEPUrl is invalid or if the flow of sending the CSR and getting + * the signed certificate fails or if the signed certificate cannot be + * retrieved from the reply from the server. + */ + private X509Certificate getSignedCertificateFromServer(X509Certificate tempCert, + PKCS10CertificationRequest certSignRequest) + throws AgentCoreOperationException { + + X509Certificate signedSCEPCertificate = null; + URL url; + EnrollmentResponse enrolResponse; + CertStore certStore; + + try { + // The URL where we are going to request our cert from + url = new URL(this.SCEPUrl); + + /* // This is called when we get the certificate for our CSR signed by CA + // Implement this handler to check the CA cert in prod. We can do cert pinning here + CallbackHandler cb = new CallbackHandler() { + @Override + public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { + //throw new UnsupportedOperationException("Not supported yet."); //To change body of generated + methods, choose Tools | Templates. + } + };*/ + + // Implement verification of the CA cert. VERIFY the CA + CertificateVerifier ocv = new OptimisticCertificateVerifier(); + + // Instantiate our SCEP client + Client scepClient = new Client(url, ocv); + + // Submit our cert for signing. SCEP server should allow the client to specify + // the SCEP CA to issue the request against, if there are multiple CAs + enrolResponse = scepClient.enrol(tempCert, this.privateKey, certSignRequest); + + // Verify we got what we want, and just print out the cert. + certStore = enrolResponse.getCertStore(); + + for (Certificate x509Certificate : certStore.getCertificates(null)) { + if (log.isDebugEnabled()) { + log.debug(x509Certificate.toString()); + } + signedSCEPCertificate = (X509Certificate) x509Certificate; + } + + } catch (MalformedURLException ex) { + String errorMsg = "Could not create valid URL from given SCEP URI: " + SCEPUrl; + log.error(errorMsg); + throw new AgentCoreOperationException(errorMsg, ex); + } catch (TransactionException | ClientException e) { + String errorMsg = "Enrollment process to SCEP Server at: " + SCEPUrl + " failed."; + log.error(errorMsg); + throw new AgentCoreOperationException(errorMsg, e); + } catch (CertStoreException e) { + String errorMsg = "Could not retrieve [Signed-Certificate] from the response message from SCEP-Server."; + log.error(errorMsg); + throw new AgentCoreOperationException(errorMsg, e); + } + + return signedSCEPCertificate; + } + + + /** + * Gets the Public Key of the SCEP-Server and initializes it for later use. This method contacts the SCEP Server + * and fetches its CA Cert and extracts the Public Key of the server from the received reply. + * + * @return the public key of the SCEP Server which is to be used to encrypt pyloads. + * @throws AgentCoreOperationException if the SCEPUrl is invalid or if the flow of sending the CSR and getting + * the signed certificate fails or if the signed certificate cannot be + * retrieved from the reply from the server. + */ + private PublicKey initPublicKeyOfServer() throws AgentCoreOperationException { + URL url; + CertStore certStore; + PublicKey serverCertPublicKey = null; + + try { + // The URL where we are going to request our cert from + url = new URL(this.SCEPUrl); + + /* // This is called when we get the certificate for our CSR signed by CA + // Implement this handler to check the CA cert in prod. We can do cert pinning here + CallbackHandler cb = new CallbackHandler() { + @Override + public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { + //throw new UnsupportedOperationException("Not supported yet."); //To change body of generated + methods, choose Tools | Templates. + } + };*/ + + // Implement verification of the CA cert. VERIFY the CA + CertificateVerifier ocv = new OptimisticCertificateVerifier(); + + // Instantiate our SCEP client + Client scepClient = new Client(url, ocv); + + // Get the CA capabilities. Should return SHA1withRSA for strongest hash and sig. Returns MD5. + if (log.isDebugEnabled()) { + Capabilities cap = scepClient.getCaCapabilities(); + log.debug(String.format( + "\nStrongestCipher: %s,\nStrongestMessageDigest: %s,\nStrongestSignatureAlgorithm: %s," + + "\nIsRenewalSupported: %s,\nIsRolloverSupported: %s", + cap.getStrongestCipher(), cap.getStrongestMessageDigest(), cap.getStrongestSignatureAlgorithm(), + cap.isRenewalSupported(), cap.isRolloverSupported())); + } + + certStore = scepClient.getCaCertificate(); + + for (Certificate cert : certStore.getCertificates(null)) { + if (cert instanceof X509Certificate) { + if (log.isDebugEnabled()) { + log.debug(((X509Certificate) cert).getIssuerDN().getName()); + } + + //TODO: Need to identify the correct certificate. + // I have chosen the CA cert based on its BasicConstraint criticality being set to "true" + if (((X509CertImpl) cert).getBasicConstraintsExtension().isCritical()) { + serverCertPublicKey = cert.getPublicKey(); + } + } + } + + } catch (MalformedURLException ex) { + String errorMsg = "Could not create valid URL from given SCEP URI: " + SCEPUrl; + log.error(errorMsg); + throw new AgentCoreOperationException(errorMsg, ex); + } catch (ClientException e) { + String errorMsg = "Could not retrieve [Server-Certificate] from the SCEP-Server."; + log.error(errorMsg); + throw new AgentCoreOperationException(errorMsg, e); + } catch (CertStoreException e) { + String errorMsg = "Could not retrieve [Server-Certificates] from the response message from SCEP-Server."; + log.error(errorMsg); + throw new AgentCoreOperationException(errorMsg, e); + } + + return serverCertPublicKey; + } + + + /** + * Gets the Public-Key of the client. + * @return the public key of the client. + */ + public PublicKey getPublicKey() { + return publicKey; + } + + /** + * Gets the Private-Key of the client. + * @return the private key of the client. + */ + public PrivateKey getPrivateKey() { + return privateKey; + } + + /** + * Gets the SCEP-Certificate of the client. + * @return the SCEP Certificate of the client. + */ + public X509Certificate getSCEPCertificate() { + return SCEPCertificate; + } + + /** + * Gets the Public-Key of the Server. + * @return the pubic key of the server. + */ + public PublicKey getServerPublicKey() { + return serverPublicKey; + } +} diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/exception/AgentCoreOperationException.java b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/exception/AgentCoreOperationException.java new file mode 100644 index 0000000000..fac321ef90 --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/exception/AgentCoreOperationException.java @@ -0,0 +1,57 @@ +/* + * 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.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.exception; + + +public class AgentCoreOperationException extends Exception{ + private static final long serialVersionUID = 2736466230451105710L; + + private String errorMessage; + + public String getErrorMessage() { + return errorMessage; + } + + public void setErrorMessage(String errorMessage) { + this.errorMessage = errorMessage; + } + + public AgentCoreOperationException(String msg, Exception nestedEx) { + super(msg, nestedEx); + setErrorMessage(msg); + } + + public AgentCoreOperationException(String message, Throwable cause) { + super(message, cause); + setErrorMessage(message); + } + + public AgentCoreOperationException(String msg) { + super(msg); + setErrorMessage(msg); + } + + public AgentCoreOperationException() { + super(); + } + + public AgentCoreOperationException(Throwable cause) { + super(cause); + } +} diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/transport/CommunicationUtils.java b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/transport/CommunicationUtils.java new file mode 100644 index 0000000000..bb445a3d9d --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/transport/CommunicationUtils.java @@ -0,0 +1,225 @@ +/* + * 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.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.transport; + +import org.apache.commons.codec.binary.Base64; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; +import java.nio.charset.StandardCharsets; +import java.security.InvalidKeyException; +import java.security.Key; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.Signature; +import java.security.SignatureException; + +/** + * This is a utility class which contains methods common to the communication process of a client and the server. The + * methods include encryption/decryption of payloads and signing/verification of payloads received and to be sent. + */ +public class CommunicationUtils { + private static final Log log = LogFactory.getLog(TransportUtils.class); + + // The Signature Algorithm used. + private static final String SIGNATURE_ALG = "SHA1withRSA"; + // The Encryption Algorithm and the Padding used. + private static final String CIPHER_PADDING = "RSA/ECB/PKCS1Padding"; + + + /** + * Encrypts the message with the key that's passed in. + * + * @param message the message to be encrypted. + * @param encryptionKey the key to use for the encryption of the message. + * @return the encrypted message in String format. + * @throws TransportHandlerException if an error occurs with the encryption flow which can be due to Padding + * issues, encryption key being invalid or the algorithm used is unrecognizable. + */ + public static String encryptMessage(String message, Key encryptionKey) throws TransportHandlerException { + Cipher encrypter; + byte[] cipherData; + + try { + encrypter = Cipher.getInstance(CIPHER_PADDING); + encrypter.init(Cipher.ENCRYPT_MODE, encryptionKey); + cipherData = encrypter.doFinal(message.getBytes(StandardCharsets.UTF_8)); + + } catch (NoSuchAlgorithmException e) { + String errorMsg = "Algorithm not found exception occurred for Cipher instance of [" + CIPHER_PADDING + "]"; + log.error(errorMsg); + throw new TransportHandlerException(errorMsg, e); + } catch (NoSuchPaddingException e) { + String errorMsg = "No Padding error occurred for Cipher instance of [" + CIPHER_PADDING + "]"; + log.error(errorMsg); + throw new TransportHandlerException(errorMsg, e); + } catch (InvalidKeyException e) { + String errorMsg = "InvalidKey exception occurred for encryptionKey \n[\n" + encryptionKey + "\n]\n"; + log.error(errorMsg); + throw new TransportHandlerException(errorMsg, e); + } catch (BadPaddingException e) { + String errorMsg = "Bad Padding error occurred for Cipher instance of [" + CIPHER_PADDING + "]"; + log.error(errorMsg); + throw new TransportHandlerException(errorMsg, e); + } catch (IllegalBlockSizeException e) { + String errorMsg = "Illegal blockSize error occurred for Cipher instance of [" + CIPHER_PADDING + "]"; + log.error(errorMsg); + throw new TransportHandlerException(errorMsg, e); + } + + return Base64.encodeBase64String(cipherData); + } + +///TODO:: Exception needs to change according to the common package + /** + * Signed a given message using the PrivateKey that's passes in. + * + * @param message the message to be signed. Ideally some encrypted payload. + * @param signatureKey the PrivateKey with which the message is to be signed. + * @return the Base64Encoded String of the signed payload. + * @throws TransportHandlerException if some error occurs with the signing process which may be related to the + * signature algorithm used or the key used for signing. + */ + public static String signMessage(String message, PrivateKey signatureKey) throws TransportHandlerException { + + Signature signature; + String signedEncodedString; + + try { + signature = Signature.getInstance(SIGNATURE_ALG); + signature.initSign(signatureKey); + signature.update(Base64.decodeBase64(message)); + + byte[] signatureBytes = signature.sign(); + signedEncodedString = Base64.encodeBase64String(signatureBytes); + + } catch (NoSuchAlgorithmException e) { + String errorMsg = + "Algorithm not found exception occurred for Signature instance of [" + SIGNATURE_ALG + "]"; + log.error(errorMsg); + throw new TransportHandlerException(errorMsg, e); + } catch (SignatureException e) { + String errorMsg = "Signature exception occurred for Signature instance of [" + SIGNATURE_ALG + "]"; + log.error(errorMsg); + throw new TransportHandlerException(errorMsg, e); + } catch (InvalidKeyException e) { + String errorMsg = "InvalidKey exception occurred for signatureKey \n[\n" + signatureKey + "\n]\n"; + log.error(errorMsg); + throw new TransportHandlerException(errorMsg, e); + } + + return signedEncodedString; + } + + + /** + * Verifies some signed-data against the a Public-Key to ensure that it was produced by the holder of the + * corresponding Private Key. + * + * @param data the actual payoad which was signed by some Private Key. + * @param signedData the signed data produced by signing the payload using a Private Key. + * @param verificationKey the corresponding Public Key which is an exact pair of the Private-Key with we expect + * the data to be signed by. + * @return true if the signed data verifies to be signed by the corresponding Private Key. + * @throws TransportHandlerException if some error occurs with the verification process which may be related to + * the signature algorithm used or the key used for signing. + */ + public static boolean verifySignature(String data, String signedData, PublicKey verificationKey) + throws TransportHandlerException { + + Signature signature; + boolean verified; + + try { + signature = Signature.getInstance(SIGNATURE_ALG); + signature.initVerify(verificationKey); + signature.update(Base64.decodeBase64(data)); + + verified = signature.verify(Base64.decodeBase64(signedData)); + + } catch (NoSuchAlgorithmException e) { + String errorMsg = + "Algorithm not found exception occurred for Signature instance of [" + SIGNATURE_ALG + "]"; + log.error(errorMsg); + throw new TransportHandlerException(errorMsg, e); + } catch (SignatureException e) { + String errorMsg = "Signature exception occurred for Signature instance of [" + SIGNATURE_ALG + "]"; + log.error(errorMsg); + throw new TransportHandlerException(errorMsg, e); + } catch (InvalidKeyException e) { + String errorMsg = "InvalidKey exception occurred for signatureKey \n[\n" + verificationKey + "\n]\n"; + log.error(errorMsg); + throw new TransportHandlerException(errorMsg, e); + } + + return verified; + } + + + /** + * Encrypts the message with the key that's passed in. + * + * @param encryptedMessage the encrypted message that is supposed to be decrypted. + * @param decryptKey the key to use in the decryption process. + * @return the decrypted message in String format. + * @throws TransportHandlerException if an error occurs with the encryption flow which can be due to Padding + * issues, encryption key being invalid or the algorithm used is unrecognizable. + */ + public static String decryptMessage(String encryptedMessage, Key decryptKey) throws TransportHandlerException { + + Cipher decrypter; + String decryptedMessage; + + try { + + decrypter = Cipher.getInstance(CIPHER_PADDING); + decrypter.init(Cipher.DECRYPT_MODE, decryptKey); + decryptedMessage = new String(decrypter.doFinal(Base64.decodeBase64(encryptedMessage)), + StandardCharsets.UTF_8); + + } catch (NoSuchAlgorithmException e) { + String errorMsg = "Algorithm not found exception occurred for Cipher instance of [" + CIPHER_PADDING + "]"; + log.error(errorMsg); + throw new TransportHandlerException(errorMsg, e); + } catch (NoSuchPaddingException e) { + String errorMsg = "No Padding error occurred for Cipher instance of [" + CIPHER_PADDING + "]"; + log.error(errorMsg); + throw new TransportHandlerException(errorMsg, e); + } catch (InvalidKeyException e) { + String errorMsg = "InvalidKey exception occurred for encryptionKey \n[\n" + decryptKey + "\n]\n"; + log.error(errorMsg); + throw new TransportHandlerException(errorMsg, e); + } catch (BadPaddingException e) { + String errorMsg = "Bad Padding error occurred for Cipher instance of [" + CIPHER_PADDING + "]"; + log.error(errorMsg); + throw new TransportHandlerException(errorMsg, e); + } catch (IllegalBlockSizeException e) { + String errorMsg = "Illegal blockSize error occurred for Cipher instance of [" + CIPHER_PADDING + "]"; + log.error(errorMsg); + throw new TransportHandlerException(errorMsg, e); + } + + return decryptedMessage; + } +} diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/transport/TransportHandler.java b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/transport/TransportHandler.java new file mode 100644 index 0000000000..fa45cdbb0c --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/transport/TransportHandler.java @@ -0,0 +1,47 @@ +/* + * 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.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.transport; + +/** + * This interface consists of the core functionality related to the transport between any device and the server. The + * interface is an abstraction, regardless of the underlying protocol used for the transport. Implementation of this + * interface by any class that caters a specific protocol (ex: HTTP, XMPP, MQTT, CoAP) would ideally have methods + * specific to the protocol used for communication and thees methods that implement the logic related to the devices + * using the protocol. + * + * @param a message type specific to the protocol implemented + */ +public interface TransportHandler { + int DEFAULT_TIMEOUT_INTERVAL = 5000; // millis ~ 10 sec + + void connect(); + + boolean isConnected(); + + //TODO:: Any errors needs to be thrown ahead + void processIncomingMessage(T message, String... messageParams); + + void processIncomingMessage(); + + void publishDeviceData(String... publishData); + + void publishDeviceData(); + + void disconnect(); +} diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/transport/TransportHandlerException.java b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/transport/TransportHandlerException.java new file mode 100644 index 0000000000..9ae69a998f --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/transport/TransportHandlerException.java @@ -0,0 +1,56 @@ +/* + * 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.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.transport; + +public class TransportHandlerException extends Exception { + private static final long serialVersionUID = 2736466230451105440L; + + private String errorMessage; + + public String getErrorMessage() { + return errorMessage; + } + + public void setErrorMessage(String errorMessage) { + this.errorMessage = errorMessage; + } + + public TransportHandlerException(String msg, Exception nestedEx) { + super(msg, nestedEx); + setErrorMessage(msg); + } + + public TransportHandlerException(String message, Throwable cause) { + super(message, cause); + setErrorMessage(message); + } + + public TransportHandlerException(String msg) { + super(msg); + setErrorMessage(msg); + } + + public TransportHandlerException() { + super(); + } + + public TransportHandlerException(Throwable cause) { + super(cause); + } +} diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/transport/TransportUtils.java b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/transport/TransportUtils.java new file mode 100644 index 0000000000..a5c20bbf0c --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/transport/TransportUtils.java @@ -0,0 +1,302 @@ +/* + * 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.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.transport; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.transport.TransportHandlerException; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.DatagramSocket; +import java.net.HttpURLConnection; +import java.net.InetAddress; +import java.net.MalformedURLException; +import java.net.NetworkInterface; +import java.net.ServerSocket; +import java.net.SocketException; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; +import java.util.Random; + +public class TransportUtils { + private static final Log log = LogFactory.getLog(TransportUtils.class); + + public static final int MIN_PORT_NUMBER = 9000; + public static final int MAX_PORT_NUMBER = 11000; + + /** + * Given a server endpoint as a String, this method splits it into Protocol, Host and Port + * + * @param ipString a network endpoint in the format - '://:' + * @return a map with keys "Protocol", "Host" & "Port" for the related values from the ipString + * @throws TransportHandlerException + */ + public static Map getHostAndPort(String ipString) + throws TransportHandlerException { + Map ipPortMap = new HashMap(); + String[] ipPortArray = ipString.split(":"); + + if (ipPortArray.length != 3) { + String errorMsg = + "The IP String - '" + ipString + + "' is invalid. It needs to be in format '://:'."; + log.info(errorMsg); + throw new TransportHandlerException(errorMsg); + } + + ipPortMap.put("Protocol", ipPortArray[0]); + ipPortMap.put("Host", ipPortArray[1].replace(File.separator, "")); + ipPortMap.put("Port", ipPortArray[2]); + return ipPortMap; + } + + /** + * This method validates whether a specific IP Address is of IPv4 type + * + * @param ipAddress the IP Address which needs to be validated + * @return true if it is of IPv4 type and false otherwise + */ + public static boolean validateIPv4(String ipAddress) { + try { + if (ipAddress == null || ipAddress.isEmpty()) { + return false; + } + + String[] parts = ipAddress.split("\\."); + if (parts.length != 4) { + return false; + } + + for (String s : parts) { + int i = Integer.parseInt(s); + if ((i < 0) || (i > 255)) { + return false; + } + } + return !ipAddress.endsWith("."); + + } catch (NumberFormatException nfe) { + log.warn("The IP Address: " + ipAddress + " could not " + + "be validated against IPv4-style"); + return false; + } + } + + + public static Map getInterfaceIPMap() throws TransportHandlerException { + + Map interfaceToIPMap = new HashMap(); + Enumeration networkInterfaces; + String networkInterfaceName = ""; + String ipAddress; + + try { + networkInterfaces = NetworkInterface.getNetworkInterfaces(); + } catch (SocketException exception) { + String errorMsg = + "Error encountered whilst trying to get the list of network-interfaces"; + log.error(errorMsg); + throw new TransportHandlerException(errorMsg, exception); + } + + try { + for (; networkInterfaces.hasMoreElements(); ) { + networkInterfaceName = networkInterfaces.nextElement().getName(); + + if (log.isDebugEnabled()) { + log.debug("Network Interface: " + networkInterfaceName); + log.debug("------------------------------------------"); + } + + Enumeration interfaceIPAddresses = NetworkInterface.getByName( + networkInterfaceName).getInetAddresses(); + + for (; interfaceIPAddresses.hasMoreElements(); ) { + ipAddress = interfaceIPAddresses.nextElement().getHostAddress(); + + if (log.isDebugEnabled()) { + log.debug("IP Address: " + ipAddress); + } + + if (TransportUtils.validateIPv4(ipAddress)) { + interfaceToIPMap.put(networkInterfaceName, ipAddress); + } + } + + if (log.isDebugEnabled()) { + log.debug("------------------------------------------"); + } + } + } catch (SocketException exception) { + String errorMsg = + "Error encountered whilst trying to get the IP Addresses of the network " + + "interface: " + networkInterfaceName; + log.error(errorMsg); + throw new TransportHandlerException(errorMsg, exception); + } + + return interfaceToIPMap; + } + + + /** + * Attempts to find a free port between the MIN_PORT_NUMBER(9000) and MAX_PORT_NUMBER(11000). + * Tries 'RANDOMLY picked' port numbers between this range up-until "randomAttempts" number of + * times. If still fails, then tries each port in descending order from the MAX_PORT_NUMBER + * whilst skipping already attempted ones via random selection. + * + * @param randomAttempts no of times to TEST port numbers picked randomly over the given range + * @return an available/free port + */ + public static synchronized int getAvailablePort(int randomAttempts) { + ArrayList failedPorts = new ArrayList(randomAttempts); + + Random randomNum = new Random(); + int randomPort = MAX_PORT_NUMBER; + + while (randomAttempts > 0) { + randomPort = randomNum.nextInt(MAX_PORT_NUMBER - MIN_PORT_NUMBER) + MIN_PORT_NUMBER; + + if (checkIfPortAvailable(randomPort)) { + return randomPort; + } + failedPorts.add(randomPort); + randomAttempts--; + } + + randomPort = MAX_PORT_NUMBER; + + while (true) { + if (!failedPorts.contains(randomPort) && checkIfPortAvailable(randomPort)) { + return randomPort; + } + randomPort--; + } + } + + + private static boolean checkIfPortAvailable(int port) { + ServerSocket tcpSocket = null; + DatagramSocket udpSocket = null; + + try { + tcpSocket = new ServerSocket(port); + tcpSocket.setReuseAddress(true); + + udpSocket = new DatagramSocket(port); + udpSocket.setReuseAddress(true); + return true; + } catch (IOException ex) { + // denotes the port is in use + } finally { + if (tcpSocket != null) { + try { + tcpSocket.close(); + } catch (IOException e) { + /* not to be thrown */ + } + } + + if (udpSocket != null) { + udpSocket.close(); + } + } + + return false; + } + + + /** + * This is a utility method that creates and returns a HTTP connection object. + * + * @param urlString the URL pattern to which the connection needs to be created + * @return an HTTPConnection object which cn be used to send HTTP requests + * @throws TransportHandlerException if errors occur when creating the HTTP connection with + * the given URL string + */ + public static HttpURLConnection getHttpConnection(String urlString) throws + TransportHandlerException { + URL connectionUrl; + 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 TransportHandlerException(errorMsg, e); + } catch (IOException exception) { + String errorMsg = "Error occured whilst trying to open a connection to: " + urlString; + log.error(errorMsg); + throw new TransportHandlerException(errorMsg, exception); + } + return httpConnection; + } + + /** + * This is a utility method that reads and returns the response from a HTTP connection + * + * @param httpConnection the connection from which a response is expected + * @return the response (as a string) from the given HTTP connection + * @throws TransportHandlerException if any errors occur whilst reading the response from + * the connection stream + */ + public static String readResponseFromHttpRequest(HttpURLConnection httpConnection) + throws TransportHandlerException { + BufferedReader bufferedReader; + try { + bufferedReader = new BufferedReader(new InputStreamReader( + httpConnection.getInputStream(), StandardCharsets.UTF_8)); + } catch (IOException exception) { + String errorMsg = "There is an issue with connecting the reader to the input stream at: " + + httpConnection.getURL(); + log.error(errorMsg); + throw new TransportHandlerException(errorMsg, exception); + } + + String responseLine; + StringBuilder completeResponse = new StringBuilder(); + + try { + while ((responseLine = bufferedReader.readLine()) != null) { + completeResponse.append(responseLine); + } + } catch (IOException exception) { + String errorMsg = "Error occured whilst trying read from the connection stream at: " + + httpConnection.getURL(); + log.error(errorMsg); + throw new TransportHandlerException(errorMsg, exception); + } + try { + bufferedReader.close(); + } catch (IOException exception) { + log.error("Could not succesfully close the bufferedReader to the connection at: " + httpConnection.getURL()); + } + return completeResponse.toString(); + } + +} diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/transport/http/HTTPTransportHandler.java b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/transport/http/HTTPTransportHandler.java new file mode 100644 index 0000000000..c771eb8edf --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/transport/http/HTTPTransportHandler.java @@ -0,0 +1,91 @@ +/* + * 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.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.transport.http; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.eclipse.jetty.server.Server; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.transport.TransportHandler; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.transport.TransportUtils; + +/** + * This is an abstract class that implements the "TransportHandler" interface. The interface is an abstraction for + * the core functionality with regards to device-server communication regardless of the Transport protocol. This + * specific class contains the HTTP-Transport specific implementations. The class implements utility methods for the + * case of a HTTP communication. However, this "abstract class", even-though it implements the "TransportHandler" + * interface, does not contain the logic relevant to the interface methods. The specific functionality of the + * interface methods are intended to be implemented by the concrete class that extends this abstract class and + * utilizes the HTTP specific functionality (ideally a device API writer who would like to communicate to the device + * via HTTP Protocol). + */ +public abstract class HTTPTransportHandler implements TransportHandler { + private static final Log log = LogFactory.getLog(HTTPTransportHandler.class); + + protected Server server; + protected int port; + protected int timeoutInterval; + + protected HTTPTransportHandler() { + this.port = TransportUtils.getAvailablePort(10); + this.server = new Server(port); + timeoutInterval = DEFAULT_TIMEOUT_INTERVAL; + } + + protected HTTPTransportHandler(int port) { + this.port = port; + this.server = new Server(this.port); + timeoutInterval = DEFAULT_TIMEOUT_INTERVAL; + } + + protected HTTPTransportHandler(int port, int timeoutInterval) { + this.port = port; + this.server = new Server(this.port); + this.timeoutInterval = timeoutInterval; + } + + public void setTimeoutInterval(int timeoutInterval) { + this.timeoutInterval = timeoutInterval; + } + + /** + * Checks whether the HTTP server is up and listening for incoming requests. + * + * @return true if the server is up & listening for requests, else false. + */ + public boolean isConnected() { + return server.isStarted(); + } + + + protected void incrementPort() { + this.port = this.port + 1; + server = new Server(port); + } + + /** + * Shuts-down the HTTP Server. + */ + public void closeConnection() throws Exception { + if (server != null && isConnected()) { + server.stop(); + } + } + + +} diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/transport/mqtt/MQTTTransportHandler.java b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/transport/mqtt/MQTTTransportHandler.java new file mode 100644 index 0000000000..597f50a4d7 --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/transport/mqtt/MQTTTransportHandler.java @@ -0,0 +1,360 @@ +/* + * 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.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.transport.mqtt; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken; +import org.eclipse.paho.client.mqttv3.MqttCallback; +import org.eclipse.paho.client.mqttv3.MqttClient; +import org.eclipse.paho.client.mqttv3.MqttConnectOptions; +import org.eclipse.paho.client.mqttv3.MqttException; +import org.eclipse.paho.client.mqttv3.MqttMessage; +import org.eclipse.paho.client.mqttv3.MqttSecurityException; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.transport.TransportHandler; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.transport.TransportHandlerException; + +import java.io.File; +import java.nio.charset.StandardCharsets; + +/** + * This is an abstract class that implements the "TransportHandler" interface. The interface is an abstraction for + * the core functionality with regards to device-server communication regardless of the Transport protocol. This + * specific class contains the HTTP-Transport specific implementations. The class implements utility methods for the + * case of a HTTP communication. However, this "abstract class", even-though it implements the "TransportHandler" + * interface, does not contain the logic relevant to the interface methods. The specific functionality of the + * interface methods are intended to be implemented by the concrete class that extends this abstract class and + * utilizes the HTTP specific functionality (ideally a device API writer who would like to communicate to the device + * via HTTP Protocol). + *

      + * This class contains the Device-Management specific implementation for all the MQTT functionality. This includes + * connecting to a MQTT Broker & subscribing to the appropriate MQTT-topic, action plan upon losing connection or + * successfully delivering a message to the broker and processing incoming messages. Makes use of the 'Paho-MQTT' + * library provided by Eclipse Org. + */ +public abstract class MQTTTransportHandler + implements MqttCallback, TransportHandler { + private static final Log log = LogFactory.getLog(MQTTTransportHandler.class); + + public static final int DEFAULT_MQTT_QUALITY_OF_SERVICE = 0; + + private MqttClient client; + private String clientId; + private MqttConnectOptions options; + private String clientWillTopic; + + protected String mqttBrokerEndPoint; + protected int timeoutInterval; + protected String subscribeTopic; + + /** + * Constructor for the MQTTTransportHandler which takes in the owner, type of the device + * and the MQTT Broker URL and the topic to subscribe. + * + * @param deviceOwner the owner of the device. + * @param deviceType the CDMF Device-Type of the device. + * @param mqttBrokerEndPoint the IP/URL of the MQTT broker endpoint. + * @param subscribeTopic the MQTT topic to which the client is to be subscribed + */ + protected MQTTTransportHandler(String deviceOwner, String deviceType, + String mqttBrokerEndPoint, + String subscribeTopic) { + this.clientId = deviceOwner + ":" + deviceType; + this.subscribeTopic = subscribeTopic; + this.clientWillTopic = deviceType + File.separator + "disconnection"; + this.mqttBrokerEndPoint = mqttBrokerEndPoint; + this.timeoutInterval = DEFAULT_TIMEOUT_INTERVAL; + this.initSubscriber(); + } + + /** + * Constructor for the MQTTTransportHandler which takes in the owner, type of the device + * and the MQTT Broker URL and the topic to subscribe. Additionally this constructor takes in + * the reconnection-time interval between successive attempts to connect to the broker. + * + * @param deviceOwner the owner of the device. + * @param deviceType the CDMF Device-Type of the device. + * @param mqttBrokerEndPoint the IP/URL of the MQTT broker endpoint. + * @param subscribeTopic the MQTT topic to which the client is to be subscribed + * @param intervalInMillis the time interval in MILLI-SECONDS between successive + * attempts to connect to the broker. + */ + protected MQTTTransportHandler(String deviceOwner, String deviceType, + String mqttBrokerEndPoint, String subscribeTopic, + int intervalInMillis) { + this.clientId = deviceOwner + ":" + deviceType; + this.subscribeTopic = subscribeTopic; + //TODO:: Use constant strings + this.clientWillTopic = deviceType + File.separator + "disconnection"; + this.mqttBrokerEndPoint = mqttBrokerEndPoint; + this.timeoutInterval = intervalInMillis; + this.initSubscriber(); + } + + public void setTimeoutInterval(int timeoutInterval) { + this.timeoutInterval = timeoutInterval; + } + + /** + * Initializes the MQTT-Client. Creates a client using the given MQTT-broker endpoint and the + * clientId (which is constructed by a concatenation of [deviceOwner]:[deviceType]). Also sets + * the client's options parameter with the clientWillTopic (in-case of connection failure) and + * other info. Also sets the call-back this current class. + */ + private void initSubscriber() { + try { + client = new MqttClient(this.mqttBrokerEndPoint, clientId, null); + //TODO:: Need to check for debug + log.info("MQTT subscriber was created with ClientID : " + clientId); + } catch (MqttException ex) { + //TODO:: Remove unnecessary formatting and print exception + String errorMsg = "MQTT Client Error\n" + "\tReason: " + ex.getReasonCode() + + "\n\tMessage: " + ex.getMessage() + "\n\tLocalMsg: " + + ex.getLocalizedMessage() + "\n\tCause: " + ex.getCause() + + "\n\tException: " + ex; + log.error(errorMsg); + //TODO:: Throw the error out + } + + options = new MqttConnectOptions(); + options.setCleanSession(false); + //TODO:: Use constant strings + options.setWill(clientWillTopic, "Connection-Lost".getBytes(StandardCharsets.UTF_8), 2, + true); + client.setCallback(this); + } + + /** + * Checks whether the connection to the MQTT-Broker persists. + * + * @return true if the client is connected to the MQTT-Broker, else false. + */ + @Override + public boolean isConnected() { + return client.isConnected(); + } + + + /** + * Connects to the MQTT-Broker and if successfully established connection. + * + * @throws TransportHandlerException in the event of 'Connecting to' the MQTT broker fails. + */ + protected void connectToQueue() throws TransportHandlerException { + try { + client.connect(options); + + if (log.isDebugEnabled()) { + log.debug("Subscriber connected to queue at: " + this.mqttBrokerEndPoint); + } + } catch (MqttSecurityException ex) { + String errorMsg = "MQTT Security Exception when connecting to queue\n" + "\tReason: " + + " " + + ex.getReasonCode() + "\n\tMessage: " + ex.getMessage() + + "\n\tLocalMsg: " + ex.getLocalizedMessage() + "\n\tCause: " + + ex.getCause() + "\n\tException: " + ex; + //TODO:: Compulsory log of errors and remove formatted error + if (log.isDebugEnabled()) { + log.debug(errorMsg); + } + throw new TransportHandlerException(errorMsg, ex); + + } catch (MqttException ex) { + //TODO:: Compulsory log of errors and remove formatted error + String errorMsg = "MQTT Exception when connecting to queue\n" + "\tReason: " + + ex.getReasonCode() + "\n\tMessage: " + ex.getMessage() + + "\n\tLocalMsg: " + ex.getLocalizedMessage() + "\n\tCause: " + + ex.getCause() + "\n\tException: " + ex; + if (log.isDebugEnabled()) { + log.debug(errorMsg); + } + throw new TransportHandlerException(errorMsg, ex); + } + } + + /** + * Subscribes to the MQTT-Topic specific to this MQTT Client. (The MQTT-Topic specific to the + * device is taken in as a constructor parameter of this class) . + * + * @throws TransportHandlerException in the event of 'Subscribing to' the MQTT broker + * fails. + */ + protected void subscribeToQueue() throws TransportHandlerException { + try { + //TODO:: QoS Level take it from a variable + client.subscribe(subscribeTopic, 0); + log.info("Subscriber '" + clientId + "' subscribed to topic: " + subscribeTopic); + } catch (MqttException ex) { + //TODO:: Compulsory log of errors and remove formatted error + String errorMsg = "MQTT Exception when trying to subscribe to topic: " + + subscribeTopic + "\n\tReason: " + ex.getReasonCode() + + "\n\tMessage: " + ex.getMessage() + "\n\tLocalMsg: " + + ex.getLocalizedMessage() + "\n\tCause: " + ex.getCause() + + "\n\tException: " + ex; + if (log.isDebugEnabled()) { + log.debug(errorMsg); + } + + throw new TransportHandlerException(errorMsg, ex); + } + } + + + /** + * This method is used to publish reply-messages for the control signals received. + * Invocation of this method calls its overloaded-method with a QoS equal to that of the + * default value. + * + * @param topic the topic to which the reply message is to be published. + * @param payLoad the reply-message (payload) of the MQTT publish action. + */ + protected void publishToQueue(String topic, String payLoad) + throws TransportHandlerException { + publishToQueue(topic, payLoad, DEFAULT_MQTT_QUALITY_OF_SERVICE, false); + } + + /** + * This is an overloaded method that publishes MQTT reply-messages for control signals + * received form the IoT-Server. + * + * @param topic the topic to which the reply message is to be published + * @param payLoad the reply-message (payload) of the MQTT publish action. + * @param qos the Quality-of-Service of the current publish action. + * Could be 0(At-most once), 1(At-least once) or 2(Exactly once) + */ + protected void publishToQueue(String topic, String payLoad, int qos, boolean retained) + throws TransportHandlerException { + try { + client.publish(topic, payLoad.getBytes(StandardCharsets.UTF_8), qos, retained); + if (log.isDebugEnabled()) { + log.debug("Message: " + payLoad + " to MQTT topic [" + topic + + "] published successfully"); + } + } catch (MqttException ex) { + String errorMsg = + "MQTT Client Error" + "\n\tReason: " + ex.getReasonCode() + "\n\tMessage: " + + ex.getMessage() + "\n\tLocalMsg: " + ex.getLocalizedMessage() + + "\n\tCause: " + ex.getCause() + "\n\tException: " + ex; + log.info(errorMsg); + throw new TransportHandlerException(errorMsg, ex); + } + } + + + protected void publishToQueue(String topic, MqttMessage message) + throws TransportHandlerException { + try { + client.publish(topic, message); + if (log.isDebugEnabled()) { + log.debug("Message: " + message.toString() + " to MQTT topic [" + topic + + "] published successfully"); + } + } catch (MqttException ex) { + //TODO:: Compulsory log of errors and remove formatted error + String errorMsg = + "MQTT Client Error" + "\n\tReason: " + ex.getReasonCode() + "\n\tMessage: " + + ex.getMessage() + "\n\tLocalMsg: " + ex.getLocalizedMessage() + + "\n\tCause: " + ex.getCause() + "\n\tException: " + ex; + log.info(errorMsg); + throw new TransportHandlerException(errorMsg, ex); + } + } + + + /** + * Callback method which is triggered once the MQTT client losers its connection to the broker. + * Spawns a new thread that executes necessary actions to try and reconnect to the endpoint. + * + * @param throwable a Throwable Object containing the details as to why the failure occurred. + */ + @Override + public void connectionLost(Throwable throwable) { + log.warn("Lost Connection for client: " + this.clientId + + " to " + this.mqttBrokerEndPoint + ".\nThis was due to - " + + throwable.getMessage()); + + Thread reconnectThread = new Thread() { + public void run() { + connect(); + } + }; + reconnectThread.setDaemon(true); + reconnectThread.start(); + } + + /** + * Callback method which is triggered upon receiving a MQTT Message from the broker. Spawns a + * new thread that executes any actions to be taken with the received message. + * + * @param topic the MQTT-Topic to which the received message was published to and the + * client was subscribed to. + * @param mqttMessage the actual MQTT-Message that was received from the broker. + */ + @Override + public void messageArrived(final String topic, final MqttMessage mqttMessage) { + if (log.isDebugEnabled()) { + log.info("Got an MQTT message '" + mqttMessage.toString() + "' for topic '" + topic + "'."); + } + + Thread messageProcessorThread = new Thread() { + public void run() { + processIncomingMessage(mqttMessage, topic); + } + }; + messageProcessorThread.setDaemon(true); + messageProcessorThread.start(); + } + + /** + * Callback method which gets triggered upon successful completion of a message delivery to + * the broker. + * + * @param iMqttDeliveryToken the MQTT-DeliveryToken which includes the details about the + * specific message delivery. + */ + @Override + public void deliveryComplete(IMqttDeliveryToken iMqttDeliveryToken) { + String message = ""; + try { + message = iMqttDeliveryToken.getMessage().toString(); + } catch (MqttException e) { + //TODO:: Throw errors + log.error( + "Error occurred whilst trying to read the message from the MQTT delivery " + + "token."); + } + String topic = iMqttDeliveryToken.getTopics()[0]; + String client = iMqttDeliveryToken.getClient().getClientId(); + + if (log.isDebugEnabled()) { + log.debug("Message - '" + message + "' of client [" + client + "] for the topic (" + + topic + ") was delivered successfully."); + } + } + + /** + * Closes the connection to the MQTT Broker. + */ + public void closeConnection() throws MqttException { + if (client != null && isConnected()) { + client.disconnect(); + } + } +} + diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/transport/xmpp/XMPPTransportHandler.java b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/transport/xmpp/XMPPTransportHandler.java new file mode 100644 index 0000000000..4ac76f45b6 --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/transport/xmpp/XMPPTransportHandler.java @@ -0,0 +1,366 @@ +/* + * 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.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.transport.xmpp; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.jivesoftware.smack.ConnectionConfiguration; +import org.jivesoftware.smack.PacketListener; +import org.jivesoftware.smack.SmackConfiguration; +import org.jivesoftware.smack.XMPPConnection; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smack.filter.AndFilter; +import org.jivesoftware.smack.filter.FromContainsFilter; +import org.jivesoftware.smack.filter.OrFilter; +import org.jivesoftware.smack.filter.PacketFilter; +import org.jivesoftware.smack.filter.PacketTypeFilter; +import org.jivesoftware.smack.filter.ToContainsFilter; +import org.jivesoftware.smack.packet.Message; +import org.jivesoftware.smack.packet.Packet; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.transport.TransportHandler; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.transport.TransportHandlerException; + +/** + * This is an abstract class that implements the "TransportHandler" interface. The interface is an abstraction for + * the core functionality with regards to device-server communication regardless of the Transport protocol. This + * specific class contains the HTTP-Transport specific implementations. The class implements utility methods for the + * case of a HTTP communication. However, this "abstract class", even-though it implements the "TransportHandler" + * interface, does not contain the logic relevant to the interface methods. The specific functionality of the + * interface methods are intended to be implemented by the concrete class that extends this abstract class and + * utilizes the HTTP specific functionality (ideally a device API writer who would like to communicate to the device + * via HTTP Protocol). + *

      + * This class contains the IoT-Server specific implementation for all the XMPP functionality. This includes + * connecting to a XMPP Server & Login-In using the device's/server's XMPP-Account, Setting listeners and filters on + * incoming XMPP messages and Sending XMPP replies for messages received. Makes use of the 'Smack-XMPP' library + * provided by jivesoftware/igniterealtime. + */ +public abstract class XMPPTransportHandler implements TransportHandler { + private static final Log log = LogFactory.getLog(XMPPTransportHandler.class); + + protected String server; + protected int timeoutInterval; // millis + + //TODO:: Shouldnt be hard-coded. Need to be read from configs + private static final int DEFAULT_XMPP_PORT = 5222; + private XMPPConnection connection; + private int port; + private ConnectionConfiguration config; + private PacketFilter filter; + private PacketListener listener; + + + /** + * Constructor for XMPPTransportHandler passing only the server-IP. + * + * @param server the IP of the XMPP server. + */ + protected XMPPTransportHandler(String server) { + this.server = server; + this.port = DEFAULT_XMPP_PORT; + this.timeoutInterval = DEFAULT_TIMEOUT_INTERVAL; + initXMPPClient(); + } + + /** + * Constructor for XMPPTransportHandler passing server-IP and the XMPP-port. + * + * @param server the IP of the XMPP server. + * @param port the XMPP server's port to connect to. (default - 5222) + */ + protected XMPPTransportHandler(String server, int port) { + this.server = server; + this.port = port; + this.timeoutInterval = DEFAULT_TIMEOUT_INTERVAL; + initXMPPClient(); + } + + /** + * Constructor for XMPPTransportHandler passing server-IP, the XMPP-port and the + * timeoutInterval used by listeners to the server and for reconnection schedules. + * + * @param server the IP of the XMPP server. + * @param port the XMPP server's port to connect to. (default - 5222) + * @param timeoutInterval the timeout interval to use for the connection and reconnection + */ + protected XMPPTransportHandler(String server, int port, int timeoutInterval) { + this.server = server; + this.port = port; + this.timeoutInterval = timeoutInterval; + initXMPPClient(); + } + + /** + * Sets the client's time-out-limit whilst waiting for XMPP-replies from server. + * + * @param millis the time in millis to be set as the time-out-limit whilst waiting for a + * XMPP-reply. + */ + public void setTimeoutInterval(int millis) { + this.timeoutInterval = millis; + } + + /** + * Checks whether the connection to the XMPP-Server persists. + * + * @return true if the client is connected to the XMPP-Server, else false. + */ + @Override + public boolean isConnected() { + return connection.isConnected(); + } + + /** + * Initializes the XMPP Client. Sets the time-out-limit whilst waiting for XMPP-replies from + * server. Sets the XMPP configurations to connect to the server and creates the + * XMPPConnection object used for connecting and Logging-In. + */ + private void initXMPPClient() { + log.info(String.format("Initializing connection to XMPP Server at %1$s via port " + + "%2$d.", server, port)); + SmackConfiguration.setPacketReplyTimeout(timeoutInterval); + config = new ConnectionConfiguration(server, port); +// TODO:: Need to enable SASL-Authentication appropriately + config.setSASLAuthenticationEnabled(false); + config.setSecurityMode(ConnectionConfiguration.SecurityMode.disabled); + connection = new XMPPConnection(config); + } + +//TODO:: Re-check all exception handling + + /** + * Connects to the XMPP-Server and if attempt unsuccessful, then throws exception. + * + * @throws TransportHandlerException in the event of 'Connecting to' the XMPP server fails. + */ + protected void connectToServer() throws TransportHandlerException { + try { + connection.connect(); + log.info(String.format("Connection to XMPP Server at %1$s established successfully......", server)); + + } catch (XMPPException xmppExcepion) { + String errorMsg = "Connection attempt to the XMPP Server at " + server + " via port " + port + " failed."; + log.info(errorMsg); + throw new TransportHandlerException(errorMsg, xmppExcepion); + } + } + + /** + * If successfully established connection, then tries to Log in using the device's XMPP + * Account credentials. + * + * @param username the username of the device's XMPP-Account. + * @param password the password of the device's XMPP-Account. + * @param resource the resource the resource, specific to the XMPP-Account to which the login + * is made to + * @throws TransportHandlerException in the event of 'Logging into' the XMPP server fails. + */ + protected void loginToServer(String username, String password, String resource) + throws TransportHandlerException { + if (isConnected()) { + try { + if (resource == null) { + connection.login(username, password); + log.info(String.format("Logged into XMPP Server at %1$s as user %2$s......", server, username)); + } else { + connection.login(username, password, resource); + log.info(String.format("Logged into XMPP Server at %1$s as user %2$s on resource %3$s......", + server, username, resource)); + } + } catch (XMPPException xmppException) { + String errorMsg = + "Login attempt to the XMPP Server at " + server + " with username - " + username + " failed."; + log.error(errorMsg); + throw new TransportHandlerException(errorMsg, xmppException); + } + } else { + String errorMsg = "Not connected to XMPP-Server to attempt Login. Please 'connectToServer' before Login"; + throw new TransportHandlerException(errorMsg); + } + } + + + /** + * Sets a filter for all the incoming XMPP-Messages on the Sender's JID (XMPP-Account ID). + * Also creates a listener for the incoming messages and connects the listener to the + * XMPPConnection alongside the set filter. + * + * @param senderJID the JID (XMPP-Account ID of the sender) to which the filter is to be set. + */ + protected void setFilterOnSender(String senderJID) { + filter = new AndFilter(new PacketTypeFilter(Message.class), new FromContainsFilter( + senderJID)); + listener = new PacketListener() { + @Override + public void processPacket(Packet packet) { + if (packet instanceof Message) { + final Message xmppMessage = (Message) packet; + Thread msgProcessThread = new Thread() { + public void run() { + processIncomingMessage(xmppMessage); + } + }; + msgProcessThread.setDaemon(true); + msgProcessThread.start(); + } + } + }; + + connection.addPacketListener(listener, filter); + } + + + /** + * Sets a filter for all the incoming XMPP-Messages on the Receiver's JID (XMPP-Account ID). + * Also creates a listener for the incoming messages and connects the listener to the + * XMPPConnection alongside the set filter. + * + * @param receiverJID the JID (XMPP-Account ID of the receiver) to which the filter is to be + * set. + */ + protected void setFilterOnReceiver(String receiverJID) { + filter = new AndFilter(new PacketTypeFilter(Message.class), new ToContainsFilter( + receiverJID)); + listener = new PacketListener() { + @Override + public void processPacket(Packet packet) { + if (packet instanceof Message) { + final Message xmppMessage = (Message) packet; + Thread msgProcessThread = new Thread() { + public void run() { + processIncomingMessage(xmppMessage); + } + }; + msgProcessThread.setDaemon(true); + msgProcessThread.start(); + } + } + }; + + connection.addPacketListener(listener, filter); + } + + + /** + * Sets a filter for all the incoming XMPP-Messages on the From-JID & To-JID (XMPP-Account IDs) + * passed in. Also creates a listener for the incoming messages and connects the listener to + * the XMPPConnection alongside the set filter. + * + * @param senderJID the From-JID (XMPP-Account ID) to which the filter is to be set. + * @param receiverJID the To-JID (XMPP-Account ID) to which the filter is to be set. + * @param andCondition if true: then filter is set with 'AND' operator (senderJID && + * receiverJID), + * if false: then the filter is set with 'OR' operator (senderJID | + * receiverJID) + */ + protected void setMessageFilterAndListener(String senderJID, String receiverJID, boolean + andCondition) { + PacketFilter jidFilter; + + if (andCondition) { + jidFilter = new AndFilter(new FromContainsFilter(senderJID), new ToContainsFilter( + receiverJID)); + } else { + jidFilter = new OrFilter(new FromContainsFilter(senderJID), new ToContainsFilter( + receiverJID)); + } + + filter = new AndFilter(new PacketTypeFilter(Message.class), jidFilter); + listener = new PacketListener() { + @Override + public void processPacket(Packet packet) { + if (packet instanceof Message) { + final Message xmppMessage = (Message) packet; + Thread msgProcessThread = new Thread() { + public void run() { + processIncomingMessage(xmppMessage); + } + }; + msgProcessThread.setDaemon(true); + msgProcessThread.start(); + } + } + }; + + connection.addPacketListener(listener, filter); + } + + + /** + * Sends an XMPP message. Calls the overloaded method with Subject set to "Reply-From-Device" + * + * @param JID the JID (XMPP Account ID) to which the message is to be sent to. + * @param message the XMPP-Message that is to be sent. + */ + protected void sendXMPPMessage(String JID, String message) { + sendXMPPMessage(JID, message, "XMPP-Message"); + } + + + /** + * Overloaded method to send an XMPP message. Includes the subject to be mentioned in the + * message that is sent. + * + * @param JID the JID (XMPP Account ID) to which the message is to be sent to. + * @param message the XMPP-Message that is to be sent. + * @param subject the subject that the XMPP-Message would carry. + */ + protected void sendXMPPMessage(String JID, String message, String subject) { + Message xmppMessage = new Message(); + xmppMessage.setTo(JID); + xmppMessage.setSubject(subject); + xmppMessage.setBody(message); + xmppMessage.setType(Message.Type.chat); + sendXMPPMessage(JID, xmppMessage); + } + + + /** + * Sends an XMPP message. + * + * @param JID the JID (XMPP Account ID) to which the message is to be sent to. + * @param xmppMessage the XMPP-Message that is to be sent. + */ + protected void sendXMPPMessage(String JID, Message xmppMessage) { + connection.sendPacket(xmppMessage); + if (log.isDebugEnabled()) { + log.debug("Message: '" + xmppMessage.getBody() + "' sent to XMPP JID [" + JID + + "] sent successfully."); + } + } + + + /** + * Disables default debugger provided by the XMPPConnection. + */ + protected void disableDebugger() { + connection.DEBUG_ENABLED = false; + } + + + /** + * Closes the connection to the XMPP Server. + */ + public void closeConnection() { + if (connection != null && isConnected()) { + connection.disconnect(); + } + } + +} diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/virtual/VirtualHardwareManager.java b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/virtual/VirtualHardwareManager.java new file mode 100644 index 0000000000..3b777cf75f --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/virtual/VirtualHardwareManager.java @@ -0,0 +1,213 @@ +/* + * 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.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.virtual; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.core.AgentConstants; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.core.AgentUtilOperations; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.virtual.ui.AgentUI; + +import javax.sound.midi.InvalidMidiDataException; +import javax.sound.midi.MidiSystem; +import javax.sound.midi.MidiUnavailableException; +import javax.sound.midi.Sequence; +import javax.sound.midi.Sequencer; +import javax.sound.sampled.Clip; +import javax.swing.*; +import java.io.IOException; +import java.io.InputStream; + +/** + * This class use to emulate virtual hardware functionality + */ +public class VirtualHardwareManager { + + private static final Log log = LogFactory.getLog(VirtualHardwareManager.class); + + private static VirtualHardwareManager virtualHardwareManager; + + private AgentUI agentUI; + private Sequencer sequencer = null; + + private int temperature = 30, humidity = 30; + private int temperatureMin = 20, temperatureMax = 50, humidityMin = 20, humidityMax = 50; + private int temperatureSVF = 50, humiditySVF = 50; + private boolean isTemperatureRandomized, isHumidityRandomized; + private boolean isTemperatureSmoothed, isHumiditySmoothed; + + private VirtualHardwareManager(){ + } + + public static VirtualHardwareManager getInstance(){ + if (virtualHardwareManager == null){ + virtualHardwareManager = new VirtualHardwareManager(); + } + return virtualHardwareManager; + } + + public void init(){ + try { + // Set System L&F for Device UI + UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); + } catch (UnsupportedLookAndFeelException e) { + log.error( + "'UnsupportedLookAndFeelException' error occurred whilst initializing the" + + " Agent UI."); + } catch (ClassNotFoundException e) { + log.error( + "'ClassNotFoundException' error occurred whilst initializing the Agent UI."); + } catch (InstantiationException e) { + log.error( + "'InstantiationException' error occurred whilst initializing the Agent UI."); + } catch (IllegalAccessException e) { + log.error( + "'IllegalAccessException' error occurred whilst initializing the Agent UI."); + } + java.awt.EventQueue.invokeLater(new Runnable() { + public void run() { + agentUI = new AgentUI(); + agentUI.setVisible(true); + } + }); + setAudioSequencer(); + } + + /** + * Get temperature from emulated device + * @return Temperature + */ + public int getTemperature() { + if (isTemperatureRandomized) { + temperature = getRandom(temperatureMax, temperatureMin, temperature, + isTemperatureSmoothed, temperatureSVF); + agentUI.updateTemperature(temperature); + } + return temperature; + } + + public void setTemperature(int temperature) { + this.temperature = temperature; + } + + /** + * Get humidity from emulated device + * @return Humidity + */ + public int getHumidity() { + if (isHumidityRandomized) { + humidity = getRandom(humidityMax, humidityMin, humidity, isHumiditySmoothed, + humiditySVF); + agentUI.updateHumidity(humidity); + } + return humidity; + } + + public void setHumidity(int humidity) { + this.humidity = humidity; + } + + public void setTemperatureMin(int temperatureMin) { + this.temperatureMin = temperatureMin; + } + + public void setTemperatureMax(int temperatureMax) { + this.temperatureMax = temperatureMax; + } + + public void setHumidityMin(int humidityMin) { + this.humidityMin = humidityMin; + } + + public void setHumidityMax(int humidityMax) { + this.humidityMax = humidityMax; + } + + public void setIsHumidityRandomized(boolean isHumidityRandomized) { + this.isHumidityRandomized = isHumidityRandomized; + } + + public void setIsTemperatureRandomized(boolean isTemperatureRandomized) { + this.isTemperatureRandomized = isTemperatureRandomized; + } + + public void setTemperatureSVF(int temperatureSVF) { + this.temperatureSVF = temperatureSVF; + } + + public void setHumiditySVF(int humiditySVF) { + this.humiditySVF = humiditySVF; + } + + public void setIsTemperatureSmoothed(boolean isTemperatureSmoothed) { + this.isTemperatureSmoothed = isTemperatureSmoothed; + } + + public void setIsHumiditySmoothed(boolean isHumiditySmoothed) { + this.isHumiditySmoothed = isHumiditySmoothed; + } + + public void changeAlarmStatus(boolean isOn) { + agentUI.setAlarmStatus(isOn); + + if (isOn) { + sequencer.start(); + } else { + sequencer.stop(); + } + } + + private int getRandom(int max, int min, int current, boolean isSmoothed, int svf) { + + if (isSmoothed) { + int offset = (max - min) * svf / 100; + double mx = current + offset; + max = (mx > max) ? max : (int) Math.round(mx); + + double mn = current - offset; + min = (mn < min) ? min : (int) Math.round(mn); + } + + double rnd = Math.random() * (max - min) + min; + return (int) Math.round(rnd); + + } + + private void setAudioSequencer() { + InputStream audioSrc = AgentUtilOperations.class.getResourceAsStream( + "/" + AgentConstants.AUDIO_FILE_NAME); + Sequence sequence; + + try { + sequence = MidiSystem.getSequence(audioSrc); + sequencer = MidiSystem.getSequencer(); + sequencer.open(); + sequencer.setSequence(sequence); + } catch (InvalidMidiDataException e) { + log.error("AudioReader: Error whilst setting MIDI Audio reader sequence"); + } catch (IOException e) { + log.error("AudioReader: Error whilst getting audio sequence from stream"); + } catch (MidiUnavailableException e) { + log.error("AudioReader: Error whilst openning MIDI Audio reader sequencer"); + } + + sequencer.setLoopCount(Clip.LOOP_CONTINUOUSLY); + } + +} diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/virtual/ui/AgentUI.java b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/virtual/ui/AgentUI.java new file mode 100644 index 0000000000..4a9b76c864 --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/virtual/ui/AgentUI.java @@ -0,0 +1,954 @@ +/* + * 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.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.virtual.ui; + +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.core.AgentConstants; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.core.AgentManager; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.virtual.VirtualHardwareManager; + +import javax.swing.*; +import java.awt.*; +import java.net.URI; +import java.net.URL; + +public class AgentUI extends JFrame { + + private boolean isTemperatureRandomized, isHumidityRandomized; + private boolean isTemperatureSmoothed, isHumiditySmoothed; + + private volatile boolean isAlarmOn = false; + + private JLabel picLabelBulbOn, picLabelBulbOff; + + // Variables declaration - do not modify + private JButton btnControl; + private JButton btnView; + private JCheckBox chkbxEmulate; + private JCheckBox chkbxHumidityRandom; + private JCheckBox chkbxHumiditySmooth; + private JCheckBox chkbxTemperatureRandom; + private JCheckBox chkbxTemperatureSmooth; + private JComboBox cmbInterface; + private JComboBox cmbPeriod; + private JComboBox cmbProtocol; + private JLabel jLabel1; + private JLabel jLabel10; + private JLabel jLabel11; + private JLabel jLabel12; + private JLabel jLabel2; + private JLabel jLabel20; + private JLabel jLabel23; + private JLabel jLabel24; + private JLabel jLabel25; + private JLabel jLabel3; + private JLabel jLabel4; + private JLabel jLabel5; + private JLabel jLabel6; + private JLabel jLabel7; + private JLabel jLabel8; + private JLabel jLabel9; + private JPanel jPanel1; + private JPanel jPanel2; + private JPanel jPanel3; + private JPanel jPanel4; + private JPanel jPanel6; + private JPanel jPanel7; + private JPanel jPanel8; + private JPanel jPanel9; + private JSeparator jSeparator1; + private JSeparator jSeparator5; + private JLabel lblAgentName; + private JLabel lblStatus; + private JPanel pnlBulbStatus; + private JSpinner spinnerHumidity; + private JSpinner spinnerInterval; + private JSpinner spinnerTemperature; + private JTextField txtHumidityMax; + private JTextField txtHumidityMin; + private JTextField txtHumiditySVF; + private JTextField txtTemperatureMax; + private JTextField txtTemperatureMin; + private JTextField txtTemperatureSVF; + // End of variables declaration + + private Runnable uiUpdater = new Runnable() { + @Override + public void run() { + while (true) { + EventQueue.invokeLater(new Runnable() { + @Override + public void run() { + pnlBulbStatus.removeAll(); + pnlBulbStatus.add(isAlarmOn ? picLabelBulbOn : picLabelBulbOff); + pnlBulbStatus.updateUI(); + lblStatus.setText(AgentManager.getInstance().getAgentStatus()); + if (isTemperatureRandomized) { + txtTemperatureMinActionPerformed(null); + txtTemperatureMaxActionPerformed(null); + if (isTemperatureSmoothed) { + txtTemperatureSVFActionPerformed(null); + } + } + if (isHumidityRandomized) { + txtHumidityMinActionPerformed(null); + txtHumidityMaxActionPerformed(null); + if (isHumiditySmoothed) { + txtHumiditySVFActionPerformed(null); + } + } + } + }); + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + break; + } + } + } + }; + + /** + * Creates new form AgentUI + */ + public AgentUI() { + initComponents(); + } + + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + private void initComponents() { + + lblAgentName = new JLabel(); + jLabel2 = new JLabel(); + jPanel1 = new JPanel(); + jLabel3 = new JLabel(); + pnlBulbStatus = new JPanel(); + jPanel2 = new JPanel(); + jLabel4 = new JLabel(); + chkbxTemperatureRandom = new JCheckBox(); + jSeparator1 = new JSeparator(); + jPanel7 = new JPanel(); + jLabel5 = new JLabel(); + txtTemperatureMin = new JTextField(); + jLabel6 = new JLabel(); + txtTemperatureMax = new JTextField(); + jLabel10 = new JLabel(); + txtTemperatureSVF = new JTextField(); + spinnerTemperature = new JSpinner(); + chkbxTemperatureSmooth = new JCheckBox(); + jPanel6 = new JPanel(); + jLabel20 = new JLabel(); + btnView = new JButton(); + btnControl = new JButton(); + lblStatus = new JLabel(); + jPanel8 = new JPanel(); + jLabel23 = new JLabel(); + chkbxHumidityRandom = new JCheckBox(); + jSeparator5 = new JSeparator(); + jPanel9 = new JPanel(); + jLabel24 = new JLabel(); + txtHumidityMin = new JTextField(); + jLabel25 = new JLabel(); + txtHumidityMax = new JTextField(); + txtHumiditySVF = new JTextField(); + jLabel11 = new JLabel(); + spinnerHumidity = new JSpinner(); + chkbxHumiditySmooth = new JCheckBox(); + jPanel3 = new JPanel(); + jLabel7 = new JLabel(); + spinnerInterval = new JSpinner(); + jLabel8 = new JLabel(); + jLabel9 = new JLabel(); + cmbProtocol = new JComboBox(); + jLabel12 = new JLabel(); + cmbInterface = new JComboBox(); + jPanel4 = new JPanel(); + chkbxEmulate = new JCheckBox(); + cmbPeriod = new JComboBox(); + jLabel1 = new JLabel(); + + setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); + setTitle("Fire Alarm Emulator"); + setResizable(false); + Dimension dim = Toolkit.getDefaultToolkit().getScreenSize(); + setLocation(dim.width / 2 - 650 / 2, dim.height / 2 - 440 / 2); + + lblAgentName.setFont(new Font("Cantarell", 1, 24)); // NOI18N + lblAgentName.setHorizontalAlignment(SwingConstants.LEFT); + lblAgentName.setText("Device Name: " + AgentManager.getInstance().getDeviceName()); + + jLabel2.setHorizontalAlignment(SwingConstants.CENTER); + jLabel2.setText("Copyright (c) 2015, WSO2 Inc."); + + jPanel1.setBackground(new Color(220, 220, 220)); + + jLabel3.setFont(new Font("Cantarell", 0, 18)); // NOI18N + jLabel3.setHorizontalAlignment(SwingConstants.CENTER); + jLabel3.setText("Alarm Status"); + + pnlBulbStatus.setBackground(new Color(220, 220, 220)); + + GroupLayout pnlBulbStatusLayout = new GroupLayout(pnlBulbStatus); + pnlBulbStatus.setLayout(pnlBulbStatusLayout); + pnlBulbStatusLayout.setHorizontalGroup( + pnlBulbStatusLayout.createParallelGroup(GroupLayout.Alignment.LEADING) + .addGap(0, 0, Short.MAX_VALUE) + ); + pnlBulbStatusLayout.setVerticalGroup( + pnlBulbStatusLayout.createParallelGroup(GroupLayout.Alignment.LEADING) + .addGap(0, 167, Short.MAX_VALUE) + ); + + GroupLayout jPanel1Layout = new GroupLayout(jPanel1); + jPanel1.setLayout(jPanel1Layout); + jPanel1Layout.setHorizontalGroup( + jPanel1Layout.createParallelGroup(GroupLayout.Alignment.LEADING) + .addGroup(GroupLayout.Alignment.TRAILING, jPanel1Layout.createSequentialGroup() + .addContainerGap() + .addGroup(jPanel1Layout.createParallelGroup(GroupLayout.Alignment.TRAILING) + .addComponent(pnlBulbStatus, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(jLabel3, GroupLayout.DEFAULT_SIZE, 190, Short.MAX_VALUE)) + .addContainerGap()) + ); + jPanel1Layout.setVerticalGroup( + jPanel1Layout.createParallelGroup(GroupLayout.Alignment.LEADING) + .addGroup(jPanel1Layout.createSequentialGroup() + .addContainerGap() + .addComponent(jLabel3) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addComponent(pnlBulbStatus, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addContainerGap()) + ); + + jPanel2.setBackground(new Color(220, 220, 220)); + + jLabel4.setFont(new Font("Cantarell", 0, 18)); // NOI18N + jLabel4.setHorizontalAlignment(SwingConstants.CENTER); + jLabel4.setText("Temperature"); + + chkbxTemperatureRandom.setText("Randomize Data"); + chkbxTemperatureRandom.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + chkbxTemperatureRandomActionPerformed(evt); + } + }); + + jSeparator1.setOrientation(SwingConstants.VERTICAL); + + jPanel7.setBackground(new Color(220, 220, 220)); + + jLabel5.setHorizontalAlignment(SwingConstants.LEFT); + jLabel5.setText("Min"); + + txtTemperatureMin.setHorizontalAlignment(JTextField.CENTER); + txtTemperatureMin.setText("20"); + txtTemperatureMin.setEnabled(false); + txtTemperatureMin.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + txtTemperatureMinActionPerformed(evt); + } + }); + + jLabel6.setHorizontalAlignment(SwingConstants.RIGHT); + jLabel6.setText("Max"); + + txtTemperatureMax.setHorizontalAlignment(JTextField.CENTER); + txtTemperatureMax.setText("50"); + txtTemperatureMax.setEnabled(false); + txtTemperatureMax.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + txtTemperatureMaxActionPerformed(evt); + } + }); + + jLabel10.setHorizontalAlignment(SwingConstants.RIGHT); + jLabel10.setText("SV %"); + + txtTemperatureSVF.setHorizontalAlignment(JTextField.CENTER); + txtTemperatureSVF.setText("50"); + txtTemperatureSVF.setEnabled(false); + txtTemperatureSVF.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + txtTemperatureSVFActionPerformed(evt); + } + }); + + GroupLayout jPanel7Layout = new GroupLayout(jPanel7); + jPanel7.setLayout(jPanel7Layout); + jPanel7Layout.setHorizontalGroup( + jPanel7Layout.createParallelGroup(GroupLayout.Alignment.LEADING) + .addGroup(jPanel7Layout.createSequentialGroup() + .addComponent(jLabel5) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addComponent(txtTemperatureMin, GroupLayout.PREFERRED_SIZE, 45, GroupLayout.PREFERRED_SIZE) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jLabel6) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addComponent(txtTemperatureMax, GroupLayout.PREFERRED_SIZE, 45, GroupLayout.PREFERRED_SIZE) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jLabel10) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addComponent(txtTemperatureSVF, GroupLayout.PREFERRED_SIZE, 45, GroupLayout.PREFERRED_SIZE) + .addContainerGap(GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + ); + jPanel7Layout.setVerticalGroup( + jPanel7Layout.createParallelGroup(GroupLayout.Alignment.LEADING) + .addGroup(GroupLayout.Alignment.TRAILING, jPanel7Layout.createSequentialGroup() + .addContainerGap(GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGroup(jPanel7Layout.createParallelGroup(GroupLayout.Alignment.BASELINE) + .addComponent(txtTemperatureMin, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) + .addComponent(txtTemperatureMax, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) + .addComponent(jLabel6) + .addComponent(jLabel5) + .addComponent(jLabel10) + .addComponent(txtTemperatureSVF, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)) + .addGap(35, 35, 35)) + ); + + spinnerTemperature.setFont(new Font("Cantarell", 1, 24)); // NOI18N + spinnerTemperature.setModel(new SpinnerNumberModel(30, 0, 100, 1)); + spinnerTemperature.addChangeListener(new javax.swing.event.ChangeListener() { + public void stateChanged(javax.swing.event.ChangeEvent evt) { + spinnerTemperatureStateChanged(evt); + } + }); + + chkbxTemperatureSmooth.setText("Smooth Variation"); + chkbxTemperatureSmooth.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + chkbxTemperatureSmoothActionPerformed(evt); + } + }); + + GroupLayout jPanel2Layout = new GroupLayout(jPanel2); + jPanel2.setLayout(jPanel2Layout); + jPanel2Layout.setHorizontalGroup( + jPanel2Layout.createParallelGroup(GroupLayout.Alignment.LEADING) + .addGroup(jPanel2Layout.createSequentialGroup() + .addContainerGap() + .addGroup(jPanel2Layout.createParallelGroup(GroupLayout.Alignment.LEADING, false) + .addComponent(jLabel4, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(spinnerTemperature)) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(jSeparator1, GroupLayout.PREFERRED_SIZE, 6, GroupLayout.PREFERRED_SIZE) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addGroup(jPanel2Layout.createParallelGroup(GroupLayout.Alignment.LEADING) + .addComponent(jPanel7, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) + .addGroup(jPanel2Layout.createSequentialGroup() + .addComponent(chkbxTemperatureRandom) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(chkbxTemperatureSmooth))) + .addContainerGap()) + ); + jPanel2Layout.setVerticalGroup( + jPanel2Layout.createParallelGroup(GroupLayout.Alignment.LEADING) + .addGroup(jPanel2Layout.createSequentialGroup() + .addContainerGap() + .addGroup(jPanel2Layout.createParallelGroup(GroupLayout.Alignment.LEADING) + .addComponent(jSeparator1) + .addGroup(jPanel2Layout.createSequentialGroup() + .addGroup(jPanel2Layout.createParallelGroup( + + GroupLayout.Alignment.BASELINE) + .addComponent(chkbxTemperatureRandom) + .addComponent(chkbxTemperatureSmooth)) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jPanel7, GroupLayout.PREFERRED_SIZE, 51, GroupLayout.PREFERRED_SIZE) + .addGap(0, 0, Short.MAX_VALUE)) + .addGroup(jPanel2Layout.createSequentialGroup() + .addComponent(jLabel4, GroupLayout.PREFERRED_SIZE, 23, GroupLayout.PREFERRED_SIZE) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addComponent(spinnerTemperature))) + .addContainerGap()) + ); + + jPanel6.setBackground(new Color(253, 254, 209)); + + jLabel20.setText("Connection Status:"); + jLabel20.setVerticalTextPosition(SwingConstants.TOP); + + btnView.setText("View Device Data"); + btnView.addMouseListener(new java.awt.event.MouseAdapter() { + public void mouseClicked(java.awt.event.MouseEvent evt) { + btnViewMouseClicked(evt); + } + }); + + btnControl.setText("Control Device"); + btnControl.addMouseListener(new java.awt.event.MouseAdapter() { + public void mouseClicked(java.awt.event.MouseEvent evt) { + btnControlMouseClicked(evt); + } + }); + + lblStatus.setFont(new Font("Cantarell", 1, 15)); // NOI18N + lblStatus.setText("Not Connected"); + + GroupLayout jPanel6Layout = new GroupLayout(jPanel6); + jPanel6.setLayout(jPanel6Layout); + jPanel6Layout.setHorizontalGroup( + jPanel6Layout.createParallelGroup(GroupLayout.Alignment.LEADING) + .addGroup(jPanel6Layout.createSequentialGroup() + .addContainerGap() + .addComponent(jLabel20) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addComponent(lblStatus, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addComponent(btnControl) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addComponent(btnView) + .addContainerGap()) + ); + jPanel6Layout.setVerticalGroup( + jPanel6Layout.createParallelGroup(GroupLayout.Alignment.LEADING) + .addGroup(GroupLayout.Alignment.TRAILING, jPanel6Layout.createSequentialGroup() + .addContainerGap(GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGroup(jPanel6Layout.createParallelGroup(GroupLayout.Alignment.LEADING, false) + .addComponent(jLabel20, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGroup(jPanel6Layout.createParallelGroup(GroupLayout.Alignment.BASELINE) + .addComponent(btnView, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(btnControl) + .addComponent(lblStatus))) + .addContainerGap()) + ); + + jPanel8.setBackground(new Color(220, 220, 220)); + + jLabel23.setFont(new Font("Cantarell", 0, 18)); // NOI18N + jLabel23.setHorizontalAlignment(SwingConstants.CENTER); + jLabel23.setText("Humidity"); + + chkbxHumidityRandom.setText("Randomize Data"); + chkbxHumidityRandom.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + chkbxHumidityRandomActionPerformed(evt); + } + }); + + jSeparator5.setOrientation(SwingConstants.VERTICAL); + + jPanel9.setBackground(new Color(220, 220, 220)); + + jLabel24.setHorizontalAlignment(SwingConstants.LEFT); + jLabel24.setText("Min"); + + txtHumidityMin.setHorizontalAlignment(JTextField.CENTER); + txtHumidityMin.setText("20"); + txtHumidityMin.setEnabled(false); + txtHumidityMin.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + txtHumidityMinActionPerformed(evt); + } + }); + + jLabel25.setHorizontalAlignment(SwingConstants.RIGHT); + jLabel25.setText("Max"); + + txtHumidityMax.setHorizontalAlignment(JTextField.CENTER); + txtHumidityMax.setText("50"); + txtHumidityMax.setEnabled(false); + txtHumidityMax.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + txtHumidityMaxActionPerformed(evt); + } + }); + + txtHumiditySVF.setHorizontalAlignment(JTextField.CENTER); + txtHumiditySVF.setText("50"); + txtHumiditySVF.setEnabled(false); + txtHumiditySVF.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + txtHumiditySVFActionPerformed(evt); + } + }); + + jLabel11.setHorizontalAlignment(SwingConstants.RIGHT); + jLabel11.setText("SV %"); + + GroupLayout jPanel9Layout = new GroupLayout(jPanel9); + jPanel9.setLayout(jPanel9Layout); + jPanel9Layout.setHorizontalGroup( + jPanel9Layout.createParallelGroup(GroupLayout.Alignment.LEADING) + .addGroup(jPanel9Layout.createSequentialGroup() + .addComponent(jLabel24) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addComponent(txtHumidityMin, GroupLayout.PREFERRED_SIZE, 45, GroupLayout.PREFERRED_SIZE) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jLabel25) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addComponent(txtHumidityMax, GroupLayout.PREFERRED_SIZE, 45, GroupLayout.PREFERRED_SIZE) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jLabel11) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addComponent(txtHumiditySVF, GroupLayout.PREFERRED_SIZE, 45, GroupLayout.PREFERRED_SIZE) + .addContainerGap(GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + ); + jPanel9Layout.setVerticalGroup( + jPanel9Layout.createParallelGroup(GroupLayout.Alignment.LEADING) + .addGroup(GroupLayout.Alignment.TRAILING, jPanel9Layout.createSequentialGroup() + .addContainerGap(GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGroup(jPanel9Layout.createParallelGroup(GroupLayout.Alignment.LEADING) + .addGroup(jPanel9Layout.createParallelGroup(GroupLayout.Alignment.BASELINE) + .addComponent(jLabel11) + .addComponent(txtHumiditySVF, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)) + .addGroup(jPanel9Layout.createParallelGroup(GroupLayout.Alignment.BASELINE) + .addComponent(txtHumidityMin, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) + .addComponent(txtHumidityMax, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) + .addComponent(jLabel25) + .addComponent(jLabel24))) + .addGap(35, 35, 35)) + ); + + spinnerHumidity.setFont(new Font("Cantarell", 1, 24)); // NOI18N + spinnerHumidity.setModel(new SpinnerNumberModel(30, 0, 100, 1)); + spinnerHumidity.addChangeListener(new javax.swing.event.ChangeListener() { + public void stateChanged(javax.swing.event.ChangeEvent evt) { + spinnerHumidityStateChanged(evt); + } + }); + + chkbxHumiditySmooth.setText("Smooth Variation"); + chkbxHumiditySmooth.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + chkbxHumiditySmoothActionPerformed(evt); + } + }); + + GroupLayout jPanel8Layout = new GroupLayout(jPanel8); + jPanel8.setLayout(jPanel8Layout); + jPanel8Layout.setHorizontalGroup( + jPanel8Layout.createParallelGroup(GroupLayout.Alignment.LEADING) + .addGroup(jPanel8Layout.createSequentialGroup() + .addContainerGap() + .addGroup(jPanel8Layout.createParallelGroup(GroupLayout.Alignment.LEADING) + .addComponent(jLabel23, GroupLayout.PREFERRED_SIZE, 100, GroupLayout.PREFERRED_SIZE) + .addComponent(spinnerHumidity)) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(jSeparator5, GroupLayout.PREFERRED_SIZE, 6, GroupLayout.PREFERRED_SIZE) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addGroup(jPanel8Layout.createParallelGroup(GroupLayout.Alignment.LEADING, false) + .addComponent(jPanel9, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) + .addGroup(jPanel8Layout.createSequentialGroup() + .addComponent(chkbxHumidityRandom) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(chkbxHumiditySmooth))) + .addContainerGap()) + ); + jPanel8Layout.setVerticalGroup( + jPanel8Layout.createParallelGroup(GroupLayout.Alignment.LEADING) + .addGroup(jPanel8Layout.createSequentialGroup() + .addContainerGap() + .addGroup(jPanel8Layout.createParallelGroup(GroupLayout.Alignment.LEADING) + .addComponent(jSeparator5) + .addGroup(jPanel8Layout.createSequentialGroup() + .addGroup(jPanel8Layout.createParallelGroup( + GroupLayout.Alignment.BASELINE) + .addComponent(chkbxHumidityRandom) + .addComponent(chkbxHumiditySmooth)) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jPanel9, GroupLayout.PREFERRED_SIZE, 51, GroupLayout.PREFERRED_SIZE) + .addGap(0, 1, Short.MAX_VALUE)) + .addGroup(jPanel8Layout.createSequentialGroup() + .addComponent(jLabel23, GroupLayout.PREFERRED_SIZE, 23, GroupLayout.PREFERRED_SIZE) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addComponent(spinnerHumidity))) + .addContainerGap()) + ); + + jPanel3.setBackground(new Color(207, 233, 234)); + + jLabel7.setText("Data Push Interval:"); + + spinnerInterval.setModel(new SpinnerNumberModel(Integer.valueOf(AgentManager.getInstance().getPushInterval()), Integer.valueOf(1), null, Integer.valueOf(1))); + spinnerInterval.addChangeListener(new javax.swing.event.ChangeListener() { + public void stateChanged(javax.swing.event.ChangeEvent evt) { + spinnerIntervalStateChanged(evt); + } + }); + + jLabel8.setText("Seconds"); + + jLabel9.setText("Protocol:"); + + cmbProtocol.setModel(new DefaultComboBoxModel(new String[] { "MQTT", "XMPP", "HTTP" })); + cmbProtocol.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + cmbProtocolActionPerformed(evt); + } + }); + + jLabel12.setText("Interface:"); + + cmbInterface.setModel(new DefaultComboBoxModel(new String[] { "eth0" })); + cmbInterface.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + cmbInterfaceActionPerformed(evt); + } + }); + + GroupLayout jPanel3Layout = new GroupLayout(jPanel3); + jPanel3.setLayout(jPanel3Layout); + jPanel3Layout.setHorizontalGroup( + jPanel3Layout.createParallelGroup(GroupLayout.Alignment.LEADING) + .addGroup(jPanel3Layout.createSequentialGroup() + .addContainerGap() + .addComponent(jLabel7) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addComponent(spinnerInterval, GroupLayout.PREFERRED_SIZE, 55, GroupLayout.PREFERRED_SIZE) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jLabel8) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(jLabel12) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addComponent(cmbInterface, GroupLayout.PREFERRED_SIZE, 100, GroupLayout.PREFERRED_SIZE) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jLabel9) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addComponent(cmbProtocol, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) + .addContainerGap()) + ); + jPanel3Layout.setVerticalGroup( + jPanel3Layout.createParallelGroup(GroupLayout.Alignment.LEADING) + .addGroup(GroupLayout.Alignment.TRAILING, jPanel3Layout.createSequentialGroup() + .addContainerGap(GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGroup(jPanel3Layout.createParallelGroup(GroupLayout.Alignment.LEADING) + .addGroup(jPanel3Layout.createParallelGroup(GroupLayout.Alignment.BASELINE) + .addComponent(jLabel12) + .addComponent(cmbInterface, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)) + .addGroup(jPanel3Layout.createParallelGroup(GroupLayout.Alignment.BASELINE) + .addComponent(jLabel7) + .addComponent(spinnerInterval, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) + .addComponent(jLabel8) + .addComponent(jLabel9) + .addComponent(cmbProtocol, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE))) + .addContainerGap()) + ); + + jPanel4.setBackground(new Color(169, 253, 173)); + + chkbxEmulate.setText("Emulate data"); + chkbxEmulate.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + chkbxEmulateActionPerformed(evt); + } + }); + + cmbPeriod.setModel(new DefaultComboBoxModel(new String[] { "1 hour", "1 day", "1 week", "1 month " })); + cmbPeriod.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + cmbPeriodActionPerformed(evt); + } + }); + + jLabel1.setText("Emulation Period"); + + GroupLayout jPanel4Layout = new GroupLayout(jPanel4); + jPanel4.setLayout(jPanel4Layout); + jPanel4Layout.setHorizontalGroup( + jPanel4Layout.createParallelGroup(GroupLayout.Alignment.LEADING) + .addGroup(jPanel4Layout.createSequentialGroup() + .addContainerGap() + .addComponent(chkbxEmulate) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(jLabel1) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addComponent(cmbPeriod, GroupLayout.PREFERRED_SIZE, 162, GroupLayout.PREFERRED_SIZE) + .addContainerGap()) + ); + jPanel4Layout.setVerticalGroup( + jPanel4Layout.createParallelGroup(GroupLayout.Alignment.LEADING) + .addGroup(GroupLayout.Alignment.TRAILING, jPanel4Layout.createSequentialGroup() + .addContainerGap(GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGroup(jPanel4Layout.createParallelGroup(GroupLayout.Alignment.BASELINE) + .addComponent(chkbxEmulate) + .addComponent(cmbPeriod, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) + .addComponent(jLabel1)) + .addContainerGap()) + ); + + GroupLayout layout = new GroupLayout(getContentPane()); + getContentPane().setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING) + .addComponent(lblAgentName, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(jPanel6, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGroup(layout.createSequentialGroup() + .addComponent(jPanel1, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup( + GroupLayout.Alignment.LEADING) + .addComponent(jPanel8, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(jPanel2, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))) + .addComponent(jLabel2, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(jPanel4, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(jPanel3, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addContainerGap()) + ); + layout.setVerticalGroup( + layout.createParallelGroup(GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addComponent(lblAgentName, GroupLayout.PREFERRED_SIZE, 53, GroupLayout.PREFERRED_SIZE) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jPanel6, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addComponent(jPanel2, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jPanel8, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)) + .addComponent(jPanel1, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jPanel3, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jPanel4, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jLabel2, GroupLayout.PREFERRED_SIZE, 28, GroupLayout.PREFERRED_SIZE) + .addContainerGap()) + ); + + pack(); + + chkbxTemperatureSmooth.setEnabled(false); + chkbxTemperatureSmooth.setEnabled(false); + + cmbInterface.removeAllItems(); + for (String item : AgentManager.getInstance().getInterfaceList()){ + cmbInterface.addItem(item); + } + cmbInterface.setEnabled(false); + + cmbProtocol.removeAllItems(); + for (String item : AgentManager.getInstance().getProtocolList()){ + cmbProtocol.addItem(item); + } + cmbProtocol.setSelectedItem(AgentConstants.DEFAULT_PROTOCOL); + + URL urlAlarmOn = this.getClass().getResource("/alarm-on.gif"); + ImageIcon imageIconAlarmOn = new ImageIcon(urlAlarmOn); + + URL urlAlarmOff = this.getClass().getResource("/alarm-off.gif"); + ImageIcon imageIconAlarmOff = new ImageIcon(urlAlarmOff); + + picLabelBulbOn = new JLabel(imageIconAlarmOn); + picLabelBulbOn.setSize(pnlBulbStatus.getSize()); + + picLabelBulbOff = new JLabel(imageIconAlarmOff); + picLabelBulbOff.setSize(pnlBulbStatus.getSize()); + + new Thread(uiUpdater).start(); + + AgentManager.getInstance().setDeviceReady(true); + } + + private void btnControlMouseClicked(java.awt.event.MouseEvent evt) { + Desktop desktop = Desktop.isDesktopSupported() ? Desktop.getDesktop() : null; + if (desktop != null && desktop.isSupported(Desktop.Action.BROWSE)) { + try { + URI uri = new URI(AgentManager.getInstance().getDeviceMgtControlUrl()); + desktop.browse(uri); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + private void btnViewMouseClicked(java.awt.event.MouseEvent evt) { + Desktop desktop = Desktop.isDesktopSupported() ? Desktop.getDesktop() : null; + if (desktop != null && desktop.isSupported(Desktop.Action.BROWSE)) { + try { + URI uri = new URI(AgentManager.getInstance().getDeviceMgtAnalyticUrl()); + desktop.browse(uri); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + private void chkbxTemperatureRandomActionPerformed(java.awt.event.ActionEvent evt) { + isTemperatureRandomized = chkbxTemperatureRandom.isSelected(); + VirtualHardwareManager.getInstance().setIsTemperatureRandomized(isTemperatureRandomized); + spinnerTemperature.setEnabled(!isTemperatureRandomized); + txtTemperatureMax.setEnabled(isTemperatureRandomized); + txtTemperatureMin.setEnabled(isTemperatureRandomized); + chkbxTemperatureSmooth.setEnabled(isTemperatureRandomized); + txtTemperatureSVF.setEnabled(isTemperatureRandomized && isTemperatureSmoothed); + } + + private void chkbxHumidityRandomActionPerformed(java.awt.event.ActionEvent evt) { + isHumidityRandomized = chkbxHumidityRandom.isSelected(); + VirtualHardwareManager.getInstance().setIsHumidityRandomized(isHumidityRandomized); + spinnerHumidity.setEnabled(!isHumidityRandomized); + txtHumidityMax.setEnabled(isHumidityRandomized); + txtHumidityMin.setEnabled(isHumidityRandomized); + chkbxHumiditySmooth.setEnabled(isHumidityRandomized); + txtTemperatureSVF.setEnabled(isHumidityRandomized && isHumiditySmoothed); + } + + private void spinnerTemperatureStateChanged(javax.swing.event.ChangeEvent evt) { + if (!isTemperatureRandomized) { + try { + int temperature = Integer.parseInt(spinnerTemperature.getValue().toString()); + VirtualHardwareManager.getInstance().setTemperature(temperature); + } catch (NumberFormatException e) { + JOptionPane.showMessageDialog(this, "Invalid temperature value", "Error", JOptionPane.ERROR_MESSAGE); + spinnerTemperature.setValue(VirtualHardwareManager.getInstance().getTemperature()); + } + } + } + + private void spinnerHumidityStateChanged(javax.swing.event.ChangeEvent evt) { + if (!isHumidityRandomized) { + try { + int humidity = Integer.parseInt(spinnerHumidity.getValue().toString()); + VirtualHardwareManager.getInstance().setHumidity(humidity); + } catch (NumberFormatException e) { + JOptionPane.showMessageDialog(this, "Invalid humidity value", "Error", JOptionPane.ERROR_MESSAGE); + spinnerHumidity.setValue(VirtualHardwareManager.getInstance().getHumidity()); + } + } + } + + private void txtTemperatureMinActionPerformed(java.awt.event.ActionEvent evt) { + try { + int temperature = Integer.parseInt(txtTemperatureMin.getText()); + VirtualHardwareManager.getInstance().setTemperatureMin(temperature); + } catch (NumberFormatException e) { + JOptionPane.showMessageDialog(this, "Invalid temperature value", "Error", JOptionPane.ERROR_MESSAGE); + txtTemperatureMin.setText("20"); + } + } + + private void txtTemperatureMaxActionPerformed(java.awt.event.ActionEvent evt) { + try { + int temperature = Integer.parseInt(txtTemperatureMax.getText()); + VirtualHardwareManager.getInstance().setTemperatureMax(temperature); + } catch (NumberFormatException e) { + JOptionPane.showMessageDialog(this, "Invalid temperature value", "Error", JOptionPane.ERROR_MESSAGE); + txtTemperatureMax.setText("50"); + } + } + + private void txtHumidityMinActionPerformed(java.awt.event.ActionEvent evt) { + try { + int humidity = Integer.parseInt(txtHumidityMin.getText()); + VirtualHardwareManager.getInstance().setHumidityMin(humidity); + } catch (NumberFormatException e) { + JOptionPane.showMessageDialog(this, "Invalid humidity value", "Error", JOptionPane.ERROR_MESSAGE); + txtHumidityMin.setText("20"); + } + } + + private void txtHumidityMaxActionPerformed(java.awt.event.ActionEvent evt) { + try { + int humidity = Integer.parseInt(txtHumidityMax.getText()); + VirtualHardwareManager.getInstance().setHumidityMax(humidity); + } catch (NumberFormatException e) { + JOptionPane.showMessageDialog(this, "Invalid humidity value", "Error", JOptionPane.ERROR_MESSAGE); + txtHumidityMax.setText("50"); + } + } + + private void spinnerIntervalStateChanged(javax.swing.event.ChangeEvent evt) { + try { + int interval = Integer.parseInt(spinnerInterval.getValue().toString()); + AgentManager.getInstance().setPushInterval(interval); + } catch (NumberFormatException e) { + JOptionPane.showMessageDialog(this, "Invalid time interval value", "Error", JOptionPane.ERROR_MESSAGE); + spinnerInterval.setValue(5); + } + } + + private void cmbInterfaceActionPerformed(java.awt.event.ActionEvent evt) { + AgentManager.getInstance().setInterface(cmbInterface.getSelectedIndex()); + } + + private void cmbProtocolActionPerformed(java.awt.event.ActionEvent evt) { + if (cmbProtocol.getSelectedIndex() != -1 && cmbProtocol.getItemAt( + cmbProtocol.getSelectedIndex()).equals(AgentConstants.HTTP_PROTOCOL)) { + cmbInterface.setEnabled(true); + } else { + cmbInterface.setEnabled(false); + } + + AgentManager.getInstance().setProtocol(cmbProtocol.getSelectedIndex()); + + } + + private void txtTemperatureSVFActionPerformed(java.awt.event.ActionEvent evt) { + try { + int temperatureSVF = Integer.parseInt(txtTemperatureSVF.getText()); + VirtualHardwareManager.getInstance().setTemperatureSVF(temperatureSVF); + } catch (NumberFormatException e) { + JOptionPane.showMessageDialog(this, "Invalid value", "Error", JOptionPane.ERROR_MESSAGE); + txtTemperatureSVF.setText("50"); + } + } + + private void txtHumiditySVFActionPerformed(java.awt.event.ActionEvent evt) { + try { + int humiditySVF = Integer.parseInt(txtHumiditySVF.getText()); + VirtualHardwareManager.getInstance().setHumiditySVF(humiditySVF); + } catch (NumberFormatException e) { + JOptionPane.showMessageDialog(this, "Invalid value", "Error", JOptionPane.ERROR_MESSAGE); + txtHumiditySVF.setText("50"); + } + } + + private void chkbxTemperatureSmoothActionPerformed(java.awt.event.ActionEvent evt) { + isTemperatureSmoothed = chkbxTemperatureSmooth.isSelected(); + txtTemperatureSVF.setEnabled(isTemperatureSmoothed); + VirtualHardwareManager.getInstance().setIsTemperatureSmoothed(isTemperatureSmoothed); + } + + private void chkbxHumiditySmoothActionPerformed(java.awt.event.ActionEvent evt) { + isHumiditySmoothed = chkbxHumiditySmooth.isSelected(); + txtHumiditySVF.setEnabled(isHumiditySmoothed); + VirtualHardwareManager.getInstance().setIsHumiditySmoothed(isHumiditySmoothed); + } + + private void cmbPeriodActionPerformed(java.awt.event.ActionEvent evt) { + // TODO add your handling code here: + } + + private void chkbxEmulateActionPerformed(java.awt.event.ActionEvent evt) { + // TODO add your handling code here: + } + + public void setAlarmStatus(boolean isAlarmOn) { + this.isAlarmOn = isAlarmOn; + } + + public void updateTemperature(int temperature) { + spinnerTemperature.setValue(temperature); + spinnerTemperature.updateUI(); + } + + public void updateHumidity(int humidity) { + spinnerHumidity.setValue(humidity); + spinnerHumidity.updateUI(); + } + +} diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.impl/src/main/resources/alarm-off.gif b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.impl/src/main/resources/alarm-off.gif new file mode 100644 index 0000000000..c346605ad0 Binary files /dev/null and b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.impl/src/main/resources/alarm-off.gif differ diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.impl/src/main/resources/alarm-on.gif b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.impl/src/main/resources/alarm-on.gif new file mode 100644 index 0000000000..d7c83f6aa8 Binary files /dev/null and b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.impl/src/main/resources/alarm-on.gif differ diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.impl/src/main/resources/deviceConfig.properties b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.impl/src/main/resources/deviceConfig.properties new file mode 100644 index 0000000000..930742a2aa --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.impl/src/main/resources/deviceConfig.properties @@ -0,0 +1,33 @@ +# +# 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] +server-name=WSO2IoTServer +owner=shabirmean +deviceId=t4ctwq8qfl11 +device-name=SMEAN_t4ctwq8qfl11 +controller-context=/virtual_firealarm/controller +https-ep=https://localhost:9443 +http-ep=http://localhost:9763 +apim-ep=http://192.168.67.21:8281 +mqtt-ep=tcp://192.168.67.21:1883 +xmpp-ep=http://204.232.188.215:5222 +auth-method=token +auth-token=79d68b50ae5f5a06e812889979b3453 +refresh-token=8bdda6359dddad218cff3354d5a8cb3b +network-interface=en0 +push-interval=14 diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.impl/src/main/resources/fireAlarmSound.mid b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.impl/src/main/resources/fireAlarmSound.mid new file mode 100644 index 0000000000..d1a2241b2d Binary files /dev/null and b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.impl/src/main/resources/fireAlarmSound.mid differ diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.impl/src/main/ui/build.xml b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.impl/src/main/ui/build.xml new file mode 100644 index 0000000000..bc3a404447 --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.impl/src/main/ui/build.xml @@ -0,0 +1,73 @@ + + + + + + + + + + + Builds, tests, and runs the project VirtualAgentUI. + + + diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.impl/src/main/ui/manifest.mf b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.impl/src/main/ui/manifest.mf new file mode 100644 index 0000000000..328e8e5bc3 --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.impl/src/main/ui/manifest.mf @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +X-COMMENT: Main-Class will be added automatically by build + diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.impl/src/main/ui/nbproject/build-impl.xml b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.impl/src/main/ui/nbproject/build-impl.xml new file mode 100644 index 0000000000..1124542077 --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.impl/src/main/ui/nbproject/build-impl.xml @@ -0,0 +1,1413 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must set src.dir + Must set test.src.dir + Must set build.dir + Must set dist.dir + Must set build.classes.dir + Must set dist.javadoc.dir + Must set build.test.classes.dir + Must set build.test.results.dir + Must set build.classes.excludes + Must set dist.jar + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must set javac.includes + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + No tests executed. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must set JVM to use for profiling in profiler.info.jvm + Must set profiler agent JVM arguments in profiler.info.jvmargs.agent + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must select some files in the IDE or set javac.includes + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + To run this application from the command line without Ant, try: + + java -jar "${dist.jar.resolved}" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must select one file in the IDE or set run.class + + + + Must select one file in the IDE or set run.class + + + + + + + + + + + + + + + + + + + + + + + Must select one file in the IDE or set debug.class + + + + + Must select one file in the IDE or set debug.class + + + + + Must set fix.includes + + + + + + + + + + This target only works when run from inside the NetBeans IDE. + + + + + + + + + Must select one file in the IDE or set profile.class + This target only works when run from inside the NetBeans IDE. + + + + + + + + + This target only works when run from inside the NetBeans IDE. + + + + + + + + + + + + + This target only works when run from inside the NetBeans IDE. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must select one file in the IDE or set run.class + + + + + + Must select some files in the IDE or set test.includes + + + + + Must select one file in the IDE or set run.class + + + + + Must select one file in the IDE or set applet.url + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must select some files in the IDE or set javac.includes + + + + + + + + + + + + + + + + + + + + Some tests failed; see details above. + + + + + + + + + Must select some files in the IDE or set test.includes + + + + Some tests failed; see details above. + + + + Must select some files in the IDE or set test.class + Must select some method in the IDE or set test.method + + + + Some tests failed; see details above. + + + + + Must select one file in the IDE or set test.class + + + + Must select one file in the IDE or set test.class + Must select some method in the IDE or set test.method + + + + + + + + + + + + + + Must select one file in the IDE or set applet.url + + + + + + + + + Must select one file in the IDE or set applet.url + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.impl/src/main/ui/nbproject/genfiles.properties b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.impl/src/main/ui/nbproject/genfiles.properties new file mode 100644 index 0000000000..a6df38fd89 --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.impl/src/main/ui/nbproject/genfiles.properties @@ -0,0 +1,8 @@ +build.xml.data.CRC32=e60df945 +build.xml.script.CRC32=7c331eea +build.xml.stylesheet.CRC32=8064a381@1.75.2.48 +# This file is used by a NetBeans-based IDE to track changes in generated files such as build-impl.xml. +# Do not edit this file. You may delete it but then the IDE will never regenerate such files for you. +nbproject/build-impl.xml.data.CRC32=e60df945 +nbproject/build-impl.xml.script.CRC32=4fa004f7 +nbproject/build-impl.xml.stylesheet.CRC32=876e7a8f@1.75.2.48 diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.impl/src/main/ui/nbproject/private/private.properties b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.impl/src/main/ui/nbproject/private/private.properties new file mode 100644 index 0000000000..e59ac1df68 --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.impl/src/main/ui/nbproject/private/private.properties @@ -0,0 +1,2 @@ +compile.on.save=true +user.properties.file=/home/charitha/.netbeans/8.0.2/build.properties diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.impl/src/main/ui/nbproject/private/private.xml b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.impl/src/main/ui/nbproject/private/private.xml new file mode 100644 index 0000000000..2f9a6910bd --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.impl/src/main/ui/nbproject/private/private.xml @@ -0,0 +1,9 @@ + + + + + + file:/home/charitha/git/IoT/iot-server-agents/FireAlarmVirtualAgent/src/main/ui/src/org/wso2/carbon/device/mgt/iot/agent/virtual/ui/AgentUI.java + + + diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.impl/src/main/ui/nbproject/project.properties b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.impl/src/main/ui/nbproject/project.properties new file mode 100644 index 0000000000..fb798f6142 --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.impl/src/main/ui/nbproject/project.properties @@ -0,0 +1,73 @@ +annotation.processing.enabled=true +annotation.processing.enabled.in.editor=false +annotation.processing.processor.options= +annotation.processing.processors.list= +annotation.processing.run.all.processors=true +annotation.processing.source.output=${build.generated.sources.dir}/ap-source-output +build.classes.dir=${build.dir}/classes +build.classes.excludes=**/*.java,**/*.form +# This directory is removed when the project is cleaned: +build.dir=build +build.generated.dir=${build.dir}/generated +build.generated.sources.dir=${build.dir}/generated-sources +# Only compile against the classpath explicitly listed here: +build.sysclasspath=ignore +build.test.classes.dir=${build.dir}/test/classes +build.test.results.dir=${build.dir}/test/results +# Uncomment to specify the preferred debugger connection transport: +#debug.transport=dt_socket +debug.classpath=\ + ${run.classpath} +debug.test.classpath=\ + ${run.test.classpath} +# Files in build.classes.dir which should be excluded from distribution jar +dist.archive.excludes= +# This directory is removed when the project is cleaned: +dist.dir=dist +dist.jar=${dist.dir}/VirtualAgentUI.jar +dist.javadoc.dir=${dist.dir}/javadoc +excludes= +includes=** +jar.compress=false +javac.classpath= +# Space-separated list of extra javac options +javac.compilerargs= +javac.deprecation=false +javac.processorpath=\ + ${javac.classpath} +javac.source=1.8 +javac.target=1.8 +javac.test.classpath=\ + ${javac.classpath}:\ + ${build.classes.dir} +javac.test.processorpath=\ + ${javac.test.classpath} +javadoc.additionalparam= +javadoc.author=false +javadoc.encoding=${source.encoding} +javadoc.noindex=false +javadoc.nonavbar=false +javadoc.notree=false +javadoc.private=false +javadoc.splitindex=true +javadoc.use=true +javadoc.version=false +javadoc.windowtitle= +main.class=org.wso2.carbon.device.mgt.iot.agent.virtual.VirtualAgentUI +manifest.file=manifest.mf +meta.inf.dir=${src.dir}/META-INF +mkdist.disabled=false +platform.active=default_platform +run.classpath=\ + ${javac.classpath}:\ + ${build.classes.dir} +# Space-separated list of JVM arguments used when running the project. +# You may also define separate properties like run-sys-prop.name=value instead of -Dname=value. +# To set system properties for unit tests define test-sys-prop.name=value: +run.jvmargs= +run.test.classpath=\ + ${javac.test.classpath}:\ + ${build.test.classes.dir} +source.encoding=UTF-8 +src.dir=src +test.src.dir=test diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.impl/src/main/ui/nbproject/project.xml b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.impl/src/main/ui/nbproject/project.xml new file mode 100644 index 0000000000..438f1e6a38 --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.impl/src/main/ui/nbproject/project.xml @@ -0,0 +1,15 @@ + + + org.netbeans.modules.java.j2seproject + + + VirtualAgentUI + + + + + + + + + diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.impl/src/main/ui/src/bulb-on.jpg b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.impl/src/main/ui/src/bulb-on.jpg new file mode 100644 index 0000000000..51d40cd834 Binary files /dev/null and b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.impl/src/main/ui/src/bulb-on.jpg differ diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.impl/src/main/ui/src/org/wso2/carbon/device/mgt/iot/agent/virtual/VirtualAgentUI.java b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.impl/src/main/ui/src/org/wso2/carbon/device/mgt/iot/agent/virtual/VirtualAgentUI.java new file mode 100644 index 0000000000..a2aa2116a5 --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.impl/src/main/ui/src/org/wso2/carbon/device/mgt/iot/agent/virtual/VirtualAgentUI.java @@ -0,0 +1,37 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package org.wso2.carbon.device.mgt.iot.agent.virtual; + +import javax.swing.UIManager; +import javax.swing.UnsupportedLookAndFeelException; +import org.wso2.carbon.device.mgt.iot.agent.virtual.ui.AgentUI; + +/** + * + * @author charitha + */ +public class VirtualAgentUI { + + /** + * @param args the command line arguments + */ + public static void main(String[] args) { + try { + // Set System L&F + UIManager.setLookAndFeel( + UIManager.getSystemLookAndFeelClassName()); + } catch (UnsupportedLookAndFeelException | ClassNotFoundException | InstantiationException | IllegalAccessException e) { + // handle exception + } + + java.awt.EventQueue.invokeLater(new Runnable() { + public void run() { + new AgentUI().setVisible(true); + } + }); + } + +} diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.impl/src/main/ui/src/org/wso2/carbon/device/mgt/iot/agent/virtual/ui/AgentUI.form b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.impl/src/main/ui/src/org/wso2/carbon/device/mgt/iot/agent/virtual/ui/AgentUI.form new file mode 100644 index 0000000000..23b70e036e --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.impl/src/main/ui/src/org/wso2/carbon/device/mgt/iot/agent/virtual/ui/AgentUI.form @@ -0,0 +1,803 @@ + + +

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.impl/src/main/ui/src/org/wso2/carbon/device/mgt/iot/agent/virtual/ui/AgentUI.java b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.impl/src/main/ui/src/org/wso2/carbon/device/mgt/iot/agent/virtual/ui/AgentUI.java new file mode 100644 index 0000000000..470a7d8fe9 --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.impl/src/main/ui/src/org/wso2/carbon/device/mgt/iot/agent/virtual/ui/AgentUI.java @@ -0,0 +1,744 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package org.wso2.carbon.device.mgt.iot.agent.virtual.ui; + +/** + * + * @author charitha + */ +public class AgentUI extends javax.swing.JFrame { + + /** + * Creates new form AgentUI + */ + public AgentUI() { + initComponents(); + } + + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + + lblAgentName = new javax.swing.JLabel(); + jLabel2 = new javax.swing.JLabel(); + jPanel1 = new javax.swing.JPanel(); + jLabel3 = new javax.swing.JLabel(); + pnlBulbStatus = new javax.swing.JPanel(); + jPanel2 = new javax.swing.JPanel(); + jLabel4 = new javax.swing.JLabel(); + chkbxTemperatureRandom = new javax.swing.JCheckBox(); + jSeparator1 = new javax.swing.JSeparator(); + jPanel7 = new javax.swing.JPanel(); + jLabel5 = new javax.swing.JLabel(); + txtTemperatureMin = new javax.swing.JTextField(); + jLabel6 = new javax.swing.JLabel(); + txtTemperatureMax = new javax.swing.JTextField(); + jLabel10 = new javax.swing.JLabel(); + txtTemperatureSVF = new javax.swing.JTextField(); + spinnerTemperature = new javax.swing.JSpinner(); + chkbxTemperatureSmooth = new javax.swing.JCheckBox(); + jPanel6 = new javax.swing.JPanel(); + jLabel20 = new javax.swing.JLabel(); + btnView = new javax.swing.JButton(); + btnControl = new javax.swing.JButton(); + lblStatus = new javax.swing.JLabel(); + jPanel8 = new javax.swing.JPanel(); + jLabel23 = new javax.swing.JLabel(); + chkbxHumidityRandom = new javax.swing.JCheckBox(); + jSeparator5 = new javax.swing.JSeparator(); + jPanel9 = new javax.swing.JPanel(); + jLabel24 = new javax.swing.JLabel(); + txtHumidityMin = new javax.swing.JTextField(); + jLabel25 = new javax.swing.JLabel(); + txtHumidityMax = new javax.swing.JTextField(); + txtHumiditySVF = new javax.swing.JTextField(); + jLabel11 = new javax.swing.JLabel(); + spinnerHumidity = new javax.swing.JSpinner(); + chkbxHumiditySmooth = new javax.swing.JCheckBox(); + jPanel3 = new javax.swing.JPanel(); + jLabel7 = new javax.swing.JLabel(); + spinnerInterval = new javax.swing.JSpinner(); + jLabel8 = new javax.swing.JLabel(); + jLabel9 = new javax.swing.JLabel(); + cmbProtocol = new javax.swing.JComboBox(); + jLabel12 = new javax.swing.JLabel(); + cmbInterface = new javax.swing.JComboBox(); + jPanel4 = new javax.swing.JPanel(); + chkbxEmulate = new javax.swing.JCheckBox(); + cmbPeriod = new javax.swing.JComboBox(); + jLabel1 = new javax.swing.JLabel(); + + setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE); + setTitle("Fire Alarm Emulator"); + setResizable(false); + + lblAgentName.setFont(new java.awt.Font("Cantarell", 1, 24)); // NOI18N + lblAgentName.setHorizontalAlignment(javax.swing.SwingConstants.LEFT); + lblAgentName.setText("Device Name: WSO2 IoT Virtual Agent"); + + jLabel2.setHorizontalAlignment(javax.swing.SwingConstants.CENTER); + jLabel2.setText("Copyright (c) 2015, WSO2 Inc."); + + jPanel1.setBackground(new java.awt.Color(220, 220, 220)); + + jLabel3.setFont(new java.awt.Font("Cantarell", 0, 18)); // NOI18N + jLabel3.setHorizontalAlignment(javax.swing.SwingConstants.CENTER); + jLabel3.setText("Bulb Status"); + + pnlBulbStatus.setBackground(new java.awt.Color(220, 220, 220)); + + javax.swing.GroupLayout pnlBulbStatusLayout = new javax.swing.GroupLayout(pnlBulbStatus); + pnlBulbStatus.setLayout(pnlBulbStatusLayout); + pnlBulbStatusLayout.setHorizontalGroup( + pnlBulbStatusLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGap(0, 0, Short.MAX_VALUE) + ); + pnlBulbStatusLayout.setVerticalGroup( + pnlBulbStatusLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGap(0, 167, Short.MAX_VALUE) + ); + + javax.swing.GroupLayout jPanel1Layout = new javax.swing.GroupLayout(jPanel1); + jPanel1.setLayout(jPanel1Layout); + jPanel1Layout.setHorizontalGroup( + jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, jPanel1Layout.createSequentialGroup() + .addContainerGap() + .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) + .addComponent(pnlBulbStatus, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(jLabel3, javax.swing.GroupLayout.DEFAULT_SIZE, 190, Short.MAX_VALUE)) + .addContainerGap()) + ); + jPanel1Layout.setVerticalGroup( + jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel1Layout.createSequentialGroup() + .addContainerGap() + .addComponent(jLabel3) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(pnlBulbStatus, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addContainerGap()) + ); + + jPanel2.setBackground(new java.awt.Color(220, 220, 220)); + + jLabel4.setFont(new java.awt.Font("Cantarell", 0, 18)); // NOI18N + jLabel4.setHorizontalAlignment(javax.swing.SwingConstants.CENTER); + jLabel4.setText("Temperature"); + + chkbxTemperatureRandom.setText("Randomize Data"); + chkbxTemperatureRandom.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + chkbxTemperatureRandomActionPerformed(evt); + } + }); + + jSeparator1.setOrientation(javax.swing.SwingConstants.VERTICAL); + + jPanel7.setBackground(new java.awt.Color(220, 220, 220)); + + jLabel5.setHorizontalAlignment(javax.swing.SwingConstants.LEFT); + jLabel5.setText("Min"); + + txtTemperatureMin.setHorizontalAlignment(javax.swing.JTextField.CENTER); + txtTemperatureMin.setText("20"); + txtTemperatureMin.setEnabled(false); + txtTemperatureMin.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + txtTemperatureMinActionPerformed(evt); + } + }); + + jLabel6.setHorizontalAlignment(javax.swing.SwingConstants.RIGHT); + jLabel6.setText("Max"); + + txtTemperatureMax.setHorizontalAlignment(javax.swing.JTextField.CENTER); + txtTemperatureMax.setText("50"); + txtTemperatureMax.setEnabled(false); + txtTemperatureMax.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + txtTemperatureMaxActionPerformed(evt); + } + }); + + jLabel10.setHorizontalAlignment(javax.swing.SwingConstants.RIGHT); + jLabel10.setText("SV %"); + + txtTemperatureSVF.setHorizontalAlignment(javax.swing.JTextField.CENTER); + txtTemperatureSVF.setText("50"); + txtTemperatureSVF.setEnabled(false); + txtTemperatureSVF.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + txtTemperatureSVFActionPerformed(evt); + } + }); + + javax.swing.GroupLayout jPanel7Layout = new javax.swing.GroupLayout(jPanel7); + jPanel7.setLayout(jPanel7Layout); + jPanel7Layout.setHorizontalGroup( + jPanel7Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel7Layout.createSequentialGroup() + .addComponent(jLabel5) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(txtTemperatureMin, javax.swing.GroupLayout.PREFERRED_SIZE, 45, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jLabel6) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(txtTemperatureMax, javax.swing.GroupLayout.PREFERRED_SIZE, 45, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jLabel10) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(txtTemperatureSVF, javax.swing.GroupLayout.PREFERRED_SIZE, 45, javax.swing.GroupLayout.PREFERRED_SIZE) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + ); + jPanel7Layout.setVerticalGroup( + jPanel7Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, jPanel7Layout.createSequentialGroup() + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGroup(jPanel7Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(txtTemperatureMin, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(txtTemperatureMax, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(jLabel6) + .addComponent(jLabel5) + .addComponent(jLabel10) + .addComponent(txtTemperatureSVF, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addGap(35, 35, 35)) + ); + + spinnerTemperature.setFont(new java.awt.Font("Cantarell", 1, 24)); // NOI18N + spinnerTemperature.setModel(new javax.swing.SpinnerNumberModel(30, 0, 100, 1)); + spinnerTemperature.addChangeListener(new javax.swing.event.ChangeListener() { + public void stateChanged(javax.swing.event.ChangeEvent evt) { + spinnerTemperatureStateChanged(evt); + } + }); + + chkbxTemperatureSmooth.setText("Smooth Variation"); + chkbxTemperatureSmooth.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + chkbxTemperatureSmoothActionPerformed(evt); + } + }); + + javax.swing.GroupLayout jPanel2Layout = new javax.swing.GroupLayout(jPanel2); + jPanel2.setLayout(jPanel2Layout); + jPanel2Layout.setHorizontalGroup( + jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel2Layout.createSequentialGroup() + .addContainerGap() + .addGroup(jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) + .addComponent(jLabel4, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(spinnerTemperature)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(jSeparator1, javax.swing.GroupLayout.PREFERRED_SIZE, 6, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(jPanel7, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGroup(jPanel2Layout.createSequentialGroup() + .addComponent(chkbxTemperatureRandom) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(chkbxTemperatureSmooth))) + .addContainerGap()) + ); + jPanel2Layout.setVerticalGroup( + jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel2Layout.createSequentialGroup() + .addContainerGap() + .addGroup(jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(jSeparator1) + .addGroup(jPanel2Layout.createSequentialGroup() + .addGroup(jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(chkbxTemperatureRandom) + .addComponent(chkbxTemperatureSmooth)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jPanel7, javax.swing.GroupLayout.PREFERRED_SIZE, 51, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(0, 0, Short.MAX_VALUE)) + .addGroup(jPanel2Layout.createSequentialGroup() + .addComponent(jLabel4, javax.swing.GroupLayout.PREFERRED_SIZE, 23, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(spinnerTemperature))) + .addContainerGap()) + ); + + jPanel6.setBackground(new java.awt.Color(253, 254, 209)); + + jLabel20.setText("Connection Status:"); + jLabel20.setVerticalTextPosition(javax.swing.SwingConstants.TOP); + + btnView.setText("View Device Data"); + btnView.addMouseListener(new java.awt.event.MouseAdapter() { + public void mouseClicked(java.awt.event.MouseEvent evt) { + btnViewMouseClicked(evt); + } + }); + + btnControl.setText("Control Device"); + btnControl.addMouseListener(new java.awt.event.MouseAdapter() { + public void mouseClicked(java.awt.event.MouseEvent evt) { + btnControlMouseClicked(evt); + } + }); + + lblStatus.setFont(new java.awt.Font("Cantarell", 1, 15)); // NOI18N + lblStatus.setText("Not Connected"); + + javax.swing.GroupLayout jPanel6Layout = new javax.swing.GroupLayout(jPanel6); + jPanel6.setLayout(jPanel6Layout); + jPanel6Layout.setHorizontalGroup( + jPanel6Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel6Layout.createSequentialGroup() + .addContainerGap() + .addComponent(jLabel20) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(lblStatus, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(btnControl) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(btnView) + .addContainerGap()) + ); + jPanel6Layout.setVerticalGroup( + jPanel6Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, jPanel6Layout.createSequentialGroup() + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGroup(jPanel6Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) + .addComponent(jLabel20, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGroup(jPanel6Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(btnView, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(btnControl) + .addComponent(lblStatus))) + .addContainerGap()) + ); + + jPanel8.setBackground(new java.awt.Color(220, 220, 220)); + + jLabel23.setFont(new java.awt.Font("Cantarell", 0, 18)); // NOI18N + jLabel23.setHorizontalAlignment(javax.swing.SwingConstants.CENTER); + jLabel23.setText("Humidity"); + + chkbxHumidityRandom.setText("Randomize Data"); + chkbxHumidityRandom.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + chkbxHumidityRandomActionPerformed(evt); + } + }); + + jSeparator5.setOrientation(javax.swing.SwingConstants.VERTICAL); + + jPanel9.setBackground(new java.awt.Color(220, 220, 220)); + + jLabel24.setHorizontalAlignment(javax.swing.SwingConstants.LEFT); + jLabel24.setText("Min"); + + txtHumidityMin.setHorizontalAlignment(javax.swing.JTextField.CENTER); + txtHumidityMin.setText("20"); + txtHumidityMin.setEnabled(false); + txtHumidityMin.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + txtHumidityMinActionPerformed(evt); + } + }); + + jLabel25.setHorizontalAlignment(javax.swing.SwingConstants.RIGHT); + jLabel25.setText("Max"); + + txtHumidityMax.setHorizontalAlignment(javax.swing.JTextField.CENTER); + txtHumidityMax.setText("50"); + txtHumidityMax.setEnabled(false); + txtHumidityMax.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + txtHumidityMaxActionPerformed(evt); + } + }); + + txtHumiditySVF.setHorizontalAlignment(javax.swing.JTextField.CENTER); + txtHumiditySVF.setText("50"); + txtHumiditySVF.setEnabled(false); + txtHumiditySVF.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + txtHumiditySVFActionPerformed(evt); + } + }); + + jLabel11.setHorizontalAlignment(javax.swing.SwingConstants.RIGHT); + jLabel11.setText("SV %"); + + javax.swing.GroupLayout jPanel9Layout = new javax.swing.GroupLayout(jPanel9); + jPanel9.setLayout(jPanel9Layout); + jPanel9Layout.setHorizontalGroup( + jPanel9Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel9Layout.createSequentialGroup() + .addComponent(jLabel24) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(txtHumidityMin, javax.swing.GroupLayout.PREFERRED_SIZE, 45, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jLabel25) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(txtHumidityMax, javax.swing.GroupLayout.PREFERRED_SIZE, 45, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jLabel11) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(txtHumiditySVF, javax.swing.GroupLayout.PREFERRED_SIZE, 45, javax.swing.GroupLayout.PREFERRED_SIZE) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + ); + jPanel9Layout.setVerticalGroup( + jPanel9Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, jPanel9Layout.createSequentialGroup() + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGroup(jPanel9Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel9Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(jLabel11) + .addComponent(txtHumiditySVF, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addGroup(jPanel9Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(txtHumidityMin, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(txtHumidityMax, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(jLabel25) + .addComponent(jLabel24))) + .addGap(35, 35, 35)) + ); + + spinnerHumidity.setFont(new java.awt.Font("Cantarell", 1, 24)); // NOI18N + spinnerHumidity.setModel(new javax.swing.SpinnerNumberModel(30, 0, 100, 1)); + spinnerHumidity.addChangeListener(new javax.swing.event.ChangeListener() { + public void stateChanged(javax.swing.event.ChangeEvent evt) { + spinnerHumidityStateChanged(evt); + } + }); + + chkbxHumiditySmooth.setText("Smooth Variation"); + chkbxHumiditySmooth.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + chkbxHumiditySmoothActionPerformed(evt); + } + }); + + javax.swing.GroupLayout jPanel8Layout = new javax.swing.GroupLayout(jPanel8); + jPanel8.setLayout(jPanel8Layout); + jPanel8Layout.setHorizontalGroup( + jPanel8Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel8Layout.createSequentialGroup() + .addContainerGap() + .addGroup(jPanel8Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(jLabel23, javax.swing.GroupLayout.PREFERRED_SIZE, 100, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(spinnerHumidity)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(jSeparator5, javax.swing.GroupLayout.PREFERRED_SIZE, 6, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(jPanel8Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) + .addComponent(jPanel9, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGroup(jPanel8Layout.createSequentialGroup() + .addComponent(chkbxHumidityRandom) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(chkbxHumiditySmooth))) + .addContainerGap()) + ); + jPanel8Layout.setVerticalGroup( + jPanel8Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel8Layout.createSequentialGroup() + .addContainerGap() + .addGroup(jPanel8Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(jSeparator5) + .addGroup(jPanel8Layout.createSequentialGroup() + .addGroup(jPanel8Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(chkbxHumidityRandom) + .addComponent(chkbxHumiditySmooth)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jPanel9, javax.swing.GroupLayout.PREFERRED_SIZE, 51, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(0, 1, Short.MAX_VALUE)) + .addGroup(jPanel8Layout.createSequentialGroup() + .addComponent(jLabel23, javax.swing.GroupLayout.PREFERRED_SIZE, 23, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(spinnerHumidity))) + .addContainerGap()) + ); + + jPanel3.setBackground(new java.awt.Color(207, 233, 234)); + + jLabel7.setText("Data Push Interval:"); + + spinnerInterval.setModel(new javax.swing.SpinnerNumberModel(Integer.valueOf(5), Integer.valueOf(1), null, Integer.valueOf(1))); + spinnerInterval.addChangeListener(new javax.swing.event.ChangeListener() { + public void stateChanged(javax.swing.event.ChangeEvent evt) { + spinnerIntervalStateChanged(evt); + } + }); + + jLabel8.setText("Seconds"); + + jLabel9.setText("Protocol:"); + + cmbProtocol.setModel(new javax.swing.DefaultComboBoxModel(new String[] { "MQTT", "XMPP", "HTTP" })); + cmbProtocol.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + cmbProtocolActionPerformed(evt); + } + }); + + jLabel12.setText("Interface:"); + + cmbInterface.setModel(new javax.swing.DefaultComboBoxModel(new String[] { "eth0" })); + cmbInterface.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + cmbInterfaceActionPerformed(evt); + } + }); + + javax.swing.GroupLayout jPanel3Layout = new javax.swing.GroupLayout(jPanel3); + jPanel3.setLayout(jPanel3Layout); + jPanel3Layout.setHorizontalGroup( + jPanel3Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel3Layout.createSequentialGroup() + .addContainerGap() + .addComponent(jLabel7) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(spinnerInterval, javax.swing.GroupLayout.PREFERRED_SIZE, 55, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jLabel8) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(jLabel12) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(cmbInterface, javax.swing.GroupLayout.PREFERRED_SIZE, 100, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jLabel9) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(cmbProtocol, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addContainerGap()) + ); + jPanel3Layout.setVerticalGroup( + jPanel3Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, jPanel3Layout.createSequentialGroup() + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGroup(jPanel3Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel3Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(jLabel12) + .addComponent(cmbInterface, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addGroup(jPanel3Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(jLabel7) + .addComponent(spinnerInterval, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(jLabel8) + .addComponent(jLabel9) + .addComponent(cmbProtocol, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))) + .addContainerGap()) + ); + + jPanel4.setBackground(new java.awt.Color(169, 253, 173)); + + chkbxEmulate.setText("Emulate data"); + chkbxEmulate.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + chkbxEmulateActionPerformed(evt); + } + }); + + cmbPeriod.setModel(new javax.swing.DefaultComboBoxModel(new String[] { "1 hour", "1 day", "1 week", "1 month " })); + cmbPeriod.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + cmbPeriodActionPerformed(evt); + } + }); + + jLabel1.setText("Emulation Period"); + + javax.swing.GroupLayout jPanel4Layout = new javax.swing.GroupLayout(jPanel4); + jPanel4.setLayout(jPanel4Layout); + jPanel4Layout.setHorizontalGroup( + jPanel4Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel4Layout.createSequentialGroup() + .addContainerGap() + .addComponent(chkbxEmulate) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(jLabel1) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(cmbPeriod, javax.swing.GroupLayout.PREFERRED_SIZE, 162, javax.swing.GroupLayout.PREFERRED_SIZE) + .addContainerGap()) + ); + jPanel4Layout.setVerticalGroup( + jPanel4Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, jPanel4Layout.createSequentialGroup() + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGroup(jPanel4Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(chkbxEmulate) + .addComponent(cmbPeriod, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(jLabel1)) + .addContainerGap()) + ); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane()); + getContentPane().setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(lblAgentName, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(jPanel6, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGroup(layout.createSequentialGroup() + .addComponent(jPanel1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(jPanel8, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(jPanel2, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))) + .addComponent(jLabel2, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(jPanel4, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(jPanel3, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addContainerGap()) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addComponent(lblAgentName, javax.swing.GroupLayout.PREFERRED_SIZE, 53, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jPanel6, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addComponent(jPanel2, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jPanel8, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addComponent(jPanel1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jPanel3, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jPanel4, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jLabel2, javax.swing.GroupLayout.PREFERRED_SIZE, 28, javax.swing.GroupLayout.PREFERRED_SIZE) + .addContainerGap()) + ); + + pack(); + }// //GEN-END:initComponents + + private void btnControlMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_btnControlMouseClicked + // TODO add your handling code here: + }//GEN-LAST:event_btnControlMouseClicked + + private void btnViewMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_btnViewMouseClicked + // TODO add your handling code here: + }//GEN-LAST:event_btnViewMouseClicked + + private void chkbxTemperatureRandomActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_chkbxTemperatureRandomActionPerformed + // TODO add your handling code here: + }//GEN-LAST:event_chkbxTemperatureRandomActionPerformed + + private void chkbxHumidityRandomActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_chkbxHumidityRandomActionPerformed + // TODO add your handling code here: + }//GEN-LAST:event_chkbxHumidityRandomActionPerformed + + private void spinnerTemperatureStateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_spinnerTemperatureStateChanged + // TODO add your handling code here: + }//GEN-LAST:event_spinnerTemperatureStateChanged + + private void spinnerHumidityStateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_spinnerHumidityStateChanged + // TODO add your handling code here: + }//GEN-LAST:event_spinnerHumidityStateChanged + + private void txtTemperatureMinActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_txtTemperatureMinActionPerformed + // TODO add your handling code here: + }//GEN-LAST:event_txtTemperatureMinActionPerformed + + private void txtTemperatureMaxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_txtTemperatureMaxActionPerformed + // TODO add your handling code here: + }//GEN-LAST:event_txtTemperatureMaxActionPerformed + + private void txtHumidityMinActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_txtHumidityMinActionPerformed + // TODO add your handling code here: + }//GEN-LAST:event_txtHumidityMinActionPerformed + + private void txtHumidityMaxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_txtHumidityMaxActionPerformed + // TODO add your handling code here: + }//GEN-LAST:event_txtHumidityMaxActionPerformed + + private void spinnerIntervalStateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_spinnerIntervalStateChanged + // TODO add your handling code here: + }//GEN-LAST:event_spinnerIntervalStateChanged + + private void cmbInterfaceActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cmbInterfaceActionPerformed + // TODO add your handling code here: + }//GEN-LAST:event_cmbInterfaceActionPerformed + + private void cmbProtocolActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cmbProtocolActionPerformed + // TODO add your handling code here: + }//GEN-LAST:event_cmbProtocolActionPerformed + + private void txtTemperatureSVFActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_txtTemperatureSVFActionPerformed + // TODO add your handling code here: + }//GEN-LAST:event_txtTemperatureSVFActionPerformed + + private void txtHumiditySVFActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_txtHumiditySVFActionPerformed + // TODO add your handling code here: + }//GEN-LAST:event_txtHumiditySVFActionPerformed + + private void chkbxTemperatureSmoothActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_chkbxTemperatureSmoothActionPerformed + // TODO add your handling code here: + }//GEN-LAST:event_chkbxTemperatureSmoothActionPerformed + + private void chkbxHumiditySmoothActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_chkbxHumiditySmoothActionPerformed + // TODO add your handling code here: + }//GEN-LAST:event_chkbxHumiditySmoothActionPerformed + + private void cmbPeriodActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cmbPeriodActionPerformed + // TODO add your handling code here: + }//GEN-LAST:event_cmbPeriodActionPerformed + + private void chkbxEmulateActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_chkbxEmulateActionPerformed + // TODO add your handling code here: + }//GEN-LAST:event_chkbxEmulateActionPerformed + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JButton btnControl; + private javax.swing.JButton btnView; + private javax.swing.JCheckBox chkbxEmulate; + private javax.swing.JCheckBox chkbxHumidityRandom; + private javax.swing.JCheckBox chkbxHumiditySmooth; + private javax.swing.JCheckBox chkbxTemperatureRandom; + private javax.swing.JCheckBox chkbxTemperatureSmooth; + private javax.swing.JComboBox cmbInterface; + private javax.swing.JComboBox cmbPeriod; + private javax.swing.JComboBox cmbProtocol; + private javax.swing.JLabel jLabel1; + private javax.swing.JLabel jLabel10; + private javax.swing.JLabel jLabel11; + private javax.swing.JLabel jLabel12; + private javax.swing.JLabel jLabel2; + private javax.swing.JLabel jLabel20; + private javax.swing.JLabel jLabel23; + private javax.swing.JLabel jLabel24; + private javax.swing.JLabel jLabel25; + private javax.swing.JLabel jLabel3; + private javax.swing.JLabel jLabel4; + private javax.swing.JLabel jLabel5; + private javax.swing.JLabel jLabel6; + private javax.swing.JLabel jLabel7; + private javax.swing.JLabel jLabel8; + private javax.swing.JLabel jLabel9; + private javax.swing.JPanel jPanel1; + private javax.swing.JPanel jPanel2; + private javax.swing.JPanel jPanel3; + private javax.swing.JPanel jPanel4; + private javax.swing.JPanel jPanel6; + private javax.swing.JPanel jPanel7; + private javax.swing.JPanel jPanel8; + private javax.swing.JPanel jPanel9; + private javax.swing.JSeparator jSeparator1; + private javax.swing.JSeparator jSeparator5; + private javax.swing.JLabel lblAgentName; + private javax.swing.JLabel lblStatus; + private javax.swing.JPanel pnlBulbStatus; + private javax.swing.JSpinner spinnerHumidity; + private javax.swing.JSpinner spinnerInterval; + private javax.swing.JSpinner spinnerTemperature; + private javax.swing.JTextField txtHumidityMax; + private javax.swing.JTextField txtHumidityMin; + private javax.swing.JTextField txtHumiditySVF; + private javax.swing.JTextField txtTemperatureMax; + private javax.swing.JTextField txtTemperatureMin; + private javax.swing.JTextField txtTemperatureSVF; + // End of variables declaration//GEN-END:variables +} diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.analytics/build.xml b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.analytics/build.xml new file mode 100644 index 0000000000..a877b11413 --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.analytics/build.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.analytics/pom.xml b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.analytics/pom.xml new file mode 100644 index 0000000000..da9f26ab72 --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.analytics/pom.xml @@ -0,0 +1,92 @@ + + + + + + + + virtual-fire-alarm-plugin + org.wso2.carbon.devicemgt-plugins + 2.1.0-SNAPSHOT + ../pom.xml + + + 4.0.0 + org.wso2.carbon.device.mgt.iot.virtualfirealarm.analytics + WSO2 Carbon - IoT Server Virtual Firealarm Analytics capp + pom + + + + + maven-clean-plugin + 2.4.1 + + + auto-clean + initialize + + clean + + + + + + maven-antrun-plugin + 1.7 + + + process-resources + + + + + + + run + + + + + + maven-assembly-plugin + 2.5.5 + + ${project.artifactId}-${carbon.device.mgt.version} + false + + src/assembly/src.xml + + + + + create-archive + package + + single + + + + + + + + \ No newline at end of file diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.analytics/src/assembly/src.xml b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.analytics/src/assembly/src.xml new file mode 100644 index 0000000000..a5a375010e --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.analytics/src/assembly/src.xml @@ -0,0 +1,36 @@ + + + + src + + zip + + false + ${basedir}/src + + + ${basedir}/target/carbonapps + / + true + + + \ No newline at end of file diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.analytics/src/main/resources/carbonapps/Temperature_Sensor/Eventreceiver_temperature_1.0.0/EventReceiver_temperature.xml b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.analytics/src/main/resources/carbonapps/Temperature_Sensor/Eventreceiver_temperature_1.0.0/EventReceiver_temperature.xml new file mode 100644 index 0000000000..28b710c27f --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.analytics/src/main/resources/carbonapps/Temperature_Sensor/Eventreceiver_temperature_1.0.0/EventReceiver_temperature.xml @@ -0,0 +1,26 @@ + + + + + + false + + + + diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.analytics/src/main/resources/carbonapps/Temperature_Sensor/Eventreceiver_temperature_1.0.0/artifact.xml b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.analytics/src/main/resources/carbonapps/Temperature_Sensor/Eventreceiver_temperature_1.0.0/artifact.xml new file mode 100644 index 0000000000..25df56734b --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.analytics/src/main/resources/carbonapps/Temperature_Sensor/Eventreceiver_temperature_1.0.0/artifact.xml @@ -0,0 +1,22 @@ + + + + + EventReceiver_temperature.xml + diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.analytics/src/main/resources/carbonapps/Temperature_Sensor/Eventstore_temperature_1.0.0/artifact.xml b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.analytics/src/main/resources/carbonapps/Temperature_Sensor/Eventstore_temperature_1.0.0/artifact.xml new file mode 100644 index 0000000000..ccfb3b3140 --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.analytics/src/main/resources/carbonapps/Temperature_Sensor/Eventstore_temperature_1.0.0/artifact.xml @@ -0,0 +1,22 @@ + + + + + org_wso2_iot_devices_temperature.xml + diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.analytics/src/main/resources/carbonapps/Temperature_Sensor/Eventstore_temperature_1.0.0/org_wso2_iot_devices_temperature.xml b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.analytics/src/main/resources/carbonapps/Temperature_Sensor/Eventstore_temperature_1.0.0/org_wso2_iot_devices_temperature.xml new file mode 100644 index 0000000000..d06f73b14e --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.analytics/src/main/resources/carbonapps/Temperature_Sensor/Eventstore_temperature_1.0.0/org_wso2_iot_devices_temperature.xml @@ -0,0 +1,62 @@ + + + + + + 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/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.analytics/src/main/resources/carbonapps/Temperature_Sensor/Eventstream_temperature_1.0.0/artifact.xml b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.analytics/src/main/resources/carbonapps/Temperature_Sensor/Eventstream_temperature_1.0.0/artifact.xml new file mode 100644 index 0000000000..27ec69702e --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.analytics/src/main/resources/carbonapps/Temperature_Sensor/Eventstream_temperature_1.0.0/artifact.xml @@ -0,0 +1,23 @@ + + + + + org.wso2.iot.devices.temperature_1.0.0.json + + diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.analytics/src/main/resources/carbonapps/Temperature_Sensor/Eventstream_temperature_1.0.0/org.wso2.iot.devices.temperature_1.0.0.json b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.analytics/src/main/resources/carbonapps/Temperature_Sensor/Eventstream_temperature_1.0.0/org.wso2.iot.devices.temperature_1.0.0.json new file mode 100644 index 0000000000..5d94b9821b --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.analytics/src/main/resources/carbonapps/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/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.analytics/src/main/resources/carbonapps/Temperature_Sensor/Sparkscripts_1.0.0/Temperature_Sensor_Script.xml b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.analytics/src/main/resources/carbonapps/Temperature_Sensor/Sparkscripts_1.0.0/Temperature_Sensor_Script.xml new file mode 100644 index 0000000000..41938dd4ff --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.analytics/src/main/resources/carbonapps/Temperature_Sensor/Sparkscripts_1.0.0/Temperature_Sensor_Script.xml @@ -0,0 +1,31 @@ + + + + + IoTServer_Sensor_Script + + 0 * * * * ? + diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.analytics/src/main/resources/carbonapps/Temperature_Sensor/Sparkscripts_1.0.0/artifact.xml b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.analytics/src/main/resources/carbonapps/Temperature_Sensor/Sparkscripts_1.0.0/artifact.xml new file mode 100644 index 0000000000..9b4228e30c --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.analytics/src/main/resources/carbonapps/Temperature_Sensor/Sparkscripts_1.0.0/artifact.xml @@ -0,0 +1,22 @@ + + + + + Temperature_Sensor_Script.xml + diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.analytics/src/main/resources/carbonapps/Temperature_Sensor/artifacts.xml b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.analytics/src/main/resources/carbonapps/Temperature_Sensor/artifacts.xml new file mode 100644 index 0000000000..c4580f909d --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.analytics/src/main/resources/carbonapps/Temperature_Sensor/artifacts.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.controller.service.impl/pom.xml b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.controller.service.impl/pom.xml new file mode 100644 index 0000000000..f9a2c1abcf --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.controller.service.impl/pom.xml @@ -0,0 +1,262 @@ + + + + + + + virtual-fire-alarm-plugin + org.wso2.carbon.devicemgt-plugins + 2.1.0-SNAPSHOT + ../pom.xml + + + 4.0.0 + org.wso2.carbon.device.mgt.iot.virtualfirealarm.controller.service.impl + war + WSO2 Carbon - IoT Server VirtualFireAlarm API + WSO2 Carbon - Virtual FireAlarm Service Controller API Implementation + http://wso2.org + + + + + org.wso2.carbon.devicemgt + org.wso2.carbon.device.mgt.common + provided + + + org.wso2.carbon.devicemgt + org.wso2.carbon.device.mgt.core + provided + + + org.apache.axis2.wso2 + axis2-client + + + + + + org.wso2.carbon.devicemgt + org.wso2.carbon.device.mgt.analytics + provided + + + org.apache.axis2.wso2 + axis2-client + + + + + + org.wso2.carbon.devicemgt + org.wso2.carbon.certificate.mgt.core + provided + + + commons-codec.wso2 + commons-codec + + + + + + + + org.apache.cxf + cxf-rt-frontend-jaxws + provided + + + org.apache.cxf + cxf-rt-frontend-jaxrs + provided + + + org.apache.cxf + cxf-rt-transports-http + provided + + + + + org.eclipse.paho + org.eclipse.paho.client.mqttv3 + provided + + + + + org.apache.httpcomponents + httpasyncclient + 4.1 + provided + + + org.wso2.carbon.devicemgt-plugins + org.wso2.carbon.device.mgt.iot + provided + + + org.wso2.carbon.devicemgt-plugins + org.wso2.carbon.device.mgt.iot.virtualfirealarm.plugin.impl + provided + + + + + org.codehaus.jackson + jackson-core-asl + + + org.codehaus.jackson + jackson-jaxrs + + + javax + javaee-web-api + provided + + + javax.ws.rs + jsr311-api + provided + + + commons-httpclient.wso2 + commons-httpclient + provided + + + + org.wso2.carbon + org.wso2.carbon.utils + provided + + + 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.wso2 + commons-codec + + + + org.wso2.carbon.devicemgt + org.wso2.carbon.apimgt.annotations + provided + + + + org.igniterealtime.smack.wso2 + smack + provided + + + org.igniterealtime.smack.wso2 + smackx + provided + + + org.wso2.carbon.devicemgt + org.wso2.carbon.device.mgt.extensions + provided + + + + + + + + + maven-compiler-plugin + + UTF-8 + ${wso2.maven.compiler.source} + ${wso2.maven.compiler.target} + + + + maven-war-plugin + + virtual_firealarm + + + + + + diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.controller.service.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/controller/service/impl/VirtualFireAlarmControllerService.java b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.controller.service.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/controller/service/impl/VirtualFireAlarmControllerService.java new file mode 100644 index 0000000000..1318caf7f5 --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.controller.service.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/controller/service/impl/VirtualFireAlarmControllerService.java @@ -0,0 +1,614 @@ +/* + * Copyright (c) 2016, 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.wso2.carbon.device.mgt.iot.virtualfirealarm.controller.service.impl; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.apimgt.annotations.api.API; +import org.wso2.carbon.certificate.mgt.core.dto.SCEPResponse; +import org.wso2.carbon.certificate.mgt.core.exception.KeystoreException; +import org.wso2.carbon.certificate.mgt.core.service.CertificateManagementService; +import org.wso2.carbon.context.PrivilegedCarbonContext; +import org.wso2.carbon.device.mgt.analytics.common.AnalyticsDataRecord; +import org.wso2.carbon.device.mgt.analytics.exception.DeviceManagementAnalyticsException; +import org.wso2.carbon.device.mgt.analytics.service.DeviceAnalyticsService; +import org.wso2.carbon.device.mgt.common.DeviceManagementException; +import org.wso2.carbon.device.mgt.extensions.feature.mgt.annotations.DeviceType; +import org.wso2.carbon.device.mgt.extensions.feature.mgt.annotations.Feature; +import org.wso2.carbon.device.mgt.iot.controlqueue.mqtt.MqttConfig; +import org.wso2.carbon.device.mgt.iot.controlqueue.xmpp.XmppConfig; +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.service.IoTServerStartupListener; +import org.wso2.carbon.device.mgt.iot.transport.TransportHandlerException; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.controller.service.impl.dto.DeviceData; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.controller.service.impl.dto.SensorData; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.controller.service.impl.exception.VirtualFireAlarmException; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.controller.service.impl.transport.VirtualFireAlarmMQTTConnector; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.controller.service.impl.transport.VirtualFireAlarmXMPPConnector; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.controller.service.impl.util.SecurityManager; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.controller.service.impl.util.VirtualFireAlarmServiceUtils; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.controller.service.impl.util.scep.ContentType; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.controller.service.impl.util.scep.SCEPOperation; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.plugin.constants.VirtualFireAlarmConstants; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.ws.rs.Consumes; +import javax.ws.rs.FormParam; +import javax.ws.rs.GET; +import javax.ws.rs.HeaderParam; +import javax.ws.rs.POST; +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.MediaType; +import javax.ws.rs.core.Response; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; + +/** + * This class consists the functions/APIs specific to the "actions" of the VirtualFirealarm device-type. These APIs + * include the ones that are used by the [Device] to contact the server (i.e: Enrollment & Publishing Data) and the + * ones used by the [Server/Owner] to contact the [Device] (i.e: sending control signals). This class also initializes + * the transport 'Connectors' [XMPP & MQTT] specific to the VirtualFirealarm device-type in order to communicate with + * such devices and to receive messages form it. + */ +@API(name = "virtual_firealarm", version = "1.0.0", context = "/virtual_firealarm", tags = "virtual_firealarm") +@DeviceType(value = "virtual_firealarm") +@SuppressWarnings("Non-Annoted WebService") +public class VirtualFireAlarmControllerService { + + private static final String XMPP_PROTOCOL = "XMPP"; + private static final String HTTP_PROTOCOL = "HTTP"; + private static final String MQTT_PROTOCOL = "MQTT"; + private static Log log = LogFactory.getLog(VirtualFireAlarmControllerService.class); + @Context //injected response proxy supporting multiple thread + private HttpServletResponse response; + // consists of utility methods related to encrypting and decrypting messages + private SecurityManager securityManager; + // connects to the given MQTT broker and handles MQTT communication + private VirtualFireAlarmMQTTConnector virtualFireAlarmMQTTConnector; + // connects to the given XMPP server and handles XMPP communication + private VirtualFireAlarmXMPPConnector virtualFireAlarmXMPPConnector; + // holds a mapping of the IP addresses to Device-IDs for HTTP communication + private ConcurrentHashMap deviceToIpMap = new ConcurrentHashMap<>(); + + private boolean waitForServerStartup() { + while (!IoTServerStartupListener.isServerReady()) { + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + return true; + } + } + return false; + } + + /** + * Fetches the `SecurityManager` specific to this VirtualFirealarm controller service. + * + * @return the 'SecurityManager' instance bound to the 'securityManager' variable of this service. + */ + @SuppressWarnings("Unused") + public SecurityManager getSecurityManager() { + return securityManager; + } + + /** + * Sets the `securityManager` variable of this VirtualFirealarm controller service. + * + * @param securityManager a 'SecurityManager' object that handles the encryption, decryption, signing and validation + * of incoming messages from VirtualFirealarm device-types. + */ + @SuppressWarnings("Unused") + public void setSecurityManager(SecurityManager securityManager) { + this.securityManager = securityManager; + securityManager.initVerificationManager(); + } + + /** + * Fetches the `VirtualFireAlarmXMPPConnector` specific to this VirtualFirealarm controller service. + * + * @return the 'VirtualFireAlarmXMPPConnector' instance bound to the 'virtualFireAlarmXMPPConnector' variable of + * this service. + */ + @SuppressWarnings("Unused") + public VirtualFireAlarmXMPPConnector getVirtualFireAlarmXMPPConnector() { + return virtualFireAlarmXMPPConnector; + } + + /** + * Sets the `virtualFireAlarmXMPPConnector` variable of this VirtualFirealarm controller service. + * + * @param virtualFireAlarmXMPPConnector a 'VirtualFireAlarmXMPPConnector' object that handles all XMPP related + * communications of any connected VirtualFirealarm device-type + */ + @SuppressWarnings("Unused") + public void setVirtualFireAlarmXMPPConnector( + final VirtualFireAlarmXMPPConnector virtualFireAlarmXMPPConnector) { + Runnable connector = new Runnable() { + public void run() { + if (waitForServerStartup()) { + return; + } + VirtualFireAlarmControllerService.this.virtualFireAlarmXMPPConnector = virtualFireAlarmXMPPConnector; + + if (XmppConfig.getInstance().isEnabled()) { + Runnable xmppStarter = new Runnable() { + @Override + public void run() { + virtualFireAlarmXMPPConnector.initConnector(); + virtualFireAlarmXMPPConnector.connect(); + } + }; + + Thread xmppStarterThread = new Thread(xmppStarter); + xmppStarterThread.setDaemon(true); + xmppStarterThread.start(); + } else { + log.warn("XMPP disabled in 'devicemgt-config.xml'. Hence, VirtualFireAlarmXMPPConnector not started."); + } + } + }; + Thread connectorThread = new Thread(connector); + connectorThread.setDaemon(true); + connectorThread.start(); + } + + /** + * Fetches the `VirtualFireAlarmMQTTConnector` specific to this VirtualFirealarm controller service. + * + * @return the 'VirtualFireAlarmMQTTConnector' instance bound to the 'virtualFireAlarmMQTTConnector' variable of + * this service. + */ + @SuppressWarnings("Unused") + public VirtualFireAlarmMQTTConnector getVirtualFireAlarmMQTTConnector() { + return virtualFireAlarmMQTTConnector; + } + + /** + * Sets the `virtualFireAlarmMQTTConnector` variable of this VirtualFirealarm controller service. + * + * @param virtualFireAlarmMQTTConnector a 'VirtualFireAlarmMQTTConnector' object that handles all MQTT related + * communications of any connected VirtualFirealarm device-type + */ + @SuppressWarnings("Unused") + public void setVirtualFireAlarmMQTTConnector( + final VirtualFireAlarmMQTTConnector virtualFireAlarmMQTTConnector) { + Runnable connector = new Runnable() { + public void run() { + if (waitForServerStartup()) { + return; + } + VirtualFireAlarmControllerService.this.virtualFireAlarmMQTTConnector = virtualFireAlarmMQTTConnector; + if (MqttConfig.getInstance().isEnabled()) { + virtualFireAlarmMQTTConnector.connect(); + } else { + log.warn("MQTT disabled in 'devicemgt-config.xml'. Hence, VirtualFireAlarmMQTTConnector not started."); + } + } + }; + Thread connectorThread = new Thread(connector); + connectorThread.setDaemon(true); + connectorThread.start(); + } + + /** + * This is an API used/called by the device. It registers the IP of a VirtualFirealarm device against its DeviceID + * when the device connects with the server for the first time. This DeviceID to IP mapping is necessary only for + * cases where HTTP communication is to be used. At such instances, this mapping is used by the server to + * identify the IP of the device to which it has some message to be sent. This method becomes useful only in + * scenarios where HTTP communication is used in a setup where the IoT-Server and the devices communicating with it + * are in the same IP network. + * + * @param deviceId the ID of the VirtualFirealarm device from which this register-IP call was initiated. + * @param deviceIP the IP of the VirtualFirealarm device which has sent this register-IP request. + * @param devicePort the PORT on the VirtualFirealarm device (on this IP) that's open for HTTP communication. + * @param request the HTTP servlet request object received by default as part of the HTTP call to this API. + * @param response the HTTP servlet response object received by default as part of the HTTP call to this API. + * @return a custom message indicating whether the DeviceID to IP mapping was successful. + */ + @POST + @Path("controller/register/{deviceId}/{ip}/{port}") + public String registerDeviceIP(@PathParam("deviceId") String deviceId, @PathParam("ip") String deviceIP, + @PathParam("port") String devicePort, @Context HttpServletRequest request, + @Context HttpServletResponse response) { + try { + String result; + if (log.isDebugEnabled()) { + log.debug("Got register call from IP: " + deviceIP + " for Device ID: " + deviceId); + } + String deviceHttpEndpoint = deviceIP + ":" + devicePort; + deviceToIpMap.put(deviceId, deviceHttpEndpoint); + result = "Device-IP Registered"; + response.setStatus(Response.Status.OK.getStatusCode()); + if (log.isDebugEnabled()) { + log.debug(result); + } + return result; + } finally { + PrivilegedCarbonContext.endTenantFlow(); + } + } + + /** + * This is an API called/used from within the Server(Front-End) or by a device Owner. It sends a control command to + * the VirtualFirealarm device to switch `ON` or `OFF` its buzzer. The method also takes in the protocol to be used + * to connect-to and send the command to the device. + * + * @param deviceId the ID of the VirtualFirealarm device on which the buzzer needs to switched `ON` or `OFF`. + * @param protocol the protocol (HTTP, MQTT, XMPP) to be used to connect-to & send the message to the device. + * @param state the state to which the buzzer on the device needs to be changed. Either "ON" or "OFF". + * (Case-Insensitive String) + * @param response the HTTP servlet response object received by default as part of the HTTP call to this API. + */ + @POST + @Path("controller/device/{deviceId}/buzz/") + @Feature(code = "buzz", name = "Buzzer On / Off", type = "operation", + description = "Switch on/off Virtual Fire Alarm Buzzer. (On / Off)") + public void switchBuzzer(@PathParam("deviceId") String deviceId, @QueryParam("protocol") String protocol, + @FormParam("state") String state, @Context HttpServletResponse response) { + String switchToState = state.toUpperCase(); + if (!switchToState.equals(VirtualFireAlarmConstants.STATE_ON) && !switchToState.equals( + VirtualFireAlarmConstants.STATE_OFF)) { + log.error("The requested state change shoud be either - 'ON' or 'OFF'"); + response.setStatus(Response.Status.BAD_REQUEST.getStatusCode()); + return; + } + String protocolString = protocol.toUpperCase(); + String callUrlPattern = VirtualFireAlarmConstants.BULB_CONTEXT + switchToState; + if (log.isDebugEnabled()) { + log.debug("Sending request to switch-bulb of device [" + deviceId + "] via " + + protocolString); + } + try { + switch (protocolString) { + case HTTP_PROTOCOL: + String deviceHTTPEndpoint = deviceToIpMap.get(deviceId); + if (deviceHTTPEndpoint == null) { + response.setStatus(Response.Status.PRECONDITION_FAILED.getStatusCode()); + return; + } + VirtualFireAlarmServiceUtils.sendCommandViaHTTP(deviceHTTPEndpoint, callUrlPattern, true); + break; + case XMPP_PROTOCOL: + String xmppResource = VirtualFireAlarmConstants.BULB_CONTEXT.replace("/", ""); + virtualFireAlarmXMPPConnector.publishDeviceData(deviceId, xmppResource, switchToState); + break; + default: + String mqttResource = VirtualFireAlarmConstants.BULB_CONTEXT.replace("/", ""); + virtualFireAlarmMQTTConnector.publishDeviceData(deviceId, mqttResource, switchToState); + break; + } + response.setStatus(Response.Status.OK.getStatusCode()); + } catch (DeviceManagementException | TransportHandlerException e) { + log.error("Failed to send switch-bulb request to device [" + deviceId + "] via " + protocolString); + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + return; + } finally { + PrivilegedCarbonContext.endTenantFlow(); + } + } + + /** + * This is an API called/used from within the Server(Front-End) or by a device Owner. It sends a control command + * to the VirtualFirealarm device to 'tell what's its current temperature reading'. The method also takes in the + * protocol to be used to connect-to and send the command to the device. + * + * @param deviceId the ID of the VirtualFirealarm device on which the temperature reading is be read-from. + * @param protocol the protocol (HTTP, MQTT, XMPP) to be used to connect-to & send the message to the device. + * @param response the HTTP servlet response object received by default as part of the HTTP call to this API. + * @return an instance of the 'SensorRecord' object that holds the last updated temperature of the VirtualFirealarm + * whose temperature reading was requested. + */ + @GET + @Path("controller/device/{deviceId}/temperature") + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + @Feature(code = "temperature", name = "Temperature", type = "monitor", + description = "Request Temperature reading from Virtual Fire Alarm") + public SensorRecord requestTemperature(@PathParam("deviceId") String deviceId, + @QueryParam("protocol") String protocol, + @Context HttpServletResponse response) { + SensorRecord sensorRecord = null; + String protocolString = protocol.toUpperCase(); + if (log.isDebugEnabled()) { + log.debug("Sending request to read virtual-firealarm-temperature of device " + + "[" + deviceId + "] via " + protocolString); + } + try { + switch (protocolString) { + case HTTP_PROTOCOL: + String deviceHTTPEndpoint = deviceToIpMap.get(deviceId); + if (deviceHTTPEndpoint == null) { + response.setStatus(Response.Status.PRECONDITION_FAILED.getStatusCode()); + } + String temperatureValue = VirtualFireAlarmServiceUtils.sendCommandViaHTTP( + deviceHTTPEndpoint, VirtualFireAlarmConstants.TEMPERATURE_CONTEXT, false); + SensorDataManager.getInstance().setSensorRecord(deviceId, VirtualFireAlarmConstants.SENSOR_TEMP, + temperatureValue, + Calendar.getInstance().getTimeInMillis()); + break; + case XMPP_PROTOCOL: + String xmppResource = VirtualFireAlarmConstants.TEMPERATURE_CONTEXT.replace("/", ""); + virtualFireAlarmMQTTConnector.publishDeviceData(deviceId, xmppResource, ""); + break; + default: + String mqttResource = VirtualFireAlarmConstants.TEMPERATURE_CONTEXT.replace("/", ""); + virtualFireAlarmMQTTConnector.publishDeviceData(deviceId, mqttResource, ""); + break; + } + sensorRecord = SensorDataManager.getInstance().getSensorRecord(deviceId, VirtualFireAlarmConstants + .SENSOR_TEMP); + response.setStatus(Response.Status.OK.getStatusCode()); + } catch (DeviceManagementException | DeviceControllerException | TransportHandlerException e) { + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + } finally { + PrivilegedCarbonContext.endTenantFlow(); + } + return sensorRecord; + } + + /** + * This is an API called/used by the VirtualFirealarm device to publish its temperature to the IoT-Server. The + * received data from the device is stored in a 'DeviceRecord' under the device's ID in the 'SensorDataManager' + * of the Server. + * + * @param dataMsg the temperature data received from the device in JSON format complying to type 'DeviceData'. + * @param response the HTTP servlet response object received by default as part of the HTTP call to this API. + */ + @POST + @Path("controller/temperature") + @Consumes(MediaType.APPLICATION_JSON) + public void pushTemperatureData(final DeviceData dataMsg, @Context HttpServletResponse response) { + try { + String deviceId = dataMsg.deviceId; + String deviceIp = dataMsg.reply; + float temperature = dataMsg.value; + String registeredIp = deviceToIpMap.get(deviceId); + if (registeredIp == null) { + log.warn("Unregistered IP: Temperature Data Received from an un-registered IP " + + deviceIp + " for device ID - " + deviceId); + response.setStatus(Response.Status.PRECONDITION_FAILED.getStatusCode()); + return; + } else if (!registeredIp.equals(deviceIp)) { + log.warn("Conflicting IP: Received IP is " + deviceIp + ". Device with ID " + deviceId + + " is already registered under some other IP. Re-registration required"); + response.setStatus(Response.Status.CONFLICT.getStatusCode()); + return; + } + SensorDataManager.getInstance().setSensorRecord(deviceId, VirtualFireAlarmConstants.SENSOR_TEMP, + String.valueOf(temperature), + Calendar.getInstance().getTimeInMillis()); + if (!VirtualFireAlarmServiceUtils.publishToDAS(dataMsg.deviceId, dataMsg.value)) { + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + } + } finally { + PrivilegedCarbonContext.endTenantFlow(); + } + } + + /** + * This is an API called/used by the SCEP Client of the VirtualFirealarm device in its SCEP enrollment process. + * This acts as the endpoint exposed as part of the SCEP-Server for use by a SCEP Client. This is one of the two + * method-signatures that takes different parameters according to the SCEP-Operation executed by the SCEP-Client + * of the enrolling device. The API supports 2 SCEP Operations [GetCACert] and [GetCACaps]. + *

      + * Operation [GetCACert] returns the CA cert of the SCEP-Server for the device to verify its authenticity. + * Operation [GetCACaps] returns the CA Capabilities of the SCEP-Server. + * + * @param operation the SCEP operation requested by the client. [GetCACert] or [GetCACaps] + * @param message any messages pertaining to the requested SCEP Operation. + * @return an HTTP Response object with either the CA-Cert or the CA-Capabilities according to the operation. + */ + @GET + @Path("controller/scep") + public Response scepRequest(@QueryParam("operation") String operation, @QueryParam("message") String message) { + try { + if (log.isDebugEnabled()) { + log.debug("Invoking SCEP operation " + operation); + } + if (SCEPOperation.GET_CA_CERT.getValue().equals(operation)) { + if (log.isDebugEnabled()) { + log.debug("Invoking GetCACert"); + } + try { + CertificateManagementService certificateManagementService = + VirtualFireAlarmServiceUtils.getCertificateManagementService(); + SCEPResponse scepResponse = certificateManagementService.getCACertSCEP(); + Response.ResponseBuilder responseBuilder; + switch (scepResponse.getResultCriteria()) { + case CA_CERT_FAILED: + log.error("CA cert failed"); + responseBuilder = Response.serverError(); + break; + case CA_CERT_RECEIVED: + if (log.isDebugEnabled()) { + log.debug("CA certificate received in GetCACert"); + } + responseBuilder = Response.ok(scepResponse.getEncodedResponse(), + ContentType.X_X509_CA_CERT); + break; + case CA_RA_CERT_RECEIVED: + if (log.isDebugEnabled()) { + log.debug("CA and RA certificates received in GetCACert"); + } + responseBuilder = Response.ok(scepResponse.getEncodedResponse(), + ContentType.X_X509_CA_RA_CERT); + break; + default: + log.error("Invalid SCEP request"); + responseBuilder = Response.serverError(); + break; + } + + return responseBuilder.build(); + } catch (VirtualFireAlarmException e) { + log.error("Error occurred while enrolling the VirtualFireAlarm device", e); + } catch (KeystoreException e) { + log.error("Keystore error occurred while enrolling the VirtualFireAlarm device", e); + } + + } else if (SCEPOperation.GET_CA_CAPS.getValue().equals(operation)) { + + if (log.isDebugEnabled()) { + log.debug("Invoking GetCACaps"); + } + + try { + CertificateManagementService certificateManagementService = VirtualFireAlarmServiceUtils. + getCertificateManagementService(); + byte caCaps[] = certificateManagementService.getCACapsSCEP(); + + return Response.ok(caCaps, MediaType.TEXT_PLAIN).build(); + + } catch (VirtualFireAlarmException e) { + log.error("Error occurred while enrolling the device", e); + } + + } else { + log.error("Invalid SCEP operation " + operation); + } + + return Response.serverError().build(); + } finally { + PrivilegedCarbonContext.endTenantFlow(); + } + } + + /** + * This is an API called/used by the SCEP Client of the VirtualFirealarm device in its SCEP enrollment process. + * This acts as the endpoint exposed as part of the SCEP-Server for use by a SCEP Client. This is one of the two + * method-signatures that takes different parameters according to the SCEP-Operation executed by the SCEP-Client + * of the enrolling device. This API supports the SCEP Operation [PKIOperation]. + *

      + * Operation [PKIOperation] returns a certificate generated by the SCEP-Server for the enrolling device. + * + * @param operation the final SCEP operation executed in the enrollment process - which is [PKIOperation] + * @param inputStream an input stream consisting of the Certificate-Signing-Request (CSR) from the device. + * @return an HTTP Response object with the signed certificate for the device by the CA of the SCEP Server. + */ + @POST + @Path("controller/scep") + public Response scepRequestPost(@QueryParam("operation") String operation, InputStream inputStream) { + try { + if (log.isDebugEnabled()) { + log.debug("Invoking SCEP operation " + operation); + } + if (SCEPOperation.PKI_OPERATION.getValue().equals(operation)) { + if (log.isDebugEnabled()) { + log.debug("Invoking PKIOperation"); + } + try { + CertificateManagementService certificateManagementService = VirtualFireAlarmServiceUtils. + getCertificateManagementService(); + byte pkiMessage[] = certificateManagementService.getPKIMessageSCEP(inputStream); + return Response.ok(pkiMessage, ContentType.X_PKI_MESSAGE).build(); + } catch (VirtualFireAlarmException e) { + log.error("Error occurred while enrolling the device", e); + } catch (KeystoreException e) { + log.error("Keystore error occurred while enrolling the device", e); + } + } + return Response.serverError().build(); + } finally { + PrivilegedCarbonContext.endTenantFlow(); + } + } + + /** + * Retreive Sensor data for the device type + */ + @Path("controller/stats/device/{deviceId}/sensors/{sensorName}") + @GET + @Consumes("application/json") + @Produces("application/json") + public SensorData[] getVirtualFirealarmStats(@PathParam("deviceId") String deviceId, @PathParam("sensorName") String sensor, + @QueryParam("username") String user, @QueryParam("from") long from, + @QueryParam("to") long to) { + try { + String fromDate = String.valueOf(from); + String toDate = String.valueOf(to); + List sensorDatas = new ArrayList<>(); + PrivilegedCarbonContext ctx = PrivilegedCarbonContext.getThreadLocalCarbonContext(); + DeviceAnalyticsService deviceAnalyticsService = (DeviceAnalyticsService) ctx + .getOSGiService(DeviceAnalyticsService.class, null); + String query = "owner:" + user + " AND deviceId:" + deviceId + " AND deviceType:" + + VirtualFireAlarmConstants.DEVICE_TYPE + " AND time : [" + fromDate + " TO " + toDate + "]"; + String sensorTableName = getSensorEventTableName(sensor); + if (sensorTableName != null) { + List records = deviceAnalyticsService.getAllEventsForDevice(sensorTableName, query); + Collections.sort(records, new Comparator() { + @Override + public int compare(AnalyticsDataRecord o1, AnalyticsDataRecord o2) { + long t1 = (Long) o1.getValue("time"); + long t2 = (Long) o2.getValue("time"); + if (t1 < t2) { + return -1; + } else if (t1 > t2) { + return 1; + } else { + return 0; + } + } + }); + for (AnalyticsDataRecord record : records) { + SensorData sensorData = new SensorData(); + sensorData.setTime((long) record.getValue("time")); + sensorData.setValue("" + (float) record.getValue(sensor)); + sensorDatas.add(sensorData); + } + return sensorDatas.toArray(new SensorData[sensorDatas.size()]); + } + } catch (DeviceManagementAnalyticsException e) { + String errorMsg = "Error on retrieving stats on table for sensor name" + sensor; + log.error(errorMsg); + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + } finally { + PrivilegedCarbonContext.endTenantFlow(); + } + return new SensorData[0]; + } + + /** + * get the event table from the sensor name. + */ + private String getSensorEventTableName(String sensorName){ + String sensorEventTableName; + switch (sensorName) { + case VirtualFireAlarmConstants.SENSOR_TEMP: + sensorEventTableName = VirtualFireAlarmConstants.TEMPERATURE_EVENT_TABLE; + break; + case VirtualFireAlarmConstants.SENSOR_HUMIDITY: + sensorEventTableName = VirtualFireAlarmConstants.HUMIDITY_EVENT_TABLE; + break; + default: + sensorEventTableName = null; + } + return sensorEventTableName; + } +} diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.controller.service.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/controller/service/impl/dto/DeviceData.java b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.controller.service.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/controller/service/impl/dto/DeviceData.java new file mode 100644 index 0000000000..a6dde796bb --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.controller.service.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/controller/service/impl/dto/DeviceData.java @@ -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. + */ + +package org.wso2.carbon.device.mgt.iot.virtualfirealarm.controller.service.impl.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 DeviceData { + @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/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.controller.service.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/controller/service/impl/dto/SensorData.java b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.controller.service.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/controller/service/impl/dto/SensorData.java new file mode 100644 index 0000000000..a4792fca75 --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.controller.service.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/controller/service/impl/dto/SensorData.java @@ -0,0 +1,44 @@ +package org.wso2.carbon.device.mgt.iot.virtualfirealarm.controller.service.impl.dto; + +import org.codehaus.jackson.annotate.JsonIgnoreProperties; + +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; + +@XmlRootElement +/** + * This stores sensor event data for the device type. + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class SensorData { + + @XmlElement public Long time; + @XmlElement public String key; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } + + public Long getTime() { + return time; + } + + public void setTime(Long time) { + this.time = time; + } + + @XmlElement public String value; + +} diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.controller.service.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/controller/service/impl/exception/VirtualFireAlarmException.java b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.controller.service.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/controller/service/impl/exception/VirtualFireAlarmException.java new file mode 100644 index 0000000000..4dd6faacb8 --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.controller.service.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/controller/service/impl/exception/VirtualFireAlarmException.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.wso2.carbon.device.mgt.iot.virtualfirealarm.controller.service.impl.exception; + +public class VirtualFireAlarmException extends Exception { + private static final long serialVersionUID = 118512086957330189L; + + public VirtualFireAlarmException(String errorMessage) { + super(errorMessage); + } + + public VirtualFireAlarmException(String errorMessage, Throwable throwable) { + super(errorMessage, throwable); + } +} diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.controller.service.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/controller/service/impl/transport/VirtualFireAlarmMQTTConnector.java b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.controller.service.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/controller/service/impl/transport/VirtualFireAlarmMQTTConnector.java new file mode 100644 index 0000000000..05c0a459e9 --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.controller.service.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/controller/service/impl/transport/VirtualFireAlarmMQTTConnector.java @@ -0,0 +1,309 @@ +/* + * Copyright (c) 2016, 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.wso2.carbon.device.mgt.iot.virtualfirealarm.controller.service.impl.transport; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.eclipse.paho.client.mqttv3.MqttException; +import org.eclipse.paho.client.mqttv3.MqttMessage; +import org.wso2.carbon.context.PrivilegedCarbonContext; +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.core.service.DeviceManagementProviderService; +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 org.wso2.carbon.device.mgt.iot.virtualfirealarm.controller.service.impl.exception.VirtualFireAlarmException; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.controller.service.impl.util.SecurityManager; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.controller.service.impl.util.VirtualFireAlarmServiceUtils; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.plugin.constants.VirtualFireAlarmConstants; +import org.wso2.carbon.utils.multitenancy.MultitenantUtils; + +import java.io.File; +import java.nio.charset.StandardCharsets; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.util.Calendar; +import java.util.UUID; + +/** + * This is an example for the use of the MQTT capabilities provided by the IoT-Server. This example depicts the use + * of MQTT Transport for the VirtualFirealarm device-type. This class extends the abstract class + * "MQTTTransportHandler". "MQTTTransportHandler" consists of the MQTT client specific functionality and implements + * the "TransportHandler" interface. The actual functionality related to the "TransportHandler" interface is + * implemented here, in this concrete class. Whilst the abstract class "MQTTTransportHandler" is intended to provide + * the common MQTT functionality, this class (which is its extension) provides the implementation specific to the + * MQTT communication of the Device-Type (VirtualFirealarm) in concern. + *

      + * Hence, the methods of this class are implementation of the "TransportHandler" interface which handles the device + * specific logic to connect-to, publish-to, process-incoming-messages-from and disconnect-from the MQTT broker + * listed in the configurations. + */ +@SuppressWarnings("no JAX-WS annotation") +public class VirtualFireAlarmMQTTConnector extends MQTTTransportHandler { + + private static Log log = LogFactory.getLog(VirtualFireAlarmMQTTConnector.class); + // subscription topic: /+/virtual_firealarm/+/publisher + // wildcard (+) is in place for device_owner & device_id + private static String subscribeTopic = "wso2/" + VirtualFireAlarmConstants.DEVICE_TYPE + "/+/publisher"; + private static String iotServerSubscriber = UUID.randomUUID().toString().substring(0, 5); + + /** + * Default constructor for the VirtualFirealarmMQTTConnector. + */ + private VirtualFireAlarmMQTTConnector() { + super(iotServerSubscriber, VirtualFireAlarmConstants.DEVICE_TYPE, + MqttConfig.getInstance().getMqttQueueEndpoint(), subscribeTopic); + } + + /** + * {@inheritDoc} + * VirtualFirealarm device-type specific implementation to connect to the MQTT broker and subscribe to a topic. + * This method is called to initiate a MQTT communication. + */ + @Override + public void connect() { + Runnable connector = new Runnable() { + public void run() { + while (!isConnected()) { + try { + String brokerUsername = MqttConfig.getInstance().getMqttQueueUsername(); + String brokerPassword = MqttConfig.getInstance().getMqttQueuePassword(); + setUsernameAndPassword(brokerUsername, brokerPassword); + connectToQueue(); + } catch (TransportHandlerException e) { + log.error("Connection to MQTT Broker at: " + mqttBrokerEndPoint + " failed", e); + try { + Thread.sleep(timeoutInterval); + } catch (InterruptedException ex) { + log.error("MQTT-Connector: Thread Sleep Interrupt Exception.", ex); + } + } + + try { + subscribeToQueue(); + } catch (TransportHandlerException e) { + log.warn("Subscription to MQTT Broker at: " + mqttBrokerEndPoint + " failed", e); + } + } + } + }; + + Thread connectorThread = new Thread(connector); + connectorThread.setDaemon(true); + connectorThread.start(); + } + + /** + * {@inheritDoc} + * VirtualFirealarm device-type specific implementation to process incoming messages. This is the specific + * method signature of the overloaded "processIncomingMessage" method that gets called from the messageArrived() + * callback of the "MQTTTransportHandler". + */ + @Override + public void processIncomingMessage(MqttMessage mqttMessage, String... messageParams) { + if (messageParams.length != 0) { + // owner and the deviceId are extracted from the MQTT topic to which the message was received. + // = [ServerName/Owner/DeviceType/DeviceId/"publisher"] + String topic = messageParams[0]; + String[] topicParams = topic.split("/"); + String deviceId = topicParams[2]; + if (log.isDebugEnabled()) { + log.debug("Received MQTT message for: [DEVICE.ID-" + deviceId + "]"); + } + + String actualMessage; + try { + // the hash-code of the deviceId is used as the alias for device certificates during SCEP enrollment. + // hence, the same is used here to fetch the device-specific-certificate from the key store. + PublicKey clientPublicKey = VirtualFireAlarmServiceUtils.getDevicePublicKey(deviceId); + PrivateKey serverPrivateKey = SecurityManager.getServerPrivateKey(); + + // the MQTT-messages from VirtualFireAlarm devices are in the form {"Msg":, "Sig":} + actualMessage = VirtualFireAlarmServiceUtils.extractMessageFromPayload(mqttMessage.toString(), + serverPrivateKey, + clientPublicKey); + if (log.isDebugEnabled()) { + log.debug("MQTT: Received Message [" + actualMessage + "] topic: [" + topic + "]"); + } + + if (actualMessage.contains("PUBLISHER")) { + float temperature = Float.parseFloat(actualMessage.split(":")[2]); + try { + PrivilegedCarbonContext.startTenantFlow(); + PrivilegedCarbonContext ctx = PrivilegedCarbonContext.getThreadLocalCarbonContext(); + DeviceManagementProviderService deviceManagementProviderService = + (DeviceManagementProviderService) ctx + .getOSGiService(DeviceManagementProviderService.class, null); + if (deviceManagementProviderService != null) { + DeviceIdentifier identifier = new DeviceIdentifier(deviceId, + VirtualFireAlarmConstants.DEVICE_TYPE); + Device device = deviceManagementProviderService.getDevice(identifier); + if (device != null) { + String owner = device.getEnrolmentInfo().getOwner(); + ctx.setTenantDomain(MultitenantUtils.getTenantDomain(owner), true); + ctx.setUsername(owner); + if (!VirtualFireAlarmServiceUtils.publishToDAS(deviceId, temperature)) { + log.error("MQTT Subscriber: Publishing data to DAS failed."); + } + } + } + } catch (DeviceManagementException e) { + log.error("Failed to retreive the device managment service for device type " + + VirtualFireAlarmConstants.DEVICE_TYPE, e); + } finally { + PrivilegedCarbonContext.endTenantFlow(); + } + if (log.isDebugEnabled()) { + log.debug("MQTT Subscriber: Published data to DAS successfully."); + } + + } else if (actualMessage.contains("TEMPERATURE")) { + String temperatureValue = actualMessage.split(":")[1]; + SensorDataManager.getInstance().setSensorRecord(deviceId, VirtualFireAlarmConstants.SENSOR_TEMP, + temperatureValue, + Calendar.getInstance().getTimeInMillis()); + } + } catch (VirtualFireAlarmException e) { + String errorMsg = + "CertificateManagementService failure oo Signature-Verification/Decryption was unsuccessful."; + log.error(errorMsg, e); + } + } else { + String errorMsg = + "MQTT message [" + mqttMessage.toString() + "] was received without the topic information."; + log.warn(errorMsg); + } + } + + /** + * {@inheritDoc} + * VirtualFirealarm device-type specific implementation to publish data to the device. This method calls the + * {@link #publishToQueue(String, MqttMessage)} method of the "MQTTTransportHandler" class. + */ + @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 = "wso2/" + VirtualFireAlarmConstants.DEVICE_TYPE + "/" + deviceId; + + try { + PublicKey devicePublicKey = VirtualFireAlarmServiceUtils.getDevicePublicKey(deviceId); + PrivateKey serverPrivateKey = SecurityManager.getServerPrivateKey(); + + String actualMessage = resource + ":" + state; + String encryptedMsg = VirtualFireAlarmServiceUtils.prepareSecurePayLoad(actualMessage, + devicePublicKey, + serverPrivateKey); + + pushMessage.setPayload(encryptedMsg.getBytes(StandardCharsets.UTF_8)); + pushMessage.setQos(DEFAULT_MQTT_QUALITY_OF_SERVICE); + pushMessage.setRetained(false); + + publishToQueue(publishTopic, pushMessage); + + } catch (VirtualFireAlarmException e) { + String errorMsg = "Preparing Secure payload failed for device - [" + deviceId + "] of owner - " + + "[" + deviceOwner + "]."; + log.error(errorMsg); + throw new TransportHandlerException(errorMsg, e); + } + } + + + /** + * {@inheritDoc} + * VirtualFirealarm device-type specific implementation to disconnect from the MQTT broker. + */ + @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 - " + VirtualFireAlarmConstants.DEVICE_TYPE, e); + } + + try { + Thread.sleep(timeoutInterval); + } catch (InterruptedException e1) { + log.error("MQTT-Terminator: Thread Sleep Interrupt Exception at device-type - " + + VirtualFireAlarmConstants.DEVICE_TYPE, e1); + } + } + } + } + }; + + Thread terminatorThread = new Thread(stopConnection); + terminatorThread.setDaemon(true); + terminatorThread.start(); + } + + + /** + * {@inheritDoc} + */ + @Override + public void publishDeviceData() { + // nothing to do + } + + /** + * {@inheritDoc} + */ + @Override + public void publishDeviceData(MqttMessage publishData) throws TransportHandlerException { + // nothing to do + } + + /** + * {@inheritDoc} + */ + @Override + public void processIncomingMessage() { + // nothing to do + } + + /** + * {@inheritDoc} + */ + @Override + public void processIncomingMessage(MqttMessage message) throws TransportHandlerException { + // nothing to do + } +} diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.controller.service.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/controller/service/impl/transport/VirtualFireAlarmXMPPConnector.java b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.controller.service.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/controller/service/impl/transport/VirtualFireAlarmXMPPConnector.java new file mode 100644 index 0000000000..00697c81ac --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.controller.service.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/controller/service/impl/transport/VirtualFireAlarmXMPPConnector.java @@ -0,0 +1,293 @@ +/* + * Copyright (c) 2016, 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.wso2.carbon.device.mgt.iot.virtualfirealarm.controller.service.impl.transport; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.jivesoftware.smack.packet.Message; +import org.wso2.carbon.context.PrivilegedCarbonContext; +import org.wso2.carbon.device.mgt.iot.config.server.DeviceManagementConfigurationManager; +import org.wso2.carbon.device.mgt.iot.controlqueue.xmpp.XmppAccount; +import org.wso2.carbon.device.mgt.iot.controlqueue.xmpp.XmppConfig; +import org.wso2.carbon.device.mgt.iot.controlqueue.xmpp.XmppServerClient; +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.transport.TransportHandlerException; +import org.wso2.carbon.device.mgt.iot.transport.xmpp.XMPPTransportHandler; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.plugin.constants.VirtualFireAlarmConstants; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.controller.service.impl.exception.VirtualFireAlarmException; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.controller.service.impl.util.SecurityManager; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.controller.service.impl.util.VirtualFireAlarmServiceUtils; +import org.wso2.carbon.utils.multitenancy.MultitenantUtils; + +import java.io.File; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.util.Calendar; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +@SuppressWarnings("no JAX-WS annotation") +public class VirtualFireAlarmXMPPConnector extends XMPPTransportHandler { + private static Log log = LogFactory.getLog(VirtualFireAlarmXMPPConnector.class); + + private static String xmppServerIP; + private static String xmppVFireAlarmAdminUsername; + private static String xmppVFireAlarmAdminAccountJID; + private static final String V_FIREALARM_XMPP_PASSWORD = "vfirealarm@123"; + private static final String DEVICEMGT_CONFIG_FILE = "devicemgt-config.xml"; + + private ScheduledFuture connectorServiceHandler; + private ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor(); + + private VirtualFireAlarmXMPPConnector() { + super(XmppConfig.getInstance().getXmppServerIP(), XmppConfig.getInstance().getSERVER_CONNECTION_PORT()); + } + + public void initConnector() { + xmppVFireAlarmAdminUsername = "wso2admin_" + VirtualFireAlarmConstants.DEVICE_TYPE; + xmppServerIP = XmppConfig.getInstance().getXmppServerIP(); + xmppVFireAlarmAdminAccountJID = xmppVFireAlarmAdminUsername + "@" + xmppServerIP; + createXMPPAccountForDeviceType(); + } + + public void createXMPPAccountForDeviceType() { + boolean accountExists; + XmppServerClient xmppServerClient = new XmppServerClient(); + xmppServerClient.initControlQueue(); + + try { + accountExists = xmppServerClient.doesXMPPUserAccountExist(xmppVFireAlarmAdminUsername); + + if (!accountExists) { + XmppAccount xmppAccount = new XmppAccount(); + + xmppAccount.setAccountName(xmppVFireAlarmAdminUsername); + xmppAccount.setUsername(xmppVFireAlarmAdminUsername); + xmppAccount.setPassword(V_FIREALARM_XMPP_PASSWORD); + xmppAccount.setEmail(""); + + try { + boolean xmppCreated = xmppServerClient.createXMPPAccount(xmppAccount); + if (!xmppCreated) { + log.warn("Server XMPP Account was not created for device-type - " + + VirtualFireAlarmConstants.DEVICE_TYPE + + ". Check whether XMPP is enabled in \"devicemgt-config.xml\" & restart."); + } else { + log.info("Server XMPP Account [" + xmppVFireAlarmAdminUsername + + "] was not created for device - " + VirtualFireAlarmConstants.DEVICE_TYPE); + } + } catch (DeviceControllerException e) { + String errorMsg = + "An error was encountered whilst trying to create Server XMPP account for device-type - " + + VirtualFireAlarmConstants.DEVICE_TYPE; + log.error(errorMsg, e); + } + } + + } catch (DeviceControllerException e) { + if (e.getMessage().contains(DEVICEMGT_CONFIG_FILE)) { + log.warn("XMPP not Enabled"); + } else { + String errorMsg = "An error was encountered whilst trying to check whether Server XMPP account " + + "exists for device-type - " + VirtualFireAlarmConstants.DEVICE_TYPE ; + log.error(errorMsg, e); + } + } + } + + + @Override + public void connect() { + Runnable connector = new Runnable() { + public void run() { + if (!isConnected()) { + try { + connectToServer(); + loginToServer(xmppVFireAlarmAdminUsername, V_FIREALARM_XMPP_PASSWORD, null); + setFilterOnReceiver(xmppVFireAlarmAdminAccountJID); + + } catch (TransportHandlerException e) { + if (log.isDebugEnabled()) { + log.warn("Connection/Login to XMPP server at: " + server + " as " + + xmppVFireAlarmAdminUsername + " failed for device-type [" + + VirtualFireAlarmConstants.DEVICE_TYPE + "].", e); + } + } + } + } + }; + + connectorServiceHandler = service.scheduleAtFixedRate(connector, 0, timeoutInterval, TimeUnit.MILLISECONDS); + } + + @Override + public void processIncomingMessage(Message xmppMessage) throws TransportHandlerException { + String from = xmppMessage.getFrom(); + String subject = xmppMessage.getSubject(); + String message = xmppMessage.getBody(); + + int indexOfAt = from.indexOf("@"); + int indexOfSlash = from.indexOf("/"); + + if (indexOfAt != -1 && indexOfSlash != -1) { + String deviceId = from.substring(0, indexOfAt); + String owner = from.substring(indexOfSlash + 1, from.length()); + + if (log.isDebugEnabled()) { + log.debug("Received XMPP message for: [OWNER-" + owner + "] & [DEVICE.ID-" + deviceId + "]"); + } + + try { + PublicKey clientPublicKey = VirtualFireAlarmServiceUtils.getDevicePublicKey(deviceId); + PrivateKey serverPrivateKey = SecurityManager.getServerPrivateKey(); + String actualMessage = VirtualFireAlarmServiceUtils.extractMessageFromPayload(message, serverPrivateKey, + clientPublicKey); + if (log.isDebugEnabled()) { + log.debug("XMPP: Received Message [" + actualMessage + "] from: [" + from + "]"); + } + if (subject != null) { + switch (subject) { + case "PUBLISHER": + float temperature = Float.parseFloat(actualMessage.split(":")[1]); + PrivilegedCarbonContext.startTenantFlow(); + String tenantDomain = MultitenantUtils.getTenantDomain(owner); + PrivilegedCarbonContext.getThreadLocalCarbonContext().setTenantDomain(tenantDomain); + PrivilegedCarbonContext.getThreadLocalCarbonContext().setUsername(owner); + if (!VirtualFireAlarmServiceUtils.publishToDAS(deviceId, temperature)) { + log.error("XMPP Connector: Publishing VirtualFirealarm data to DAS failed."); + } + PrivilegedCarbonContext.endTenantFlow(); + if (log.isDebugEnabled()) { + log.debug("XMPP: Publisher Message [" + actualMessage + "] from [" + from + "] " + + "was successfully published to DAS"); + } + break; + case "CONTROL-REPLY": + String tempVal = actualMessage.split(":")[1]; + SensorDataManager.getInstance().setSensorRecord(deviceId, + VirtualFireAlarmConstants.SENSOR_TEMP, + tempVal, + Calendar.getInstance().getTimeInMillis()); + break; + + default: + if (log.isDebugEnabled()) { + log.warn("Unknown XMPP Message [" + actualMessage + "] from [" + from + "] received"); + } + break; + } + } + } catch (VirtualFireAlarmException e) { + String errorMsg = + "CertificateManagementService failure oo Signature-Verification/Decryption was unsuccessful."; + log.error(errorMsg, e); + } + } else { + log.warn("Received XMPP message from client with unexpected JID [" + from + "]."); + } + } + + @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]; + + try { + PublicKey devicePublicKey = VirtualFireAlarmServiceUtils.getDevicePublicKey(deviceId); + PrivateKey serverPrivateKey = SecurityManager.getServerPrivateKey(); + + String actualMessage = resource + ":" + state; + String encryptedMsg = VirtualFireAlarmServiceUtils.prepareSecurePayLoad(actualMessage, + devicePublicKey, + serverPrivateKey); + + String clientToConnect = deviceId + "@" + xmppServerIP + File.separator + deviceOwner; + sendXMPPMessage(clientToConnect, encryptedMsg, "CONTROL-REQUEST"); + + } catch (VirtualFireAlarmException e) { + String errorMsg = "Preparing Secure payload failed for device - [" + deviceId + "] of owner - " + + "[" + deviceOwner + "]."; + log.error(errorMsg); + throw new TransportHandlerException(errorMsg, e); + } + } + + + @Override + public void disconnect() { + Runnable stopConnection = new Runnable() { + public void run() { + while (isConnected()) { + connectorServiceHandler.cancel(true); + closeConnection(); + if (log.isDebugEnabled()) { + log.warn("Unable to 'STOP' connection to XMPP server at: " + server + + " for user - " + xmppVFireAlarmAdminUsername); + } + + try { + Thread.sleep(timeoutInterval); + } catch (InterruptedException e1) { + log.error("XMPP-Terminator: Thread Sleep Interrupt Exception for " + + VirtualFireAlarmConstants.DEVICE_TYPE + " type.", e1); + } + + } + } + }; + + Thread terminatorThread = new Thread(stopConnection); + terminatorThread.setDaemon(true); + terminatorThread.start(); + } + + + @Override + public void processIncomingMessage(Message message, String... messageParams) throws TransportHandlerException { + // nothing to do + } + + @Override + public void processIncomingMessage() throws TransportHandlerException { + // nothing to do + } + + @Override + public void publishDeviceData() throws TransportHandlerException { + // nothing to do + } + + @Override + public void publishDeviceData(Message publishData) throws TransportHandlerException { + // nothing to do + } +} + diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.controller.service.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/controller/service/impl/util/SecurityManager.java b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.controller.service.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/controller/service/impl/util/SecurityManager.java new file mode 100644 index 0000000000..5098e4eab3 --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.controller.service.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/controller/service/impl/util/SecurityManager.java @@ -0,0 +1,254 @@ +/* + * Copyright (c) 2016, 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.wso2.carbon.device.mgt.iot.virtualfirealarm.controller.service.impl.util; + +import org.apache.commons.codec.binary.Base64; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.certificate.mgt.core.exception.KeystoreException; +import org.wso2.carbon.certificate.mgt.core.util.ConfigurationUtil; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.controller.service.impl.exception.VirtualFireAlarmException; + +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.security.InvalidKeyException; +import java.security.Key; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.Signature; +import java.security.SignatureException; +import java.security.UnrecoverableKeyException; +import java.security.cert.CertificateException; + + +public class SecurityManager { + private static final Log log = LogFactory.getLog(SecurityManager.class); + + private static PrivateKey serverPrivateKey; + private static final String SIGNATURE_ALG = "SHA1withRSA"; + private static final String CIPHER_PADDING = "RSA/ECB/PKCS1Padding"; + + private SecurityManager() { + + } + + public void initVerificationManager() { + serverPrivateKey = retrievePrivateKey(ConfigurationUtil.CA_CERT_ALIAS, + ConfigurationUtil.KEYSTORE_CA_CERT_PRIV_PASSWORD); + } + + public static PrivateKey retrievePrivateKey(String alias, String password){ + PrivateKey privateKey = null; + InputStream inputStream = null; + KeyStore keyStore; + + try { + keyStore = KeyStore.getInstance(ConfigurationUtil.getConfigEntry(ConfigurationUtil.CERTIFICATE_KEYSTORE)); + inputStream = new FileInputStream(ConfigurationUtil.getConfigEntry( + ConfigurationUtil.PATH_CERTIFICATE_KEYSTORE)); + + keyStore.load(inputStream, ConfigurationUtil.getConfigEntry(ConfigurationUtil.CERTIFICATE_KEYSTORE_PASSWORD) + .toCharArray()); + + privateKey = (PrivateKey) (keyStore.getKey(ConfigurationUtil.getConfigEntry(alias), + ConfigurationUtil.getConfigEntry(password).toCharArray())); + + } catch (KeyStoreException e) { + String errorMsg = "Could not load KeyStore of given type in [certificate-config.xml] file." ; + log.error(errorMsg, e); + } catch (FileNotFoundException e) { + String errorMsg = "KeyStore file could not be loaded from path given in [certificate-config.xml] file."; + log.error(errorMsg, e); + } catch (NoSuchAlgorithmException e) { + String errorMsg = "Algorithm not found when loading KeyStore"; + log.error(errorMsg, e); + } catch (CertificateException e) { + String errorMsg = "CertificateException when loading KeyStore"; + log.error(errorMsg, e); + } catch (IOException e) { + String errorMsg = "Input output issue occurred when loading KeyStore"; + log.error(errorMsg, e); + } catch (KeystoreException e) { + String errorMsg = "An error occurred whilst trying load Configs for KeyStoreReader"; + log.error(errorMsg, e); + } catch (UnrecoverableKeyException e) { + String errorMsg = "Key is unrecoverable when retrieving CA private key"; + log.error(errorMsg, e); + } finally { + try { + if (inputStream != null) { + inputStream.close(); + } + } catch (IOException e) { + log.error("Error closing KeyStore input stream", e); + } + } + + return privateKey; + } + + public static PrivateKey getServerPrivateKey() { + return serverPrivateKey; + } + + public static String encryptMessage(String message, Key encryptionKey) throws VirtualFireAlarmException { + Cipher encrypter; + byte[] cipherData; + + try { + encrypter = Cipher.getInstance(CIPHER_PADDING); + encrypter.init(Cipher.ENCRYPT_MODE, encryptionKey); + cipherData = encrypter.doFinal(message.getBytes(StandardCharsets.UTF_8)); + + } catch (NoSuchAlgorithmException e) { + String errorMsg = "Algorithm not found exception occurred for Cipher instance of [" + CIPHER_PADDING + "]"; + log.error(errorMsg); + throw new VirtualFireAlarmException(errorMsg, e); + } catch (NoSuchPaddingException e) { + String errorMsg = "No Padding error occurred for Cipher instance of [" + CIPHER_PADDING + "]"; + log.error(errorMsg); + throw new VirtualFireAlarmException(errorMsg, e); + } catch (InvalidKeyException e) { + String errorMsg = "InvalidKey exception occurred for encryptionKey \n[\n" + encryptionKey + "\n]\n"; + log.error(errorMsg); + throw new VirtualFireAlarmException(errorMsg, e); + } catch (BadPaddingException e) { + String errorMsg = "Bad Padding error occurred for Cipher instance of [" + CIPHER_PADDING + "]"; + log.error(errorMsg); + throw new VirtualFireAlarmException(errorMsg, e); + } catch (IllegalBlockSizeException e) { + String errorMsg = "Illegal blockSize error occurred for Cipher instance of [" + CIPHER_PADDING + "]"; + log.error(errorMsg); + throw new VirtualFireAlarmException(errorMsg, e); + } + + return Base64.encodeBase64String(cipherData); + } + + + public static String signMessage(String encryptedData, PrivateKey signatureKey) throws VirtualFireAlarmException { + + Signature signature; + String signedEncodedString; + + try { + signature = Signature.getInstance(SIGNATURE_ALG); + signature.initSign(signatureKey); + signature.update(Base64.decodeBase64(encryptedData)); + + byte[] signatureBytes = signature.sign(); + signedEncodedString = Base64.encodeBase64String(signatureBytes); + + } catch (NoSuchAlgorithmException e) { + String errorMsg = "Algorithm not found exception occurred for Signature instance of [" + SIGNATURE_ALG + "]"; + log.error(errorMsg); + throw new VirtualFireAlarmException(errorMsg, e); + } catch (SignatureException e) { + String errorMsg = "Signature exception occurred for Signature instance of [" + SIGNATURE_ALG + "]"; + log.error(errorMsg); + throw new VirtualFireAlarmException(errorMsg, e); + } catch (InvalidKeyException e) { + String errorMsg = "InvalidKey exception occurred for signatureKey \n[\n" + signatureKey + "\n]\n"; + log.error(errorMsg); + throw new VirtualFireAlarmException(errorMsg, e); + } + + return signedEncodedString; + } + + + public static boolean verifySignature(String data, String signedData, PublicKey verificationKey) + throws VirtualFireAlarmException { + + Signature signature; + boolean verified; + + try { + signature = Signature.getInstance(SIGNATURE_ALG); + signature.initVerify(verificationKey); + signature.update(Base64.decodeBase64(data)); + + verified = signature.verify(Base64.decodeBase64(signedData)); + + } catch (NoSuchAlgorithmException e) { + String errorMsg = "Algorithm not found exception occurred for Signature instance of [" + SIGNATURE_ALG + "]"; + log.error(errorMsg); + throw new VirtualFireAlarmException(errorMsg, e); + } catch (SignatureException e) { + String errorMsg = "Signature exception occurred for Signature instance of [" + SIGNATURE_ALG + "]"; + log.error(errorMsg); + throw new VirtualFireAlarmException(errorMsg, e); + } catch (InvalidKeyException e) { + String errorMsg = "InvalidKey exception occurred for signatureKey \n[\n" + verificationKey + "\n]\n"; + log.error(errorMsg); + throw new VirtualFireAlarmException(errorMsg, e); + } + + return verified; + } + + + public static String decryptMessage(String encryptedMessage, Key decryptKey) throws VirtualFireAlarmException { + + Cipher decrypter; + String decryptedMessage; + + try { + + decrypter = Cipher.getInstance(CIPHER_PADDING); + decrypter.init(Cipher.DECRYPT_MODE, decryptKey); + decryptedMessage = new String(decrypter.doFinal(Base64.decodeBase64(encryptedMessage)), StandardCharsets.UTF_8); + + } catch (NoSuchAlgorithmException e) { + String errorMsg = "Algorithm not found exception occurred for Cipher instance of [" + CIPHER_PADDING + "]"; + log.error(errorMsg); + throw new VirtualFireAlarmException(errorMsg, e); + } catch (NoSuchPaddingException e) { + String errorMsg = "No Padding error occurred for Cipher instance of [" + CIPHER_PADDING + "]"; + log.error(errorMsg); + throw new VirtualFireAlarmException(errorMsg, e); + } catch (InvalidKeyException e) { + String errorMsg = "InvalidKey exception occurred for encryptionKey \n[\n" + decryptKey + "\n]\n"; + log.error(errorMsg); + throw new VirtualFireAlarmException(errorMsg, e); + } catch (BadPaddingException e) { + String errorMsg = "Bad Padding error occurred for Cipher instance of [" + CIPHER_PADDING + "]"; + log.error(errorMsg); + throw new VirtualFireAlarmException(errorMsg, e); + } catch (IllegalBlockSizeException e) { + String errorMsg = "Illegal blockSize error occurred for Cipher instance of [" + CIPHER_PADDING + "]"; + log.error(errorMsg); + throw new VirtualFireAlarmException(errorMsg, e); + } + + return decryptedMessage; + } + + +} diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.controller.service.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/controller/service/impl/util/VirtualFireAlarmServiceUtils.java b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.controller.service.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/controller/service/impl/util/VirtualFireAlarmServiceUtils.java new file mode 100644 index 0000000000..4126235b1e --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.controller.service.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/controller/service/impl/util/VirtualFireAlarmServiceUtils.java @@ -0,0 +1,366 @@ +/* + * Copyright (c) 2016, 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.wso2.carbon.device.mgt.iot.virtualfirealarm.controller.service.impl.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.json.JSONObject; +import org.wso2.carbon.certificate.mgt.core.exception.KeystoreException; +import org.wso2.carbon.certificate.mgt.core.service.CertificateManagementService; +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.Device; +import org.wso2.carbon.device.mgt.common.DeviceIdentifier; +import org.wso2.carbon.device.mgt.common.DeviceManagementException; +import org.wso2.carbon.device.mgt.core.service.DeviceManagementProviderService; +import org.wso2.carbon.device.mgt.iot.sensormgt.SensorDataManager; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.plugin.constants.VirtualFireAlarmConstants; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.controller.service.impl.exception.VirtualFireAlarmException; +import org.wso2.carbon.utils.multitenancy.MultitenantUtils; + +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.security.Key; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.cert.X509Certificate; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Future; + +/** + * + */ +public class VirtualFireAlarmServiceUtils { + private static final Log log = LogFactory.getLog(VirtualFireAlarmServiceUtils.class); + + private static final String TEMPERATURE_STREAM_DEFINITION = "org.wso2.iot.devices.temperature"; + private static final String JSON_MESSAGE_KEY = "Msg"; + private static final String JSON_SIGNATURE_KEY = "Sig"; + + /** + * + * @return + * @throws VirtualFireAlarmException + */ + public static CertificateManagementService getCertificateManagementService() throws VirtualFireAlarmException { + + PrivilegedCarbonContext ctx = PrivilegedCarbonContext.getThreadLocalCarbonContext(); + CertificateManagementService certificateManagementService = (CertificateManagementService) + ctx.getOSGiService(CertificateManagementService.class, null); + + if (certificateManagementService == null) { + String msg = "EnrollmentService is not initialized"; + log.error(msg); + throw new VirtualFireAlarmException(msg); + } + + return certificateManagementService; + } + + + /** + * + * @param deviceHTTPEndpoint + * @param urlContext + * @param fireAndForgot + * @return + * @throws DeviceManagementException + */ + public static String sendCommandViaHTTP(final String deviceHTTPEndpoint, String urlContext, boolean fireAndForgot) + throws DeviceManagementException { + + String responseMsg = ""; + String urlString = VirtualFireAlarmConstants.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 */ + + /** + * + * @param urlString + * @return + * @throws DeviceManagementException + */ + 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 deviceId, float temperature) { + PrivilegedCarbonContext ctx = PrivilegedCarbonContext.getThreadLocalCarbonContext(); + DeviceAnalyticsService deviceAnalyticsService = (DeviceAnalyticsService) ctx.getOSGiService( + DeviceAnalyticsService.class, null); + if (deviceAnalyticsService != null) { + String owner = PrivilegedCarbonContext.getThreadLocalCarbonContext().getUsername(); + Object metdaData[] = {owner, VirtualFireAlarmConstants.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; + } + return false; + } + + /** + * + * @param message + * @param encryptionKey + * @param signatureKey + * @return + * @throws VirtualFireAlarmException + */ + public static String prepareSecurePayLoad(String message, Key encryptionKey, PrivateKey signatureKey) + throws VirtualFireAlarmException { + String encryptedMsg = SecurityManager.encryptMessage(message, encryptionKey); + String signedPayload = SecurityManager.signMessage(encryptedMsg, signatureKey); + + JSONObject jsonPayload = new JSONObject(); + jsonPayload.put(JSON_MESSAGE_KEY, encryptedMsg); + jsonPayload.put(JSON_SIGNATURE_KEY, signedPayload); + + return jsonPayload.toString(); + } + + /** + * + * @param message + * @param decryptionKey + * @param verifySignatureKey + * @return + * @throws VirtualFireAlarmException + */ + public static String extractMessageFromPayload(String message, Key decryptionKey, PublicKey verifySignatureKey) + throws VirtualFireAlarmException { + String actualMessage; + + JSONObject jsonPayload = new JSONObject(message); + Object encryptedMessage = jsonPayload.get(JSON_MESSAGE_KEY); + Object signedPayload = jsonPayload.get(JSON_SIGNATURE_KEY); + + if (encryptedMessage != null && signedPayload != null) { + if (SecurityManager.verifySignature( + encryptedMessage.toString(), signedPayload.toString(), verifySignatureKey)) { + actualMessage = SecurityManager.decryptMessage(encryptedMessage.toString(), decryptionKey); + } else { + String errorMsg = "The message was not signed by a valid client. Could not verify signature on payload"; + throw new VirtualFireAlarmException(errorMsg); + } + } else { + String errorMsg = "The received message is in an INVALID format. " + + "Need to be JSON - {\"Msg\":\"\", \"Sig\":\"\"}."; + throw new VirtualFireAlarmException(errorMsg); + } + + return actualMessage; + } + + /** + * + * @param deviceId + * @return + * @throws VirtualFireAlarmException + */ + public static PublicKey getDevicePublicKey(String deviceId) throws VirtualFireAlarmException { + PublicKey clientPublicKey; + String alias = ""; + + try { + alias += deviceId.hashCode(); + + CertificateManagementService certificateManagementService = + VirtualFireAlarmServiceUtils.getCertificateManagementService(); + X509Certificate clientCertificate = (X509Certificate) certificateManagementService.getCertificateByAlias( + alias); + clientPublicKey = clientCertificate.getPublicKey(); + + } catch (VirtualFireAlarmException e) { + String errorMsg = "Could not retrieve CertificateManagementService from the runtime."; + if(log.isDebugEnabled()){ + log.debug(errorMsg); + } + throw new VirtualFireAlarmException(errorMsg, e); + } catch (KeystoreException e) { + String errorMsg; + if (e.getMessage().contains("NULL_CERT")) { + errorMsg = "The Device-View page might have been accessed prior to the device being started."; + if(log.isDebugEnabled()){ + log.debug(errorMsg); + } + throw new VirtualFireAlarmException(errorMsg, e); + } else { + errorMsg = "An error occurred whilst trying to retrieve certificate for deviceId [" + deviceId + + "] with alias: [" + alias + "]"; + if(log.isDebugEnabled()){ + log.debug(errorMsg); + } + throw new VirtualFireAlarmException(errorMsg, e); + } + } + return clientPublicKey; + } + +} diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.controller.service.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/controller/service/impl/util/scep/ContentType.java b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.controller.service.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/controller/service/impl/util/scep/ContentType.java new file mode 100644 index 0000000000..879309c122 --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.controller.service.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/controller/service/impl/util/scep/ContentType.java @@ -0,0 +1,26 @@ +/* + * 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.wso2.carbon.device.mgt.iot.virtualfirealarm.controller.service.impl.util.scep; + +public class ContentType { + public static final String X_PKI_MESSAGE = "application/x-pki-message"; + public static final String X_X509_CA_CERT = "application/x-x509-ca-cert"; + public static final String X_X509_CA_RA_CERT = "application/x-x509-ca-ra-cert"; +} + diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.controller.service.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/controller/service/impl/util/scep/SCEPOperation.java b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.controller.service.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/controller/service/impl/util/scep/SCEPOperation.java new file mode 100644 index 0000000000..f9f8f08980 --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.controller.service.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/controller/service/impl/util/scep/SCEPOperation.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2016, 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.wso2.carbon.device.mgt.iot.virtualfirealarm.controller.service.impl.util.scep; + +public enum SCEPOperation { + GET_CA_CERT("GetCACert"), + GET_CA_CAPS("GetCACaps"), + PKI_OPERATION("PKIOperation"); + + private String value; + + private SCEPOperation(String value) { + this.setValue(value); + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } +} diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.controller.service.impl/src/main/webapp/META-INF/webapp-classloading.xml b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.controller.service.impl/src/main/webapp/META-INF/webapp-classloading.xml new file mode 100644 index 0000000000..fa44619195 --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.controller.service.impl/src/main/webapp/META-INF/webapp-classloading.xml @@ -0,0 +1,33 @@ + + + + + + + + + false + + + CXF,Carbon + diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.controller.service.impl/src/main/webapp/WEB-INF/cxf-servlet.xml b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.controller.service.impl/src/main/webapp/WEB-INF/cxf-servlet.xml new file mode 100644 index 0000000000..fdb49fd8a9 --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.controller.service.impl/src/main/webapp/WEB-INF/cxf-servlet.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.controller.service.impl/src/main/webapp/WEB-INF/web.xml b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.controller.service.impl/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 0000000000..0689ffaf06 --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.controller.service.impl/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,65 @@ + + + WSO2 IoT Server + WSO2 IoT Server + + + 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 + /virtual_firealarm/{version} + + + managed-api-application + virtual_firealarm + + + managed-api-isSecured + true + + + + + + + + + + + + + + + diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.manager.service.impl/pom.xml b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.manager.service.impl/pom.xml new file mode 100644 index 0000000000..ed8eb5d9b6 --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.manager.service.impl/pom.xml @@ -0,0 +1,271 @@ + + + + + + + virtual-fire-alarm-plugin + org.wso2.carbon.devicemgt-plugins + 2.1.0-SNAPSHOT + ../pom.xml + + + 4.0.0 + org.wso2.carbon.device.mgt.iot.virtualfirealarm.manager.service.impl + war + WSO2 Carbon - IoT Server VirtualFireAlarm API + WSO2 Carbon - Virtual FireAlarm Service Management API Implementation + http://wso2.org + + + + + org.wso2.carbon.devicemgt + org.wso2.carbon.device.mgt.common + provided + + + org.wso2.carbon.devicemgt + org.wso2.carbon.device.mgt.core + provided + + + org.apache.axis2.wso2 + axis2-client + + + + + + org.wso2.carbon.devicemgt + org.wso2.carbon.device.mgt.analytics + provided + + + org.apache.axis2.wso2 + axis2-client + + + + + + org.wso2.carbon.devicemgt + org.wso2.carbon.certificate.mgt.core + provided + + + commons-codec.wso2 + commons-codec + + + + + + + + org.apache.cxf + cxf-rt-frontend-jaxws + provided + + + org.apache.cxf + cxf-rt-frontend-jaxrs + provided + + + org.apache.cxf + cxf-rt-transports-http + provided + + + + + org.eclipse.paho + org.eclipse.paho.client.mqttv3 + provided + + + + + org.apache.httpcomponents + httpasyncclient + 4.1 + provided + + + org.wso2.carbon.devicemgt-plugins + org.wso2.carbon.device.mgt.iot + provided + + + org.wso2.carbon.devicemgt-plugins + org.wso2.carbon.device.mgt.iot.virtualfirealarm.plugin.impl + provided + + + + + org.codehaus.jackson + jackson-core-asl + + + org.codehaus.jackson + jackson-jaxrs + + + javax + javaee-web-api + provided + + + javax.ws.rs + jsr311-api + provided + + + commons-httpclient.wso2 + commons-httpclient + provided + + + + org.wso2.carbon + org.wso2.carbon.utils + provided + + + 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.wso2 + commons-codec + + + + org.wso2.carbon.devicemgt + org.wso2.carbon.apimgt.annotations + provided + + + + org.igniterealtime.smack.wso2 + smack + provided + + + org.igniterealtime.smack.wso2 + smackx + provided + + + org.wso2.carbon.devicemgt + org.wso2.carbon.apimgt.webapp.publisher + provided + + + org.wso2.carbon.devicemgt + org.wso2.carbon.device.mgt.jwt.client.extension + provided + + + org.wso2.carbon.devicemgt + org.wso2.carbon.apimgt.application.extension + provided + + + + + + + + maven-compiler-plugin + + UTF-8 + ${wso2.maven.compiler.source} + ${wso2.maven.compiler.target} + + + + maven-war-plugin + + virtual_firealarm_mgt + + + + + + diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.manager.service.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/manager/service/impl/VirtualFireAlarmManagerService.java b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.manager.service.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/manager/service/impl/VirtualFireAlarmManagerService.java new file mode 100644 index 0000000000..50f1c1c19b --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.manager.service.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/manager/service/impl/VirtualFireAlarmManagerService.java @@ -0,0 +1,312 @@ +/* + * Copyright (c) 2016, 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.wso2.carbon.device.mgt.iot.virtualfirealarm.manager.service.impl; + +import org.apache.commons.io.FileUtils; +import org.wso2.carbon.apimgt.application.extension.APIManagementProviderService; +import org.wso2.carbon.apimgt.application.extension.dto.ApiApplicationKey; +import org.wso2.carbon.apimgt.application.extension.exception.APIManagerException; +import org.wso2.carbon.context.PrivilegedCarbonContext; +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.controlqueue.xmpp.XmppAccount; +import org.wso2.carbon.device.mgt.iot.controlqueue.xmpp.XmppConfig; +import org.wso2.carbon.device.mgt.iot.controlqueue.xmpp.XmppServerClient; +import org.wso2.carbon.device.mgt.iot.exception.DeviceControllerException; +import org.wso2.carbon.device.mgt.iot.util.ZipArchive; +import org.wso2.carbon.device.mgt.iot.util.ZipUtil; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.manager.service.impl.util.APIUtil; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.plugin.constants.VirtualFireAlarmConstants; +import org.wso2.carbon.device.mgt.jwt.client.extension.JWTClient; +import org.wso2.carbon.device.mgt.jwt.client.extension.JWTClientManager; +import org.wso2.carbon.device.mgt.jwt.client.extension.dto.AccessTokenInfo; +import org.wso2.carbon.device.mgt.jwt.client.extension.exception.JWTClientException; +import org.wso2.carbon.user.api.UserStoreException; +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.MediaType; +import javax.ws.rs.core.Response; +import java.io.IOException; +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; + +public class VirtualFireAlarmManagerService { + @Context //injected response proxy supporting multiple thread + private HttpServletResponse response; + private static final String KEY_TYPE = "PRODUCTION"; + private static ApiApplicationKey apiApplicationKey; + + + @Path("manager/device/register") + @POST + public boolean register(@QueryParam("deviceId") String deviceId, + @QueryParam("name") String name) { + try { + DeviceIdentifier deviceIdentifier = new DeviceIdentifier(); + deviceIdentifier.setId(deviceId); + deviceIdentifier.setType(VirtualFireAlarmConstants.DEVICE_TYPE); + if (APIUtil.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(VirtualFireAlarmConstants.DEVICE_TYPE); + enrolmentInfo.setOwner(APIUtil.getAuthenticatedUser()); + device.setEnrolmentInfo(enrolmentInfo); + boolean added = APIUtil.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; + } finally { + PrivilegedCarbonContext.endTenantFlow(); + } + } + + @Path("manager/device/{device_id}") + @DELETE + public void removeDevice(@PathParam("device_id") String deviceId, + @Context HttpServletResponse response) { + try { + DeviceIdentifier deviceIdentifier = new DeviceIdentifier(); + deviceIdentifier.setId(deviceId); + deviceIdentifier.setType(VirtualFireAlarmConstants.DEVICE_TYPE); + boolean removed = APIUtil.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 { + PrivilegedCarbonContext.endTenantFlow(); + } + } + + @Path("manager/device/{device_id}") + @PUT + public boolean updateDevice(@PathParam("device_id") String deviceId, + @QueryParam("name") String name, + @Context HttpServletResponse response) { + try { + DeviceIdentifier deviceIdentifier = new DeviceIdentifier(); + deviceIdentifier.setId(deviceId); + deviceIdentifier.setType(VirtualFireAlarmConstants.DEVICE_TYPE); + Device device = APIUtil.getDeviceManagementService().getDevice(deviceIdentifier); + device.setDeviceIdentifier(deviceId); + device.getEnrolmentInfo().setDateOfLastUpdate(new Date().getTime()); + device.setName(name); + device.setType(VirtualFireAlarmConstants.DEVICE_TYPE); + boolean updated = APIUtil.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 { + PrivilegedCarbonContext.endTenantFlow(); + } + } + + @Path("manager/device/{device_id}") + @GET + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public Device getDevice(@PathParam("device_id") String deviceId) { + try { + DeviceIdentifier deviceIdentifier = new DeviceIdentifier(); + deviceIdentifier.setId(deviceId); + deviceIdentifier.setType(VirtualFireAlarmConstants.DEVICE_TYPE); + return APIUtil.getDeviceManagementService().getDevice(deviceIdentifier); + } catch (DeviceManagementException e) { + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + return null; + } finally { + PrivilegedCarbonContext.endTenantFlow(); + } + } + + @Path("manager/devices") + @GET + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public Device[] getFirealarmDevices() { + try { + List userDevices = + APIUtil.getDeviceManagementService().getDevicesOfUser(APIUtil.getAuthenticatedUser()); + ArrayList userDevicesforFirealarm = new ArrayList<>(); + for (Device device : userDevices) { + if (device.getType().equals(VirtualFireAlarmConstants.DEVICE_TYPE) && + device.getEnrolmentInfo().getStatus().equals(EnrolmentInfo.Status.ACTIVE)) { + userDevicesforFirealarm.add(device); + } + } + return userDevicesforFirealarm.toArray(new Device[]{}); + } catch (DeviceManagementException e) { + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + return null; + } finally { + PrivilegedCarbonContext.endTenantFlow(); + } + } + + @Path("manager/device/{sketch_type}/download") + @GET + @Produces(MediaType.APPLICATION_JSON) + public Response downloadSketch(@QueryParam("deviceName") String deviceName, + @PathParam("sketch_type") String sketchType) { + try { + ZipArchive zipFile = createDownloadFile(APIUtil.getAuthenticatedUser(), deviceName, sketchType); + Response.ResponseBuilder response = Response.ok(FileUtils.readFileToByteArray(zipFile.getZipFile())); + response.type("application/zip"); + response.header("Content-Disposition", "attachment; filename=\"" + zipFile.getFileName() + "\""); + return response.build(); + } catch (IllegalArgumentException ex) { + return Response.status(400).entity(ex.getMessage()).build();//bad request + } catch (DeviceManagementException ex) { + return Response.status(500).entity(ex.getMessage()).build(); + } catch (JWTClientException ex) { + return Response.status(500).entity(ex.getMessage()).build(); + } catch (APIManagerException ex) { + return Response.status(500).entity(ex.getMessage()).build(); + }catch (DeviceControllerException ex) { + return Response.status(500).entity(ex.getMessage()).build(); + } catch (IOException ex) { + return Response.status(500).entity(ex.getMessage()).build(); + } catch (UserStoreException ex) { + return Response.status(500).entity(ex.getMessage()).build(); + } finally { + PrivilegedCarbonContext.endTenantFlow(); + } + } + + @Path("manager/device/{sketch_type}/generate_link") + @GET + public Response generateSketchLink(@QueryParam("deviceName") String deviceName, + @PathParam("sketch_type") String sketchType) { + try { + ZipArchive zipFile = createDownloadFile(APIUtil.getAuthenticatedUser(), deviceName, sketchType); + Response.ResponseBuilder rb = Response.ok(zipFile.getDeviceId()); + return rb.build(); + } catch (IllegalArgumentException ex) { + return Response.status(400).entity(ex.getMessage()).build();//bad request + } catch (DeviceManagementException ex) { + return Response.status(500).entity(ex.getMessage()).build(); + } catch (JWTClientException ex) { + return Response.status(500).entity(ex.getMessage()).build(); + } catch (APIManagerException ex) { + return Response.status(500).entity(ex.getMessage()).build(); + } catch (DeviceControllerException ex) { + return Response.status(500).entity(ex.getMessage()).build(); + } catch (UserStoreException ex) { + return Response.status(500).entity(ex.getMessage()).build(); + } finally { + PrivilegedCarbonContext.endTenantFlow(); + } + } + + private ZipArchive createDownloadFile(String owner, String deviceName, String sketchType) + throws DeviceManagementException, APIManagerException, JWTClientException, DeviceControllerException, + UserStoreException { + //create new device id + String deviceId = shortUUID(); + if (apiApplicationKey == null) { + String applicationUsername = PrivilegedCarbonContext.getThreadLocalCarbonContext().getUserRealm().getRealmConfiguration() + .getAdminUserName(); + APIManagementProviderService apiManagementProviderService = APIUtil.getAPIManagementProviderService(); + String[] tags = {VirtualFireAlarmConstants.DEVICE_TYPE}; + apiApplicationKey = apiManagementProviderService.generateAndRetrieveApplicationKeys( + VirtualFireAlarmConstants.DEVICE_TYPE, tags, KEY_TYPE, applicationUsername, true); + } + JWTClient jwtClient = JWTClientManager.getInstance().getJWTClient(); + String scopes = "device_type_" + VirtualFireAlarmConstants.DEVICE_TYPE + " device_" + deviceId; + AccessTokenInfo accessTokenInfo = jwtClient.getAccessToken(apiApplicationKey.getConsumerKey(), + apiApplicationKey.getConsumerSecret(), owner, scopes); + String accessToken = accessTokenInfo.getAccess_token(); + String refreshToken = accessTokenInfo.getRefresh_token(); + //adding registering data + XmppAccount newXmppAccount = new XmppAccount(); + newXmppAccount.setAccountName(owner + "_" + deviceId); + newXmppAccount.setUsername(deviceId); + newXmppAccount.setPassword(accessToken); + newXmppAccount.setEmail(deviceId + "@" + APIUtil.getTenantDomainOftheUser()); + XmppServerClient xmppServerClient = new XmppServerClient(); + xmppServerClient.initControlQueue(); + boolean status; + if (XmppConfig.getInstance().isEnabled()) { + status = xmppServerClient.createXMPPAccount(newXmppAccount); + if (!status) { + String msg = + "XMPP Account was not created for device - " + deviceId + " of owner - " + owner + + ".XMPP might have been disabled in org.wso2.carbon.device.mgt.iot" + + ".common.config.server.configs"; + throw new DeviceManagementException(msg); + } + } + status = register(deviceId, deviceName); + if (!status) { + String msg = "Error occurred while registering the device with " + "id: " + deviceId + " owner:" + owner; + throw new DeviceManagementException(msg); + } + ZipUtil ziputil = new ZipUtil(); + ZipArchive zipFile = ziputil.createZipFile(owner, APIUtil.getTenantDomainOftheUser(), sketchType, deviceId, + deviceName, accessToken, refreshToken); + zipFile.setDeviceId(deviceId); + return zipFile; + } + + 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); + } +} diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.manager.service.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/manager/service/impl/util/APIUtil.java b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.manager.service.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/manager/service/impl/util/APIUtil.java new file mode 100644 index 0000000000..dca274402c --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.manager.service.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/manager/service/impl/util/APIUtil.java @@ -0,0 +1,55 @@ +package org.wso2.carbon.device.mgt.iot.virtualfirealarm.manager.service.impl.util; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.apimgt.application.extension.APIManagementProviderService; +import org.wso2.carbon.context.PrivilegedCarbonContext; +import org.wso2.carbon.device.mgt.core.service.DeviceManagementProviderService; + +/** + * This class provides utility functions used by REST-API. + */ +public class APIUtil { + + private static Log log = LogFactory.getLog(APIUtil.class); + + public static String getAuthenticatedUser() { + PrivilegedCarbonContext threadLocalCarbonContext = PrivilegedCarbonContext.getThreadLocalCarbonContext(); + String username = threadLocalCarbonContext.getUsername(); + String tenantDomain = threadLocalCarbonContext.getTenantDomain(); + if (username.endsWith(tenantDomain)) { + return username.substring(0, username.lastIndexOf("@")); + } + return username; + } + + public static String getTenantDomainOftheUser() { + PrivilegedCarbonContext threadLocalCarbonContext = PrivilegedCarbonContext.getThreadLocalCarbonContext(); + String tenantDomain = threadLocalCarbonContext.getTenantDomain(); + return tenantDomain; + } + + public static DeviceManagementProviderService getDeviceManagementService() { + PrivilegedCarbonContext ctx = PrivilegedCarbonContext.getThreadLocalCarbonContext(); + DeviceManagementProviderService deviceManagementProviderService = + (DeviceManagementProviderService) ctx.getOSGiService(DeviceManagementProviderService.class, null); + if (deviceManagementProviderService == null) { + String msg = "Device Management service has not initialized."; + log.error(msg); + throw new IllegalStateException(msg); + } + return deviceManagementProviderService; + } + + public static APIManagementProviderService getAPIManagementProviderService() { + PrivilegedCarbonContext ctx = PrivilegedCarbonContext.getThreadLocalCarbonContext(); + APIManagementProviderService apiManagementProviderService = + (APIManagementProviderService) ctx.getOSGiService(APIManagementProviderService.class, null); + if (apiManagementProviderService == null) { + String msg = "API management provider service has not initialized."; + log.error(msg); + throw new IllegalStateException(msg); + } + return apiManagementProviderService; + } +} diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.manager.service.impl/src/main/webapp/META-INF/webapp-classloading.xml b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.manager.service.impl/src/main/webapp/META-INF/webapp-classloading.xml new file mode 100644 index 0000000000..fa44619195 --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.manager.service.impl/src/main/webapp/META-INF/webapp-classloading.xml @@ -0,0 +1,33 @@ + + + + + + + + + false + + + CXF,Carbon + diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.manager.service.impl/src/main/webapp/WEB-INF/cxf-servlet.xml b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.manager.service.impl/src/main/webapp/WEB-INF/cxf-servlet.xml new file mode 100644 index 0000000000..532cd8e978 --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.manager.service.impl/src/main/webapp/WEB-INF/cxf-servlet.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.manager.service.impl/src/main/webapp/WEB-INF/web.xml b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.manager.service.impl/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 0000000000..0b6b913db8 --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.manager.service.impl/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,65 @@ + + + WSO2 IoT Server + WSO2 IoT Server + + + CXFServlet + org.apache.cxf.transport.servlet.CXFServlet + 1 + + + + + CXFServlet + /* + + + + isAdminService + false + + + doAuthentication + true + + + + + managed-api-enabled + false + + + managed-api-owner + admin + + + managed-api-context-template + /virtual_firealarm/{version} + + + managed-api-application + virtual_firealarm + + + managed-api-isSecured + true + + + + + + + + + + + + + + + diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.plugin.impl/pom.xml b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.plugin.impl/pom.xml new file mode 100644 index 0000000000..0090fcc367 --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.plugin.impl/pom.xml @@ -0,0 +1,124 @@ + + + + + + + + virtual-fire-alarm-plugin + org.wso2.carbon.devicemgt-plugins + 2.1.0-SNAPSHOT + ../pom.xml + + + 4.0.0 + org.wso2.carbon.device.mgt.iot.virtualfirealarm.plugin.impl + bundle + WSO2 Carbon - IoT Server VirtualFireAlarm Management Plugin + WSO2 Carbon - Virtual FireAlarm Management/Control Plugin Implementation + http://wso2.org + + + + + org.apache.felix + maven-scr-plugin + + + maven-compiler-plugin + + 1.7 + 1.7 + + 2.3.2 + + + org.apache.felix + maven-bundle-plugin + 1.4.0 + true + + + ${project.artifactId} + ${project.artifactId} + ${carbon.devicemgt.plugins.version} + IoT Server Virtual Firealarm Impl Bundle + org.wso2.carbon.device.mgt.iot.virtualfirealarm.plugin.internal + + org.osgi.framework, + org.osgi.service.component, + org.apache.commons.logging, + javax.xml.bind.*;resolution:=optional, + javax.naming;resolution:=optional, + javax.sql;resolution:=optional, + javax.xml.bind.annotation.*;resolution:=optional, + javax.xml.parsers;resolution:=optional, + javax.net;resolution:=optional, + javax.net.ssl;resolution:=optional, + org.w3c.dom;resolution:=optional, + org.wso2.carbon.device.mgt.common.*, + org.wso2.carbon.device.mgt.common, + org.wso2.carbon.context.*, + org.wso2.carbon.ndatasource.core, + org.wso2.carbon.device.mgt.iot.*, + org.wso2.carbon.device.mgt.extensions.feature.mgt.*, + org.wso2.carbon.utils.* + + + !org.wso2.carbon.device.mgt.iot.virtualfirealarm.plugin.internal, + org.wso2.carbon.device.mgt.iot.virtualfirealarm.plugin.* + + + + + + + + + + org.eclipse.osgi + org.eclipse.osgi + + + org.eclipse.osgi + org.eclipse.osgi.services + + + org.wso2.carbon + org.wso2.carbon.logging + + + org.wso2.carbon.devicemgt + org.wso2.carbon.device.mgt.common + + + org.wso2.carbon + org.wso2.carbon.ndatasource.core + + + org.wso2.carbon.devicemgt + org.wso2.carbon.device.mgt.extensions + + + org.wso2.carbon + org.wso2.carbon.utils + + + \ No newline at end of file diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.plugin.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/plugin/constants/VirtualFireAlarmConstants.java b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.plugin.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/plugin/constants/VirtualFireAlarmConstants.java new file mode 100644 index 0000000000..3b8b5e2a82 --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.plugin.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/plugin/constants/VirtualFireAlarmConstants.java @@ -0,0 +1,40 @@ +/* + * 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.wso2.carbon.device.mgt.iot.virtualfirealarm.plugin.constants; + +public class VirtualFireAlarmConstants { + public final static String DEVICE_TYPE = "virtual_firealarm"; + public final static String DEVICE_PLUGIN_DEVICE_NAME = "DEVICE_NAME"; + public final static String DEVICE_PLUGIN_DEVICE_ID = "VIRTUAL_FIREALARM_DEVICE_ID"; + public final static String STATE_ON = "ON"; + public final static String STATE_OFF = "OFF"; + + public static final String URL_PREFIX = "http://"; + public static final String BULB_CONTEXT = "/BULB/"; + public static final String HUMIDITY_CONTEXT = "/HUMIDITY/"; + public static final String TEMPERATURE_CONTEXT = "/TEMPERATURE/"; + + public static final String SENSOR_TEMP = "temperature"; + //sensor events sumerized table name for temperature + public static final String TEMPERATURE_EVENT_TABLE = "DEVICE_TEMPERATURE_SUMMARY"; + public static final String SENSOR_HUMIDITY = "humidity"; + //sensor events summerized table name for humidity + public static final String HUMIDITY_EVENT_TABLE = "DEVICE_HUMIDITY_SUMMARY"; + public static final String DATA_SOURCE_NAME = "jdbc/VirtualFireAlarmDM_DB"; +} diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.plugin.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/plugin/exception/VirtualFirealarmDeviceMgtPluginException.java b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.plugin.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/plugin/exception/VirtualFirealarmDeviceMgtPluginException.java new file mode 100644 index 0000000000..150745ac8d --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.plugin.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/plugin/exception/VirtualFirealarmDeviceMgtPluginException.java @@ -0,0 +1,56 @@ +/* + * 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.wso2.carbon.device.mgt.iot.virtualfirealarm.plugin.exception; + + +public class VirtualFirealarmDeviceMgtPluginException extends Exception{ + + private String errorMessage; + + public String getErrorMessage() { + return errorMessage; + } + + public void setErrorMessage(String errorMessage) { + this.errorMessage = errorMessage; + } + + public VirtualFirealarmDeviceMgtPluginException(String msg, Exception nestedEx) { + super(msg, nestedEx); + setErrorMessage(msg); + } + + public VirtualFirealarmDeviceMgtPluginException(String message, Throwable cause) { + super(message, cause); + setErrorMessage(message); + } + + public VirtualFirealarmDeviceMgtPluginException(String msg) { + super(msg); + setErrorMessage(msg); + } + + public VirtualFirealarmDeviceMgtPluginException() { + super(); + } + + public VirtualFirealarmDeviceMgtPluginException(Throwable cause) { + super(cause); + } + +} diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.plugin.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/plugin/impl/VirtualFireAlarmManager.java b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.plugin.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/plugin/impl/VirtualFireAlarmManager.java new file mode 100644 index 0000000000..a90c153592 --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.plugin.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/plugin/impl/VirtualFireAlarmManager.java @@ -0,0 +1,256 @@ +/* + * 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.wso2.carbon.device.mgt.iot.virtualfirealarm.plugin.impl; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +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.DeviceManager; +import org.wso2.carbon.device.mgt.common.EnrolmentInfo; +import org.wso2.carbon.device.mgt.common.FeatureManager; +import org.wso2.carbon.device.mgt.common.configuration.mgt.TenantConfiguration; +import org.wso2.carbon.device.mgt.common.license.mgt.License; +import org.wso2.carbon.device.mgt.common.license.mgt.LicenseManagementException; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.plugin.exception.VirtualFirealarmDeviceMgtPluginException; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.plugin.impl.dao.VirtualFireAlarmDAO; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.plugin.impl.feature.VirtualFirealarmFeatureManager; +import java.util.List; + + +/** + * This represents the FireAlarm implementation of DeviceManagerService. + */ +public class VirtualFireAlarmManager implements DeviceManager { + + private static final VirtualFireAlarmDAO virtualFireAlarmDAO = new VirtualFireAlarmDAO(); + private static final Log log = LogFactory.getLog(VirtualFireAlarmManager.class); + private FeatureManager virtualFirealarmFeatureManager = new VirtualFirealarmFeatureManager(); + + @Override + public FeatureManager getFeatureManager() { + return virtualFirealarmFeatureManager; + } + + @Override + public boolean saveConfiguration(TenantConfiguration tenantConfiguration) + throws DeviceManagementException { + return false; + } + + @Override + public TenantConfiguration getConfiguration() throws DeviceManagementException { + return null; + } + + @Override + public boolean enrollDevice(Device device) throws DeviceManagementException { + boolean status; + try { + if (log.isDebugEnabled()) { + log.debug("Enrolling a new Virtual Firealarm device : " + device.getDeviceIdentifier()); + } + VirtualFireAlarmDAO.beginTransaction(); + status = virtualFireAlarmDAO.getDeviceDAO().addDevice(device); + VirtualFireAlarmDAO.commitTransaction(); + } catch (VirtualFirealarmDeviceMgtPluginException e) { + try { + VirtualFireAlarmDAO.rollbackTransaction(); + } catch (VirtualFirealarmDeviceMgtPluginException iotDAOEx) { + String msg = "Error occurred while roll back the device enrol transaction :" + device.toString(); + log.warn(msg, iotDAOEx); + } + String msg = "Error while enrolling the Virtual Firealarm device : " + device.getDeviceIdentifier(); + log.error(msg, e); + throw new DeviceManagementException(msg, e); + } + return status; + } + + @Override + public boolean modifyEnrollment(Device device) throws DeviceManagementException { + boolean status; + try { + if (log.isDebugEnabled()) { + log.debug("Modifying the Virtual Firealarm device enrollment data"); + } + VirtualFireAlarmDAO.beginTransaction(); + status = virtualFireAlarmDAO.getDeviceDAO().updateDevice(device); + VirtualFireAlarmDAO.commitTransaction(); + } catch (VirtualFirealarmDeviceMgtPluginException e) { + try { + VirtualFireAlarmDAO.rollbackTransaction(); + } catch (VirtualFirealarmDeviceMgtPluginException iotDAOEx) { + String msg = "Error occurred while roll back the update device transaction :" + device.toString(); + log.warn(msg, iotDAOEx); + } + String msg = "Error while updating the enrollment of the Virtual Firealarm device : " + + device.getDeviceIdentifier(); + log.error(msg, e); + throw new DeviceManagementException(msg, e); + } + return status; + } + + @Override + public boolean disenrollDevice(DeviceIdentifier deviceId) throws DeviceManagementException { + boolean status; + try { + if (log.isDebugEnabled()) { + log.debug("Dis-enrolling Virtual Firealarm device : " + deviceId); + } + VirtualFireAlarmDAO.beginTransaction(); + status = virtualFireAlarmDAO.getDeviceDAO().deleteDevice(deviceId.getId()); + VirtualFireAlarmDAO.commitTransaction(); + } catch (VirtualFirealarmDeviceMgtPluginException e) { + try { + VirtualFireAlarmDAO.rollbackTransaction(); + } catch (VirtualFirealarmDeviceMgtPluginException iotDAOEx) { + String msg = "Error occurred while roll back the device dis enrol transaction :" + deviceId.toString(); + log.warn(msg, iotDAOEx); + } + String msg = "Error while removing the Virtual Firealarm device : " + deviceId.getId(); + log.error(msg, e); + throw new DeviceManagementException(msg, e); + } + return status; + } + + @Override + public boolean isEnrolled(DeviceIdentifier deviceId) throws DeviceManagementException { + boolean isEnrolled = false; + try { + if (log.isDebugEnabled()) { + log.debug("Checking the enrollment of Virtual Firealarm device : " + deviceId.getId()); + } + Device device = virtualFireAlarmDAO.getDeviceDAO().getDevice(deviceId.getId()); + if (device != null) { + isEnrolled = true; + } + } catch (VirtualFirealarmDeviceMgtPluginException e) { + String msg = "Error while checking the enrollment status of Virtual Firealarm device : " + + deviceId.getId(); + log.error(msg, e); + throw new DeviceManagementException(msg, e); + } + return isEnrolled; + } + + @Override + public boolean isActive(DeviceIdentifier deviceId) throws DeviceManagementException { + return true; + } + + @Override + public boolean setActive(DeviceIdentifier deviceId, boolean status) + throws DeviceManagementException { + return true; + } + + @Override + public Device getDevice(DeviceIdentifier deviceId) throws DeviceManagementException { + Device device; + try { + if (log.isDebugEnabled()) { + log.debug("Getting the details of Virtual Firealarm device : " + deviceId.getId()); + } + device = virtualFireAlarmDAO.getDeviceDAO().getDevice(deviceId.getId()); + } catch (VirtualFirealarmDeviceMgtPluginException e) { + String msg = "Error while fetching the Virtual Firealarm device : " + deviceId.getId(); + log.error(msg, e); + throw new DeviceManagementException(msg, e); + } + return device; + } + + @Override + public boolean setOwnership(DeviceIdentifier deviceId, String ownershipType) + throws DeviceManagementException { + return true; + } + + public boolean isClaimable(DeviceIdentifier deviceIdentifier) throws DeviceManagementException { + return false; + } + + @Override + public boolean setStatus(DeviceIdentifier deviceId, String currentOwner, + EnrolmentInfo.Status status) throws DeviceManagementException { + return false; + } + + @Override + public License getLicense(String s) throws LicenseManagementException { + return null; + } + + @Override + public void addLicense(License license) throws LicenseManagementException { + + } + + @Override + public boolean requireDeviceAuthorization() { + return false; + } + + @Override + public boolean updateDeviceInfo(DeviceIdentifier deviceIdentifier, Device device) throws DeviceManagementException { + boolean status; + try { + if (log.isDebugEnabled()) { + log.debug( + "updating the details of Virtual Firealarm device : " + deviceIdentifier); + } + VirtualFireAlarmDAO.beginTransaction(); + status = virtualFireAlarmDAO.getDeviceDAO().updateDevice(device); + VirtualFireAlarmDAO.commitTransaction(); + } catch (VirtualFirealarmDeviceMgtPluginException e) { + try { + VirtualFireAlarmDAO.rollbackTransaction(); + } catch (VirtualFirealarmDeviceMgtPluginException iotDAOEx) { + String msg = "Error occurred while roll back the update device info transaction :" + device.toString(); + log.warn(msg, iotDAOEx); + } + String msg = + "Error while updating the Virtual Firealarm device : " + deviceIdentifier; + log.error(msg, e); + throw new DeviceManagementException(msg, e); + } + return status; + } + + @Override + public List getAllDevices() throws DeviceManagementException { + List devices; + try { + if (log.isDebugEnabled()) { + log.debug("Fetching the details of all Virtual Firealarm devices"); + } + devices = virtualFireAlarmDAO.getDeviceDAO().getAllDevices(); + } catch (VirtualFirealarmDeviceMgtPluginException e) { + String msg = "Error while fetching all Virtual Firealarm devices."; + log.error(msg, e); + throw new DeviceManagementException(msg, e); + } + return devices; + } + +} \ No newline at end of file diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.plugin.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/plugin/impl/VirtualFireAlarmManagerService.java b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.plugin.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/plugin/impl/VirtualFireAlarmManagerService.java new file mode 100644 index 0000000000..68c8ec94fa --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.plugin.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/plugin/impl/VirtualFireAlarmManagerService.java @@ -0,0 +1,107 @@ +/* + * 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.wso2.carbon.device.mgt.iot.virtualfirealarm.plugin.impl; + +import org.wso2.carbon.device.mgt.common.DeviceIdentifier; +import org.wso2.carbon.device.mgt.common.DeviceManagementException; +import org.wso2.carbon.device.mgt.common.DeviceManager; +import org.wso2.carbon.device.mgt.common.app.mgt.Application; +import org.wso2.carbon.device.mgt.common.app.mgt.ApplicationManagementException; +import org.wso2.carbon.device.mgt.common.app.mgt.ApplicationManager; +import org.wso2.carbon.device.mgt.common.operation.mgt.Operation; +import org.wso2.carbon.device.mgt.common.spi.DeviceManagementService; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.plugin.constants.VirtualFireAlarmConstants; + +import java.util.List; + +public class VirtualFireAlarmManagerService implements DeviceManagementService{ + private DeviceManager deviceManager; + @Override + public String getType() { + return VirtualFireAlarmConstants.DEVICE_TYPE; + } + + + @Override + public String getProviderTenantDomain() { + return "carbon.super"; + } + + @Override + public boolean isSharedWithAllTenants() { + return true; + } + + @Override + public void init() throws DeviceManagementException { + this.deviceManager=new VirtualFireAlarmManager(); + } + + @Override + public DeviceManager getDeviceManager() { + return deviceManager; + } + + @Override + public ApplicationManager getApplicationManager() { + return null; + } + + @Override + public void notifyOperationToDevices(Operation operation, List deviceIds) + throws DeviceManagementException { + + } + + @Override + public Application[] getApplications(String domain, int pageNumber, int size) + throws ApplicationManagementException { + return new Application[0]; + } + + @Override + public void updateApplicationStatus(DeviceIdentifier deviceId, Application application, + String status) throws ApplicationManagementException { + + } + + @Override + public String getApplicationStatus(DeviceIdentifier deviceId, Application application) + throws ApplicationManagementException { + return null; + } + + @Override + public void installApplicationForDevices(Operation operation, List deviceIdentifiers) + throws ApplicationManagementException { + + } + + @Override + public void installApplicationForUsers(Operation operation, List userNameList) + throws ApplicationManagementException { + + } + + @Override + public void installApplicationForUserRoles(Operation operation, List userRoleList) + throws ApplicationManagementException { + + } +} diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.plugin.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/plugin/impl/dao/VirtualFireAlarmDAO.java b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.plugin.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/plugin/impl/dao/VirtualFireAlarmDAO.java new file mode 100644 index 0000000000..8064c524e0 --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.plugin.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/plugin/impl/dao/VirtualFireAlarmDAO.java @@ -0,0 +1,125 @@ +/* + * 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.wso2.carbon.device.mgt.iot.virtualfirealarm.plugin.impl.dao; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.plugin.constants.VirtualFireAlarmConstants; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.plugin.exception.VirtualFirealarmDeviceMgtPluginException; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.plugin.impl.dao.impl.VirtualFireAlarmDeviceDAOImpl; +import javax.naming.Context; +import javax.naming.InitialContext; +import javax.naming.NamingException; +import javax.sql.DataSource; +import java.sql.Connection; +import java.sql.SQLException; + +public class VirtualFireAlarmDAO { + + private static final Log log = LogFactory.getLog(VirtualFireAlarmDAO.class); + static DataSource dataSource; + private static ThreadLocal currentConnection = new ThreadLocal(); + + public VirtualFireAlarmDAO() { + initFireAlarmDAO(); + } + + public static void initFireAlarmDAO() { + try { + Context ctx = new InitialContext(); + dataSource = (DataSource) ctx.lookup(VirtualFireAlarmConstants.DATA_SOURCE_NAME); + } catch (NamingException e) { + log.error("Error while looking up the data source: " + VirtualFireAlarmConstants.DATA_SOURCE_NAME); + } + } + + public VirtualFireAlarmDeviceDAOImpl getDeviceDAO() { + return new VirtualFireAlarmDeviceDAOImpl(); + } + + public static void beginTransaction() throws VirtualFirealarmDeviceMgtPluginException { + try { + Connection conn = dataSource.getConnection(); + conn.setAutoCommit(false); + currentConnection.set(conn); + } catch (SQLException e) { + throw new VirtualFirealarmDeviceMgtPluginException("Error occurred while retrieving datasource connection", e); + } + } + + public static Connection getConnection() throws VirtualFirealarmDeviceMgtPluginException { + if (currentConnection.get() == null) { + try { + currentConnection.set(dataSource.getConnection()); + } catch (SQLException e) { + throw new VirtualFirealarmDeviceMgtPluginException("Error occurred while retrieving data source connection", e); + } + } + return currentConnection.get(); + } + + public static void commitTransaction() throws VirtualFirealarmDeviceMgtPluginException { + 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 VirtualFirealarmDeviceMgtPluginException("Error occurred while committing the transaction", e); + } finally { + closeConnection(); + } + } + + public static void closeConnection() throws VirtualFirealarmDeviceMgtPluginException { + + 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 VirtualFirealarmDeviceMgtPluginException { + 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 VirtualFirealarmDeviceMgtPluginException("Error occurred while rollback the transaction", e); + } finally { + closeConnection(); + } + } +} \ No newline at end of file diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.plugin.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/plugin/impl/dao/impl/VirtualFireAlarmDeviceDAOImpl.java b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.plugin.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/plugin/impl/dao/impl/VirtualFireAlarmDeviceDAOImpl.java new file mode 100644 index 0000000000..c4896b0abc --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.plugin.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/plugin/impl/dao/impl/VirtualFireAlarmDeviceDAOImpl.java @@ -0,0 +1,196 @@ +/* + * 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.wso2.carbon.device.mgt.iot.virtualfirealarm.plugin.impl.dao.impl; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.device.mgt.common.Device; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.plugin.constants.VirtualFireAlarmConstants; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.plugin.exception.VirtualFirealarmDeviceMgtPluginException; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.plugin.impl.dao.VirtualFireAlarmDAO; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.plugin.impl.util.VirtualFireAlarmUtils; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; + +/** + * Implements CRUD for virtual firealarm Devices. + */ +public class VirtualFireAlarmDeviceDAOImpl { + + private static final Log log = LogFactory.getLog(VirtualFireAlarmDeviceDAOImpl.class); + + public Device getDevice(String deviceId) throws VirtualFirealarmDeviceMgtPluginException { + Connection conn = null; + PreparedStatement stmt = null; + Device device = null; + ResultSet resultSet = null; + try { + conn = VirtualFireAlarmDAO.getConnection(); + String selectDBQuery = + "SELECT VIRTUAL_FIREALARM_DEVICE_ID, DEVICE_NAME" + + " FROM VIRTUAL_FIREALARM_DEVICE WHERE VIRTUAL_FIREALARM_DEVICE_ID = ?"; + stmt = conn.prepareStatement(selectDBQuery); + stmt.setString(1, deviceId); + resultSet = stmt.executeQuery(); + + if (resultSet.next()) { + device = new Device(); + device.setName(resultSet.getString(VirtualFireAlarmConstants.DEVICE_PLUGIN_DEVICE_NAME)); + if (log.isDebugEnabled()) { + log.debug("Virtual Firealarm device " + deviceId + " data has been fetched from " + + "Virtual Firealarm database."); + } + } + } catch (SQLException e) { + String msg = "Error occurred while fetching Virtual Firealarm device : '" + deviceId + "'"; + log.error(msg, e); + throw new VirtualFirealarmDeviceMgtPluginException(msg, e); + } finally { + VirtualFireAlarmUtils.cleanupResources(stmt, resultSet); + VirtualFireAlarmDAO.closeConnection(); + } + + return device; + } + + public boolean addDevice(Device device) throws VirtualFirealarmDeviceMgtPluginException { + boolean status = false; + Connection conn = null; + PreparedStatement stmt = null; + try { + conn = VirtualFireAlarmDAO.getConnection(); + String createDBQuery = + "INSERT INTO VIRTUAL_FIREALARM_DEVICE(VIRTUAL_FIREALARM_DEVICE_ID, DEVICE_NAME) VALUES (?, ?)"; + + stmt = conn.prepareStatement(createDBQuery); + stmt.setString(1, device.getDeviceIdentifier()); + stmt.setString(2, device.getName()); + int rows = stmt.executeUpdate(); + if (rows > 0) { + status = true; + if (log.isDebugEnabled()) { + log.debug("Virtual Firealarm device " + device.getDeviceIdentifier() + " data has been" + + " added to the Virtual Firealarm database."); + } + } + } catch (SQLException e) { + String msg = "Error occurred while adding the Virtual Firealarm device '" + + device.getDeviceIdentifier() + "' to the Virtual Firealarm db."; + log.error(msg, e); + throw new VirtualFirealarmDeviceMgtPluginException(msg, e); + } finally { + VirtualFireAlarmUtils.cleanupResources(stmt, null); + } + return status; + } + + public boolean updateDevice(Device device) throws VirtualFirealarmDeviceMgtPluginException { + boolean status = false; + Connection conn = null; + PreparedStatement stmt = null; + try { + conn = VirtualFireAlarmDAO.getConnection(); + String updateDBQuery = + "UPDATE VIRTUAL_FIREALARM_DEVICE SET DEVICE_NAME = ? WHERE VIRTUAL_FIREALARM_DEVICE_ID = ?"; + + stmt = conn.prepareStatement(updateDBQuery); + stmt.setString(1, device.getName()); + stmt.setString(2, device.getDeviceIdentifier()); + int rows = stmt.executeUpdate(); + if (rows > 0) { + status = true; + if (log.isDebugEnabled()) { + log.debug("Virtualm Firealarm device " + device.getDeviceIdentifier() + " data has been" + + " modified."); + } + } + } catch (SQLException e) { + String msg = "Error occurred while modifying the Virtual Firealarm device '" + + device.getDeviceIdentifier() + "' data."; + log.error(msg, e); + throw new VirtualFirealarmDeviceMgtPluginException(msg, e); + } finally { + VirtualFireAlarmUtils.cleanupResources(stmt, null); + } + return status; + } + + public boolean deleteDevice(String iotDeviceId) throws VirtualFirealarmDeviceMgtPluginException { + boolean status = false; + Connection conn = null; + PreparedStatement stmt = null; + try { + conn = VirtualFireAlarmDAO.getConnection(); + String deleteDBQuery = "DELETE FROM VIRTUAL_FIREALARM_DEVICE WHERE VIRTUAL_FIREALARM_DEVICE_ID = ?"; + stmt = conn.prepareStatement(deleteDBQuery); + stmt.setString(1, iotDeviceId); + int rows = stmt.executeUpdate(); + if (rows > 0) { + status = true; + if (log.isDebugEnabled()) { + log.debug("Virtual Firealarm device " + iotDeviceId + " data has deleted" + + " from the Virtual Firealarm database."); + } + } + } catch (SQLException e) { + String msg = "Error occurred while deleting Virtual Firealarm device " + iotDeviceId; + log.error(msg, e); + throw new VirtualFirealarmDeviceMgtPluginException(msg, e); + } finally { + VirtualFireAlarmUtils.cleanupResources(stmt, null); + } + return status; + } + + public List getAllDevices() throws VirtualFirealarmDeviceMgtPluginException { + Connection conn; + PreparedStatement stmt = null; + ResultSet resultSet = null; + Device device; + List devices = new ArrayList<>(); + try { + conn = VirtualFireAlarmDAO.getConnection(); + String selectDBQuery = + "SELECT VIRTUAL_FIREALARM_DEVICE_ID, DEVICE_NAME FROM VIRTUAL_FIREALARM_DEVICE"; + stmt = conn.prepareStatement(selectDBQuery); + resultSet = stmt.executeQuery(); + while (resultSet.next()) { + device = new Device(); + device.setDeviceIdentifier(resultSet.getString(VirtualFireAlarmConstants.DEVICE_PLUGIN_DEVICE_ID)); + device.setName(resultSet.getString(VirtualFireAlarmConstants.DEVICE_PLUGIN_DEVICE_NAME)); + devices.add(device); + } + if (log.isDebugEnabled()) { + log.debug("All Virtual Firealarm device details have fetched from Firealarm database."); + } + return devices; + } catch (SQLException e) { + String msg = "Error occurred while fetching all Virtual Firealarm device data'"; + log.error(msg, e); + throw new VirtualFirealarmDeviceMgtPluginException(msg, e); + } finally { + VirtualFireAlarmUtils.cleanupResources(stmt, resultSet); + VirtualFireAlarmDAO.closeConnection(); + } + } +} \ No newline at end of file diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.plugin.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/plugin/impl/feature/VirtualFirealarmFeatureManager.java b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.plugin.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/plugin/impl/feature/VirtualFirealarmFeatureManager.java new file mode 100644 index 0000000000..7290b61794 --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.plugin.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/plugin/impl/feature/VirtualFirealarmFeatureManager.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2016, 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. + */ +package org.wso2.carbon.device.mgt.iot.virtualfirealarm.plugin.impl.feature; + +import org.wso2.carbon.device.mgt.common.DeviceManagementException; +import org.wso2.carbon.device.mgt.common.Feature; +import org.wso2.carbon.device.mgt.common.FeatureManager; +import org.wso2.carbon.device.mgt.extensions.feature.mgt.GenericFeatureManager; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.plugin.constants.VirtualFireAlarmConstants; + +import java.util.List; + +public class VirtualFirealarmFeatureManager implements FeatureManager{ + @Override + public boolean addFeature(Feature feature) throws DeviceManagementException { + return false; + } + + @Override + public boolean addFeatures(List features) throws DeviceManagementException { + return false; + } + + @Override + public Feature getFeature(String name) throws DeviceManagementException { + GenericFeatureManager genericFeatureManager = GenericFeatureManager.getInstance(); + return genericFeatureManager.getFeature(VirtualFireAlarmConstants.DEVICE_TYPE, name); + } + + @Override + public List getFeatures() throws DeviceManagementException { + GenericFeatureManager genericFeatureManager = GenericFeatureManager.getInstance(); + return genericFeatureManager.getFeatures(VirtualFireAlarmConstants.DEVICE_TYPE); + } + + @Override + public boolean removeFeature(String name) throws DeviceManagementException { + return false; + } + + @Override + public boolean addSupportedFeaturesToDB() throws DeviceManagementException { + return false; + } +} diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.plugin.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/plugin/impl/util/DeviceSchemaInitializer.java b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.plugin.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/plugin/impl/util/DeviceSchemaInitializer.java new file mode 100644 index 0000000000..450014502b --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.plugin.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/plugin/impl/util/DeviceSchemaInitializer.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2016, 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.wso2.carbon.device.mgt.iot.virtualfirealarm.plugin.impl.util; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.utils.CarbonUtils; +import org.wso2.carbon.utils.dbcreator.DatabaseCreator; + +import javax.sql.DataSource; +import java.io.File; + +/** + * Provides methods for initializing the database script. + */ +public class DeviceSchemaInitializer extends DatabaseCreator{ + + private static final Log log = LogFactory.getLog(DeviceSchemaInitializer.class); + private static final String setupSQLScriptBaseLocation = CarbonUtils.getCarbonHome() + File.separator + "dbscripts" + + File.separator + "cdm" + File.separator + "plugins" + File.separator; + + public DeviceSchemaInitializer(DataSource dataSource) { + super(dataSource); + } + + @Override + protected String getDbScriptLocation(String databaseType) { + String scriptName = databaseType + ".sql"; + if (log.isDebugEnabled()) { + log.debug("Loading database script from :" + scriptName); + } + return setupSQLScriptBaseLocation.replaceFirst("DBTYPE", databaseType) + scriptName; + } +} diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.plugin.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/plugin/impl/util/VirtualFireAlarmUtils.java b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.plugin.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/plugin/impl/util/VirtualFireAlarmUtils.java new file mode 100644 index 0000000000..216b41f246 --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.plugin.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/plugin/impl/util/VirtualFireAlarmUtils.java @@ -0,0 +1,111 @@ +/* + * 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.wso2.carbon.device.mgt.iot.virtualfirealarm.plugin.impl.util; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.device.mgt.common.Device; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.plugin.constants.VirtualFireAlarmConstants; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.plugin.exception.VirtualFirealarmDeviceMgtPluginException; + +import javax.naming.Context; +import javax.naming.InitialContext; +import javax.naming.NamingException; +import javax.sql.DataSource; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.List; +import java.util.Map; + +/** + * Contains utility methods used by FireAlarm plugin. + */ +public class VirtualFireAlarmUtils { + + private static Log log = LogFactory.getLog(VirtualFireAlarmUtils.class); + + public static String getDeviceProperty(List deviceProperties, String propertyKey) { + String deviceProperty = ""; + for(Device.Property property :deviceProperties){ + if(propertyKey.equals(property.getName())){ + deviceProperty = property.getValue(); + } + } + return deviceProperty; + } + + public static Device.Property getProperty(String property, String value) { + if (property != null) { + Device.Property prop = new Device.Property(); + prop.setName(property); + prop.setValue(value); + return prop; + } + return null; + } + + public static void cleanupResources(Connection conn, PreparedStatement stmt, ResultSet rs) { + if (rs != null) { + try { + rs.close(); + } catch (SQLException e) { + log.warn("Error occurred while closing result set", e); + } + } + if (stmt != null) { + try { + stmt.close(); + } catch (SQLException e) { + log.warn("Error occurred while closing prepared statement", e); + } + } + if (conn != null) { + try { + conn.close(); + } catch (SQLException e) { + log.warn("Error occurred while closing database connection", e); + } + } + } + + public static void cleanupResources(PreparedStatement stmt, ResultSet rs) { + cleanupResources(null, stmt, rs); + } + + /** + * Creates the device management schema. + */ + public static void setupDeviceManagementSchema() throws VirtualFirealarmDeviceMgtPluginException { + try { + Context ctx = new InitialContext(); + DataSource dataSource = (DataSource) ctx.lookup(VirtualFireAlarmConstants.DATA_SOURCE_NAME); + DeviceSchemaInitializer initializer = + new DeviceSchemaInitializer(dataSource); + log.info("Initializing device management repository database schema"); + initializer.createRegistryDatabase(); + } catch (NamingException e) { + log.error("Error while looking up the data source: " + VirtualFireAlarmConstants.DATA_SOURCE_NAME); + } catch (Exception e) { + throw new VirtualFirealarmDeviceMgtPluginException("Error occurred while initializing Iot Device " + + "Management database schema", e); + } + } +} diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.plugin.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/plugin/internal/VirtualFirealarmManagementServiceComponent.java b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.plugin.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/plugin/internal/VirtualFirealarmManagementServiceComponent.java new file mode 100644 index 0000000000..bdc3d3a758 --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.plugin.impl/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/plugin/internal/VirtualFirealarmManagementServiceComponent.java @@ -0,0 +1,84 @@ +/* + * 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.wso2.carbon.device.mgt.iot.virtualfirealarm.plugin.internal; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.osgi.framework.BundleContext; +import org.osgi.framework.ServiceRegistration; +import org.osgi.service.component.ComponentContext; +import org.wso2.carbon.device.mgt.common.spi.DeviceManagementService; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.plugin.exception.VirtualFirealarmDeviceMgtPluginException; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.plugin.impl.VirtualFireAlarmManagerService; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.plugin.impl.util.VirtualFireAlarmUtils; + +/** + * @scr.component name="org.wso2.carbon.device.mgt.iot.virtualfirealarm.plugin.internal + * .VirtualFirealarmManagementServiceComponent" + * immediate="true" + */ +public class VirtualFirealarmManagementServiceComponent { + + private static final Log log = LogFactory.getLog(VirtualFirealarmManagementServiceComponent.class); + private ServiceRegistration firealarmServiceRegRef; + + protected void activate(ComponentContext ctx) { + if (log.isDebugEnabled()) { + log.debug("Activating Virtual Firealarm Device Management Service Component"); + } + try { + BundleContext bundleContext = ctx.getBundleContext(); + firealarmServiceRegRef = bundleContext.registerService(DeviceManagementService.class.getName(), + new VirtualFireAlarmManagerService(), null); + String setupOption = System.getProperty("setup"); + if (setupOption != null) { + if (log.isDebugEnabled()) { + log.debug("-Dsetup is enabled. Iot Device management repository schema initialization is about " + + "to begin"); + } + try { + VirtualFireAlarmUtils.setupDeviceManagementSchema(); + } catch (VirtualFirealarmDeviceMgtPluginException e) { + log.error("Exception occurred while initializing device management database schema", e); + } + } + if (log.isDebugEnabled()) { + log.debug("Virtual Firealarm Device Management Service Component has been successfully activated"); + } + } catch (Throwable e) { + log.error("Error occurred while activating Virtual Firealarm Device Management Service Component", e); + } + } + + protected void deactivate(ComponentContext ctx) { + if (log.isDebugEnabled()) { + log.debug("De-activating Virtual Firealarm Device Management Service Component"); + } + try { + if (firealarmServiceRegRef != null) { + firealarmServiceRegRef.unregister(); + } + if (log.isDebugEnabled()) { + log.debug("Virtual Firealarm Device Management Service Component has been successfully de-activated"); + } + } catch (Throwable e) { + log.error("Error occurred while de-activating Virtual Firealarm Device Management bundle", e); + } + } +} diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.ui/pom.xml b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.ui/pom.xml new file mode 100644 index 0000000000..77cd6f2f7c --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.ui/pom.xml @@ -0,0 +1,62 @@ + + + + + + + + virtual-fire-alarm-plugin + org.wso2.carbon.devicemgt-plugins + 2.1.0-SNAPSHOT + ../pom.xml + + + 4.0.0 + org.wso2.carbon.device.mgt.iot.virtualfirealarm.ui + WSO2 Carbon - IoT Server Virtual Firealarm UI + pom + + + + + maven-assembly-plugin + 2.5.5 + + ${project.artifactId}-${carbon.device.mgt.version} + false + + src/assembly/src.xml + + + + + create-archive + package + + single + + + + + + + + \ No newline at end of file diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.ui/src/assembly/src.xml b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.ui/src/assembly/src.xml new file mode 100644 index 0000000000..2797034e07 --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.ui/src/assembly/src.xml @@ -0,0 +1,36 @@ + + + + src + + zip + + false + ${basedir}/src + + + ${basedir}/src/main/resources/jaggeryapps/devicemgt + / + true + + + \ No newline at end of file diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.virtual_firealarm.device-view/device-view.hbs b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.virtual_firealarm.device-view/device-view.hbs new file mode 100644 index 0000000000..c6d599d8e9 --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.virtual_firealarm.device-view/device-view.hbs @@ -0,0 +1,105 @@ +{{#zone "topCss"}} + +{{/zone}} + +{{#zone "device-thumbnail"}} + +{{/zone}} + +{{#zone "device-opetations"}} +

      + Operations +
      +
      + {{unit "iot.unit.device.operation-bar" device=device}} +
      +{{/zone}} + +{{#zone "device-detail-properties"}} +
      + +
      +
      + +
      +
      Device Statistics
      + {{unit "iot.unit.device.stats" device=device}} +
      + +
      +
      Policies
      +
      + +
      +
      + No policies found +
      +
      +
      +
      + + + + + Add device specific policy +
      +
      +
      Operations Log
      +
      + +
      +
      + Not available yet +
      +
      +
      +
      +
      +
      +
      +
      +{{/zone}} + +{{#zone "bottomJs"}} + +{{/zone}} \ No newline at end of file diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.virtual_firealarm.device-view/device-view.js b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.virtual_firealarm.device-view/device-view.js new file mode 100644 index 0000000000..dbec07bd6d --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.virtual_firealarm.device-view/device-view.js @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2016, 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("device-view.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") { + return {"device": device}; + } else { + response.sendError(404, "Device Id " + deviceId + " of type " + deviceType + " cannot be found!"); + exit(); + } + } +} \ No newline at end of file diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.virtual_firealarm.device-view/device-view.json b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.virtual_firealarm.device-view/device-view.json new file mode 100644 index 0000000000..9eecd8f5bf --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.virtual_firealarm.device-view/device-view.json @@ -0,0 +1,3 @@ +{ + "version": "1.0.0" +} \ No newline at end of file diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.virtual_firealarm.device-view/public/images/firealarm-icon.png b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.virtual_firealarm.device-view/public/images/firealarm-icon.png new file mode 100644 index 0000000000..7c5808697e Binary files /dev/null and b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.virtual_firealarm.device-view/public/images/firealarm-icon.png differ diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.virtual_firealarm.device-view/public/images/thumb.png b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.virtual_firealarm.device-view/public/images/thumb.png new file mode 100644 index 0000000000..8069604984 Binary files /dev/null and b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.virtual_firealarm.device-view/public/images/thumb.png differ diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.virtual_firealarm.policy-edit/policy-edit.hbs b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.virtual_firealarm.policy-edit/policy-edit.hbs new file mode 100644 index 0000000000..6383583446 --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.virtual_firealarm.policy-edit/policy-edit.hbs @@ -0,0 +1 @@ +{{unit "iot.unit.policy.edit"}} \ No newline at end of file diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.virtual_firealarm.policy-edit/policy-edit.json b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.virtual_firealarm.policy-edit/policy-edit.json new file mode 100644 index 0000000000..9eecd8f5bf --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.virtual_firealarm.policy-edit/policy-edit.json @@ -0,0 +1,3 @@ +{ + "version": "1.0.0" +} \ No newline at end of file diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.virtual_firealarm.policy-view/policy-view.hbs b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.virtual_firealarm.policy-view/policy-view.hbs new file mode 100644 index 0000000000..00e27f7cf1 --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.virtual_firealarm.policy-view/policy-view.hbs @@ -0,0 +1 @@ +{{unit "iot.unit.policy.view"}} \ No newline at end of file diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.virtual_firealarm.policy-view/policy-view.json b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.virtual_firealarm.policy-view/policy-view.json new file mode 100644 index 0000000000..9eecd8f5bf --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.virtual_firealarm.policy-view/policy-view.json @@ -0,0 +1,3 @@ +{ + "version": "1.0.0" +} \ No newline at end of file diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.virtual_firealarm.policy-wizard/policy-wizard.hbs b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.virtual_firealarm.policy-wizard/policy-wizard.hbs new file mode 100644 index 0000000000..3a0c0687fb --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.virtual_firealarm.policy-wizard/policy-wizard.hbs @@ -0,0 +1 @@ +{{unit "iot.unit.policy.wizard"}} \ No newline at end of file diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.virtual_firealarm.policy-wizard/policy-wizard.json b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.virtual_firealarm.policy-wizard/policy-wizard.json new file mode 100644 index 0000000000..9eecd8f5bf --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.virtual_firealarm.policy-wizard/policy-wizard.json @@ -0,0 +1,3 @@ +{ + "version": "1.0.0" +} \ No newline at end of file diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.virtual_firealarm.type-view/public/images/firealarm-icon.png b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.virtual_firealarm.type-view/public/images/firealarm-icon.png new file mode 100644 index 0000000000..7c5808697e Binary files /dev/null and b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.virtual_firealarm.type-view/public/images/firealarm-icon.png differ diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.virtual_firealarm.type-view/public/images/myDevices_analytics.png b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.virtual_firealarm.type-view/public/images/myDevices_analytics.png new file mode 100644 index 0000000000..2fd8075ff4 Binary files /dev/null and b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.virtual_firealarm.type-view/public/images/myDevices_analytics.png differ diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.virtual_firealarm.type-view/public/images/schematicsGuide.png b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.virtual_firealarm.type-view/public/images/schematicsGuide.png new file mode 100644 index 0000000000..6933489b9e Binary files /dev/null and b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.virtual_firealarm.type-view/public/images/schematicsGuide.png differ diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.virtual_firealarm.type-view/public/images/thumb.png b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.virtual_firealarm.type-view/public/images/thumb.png new file mode 100644 index 0000000000..8069604984 Binary files /dev/null and b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.virtual_firealarm.type-view/public/images/thumb.png differ diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.virtual_firealarm.type-view/public/js/download.js b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.virtual_firealarm.type-view/public/js/download.js new file mode 100644 index 0000000000..c9dd69c057 --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.virtual_firealarm.type-view/public/js/download.js @@ -0,0 +1,184 @@ +/* + * Copyright (c) 2016, 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. + */ + +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))); +} + +/* + * Shows agent download popup. + */ +function showAgentDownloadPopup() { + $(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; + } + }); +} + +/* + * Hides agent download popup. + */ +function hideAgentDownloadPopup() { + $('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()); + showAgentDownloadPopup(); + 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 () { + hideAgentDownloadPopup(); + }); + + }); +} + +function downloadAgent() { + var deviceName; + $('.new-device-name').each(function () { + if (this.value != "") { + deviceName = this.value; + } + }); + var deviceNameFormat = /^[^~?!#$:;%^*`+={}\[\]\\()|<>,'"]{1,30}$/; + if (deviceName && deviceNameFormat.test(deviceName)) { + $('#downloadForm').submit(); + hideAgentDownloadPopup(); + $(modalPopupContent).html($('#device-agent-downloading-content').html()); + showAgentDownloadPopup(); + setTimeout(function () { + hideAgentDownloadPopup(); + }, 1000); + } else { + $("#invalid-username-error-msg span").text("Invalid device name"); + $("#invalid-username-error-msg").removeClass("hidden"); + } +} + +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(); + }); + showAgentDownloadPopup(); + } else if (data.status == "401") { + $(modalPopupContent).html($('#device-401-content').html()); + $("#device-401-link").click(function () { + window.location = "/devicemgt/login"; + }); + showAgentDownloadPopup(); + } else if (data == "403") { + $(modalPopupContent).html($('#device-403-content').html()); + $("#device-403-link").click(function () { + window.location = "/devicemgt/login"; + }); + showAgentDownloadPopup(); + } else { + $(modalPopupContent).html($('#device-unexpected-error-content').html()); + $("a#device-unexpected-error-link").click(function () { + hideAgentDownloadPopup(); + }); + } +} \ No newline at end of file diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.virtual_firealarm.type-view/public/js/jquery.validate.js b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.virtual_firealarm.type-view/public/js/jquery.validate.js new file mode 100644 index 0000000000..fe7ecf07a6 --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.virtual_firealarm.type-view/public/js/jquery.validate.js @@ -0,0 +1,1227 @@ +/* + * Copyright (c) 2016, 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($) { + +$.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/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.virtual_firealarm.type-view/type-view.hbs b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.virtual_firealarm.type-view/type-view.hbs new file mode 100644 index 0000000000..a27f5547ae --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.virtual_firealarm.type-view/type-view.hbs @@ -0,0 +1,297 @@ +
      +

      Virtual Firealarm

      +
      +
      +
      + +
      +
      + +
      +
      +

      What it Does

      +
      +

      A Virtual Device that mimics the functionality of a real Firealarm. + Once run, the Virtual Firealarm will connect to WSO2 IoTServer and + push Temperature readings.

      +

      The device supports MQTT and XMPP Communications. It is configured to use MQTT by + default.

      +
      +

      What You Need

      +
      +
        +
      • + STEP 01 +    Go ahead and [Download] the Device. +
      • +
      • + STEP 02 +    Proceed to [Prepare] section. +
      • +
      • + STEP 03 +    Read [Try Out] section to further experiment with the device. +
      • +
      +
      + View API   + + + Download Agent +
      + +
      +
      + +
      +
      + +
      +
      + +
      +
      + +
      +
      + +
      +
      + +
      +

      +
      +
      +

      Prepare

      +
      +
        +
      • + 01 +    Download your VirtualFireAlarm using [Download Agent] button above. +
      • +
      • + 02 +    Unzip the downloaded Agent. +
      • +
      • + 03 +    Move into the unzipped Agent folder in the terminal. +
      • +
      • + 04 +    Unzip the downloaded Agent and start terminal to run this command: [sh + start-device.sh] +
      • +
      +
      +
      +
      +

      The Virtual-Fire-Alarm Device

      +
      +

      Click on the image to zoom

      +
      + + + +
      +
      +
      +
      +

      Try Out

      +
      +
        +
      • + 01 +    You can view all your connected devices at + [Device Management] page. +
      • +
      • + 02 +    Select one of connected devices and check for available control + operations and monitor Real-Time data. +
      • +
      • + 03 +    You can also view analytics of the data published to IoT-Server by + navigating to Device Analytics page. +
      • +
      +
      +

      Click on the image to zoom

      +
      + + + +
      +
      + +{{#zone "topCss"}} + +{{/zone}} + +{{#zone "bottomJs"}} + {{js "/js/download.js"}} + {{js "/js/jquery.validate.js"}} +{{/zone}} diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.virtual_firealarm.type-view/type-view.json b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.virtual_firealarm.type-view/type-view.json new file mode 100644 index 0000000000..9eecd8f5bf --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/org.wso2.carbon.device.mgt.iot.virtualfirealarm.ui/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.virtual_firealarm.type-view/type-view.json @@ -0,0 +1,3 @@ +{ + "version": "1.0.0" +} \ No newline at end of file diff --git a/components/iot-plugins/virtual-fire-alarm-plugin/pom.xml b/components/iot-plugins/virtual-fire-alarm-plugin/pom.xml new file mode 100644 index 0000000000..7bac29dcd4 --- /dev/null +++ b/components/iot-plugins/virtual-fire-alarm-plugin/pom.xml @@ -0,0 +1,64 @@ + + + + + + + org.wso2.carbon.devicemgt-plugins + iot-plugins + 2.1.0-SNAPSHOT + ../pom.xml + + + 4.0.0 + virtual-fire-alarm-plugin + pom + WSO2 Carbon - Arduino Plugin + http://wso2.org + + + org.wso2.carbon.device.mgt.iot.virtualfirealarm.manager.service.impl + org.wso2.carbon.device.mgt.iot.virtualfirealarm.plugin.impl + org.wso2.carbon.device.mgt.iot.virtualfirealarm.ui + org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.impl + org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.impl + org.wso2.carbon.device.mgt.iot.virtualfirealarm.analytics + org.wso2.carbon.device.mgt.iot.virtualfirealarm.controller.service.impl + + + + + + + org.apache.felix + maven-scr-plugin + 1.7.2 + + + generate-scr-scrdescriptor + + scr + + + + + + + + diff --git a/components/key-mgt/org.wso2.carbon.key.mgt.handler.valve/pom.xml b/components/key-mgt/org.wso2.carbon.key.mgt.handler.valve/pom.xml deleted file mode 100644 index 2f548409a6..0000000000 --- a/components/key-mgt/org.wso2.carbon.key.mgt.handler.valve/pom.xml +++ /dev/null @@ -1,83 +0,0 @@ - - - - - - org.wso2.carbon.devicemgt-plugins - key-mgt - 1.9.0-SNAPSHOT - ../pom.xml - - - 4.0.0 - org.wso2.carbon.devicemgt-plugins - org.wso2.carbon.key.mgt.handler.valve - 1.9.0-SNAPSHOT - bundle - WSO2 Carbon - Key Management Handler Valve - WSO2 Carbon - Key Management Handler Valve - http://wso2.org - - - - - org.apache.felix - maven-bundle-plugin - true - - - ${project.artifactId} - ${project.artifactId} - org.wso2.carbon.tomcat.patch - - org.wso2.carbon.key.mgt.handler.valve.* - - tomcat - - - - - - - - - org.apache.tomcat.wso2 - tomcat - - - org.wso2.carbon - org.wso2.carbon.apimgt.core - - - org.wso2.carbon - org.wso2.carbon.apimgt.impl - - - org.wso2.carbon - org.wso2.carbon.logging - - - org.wso2.carbon - org.wso2.carbon.tomcat.ext - - - - - diff --git a/components/key-mgt/org.wso2.carbon.key.mgt.handler.valve/src/main/java/org/wso2/carbon/key/mgt/handler/valve/HandlerUtil.java b/components/key-mgt/org.wso2.carbon.key.mgt.handler.valve/src/main/java/org/wso2/carbon/key/mgt/handler/valve/HandlerUtil.java deleted file mode 100644 index 69a4303802..0000000000 --- a/components/key-mgt/org.wso2.carbon.key.mgt.handler.valve/src/main/java/org/wso2/carbon/key/mgt/handler/valve/HandlerUtil.java +++ /dev/null @@ -1,141 +0,0 @@ -/* - * 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.wso2.carbon.key.mgt.handler.valve; - -import org.apache.axiom.om.OMAbstractFactory; -import org.apache.axiom.om.OMElement; -import org.apache.axiom.om.OMFactory; -import org.apache.axiom.om.OMNamespace; -import org.apache.catalina.connector.Response; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.wso2.carbon.apimgt.api.APIManagementException; -import org.wso2.carbon.apimgt.core.APIManagerErrorConstants; -import org.wso2.carbon.apimgt.core.authenticate.APITokenValidator; -import org.wso2.carbon.apimgt.impl.APIConstants; -import org.wso2.carbon.apimgt.impl.dto.APIKeyValidationInfoDTO; -import org.wso2.carbon.context.PrivilegedCarbonContext; -import org.wso2.carbon.identity.base.IdentityException; -import org.wso2.carbon.identity.core.util.IdentityUtil; - -import javax.servlet.http.HttpServletRequest; -import java.io.IOException; - -public class HandlerUtil { - - private static APIKeyValidationInfoDTO apiKeyValidationDTO; - private static final Log log = LogFactory.getLog(HandlerUtil.class); - - /** - * Retrieve bearer token form the HTTP header - * @param bearerToken Bearer Token extracted out of the corresponding HTTP header - */ - public static String getAccessToken(String bearerToken) { - String accessToken = null; - String[] token = bearerToken.split(HandlerConstants.TOKEN_NAME_BEARER); - if (token.length > 1 && token[1] != null) { - accessToken = token[1].trim(); - } - return accessToken; - } - - public static String getAPIVersion(HttpServletRequest request) { - int contextStartsIndex = (request.getRequestURI()).indexOf(request.getContextPath()) + 1; - int length = request.getContextPath().length(); - String afterContext = (request.getRequestURI()).substring(contextStartsIndex + length); - int SlashIndex = afterContext.indexOf(("/")); - - if (SlashIndex != -1) { - return afterContext.substring(0, SlashIndex); - } else { - return afterContext; - } - } - - public static void handleNoMatchAuthSchemeCallForRestService(Response response,String httpVerb, String reqUri, - String version, String context ) { - String errMsg = "Resource is not matched for HTTP Verb " + httpVerb + ". API context " + context + - ",version " + version + ", request " + reqUri; - APIFaultException e = new APIFaultException( APIManagerErrorConstants.API_AUTH_INCORRECT_API_RESOURCE, errMsg); - String faultPayload = getFaultPayload(e, APIManagerErrorConstants.API_SECURITY_NS, - APIManagerErrorConstants.API_SECURITY_NS_PREFIX).toString(); - handleRestFailure(response, faultPayload); - } - - public static boolean doAuthenticate(String context, String version, String accessToken, - String requiredAuthenticationLevel, String clientDomain) - throws APIManagementException, - APIFaultException { - - if (APIConstants.AUTH_NO_AUTHENTICATION.equals(requiredAuthenticationLevel)) { - return true; - } - APITokenValidator tokenValidator = new APITokenValidator(); - apiKeyValidationDTO = tokenValidator.validateKey(context, version, accessToken, - requiredAuthenticationLevel, clientDomain); - if (apiKeyValidationDTO.isAuthorized()) { - String userName = apiKeyValidationDTO.getEndUserName(); - PrivilegedCarbonContext.getThreadLocalCarbonContext() - .setUsername(apiKeyValidationDTO.getEndUserName()); - try { - PrivilegedCarbonContext.getThreadLocalCarbonContext() - .setTenantId(IdentityUtil.getTenantIdOFUser(userName)); - } catch (IdentityException e) { - log.error("Error while retrieving Tenant Id", e); - return false; - } - return true; - } else { - throw new APIFaultException(apiKeyValidationDTO.getValidationStatus(), - "Access failure for API: " + context + ", version: " + - version + " with key: " + accessToken); - } - } - - public static void handleRestFailure(Response response, String payload) { - response.setStatus(403); - response.setContentType("application/xml"); - response.setCharacterEncoding("UTF-8"); - try { - response.getWriter().write(payload); - } catch (IOException e) { - log.error("Error in sending fault response", e); - } - } - - public static OMElement getFaultPayload(APIFaultException exception, String FaultNS, - String FaultNSPrefix) { - OMFactory fac = OMAbstractFactory.getOMFactory(); - OMNamespace ns = fac.createOMNamespace(FaultNS, FaultNSPrefix); - OMElement payload = fac.createOMElement("fault", ns); - - OMElement errorCode = fac.createOMElement("code", ns); - errorCode.setText(String.valueOf(exception.getErrorCode())); - OMElement errorMessage = fac.createOMElement("message", ns); - errorMessage.setText(APIManagerErrorConstants.getFailureMessage(exception.getErrorCode())); - OMElement errorDetail = fac.createOMElement("description", ns); - errorDetail.setText(exception.getMessage()); - - payload.addChild(errorCode); - payload.addChild(errorMessage); - payload.addChild(errorDetail); - return payload; - } - -} diff --git a/components/key-mgt/org.wso2.carbon.key.mgt.handler.valve/src/main/java/org/wso2/carbon/key/mgt/handler/valve/OAuthTokenValidatorValve.java b/components/key-mgt/org.wso2.carbon.key.mgt.handler.valve/src/main/java/org/wso2/carbon/key/mgt/handler/valve/OAuthTokenValidatorValve.java deleted file mode 100644 index 23fd9a8f95..0000000000 --- a/components/key-mgt/org.wso2.carbon.key.mgt.handler.valve/src/main/java/org/wso2/carbon/key/mgt/handler/valve/OAuthTokenValidatorValve.java +++ /dev/null @@ -1,144 +0,0 @@ -/* - * 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.wso2.carbon.key.mgt.handler.valve; - -import org.apache.catalina.connector.Request; -import org.apache.catalina.connector.Response; -import org.apache.catalina.valves.ValveBase; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.wso2.carbon.apimgt.api.APIManagementException; -import org.wso2.carbon.apimgt.core.authenticate.APITokenValidator; -import org.wso2.carbon.apimgt.core.gateway.APITokenAuthenticator; -import org.wso2.carbon.apimgt.impl.APIConstants; -import org.wso2.carbon.apimgt.impl.dao.ApiMgtDAO; -import org.wso2.carbon.apimgt.impl.utils.APIUtil; - -import javax.servlet.ServletException; -import java.io.IOException; -import java.util.Enumeration; - -public class OAuthTokenValidatorValve extends ValveBase { - - private static final Log log = LogFactory.getLog(OAuthTokenValidatorValve.class); - - APITokenAuthenticator authenticator; - - public OAuthTokenValidatorValve() { - authenticator = new APITokenAuthenticator(); - } - - @Override - public void invoke(Request request, Response response) throws java.io.IOException, javax.servlet.ServletException { - String context = request.getContextPath(); - if (context == null || context.equals("")) { - //Invoke the next valve in handler chain. - getNext().invoke(request, response); - return; - } - - boolean contextExist; - Boolean contextValueInCache = null; - if (APIUtil.getAPIContextCache().get(context) != null) { - contextValueInCache = Boolean.parseBoolean(APIUtil.getAPIContextCache().get(context).toString()); - } - - if (contextValueInCache != null) { - contextExist = contextValueInCache; - } else { - contextExist = ApiMgtDAO.isContextExist(context); - APIUtil.getAPIContextCache().put(context, contextExist); - } - - if (!contextExist) { - getNext().invoke(request, response); - return; - } - - try { - handleWSDLGetRequest(request, response, context); - } catch (IOException e) { - e.printStackTrace(); - } catch (ServletException e) { - e.printStackTrace(); - } - - String authHeader = request.getHeader(APIConstants.OperationParameter.AUTH_PARAM_NAME); - String accessToken = null; - - /* Authenticate*/ - try { - if (authHeader != null) { - accessToken = HandlerUtil.getAccessToken(authHeader); - } else { - // There can be some API published with None Auth Type - /* - * throw new - * APIFaultException(APIConstants.KeyValidationStatus - * .API_AUTH_INVALID_CREDENTIALS, - * "Invalid format for Authorization header. Expected 'Bearer '" - * ); - */ - } - - String apiVersion = HandlerUtil.getAPIVersion(request); - String domain = request.getHeader(APITokenValidator.getAPIManagerClientDomainHeader()); - String authLevel = authenticator.getResourceAuthenticationScheme(context, - apiVersion, - request.getRequestURI(), - request.getMethod()); - if (HandlerConstants.NO_MATCHING_AUTH_SCHEME.equals(authLevel)) { - HandlerUtil.handleNoMatchAuthSchemeCallForRestService(response, - request.getMethod(), request.getRequestURI(), - apiVersion, context); - return; - } else { - HandlerUtil.doAuthenticate(context, apiVersion, accessToken, authLevel, domain); - } - } catch (APIManagementException e) { - //ignore - } catch (APIFaultException e) { - log.error("Error occurred while key validation", e); - return; - } - - getNext().invoke(request, response); - } - - private void handleWSDLGetRequest(Request request, Response response, - String context) throws IOException, ServletException { - if (request.getMethod().equals("GET")) { - // TODO:Need to get these paths from a config file. - if (request.getRequestURI().matches(context + "/[^/]*/services")) { - getNext().invoke(request, response); - return; - } - Enumeration params = request.getParameterNames(); - String paramName; - while (params.hasMoreElements()) { - paramName = params.nextElement(); - if (paramName.endsWith("wsdl") || paramName.endsWith("wadl")) { - getNext().invoke(request, response); - return; - } - } - } - } - -} diff --git a/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/pom.xml b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/pom.xml new file mode 100644 index 0000000000..0da2c63953 --- /dev/null +++ b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/pom.xml @@ -0,0 +1,180 @@ + + + + + + android-plugin + org.wso2.carbon.devicemgt-plugins + 2.1.0-SNAPSHOT + ../pom.xml + + + 4.0.0 + org.wso2.carbon.device.mgt.mobile.android.agent + WSO2 Carbon - Android JAX-RS API + Android JAX-RS API + war + + + + + maven-compiler-plugin + + 1.7 + 1.7 + + 2.3.2 + + + maven-war-plugin + 2.2 + + WEB-INF/lib/*cxf*.jar + ${project.artifactId} + + + + + + + + deploy + + compile + + + org.apache.maven.plugins + maven-antrun-plugin + 1.7 + + + compile + + run + + + + + + + + + + + + + + + + + + + client + + test + + + org.codehaus.mojo + exec-maven-plugin + 1.2.1 + + + test + + java + + + + + + + + + + + + + org.apache.cxf + cxf-rt-frontend-jaxws + + + org.apache.cxf + cxf-rt-frontend-jaxrs + + + org.apache.cxf + cxf-rt-transports-http + + + javax.ws.rs + jsr311-api + provided + + + org.wso2.carbon + org.wso2.carbon.utils + provided + + + org.wso2.carbon + org.wso2.carbon.logging + provided + + + + + org.wso2.carbon.devicemgt + org.wso2.carbon.device.mgt.common + provided + + + org.wso2.carbon.devicemgt + org.wso2.carbon.device.mgt.core + provided + + + org.wso2.carbon.devicemgt + org.wso2.carbon.policy.mgt.common + provided + + + org.wso2.carbon.devicemgt + org.wso2.carbon.policy.mgt.core + provided + + + org.wso2.carbon.devicemgt-plugins + org.wso2.carbon.device.mgt.mobile.android + provided + + + + commons-httpclient.wso2 + commons-httpclient + provided + + + + com.google.code.gson + gson + + + diff --git a/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/ConfigurationMgtService.java b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/ConfigurationMgtService.java new file mode 100644 index 0000000000..565393e498 --- /dev/null +++ b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/ConfigurationMgtService.java @@ -0,0 +1,161 @@ +/* + * 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.wso2.carbon.mdm.services.android; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.device.mgt.common.DeviceManagementConstants; +import org.wso2.carbon.device.mgt.common.DeviceManagementException; +import org.wso2.carbon.device.mgt.common.configuration.mgt.ConfigurationEntry; +import org.wso2.carbon.device.mgt.common.configuration.mgt.TenantConfiguration; +import org.wso2.carbon.device.mgt.common.license.mgt.License; +import org.wso2.carbon.mdm.services.android.exception.AndroidAgentException; +import org.wso2.carbon.mdm.services.android.util.AndroidAPIUtils; +import org.wso2.carbon.mdm.services.android.util.AndroidConstants; +import org.wso2.carbon.mdm.services.android.util.Message; + +import javax.jws.WebService; +import javax.ws.rs.*; +import javax.ws.rs.core.Response; +import java.util.ArrayList; +import java.util.List; + +/** + * Android Platform Configuration REST-API implementation. + * All end points supports JSON, XMl with content negotiation. + */ +@WebService +@Produces({ "application/json", "application/xml" }) +@Consumes({ "application/json", "application/xml" }) +public class ConfigurationMgtService { + + private static Log log = LogFactory.getLog(ConfigurationMgtService.class); + + @POST + public Message configureSettings(TenantConfiguration configuration) + throws AndroidAgentException { + + Message responseMsg = new Message(); + String msg; + ConfigurationEntry licenseEntry = null; + try { + configuration.setType(DeviceManagementConstants.MobileDeviceTypes.MOBILE_DEVICE_TYPE_ANDROID); + List configs = configuration.getConfiguration(); + for(ConfigurationEntry entry : configs){ + if(AndroidConstants.TenantConfigProperties.LICENSE_KEY.equals(entry.getName())){ + License license = new License(); + license.setName(DeviceManagementConstants.MobileDeviceTypes.MOBILE_DEVICE_TYPE_ANDROID); + license.setLanguage(AndroidConstants.TenantConfigProperties.LANGUAGE_US); + license.setVersion("1.0.0"); + license.setText(entry.getValue().toString()); + AndroidAPIUtils.getDeviceManagementService().addLicense(DeviceManagementConstants. + MobileDeviceTypes.MOBILE_DEVICE_TYPE_ANDROID, license); + licenseEntry = entry; + break; + } + } + + if(licenseEntry != null) { + configs.remove(licenseEntry); + } + configuration.setConfiguration(configs); + AndroidAPIUtils.getDeviceManagementService().saveConfiguration(configuration); + Response.status(Response.Status.CREATED); + responseMsg.setResponseMessage("Android platform configuration saved successfully."); + responseMsg.setResponseCode(Response.Status.CREATED.toString()); + } catch (DeviceManagementException e) { + msg = "Error occurred while configuring the android platform"; + log.error(msg, e); + throw new AndroidAgentException(msg, e); + } + return responseMsg; + } + + @GET + public TenantConfiguration getConfiguration() throws AndroidAgentException { + String msg; + TenantConfiguration tenantConfiguration; + List configs; + try { + tenantConfiguration = AndroidAPIUtils.getDeviceManagementService(). + getConfiguration(DeviceManagementConstants.MobileDeviceTypes.MOBILE_DEVICE_TYPE_ANDROID); + if(tenantConfiguration != null) { + configs = tenantConfiguration.getConfiguration(); + } else { + tenantConfiguration = new TenantConfiguration(); + configs = new ArrayList(); + } + + ConfigurationEntry entry = new ConfigurationEntry(); + License license = AndroidAPIUtils.getDeviceManagementService().getLicense( + DeviceManagementConstants.MobileDeviceTypes.MOBILE_DEVICE_TYPE_ANDROID, AndroidConstants. + TenantConfigProperties.LANGUAGE_US); + + if(license != null && configs != null) { + entry.setContentType(AndroidConstants.TenantConfigProperties.CONTENT_TYPE_TEXT); + entry.setName(AndroidConstants.TenantConfigProperties.LICENSE_KEY); + entry.setValue(license.getText()); + configs.add(entry); + tenantConfiguration.setConfiguration(configs); + } + } catch (DeviceManagementException e) { + msg = "Error occurred while retrieving the Android tenant configuration"; + log.error(msg, e); + throw new AndroidAgentException(msg, e); + } + return tenantConfiguration; + } + + @PUT + public Message updateConfiguration(TenantConfiguration configuration) throws AndroidAgentException { + String msg; + Message responseMsg = new Message(); + ConfigurationEntry licenseEntry = null; + try { + configuration.setType(DeviceManagementConstants.MobileDeviceTypes.MOBILE_DEVICE_TYPE_ANDROID); + List configs = configuration.getConfiguration(); + for(ConfigurationEntry entry : configs){ + if(AndroidConstants.TenantConfigProperties.LICENSE_KEY.equals(entry.getName())){ + License license = new License(); + license.setName(DeviceManagementConstants.MobileDeviceTypes.MOBILE_DEVICE_TYPE_ANDROID); + license.setLanguage(AndroidConstants.TenantConfigProperties.LANGUAGE_US); + license.setVersion("1.0.0"); + license.setText(entry.getValue().toString()); + AndroidAPIUtils.getDeviceManagementService().addLicense(DeviceManagementConstants. + MobileDeviceTypes.MOBILE_DEVICE_TYPE_ANDROID, license); + licenseEntry = entry; + } + } + + if(licenseEntry != null) { + configs.remove(licenseEntry); + } + configuration.setConfiguration(configs); + AndroidAPIUtils.getDeviceManagementService().saveConfiguration(configuration); + Response.status(Response.Status.ACCEPTED); + responseMsg.setResponseMessage("Android platform configuration has updated successfully."); + responseMsg.setResponseCode(Response.Status.ACCEPTED.toString()); + } catch (DeviceManagementException e) { + msg = "Error occurred while modifying configuration settings of Android platform"; + log.error(msg, e); + throw new AndroidAgentException(msg, e); + } + return responseMsg; + } +} diff --git a/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/DeviceManagementService.java b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/DeviceManagementService.java new file mode 100644 index 0000000000..a6be42cdc4 --- /dev/null +++ b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/DeviceManagementService.java @@ -0,0 +1,183 @@ +/* + * Copyright (c) 2014, 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.wso2.carbon.mdm.services.android; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.device.mgt.common.Device; +import org.wso2.carbon.device.mgt.common.DeviceIdentifier; +import org.wso2.carbon.device.mgt.common.DeviceManagementConstants; +import org.wso2.carbon.device.mgt.common.DeviceManagementException; +import org.wso2.carbon.device.mgt.common.app.mgt.Application; +import org.wso2.carbon.device.mgt.common.app.mgt.ApplicationManagementException; +import org.wso2.carbon.device.mgt.common.license.mgt.License; +import org.wso2.carbon.mdm.services.android.exception.AndroidAgentException; +import org.wso2.carbon.mdm.services.android.util.AndroidAPIUtils; +import org.wso2.carbon.mdm.services.android.util.Message; + +import javax.jws.WebService; +import javax.ws.rs.*; +import javax.ws.rs.core.Response; +import java.util.List; + +/** + * Android Device Management REST-API implementation. + * All end points supports JSON, XMl with content negotiation. + */ +@WebService +@Produces({ "application/json", "application/xml" }) +@Consumes({ "application/json", "application/xml" }) +public class DeviceManagementService { + + private static Log log = LogFactory.getLog(DeviceManagementService.class); + + /** + * Get all devices.Returns list of Android devices registered in MDM. + * + * @return Device List + * @throws org.wso2.carbon.mdm.services.android.exception.AndroidAgentException + */ + @GET + public List getAllDevices() + throws AndroidAgentException { + String msg; + List devices; + + try { + devices = AndroidAPIUtils.getDeviceManagementService(). + getAllDevices(DeviceManagementConstants.MobileDeviceTypes. + MOBILE_DEVICE_TYPE_ANDROID); + } catch (DeviceManagementException e) { + msg = "Error occurred while fetching the device list."; + log.error(msg, e); + throw new AndroidAgentException(msg, e); + } + return devices; + } + + /** + * Fetch Android device details of a given device Id. + * + * @param id Device Id + * @return Device + * @throws org.wso2.carbon.mdm.services.android.exception.AndroidAgentException + */ + @GET + @Path("{id}") + public org.wso2.carbon.device.mgt.common.Device getDevice(@PathParam("id") String id) + throws AndroidAgentException { + + String msg; + org.wso2.carbon.device.mgt.common.Device device; + + try { + DeviceIdentifier deviceIdentifier = AndroidAPIUtils.convertToDeviceIdentifierObject(id); + device = AndroidAPIUtils.getDeviceManagementService().getDevice(deviceIdentifier); + if (device == null) { + Response.status(Response.Status.NOT_FOUND); + } + } catch (DeviceManagementException deviceMgtEx) { + msg = "Error occurred while fetching the device information."; + log.error(msg, deviceMgtEx); + throw new AndroidAgentException(msg, deviceMgtEx); + } + return device; + } + + /** + * Update Android device details of given device id. + * + * @param id Device Id + * @param device Device Details + * @return Message + * @throws AndroidAgentException + */ + @PUT + @Path("{id}") + public Message updateDevice(@PathParam("id") String id, Device device) + throws AndroidAgentException { + String msg; + Message responseMessage = new Message(); + DeviceIdentifier deviceIdentifier = new DeviceIdentifier(); + deviceIdentifier.setId(id); + deviceIdentifier + .setType(DeviceManagementConstants.MobileDeviceTypes.MOBILE_DEVICE_TYPE_ANDROID); + boolean result; + try { + device.setType(DeviceManagementConstants.MobileDeviceTypes.MOBILE_DEVICE_TYPE_ANDROID); + result = AndroidAPIUtils.getDeviceManagementService() + .updateDeviceInfo(deviceIdentifier, device); + if (result) { + Response.status(Response.Status.ACCEPTED); + responseMessage.setResponseMessage("Device information has modified successfully."); + } else { + Response.status(Response.Status.NOT_MODIFIED); + responseMessage.setResponseMessage("Device not found for the update."); + } + } catch (DeviceManagementException e) { + msg = "Error occurred while modifying the device information."; + log.error(msg, e); + throw new AndroidAgentException(msg, e); + } + return responseMessage; + } + + @POST + @Path("appList/{id}") + public Message updateApplicationList(@PathParam("id") String id, List applications) + throws + AndroidAgentException { + + Message responseMessage = new Message(); + DeviceIdentifier deviceIdentifier = new DeviceIdentifier(); + deviceIdentifier.setId(id); + deviceIdentifier.setType(DeviceManagementConstants.MobileDeviceTypes.MOBILE_DEVICE_TYPE_ANDROID); + try { + AndroidAPIUtils.getApplicationManagerService(). + updateApplicationListInstalledInDevice(deviceIdentifier, applications); + Response.status(Response.Status.ACCEPTED); + responseMessage.setResponseMessage("Device information has modified successfully."); + + } catch (ApplicationManagementException e) { + String msg = "Error occurred while modifying the application list."; + log.error(msg, e); + throw new AndroidAgentException(msg, e); + } + return responseMessage; + } + + @GET + @Path("license") + @Produces("text/html") + public String getLicense() throws AndroidAgentException { + License license; + + try { + license = + AndroidAPIUtils.getDeviceManagementService().getLicense( + DeviceManagementConstants.MobileDeviceTypes.MOBILE_DEVICE_TYPE_ANDROID, + DeviceManagementConstants.LanguageCodes.LANGUAGE_CODE_ENGLISH_US); + } catch (DeviceManagementException e) { + String msg = "Error occurred while retrieving the license configured for Android device enrolment"; + log.error(msg, e); + throw new AndroidAgentException(msg, e); + } + return (license == null) ? null : license.getText(); + } + +} diff --git a/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/EnrollmentService.java b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/EnrollmentService.java new file mode 100644 index 0000000000..5ecf373e71 --- /dev/null +++ b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/EnrollmentService.java @@ -0,0 +1,153 @@ +/* + * Copyright (c) 2014, 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.wso2.carbon.mdm.services.android; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.device.mgt.common.DeviceIdentifier; +import org.wso2.carbon.device.mgt.common.DeviceManagementConstants; +import org.wso2.carbon.device.mgt.common.DeviceManagementException; +import org.wso2.carbon.mdm.services.android.exception.AndroidAgentException; +import org.wso2.carbon.mdm.services.android.util.AndroidAPIUtils; +import org.wso2.carbon.mdm.services.android.util.Message; + +import javax.jws.WebService; +import javax.ws.rs.*; +import javax.ws.rs.core.Response; + +/** + * Android Device Enrollment REST-API implementation. + * All end points supports JSON, XMl with content negotiation. + */ +@WebService +@Produces({ "application/json", "application/xml" }) +@Consumes({ "application/json", "application/xml" }) +public class EnrollmentService { + + private static Log log = LogFactory.getLog(EnrollmentService.class); + + @POST + public Message enrollDevice(org.wso2.carbon.device.mgt.common.Device device) + throws AndroidAgentException { + + Message responseMsg = new Message(); + String msg; + try { + device.setType(DeviceManagementConstants.MobileDeviceTypes.MOBILE_DEVICE_TYPE_ANDROID); + device.getEnrolmentInfo().setOwner(AndroidAPIUtils.getAuthenticatedUser()); + boolean status = AndroidAPIUtils.getDeviceManagementService().enrollDevice(device); + if (status) { + Response.status(Response.Status.CREATED); + responseMsg.setResponseMessage("Device enrollment succeeded."); + responseMsg.setResponseCode(Response.Status.CREATED.toString()); + } else { + Response.status(Response.Status.INTERNAL_SERVER_ERROR); + responseMsg.setResponseMessage("Device enrollment failed."); + responseMsg.setResponseCode(Response.Status.INTERNAL_SERVER_ERROR.toString()); + } + } catch (DeviceManagementException e) { + msg = "Error occurred while enrolling the device"; + log.error(msg, e); + throw new AndroidAgentException(msg, e); + } + return responseMsg; + } + + @GET + @Path("{deviceId}") + public Message isEnrolled(@PathParam("deviceId") String id) throws AndroidAgentException { + String msg; + boolean result; + Message responseMsg = new Message(); + DeviceIdentifier deviceIdentifier = AndroidAPIUtils.convertToDeviceIdentifierObject(id); + + try { + result = AndroidAPIUtils.getDeviceManagementService().isEnrolled(deviceIdentifier); + if (result) { + responseMsg.setResponseMessage("Device has already enrolled"); + responseMsg.setResponseCode(Response.Status.ACCEPTED.toString()); + Response.status(Response.Status.ACCEPTED); + } else { + responseMsg.setResponseMessage("Device not found"); + responseMsg.setResponseCode(Response.Status.NOT_FOUND.toString()); + Response.status(Response.Status.NOT_FOUND); + } + } catch (DeviceManagementException e) { + msg = "Error occurred while enrollment of the device."; + log.error(msg, e); + throw new AndroidAgentException(msg, e); + } + return responseMsg; + } + + @PUT + @Path("{deviceId}") + public Message modifyEnrollment(@PathParam("deviceId") String id, + org.wso2.carbon.device.mgt.common.Device device) + throws AndroidAgentException { + String msg; + boolean result; + Message responseMsg = new Message(); + try { + device.setType(DeviceManagementConstants.MobileDeviceTypes.MOBILE_DEVICE_TYPE_ANDROID); + result = AndroidAPIUtils.getDeviceManagementService().modifyEnrollment(device); + if (result) { + responseMsg.setResponseMessage("Device enrollment has updated successfully"); + responseMsg.setResponseCode(Response.Status.ACCEPTED.toString()); + Response.status(Response.Status.ACCEPTED); + } else { + responseMsg.setResponseMessage("Device not found for enrollment"); + responseMsg.setResponseCode(Response.Status.NOT_MODIFIED.toString()); + Response.status(Response.Status.NOT_MODIFIED); + } + } catch (DeviceManagementException e) { + msg = "Error occurred while modifying enrollment of the device"; + log.error(msg, e); + throw new AndroidAgentException(msg, e); + } + return responseMsg; + } + + @DELETE + @Path("{deviceId}") + public Message disEnrollDevice(@PathParam("deviceId") String id) throws AndroidAgentException { + Message responseMsg = new Message(); + boolean result; + String msg; + DeviceIdentifier deviceIdentifier = AndroidAPIUtils.convertToDeviceIdentifierObject(id); + + try { + result = AndroidAPIUtils.getDeviceManagementService().disenrollDevice(deviceIdentifier); + if (result) { + responseMsg.setResponseMessage("Device has removed successfully"); + responseMsg.setResponseCode(Response.Status.ACCEPTED.toString()); + Response.status(Response.Status.ACCEPTED); + } else { + responseMsg.setResponseMessage("Device not found"); + responseMsg.setResponseCode(Response.Status.NOT_FOUND.toString()); + Response.status(Response.Status.NOT_FOUND); + } + } catch (DeviceManagementException e) { + msg = "Error occurred while dis enrolling the device"; + log.error(msg, e); + throw new AndroidAgentException(msg, e); + } + return responseMsg; + } +} diff --git a/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/OperationMgtService.java b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/OperationMgtService.java new file mode 100644 index 0000000000..91329cc94b --- /dev/null +++ b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/OperationMgtService.java @@ -0,0 +1,931 @@ +/* + * 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.wso2.carbon.mdm.services.android; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.device.mgt.common.DeviceIdentifier; +import org.wso2.carbon.device.mgt.common.DeviceManagementException; +import org.wso2.carbon.device.mgt.common.app.mgt.ApplicationManagementException; +import org.wso2.carbon.device.mgt.common.notification.mgt.*; +import org.wso2.carbon.device.mgt.common.operation.mgt.Operation; +import org.wso2.carbon.device.mgt.common.operation.mgt.OperationManagementException; +import org.wso2.carbon.device.mgt.core.operation.mgt.CommandOperation; +import org.wso2.carbon.device.mgt.core.operation.mgt.ProfileOperation; +import org.wso2.carbon.mdm.services.android.bean.*; +import org.wso2.carbon.mdm.services.android.bean.Notification; +import org.wso2.carbon.mdm.services.android.bean.wrapper.*; +import org.wso2.carbon.mdm.services.android.exception.AndroidOperationException; +import org.wso2.carbon.mdm.services.android.util.AndroidAPIUtils; +import org.wso2.carbon.mdm.services.android.util.AndroidConstants; +import org.wso2.carbon.mdm.services.android.util.AndroidDeviceUtils; +import org.wso2.carbon.mdm.services.android.util.Message; +import org.wso2.carbon.policy.mgt.common.monitor.PolicyComplianceException; + +import javax.ws.rs.*; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import java.util.List; + +/** + * Android Device Operation REST-API implementation. + */ +public class OperationMgtService { + + private static Log log = LogFactory.getLog(OperationMgtService.class); + private static final String ACCEPT = "Accept"; + private static final String OPERATION_ERROR_STATUS = "ERROR"; + private static final String DEVICE_TYPE_ANDROID = "android"; + + @PUT + @Path("{id}") + public List getPendingOperations + (@HeaderParam(ACCEPT) String acceptHeader, @PathParam("id") String id, + List resultOperations) { + Message message; + MediaType responseMediaType = AndroidAPIUtils.getResponseMediaType(acceptHeader); + + if (id == null || id.isEmpty()) { + String errorMessage = "Device identifier is null or empty, hence returning device not found"; + message = Message.responseMessage(errorMessage). + responseCode(Response.Status.BAD_REQUEST.toString()).build(); + log.error(errorMessage); + throw new AndroidOperationException(message, responseMediaType); + } + + DeviceIdentifier deviceIdentifier = AndroidAPIUtils.convertToDeviceIdentifierObject(id); + try { + if (!AndroidDeviceUtils.isValidDeviceIdentifier(deviceIdentifier)) { + String errorMessage = "Device not found for identifier '" + id + "'"; + message = Message.responseMessage(errorMessage). + responseCode(Response.Status.BAD_REQUEST.toString()).build(); + log.error(errorMessage); + throw new AndroidOperationException(message, responseMediaType); + } + if (log.isDebugEnabled()) { + log.debug("Invoking Android pending operations:" + id); + } + if (resultOperations != null && !resultOperations.isEmpty()) { + updateOperations(id, resultOperations); + } + } catch (OperationManagementException e) { + log.error("Issue in retrieving operation management service instance", e); + } catch (PolicyComplianceException e) { + log.error("Issue in updating Monitoring operation"); + } catch (DeviceManagementException e) { + log.error("Issue in retrieving device management service instance", e); + } catch (ApplicationManagementException e) { + log.error("Issue in retrieving application management service instance", e); + } catch (NotificationManagementException e) { + log.error("Issue in retrieving Notification management service instance", e); + } + + List pendingOperations; + try { + pendingOperations = AndroidAPIUtils.getPendingOperations(deviceIdentifier); + } catch (OperationManagementException e) { + String errorMessage = "Issue in retrieving operation management service instance"; + message = Message.responseMessage(errorMessage). + responseCode(Response.Status.INTERNAL_SERVER_ERROR.toString()).build(); + log.error(errorMessage, e); + throw new AndroidOperationException(message, responseMediaType); + } + return pendingOperations; + } + + @POST + @Path("lock") + public Response configureDeviceLock(@HeaderParam(ACCEPT) String acceptHeader, List deviceIDs) { + + if (log.isDebugEnabled()) { + log.debug("Invoking Android device lock operation"); + } + + MediaType responseMediaType = AndroidAPIUtils.getResponseMediaType(acceptHeader); + Message message = new Message(); + Response response; + + try { + CommandOperation operation = new CommandOperation(); + operation.setCode(AndroidConstants.OperationCodes.DEVICE_LOCK); + operation.setType(Operation.Type.COMMAND); + operation.setEnabled(true); + response = AndroidAPIUtils.getOperationResponse(deviceIDs, operation, message, responseMediaType); + } catch (OperationManagementException e) { + String errorMessage = "Issue in retrieving operation management service instance"; + message = Message.responseMessage(errorMessage). + responseCode(Response.Status.INTERNAL_SERVER_ERROR.toString()).build(); + log.error(errorMessage, e); + throw new AndroidOperationException(message, responseMediaType); + } catch (DeviceManagementException e) { + String errorMessage = "Issue in retrieving device management service instance"; + message = Message.responseMessage(errorMessage). + responseCode(Response.Status.INTERNAL_SERVER_ERROR.toString()).build(); + log.error(errorMessage, e); + throw new AndroidOperationException(message, responseMediaType); + } + return response; + } + + @POST + @Path("location") + public Response getDeviceLocation(@HeaderParam(ACCEPT) String acceptHeader, + List deviceIDs) { + if (log.isDebugEnabled()) { + log.debug("Invoking Android device location operation"); + } + MediaType responseMediaType = AndroidAPIUtils.getResponseMediaType(acceptHeader); + Message message = new Message(); + + try { + CommandOperation operation = new CommandOperation(); + operation.setCode(AndroidConstants.OperationCodes.DEVICE_LOCATION); + operation.setType(Operation.Type.COMMAND); + return AndroidAPIUtils.getOperationResponse(deviceIDs, operation, + message, responseMediaType); + } catch (OperationManagementException e) { + String errorMessage = "Issue in retrieving operation management service instance"; + message = Message.responseMessage(errorMessage). + responseCode(Response.Status.INTERNAL_SERVER_ERROR.toString()).build(); + log.error(errorMessage, e); + throw new AndroidOperationException(message, responseMediaType); + } catch (DeviceManagementException e) { + String errorMessage = "Issue in retrieving device management service instance"; + message = Message.responseMessage(errorMessage). + responseCode(Response.Status.INTERNAL_SERVER_ERROR.toString()).build(); + log.error(errorMessage, e); + throw new AndroidOperationException(message, responseMediaType); + } + } + + @POST + @Path("clear-password") + public Response removePassword(@HeaderParam(ACCEPT) String acceptHeader, + List deviceIDs) { + if (log.isDebugEnabled()) { + log.debug("Invoking Android clear password operation"); + } + MediaType responseMediaType = AndroidAPIUtils.getResponseMediaType(acceptHeader); + Message message = new Message(); + + try { + CommandOperation operation = new CommandOperation(); + operation.setCode(AndroidConstants.OperationCodes.CLEAR_PASSWORD); + operation.setType(Operation.Type.COMMAND); + + return AndroidAPIUtils.getOperationResponse(deviceIDs, operation, + message, responseMediaType); + + } catch (OperationManagementException e) { + String errorMessage = "Issue in retrieving operation management service instance"; + message = Message.responseMessage(errorMessage). + responseCode(Response.Status.INTERNAL_SERVER_ERROR.toString()).build(); + log.error(errorMessage, e); + throw new AndroidOperationException(message, responseMediaType); + } catch (DeviceManagementException e) { + String errorMessage = "Issue in retrieving device management service instance"; + message = Message.responseMessage(errorMessage). + responseCode(Response.Status.INTERNAL_SERVER_ERROR.toString()).build(); + log.error(errorMessage, e); + throw new AndroidOperationException(message, responseMediaType); + } + } + + @POST + @Path("camera") + public Response configureCamera(@HeaderParam(ACCEPT) String acceptHeader, + CameraBeanWrapper cameraBeanWrapper) { + + if (log.isDebugEnabled()) { + log.debug("Invoking Android Camera operation"); + } + + MediaType responseMediaType = AndroidAPIUtils.getResponseMediaType(acceptHeader); + Message message = new Message(); + + try { + Camera camera = cameraBeanWrapper.getOperation(); + + if (camera == null) { + if (log.isDebugEnabled()) { + log.debug("The payload of the configure camera operation is incorrect"); + } + throw new OperationManagementException("Issue in creating a new camera instance"); + } + + CommandOperation operation = new CommandOperation(); + operation.setCode(AndroidConstants.OperationCodes.CAMERA); + operation.setType(Operation.Type.COMMAND); + operation.setEnabled(camera.isEnabled()); + + return AndroidAPIUtils.getOperationResponse(cameraBeanWrapper.getDeviceIDs(), operation, message, + responseMediaType); + + } catch (OperationManagementException e) { + String errorMessage = "Issue in retrieving operation management service instance"; + message = Message.responseMessage(errorMessage). + responseCode(Response.Status.INTERNAL_SERVER_ERROR.toString()).build(); + log.error(errorMessage, e); + throw new AndroidOperationException(message, responseMediaType); + } catch (DeviceManagementException e) { + String errorMessage = "Issue in retrieving device management service instance"; + message = Message.responseMessage(errorMessage). + responseCode(Response.Status.INTERNAL_SERVER_ERROR.toString()).build(); + log.error(errorMessage, e); + throw new AndroidOperationException(message, responseMediaType); + } + } + + @POST + @Path("device-info") + public Response getDeviceInformation(@HeaderParam(ACCEPT) String acceptHeader, + List deviceIDs) { + + if (log.isDebugEnabled()) { + log.debug("Invoking get Android device information operation"); + } + + MediaType responseMediaType = AndroidAPIUtils.getResponseMediaType(acceptHeader); + Message message = new Message(); + + try { + CommandOperation operation = new CommandOperation(); + operation.setCode(AndroidConstants.OperationCodes.DEVICE_INFO); + operation.setType(Operation.Type.COMMAND); + getApplications(acceptHeader, deviceIDs); + return AndroidAPIUtils.getOperationResponse(deviceIDs, operation, message, + responseMediaType); + } catch (OperationManagementException e) { + String errorMessage = "Issue in retrieving operation management service instance"; + message = Message.responseMessage(errorMessage). + responseCode(Response.Status.INTERNAL_SERVER_ERROR.toString()).build(); + log.error(errorMessage, e); + throw new AndroidOperationException(message, responseMediaType); + } catch (DeviceManagementException e) { + String errorMessage = "Issue in retrieving device management service instance"; + message = Message.responseMessage(errorMessage). + responseCode(Response.Status.INTERNAL_SERVER_ERROR.toString()).build(); + log.error(errorMessage, e); + throw new AndroidOperationException(message, responseMediaType); + } + } + + @POST + @Path("enterprise-wipe") + public Response wipeDevice(@HeaderParam(ACCEPT) String acceptHeader, + List deviceIDs) { + + if (log.isDebugEnabled()) { + log.debug("Invoking enterprise-wipe device operation"); + } + + MediaType responseMediaType = AndroidAPIUtils.getResponseMediaType(acceptHeader); + Message message = new Message(); + + try { + CommandOperation operation = new CommandOperation(); + operation.setCode(AndroidConstants.OperationCodes.ENTERPRISE_WIPE); + operation.setType(Operation.Type.COMMAND); + + return AndroidAPIUtils.getOperationResponse(deviceIDs, operation, message, + responseMediaType); + } catch (OperationManagementException e) { + String errorMessage = "Issue in retrieving operation management service instance"; + message = Message.responseMessage(errorMessage). + responseCode(Response.Status.INTERNAL_SERVER_ERROR.toString()).build(); + log.error(errorMessage, e); + throw new AndroidOperationException(message, responseMediaType); + } catch (DeviceManagementException e) { + String errorMessage = "Issue in retrieving device management service instance"; + message = Message.responseMessage(errorMessage). + responseCode(Response.Status.INTERNAL_SERVER_ERROR.toString()).build(); + log.error(errorMessage, e); + throw new AndroidOperationException(message, responseMediaType); + } + } + + @POST + @Path("wipe-data") + public Response wipeData(@HeaderParam(ACCEPT) String acceptHeader, + WipeDataBeanWrapper wipeDataBeanWrapper) { + + if (log.isDebugEnabled()) { + log.debug("Invoking Android wipe-data device operation"); + } + + MediaType responseMediaType = AndroidAPIUtils.getResponseMediaType(acceptHeader); + Message message = new Message(); + + try { + WipeData wipeData = wipeDataBeanWrapper.getOperation(); + + if (wipeData == null) { + throw new OperationManagementException("WipeData bean is empty"); + } + + ProfileOperation operation = new ProfileOperation(); + operation.setCode(AndroidConstants.OperationCodes.WIPE_DATA); + operation.setType(Operation.Type.PROFILE); + operation.setPayLoad(wipeData.toJSON()); + + return AndroidAPIUtils.getOperationResponse(wipeDataBeanWrapper.getDeviceIDs(), operation, message, + responseMediaType); + + } catch (OperationManagementException e) { + String errorMessage = "Issue in retrieving operation management service instance"; + message = Message.responseMessage(errorMessage). + responseCode(Response.Status.INTERNAL_SERVER_ERROR.toString()).build(); + log.error(errorMessage, e); + throw new AndroidOperationException(message, responseMediaType); + } catch (DeviceManagementException e) { + String errorMessage = "Issue in retrieving device management service instance"; + message = Message.responseMessage(errorMessage). + responseCode(Response.Status.INTERNAL_SERVER_ERROR.toString()).build(); + log.error(errorMessage, e); + throw new AndroidOperationException(message, responseMediaType); + } + } + + @POST + @Path("application-list") + public Response getApplications(@HeaderParam(ACCEPT) String acceptHeader, + List deviceIDs) { + + if (log.isDebugEnabled()) { + log.debug("Invoking Android getApplicationList device operation"); + } + + MediaType responseMediaType = AndroidAPIUtils.getResponseMediaType(acceptHeader); + Message message = new Message(); + + try { + CommandOperation operation = new CommandOperation(); + operation.setCode(AndroidConstants.OperationCodes.APPLICATION_LIST); + operation.setType(Operation.Type.COMMAND); + + return AndroidAPIUtils.getOperationResponse(deviceIDs, operation, message, + responseMediaType); + } catch (OperationManagementException e) { + String errorMessage = "Issue in retrieving operation management service instance"; + message = Message.responseMessage(errorMessage). + responseCode(Response.Status.INTERNAL_SERVER_ERROR.toString()).build(); + log.error(errorMessage, e); + throw new AndroidOperationException(message, responseMediaType); + } catch (DeviceManagementException e) { + String errorMessage = "Issue in retrieving device management service instance"; + message = Message.responseMessage(errorMessage). + responseCode(Response.Status.INTERNAL_SERVER_ERROR.toString()).build(); + log.error(errorMessage, e); + throw new AndroidOperationException(message, responseMediaType); + } + } + + @POST + @Path("ring-device") + public Response ringDevice(@HeaderParam(ACCEPT) String acceptHeader, + List deviceIDs) { + + if (log.isDebugEnabled()) { + log.debug("Invoking Android ring-device device operation"); + } + + MediaType responseMediaType = AndroidAPIUtils.getResponseMediaType(acceptHeader); + Message message = new Message(); + + try { + CommandOperation operation = new CommandOperation(); + operation.setCode(AndroidConstants.OperationCodes.DEVICE_RING); + operation.setType(Operation.Type.COMMAND); + return AndroidAPIUtils.getOperationResponse(deviceIDs, operation, message, + responseMediaType); + } catch (OperationManagementException e) { + String errorMessage = "Issue in retrieving operation management service instance"; + message = Message.responseMessage(errorMessage). + responseCode(Response.Status.INTERNAL_SERVER_ERROR.toString()).build(); + log.error(errorMessage, e); + throw new AndroidOperationException(message, responseMediaType); + } catch (DeviceManagementException e) { + String errorMessage = "Issue in retrieving device management service instance"; + message = Message.responseMessage(errorMessage). + responseCode(Response.Status.INTERNAL_SERVER_ERROR.toString()).build(); + log.error(errorMessage, e); + throw new AndroidOperationException(message, responseMediaType); + } + } + + @POST + @Path("mute") + public Response muteDevice(@HeaderParam(ACCEPT) String acceptHeader, + List deviceIDs) { + + if (log.isDebugEnabled()) { + log.debug("Invoking mute device operation"); + } + + MediaType responseMediaType = AndroidAPIUtils.getResponseMediaType(acceptHeader); + Message message = new Message(); + + try { + CommandOperation operation = new CommandOperation(); + operation.setCode(AndroidConstants.OperationCodes.DEVICE_MUTE); + operation.setType(Operation.Type.COMMAND); + operation.setEnabled(true); + return AndroidAPIUtils.getOperationResponse(deviceIDs, operation, message, + responseMediaType); + } catch (OperationManagementException e) { + String errorMessage = "Issue in retrieving operation management service instance"; + message = Message.responseMessage(errorMessage). + responseCode(Response.Status.INTERNAL_SERVER_ERROR.toString()).build(); + log.error(errorMessage, e); + throw new AndroidOperationException(message, responseMediaType); + } catch (DeviceManagementException e) { + String errorMessage = "Issue in retrieving device management service instance"; + message = Message.responseMessage(errorMessage). + responseCode(Response.Status.INTERNAL_SERVER_ERROR.toString()).build(); + log.error(errorMessage, e); + throw new AndroidOperationException(message, responseMediaType); + } + } + + @POST + @Path("install-application") + public Response installApplication(@HeaderParam(ACCEPT) String acceptHeader, + ApplicationInstallationBeanWrapper applicationInstallationBeanWrapper) { + + if (log.isDebugEnabled()) { + log.debug("Invoking 'InstallApplication' operation"); + } + + MediaType responseMediaType = AndroidAPIUtils.getResponseMediaType(acceptHeader); + Message message = new Message(); + + try { + ApplicationInstallation applicationInstallation = applicationInstallationBeanWrapper.getOperation(); + + if (applicationInstallation == null) { + if (log.isDebugEnabled()) { + log.debug("The payload of the application installing operation is incorrect"); + } + throw new OperationManagementException("Issue in creating a new application installation instance"); + } + + ProfileOperation operation = new ProfileOperation(); + operation.setCode(AndroidConstants.OperationCodes.INSTALL_APPLICATION); + operation.setType(Operation.Type.PROFILE); + operation.setPayLoad(applicationInstallation.toJSON()); + + return AndroidAPIUtils.getOperationResponse(applicationInstallationBeanWrapper.getDeviceIDs(), + operation, message, responseMediaType); + } catch (OperationManagementException e) { + String errorMessage = "Issue in retrieving operation management service instance"; + message = Message.responseMessage(errorMessage). + responseCode(Response.Status.INTERNAL_SERVER_ERROR.toString()).build(); + log.error(errorMessage, e); + throw new AndroidOperationException(message, responseMediaType); + } catch (DeviceManagementException e) { + String errorMessage = "Issue in retrieving device management service instance"; + message = Message.responseMessage(errorMessage). + responseCode(Response.Status.INTERNAL_SERVER_ERROR.toString()).build(); + log.error(errorMessage, e); + throw new AndroidOperationException(message, responseMediaType); + } + } + + @POST + @Path("uninstall-application") + public Response uninstallApplication(@HeaderParam(ACCEPT) String acceptHeader, + ApplicationUninstallationBeanWrapper applicationUninstallationBeanWrapper) { + if (log.isDebugEnabled()) { + log.debug("Invoking 'UninstallApplication' operation"); + } + + MediaType responseMediaType = AndroidAPIUtils.getResponseMediaType(acceptHeader); + Message message = new Message(); + + try { + ApplicationUninstallation applicationUninstallation = applicationUninstallationBeanWrapper.getOperation(); + + if (applicationUninstallation == null) { + if (log.isDebugEnabled()) { + log.debug("The payload of the application uninstalling operation is incorrect"); + } + throw new OperationManagementException("Issue in creating a new application uninstallation instance"); + } + + ProfileOperation operation = new ProfileOperation(); + operation.setCode(AndroidConstants.OperationCodes.UNINSTALL_APPLICATION); + operation.setType(Operation.Type.PROFILE); + operation.setPayLoad(applicationUninstallation.toJSON()); + + return AndroidAPIUtils.getOperationResponse(applicationUninstallationBeanWrapper.getDeviceIDs(), + operation, message, responseMediaType); + } catch (OperationManagementException e) { + String errorMessage = "Issue in retrieving operation management service instance"; + message = Message.responseMessage(errorMessage). + responseCode(Response.Status.INTERNAL_SERVER_ERROR.toString()).build(); + log.error(errorMessage, e); + throw new AndroidOperationException(message, responseMediaType); + } catch (DeviceManagementException e) { + String errorMessage = "Issue in retrieving device management service instance"; + message = Message.responseMessage(errorMessage). + responseCode(Response.Status.INTERNAL_SERVER_ERROR.toString()).build(); + log.error(errorMessage, e); + throw new AndroidOperationException(message, responseMediaType); + } + } + + @POST + @Path("blacklist-applications") + public Response blacklistApplications(@HeaderParam(ACCEPT) String acceptHeader, + BlacklistApplicationsBeanWrapper blacklistApplicationsBeanWrapper) { + if (log.isDebugEnabled()) { + log.debug("Invoking 'Blacklist-Applications' operation"); + } + + MediaType responseMediaType = AndroidAPIUtils.getResponseMediaType(acceptHeader); + Message message = new Message(); + + try { + BlacklistApplications blacklistApplications = blacklistApplicationsBeanWrapper.getOperation(); + + if (blacklistApplications == null) { + if (log.isDebugEnabled()) { + log.debug("The payload of the blacklisting apps operation is incorrect"); + } + throw new OperationManagementException("Issue in creating a new blacklist applications instance"); + } + + ProfileOperation operation = new ProfileOperation(); + operation.setCode(AndroidConstants.OperationCodes.BLACKLIST_APPLICATIONS); + operation.setType(Operation.Type.PROFILE); + operation.setPayLoad(blacklistApplications.toJSON()); + + return AndroidAPIUtils.getOperationResponse(blacklistApplicationsBeanWrapper.getDeviceIDs(), + operation, message, responseMediaType); + + } catch (OperationManagementException e) { + String errorMessage = "Issue in retrieving operation management service instance"; + message = Message.responseMessage(errorMessage). + responseCode(Response.Status.INTERNAL_SERVER_ERROR.toString()).build(); + log.error(errorMessage, e); + throw new AndroidOperationException(message, responseMediaType); + } catch (DeviceManagementException e) { + String errorMessage = "Issue in retrieving device management service instance"; + message = Message.responseMessage(errorMessage). + responseCode(Response.Status.INTERNAL_SERVER_ERROR.toString()).build(); + log.error(errorMessage, e); + throw new AndroidOperationException(message, responseMediaType); + } + } + + @POST + @Path("notification") + public Response sendNotification(@HeaderParam(ACCEPT) String acceptHeader, + NotificationBeanWrapper notificationBeanWrapper) { + if (log.isDebugEnabled()) { + log.debug("Invoking 'notification' operation"); + } + + MediaType responseMediaType = AndroidAPIUtils.getResponseMediaType(acceptHeader); + Message message = new Message(); + + try { + Notification notification = notificationBeanWrapper.getOperation(); + + if (notification == null) { + if (log.isDebugEnabled()) { + log.debug("The payload of the notification operation is incorrect"); + } + throw new OperationManagementException("Issue in creating a new notification instance"); + } + + ProfileOperation operation = new ProfileOperation(); + operation.setCode(AndroidConstants.OperationCodes.NOTIFICATION); + operation.setType(Operation.Type.PROFILE); + operation.setPayLoad(notification.toJSON()); + + return AndroidAPIUtils.getOperationResponse(notificationBeanWrapper.getDeviceIDs(), + operation, message, responseMediaType); + + } catch (OperationManagementException e) { + String errorMessage = "Issue in retrieving operation management service instance"; + message = Message.responseMessage(errorMessage). + responseCode(Response.Status.INTERNAL_SERVER_ERROR.toString()).build(); + log.error(errorMessage, e); + throw new AndroidOperationException(message, responseMediaType); + } catch (DeviceManagementException e) { + String errorMessage = "Issue in retrieving device management service instance"; + message = Message.responseMessage(errorMessage). + responseCode(Response.Status.INTERNAL_SERVER_ERROR.toString()).build(); + log.error(errorMessage, e); + throw new AndroidOperationException(message, responseMediaType); + } + } + + @POST + @Path("wifi") + public Response configureWifi(@HeaderParam(ACCEPT) String acceptHeader, + WifiBeanWrapper wifiBeanWrapper) { + if (log.isDebugEnabled()) { + log.debug("Invoking 'configure wifi' operation"); + } + + MediaType responseMediaType = AndroidAPIUtils.getResponseMediaType(acceptHeader); + Message message = new Message(); + + try { + Wifi wifi = wifiBeanWrapper.getOperation(); + + if (wifi == null) { + if (log.isDebugEnabled()) { + log.debug("The payload of the wifi operation is incorrect"); + } + throw new OperationManagementException("Issue in creating a new Wifi instance"); + } + + ProfileOperation operation = new ProfileOperation(); + operation.setCode(AndroidConstants.OperationCodes.WIFI); + operation.setType(Operation.Type.PROFILE); + operation.setPayLoad(wifi.toJSON()); + + return AndroidAPIUtils.getOperationResponse(wifiBeanWrapper.getDeviceIDs(), + operation, message, responseMediaType); + + } catch (OperationManagementException e) { + String errorMessage = "Issue in retrieving operation management service instance"; + message = Message.responseMessage(errorMessage). + responseCode(Response.Status.INTERNAL_SERVER_ERROR.toString()).build(); + log.error(errorMessage, e); + throw new AndroidOperationException(message, responseMediaType); + } catch (DeviceManagementException e) { + String errorMessage = "Issue in retrieving device management service instance"; + message = Message.responseMessage(errorMessage). + responseCode(Response.Status.INTERNAL_SERVER_ERROR.toString()).build(); + log.error(errorMessage, e); + throw new AndroidOperationException(message, responseMediaType); + } + } + + @POST + @Path("encrypt") + public Response encryptStorage(@HeaderParam(ACCEPT) String acceptHeader, + EncryptionBeanWrapper encryptionBeanWrapper) { + if (log.isDebugEnabled()) { + log.debug("Invoking 'encrypt' operation"); + } + + MediaType responseMediaType = AndroidAPIUtils.getResponseMediaType(acceptHeader); + Message message = new Message(); + + try { + DeviceEncryption deviceEncryption = encryptionBeanWrapper.getOperation(); + + if (deviceEncryption == null) { + if (log.isDebugEnabled()) { + log.debug("The payload of the device encryption operation is incorrect"); + } + throw new OperationManagementException("Issue in creating a new encryption instance"); + } + + CommandOperation operation = new CommandOperation(); + operation.setCode(AndroidConstants.OperationCodes.ENCRYPT_STORAGE); + operation.setType(Operation.Type.COMMAND); + operation.setEnabled(deviceEncryption.isEncrypted()); + + return AndroidAPIUtils.getOperationResponse(encryptionBeanWrapper.getDeviceIDs(), + operation, message, responseMediaType); + + } catch (OperationManagementException e) { + String errorMessage = "Issue in retrieving operation management service instance"; + message = Message.responseMessage(errorMessage). + responseCode(Response.Status.INTERNAL_SERVER_ERROR.toString()).build(); + log.error(errorMessage, e); + throw new AndroidOperationException(message, responseMediaType); + } catch (DeviceManagementException e) { + String errorMessage = "Issue in retrieving device management service instance"; + message = Message.responseMessage(errorMessage). + responseCode(Response.Status.INTERNAL_SERVER_ERROR.toString()).build(); + log.error(errorMessage, e); + throw new AndroidOperationException(message, responseMediaType); + } + } + + @POST + @Path("change-lock-code") + public Response changeLockCode(@HeaderParam(ACCEPT) String acceptHeader, + LockCodeBeanWrapper lockCodeBeanWrapper) { + if (log.isDebugEnabled()) { + log.debug("Invoking 'change lock code' operation"); + } + + MediaType responseMediaType = AndroidAPIUtils.getResponseMediaType(acceptHeader); + Message message = new Message(); + + try { + LockCode lockCode = lockCodeBeanWrapper.getOperation(); + + if (lockCode == null) { + if (log.isDebugEnabled()) { + log.debug("The payload of the change lock code operation is incorrect"); + } + throw new OperationManagementException("Issue in retrieving a new lock-code instance"); + } + + ProfileOperation operation = new ProfileOperation(); + operation.setCode(AndroidConstants.OperationCodes.CHANGE_LOCK_CODE); + operation.setType(Operation.Type.PROFILE); + operation.setPayLoad(lockCode.toJSON()); + + return AndroidAPIUtils.getOperationResponse(lockCodeBeanWrapper.getDeviceIDs(), + operation, message, responseMediaType); + + } catch (OperationManagementException e) { + String errorMessage = "Issue in retrieving operation management service instance"; + message = Message.responseMessage(errorMessage). + responseCode(Response.Status.INTERNAL_SERVER_ERROR.toString()).build(); + log.error(errorMessage, e); + throw new AndroidOperationException(message, responseMediaType); + } catch (DeviceManagementException e) { + String errorMessage = "Issue in retrieving device management service instance"; + message = Message.responseMessage(errorMessage). + responseCode(Response.Status.INTERNAL_SERVER_ERROR.toString()).build(); + log.error(errorMessage, e); + throw new AndroidOperationException(message, responseMediaType); + } + } + + @POST + @Path("password-policy") + public Response setPasswordPolicy(@HeaderParam(ACCEPT) String acceptHeader, + PasswordPolicyBeanWrapper passwordPolicyBeanWrapper) { + if (log.isDebugEnabled()) { + log.debug("Invoking 'password policy' operation"); + } + + MediaType responseMediaType = AndroidAPIUtils.getResponseMediaType(acceptHeader); + Message message = new Message(); + + try { + PasscodePolicy passcodePolicy = passwordPolicyBeanWrapper.getOperation(); + + if (passcodePolicy == null) { + if (log.isDebugEnabled()) { + log.debug("The payload of the change password policy operation is incorrect"); + } + throw new OperationManagementException("Issue in creating a new Password policy instance"); + } + + ProfileOperation operation = new ProfileOperation(); + operation.setCode(AndroidConstants.OperationCodes.PASSCODE_POLICY); + operation.setType(Operation.Type.PROFILE); + operation.setPayLoad(passcodePolicy.toJSON()); + + return AndroidAPIUtils.getOperationResponse(passwordPolicyBeanWrapper.getDeviceIDs(), + operation, message, responseMediaType); + + } catch (OperationManagementException e) { + String errorMessage = "Issue in retrieving operation management service instance"; + message = Message.responseMessage(errorMessage). + responseCode(Response.Status.INTERNAL_SERVER_ERROR.toString()).build(); + log.error(errorMessage, e); + throw new AndroidOperationException(message, responseMediaType); + } catch (DeviceManagementException e) { + String errorMessage = "Issue in retrieving device management service instance"; + message = Message.responseMessage(errorMessage). + responseCode(Response.Status.INTERNAL_SERVER_ERROR.toString()).build(); + log.error(errorMessage, e); + throw new AndroidOperationException(message, responseMediaType); + } + } + + @POST + @Path("webclip") + public Response setWebClip(@HeaderParam(ACCEPT) String acceptHeader, + WebClipBeanWrapper webClipBeanWrapper) { + if (log.isDebugEnabled()) { + log.debug("Invoking 'webclip' operation"); + } + + MediaType responseMediaType = AndroidAPIUtils.getResponseMediaType(acceptHeader); + Message message = new Message(); + + try { + WebClip webClip = webClipBeanWrapper.getOperation(); + + if (webClip == null) { + if (log.isDebugEnabled()) { + log.debug("The payload of the add webclip operation is incorrect"); + } + throw new OperationManagementException("Issue in creating a new web clip instance"); + } + + ProfileOperation operation = new ProfileOperation(); + operation.setCode(AndroidConstants.OperationCodes.WEBCLIP); + operation.setType(Operation.Type.PROFILE); + operation.setPayLoad(webClip.toJSON()); + + return AndroidAPIUtils.getOperationResponse(webClipBeanWrapper.getDeviceIDs(), + operation, message, responseMediaType); + + } catch (OperationManagementException e) { + String errorMessage = "Issue in retrieving operation management service instance"; + message = Message.responseMessage(errorMessage). + responseCode(Response.Status.INTERNAL_SERVER_ERROR.toString()).build(); + log.error(errorMessage, e); + throw new AndroidOperationException(message, responseMediaType); + } catch (DeviceManagementException e) { + String errorMessage = "Issue in retrieving device management service instance"; + message = Message.responseMessage(errorMessage). + responseCode(Response.Status.INTERNAL_SERVER_ERROR.toString()).build(); + log.error(errorMessage, e); + throw new AndroidOperationException(message, responseMediaType); + } + } + + @POST + @Path("disenroll") + public Response setDisenrollment(@HeaderParam(ACCEPT) String acceptHeader, + DisenrollmentBeanWrapper disenrollmentBeanWrapper) { + + if (log.isDebugEnabled()) { + log.debug("Invoking Android device disenrollment operation"); + } + + MediaType responseMediaType = AndroidAPIUtils.getResponseMediaType(acceptHeader); + Message message = new Message(); + + try { + Disenrollment disenrollment = disenrollmentBeanWrapper.getOperation(); + + if (disenrollment == null) { + if (log.isDebugEnabled()) { + log.debug("The payload of the device disenrollment operation is incorrect"); + } + throw new OperationManagementException("Issue in creating a new disenrollment instance"); + } + + CommandOperation operation = new CommandOperation(); + operation.setCode(AndroidConstants.OperationCodes.DISENROLL); + operation.setType(Operation.Type.COMMAND); + operation.setEnabled(disenrollment.isEnabled()); + + return AndroidAPIUtils.getOperationResponse(disenrollmentBeanWrapper.getDeviceIDs(), operation, + message, responseMediaType); + + } catch (OperationManagementException e) { + String errorMessage = "Issue in retrieving operation management service instance"; + message = Message.responseMessage(errorMessage). + responseCode(Response.Status.INTERNAL_SERVER_ERROR.toString()).build(); + log.error(errorMessage, e); + throw new AndroidOperationException(message, responseMediaType); + } catch (DeviceManagementException e) { + String errorMessage = "Issue in retrieving device management service instance"; + message = Message.responseMessage(errorMessage). + responseCode(Response.Status.INTERNAL_SERVER_ERROR.toString()).build(); + log.error(errorMessage, e); + throw new AndroidOperationException(message, responseMediaType); + } + } + + private void updateOperations(String deviceId, List operations) + throws OperationManagementException, PolicyComplianceException, + ApplicationManagementException, NotificationManagementException, DeviceManagementException { + for (org.wso2.carbon.device.mgt.common.operation.mgt.Operation operation : operations) { + AndroidAPIUtils.updateOperation(deviceId, operation); + if(operation.getStatus().equals(OPERATION_ERROR_STATUS)){ + org.wso2.carbon.device.mgt.common.notification.mgt.Notification notification = new + org.wso2.carbon.device.mgt.common.notification.mgt.Notification(); + DeviceIdentifier id = new DeviceIdentifier(); + id.setId(deviceId); + id.setType(DEVICE_TYPE_ANDROID); + String deviceName = AndroidAPIUtils.getDeviceManagementService().getDevice(id).getName(); + notification.setOperationId(operation.getId()); + notification.setStatus(org.wso2.carbon.device.mgt.common.notification.mgt.Notification. + Status.NEW.toString()); + notification.setDeviceIdentifier(id); + notification.setDescription("Operation " + operation.getCode() + " failed to execute on device "+ + deviceName+". Device ID : " + deviceId); + AndroidAPIUtils.getNotificationManagementService().addNotification(notification); + } + if (log.isDebugEnabled()) { + log.debug("Updating operation '" + operation.toString() + "'"); + } + } + } +} diff --git a/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/PolicyMgtService.java b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/PolicyMgtService.java new file mode 100644 index 0000000000..e2e9d2cb8a --- /dev/null +++ b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/PolicyMgtService.java @@ -0,0 +1,92 @@ +/* + * 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.wso2.carbon.mdm.services.android; + + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.device.mgt.common.DeviceIdentifier; +import org.wso2.carbon.mdm.services.android.exception.AndroidAgentException; +import org.wso2.carbon.mdm.services.android.util.AndroidAPIUtils; +import org.wso2.carbon.mdm.services.android.util.Message; +import org.wso2.carbon.policy.mgt.common.FeatureManagementException; +import org.wso2.carbon.policy.mgt.common.Policy; +import org.wso2.carbon.policy.mgt.common.PolicyManagementException; +import org.wso2.carbon.policy.mgt.common.ProfileFeature; +import org.wso2.carbon.policy.mgt.core.PolicyManagerService; + +import javax.jws.WebService; +import javax.ws.rs.*; +import javax.ws.rs.core.Response; +import java.util.List; + +@WebService +@Produces({"application/json", "application/xml"}) +@Consumes({"application/json", "application/xml"}) +public class PolicyMgtService { + + private static Log log = LogFactory.getLog(PolicyMgtService.class); + + @GET + @Path("{deviceId}") + public Message getEffectivePolicy(@HeaderParam("Accept") String acceptHeader, + @PathParam("deviceId") String id) throws AndroidAgentException { + + DeviceIdentifier deviceIdentifier = AndroidAPIUtils.convertToDeviceIdentifierObject(id); + Message responseMessage = new Message(); + Policy policy; + try { + PolicyManagerService policyManagerService = AndroidAPIUtils.getPolicyManagerService(); + policy = policyManagerService.getEffectivePolicy(deviceIdentifier); + if (policy == null) { + responseMessage = Message.responseMessage("No effective policy found"). + responseCode(Response.Status.NO_CONTENT.toString()).build(); + } else { + responseMessage = Message.responseMessage("Effective policy added to operation"). + responseCode(Response.Status.OK.toString()).build(); + } + } catch (PolicyManagementException e) { + String msg = "Error occurred while getting the policy."; + log.error(msg, e); + throw new AndroidAgentException(msg, e); + } + return responseMessage; + } + + @GET + @Path("/features/{deviceId}") + public List getEffectiveFeatures(@HeaderParam("Accept") String acceptHeader, + @PathParam("deviceId") String id) throws AndroidAgentException { + List profileFeatures; + DeviceIdentifier deviceIdentifier = AndroidAPIUtils.convertToDeviceIdentifierObject(id); + try { + PolicyManagerService policyManagerService = AndroidAPIUtils.getPolicyManagerService(); + profileFeatures = policyManagerService.getEffectiveFeatures(deviceIdentifier); + if (profileFeatures == null) { + Response.status(Response.Status.NOT_FOUND); + } + } catch (FeatureManagementException e) { + String msg = "Error occurred while getting the features."; + log.error(msg, e); + throw new AndroidAgentException(msg, e); + } + return profileFeatures; + } +} diff --git a/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/bean/AndroidOperation.java b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/bean/AndroidOperation.java new file mode 100644 index 0000000000..297a5df757 --- /dev/null +++ b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/bean/AndroidOperation.java @@ -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. + */ + +package org.wso2.carbon.mdm.services.android.bean; + +import com.google.gson.Gson; +/* +* This abstract class is used for extending generic functions with regard to operation. +*/ +public abstract class AndroidOperation { + + /* + * This method is used to convert operation object to a json format. + * + * @return json formatted String. + */ + public String toJSON() { + Gson gson = new Gson(); + return gson.toJson(this); + } +} diff --git a/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/bean/ApplicationInstallation.java b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/bean/ApplicationInstallation.java new file mode 100644 index 0000000000..e545c5bab7 --- /dev/null +++ b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/bean/ApplicationInstallation.java @@ -0,0 +1,55 @@ +/* + * 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.wso2.carbon.mdm.services.android.bean; + +import java.io.Serializable; + +/** + * This class represents the information of install application operation. + */ +public class ApplicationInstallation extends AndroidOperation implements Serializable { + + private String appIdentifier; + private String type; + private String url; + + public String getAppIdentifier() { + return appIdentifier; + } + + public void setAppIdentifier(String appIdentifier) { + this.appIdentifier = appIdentifier; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } +} diff --git a/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/bean/ApplicationUninstallation.java b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/bean/ApplicationUninstallation.java new file mode 100644 index 0000000000..5e0b2909f7 --- /dev/null +++ b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/bean/ApplicationUninstallation.java @@ -0,0 +1,64 @@ +/* + * 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.wso2.carbon.mdm.services.android.bean; + +import java.io.Serializable; + +/** + * This class represents the information of uninstall application operation. + */ +public class ApplicationUninstallation extends AndroidOperation implements Serializable { + + private String appIdentifier; + private String type; + private String url; + private String name; + + public String getAppIdentifier() { + return appIdentifier; + } + + public void setAppIdentifier(String appIdentifier) { + this.appIdentifier = appIdentifier; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/bean/BlacklistApplications.java b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/bean/BlacklistApplications.java new file mode 100644 index 0000000000..159ddfec9c --- /dev/null +++ b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/bean/BlacklistApplications.java @@ -0,0 +1,39 @@ +/* + * 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.wso2.carbon.mdm.services.android.bean; + +import java.io.Serializable; +import java.util.List; + +/** + * This class represents the blacklist applications information. + */ +public class BlacklistApplications extends AndroidOperation implements Serializable { + + private List appIdentifiers; + + public List getAppIdentifier() { + return appIdentifiers; + } + + public void setAppIdentifier(List appIdentifiers) { + this.appIdentifiers = appIdentifiers; + } + +} diff --git a/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/bean/Camera.java b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/bean/Camera.java new file mode 100644 index 0000000000..2b392a19c2 --- /dev/null +++ b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/bean/Camera.java @@ -0,0 +1,37 @@ +/* + * 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.wso2.carbon.mdm.services.android.bean; + +import java.io.Serializable; + +/** + * This class represents the information of camera operation. + */ +public class Camera extends AndroidOperation implements Serializable { + + private boolean enabled; + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } +} diff --git a/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/bean/DeviceEncryption.java b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/bean/DeviceEncryption.java new file mode 100644 index 0000000000..de81c94a57 --- /dev/null +++ b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/bean/DeviceEncryption.java @@ -0,0 +1,37 @@ +/* + * 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.wso2.carbon.mdm.services.android.bean; + +import java.io.Serializable; + +/** + * This class represents the information of encrypt operation. + */ +public class DeviceEncryption extends AndroidOperation implements Serializable { + + private boolean encrypted; + + public boolean isEncrypted() { + return encrypted; + } + + public void setEncrypted(boolean encrypted) { + this.encrypted = encrypted; + } +} \ No newline at end of file diff --git a/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/bean/Disenrollment.java b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/bean/Disenrollment.java new file mode 100644 index 0000000000..8a1c2f8ac8 --- /dev/null +++ b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/bean/Disenrollment.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.wso2.carbon.mdm.services.android.bean; + +import org.wso2.carbon.device.mgt.common.operation.mgt.Operation; +import java.io.Serializable; + +public class Disenrollment extends AndroidOperation implements Serializable { + + private boolean enabled; + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } +} diff --git a/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/bean/LockCode.java b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/bean/LockCode.java new file mode 100644 index 0000000000..a68ec2253a --- /dev/null +++ b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/bean/LockCode.java @@ -0,0 +1,37 @@ +/* + * 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.wso2.carbon.mdm.services.android.bean; + +import java.io.Serializable; + +/** + * This class represents the information of changing lock code operation. + */ +public class LockCode extends AndroidOperation implements Serializable { + + private String lockCode; + + public String getLockCode() { + return lockCode; + } + + public void setLockCode(String lockCode) { + this.lockCode = lockCode; + } +} diff --git a/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/bean/Notification.java b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/bean/Notification.java new file mode 100644 index 0000000000..95e6b56f1d --- /dev/null +++ b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/bean/Notification.java @@ -0,0 +1,37 @@ +/* + * 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.wso2.carbon.mdm.services.android.bean; + +import java.io.Serializable; + +/** + * This class represents the information of sending notification operation. + */ +public class Notification extends AndroidOperation implements Serializable { + + private String message; + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } +} diff --git a/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/bean/PasscodePolicy.java b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/bean/PasscodePolicy.java new file mode 100644 index 0000000000..5142f9e630 --- /dev/null +++ b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/bean/PasscodePolicy.java @@ -0,0 +1,91 @@ +/* + * 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.wso2.carbon.mdm.services.android.bean; + +import java.io.Serializable; + +/** + * This class represents the information of setting up password policy. + */ +public class PasscodePolicy extends AndroidOperation implements Serializable { + + private int maxFailedAttempts; + private int minLength; + private int pinHistory; + private int minComplexChars; + private int maxPINAgeInDays; + private boolean requireAlphanumeric; + private boolean allowSimple; + + public int getMaxFailedAttempts() { + return maxFailedAttempts; + } + + public void setMaxFailedAttempts(int maxFailedAttempts) { + this.maxFailedAttempts = maxFailedAttempts; + } + + public int getMinLength() { + return minLength; + } + + public void setMinLength(int minLength) { + this.minLength = minLength; + } + + public int getPinHistory() { + return pinHistory; + } + + public void setPinHistory(int pinHistory) { + this.pinHistory = pinHistory; + } + + public int getMinComplexChars() { + return minComplexChars; + } + + public void setMinComplexChars(int minComplexChars) { + this.minComplexChars = minComplexChars; + } + + public int getMaxPINAgeInDays() { + return maxPINAgeInDays; + } + + public void setMaxPINAgeInDays(int maxPINAgeInDays) { + this.maxPINAgeInDays = maxPINAgeInDays; + } + + public boolean isRequireAlphanumeric() { + return requireAlphanumeric; + } + + public void setRequireAlphanumeric(boolean requireAlphanumeric) { + this.requireAlphanumeric = requireAlphanumeric; + } + + public boolean isAllowSimple() { + return allowSimple; + } + + public void setAllowSimple(boolean allowSimple) { + this.allowSimple = allowSimple; + } +} diff --git a/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/bean/WebClip.java b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/bean/WebClip.java new file mode 100644 index 0000000000..77fa8e18d7 --- /dev/null +++ b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/bean/WebClip.java @@ -0,0 +1,55 @@ +/* + * 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.wso2.carbon.mdm.services.android.bean; + +import java.io.Serializable; + +/** + * This class represents the information of setting up webclip. + */ +public class WebClip extends AndroidOperation implements Serializable { + + private String identity; + private String title; + private String type; + + public String getIdentity() { + return identity; + } + + public void setIdentity(String identity) { + this.identity = identity; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } +} diff --git a/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/bean/Wifi.java b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/bean/Wifi.java new file mode 100644 index 0000000000..97cee8634d --- /dev/null +++ b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/bean/Wifi.java @@ -0,0 +1,46 @@ +/* + * 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.wso2.carbon.mdm.services.android.bean; + +import java.io.Serializable; + +/** + * This class represents the information of configuring wifi operation. + */ +public class Wifi extends AndroidOperation implements Serializable { + + private String ssid; + private String password; + + public String getSsid() { + return ssid; + } + + public void setSsid(String ssid) { + this.ssid = ssid; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } +} diff --git a/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/bean/WipeData.java b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/bean/WipeData.java new file mode 100644 index 0000000000..e3ba747cd7 --- /dev/null +++ b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/bean/WipeData.java @@ -0,0 +1,37 @@ +/* + * 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.wso2.carbon.mdm.services.android.bean; + +import java.io.Serializable; + +/** + * This class represents the information of wipedata operation. + */ +public class WipeData extends AndroidOperation implements Serializable { + + private String pin; + + public String getPin() { + return pin; + } + + public void setPin(String pin) { + this.pin = pin; + } +} diff --git a/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/bean/wrapper/ApplicationInstallationBeanWrapper.java b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/bean/wrapper/ApplicationInstallationBeanWrapper.java new file mode 100644 index 0000000000..ae809b8ee0 --- /dev/null +++ b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/bean/wrapper/ApplicationInstallationBeanWrapper.java @@ -0,0 +1,47 @@ +/* + * 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.wso2.carbon.mdm.services.android.bean.wrapper; + +import org.wso2.carbon.mdm.services.android.bean.ApplicationInstallation; + +import java.util.List; + +/** + * This class is used to wrap the InstallApplication bean with devices. + */ +public class ApplicationInstallationBeanWrapper { + + private List deviceIDs; + private ApplicationInstallation operation; + + public List getDeviceIDs() { + return deviceIDs; + } + + public void setDeviceIDs(List deviceIDs) { + this.deviceIDs = deviceIDs; + } + + public ApplicationInstallation getOperation() { + return operation; + } + + public void setOperation(ApplicationInstallation operation) { + this.operation = operation; + } +} diff --git a/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/bean/wrapper/ApplicationUninstallationBeanWrapper.java b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/bean/wrapper/ApplicationUninstallationBeanWrapper.java new file mode 100644 index 0000000000..ef34d97dcb --- /dev/null +++ b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/bean/wrapper/ApplicationUninstallationBeanWrapper.java @@ -0,0 +1,47 @@ +/* + * 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.wso2.carbon.mdm.services.android.bean.wrapper; + +import org.wso2.carbon.mdm.services.android.bean.ApplicationUninstallation; + +import java.util.List; + +/** + * This class is used to wrap the UninstallApplication bean with devices. + */ +public class ApplicationUninstallationBeanWrapper { + + private List deviceIDs; + private ApplicationUninstallation operation; + + public List getDeviceIDs() { + return deviceIDs; + } + + public void setDeviceIDs(List deviceIDs) { + this.deviceIDs = deviceIDs; + } + + public ApplicationUninstallation getOperation() { + return operation; + } + + public void setOperation(ApplicationUninstallation operation) { + this.operation = operation; + } +} diff --git a/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/bean/wrapper/BlacklistApplicationsBeanWrapper.java b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/bean/wrapper/BlacklistApplicationsBeanWrapper.java new file mode 100644 index 0000000000..f74b0e863a --- /dev/null +++ b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/bean/wrapper/BlacklistApplicationsBeanWrapper.java @@ -0,0 +1,47 @@ +/* + * 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.wso2.carbon.mdm.services.android.bean.wrapper; + +import org.wso2.carbon.mdm.services.android.bean.BlacklistApplications; + +import java.util.List; + +/** + * This class is used to wrap the BlacklistApplications bean with devices. + */ +public class BlacklistApplicationsBeanWrapper { + + private BlacklistApplications operation; + private List deviceIDs; + + public BlacklistApplications getOperation() { + return operation; + } + + public void setOperation(BlacklistApplications operation) { + this.operation = operation; + } + + public List getDeviceIDs() { + return deviceIDs; + } + + public void setDeviceIDs(List deviceIDs) { + this.deviceIDs = deviceIDs; + } +} diff --git a/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/bean/wrapper/CameraBeanWrapper.java b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/bean/wrapper/CameraBeanWrapper.java new file mode 100644 index 0000000000..4bbde3ac4f --- /dev/null +++ b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/bean/wrapper/CameraBeanWrapper.java @@ -0,0 +1,47 @@ +/* + * 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.wso2.carbon.mdm.services.android.bean.wrapper; + +import org.wso2.carbon.mdm.services.android.bean.Camera; + +import java.util.List; + +/** + * This class is used to wrap the Camera bean with devices. + */ +public class CameraBeanWrapper { + + private Camera operation; + private List deviceIDs; + + public Camera getOperation() { + return operation; + } + + public void setOperation(Camera operation) { + this.operation = operation; + } + + public List getDeviceIDs() { + return deviceIDs; + } + + public void setDeviceIDs(List deviceIDs) { + this.deviceIDs = deviceIDs; + } +} diff --git a/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/bean/wrapper/DisenrollmentBeanWrapper.java b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/bean/wrapper/DisenrollmentBeanWrapper.java new file mode 100644 index 0000000000..c4d22061ea --- /dev/null +++ b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/bean/wrapper/DisenrollmentBeanWrapper.java @@ -0,0 +1,44 @@ +/* + * 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.wso2.carbon.mdm.services.android.bean.wrapper; + +import org.wso2.carbon.mdm.services.android.bean.Disenrollment; + +import java.util.List; + +public class DisenrollmentBeanWrapper { + + private Disenrollment operation; + private List deviceIDs; + + public Disenrollment getOperation() { + return operation; + } + + public void setOperation(Disenrollment operation) { + this.operation = operation; + } + + public List getDeviceIDs() { + return deviceIDs; + } + + public void setDeviceIDs(List deviceIDs) { + this.deviceIDs = deviceIDs; + } +} diff --git a/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/bean/wrapper/EncryptionBeanWrapper.java b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/bean/wrapper/EncryptionBeanWrapper.java new file mode 100644 index 0000000000..462b1137f7 --- /dev/null +++ b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/bean/wrapper/EncryptionBeanWrapper.java @@ -0,0 +1,47 @@ +/* + * 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.wso2.carbon.mdm.services.android.bean.wrapper; + +import org.wso2.carbon.mdm.services.android.bean.DeviceEncryption; + +import java.util.List; + +/** + * This class is used to wrap the Encrypt bean with devices. + */ +public class EncryptionBeanWrapper { + + private DeviceEncryption operation; + private List deviceIDs; + + public DeviceEncryption getOperation() { + return operation; + } + + public void setOperation(DeviceEncryption operation) { + this.operation = operation; + } + + public List getDeviceIDs() { + return deviceIDs; + } + + public void setDeviceIDs(List deviceIDs) { + this.deviceIDs = deviceIDs; + } +} diff --git a/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/bean/wrapper/LockCodeBeanWrapper.java b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/bean/wrapper/LockCodeBeanWrapper.java new file mode 100644 index 0000000000..41594c280f --- /dev/null +++ b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/bean/wrapper/LockCodeBeanWrapper.java @@ -0,0 +1,47 @@ +/* + * 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.wso2.carbon.mdm.services.android.bean.wrapper; + +import org.wso2.carbon.mdm.services.android.bean.LockCode; + +import java.util.List; + +/** + * This class is used to wrap the LockCode bean with devices. + */ +public class LockCodeBeanWrapper { + + private LockCode operation; + private List deviceIDs; + + public LockCode getOperation() { + return operation; + } + + public void setOperation(LockCode operation) { + this.operation = operation; + } + + public List getDeviceIDs() { + return deviceIDs; + } + + public void setDeviceIDs(List deviceIDs) { + this.deviceIDs = deviceIDs; + } +} diff --git a/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/bean/wrapper/NotificationBeanWrapper.java b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/bean/wrapper/NotificationBeanWrapper.java new file mode 100644 index 0000000000..e1092861af --- /dev/null +++ b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/bean/wrapper/NotificationBeanWrapper.java @@ -0,0 +1,47 @@ +/* + * 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.wso2.carbon.mdm.services.android.bean.wrapper; + +import org.wso2.carbon.mdm.services.android.bean.Notification; + +import java.util.List; + +/** + * This class is used to wrap the Notification bean with devices. + */ +public class NotificationBeanWrapper { + + private List deviceIDs; + private Notification operation; + + public List getDeviceIDs() { + return deviceIDs; + } + + public void setDeviceIDs(List deviceIDs) { + this.deviceIDs = deviceIDs; + } + + public Notification getOperation() { + return operation; + } + + public void setOperation(Notification operation) { + this.operation = operation; + } +} diff --git a/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/bean/wrapper/PasswordPolicyBeanWrapper.java b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/bean/wrapper/PasswordPolicyBeanWrapper.java new file mode 100644 index 0000000000..cf54147e03 --- /dev/null +++ b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/bean/wrapper/PasswordPolicyBeanWrapper.java @@ -0,0 +1,47 @@ +/* + * 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.wso2.carbon.mdm.services.android.bean.wrapper; + +import org.wso2.carbon.mdm.services.android.bean.PasscodePolicy; + +import java.util.List; + +/** + * This class is used to wrap the PasswordPolicy bean with devices. + */ +public class PasswordPolicyBeanWrapper { + + private PasscodePolicy operation; + private List deviceIDs; + + public PasscodePolicy getOperation() { + return operation; + } + + public void setOperation(PasscodePolicy operation) { + this.operation = operation; + } + + public List getDeviceIDs() { + return deviceIDs; + } + + public void setDeviceIDs(List deviceIDs) { + this.deviceIDs = deviceIDs; + } +} diff --git a/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/bean/wrapper/WebClipBeanWrapper.java b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/bean/wrapper/WebClipBeanWrapper.java new file mode 100644 index 0000000000..7fbbe8559f --- /dev/null +++ b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/bean/wrapper/WebClipBeanWrapper.java @@ -0,0 +1,47 @@ +/* + * 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.wso2.carbon.mdm.services.android.bean.wrapper; + +import org.wso2.carbon.mdm.services.android.bean.WebClip; + +import java.util.List; + +/** + * This class is used to wrap the WebClip bean with devices. + */ +public class WebClipBeanWrapper { + + private WebClip operation; + private List deviceIDs; + + public WebClip getOperation() { + return operation; + } + + public void setOperation(WebClip operation) { + this.operation = operation; + } + + public List getDeviceIDs() { + return deviceIDs; + } + + public void setDeviceIDs(List deviceIDs) { + this.deviceIDs = deviceIDs; + } +} diff --git a/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/bean/wrapper/WifiBeanWrapper.java b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/bean/wrapper/WifiBeanWrapper.java new file mode 100644 index 0000000000..b19e017269 --- /dev/null +++ b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/bean/wrapper/WifiBeanWrapper.java @@ -0,0 +1,47 @@ +/* + * 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.wso2.carbon.mdm.services.android.bean.wrapper; + +import org.wso2.carbon.mdm.services.android.bean.Wifi; + +import java.util.List; + +/** + * This class is used to wrap the Wifi bean with devices. + */ +public class WifiBeanWrapper { + + private Wifi operation; + private List deviceIDs; + + public Wifi getOperation() { + return operation; + } + + public void setOperation(Wifi operation) { + this.operation = operation; + } + + public List getDeviceIDs() { + return deviceIDs; + } + + public void setDeviceIDs(List deviceIDs) { + this.deviceIDs = deviceIDs; + } +} diff --git a/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/bean/wrapper/WipeDataBeanWrapper.java b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/bean/wrapper/WipeDataBeanWrapper.java new file mode 100644 index 0000000000..ebf05d1cd7 --- /dev/null +++ b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/bean/wrapper/WipeDataBeanWrapper.java @@ -0,0 +1,47 @@ +/* + * 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.wso2.carbon.mdm.services.android.bean.wrapper; + +import org.wso2.carbon.mdm.services.android.bean.WipeData; + +import java.util.List; + +/** + * This class is used to wrap the WipeData bean with devices. + */ +public class WipeDataBeanWrapper { + + private WipeData operation; + private List deviceIDs; + + public WipeData getOperation() { + return operation; + } + + public void setOperation(WipeData operation) { + this.operation = operation; + } + + public List getDeviceIDs() { + return deviceIDs; + } + + public void setDeviceIDs(List deviceIDs) { + this.deviceIDs = deviceIDs; + } +} diff --git a/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/common/ErrorHandler.java b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/common/ErrorHandler.java new file mode 100644 index 0000000000..6d5e0d1c4a --- /dev/null +++ b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/common/ErrorHandler.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2014, 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.wso2.carbon.mdm.services.android.common; + +import org.wso2.carbon.mdm.services.android.exception.AndroidAgentException; + +import javax.ws.rs.Produces; +import javax.ws.rs.core.Response; +import javax.ws.rs.ext.ExceptionMapper; + + +@Produces({ "application/json", "application/xml" }) +public class ErrorHandler implements ExceptionMapper { + + public Response toResponse(AndroidAgentException exception) { + ErrorMessage errorMessage = new ErrorMessage(); + errorMessage.setErrorMessage(exception.getErrorMessage()); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(errorMessage).build(); + } +} diff --git a/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/common/ErrorMessage.java b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/common/ErrorMessage.java new file mode 100644 index 0000000000..86be36f36f --- /dev/null +++ b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/common/ErrorMessage.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2014, 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.wso2.carbon.mdm.services.android.common; + + +public class ErrorMessage { + + private String errorMessage; + private String errorCode; + + public String getErrorMessage() { + return errorMessage; + } + + public void setErrorMessage(String errorMessage) { + this.errorMessage = errorMessage; + } + + public String getErrorCode() { + return errorCode; + } + + public void setErrorCode(String errorCode) { + this.errorCode = errorCode; + } +} diff --git a/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/common/GsonMessageBodyHandler.java b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/common/GsonMessageBodyHandler.java new file mode 100644 index 0000000000..193eda1bc8 --- /dev/null +++ b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/common/GsonMessageBodyHandler.java @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2014, 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.wso2.carbon.mdm.services.android.common; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + +import javax.ws.rs.Consumes; +import javax.ws.rs.Produces; +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.ext.MessageBodyReader; +import javax.ws.rs.ext.MessageBodyWriter; +import javax.ws.rs.ext.Provider; + +import java.io.*; +import java.lang.annotation.Annotation; +import java.lang.reflect.Type; + +import static javax.ws.rs.core.MediaType.APPLICATION_JSON; + +@Provider +@Produces(APPLICATION_JSON) +@Consumes(APPLICATION_JSON) +public class GsonMessageBodyHandler implements MessageBodyWriter, MessageBodyReader { + + private Gson gson; + private static final String UTF_8 = "UTF-8"; + + public boolean isReadable(Class aClass, Type type, Annotation[] annotations, MediaType mediaType) { + return true; + } + + private Gson getGson() { + if (gson == null) { + final GsonBuilder gsonBuilder = new GsonBuilder(); + gson = gsonBuilder.create(); + } + return gson; + } + + public Object readFrom(Class objectClass, Type type, Annotation[] annotations, MediaType mediaType, + MultivaluedMap stringStringMultivaluedMap, InputStream entityStream) + throws IOException, WebApplicationException { + + InputStreamReader reader = new InputStreamReader(entityStream, "UTF-8"); + + try { + return getGson().fromJson(reader, type); + } finally { + reader.close(); + } + } + + public boolean isWriteable(Class aClass, Type type, Annotation[] annotations, MediaType mediaType) { + return true; + } + + public long getSize(Object o, Class aClass, Type type, Annotation[] annotations, MediaType mediaType) { + return -1; + } + + public void writeTo(Object object, Class aClass, Type type, Annotation[] annotations, MediaType mediaType, + MultivaluedMap stringObjectMultivaluedMap, OutputStream entityStream) + throws IOException, WebApplicationException { + + OutputStreamWriter writer = new OutputStreamWriter(entityStream, UTF_8); + try { + Type jsonType; + if (type.equals(type)) { + jsonType = type; + } else { + jsonType = type; + } + getGson().toJson(object, jsonType, writer); + } finally { + writer.close(); + } + } +} diff --git a/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/exception/AndroidAgentException.java b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/exception/AndroidAgentException.java new file mode 100644 index 0000000000..8698dda76e --- /dev/null +++ b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/exception/AndroidAgentException.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2014, 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.wso2.carbon.mdm.services.android.exception; + +/** + * + * Custom exception class for Android API related exceptions. + * + */ +public class AndroidAgentException extends Exception{ + + private static final long serialVersionUID = 7950151650447893900L; + private String errorMessage; + + public String getErrorMessage() { + return errorMessage; + } + + public void setErrorMessage(String errorMessage) { + this.errorMessage = errorMessage; + } + + public AndroidAgentException(String msg, Exception nestedEx) { + super(msg, nestedEx); + setErrorMessage(msg); + } + + public AndroidAgentException(String message, Throwable cause) { + super(message, cause); + setErrorMessage(message); + } + + public AndroidAgentException(String msg) { + super(msg); + setErrorMessage(msg); + } + + public AndroidAgentException() { + super(); + } + + public AndroidAgentException(Throwable cause) { + super(cause); + } + + + +} diff --git a/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/exception/AndroidOperationException.java b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/exception/AndroidOperationException.java new file mode 100644 index 0000000000..d2938a115d --- /dev/null +++ b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/exception/AndroidOperationException.java @@ -0,0 +1,37 @@ +/* + * 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.wso2.carbon.mdm.services.android.exception; + +import org.wso2.carbon.mdm.services.android.util.Message; + +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +/** + * Custom exception class for wrapping Android Operation related exceptions. + */ +public class AndroidOperationException extends WebApplicationException { + + public AndroidOperationException(Message message, MediaType mediaType) { + super(Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(message). + type(mediaType).build()); + } +} + diff --git a/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/exception/BadRequestException.java b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/exception/BadRequestException.java new file mode 100644 index 0000000000..dcf69575ee --- /dev/null +++ b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/exception/BadRequestException.java @@ -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. + */ + +package org.wso2.carbon.mdm.services.android.exception; + +import org.wso2.carbon.mdm.services.android.util.Message; + +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +/** + * Custom exception class for wrapping BadRequest related exceptions. + */ +public class BadRequestException extends WebApplicationException { + + public BadRequestException(Message message, MediaType mediaType) { + super(Response.status(Response.Status.BAD_REQUEST).entity(message). + type(mediaType).build()); + } +} \ No newline at end of file diff --git a/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/exception/OperationConfigurationException.java b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/exception/OperationConfigurationException.java new file mode 100644 index 0000000000..f0437f9d95 --- /dev/null +++ b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/exception/OperationConfigurationException.java @@ -0,0 +1,37 @@ +/* + * 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.wso2.carbon.mdm.services.android.exception; + +/** + * + * Custom exception class for operation configuration related exceptions. + * + */ +public class OperationConfigurationException extends Exception { + + private static final long serialVersionUID = 7435543643747623629L; + + public OperationConfigurationException(String errorMessage) { + super(errorMessage); + } + + public OperationConfigurationException(String errorMessage, Throwable throwable) { + super(errorMessage, throwable); + } +} diff --git a/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/util/AndroidAPIUtils.java b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/util/AndroidAPIUtils.java new file mode 100644 index 0000000000..2292a3ea2c --- /dev/null +++ b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/util/AndroidAPIUtils.java @@ -0,0 +1,215 @@ +/* + * 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.wso2.carbon.mdm.services.android.util; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonParser; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.context.PrivilegedCarbonContext; +import org.wso2.carbon.device.mgt.common.Device; +import org.wso2.carbon.device.mgt.common.DeviceIdentifier; +import org.wso2.carbon.device.mgt.common.DeviceManagementConstants; +import org.wso2.carbon.device.mgt.common.DeviceManagementException; +import org.wso2.carbon.device.mgt.common.app.mgt.Application; +import org.wso2.carbon.device.mgt.common.app.mgt.ApplicationManagementException; +import org.wso2.carbon.device.mgt.common.notification.mgt.NotificationManagementService; +import org.wso2.carbon.device.mgt.common.operation.mgt.Operation; +import org.wso2.carbon.device.mgt.common.operation.mgt.OperationManagementException; +import org.wso2.carbon.device.mgt.core.app.mgt.ApplicationManagementProviderService; +import org.wso2.carbon.device.mgt.core.service.DeviceManagementProviderService; +import org.wso2.carbon.device.mgt.mobile.android.impl.gcm.GCMService; +import org.wso2.carbon.policy.mgt.common.monitor.PolicyComplianceException; +import org.wso2.carbon.policy.mgt.core.PolicyManagerService; + +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import java.util.ArrayList; +import java.util.List; + +/** + * AndroidAPIUtil class provides utility functions used by Android REST-API classes. + */ +public class AndroidAPIUtils { + + private static Log log = LogFactory.getLog(AndroidAPIUtils.class); + + public static DeviceIdentifier convertToDeviceIdentifierObject(String deviceId) { + DeviceIdentifier identifier = new DeviceIdentifier(); + identifier.setId(deviceId); + identifier.setType(DeviceManagementConstants.MobileDeviceTypes.MOBILE_DEVICE_TYPE_ANDROID); + return identifier; + } + + public static String getAuthenticatedUser() { + PrivilegedCarbonContext threadLocalCarbonContext = PrivilegedCarbonContext.getThreadLocalCarbonContext(); + String username = threadLocalCarbonContext.getUsername(); + String tenantDomain = threadLocalCarbonContext.getTenantDomain(); + if (username.endsWith(tenantDomain)) { + return username.substring(0, username.lastIndexOf("@")); + } + return username; + } + + public static DeviceManagementProviderService getDeviceManagementService() { + PrivilegedCarbonContext ctx = PrivilegedCarbonContext.getThreadLocalCarbonContext(); + DeviceManagementProviderService deviceManagementProviderService = + (DeviceManagementProviderService) ctx.getOSGiService(DeviceManagementProviderService.class, null); + if (deviceManagementProviderService == null) { + String msg = "Device Management service has not initialized."; + log.error(msg); + throw new IllegalStateException(msg); + } + return deviceManagementProviderService; + } + + public static GCMService getGCMService() { + PrivilegedCarbonContext ctx = PrivilegedCarbonContext.getThreadLocalCarbonContext(); + GCMService gcmService = (GCMService) ctx.getOSGiService(GCMService.class, null); + if (gcmService == null) { + String msg = "GCM service has not initialized."; + log.error(msg); + throw new IllegalStateException(msg); + } + return gcmService; + } + + public static MediaType getResponseMediaType(String acceptHeader) { + MediaType responseMediaType; + if (MediaType.WILDCARD.equals(acceptHeader)) { + responseMediaType = MediaType.APPLICATION_JSON_TYPE; + } else { + responseMediaType = MediaType.valueOf(acceptHeader); + } + return responseMediaType; + } + + public static Response getOperationResponse(List deviceIDs, Operation operation, + Message message, MediaType responseMediaType) + throws DeviceManagementException, OperationManagementException { + + AndroidDeviceUtils deviceUtils = new AndroidDeviceUtils(); + DeviceIDHolder deviceIDHolder = deviceUtils.validateDeviceIdentifiers(deviceIDs, + message, responseMediaType); + int status = getDeviceManagementService().addOperation(operation, deviceIDHolder.getValidDeviceIDList()); + if (status > 0) { + GCMService gcmService = getGCMService(); + if (gcmService.isGCMEnabled()) { + List devices = new ArrayList(); + for (DeviceIdentifier deviceIdentifier : deviceIDHolder.getValidDeviceIDList()) { + devices.add(getDeviceManagementService().getDevice(deviceIdentifier)); + } + getGCMService().sendNotification(operation.getCode(), devices); + } + } + if (!deviceIDHolder.getErrorDeviceIdList().isEmpty()) { + return javax.ws.rs.core.Response.status(AndroidConstants.StatusCodes. + MULTI_STATUS_HTTP_CODE).type( + responseMediaType).entity(deviceUtils. + convertErrorMapIntoErrorMessage(deviceIDHolder.getErrorDeviceIdList())).build(); + } + return javax.ws.rs.core.Response.status(javax.ws.rs.core.Response.Status.CREATED). + type(responseMediaType).build(); + } + + + public static PolicyManagerService getPolicyManagerService() { + PrivilegedCarbonContext ctx = PrivilegedCarbonContext.getThreadLocalCarbonContext(); + PolicyManagerService policyManagerService = (PolicyManagerService) ctx.getOSGiService( + PolicyManagerService.class, null); + if (policyManagerService == null) { + String msg = "Policy Manager service has not initialized"; + log.error(msg); + throw new IllegalStateException(msg); + } + return policyManagerService; + } + + public static ApplicationManagementProviderService getApplicationManagerService() { + PrivilegedCarbonContext ctx = PrivilegedCarbonContext.getThreadLocalCarbonContext(); + ApplicationManagementProviderService applicationManagementProviderService = + (ApplicationManagementProviderService) ctx.getOSGiService(ApplicationManagementProviderService.class, null); + if (applicationManagementProviderService == null) { + String msg = "Application Management provider service has not initialized"; + log.error(msg); + throw new IllegalStateException(msg); + } + return applicationManagementProviderService; + } + + public static NotificationManagementService getNotificationManagementService() { + NotificationManagementService notificationManagementService; + PrivilegedCarbonContext ctx = PrivilegedCarbonContext.getThreadLocalCarbonContext(); + notificationManagementService = (NotificationManagementService) ctx.getOSGiService( + NotificationManagementService.class, null); + if (notificationManagementService == null) { + String msg = "Notification Management service not initialized."; + log.error(msg); + throw new IllegalStateException(msg); + } + return notificationManagementService; + } + + public static void updateOperation(String deviceId, Operation operation) + throws OperationManagementException, PolicyComplianceException, ApplicationManagementException { + DeviceIdentifier deviceIdentifier = new DeviceIdentifier(); + deviceIdentifier.setId(deviceId); + deviceIdentifier.setType(DeviceManagementConstants.MobileDeviceTypes.MOBILE_DEVICE_TYPE_ANDROID); + if (AndroidConstants.OperationCodes.MONITOR.equals(operation.getCode())) { + if (log.isDebugEnabled()) { + log.info("Received compliance status from MONITOR operation ID: " + operation.getId()); + } + getPolicyManagerService().checkPolicyCompliance(deviceIdentifier, operation.getOperationResponse()); + } else if (AndroidConstants.OperationCodes.APPLICATION_LIST.equals(operation.getCode())) { + if (log.isDebugEnabled()) { + log.info("Received applications list from device '" + deviceId + "'"); + } + updateApplicationList(operation, deviceIdentifier); + } + getDeviceManagementService().updateOperation(deviceIdentifier, operation); + } + + public static List getPendingOperations + (DeviceIdentifier deviceIdentifier) throws OperationManagementException { + + List operations; + operations = getDeviceManagementService().getPendingOperations(deviceIdentifier); + return operations; + } + + private static void updateApplicationList(Operation operation, DeviceIdentifier deviceIdentifier) + throws ApplicationManagementException { + List applications = new ArrayList(); + // Parsing json string to get applications list. + JsonElement jsonElement = new JsonParser().parse(operation.getOperationResponse()); + JsonArray jsonArray = jsonElement.getAsJsonArray(); + Application app; + for (JsonElement element : jsonArray) { + app = new Application(); + app.setName(element.getAsJsonObject(). + get(AndroidConstants.ApplicationProperties.NAME).getAsString()); + app.setApplicationIdentifier(element.getAsJsonObject(). + get(AndroidConstants.ApplicationProperties.IDENTIFIER).getAsString()); + app.setPlatform(DeviceManagementConstants.MobileDeviceTypes.MOBILE_DEVICE_TYPE_ANDROID); + applications.add(app); + } + getApplicationManagerService().updateApplicationListInstalledInDevice(deviceIdentifier, applications); + } +} diff --git a/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/util/AndroidConstants.java b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/util/AndroidConstants.java new file mode 100644 index 0000000000..875560de79 --- /dev/null +++ b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/util/AndroidConstants.java @@ -0,0 +1,114 @@ +/* + * 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.wso2.carbon.mdm.services.android.util; + +/** + * Defines constants used in Android-REST API bundle. + */ +public final class AndroidConstants { + + public final class DeviceProperties{ + private DeviceProperties() { + throw new AssertionError(); + } + public static final String PROPERTY_USER_KEY = "username"; + public static final String PROPERTY_DEVICE_KEY = "device"; + } + + public final class DeviceFeatures{ + private DeviceFeatures() { + throw new AssertionError(); + } + } + + public final class DeviceConstants{ + private DeviceConstants() { + throw new AssertionError(); + } + public static final String DEVICE_MAC_KEY = "mac"; + public static final String DEVICE_DESCRIPTION_KEY = "description"; + public static final String DEVICE_OWNERSHIP_KEY = "ownership"; + public static final String DEVICE_PROPERTIES_KEY = "properties"; + public static final String DEVICE_FEATURES_KEY = "features"; + public static final String DEVICE_DATA = "data"; + public static final String DEVICE_ID_NOT_FOUND = "Device Id not found for device found at %s"; + public static final String DEVICE_ID_SERVICE_NOT_FOUND = + "Issue in retrieving device management service instance for device found at %s"; + } + + public final class Messages{ + private Messages(){ + throw new AssertionError(); + } + public static final String DEVICE_MANAGER_SERVICE_NOT_AVAILABLE = + "Device Manager service not available"; + } + + public final class OperationCodes{ + private OperationCodes(){ + throw new AssertionError(); + } + public static final String DEVICE_LOCK = "DEVICE_LOCK"; + public static final String DEVICE_LOCATION = "DEVICE_LOCATION"; + public static final String WIFI = "WIFI"; + public static final String CAMERA = "CAMERA"; + public static final String DEVICE_MUTE = "DEVICE_MUTE"; + public static final String PASSCODE_POLICY = "PASSCODE_POLICY"; + public static final String DEVICE_INFO = "DEVICE_INFO"; + public static final String ENTERPRISE_WIPE = "ENTERPRISE_WIPE"; + public static final String CLEAR_PASSWORD = "CLEAR_PASSWORD"; + public static final String WIPE_DATA = "WIPE_DATA"; + public static final String APPLICATION_LIST = "APPLICATION_LIST"; + public static final String CHANGE_LOCK_CODE = "CHANGE_LOCK_CODE"; + public static final String INSTALL_APPLICATION = "INSTALL_APPLICATION"; + public static final String UNINSTALL_APPLICATION = "UNINSTALL_APPLICATION"; + public static final String BLACKLIST_APPLICATIONS = "BLACKLIST_APPLICATIONS"; + public static final String ENCRYPT_STORAGE = "ENCRYPT_STORAGE"; + public static final String DEVICE_RING = "DEVICE_RING"; + public static final String NOTIFICATION = "NOTIFICATION"; + public static final String WEBCLIP = "WEBCLIP"; + public static final String DISENROLL = "DISENROLL"; + public static final String MONITOR = "MONITOR"; + } + + public final class StatusCodes{ + private StatusCodes(){ + throw new AssertionError(); + } + public static final int MULTI_STATUS_HTTP_CODE = 207; + } + + public final class TenantConfigProperties{ + private TenantConfigProperties(){ + throw new AssertionError(); + } + public static final String LICENSE_KEY = "androidEula"; + public static final String LANGUAGE_US = "en_US"; + public static final String CONTENT_TYPE_TEXT = "text"; + } + + public final class ApplicationProperties { + private ApplicationProperties(){ + throw new AssertionError(); + } + public static final String NAME = "name"; + public static final String IDENTIFIER = "package"; + public static final String ICON = "icon"; + } +} diff --git a/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/util/AndroidDeviceUtils.java b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/util/AndroidDeviceUtils.java new file mode 100644 index 0000000000..b41fbd392e --- /dev/null +++ b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/util/AndroidDeviceUtils.java @@ -0,0 +1,98 @@ +/* + * 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.wso2.carbon.mdm.services.android.util; + +import org.apache.commons.lang.StringUtils; +import org.wso2.carbon.device.mgt.common.*; +import org.wso2.carbon.mdm.services.android.exception.BadRequestException; + +import javax.ws.rs.core.MediaType; +import java.util.ArrayList; +import java.util.List; + +/** + * Util class for holding Android device related util methods. + */ +public class AndroidDeviceUtils { + + private static final String COMMA_SEPARATION_PATTERN = ", "; + + public DeviceIDHolder validateDeviceIdentifiers(List deviceIDs, + Message message, MediaType responseMediaType) { + + if (deviceIDs == null || deviceIDs.isEmpty()) { + message.setResponseMessage("Device identifier list is empty"); + throw new BadRequestException(message, responseMediaType); + } + + List errorDeviceIdList = new ArrayList(); + List validDeviceIDList = new ArrayList(); + + int deviceIDCounter = 0; + for (String deviceID : deviceIDs) { + + deviceIDCounter++; + + if (deviceID == null || deviceID.isEmpty()) { + errorDeviceIdList.add(String.format(AndroidConstants.DeviceConstants.DEVICE_ID_NOT_FOUND, + deviceIDCounter)); + continue; + } + + try { + DeviceIdentifier deviceIdentifier = new DeviceIdentifier(); + deviceIdentifier.setId(deviceID); + deviceIdentifier.setType(DeviceManagementConstants.MobileDeviceTypes. + MOBILE_DEVICE_TYPE_ANDROID); + + if (isValidDeviceIdentifier(deviceIdentifier)) { + validDeviceIDList.add(deviceIdentifier); + } else { + errorDeviceIdList.add(String.format(AndroidConstants.DeviceConstants.DEVICE_ID_NOT_FOUND, + deviceIDCounter)); + } + } catch (DeviceManagementException e) { + errorDeviceIdList.add(String.format(AndroidConstants.DeviceConstants.DEVICE_ID_SERVICE_NOT_FOUND, + deviceIDCounter)); + } + } + + DeviceIDHolder deviceIDHolder = new DeviceIDHolder(); + deviceIDHolder.setValidDeviceIDList(validDeviceIDList); + deviceIDHolder.setErrorDeviceIdList(errorDeviceIdList); + + return deviceIDHolder; + } + + public String convertErrorMapIntoErrorMessage(List errorDeviceIdList) { + return StringUtils.join(errorDeviceIdList.iterator(), COMMA_SEPARATION_PATTERN); + } + + public static boolean isValidDeviceIdentifier(DeviceIdentifier deviceIdentifier) throws DeviceManagementException { + Device device = AndroidAPIUtils.getDeviceManagementService(). + getDevice(deviceIdentifier); + if (device == null || device.getDeviceIdentifier() == null || + device.getDeviceIdentifier().isEmpty() || device.getEnrolmentInfo() == null) { + return false; + } else if (EnrolmentInfo.Status.REMOVED.equals(device.getEnrolmentInfo().getStatus())) { + return false; + } + return true; + } +} diff --git a/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/util/DeviceIDHolder.java b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/util/DeviceIDHolder.java new file mode 100644 index 0000000000..db85657303 --- /dev/null +++ b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/util/DeviceIDHolder.java @@ -0,0 +1,48 @@ +/* + * 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.wso2.carbon.mdm.services.android.util; + +import org.wso2.carbon.device.mgt.common.DeviceIdentifier; + +import java.util.List; + +/** + * Holder class for storing valid & invalid device-ids. + */ +public class DeviceIDHolder { + + private List errorDeviceIdList; + private List validDeviceIDList; + + public List getErrorDeviceIdList() { + return errorDeviceIdList; + } + + public void setErrorDeviceIdList(List errorDeviceIdList) { + this.errorDeviceIdList = errorDeviceIdList; + } + + public List getValidDeviceIDList() { + return validDeviceIDList; + } + + public void setValidDeviceIDList(List validDeviceIDList) { + this.validDeviceIDList = validDeviceIDList; + } +} diff --git a/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/util/Message.java b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/util/Message.java new file mode 100644 index 0000000000..d756310017 --- /dev/null +++ b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/java/org/wso2/carbon/mdm/services/android/util/Message.java @@ -0,0 +1,88 @@ +/* + * 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.wso2.carbon.mdm.services.android.util; + +import javax.ws.rs.core.MediaType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; + +/** + * This class contains the information of response message. + */ +@XmlRootElement +public class Message { + + private String responseCode; + private String responseMessage; + + @XmlElement + public String getResponseMessage() { + return responseMessage; + } + + public void setResponseMessage(String responseMessage) { + this.responseMessage = responseMessage; + } + + @XmlElement + public String getResponseCode() { + return responseCode; + } + + public void setResponseCode(String responseCode) { + this.responseCode = responseCode; + } + + private Message.MessageBuilder getBuilder() { + return new Message.MessageBuilder(); + } + + public static Message.MessageBuilder responseCode(String responseCode) { + Message message = new Message(); + return message.getBuilder().responseCode(responseCode); + } + + public static Message.MessageBuilder responseMessage(String responseMessage) { + Message message = new Message(); + return message.getBuilder().responseMessage(responseMessage); + } + + public class MessageBuilder { + + private String responseCode; + private String responseMessage; + + public MessageBuilder responseCode(String responseCode) { + this.responseCode = responseCode; + return this; + } + + public MessageBuilder responseMessage(String responseMessage) { + this.responseMessage = responseMessage; + return this; + } + + public Message build() { + Message message = new Message(); + message.setResponseCode(responseCode); + message.setResponseMessage(responseMessage); + return message; + } + } +} diff --git a/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/webapp/META-INF/permissions.xml b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/webapp/META-INF/permissions.xml new file mode 100644 index 0000000000..42478f0217 --- /dev/null +++ b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/webapp/META-INF/permissions.xml @@ -0,0 +1,315 @@ + + + + + + + + + Get Pending Operation + /device-mgt/android/operations/poll + /operation/* + PUT + emm_android_agent + + + + Lock + /device-mgt/android/operations/lock + /operation/lock + POST + emm_admin,emm_user + + + + Mute + /device-mgt/android/operations/mute + /operation/mute + POST + emm_admin,emm_user + + + + Location + /device-mgt/android/operations/location + /operation/location + POST + emm_admin,emm_user + + + + Clear Passcode + /operation/clear-password/ + POST + emm_admin + + + + Camera + /device-mgt/android/operations/camera + /operation/camera + POST + emm_admin,emm_user + + + + Device Info + /device-mgt/android/operations/device-info + /operation/device-info + POST + emm_admin,emm_user + + + + Enterprise Wipe + /device-mgt/android/operations/enterprise-wipe + /operation/enterprise-wipe + POST + emm_admin + + + + Wipe Data + /device-mgt/android/operations/wipe-data + /operation/wipe-data + POST + emm_admin + + + + Application List + /device-mgt/android/operations/application-list + /operation/application-list + POST + emm_admin,emm_user + + + + Ring + /device-mgt/android/operations/ring-device + /operation/ring-device + POST + emm_admin,emm_user + + + + Install Application + /device-mgt/android/operations/install-application + /operation/install-application + POST + emm_admin,emm_user + + + + Uninstall Application + /device-mgt/android/operations/uninstall-application + /operation/uninstall-application + POST + emm_admin,emm_user + + + + Blacklist Applications + /device-mgt/android/operations/blacklist-applications + /operation/blacklist-applications + POST + emm_admin + + + + Notification + /device-mgt/android/operations/notification + /operation/notification + POST + emm_admin,emm_user + + + + Wifi + /device-mgt/android/operations/wifi + /operation/wifi + POST + emm_admin + + + + Encryption + /device-mgt/android/operations/encrypt + /operation/encrypt + POST + emm_admin + + + + Change lock code + /device-mgt/android/operations/change-lock-code + /operation/change-lock-code + POST + emm_admin + + + + Password Policy + /device-mgt/android/operations/password-policy + /operation/password-policy + POST + emm_admin + + + + Webclip + /device-mgt/android/operations/webclip + /operation/webclip + POST + emm_admin + + + + Disenroll + /device-mgt/android/operations/disenroll + /operation/disenroll + POST + emm_android_agent + + + + + View devices + /device-mgt/android/devices/list + /device + GET + emm_admin + + + + View device + /device-mgt/android/devices/view + /device/* + GET + emm_admin,emm_user + + + + Update device + /device-mgt/android/devices/update + /device/* + PUT + emm_admin,emm_user + + + + Update application list + /device-mgt/android/devices/update-app + /device/appList/* + POST + emm_admin + + + + View license + /device-mgt/android/license/view + /device/license + GET + emm_android_agent + + + + + Enroll device + /device-mgt/android/devices/enroll + /enrollment + POST + emm_android_agent + + + + Get Enrollment Status + /device-mgt/android/devices/enroll + /enrollment/* + GET + emm_android_agent + + + + Update Enrollment + /device-mgt/android/devices/enroll + /enrollment/* + PUT + emm_user,emm_admin + + + + Disenroll device + /device-mgt/android/devices/enroll + /enrollment/* + DELETE + emm_user,emm_admin + + + + + View Policies + /device-mgt/android/policies/view + /policy/* + GET + emm_admin + + + + View Policy Features + /device-mgt/android/policies/view + /policy/features/* + GET + emm_admin + + + + + View Tenant configuration + /device-mgt/android/tenant/configuration + /configuration + GET + emm_admin + + + + Add Tenant configuration + /device-mgt/android/tenant/configuration + /configuration + POST + emm_admin + + + + Update Tenant configuration + /device-mgt/android/tenant/configuration + /configuration + PUT + emm_admin + + \ No newline at end of file diff --git a/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/webapp/META-INF/webapp-classloading.xml b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/webapp/META-INF/webapp-classloading.xml new file mode 100644 index 0000000000..46c211bb2b --- /dev/null +++ b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/webapp/META-INF/webapp-classloading.xml @@ -0,0 +1,35 @@ + + + + + + + + + false + + + CXF,Carbon + diff --git a/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/webapp/WEB-INF/cxf-servlet.xml b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/webapp/WEB-INF/cxf-servlet.xml new file mode 100644 index 0000000000..cfe04fb73a --- /dev/null +++ b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/webapp/WEB-INF/cxf-servlet.xml @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/webapp/WEB-INF/web.xml b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 0000000000..9e336958f0 --- /dev/null +++ b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,77 @@ + + + + Android-Agent-Webapp + + JAX-WS/JAX-RS MDM Android Endpoint + JAX-WS/JAX-RS Servlet + CXFServlet + + org.apache.cxf.transport.servlet.CXFServlet + + 1 + + + CXFServlet + /* + + + 60 + + + + isAdminService + false + + + managed-api-enabled + false + + + managed-api-owner + admin + + + doAuthentication + true + + + + + + + + + + + + + + diff --git a/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/webapp/servicelist.css b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/webapp/servicelist.css new file mode 100644 index 0000000000..85bc3f613c --- /dev/null +++ b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android.agent/src/main/webapp/servicelist.css @@ -0,0 +1,117 @@ +@CHARSET "ISO-8859-1"; + +/* http://meyerweb.com/eric/tools/css/reset/ + v2.0 | 20110126 + License: none (public domain) +*/ + +html, body, div, span, applet, object, iframe, +h1, h2, h3, h4, h5, h6, p, blockquote, pre, +a, abbr, acronym, address, big, cite, code, +del, dfn, em, img, ins, kbd, q, s, samp, +small, strike, strong, sub, sup, tt, var, +b, u, i, center, +dl, dt, dd, ol, ul, li, +fieldset, form, label, legend, +table, caption, tbody, tfoot, thead, tr, th, td, +article, aside, canvas, details, embed, +figure, figcaption, footer, header, hgroup, +menu, nav, output, ruby, section, summary, +time, mark, audio, video { + margin: 0; + padding: 0; + border: 0; + font-size: 100%; + font: inherit; + vertical-align: baseline; +} +/* HTML5 display-role reset for older browsers */ +article, aside, details, figcaption, figure, +footer, header, hgroup, menu, nav, section { + display: block; +} + +html { + background: #efefef; +} + +body { + line-height: 1; + width:960px; + margin:auto; + background:white; + padding:10px; + box-shadow:0px 0px 5px #CCC; + font-family:"Lucida Grande","Lucida Sans","Microsoft Sans Serif", "Lucida Sans Unicode","Verdana","Sans-serif","trebuchet ms" !important; + +} +ol, ul { + list-style: none; +} +blockquote, q { + quotes: none; +} +blockquote:before, blockquote:after, +q:before, q:after { + content: ''; + content: none; +} +table { + border-collapse: collapse; + border-spacing: 0; + width:960px; + border:solid 1px #ccc; +} + +table a { + font-size:12px; + color:#1e90ff; + padding:7px; +float:left; +; +} + +.heading { + font-size: 18px; + margin-top: 20px; + float:left; + color:#0067B1; + margin-bottom:20px; + padding-top:20px; +} + +.field { + font-weight: normal; + width:120px; + font-size:12px; + float:left; + padding:7px; + clear:left; +} +.value { + font-weight: bold; + font-size:12px; + float:left; + padding:7px; + clear:right; +} +.porttypename { + font-weight: bold; + font-size:14px; +} +UL { + margin-top: 0; +} +LI { + font-weight: normal; + font-size:12px; + margin-top:10px; +} + +TD { + border:1px solid #ccc; + vertical-align: text-top; + padding: 5px; +} + + diff --git a/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android/pom.xml b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android/pom.xml new file mode 100644 index 0000000000..15f5e0f91e --- /dev/null +++ b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android/pom.xml @@ -0,0 +1,176 @@ + + + + + + + android-plugin + org.wso2.carbon.devicemgt-plugins + 2.1.0-SNAPSHOT + ../pom.xml + + + 4.0.0 + org.wso2.carbon.device.mgt.mobile.android + bundle + WSO2 Carbon - Mobile Device Management Android Impl + WSO2 Carbon - Mobile Device Management Android Implementation + http://wso2.org + + + + + org.apache.felix + maven-scr-plugin + + + org.apache.felix + maven-bundle-plugin + 1.4.0 + true + + + ${project.artifactId} + ${project.artifactId} + ${carbon.devicemgt.plugins.version} + Device Management Mobile Android Impl Bundle + org.wso2.carbon.device.mgt.mobile.android.internal + + org.osgi.framework, + org.osgi.service.component, + org.apache.commons.logging, + javax.xml.bind.*, + javax.naming, + javax.sql, + javax.xml.bind.annotation.*, + javax.xml.parsers.*;resolution:=optional, + org.w3c.dom, + org.wso2.carbon.core, + org.wso2.carbon.context, + org.wso2.carbon.utils.*, + org.wso2.carbon.device.mgt.common.*, + org.wso2.carbon.device.mgt.mobile.*, + org.wso2.carbon.ndatasource.core, + org.wso2.carbon.policy.mgt.common.*, + org.wso2.carbon.policy.mgt.core.*, + org.wso2.carbon.registry.core, + org.wso2.carbon.registry.core.exceptions, + org.wso2.carbon.registry.core.service, + org.wso2.carbon.registry.core.session, + org.wso2.carbon.registry.api, + org.wso2.carbon.device.mgt.extensions.license.mgt.registry, + com.google.gson.* + + + !org.wso2.carbon.device.mgt.mobile.android.internal, + org.wso2.carbon.device.mgt.mobile.android.* + + + + + + + + + + + + + + + + + + + + + + + + org.wso2.carbon.devicemgt-plugins + org.wso2.carbon.device.mgt.mobile + + + org.eclipse.osgi + org.eclipse.osgi + + + org.eclipse.osgi + org.eclipse.osgi.services + + + org.wso2.carbon + org.wso2.carbon.core + + + org.wso2.carbon + org.wso2.carbon.logging + + + org.wso2.carbon + org.wso2.carbon.utils + + + org.wso2.carbon.devicemgt + org.wso2.carbon.device.mgt.common + + + org.wso2.carbon.devicemgt + org.wso2.carbon.device.mgt.extensions + + + org.wso2.carbon + org.wso2.carbon.ndatasource.core + + + org.wso2.carbon.devicemgt + org.wso2.carbon.policy.mgt.common + + + org.wso2.carbon.devicemgt + org.wso2.carbon.policy.mgt.core + + + org.wso2.carbon + org.wso2.carbon.registry.api + + + org.wso2.carbon + org.wso2.carbon.registry.core + + + org.testng + testng + + + org.apache.tomcat.wso2 + jdbc-pool + + + com.h2database.wso2 + h2-database-engine + test + + + com.google.code.gson + gson + + + diff --git a/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android/src/main/java/org/wso2/carbon/device/mgt/mobile/android/impl/AndroidDeviceManagementService.java b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android/src/main/java/org/wso2/carbon/device/mgt/mobile/android/impl/AndroidDeviceManagementService.java new file mode 100644 index 0000000000..60c29fec9b --- /dev/null +++ b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android/src/main/java/org/wso2/carbon/device/mgt/mobile/android/impl/AndroidDeviceManagementService.java @@ -0,0 +1,109 @@ +/* + * 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.wso2.carbon.device.mgt.mobile.android.impl; + +import org.wso2.carbon.device.mgt.common.DeviceIdentifier; +import org.wso2.carbon.device.mgt.common.DeviceManagementException; +import org.wso2.carbon.device.mgt.common.DeviceManager; +import org.wso2.carbon.device.mgt.common.app.mgt.Application; +import org.wso2.carbon.device.mgt.common.app.mgt.ApplicationManagementException; +import org.wso2.carbon.device.mgt.common.app.mgt.ApplicationManager; +import org.wso2.carbon.device.mgt.common.operation.mgt.Operation; +import org.wso2.carbon.device.mgt.common.spi.DeviceManagementService; + +import java.util.List; + +/** + * This represents the Android implementation of DeviceManagerService. + */ +public class AndroidDeviceManagementService implements DeviceManagementService { + + private DeviceManager deviceManager; + public static final String DEVICE_TYPE_ANDROID = "android"; + public static final String DEVICE_TYPE_TENANT = "carbon.super"; + + @Override + public String getType() { + return AndroidDeviceManagementService.DEVICE_TYPE_ANDROID; + } + + @Override + public String getProviderTenantDomain() { + return DEVICE_TYPE_TENANT; + } + + @Override + public boolean isSharedWithAllTenants() { + return true; + } + + @Override + public void init() throws DeviceManagementException { + this.deviceManager = new AndroidDeviceManager(); + } + + @Override + public DeviceManager getDeviceManager() { + return deviceManager; + } + + @Override + public ApplicationManager getApplicationManager() { + return null; + } + + @Override + public void notifyOperationToDevices(Operation operation, List deviceIdentifiers) + throws DeviceManagementException { + + } + + @Override + public Application[] getApplications(String s, int i, int i2) throws ApplicationManagementException { + return new Application[0]; + } + + @Override + public void updateApplicationStatus(DeviceIdentifier deviceIdentifier, Application application, + String s) throws ApplicationManagementException { + + } + + @Override + public String getApplicationStatus(DeviceIdentifier deviceIdentifier, + Application application) throws ApplicationManagementException { + return null; + } + + @Override public void installApplicationForDevices(Operation operation, List deviceIdentifiers) + throws ApplicationManagementException { + + } + + @Override public void installApplicationForUsers(Operation operation, List strings) + throws ApplicationManagementException { + + } + + @Override public void installApplicationForUserRoles(Operation operation, List strings) + throws ApplicationManagementException { + + } + +} diff --git a/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android/src/main/java/org/wso2/carbon/device/mgt/mobile/android/impl/AndroidDeviceManager.java b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android/src/main/java/org/wso2/carbon/device/mgt/mobile/android/impl/AndroidDeviceManager.java new file mode 100644 index 0000000000..e4996c0e4d --- /dev/null +++ b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android/src/main/java/org/wso2/carbon/device/mgt/mobile/android/impl/AndroidDeviceManager.java @@ -0,0 +1,356 @@ +/* + * 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.wso2.carbon.device.mgt.mobile.android.impl; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.device.mgt.common.Device; +import org.wso2.carbon.device.mgt.common.DeviceIdentifier; +import org.wso2.carbon.device.mgt.common.DeviceManagementConstants; +import org.wso2.carbon.device.mgt.common.DeviceManagementException; +import org.wso2.carbon.device.mgt.common.DeviceManager; +import org.wso2.carbon.device.mgt.common.EnrolmentInfo; +import org.wso2.carbon.device.mgt.common.FeatureManager; +import org.wso2.carbon.device.mgt.common.configuration.mgt.TenantConfiguration; +import org.wso2.carbon.device.mgt.common.license.mgt.License; +import org.wso2.carbon.device.mgt.common.license.mgt.LicenseManagementException; +import org.wso2.carbon.device.mgt.common.license.mgt.LicenseManager; +import org.wso2.carbon.device.mgt.extensions.license.mgt.registry.RegistryBasedLicenseManager; +import org.wso2.carbon.device.mgt.mobile.android.impl.dao.AndroidDAOFactory; +import org.wso2.carbon.device.mgt.mobile.android.impl.util.AndroidPluginUtils; +import org.wso2.carbon.device.mgt.mobile.common.MobileDeviceMgtPluginException; +import org.wso2.carbon.device.mgt.mobile.common.MobilePluginConstants; +import org.wso2.carbon.device.mgt.mobile.dao.AbstractMobileDeviceManagementDAOFactory; +import org.wso2.carbon.device.mgt.mobile.dao.MobileDeviceManagementDAOException; +import org.wso2.carbon.device.mgt.mobile.dto.MobileDevice; +import org.wso2.carbon.device.mgt.mobile.util.MobileDeviceManagementUtil; +import org.wso2.carbon.registry.api.RegistryException; +import org.wso2.carbon.registry.api.Resource; + +import javax.xml.bind.JAXBContext; +import javax.xml.bind.JAXBException; +import javax.xml.bind.Marshaller; +import javax.xml.bind.Unmarshaller; +import java.io.StringReader; +import java.io.StringWriter; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.List; + +public class AndroidDeviceManager implements DeviceManager { + + private AbstractMobileDeviceManagementDAOFactory daoFactory; + private static final Log log = LogFactory.getLog(AndroidDeviceManagementService.class); + private FeatureManager featureManager = new AndroidFeatureManager(); + private LicenseManager licenseManager; + + public AndroidDeviceManager() { + this.daoFactory = new AndroidDAOFactory(); + this.licenseManager = new RegistryBasedLicenseManager(); + License defaultLicense; + + try { + if (licenseManager.getLicense(AndroidDeviceManagementService.DEVICE_TYPE_ANDROID, + MobilePluginConstants.LANGUAGE_CODE_ENGLISH_US) == null) { + defaultLicense = AndroidPluginUtils.getDefaultLicense(); + licenseManager.addLicense(AndroidDeviceManagementService.DEVICE_TYPE_ANDROID, defaultLicense); + } + featureManager.addSupportedFeaturesToDB(); + } catch (LicenseManagementException e) { + log.error("Error occurred while adding default license for Android devices", e); + } catch (DeviceManagementException e) { + log.error("Error occurred while adding supported device features for Android platform", e); + } + } + + @Override + public FeatureManager getFeatureManager() { + return featureManager; + } + + @Override + public boolean saveConfiguration(TenantConfiguration tenantConfiguration) + throws DeviceManagementException { + boolean status; + try { + if (log.isDebugEnabled()) { + log.debug("Persisting android configurations in Registry"); + } + String resourcePath = MobileDeviceManagementUtil.getPlatformConfigPath( + DeviceManagementConstants. + MobileDeviceTypes.MOBILE_DEVICE_TYPE_ANDROID); + StringWriter writer = new StringWriter(); + JAXBContext context = JAXBContext.newInstance(TenantConfiguration.class); + Marshaller marshaller = context.createMarshaller(); + marshaller.marshal(tenantConfiguration, writer); + + Resource resource = MobileDeviceManagementUtil.getConfigurationRegistry().newResource(); + resource.setContent(writer.toString()); + resource.setMediaType(MobilePluginConstants.MEDIA_TYPE_XML); + MobileDeviceManagementUtil.putRegistryResource(resourcePath, resource); + status = true; + } catch (MobileDeviceMgtPluginException e) { + throw new DeviceManagementException( + "Error occurred while retrieving the Registry instance : " + e.getMessage(), e); + } catch (RegistryException e) { + throw new DeviceManagementException( + "Error occurred while persisting the Registry resource of Android Configuration : " + e.getMessage(), e); + } catch (JAXBException e) { + throw new DeviceManagementException( + "Error occurred while parsing the Android configuration : " + e.getMessage(), e); + } + return status; + } + + @Override + public TenantConfiguration getConfiguration() throws DeviceManagementException { + Resource resource; + try { + String androidRegPath = + MobileDeviceManagementUtil.getPlatformConfigPath(DeviceManagementConstants. + MobileDeviceTypes.MOBILE_DEVICE_TYPE_ANDROID); + resource = MobileDeviceManagementUtil.getRegistryResource(androidRegPath); + if (resource != null) { + JAXBContext context = JAXBContext.newInstance(TenantConfiguration.class); + Unmarshaller unmarshaller = context.createUnmarshaller(); + return (TenantConfiguration) unmarshaller.unmarshal( + new StringReader(new String((byte[]) resource.getContent(), Charset. + forName(MobilePluginConstants.CHARSET_UTF8)))); + } + return null; + } catch (MobileDeviceMgtPluginException e) { + throw new DeviceManagementException( + "Error occurred while retrieving the Registry instance : " + e.getMessage(), e); + } catch (JAXBException e) { + throw new DeviceManagementException( + "Error occurred while parsing the Android configuration : " + e.getMessage(), e); + } catch (RegistryException e) { + throw new DeviceManagementException( + "Error occurred while retrieving the Registry resource of Android Configuration : " + e.getMessage(), e); + } + } + + @Override + public boolean enrollDevice(Device device) throws DeviceManagementException { + boolean status = false; + MobileDevice mobileDevice = MobileDeviceManagementUtil.convertToMobileDevice(device); + try { + if (log.isDebugEnabled()) { + log.debug("Enrolling a new Android device : " + device.getDeviceIdentifier()); + } + boolean isEnrolled = this.isEnrolled( + new DeviceIdentifier(device.getDeviceIdentifier(), device.getType())); + if (isEnrolled) { + this.modifyEnrollment(device); + } else { + AndroidDAOFactory.beginTransaction(); + status = daoFactory.getMobileDeviceDAO().addMobileDevice(mobileDevice); + AndroidDAOFactory.commitTransaction(); + } + } catch (MobileDeviceManagementDAOException e) { + try { + AndroidDAOFactory.rollbackTransaction(); + } catch (MobileDeviceManagementDAOException mobileDAOEx) { + String msg = "Error occurred while roll back the device enrol transaction :" + + device.toString(); + log.warn(msg, mobileDAOEx); + } + String msg = + "Error while enrolling the Android device : " + device.getDeviceIdentifier(); + throw new DeviceManagementException(msg, e); + } + return status; + } + + @Override + public boolean modifyEnrollment(Device device) throws DeviceManagementException { + boolean status; + MobileDevice mobileDevice = MobileDeviceManagementUtil.convertToMobileDevice(device); + try { + if (log.isDebugEnabled()) { + log.debug("Modifying the Android device enrollment data"); + } + AndroidDAOFactory.beginTransaction(); + status = daoFactory.getMobileDeviceDAO().updateMobileDevice(mobileDevice); + AndroidDAOFactory.commitTransaction(); + } catch (MobileDeviceManagementDAOException e) { + try { + AndroidDAOFactory.rollbackTransaction(); + } catch (MobileDeviceManagementDAOException mobileDAOEx) { + String msg = "Error occurred while roll back the update device transaction :" + + device.toString(); + log.warn(msg, mobileDAOEx); + } + String msg = "Error while updating the enrollment of the Android device : " + + device.getDeviceIdentifier(); + throw new DeviceManagementException(msg, e); + } + return status; + } + + @Override + public boolean disenrollDevice(DeviceIdentifier deviceId) throws DeviceManagementException { + //Here we don't have anything specific to do. Hence returning. + return true; + } + + @Override + public boolean isEnrolled(DeviceIdentifier deviceId) throws DeviceManagementException { + boolean isEnrolled = false; + try { + if (log.isDebugEnabled()) { + log.debug("Checking the enrollment of Android device : " + deviceId.getId()); + } + MobileDevice mobileDevice = + daoFactory.getMobileDeviceDAO().getMobileDevice(deviceId.getId()); + if (mobileDevice != null) { + isEnrolled = true; + } + } catch (MobileDeviceManagementDAOException e) { + String msg = "Error while checking the enrollment status of Android device : " + + deviceId.getId(); + throw new DeviceManagementException(msg, e); + } + return isEnrolled; + } + + @Override + public boolean isActive(DeviceIdentifier deviceId) throws DeviceManagementException { + return true; + } + + @Override + public boolean setActive(DeviceIdentifier deviceId, boolean status) + throws DeviceManagementException { + return true; + } + + @Override + public Device getDevice(DeviceIdentifier deviceId) throws DeviceManagementException { + Device device; + try { + if (log.isDebugEnabled()) { + log.debug("Getting the details of Android device : '" + deviceId.getId() + "'"); + } + MobileDevice mobileDevice = daoFactory.getMobileDeviceDAO(). + getMobileDevice(deviceId.getId()); + device = MobileDeviceManagementUtil.convertToDevice(mobileDevice); + } catch (MobileDeviceManagementDAOException e) { + throw new DeviceManagementException( + "Error occurred while fetching the Android device: '" + + deviceId.getId() + "'", e); + } + return device; + } + + @Override + public boolean setOwnership(DeviceIdentifier deviceId, String ownershipType) + throws DeviceManagementException { + return true; + } + + @Override + public boolean isClaimable(DeviceIdentifier deviceIdentifier) throws DeviceManagementException { + return false; + } + + @Override + public boolean setStatus(DeviceIdentifier deviceIdentifier, String currentUser, + EnrolmentInfo.Status status) throws DeviceManagementException { + return false; + } + + @Override + public License getLicense(String languageCode) throws LicenseManagementException { + return licenseManager. + getLicense(AndroidDeviceManagementService.DEVICE_TYPE_ANDROID, languageCode); + } + + @Override + public void addLicense(License license) throws LicenseManagementException { + licenseManager.addLicense(AndroidDeviceManagementService.DEVICE_TYPE_ANDROID, license); + } + + @Override + public boolean requireDeviceAuthorization() { + return true; + } + + @Override + public boolean updateDeviceInfo(DeviceIdentifier deviceIdentifier, Device device) + throws DeviceManagementException { + boolean status; + Device existingDevice = this.getDevice(deviceIdentifier); + // This object holds the current persisted device object + MobileDevice existingMobileDevice = + MobileDeviceManagementUtil.convertToMobileDevice(existingDevice); + + // This object holds the newly received device object from response + MobileDevice mobileDevice = MobileDeviceManagementUtil.convertToMobileDevice(device); + + // Updating current object features using newer ones + existingMobileDevice.setLatitude(mobileDevice.getLatitude()); + existingMobileDevice.setLongitude(mobileDevice.getLongitude()); + existingMobileDevice.setDeviceProperties(mobileDevice.getDeviceProperties()); + + try { + if (log.isDebugEnabled()) { + log.debug( + "updating the details of Android device : " + device.getDeviceIdentifier()); + } + AndroidDAOFactory.beginTransaction(); + status = daoFactory.getMobileDeviceDAO().updateMobileDevice(existingMobileDevice); + AndroidDAOFactory.commitTransaction(); + } catch (MobileDeviceManagementDAOException e) { + try { + AndroidDAOFactory.rollbackTransaction(); + } catch (MobileDeviceManagementDAOException e1) { + log.warn("Error occurred while roll back the update device info transaction : '" + + device.toString() + "'", e1); + } + throw new DeviceManagementException( + "Error occurred while updating the Android device: '" + + device.getDeviceIdentifier() + "'", e); + } + return status; + } + + @Override + public List getAllDevices() throws DeviceManagementException { + List devices = null; + try { + if (log.isDebugEnabled()) { + log.debug("Fetching the details of all Android devices"); + } + List mobileDevices = + daoFactory.getMobileDeviceDAO().getAllMobileDevices(); + if (mobileDevices != null) { + devices = new ArrayList<>(mobileDevices.size()); + for (MobileDevice mobileDevice : mobileDevices) { + devices.add(MobileDeviceManagementUtil.convertToDevice(mobileDevice)); + } + } + } catch (MobileDeviceManagementDAOException e) { + throw new DeviceManagementException("Error occurred while fetching all Android devices", + e); + } + return devices; + } + +} diff --git a/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android/src/main/java/org/wso2/carbon/device/mgt/mobile/android/impl/AndroidFeatureManager.java b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android/src/main/java/org/wso2/carbon/device/mgt/mobile/android/impl/AndroidFeatureManager.java new file mode 100644 index 0000000000..4649cc63b0 --- /dev/null +++ b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android/src/main/java/org/wso2/carbon/device/mgt/mobile/android/impl/AndroidFeatureManager.java @@ -0,0 +1,244 @@ +/* + * 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.wso2.carbon.device.mgt.mobile.android.impl; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.device.mgt.common.DeviceManagementException; +import org.wso2.carbon.device.mgt.common.Feature; +import org.wso2.carbon.device.mgt.common.FeatureManager; +import org.wso2.carbon.device.mgt.mobile.android.impl.dao.AndroidDAOFactory; +import org.wso2.carbon.device.mgt.mobile.dao.MobileDeviceManagementDAOException; +import org.wso2.carbon.device.mgt.mobile.dao.MobileDeviceManagementDAOFactory; +import org.wso2.carbon.device.mgt.mobile.dao.MobileFeatureDAO; +import org.wso2.carbon.device.mgt.mobile.dto.MobileFeature; +import org.wso2.carbon.device.mgt.mobile.util.MobileDeviceManagementUtil; + +import java.util.ArrayList; +import java.util.List; + +public class AndroidFeatureManager implements FeatureManager { + + private MobileFeatureDAO featureDAO; + private static final Log log = LogFactory.getLog(AndroidFeatureManager.class); + + public AndroidFeatureManager() { + MobileDeviceManagementDAOFactory daoFactory = new AndroidDAOFactory(); + this.featureDAO = daoFactory.getMobileFeatureDAO(); + } + + @Override + public boolean addFeature(Feature feature) throws DeviceManagementException { + try { + AndroidDAOFactory.beginTransaction(); + MobileFeature mobileFeature = MobileDeviceManagementUtil.convertToMobileFeature(feature); + featureDAO.addFeature(mobileFeature); + AndroidDAOFactory.commitTransaction(); + return true; + } catch (MobileDeviceManagementDAOException e) { + try { + AndroidDAOFactory.rollbackTransaction(); + } catch (MobileDeviceManagementDAOException e1) { + log.warn("Error occurred while roll-backing the transaction", e); + } + throw new DeviceManagementException("Error occurred while adding the feature", e); + } + } + + @Override + public boolean addFeatures(List features) throws DeviceManagementException { + List mobileFeatures = new ArrayList(features.size()); + for (Feature feature : features) { + mobileFeatures.add(MobileDeviceManagementUtil.convertToMobileFeature(feature)); + } + try { + AndroidDAOFactory.beginTransaction(); + featureDAO.addFeatures(mobileFeatures); + AndroidDAOFactory.commitTransaction(); + return true; + } catch (MobileDeviceManagementDAOException e) { + try { + AndroidDAOFactory.rollbackTransaction(); + } catch (MobileDeviceManagementDAOException e1) { + log.warn("Error occurred while roll-backing the transaction", e); + } + throw new DeviceManagementException("Error occurred while adding the features", e); + } + } + + @Override + public Feature getFeature(String name) throws DeviceManagementException { + try { + MobileFeature mobileFeature = featureDAO.getFeatureByCode(name); + Feature feature = MobileDeviceManagementUtil.convertToFeature(mobileFeature); + return feature; + } catch (MobileDeviceManagementDAOException e) { + throw new DeviceManagementException("Error occurred while retrieving the feature", e); + } + } + + @Override + public List getFeatures() throws DeviceManagementException { + try { + List mobileFeatures = featureDAO.getAllFeatures(); + List featureList = new ArrayList(mobileFeatures.size()); + for (MobileFeature mobileFeature : mobileFeatures) { + featureList.add(MobileDeviceManagementUtil.convertToFeature(mobileFeature)); + } + return featureList; + } catch (MobileDeviceManagementDAOException e) { + throw new DeviceManagementException("Error occurred while retrieving the list of features registered for " + + "Android platform", e); + } + } + + @Override + public boolean removeFeature(String code) throws DeviceManagementException { + boolean status; + try { + AndroidDAOFactory.beginTransaction(); + featureDAO.deleteFeatureByCode(code); + AndroidDAOFactory.commitTransaction(); + status = true; + } catch (MobileDeviceManagementDAOException e) { + try { + AndroidDAOFactory.rollbackTransaction(); + } catch (MobileDeviceManagementDAOException e1) { + log.warn("Error occurred while roll-backing the transaction", e); + } + throw new DeviceManagementException("Error occurred while removing the feature", e); + } + return status; + } + + @Override + public boolean addSupportedFeaturesToDB() throws DeviceManagementException { + synchronized (this) { + List supportedFeatures = getSupportedFeatures(); + List existingFeatures = this.getFeatures(); + List missingFeatures = MobileDeviceManagementUtil. + getMissingFeatures(supportedFeatures, existingFeatures); + if (missingFeatures.size() > 0) { + return this.addFeatures(missingFeatures); + } + return true; + } + } + + //Get the supported feature list. + private static List getSupportedFeatures() { + List supportedFeatures = new ArrayList(); + Feature feature = new Feature(); + feature.setCode("DEVICE_LOCK"); + feature.setName("Device Lock"); + feature.setDescription("Lock the device"); + supportedFeatures.add(feature); + feature = new Feature(); + feature.setCode("DEVICE_LOCATION"); + feature.setName("Location"); + feature.setDescription("Request coordinates of device location"); + supportedFeatures.add(feature); + feature = new Feature(); + feature.setCode("WIFI"); + feature.setName("wifi"); + feature.setDescription("Setting up wifi configuration"); + supportedFeatures.add(feature); + feature = new Feature(); + feature.setCode("CAMERA"); + feature.setName("camera"); + feature.setDescription("Enable or disable camera"); + supportedFeatures.add(feature); + feature = new Feature(); + feature.setCode("EMAIL"); + feature.setName("Email"); + feature.setDescription("Configure email settings"); + supportedFeatures.add(feature); + feature = new Feature(); + feature.setCode("DEVICE_MUTE"); + feature.setName("mute"); + feature.setDescription("Enable mute in the device"); + supportedFeatures.add(feature); + feature = new Feature(); + feature.setCode("DEVICE_INFO"); + feature.setName("Device info"); + feature.setDescription("Request device information"); + supportedFeatures.add(feature); + feature = new Feature(); + feature.setCode("ENTERPRISE_WIPE"); + feature.setName("Enterprise Wipe"); + feature.setDescription("Remove enterprise applications"); + supportedFeatures.add(feature); + feature = new Feature(); + feature.setCode("CLEAR_PASSWORD"); + feature.setName("Clear Password"); + feature.setDescription("Clear current password"); + supportedFeatures.add(feature); + feature = new Feature(); + feature.setCode("WIPE_DATA"); + feature.setName("Wipe Data"); + feature.setDescription("Factory reset the device"); + supportedFeatures.add(feature); + feature = new Feature(); + feature.setCode("APPLICATION_LIST"); + feature.setName("Application List"); + feature.setDescription("Request list of current installed applications"); + supportedFeatures.add(feature); + feature = new Feature(); + feature.setCode("CHANGE_LOCK_CODE"); + feature.setName("Change Lock-code"); + feature.setDescription("Change current lock code"); + supportedFeatures.add(feature); + feature = new Feature(); + feature.setCode("INSTALL_APPLICATION"); + feature.setName("Install App"); + feature.setDescription("Install Enterprise or Market application"); + supportedFeatures.add(feature); + feature = new Feature(); + feature.setCode("UNINSTALL_APPLICATION"); + feature.setName("Uninstall App"); + feature.setDescription("Uninstall application"); + supportedFeatures.add(feature); + feature = new Feature(); + feature.setCode("BLACKLIST_APPLICATIONS"); + feature.setName("Blacklist app"); + feature.setDescription("Blacklist applications"); + supportedFeatures.add(feature); + feature = new Feature(); + feature.setCode("ENCRYPT_STORAGE"); + feature.setName("Encrypt storage"); + feature.setDescription("Encrypt storage"); + supportedFeatures.add(feature); + feature = new Feature(); + feature.setCode("DEVICE_RING"); + feature.setName("Ring"); + feature.setDescription("Ring the device"); + supportedFeatures.add(feature); + feature = new Feature(); + feature.setCode("PASSCODE_POLICY"); + feature.setName("Password Policy"); + feature.setDescription("Set passcode policy"); + supportedFeatures.add(feature); + feature = new Feature(); + feature.setCode("NOTIFICATION"); + feature.setName("Message"); + feature.setDescription("Send message"); + supportedFeatures.add(feature); + return supportedFeatures; + } +} \ No newline at end of file diff --git a/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android/src/main/java/org/wso2/carbon/device/mgt/mobile/android/impl/AndroidPolicyMonitoringService.java b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android/src/main/java/org/wso2/carbon/device/mgt/mobile/android/impl/AndroidPolicyMonitoringService.java new file mode 100644 index 0000000000..35c28bd2f2 --- /dev/null +++ b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android/src/main/java/org/wso2/carbon/device/mgt/mobile/android/impl/AndroidPolicyMonitoringService.java @@ -0,0 +1,100 @@ +/* + * 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.wso2.carbon.device.mgt.mobile.android.impl; + +import com.google.gson.Gson; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonParser; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.device.mgt.common.Device; +import org.wso2.carbon.device.mgt.common.DeviceIdentifier; +import org.wso2.carbon.device.mgt.common.DeviceManagementConstants; +import org.wso2.carbon.device.mgt.mobile.android.impl.gcm.GCMService; +import org.wso2.carbon.device.mgt.mobile.android.internal.AndroidDeviceManagementDataHolder; +import org.wso2.carbon.policy.mgt.common.Policy; +import org.wso2.carbon.policy.mgt.common.monitor.ComplianceData; +import org.wso2.carbon.policy.mgt.common.monitor.ComplianceFeature; +import org.wso2.carbon.policy.mgt.common.monitor.PolicyComplianceException; +import org.wso2.carbon.policy.mgt.common.spi.PolicyMonitoringService; + +import java.util.ArrayList; +import java.util.List; + +public class AndroidPolicyMonitoringService implements PolicyMonitoringService { + + private static Log log = LogFactory.getLog(AndroidPolicyMonitoringService.class); + + @Override + public void notifyDevices(List list) throws PolicyComplianceException { + GCMService gcmService = AndroidDeviceManagementDataHolder.getInstance().getGCMService(); + if (gcmService.isGCMEnabled() && !list.isEmpty()) { + gcmService.sendNotification("POLICY_BUNDLE", list); + } + } + + @Override + public ComplianceData checkPolicyCompliance(DeviceIdentifier deviceIdentifier, Policy policy, + Object compliancePayload) throws PolicyComplianceException { + if (log.isDebugEnabled()) { + log.debug("Checking policy compliance status of device '" + deviceIdentifier.getId() + "'"); + } + ComplianceData complianceData = new ComplianceData(); + if (compliancePayload == null || policy == null) { + return complianceData; + } + String compliancePayloadString = new Gson().toJson(compliancePayload); + // Parsing json string to get compliance features. + JsonElement jsonElement; + if (compliancePayloadString instanceof String) { + jsonElement = new JsonParser().parse(compliancePayloadString); + } else { + throw new PolicyComplianceException("Invalid policy compliance payload"); + } + + JsonArray jsonArray = jsonElement.getAsJsonArray(); + Gson gson = new Gson(); + ComplianceFeature complianceFeature; + List complianceFeatures = new ArrayList(jsonArray.size()); + List nonComplianceFeatures = new ArrayList<>(); + + for (JsonElement element : jsonArray) { + complianceFeature = gson.fromJson(element, ComplianceFeature.class); + complianceFeatures.add(complianceFeature); + } + + for (ComplianceFeature cf : complianceFeatures) { + if (!cf.isCompliant()) { + complianceData.setStatus(false); + nonComplianceFeatures.add(cf); + break; + } + } + + complianceData.setComplianceFeatures(nonComplianceFeatures); + return complianceData; + } + + @Override + public String getType() { + return DeviceManagementConstants.MobileDeviceTypes.MOBILE_DEVICE_TYPE_ANDROID; + } +} \ No newline at end of file diff --git a/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android/src/main/java/org/wso2/carbon/device/mgt/mobile/android/impl/dao/AndroidDAOFactory.java b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android/src/main/java/org/wso2/carbon/device/mgt/mobile/android/impl/dao/AndroidDAOFactory.java new file mode 100644 index 0000000000..a2e676accb --- /dev/null +++ b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android/src/main/java/org/wso2/carbon/device/mgt/mobile/android/impl/dao/AndroidDAOFactory.java @@ -0,0 +1,120 @@ +/* + * 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.wso2.carbon.device.mgt.mobile.android.impl.dao; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.device.mgt.common.DeviceManagementConstants; +import org.wso2.carbon.device.mgt.mobile.android.impl.dao.impl.AndroidDeviceDAOImpl; +import org.wso2.carbon.device.mgt.mobile.android.impl.dao.impl.AndroidFeatureDAOImpl; +import org.wso2.carbon.device.mgt.mobile.dao.AbstractMobileDeviceManagementDAOFactory; +import org.wso2.carbon.device.mgt.mobile.dao.MobileDeviceDAO; +import org.wso2.carbon.device.mgt.mobile.dao.MobileDeviceManagementDAOException; +import org.wso2.carbon.device.mgt.mobile.dao.MobileFeatureDAO; + +import javax.sql.DataSource; +import java.sql.Connection; +import java.sql.SQLException; + +public class AndroidDAOFactory extends AbstractMobileDeviceManagementDAOFactory { + + private static final Log log = LogFactory.getLog(AndroidDAOFactory.class); + protected static DataSource dataSource; + private static ThreadLocal currentConnection = new ThreadLocal<>(); + + public AndroidDAOFactory() { + this.dataSource = getDataSourceMap().get(DeviceManagementConstants.MobileDeviceTypes.MOBILE_DEVICE_TYPE_ANDROID); + } + + @Override + public MobileDeviceDAO getMobileDeviceDAO() { + return new AndroidDeviceDAOImpl(); + } + + public MobileFeatureDAO getMobileFeatureDAO() { + return new AndroidFeatureDAOImpl(); + } + + public static void beginTransaction() throws MobileDeviceManagementDAOException { + try { + Connection conn = dataSource.getConnection(); + conn.setAutoCommit(false); + currentConnection.set(conn); + } catch (SQLException e) { + throw new MobileDeviceManagementDAOException("Error occurred while retrieving datasource connection", e); + } + } + + public static Connection getConnection() throws MobileDeviceManagementDAOException { + if (currentConnection.get() == null) { + try { + currentConnection.set(dataSource.getConnection()); + } catch (SQLException e) { + throw new MobileDeviceManagementDAOException("Error occurred while retrieving data source connection", + e); + } + } + return currentConnection.get(); + } + + public static void commitTransaction() throws MobileDeviceManagementDAOException { + 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 MobileDeviceManagementDAOException("Error occurred while committing the transaction", e); + } + } + + public static void closeConnection() throws MobileDeviceManagementDAOException { + Connection conn = currentConnection.get(); + try { + if (conn != null) { + conn.close(); + } + } catch (SQLException e) { + log.error("Error occurred while close the connection"); + } + currentConnection.remove(); + } + + public static void rollbackTransaction() throws MobileDeviceManagementDAOException { + 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 MobileDeviceManagementDAOException("Error occurred while rollback the transaction", e); + } + } + +} \ No newline at end of file diff --git a/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android/src/main/java/org/wso2/carbon/device/mgt/mobile/android/impl/dao/AndroidFeatureManagementDAOException.java b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android/src/main/java/org/wso2/carbon/device/mgt/mobile/android/impl/dao/AndroidFeatureManagementDAOException.java new file mode 100644 index 0000000000..db218d6df8 --- /dev/null +++ b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android/src/main/java/org/wso2/carbon/device/mgt/mobile/android/impl/dao/AndroidFeatureManagementDAOException.java @@ -0,0 +1,79 @@ +/* + * 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.wso2.carbon.device.mgt.mobile.android.impl.dao; + +import org.wso2.carbon.device.mgt.mobile.dao.MobileDeviceManagementDAOException; + +public class AndroidFeatureManagementDAOException extends MobileDeviceManagementDAOException { + + private String message; + private static final long serialVersionUID = 2021891706072918865L; + + /** + * Constructs a new MobileDeviceManagementDAOException with the specified detail message and + * nested exception. + * + * @param message error message + * @param nestedException exception + */ + public AndroidFeatureManagementDAOException(String message, Exception nestedException) { + super(message, nestedException); + setErrorMessage(message); + } + + /** + * Constructs a new MobileDeviceManagementDAOException with the specified detail message + * and cause. + * + * @param message the detail message. + * @param cause the cause of this exception. + */ + public AndroidFeatureManagementDAOException(String message, Throwable cause) { + super(message, cause); + setErrorMessage(message); + } + + /** + * Constructs a new MobileDeviceManagementDAOException with the specified detail message. + * + * @param message the detail message. + */ + public AndroidFeatureManagementDAOException(String message) { + super(message); + setErrorMessage(message); + } + + /** + * Constructs a new MobileDeviceManagementDAOException with the specified and cause. + * + * @param cause the cause of this exception. + */ + public AndroidFeatureManagementDAOException(Throwable cause) { + super(cause); + } + + public String getMessage() { + return message; + } + + public void setErrorMessage(String errorMessage) { + this.message = errorMessage; + } + +} diff --git a/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android/src/main/java/org/wso2/carbon/device/mgt/mobile/android/impl/dao/impl/AndroidDeviceDAOImpl.java b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android/src/main/java/org/wso2/carbon/device/mgt/mobile/android/impl/dao/impl/AndroidDeviceDAOImpl.java new file mode 100644 index 0000000000..0d831901dd --- /dev/null +++ b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android/src/main/java/org/wso2/carbon/device/mgt/mobile/android/impl/dao/impl/AndroidDeviceDAOImpl.java @@ -0,0 +1,265 @@ +/* + * 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.wso2.carbon.device.mgt.mobile.android.impl.dao.impl; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.device.mgt.mobile.android.impl.dao.AndroidDAOFactory; +import org.wso2.carbon.device.mgt.mobile.android.impl.util.AndroidPluginConstants; +import org.wso2.carbon.device.mgt.mobile.dao.MobileDeviceDAO; +import org.wso2.carbon.device.mgt.mobile.dao.MobileDeviceManagementDAOException; +import org.wso2.carbon.device.mgt.mobile.dao.util.MobileDeviceManagementDAOUtil; +import org.wso2.carbon.device.mgt.mobile.dto.MobileDevice; + +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 MobileDeviceDAO for Android Devices. + */ +public class AndroidDeviceDAOImpl implements MobileDeviceDAO{ + + private static final Log log = LogFactory.getLog(AndroidDeviceDAOImpl.class); + + @Override + public MobileDevice getMobileDevice(String mblDeviceId) throws MobileDeviceManagementDAOException { + Connection conn; + PreparedStatement stmt = null; + MobileDevice mobileDevice = null; + ResultSet rs = null; + try { + conn = AndroidDAOFactory.getConnection(); + String selectDBQuery = + "SELECT DEVICE_ID, GCM_TOKEN, DEVICE_INFO, DEVICE_MODEL, SERIAL, " + + "VENDOR, MAC_ADDRESS, DEVICE_NAME, LATITUDE, LONGITUDE, IMEI, IMSI, OS_VERSION" + + " FROM AD_DEVICE WHERE DEVICE_ID = ?"; + stmt = conn.prepareStatement(selectDBQuery); + stmt.setString(1, mblDeviceId); + rs = stmt.executeQuery(); + + if (rs.next()) { + mobileDevice = new MobileDevice(); + mobileDevice.setMobileDeviceId(rs.getString(AndroidPluginConstants.DEVICE_ID)); + mobileDevice.setModel(rs.getString(AndroidPluginConstants.DEVICE_MODEL)); + mobileDevice.setSerial(rs.getString(AndroidPluginConstants.SERIAL)); + mobileDevice.setVendor(rs.getString(AndroidPluginConstants.VENDOR)); + mobileDevice.setLatitude(rs.getString(AndroidPluginConstants.LATITUDE)); + mobileDevice.setLongitude(rs.getString(AndroidPluginConstants.LONGITUDE)); + mobileDevice.setImei(rs.getString(AndroidPluginConstants.IMEI)); + mobileDevice.setImsi(rs.getString(AndroidPluginConstants.IMSI)); + mobileDevice.setOsVersion(rs.getString(AndroidPluginConstants.OS_VERSION)); + + Map propertyMap = new HashMap(); + propertyMap.put(AndroidPluginConstants.GCM_TOKEN, rs.getString(AndroidPluginConstants.GCM_TOKEN)); + propertyMap.put(AndroidPluginConstants.DEVICE_INFO, rs.getString(AndroidPluginConstants.DEVICE_INFO)); + propertyMap.put(AndroidPluginConstants.DEVICE_NAME, rs.getString(AndroidPluginConstants.DEVICE_NAME)); + mobileDevice.setDeviceProperties(propertyMap); + + if (log.isDebugEnabled()) { + log.debug("Android device " + mblDeviceId + " data has been fetched from " + + "Android database."); + } + } + } catch (SQLException e) { + String msg = "Error occurred while fetching Android device : '" + mblDeviceId + "'"; + log.error(msg, e); + throw new MobileDeviceManagementDAOException(msg, e); + } finally { + MobileDeviceManagementDAOUtil.cleanupResources(stmt, rs); + AndroidDAOFactory.closeConnection(); + } + + return mobileDevice; + } + + @Override + public boolean addMobileDevice(MobileDevice mobileDevice) throws MobileDeviceManagementDAOException { + boolean status = false; + Connection conn; + PreparedStatement stmt = null; + try { + conn = AndroidDAOFactory.getConnection(); + String createDBQuery = + "INSERT INTO AD_DEVICE(DEVICE_ID, GCM_TOKEN, DEVICE_INFO, SERIAL, " + + "VENDOR, MAC_ADDRESS, DEVICE_NAME, LATITUDE, LONGITUDE, IMEI, IMSI, " + + "OS_VERSION, DEVICE_MODEL) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; + + stmt = conn.prepareStatement(createDBQuery); + stmt.setString(1, mobileDevice.getMobileDeviceId()); + + Map properties = mobileDevice.getDeviceProperties(); + stmt.setString(2, properties.get(AndroidPluginConstants.GCM_TOKEN)); + stmt.setString(3, properties.get(AndroidPluginConstants.DEVICE_INFO)); + stmt.setString(4, mobileDevice.getSerial()); + stmt.setString(5, mobileDevice.getVendor()); + stmt.setString(6, mobileDevice.getMobileDeviceId()); + stmt.setString(7, properties.get(AndroidPluginConstants.DEVICE_NAME)); + stmt.setString(8, mobileDevice.getLongitude()); + stmt.setString(9, mobileDevice.getLongitude()); + stmt.setString(10, mobileDevice.getImei()); + stmt.setString(11, mobileDevice.getImsi()); + stmt.setString(12, mobileDevice.getOsVersion()); + stmt.setString(13, mobileDevice.getModel()); + int rows = stmt.executeUpdate(); + if (rows > 0) { + status = true; + if (log.isDebugEnabled()) { + log.debug("Android device " + mobileDevice.getMobileDeviceId() + " data has been" + + " added to the Android database."); + } + } + } catch (SQLException e) { + throw new MobileDeviceManagementDAOException("Error occurred while adding the Android device '" + + mobileDevice.getMobileDeviceId() + "' information to the Android plugin data store.", e); + } finally { + MobileDeviceManagementDAOUtil.cleanupResources(stmt, null); + } + return status; + } + + @Override + public boolean updateMobileDevice(MobileDevice mobileDevice) throws MobileDeviceManagementDAOException { + boolean status = false; + Connection conn; + PreparedStatement stmt = null; + try { + conn = AndroidDAOFactory.getConnection(); + String updateDBQuery = + "UPDATE AD_DEVICE SET GCM_TOKEN = ?, DEVICE_INFO = ?, SERIAL = ?, VENDOR = ?, " + + "MAC_ADDRESS = ?, DEVICE_NAME = ?, LATITUDE = ?, LONGITUDE = ?, IMEI = ?, " + + "IMSI = ?, OS_VERSION = ?, DEVICE_MODEL = ? WHERE DEVICE_ID = ?"; + stmt = conn.prepareStatement(updateDBQuery); + + Map properties = mobileDevice.getDeviceProperties(); + stmt.setString(1, properties.get(AndroidPluginConstants.GCM_TOKEN)); + stmt.setString(2, properties.get(AndroidPluginConstants.DEVICE_INFO)); + stmt.setString(3, mobileDevice.getSerial()); + stmt.setString(4, mobileDevice.getVendor()); + stmt.setString(5, mobileDevice.getMobileDeviceId()); + stmt.setString(6, properties.get(AndroidPluginConstants.DEVICE_NAME)); + stmt.setString(7, mobileDevice.getLatitude()); + stmt.setString(8, mobileDevice.getLongitude()); + stmt.setString(9, mobileDevice.getImei()); + stmt.setString(10, mobileDevice.getImsi()); + stmt.setString(11, mobileDevice.getOsVersion()); + stmt.setString(12, mobileDevice.getModel()); + stmt.setString(13, mobileDevice.getMobileDeviceId()); + int rows = stmt.executeUpdate(); + if (rows > 0) { + status = true; + if (log.isDebugEnabled()) { + log.debug("Android device " + mobileDevice.getMobileDeviceId() + " data has been" + + " modified."); + } + } + } catch (SQLException e) { + String msg = "Error occurred while modifying the Android device '" + + mobileDevice.getMobileDeviceId() + "' data."; + log.error(msg, e); + throw new MobileDeviceManagementDAOException(msg, e); + } finally { + MobileDeviceManagementDAOUtil.cleanupResources(stmt, null); + } + return status; + } + + @Override + public boolean deleteMobileDevice(String mblDeviceId) + throws MobileDeviceManagementDAOException { + boolean status = false; + Connection conn; + PreparedStatement stmt = null; + try { + conn = AndroidDAOFactory.getConnection(); + String deleteDBQuery = + "DELETE FROM AD_DEVICE WHERE DEVICE_ID = ?"; + stmt = conn.prepareStatement(deleteDBQuery); + stmt.setString(1, mblDeviceId); + int rows = stmt.executeUpdate(); + if (rows > 0) { + status = true; + if (log.isDebugEnabled()) { + log.debug("Android device " + mblDeviceId + " data has deleted" + + " from the Android database."); + } + } + } catch (SQLException e) { + throw new MobileDeviceManagementDAOException("Error occurred while deleting android device '" + + mblDeviceId + "'", e); + } finally { + MobileDeviceManagementDAOUtil.cleanupResources(stmt, null); + } + return status; + } + + @Override + public List getAllMobileDevices() throws MobileDeviceManagementDAOException { + Connection conn; + PreparedStatement stmt = null; + ResultSet rs = null; + MobileDevice mobileDevice; + List mobileDevices = new ArrayList(); + try { + conn = AndroidDAOFactory.getConnection(); + String selectDBQuery = + "SELECT DEVICE_ID, GCM_TOKEN, DEVICE_INFO, DEVICE_MODEL, SERIAL, " + + "VENDOR, MAC_ADDRESS, DEVICE_NAME, LATITUDE, LONGITUDE, IMEI, IMSI, OS_VERSION " + + "FROM AD_DEVICE"; + stmt = conn.prepareStatement(selectDBQuery); + rs = stmt.executeQuery(); + + while (rs.next()) { + mobileDevice = new MobileDevice(); + mobileDevice.setMobileDeviceId(rs.getString(AndroidPluginConstants.DEVICE_ID)); + mobileDevice.setModel(rs.getString(AndroidPluginConstants.DEVICE_MODEL)); + mobileDevice.setSerial(rs.getString(AndroidPluginConstants.SERIAL)); + mobileDevice.setVendor(rs.getString(AndroidPluginConstants.VENDOR)); + mobileDevice.setLatitude(rs.getString(AndroidPluginConstants.LATITUDE)); + mobileDevice.setLongitude(rs.getString(AndroidPluginConstants.LONGITUDE)); + mobileDevice.setImei(rs.getString(AndroidPluginConstants.IMEI)); + mobileDevice.setImsi(rs.getString(AndroidPluginConstants.IMSI)); + mobileDevice.setOsVersion(rs.getString(AndroidPluginConstants.OS_VERSION)); + + Map propertyMap = new HashMap<>(); + propertyMap.put(AndroidPluginConstants.GCM_TOKEN, rs.getString(AndroidPluginConstants.GCM_TOKEN)); + propertyMap.put(AndroidPluginConstants.DEVICE_INFO, rs.getString(AndroidPluginConstants.DEVICE_INFO)); + propertyMap.put(AndroidPluginConstants.DEVICE_NAME, rs.getString(AndroidPluginConstants.DEVICE_NAME)); + mobileDevice.setDeviceProperties(propertyMap); + + mobileDevices.add(mobileDevice); + } + if (log.isDebugEnabled()) { + log.debug("All Android device details have fetched from Android database."); + } + return mobileDevices; + } catch (SQLException e) { + throw new MobileDeviceManagementDAOException("Error occurred while fetching all Android device data", e); + } finally { + MobileDeviceManagementDAOUtil.cleanupResources(stmt, rs); + AndroidDAOFactory.closeConnection(); + } + } + +} diff --git a/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android/src/main/java/org/wso2/carbon/device/mgt/mobile/android/impl/dao/impl/AndroidFeatureDAOImpl.java b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android/src/main/java/org/wso2/carbon/device/mgt/mobile/android/impl/dao/impl/AndroidFeatureDAOImpl.java new file mode 100644 index 0000000000..072a54c9e4 --- /dev/null +++ b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android/src/main/java/org/wso2/carbon/device/mgt/mobile/android/impl/dao/impl/AndroidFeatureDAOImpl.java @@ -0,0 +1,285 @@ +/* + * 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.wso2.carbon.device.mgt.mobile.android.impl.dao.impl; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.device.mgt.common.DeviceManagementConstants; +import org.wso2.carbon.device.mgt.mobile.android.impl.dao.AndroidDAOFactory; +import org.wso2.carbon.device.mgt.mobile.android.impl.dao.AndroidFeatureManagementDAOException; +import org.wso2.carbon.device.mgt.mobile.android.impl.util.AndroidPluginConstants; +import org.wso2.carbon.device.mgt.mobile.dao.MobileDeviceManagementDAOException; +import org.wso2.carbon.device.mgt.mobile.dao.MobileFeatureDAO; +import org.wso2.carbon.device.mgt.mobile.dao.util.MobileDeviceManagementDAOUtil; +import org.wso2.carbon.device.mgt.mobile.dto.MobileFeature; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; + +public class AndroidFeatureDAOImpl implements MobileFeatureDAO { + + private static final Log log = LogFactory.getLog(AndroidFeatureDAOImpl.class); + + public AndroidFeatureDAOImpl() { + + } + + @Override + public boolean addFeature(MobileFeature mobileFeature) throws MobileDeviceManagementDAOException { + PreparedStatement stmt = null; + boolean status = false; + Connection conn; + try { + conn = AndroidDAOFactory.getConnection(); + String sql = "INSERT INTO AD_FEATURE(CODE, NAME, DESCRIPTION) VALUES (?, ?, ?)"; + stmt = conn.prepareStatement(sql); + stmt.setString(1, mobileFeature.getCode()); + stmt.setString(2, mobileFeature.getName()); + stmt.setString(3, mobileFeature.getDescription()); + stmt.executeUpdate(); + status = true; + } catch (SQLException e) { + throw new AndroidFeatureManagementDAOException( + "Error occurred while adding android feature '" + + mobileFeature.getName() + "' into the metadata repository", e); + } finally { + MobileDeviceManagementDAOUtil.cleanupResources(stmt, null); + } + return status; + } + + @Override + public boolean addFeatures(List mobileFeatures) throws MobileDeviceManagementDAOException { + PreparedStatement stmt = null; + MobileFeature mobileFeature; + boolean status = false; + Connection conn; + try { + conn = AndroidDAOFactory.getConnection(); + stmt = conn.prepareStatement("INSERT INTO AD_FEATURE(CODE, NAME, DESCRIPTION) VALUES (?, ?, ?)"); + for (int i = 0; i < mobileFeatures.size(); i++) { + mobileFeature = mobileFeatures.get(i); + stmt.setString(1, mobileFeature.getCode()); + stmt.setString(2, mobileFeature.getName()); + stmt.setString(3, mobileFeature.getDescription()); + stmt.addBatch(); + } + stmt.executeBatch(); + status = true; + } catch (SQLException e) { + throw new AndroidFeatureManagementDAOException( + "Error occurred while adding android features into the metadata repository", e); + } finally { + MobileDeviceManagementDAOUtil.cleanupResources(stmt, null); + } + return status; + } + + @Override + public boolean updateFeature(MobileFeature mobileFeature) throws MobileDeviceManagementDAOException { + boolean status = false; + Connection conn; + PreparedStatement stmt = null; + try { + conn = AndroidDAOFactory.getConnection(); + String updateDBQuery = + "UPDATE AD_FEATURE SET NAME = ?, DESCRIPTION = ?" + + "WHERE CODE = ?"; + + stmt = conn.prepareStatement(updateDBQuery); + stmt.setString(1, mobileFeature.getName()); + stmt.setString(2, mobileFeature.getDescription()); + stmt.setString(3, mobileFeature.getCode()); + + int rows = stmt.executeUpdate(); + if (rows > 0) { + status = true; + if (log.isDebugEnabled()) { + log.debug("Android Feature " + mobileFeature.getCode() + " data has been " + + "modified."); + } + } + } catch (SQLException e) { + String msg = "Error occurred while updating the Android Feature '" + + mobileFeature.getCode() + "' to the Android db."; + log.error(msg, e); + throw new AndroidFeatureManagementDAOException(msg, e); + } finally { + MobileDeviceManagementDAOUtil.cleanupResources(stmt, null); + } + return status; + } + + @Override + public boolean deleteFeatureById(int mblFeatureId) throws MobileDeviceManagementDAOException { + + PreparedStatement stmt = null; + boolean status = false; + Connection conn; + try { + conn = AndroidDAOFactory.getConnection(); + String sql = "DELETE FROM AD_FEATURE WHERE ID = ?"; + stmt = conn.prepareStatement(sql); + stmt.setInt(1, mblFeatureId); + stmt.execute(); + status = true; + } catch (SQLException e) { + throw new AndroidFeatureManagementDAOException( + "Error occurred while deleting android feature '" + + mblFeatureId + "' from Android database.", e); + } finally { + MobileDeviceManagementDAOUtil.cleanupResources(stmt, null); + } + return status; + } + + @Override + public boolean deleteFeatureByCode(String mblFeatureCode) throws MobileDeviceManagementDAOException { + PreparedStatement stmt = null; + boolean status = false; + Connection conn; + try { + conn = AndroidDAOFactory.getConnection(); + String sql = "DELETE FROM AD_FEATURE WHERE CODE = ?"; + stmt = conn.prepareStatement(sql); + stmt.setString(1, mblFeatureCode); + stmt.execute(); + status = true; + } catch (SQLException e) { + throw new AndroidFeatureManagementDAOException( + "Error occurred while deleting android feature '" + + mblFeatureCode + "' from Android database.", e); + } finally { + MobileDeviceManagementDAOUtil.cleanupResources(stmt, null); + } + return status; + } + + @Override + public MobileFeature getFeatureById(int mblFeatureId) throws MobileDeviceManagementDAOException { + PreparedStatement stmt = null; + ResultSet rs = null; + Connection conn; + try { + conn = AndroidDAOFactory.getConnection(); + String sql = "SELECT ID, CODE, NAME, DESCRIPTION FROM AD_FEATURE WHERE ID = ?"; + stmt = conn.prepareStatement(sql); + stmt.setInt(1, mblFeatureId); + rs = stmt.executeQuery(); + + MobileFeature mobileFeature = null; + if (rs.next()) { + mobileFeature = new MobileFeature(); + mobileFeature.setId(rs.getInt(AndroidPluginConstants.ANDROID_FEATURE_ID)); + mobileFeature.setCode(rs.getString(AndroidPluginConstants.ANDROID_FEATURE_CODE)); + mobileFeature.setName(rs.getString(AndroidPluginConstants.ANDROID_FEATURE_NAME)); + mobileFeature.setDescription(rs.getString(AndroidPluginConstants. + ANDROID_FEATURE_DESCRIPTION)); + mobileFeature.setDeviceType( + DeviceManagementConstants.MobileDeviceTypes.MOBILE_DEVICE_TYPE_ANDROID); + } + return mobileFeature; + } catch (SQLException e) { + throw new AndroidFeatureManagementDAOException( + "Error occurred while retrieving android feature '" + + mblFeatureId + "' from the Android database.", e); + } finally { + MobileDeviceManagementDAOUtil.cleanupResources(stmt, rs); + AndroidDAOFactory.closeConnection(); + } + } + + @Override + public MobileFeature getFeatureByCode(String mblFeatureCode) throws MobileDeviceManagementDAOException { + PreparedStatement stmt = null; + ResultSet rs = null; + Connection conn; + + try { + conn = AndroidDAOFactory.getConnection(); + String sql = "SELECT ID, CODE, NAME, DESCRIPTION FROM AD_FEATURE WHERE CODE = ?"; + stmt = conn.prepareStatement(sql); + stmt.setString(1, mblFeatureCode); + rs = stmt.executeQuery(); + + MobileFeature mobileFeature = null; + if (rs.next()) { + mobileFeature = new MobileFeature(); + mobileFeature.setId(rs.getInt(AndroidPluginConstants.ANDROID_FEATURE_ID)); + mobileFeature.setCode(rs.getString(AndroidPluginConstants.ANDROID_FEATURE_CODE)); + mobileFeature.setName(rs.getString(AndroidPluginConstants.ANDROID_FEATURE_NAME)); + mobileFeature.setDescription(rs.getString(AndroidPluginConstants. + ANDROID_FEATURE_DESCRIPTION)); + mobileFeature.setDeviceType( + DeviceManagementConstants.MobileDeviceTypes.MOBILE_DEVICE_TYPE_ANDROID); + } + return mobileFeature; + } catch (SQLException e) { + throw new AndroidFeatureManagementDAOException( + "Error occurred while retrieving android feature '" + + mblFeatureCode + "' from the Android database.", e); + } finally { + MobileDeviceManagementDAOUtil.cleanupResources(stmt, rs); + AndroidDAOFactory.closeConnection(); + } + } + + @Override + public List getFeatureByDeviceType(String deviceType) + throws MobileDeviceManagementDAOException { + return this.getAllFeatures(); + } + + @Override + public List getAllFeatures() throws MobileDeviceManagementDAOException { + PreparedStatement stmt = null; + ResultSet rs = null; + Connection conn = null; + List features = new ArrayList<>(); + try { + conn = AndroidDAOFactory.getConnection(); + String sql = "SELECT ID, CODE, NAME, DESCRIPTION FROM AD_FEATURE"; + stmt = conn.prepareStatement(sql); + rs = stmt.executeQuery(); + MobileFeature mobileFeature = null; + + while (rs.next()) { + mobileFeature = new MobileFeature(); + mobileFeature.setId(rs.getInt(AndroidPluginConstants.ANDROID_FEATURE_ID)); + mobileFeature.setCode(rs.getString(AndroidPluginConstants.ANDROID_FEATURE_CODE)); + mobileFeature.setName(rs.getString(AndroidPluginConstants.ANDROID_FEATURE_NAME)); + mobileFeature.setDescription(rs.getString(AndroidPluginConstants.ANDROID_FEATURE_DESCRIPTION)); + mobileFeature.setDeviceType( + DeviceManagementConstants.MobileDeviceTypes.MOBILE_DEVICE_TYPE_ANDROID); + features.add(mobileFeature); + } + return features; + } catch (SQLException e) { + throw new AndroidFeatureManagementDAOException("Error occurred while retrieving all " + + "android features from the android database.", e); + } finally { + MobileDeviceManagementDAOUtil.cleanupResources(stmt, rs); + AndroidDAOFactory.closeConnection(); + } + } +} \ No newline at end of file diff --git a/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android/src/main/java/org/wso2/carbon/device/mgt/mobile/android/impl/gcm/GCMResult.java b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android/src/main/java/org/wso2/carbon/device/mgt/mobile/android/impl/gcm/GCMResult.java new file mode 100644 index 0000000000..f9c7703587 --- /dev/null +++ b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android/src/main/java/org/wso2/carbon/device/mgt/mobile/android/impl/gcm/GCMResult.java @@ -0,0 +1,53 @@ +/* + * 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.wso2.carbon.device.mgt.mobile.android.impl.gcm; + +/** + * Represents model object for holding GCM response data. + */ +public class GCMResult { + + private String errorMsg; + private String msg; + private int statusCode; + + public String getErrorMsg() { + return errorMsg; + } + + public void setErrorMsg(String errorMsg) { + this.errorMsg = errorMsg; + } + + public String getMsg() { + return msg; + } + + public void setMsg(String msg) { + this.msg = msg; + } + + public int getStatusCode() { + return statusCode; + } + + public void setStatusCode(int statusCode) { + this.statusCode = statusCode; + } +} diff --git a/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android/src/main/java/org/wso2/carbon/device/mgt/mobile/android/impl/gcm/GCMService.java b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android/src/main/java/org/wso2/carbon/device/mgt/mobile/android/impl/gcm/GCMService.java new file mode 100644 index 0000000000..bfa1685faa --- /dev/null +++ b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android/src/main/java/org/wso2/carbon/device/mgt/mobile/android/impl/gcm/GCMService.java @@ -0,0 +1,64 @@ +/* + * 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.wso2.carbon.device.mgt.mobile.android.impl.gcm; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.device.mgt.common.Device; + +import java.util.ArrayList; +import java.util.List; + +/** + * GCM notification service implementation for Android platform. + */ +public class GCMService { + + private static final Log log = LogFactory.getLog(GCMService.class); + private static final String NOTIFIER_TYPE = "notifierType"; + private static final String GCM_NOTIFIER_CODE = "2"; + + public boolean isGCMEnabled() { + String notifierType = GCMUtil.getConfigurationProperty(NOTIFIER_TYPE); + if (GCM_NOTIFIER_CODE.equals(notifierType)) { + return true; + } + return false; + } + + public void sendNotification(String messageData, Device device) { + List devices = new ArrayList<>(1); + devices.add(device); + GCMResult result = GCMUtil.sendWakeUpCall(messageData, devices); + if (result.getStatusCode() != 200) { + log.error("Exception occurred while sending the GCM notification : " + result.getErrorMsg()); + } + } + + public void sendNotification(String messageData, List devices) { + GCMResult result = GCMUtil.sendWakeUpCall(messageData, devices); + if (result.getStatusCode() != 200) { + log.error("Exception occurred while sending the GCM notification : " + result.getErrorMsg()); + } + } + + public void resetTenantConfigCache() { + GCMUtil.resetTenantConfigCache(); + } +} \ No newline at end of file diff --git a/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android/src/main/java/org/wso2/carbon/device/mgt/mobile/android/impl/gcm/GCMUtil.java b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android/src/main/java/org/wso2/carbon/device/mgt/mobile/android/impl/gcm/GCMUtil.java new file mode 100644 index 0000000000..ba41698e8a --- /dev/null +++ b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android/src/main/java/org/wso2/carbon/device/mgt/mobile/android/impl/gcm/GCMUtil.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.wso2.carbon.device.mgt.mobile.android.impl.gcm; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.context.CarbonContext; +import org.wso2.carbon.device.mgt.common.Device; +import org.wso2.carbon.device.mgt.common.DeviceManagementException; +import org.wso2.carbon.device.mgt.common.configuration.mgt.ConfigurationEntry; +import org.wso2.carbon.device.mgt.common.configuration.mgt.TenantConfiguration; +import org.wso2.carbon.device.mgt.common.spi.DeviceManagementService; +import org.wso2.carbon.device.mgt.mobile.android.impl.util.AndroidPluginConstants; +import org.wso2.carbon.device.mgt.mobile.android.internal.AndroidDeviceManagementDataHolder; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.ProtocolException; +import java.net.URL; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +/** + * Implements utility methods used by GCMService. + */ +public class GCMUtil { + + private static final Log log = LogFactory.getLog(GCMService.class); + + private final static String GCM_ENDPOINT = "https://gcm-http.googleapis.com/gcm/send"; + private static final String GCM_API_KEY = "gcmAPIKey"; + private static final int TIME_TO_LIVE = 60; + private static final int HTTP_STATUS_CODE_OK = 200; + + private static HashMap tenantConfigurationCache = new HashMap<>(); + + public static GCMResult sendWakeUpCall(String message, List devices) { + GCMResult result = new GCMResult(); + + byte[] bytes = getGCMRequest(message, getGCMTokens(devices)).getBytes(); + HttpURLConnection conn; + try { + conn = (HttpURLConnection) (new URL(GCM_ENDPOINT)).openConnection(); + conn.setDoOutput(true); + conn.setUseCaches(false); + conn.setFixedLengthStreamingMode(bytes.length); + conn.setRequestMethod("POST"); + conn.setRequestProperty("Content-Type", "application/json"); + conn.setRequestProperty("Authorization", "key=" + getConfigurationProperty(GCM_API_KEY)); + + OutputStream out = conn.getOutputStream(); + out.write(bytes); + out.close(); + + int status = conn.getResponseCode(); + result.setStatusCode(status); + if (status != HTTP_STATUS_CODE_OK) { + result.setErrorMsg(getString(conn.getErrorStream())); + } else { + result.setMsg(getString(conn.getInputStream())); + } + } catch (ProtocolException e) { + log.error("Exception occurred while setting the HTTP protocol.", e); + } catch (IOException ex) { + log.error("Exception occurred while sending the GCM request.", ex); + } + + return result; + } + + private static String getString(InputStream stream) throws IOException { + if (stream != null) { + BufferedReader reader = new BufferedReader(new InputStreamReader(stream)); + StringBuilder content = new StringBuilder(); + + String newLine; + do { + newLine = reader.readLine(); + if (newLine != null) { + content.append(newLine).append('\n'); + } + } while (newLine != null); + + if (content.length() > 0) { + content.setLength(content.length() - 1); + } + + return content.toString(); + } + return null; + } + + private static String getGCMRequest(String message, List registrationIds) { + JsonObject gcmRequest = new JsonObject(); + gcmRequest.addProperty("delay_while_idle", false); + gcmRequest.addProperty("time_to_live", TIME_TO_LIVE); + + //Add message to GCM request + JsonObject data = new JsonObject(); + if (message != null && !message.isEmpty()) { + data.addProperty("data", message); + gcmRequest.add("data", data); + } + + //Set device reg-ids + JsonArray regIds = new JsonArray(); + for (String regId : registrationIds) { + if (regId == null || regId.isEmpty()) { + continue; + } + regIds.add(new JsonPrimitive(regId)); + } + + gcmRequest.add("registration_ids", regIds); + return gcmRequest.toString(); + } + + private static List getGCMTokens(List devices) { + List tokens = new ArrayList<>(devices.size()); + for (Device device : devices) { + tokens.add(getGCMToken(device.getProperties())); + } + return tokens; + } + + private static String getGCMToken(List properties) { + String gcmToken = null; + for (Device.Property property : properties) { + if (AndroidPluginConstants.GCM_TOKEN.equals(property.getName())) { + gcmToken = property.getValue(); + break; + } + } + return gcmToken; + } + + public static String getConfigurationProperty(String property) { + DeviceManagementService androidDMService = AndroidDeviceManagementDataHolder.getInstance(). + getAndroidDeviceManagementService(); + try { + //Get the TenantConfiguration from cache if not we'll get it from DM service + TenantConfiguration tenantConfiguration = getTenantConfigurationFromCache(); + if (tenantConfiguration == null) { + tenantConfiguration = androidDMService.getDeviceManager().getConfiguration(); + if (tenantConfiguration != null) { + addTenantConfigurationToCache(tenantConfiguration); + } + } + + if (tenantConfiguration != null) { + List configs = tenantConfiguration.getConfiguration(); + for (ConfigurationEntry entry : configs) { + if (property.equals(entry.getName())) { + return (String) entry.getValue(); + } + } + } + return ""; + } catch (DeviceManagementException e) { + log.error("Exception occurred while fetching the tenant-config.",e); + } + return null; + } + + public static void resetTenantConfigCache() { + tenantConfigurationCache.remove(getTenantId()); + } + + private static void addTenantConfigurationToCache(TenantConfiguration tenantConfiguration) { + tenantConfigurationCache.put(getTenantId(), tenantConfiguration); + } + + private static TenantConfiguration getTenantConfigurationFromCache() { + return tenantConfigurationCache.get(getTenantId()); + } + + private static int getTenantId() { + return CarbonContext.getThreadLocalCarbonContext().getTenantId(); + } +} \ No newline at end of file diff --git a/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android/src/main/java/org/wso2/carbon/device/mgt/mobile/android/impl/util/AndroidPluginConstants.java b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android/src/main/java/org/wso2/carbon/device/mgt/mobile/android/impl/util/AndroidPluginConstants.java new file mode 100644 index 0000000000..7f6386210b --- /dev/null +++ b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android/src/main/java/org/wso2/carbon/device/mgt/mobile/android/impl/util/AndroidPluginConstants.java @@ -0,0 +1,47 @@ +/* + * 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.wso2.carbon.device.mgt.mobile.android.impl.util; + +/** + * Defines constants used by android plugin. + */ +public class AndroidPluginConstants { + + //Properties related to AD_DEVICE table + public static final String DEVICE_ID = "DEVICE_ID"; + public static final String GCM_TOKEN = "GCM_TOKEN"; + public static final String DEVICE_INFO = "DEVICE_INFO"; + public static final String SERIAL = "SERIAL"; + public static final String DEVICE_MODEL = "DEVICE_MODEL"; + public static final String DEVICE_NAME = "DEVICE_NAME"; + public static final String LATITUDE = "LATITUDE"; + public static final String LONGITUDE = "LONGITUDE"; + public static final String IMEI = "IMEI"; + public static final String IMSI = "IMSI"; + public static final String VENDOR = "VENDOR"; + public static final String OS_VERSION = "OS_VERSION"; + public static final String MAC_ADDRESS = "MAC_ADDRESS"; + + //Properties related to AD_FEATURE table + public static final String ANDROID_FEATURE_ID = "ID"; + public static final String ANDROID_FEATURE_CODE = "CODE"; + public static final String ANDROID_FEATURE_NAME = "NAME"; + public static final String ANDROID_FEATURE_DESCRIPTION = "DESCRIPTION"; + +} diff --git a/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android/src/main/java/org/wso2/carbon/device/mgt/mobile/android/impl/util/AndroidPluginUtils.java b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android/src/main/java/org/wso2/carbon/device/mgt/mobile/android/impl/util/AndroidPluginUtils.java new file mode 100644 index 0000000000..40ea3c18bf --- /dev/null +++ b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android/src/main/java/org/wso2/carbon/device/mgt/mobile/android/impl/util/AndroidPluginUtils.java @@ -0,0 +1,58 @@ +/* + * 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.wso2.carbon.device.mgt.mobile.android.impl.util; + +import org.wso2.carbon.device.mgt.common.license.mgt.License; +import org.wso2.carbon.device.mgt.mobile.android.impl.AndroidDeviceManagementService; + +/** + * Contains utility methods used by Android plugin. + */ +public class AndroidPluginUtils { + + public static License getDefaultLicense() { + License license = new License(); + license.setName(AndroidDeviceManagementService.DEVICE_TYPE_ANDROID); + license.setLanguage("en_US"); + license.setVersion("1.0.0"); + license.setText("This End User License Agreement (\"Agreement\") is a legal agreement between you (\"You\") " + + "and WSO2, Inc., regarding the enrollment of Your personal mobile device (\"Device\") in SoR's " + + "mobile device management program, and the loading to and removal from Your Device and Your use " + + "of certain applications and any associated software and user documentation, whether provided in " + + "\"online\" or electronic format, used in connection with the operation of or provision of services " + + "to WSO2, Inc., BY SELECTING \"I ACCEPT\" DURING INSTALLATION, YOU ARE ENROLLING YOUR DEVICE, AND " + + "THEREBY AUTHORIZING SOR OR ITS AGENTS TO INSTALL, UPDATE AND REMOVE THE APPS FROM YOUR DEVICE AS " + + "DESCRIBED IN THIS AGREEMENT. YOU ARE ALSO EXPLICITLY ACKNOWLEDGING AND AGREEING THAT (1) THIS IS " + + "A BINDING CONTRACT AND (2) YOU HAVE READ AND AGREE TO THE TERMS OF THIS AGREEMENT.\n" + + "\n" + + "IF YOU DO NOT ACCEPT THESE TERMS, DO NOT ENROLL YOUR DEVICE AND DO NOT PROCEED ANY FURTHER.\n" + + "\n" + + "You agree that: (1) You understand and agree to be bound by the terms and conditions contained " + + "in this Agreement, and (2) You are at least 21 years old and have the legal capacity to enter " + + "into this Agreement as defined by the laws of Your jurisdiction. SoR shall have the right, " + + "without prior notice, to terminate or suspend (i) this Agreement, (ii) the enrollment of Your " + + "Device, or (iii) the functioning of the Apps in the event of a violation of this Agreement or " + + "the cessation of Your relationship with SoR (including termination of Your employment if You are " + + "an employee or expiration or termination of Your applicable franchise or supply agreement if You " + + "are a franchisee of or supplier to the WSO2 WSO2, Inc., system). SoR expressly reserves all " + + "rights not expressly granted herein."); + return license; + } + +} diff --git a/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android/src/main/java/org/wso2/carbon/device/mgt/mobile/android/impl/util/AndroidUtils.java b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android/src/main/java/org/wso2/carbon/device/mgt/mobile/android/impl/util/AndroidUtils.java new file mode 100644 index 0000000000..583a6cef0b --- /dev/null +++ b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android/src/main/java/org/wso2/carbon/device/mgt/mobile/android/impl/util/AndroidUtils.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.wso2.carbon.device.mgt.mobile.android.impl.util; + +import java.util.Map; + +/** + * Contains utility methods used by Android plugin. + */ +public class AndroidUtils { + + public static String getDeviceProperty(Map deviceProperties, String property) { + return deviceProperties.get(property); + } +} diff --git a/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android/src/main/java/org/wso2/carbon/device/mgt/mobile/android/internal/AndroidDeviceManagementDataHolder.java b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android/src/main/java/org/wso2/carbon/device/mgt/mobile/android/internal/AndroidDeviceManagementDataHolder.java new file mode 100644 index 0000000000..ee4a7c0217 --- /dev/null +++ b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android/src/main/java/org/wso2/carbon/device/mgt/mobile/android/internal/AndroidDeviceManagementDataHolder.java @@ -0,0 +1,67 @@ +/* + * 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.wso2.carbon.device.mgt.mobile.android.internal; + +import org.wso2.carbon.device.mgt.common.spi.DeviceManagementService; +import org.wso2.carbon.device.mgt.mobile.android.impl.gcm.GCMService; +import org.wso2.carbon.registry.core.service.RegistryService; + +/** + * DataHolder class of Mobile plugins component. + */ +public class AndroidDeviceManagementDataHolder { + + private RegistryService registryService; + private DeviceManagementService androidDeviceManagementService; + private GCMService gcmService; + + private static AndroidDeviceManagementDataHolder thisInstance = new AndroidDeviceManagementDataHolder(); + + private AndroidDeviceManagementDataHolder() { + } + + public static AndroidDeviceManagementDataHolder getInstance() { + return thisInstance; + } + + public RegistryService getRegistryService() { + return registryService; + } + + public void setRegistryService(RegistryService registryService) { + this.registryService = registryService; + } + + public DeviceManagementService getAndroidDeviceManagementService() { + return androidDeviceManagementService; + } + + public void setAndroidDeviceManagementService( + DeviceManagementService androidDeviceManagementService) { + this.androidDeviceManagementService = androidDeviceManagementService; + } + + public GCMService getGCMService() { + return gcmService; + } + + public void setGCMService(GCMService gcmService) { + this.gcmService = gcmService; + } +} diff --git a/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android/src/main/java/org/wso2/carbon/device/mgt/mobile/android/internal/AndroidDeviceManagementServiceComponent.java b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android/src/main/java/org/wso2/carbon/device/mgt/mobile/android/internal/AndroidDeviceManagementServiceComponent.java new file mode 100644 index 0000000000..7eea7f814f --- /dev/null +++ b/components/mobile-plugins/android-plugin/org.wso2.carbon.device.mgt.mobile.android/src/main/java/org/wso2/carbon/device/mgt/mobile/android/internal/AndroidDeviceManagementServiceComponent.java @@ -0,0 +1,137 @@ +/* + * 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.wso2.carbon.device.mgt.mobile.android.internal; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.osgi.framework.BundleContext; +import org.osgi.framework.ServiceRegistration; +import org.osgi.service.component.ComponentContext; +import org.wso2.carbon.device.mgt.common.spi.DeviceManagementService; +import org.wso2.carbon.device.mgt.mobile.android.impl.AndroidDeviceManagementService; +import org.wso2.carbon.device.mgt.mobile.android.impl.AndroidPolicyMonitoringService; +import org.wso2.carbon.device.mgt.mobile.android.impl.gcm.GCMService; +import org.wso2.carbon.device.mgt.mobile.internal.MobileDeviceManagementDataHolder; +import org.wso2.carbon.ndatasource.core.DataSourceService; +import org.wso2.carbon.policy.mgt.common.spi.PolicyMonitoringService; +import org.wso2.carbon.registry.core.service.RegistryService; + +/** + * @scr.component name="org.wso2.carbon.device.mgt.mobile.android.impl.internal.AndroidDeviceManagementServiceComponent" + * immediate="true" + * @scr.reference name="org.wso2.carbon.ndatasource" + * interface="org.wso2.carbon.ndatasource.core.DataSourceService" + * cardinality="1..1" + * policy="dynamic" + * bind="setDataSourceService" + * unbind="unsetDataSourceService" + * @scr.reference name="registry.service" + * interface="org.wso2.carbon.registry.core.service.RegistryService" cardinality="0..1" + * policy="dynamic" bind="setRegistryService" unbind="unsetRegistryService" + *

      + * Adding reference to API Manager Configuration service is an unavoidable hack to get rid of NPEs thrown while + * initializing APIMgtDAOs attempting to register APIs programmatically. APIMgtDAO needs to be proper cleaned up + * to avoid as an ideal fix + */ +public class AndroidDeviceManagementServiceComponent { + + private static final Log log = LogFactory.getLog(AndroidDeviceManagementServiceComponent.class); + private ServiceRegistration androidServiceRegRef; + private ServiceRegistration gcmServiceRegRef; + + protected void activate(ComponentContext ctx) { + + if (log.isDebugEnabled()) { + log.debug("Activating Android Mobile Device Management Service Component"); + } + try { + BundleContext bundleContext = ctx.getBundleContext(); + + DeviceManagementService androidDeviceManagementService = new AndroidDeviceManagementService(); + GCMService gcmService = new GCMService(); + + androidServiceRegRef = + bundleContext.registerService(DeviceManagementService.class.getName(), + androidDeviceManagementService, null); + + gcmServiceRegRef = + bundleContext.registerService(GCMService.class.getName(), gcmService, null); + + + // Policy management service + + bundleContext.registerService(PolicyMonitoringService.class, + new AndroidPolicyMonitoringService(), null); + + AndroidDeviceManagementDataHolder.getInstance().setAndroidDeviceManagementService( + androidDeviceManagementService); + AndroidDeviceManagementDataHolder.getInstance().setGCMService(gcmService); + + if (log.isDebugEnabled()) { + log.debug("Android Mobile Device Management Service Component has been successfully activated"); + } + } catch (Throwable e) { + log.error("Error occurred while activating Android Mobile Device Management Service Component", e); + } + } + + protected void deactivate(ComponentContext ctx) { + if (log.isDebugEnabled()) { + log.debug("De-activating Android Mobile Device Management Service Component"); + } + try { + if (androidServiceRegRef != null) { + androidServiceRegRef.unregister(); + } + if (gcmServiceRegRef != null) { + gcmServiceRegRef.unregister(); + } + if (log.isDebugEnabled()) { + log.debug( + "Android Mobile Device Management Service Component has been successfully de-activated"); + } + } catch (Throwable e) { + log.error("Error occurred while de-activating Android Mobile Device Management bundle", e); + } + } + + protected void setDataSourceService(DataSourceService dataSourceService) { + /* This is to avoid mobile device management component getting initialized before the underlying datasources + are registered */ + if (log.isDebugEnabled()) { + log.debug("Data source service set to android mobile service component"); + } + } + + protected void unsetDataSourceService(DataSourceService dataSourceService) { + //do nothing + } + + protected void setRegistryService(RegistryService registryService) { + if (log.isDebugEnabled()) { + log.debug("RegistryService acquired"); + } + AndroidDeviceManagementDataHolder.getInstance().setRegistryService(registryService); + } + + protected void unsetRegistryService(RegistryService registryService) { + AndroidDeviceManagementDataHolder.getInstance().setRegistryService(null); + } + +} diff --git a/components/mobile-plugins/android-plugin/pom.xml b/components/mobile-plugins/android-plugin/pom.xml new file mode 100644 index 0000000000..0dbcdff771 --- /dev/null +++ b/components/mobile-plugins/android-plugin/pom.xml @@ -0,0 +1,59 @@ + + + + + + + org.wso2.carbon.devicemgt-plugins + mobile-plugins + 2.1.0-SNAPSHOT + ../pom.xml + + + 4.0.0 + android-plugin + pom + WSO2 Carbon - Mobile Plugins + http://wso2.org + + + org.wso2.carbon.device.mgt.mobile.android + org.wso2.carbon.device.mgt.mobile.android.agent + + + + + + + org.apache.felix + maven-scr-plugin + 1.7.2 + + + generate-scr-scrdescriptor + + scr + + + + + + + + diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/pom.xml b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/pom.xml new file mode 100644 index 0000000000..bd997683e3 --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/pom.xml @@ -0,0 +1,205 @@ + + + + + + + mobile-base-plugin + org.wso2.carbon.devicemgt-plugins + 2.1.0-SNAPSHOT + ../pom.xml + + + 4.0.0 + org.wso2.carbon.device.mgt.mobile.api + war + WSO2 Carbon - Mobile Device Management API + WSO2 Carbon - Mobile Device Management API + http://wso2.org + + + + + maven-compiler-plugin + + 1.7 + 1.7 + + 2.3.2 + + + maven-war-plugin + 2.2 + + WEB-INF/lib/*cxf*.jar + ${project.artifactId} + + + + + + + + deploy + + compile + + + org.apache.maven.plugins + maven-antrun-plugin + 1.7 + + + compile + + run + + + + + + + + + + + + + + + + + + + client + + test + + + org.codehaus.mojo + exec-maven-plugin + 1.2.1 + + + test + + java + + + + + + + + + + + + org.apache.cxf + cxf-rt-frontend-jaxws + + + org.apache.cxf + cxf-rt-frontend-jaxrs + + + org.apache.cxf + cxf-rt-transports-http + + + junit + junit + test + + + commons-httpclient.wso2 + commons-httpclient + provided + + + javax.ws.rs + jsr311-api + provided + + + org.wso2.carbon + org.wso2.carbon.utils + provided + + + org.wso2.carbon.commons + org.wso2.carbon.user.mgt + provided + + + org.wso2.carbon + org.wso2.carbon.logging + provided + + + org.wso2.carbon.devicemgt + org.wso2.carbon.device.mgt.common + provided + + + org.wso2.carbon.devicemgt + org.wso2.carbon.device.mgt.core + provided + + + org.apache.axis2.wso2 + axis2-client + + + + + org.wso2.carbon.devicemgt + org.wso2.carbon.policy.mgt.core + provided + + + org.apache.axis2.wso2 + axis2-client + + + + + org.wso2.carbon.identity + org.wso2.carbon.identity.oauth.stub + provided + + + org.apache.axis2.wso2 + axis2-client + + + + + com.google.code.gson + gson + + + commons-codec.wso2 + commons-codec + provided + + + + diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/api/Authentication.java b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/api/Authentication.java new file mode 100644 index 0000000000..42257b7944 --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/api/Authentication.java @@ -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. + */ + +package org.wso2.carbon.mdm.api; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import javax.ws.rs.Consumes; +import javax.ws.rs.Produces; + +/** + * Authentication related REST-API implementation. + */ +@Produces({ "application/json", "application/xml" }) +@Consumes({ "application/json", "application/xml" }) +public class Authentication { + + private static Log log = LogFactory.getLog(Authentication.class); +} + diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/api/Configuration.java b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/api/Configuration.java new file mode 100644 index 0000000000..8acf4efe9b --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/api/Configuration.java @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2016, 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.wso2.carbon.mdm.api; + +import org.apache.commons.httpclient.HttpStatus; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.device.mgt.common.configuration.mgt.ConfigurationManagementException; +import org.wso2.carbon.device.mgt.common.configuration.mgt.TenantConfiguration; +import org.wso2.carbon.mdm.api.common.MDMAPIException; +import org.wso2.carbon.mdm.api.util.MDMAPIUtils; +import org.wso2.carbon.mdm.api.util.MDMAppConstants; +import org.wso2.carbon.mdm.api.util.ResponsePayload; + +import javax.jws.WebService; +import javax.ws.rs.*; +import javax.ws.rs.core.Response; + +/** + * General Tenant Configuration REST-API implementation. + * All end points support JSON, XMl with content negotiation. + */ +@WebService +@Produces({ "application/json", "application/xml" }) +@Consumes({ "application/json", "application/xml" }) +public class Configuration { + + private static Log log = LogFactory.getLog(Configuration.class); + + @POST + public ResponsePayload saveTenantConfiguration(TenantConfiguration configuration) + throws MDMAPIException { + ResponsePayload responseMsg = new ResponsePayload(); + try { + MDMAPIUtils.getTenantConfigurationManagementService().saveConfiguration(configuration, + MDMAppConstants.RegistryConstants.GENERAL_CONFIG_RESOURCE_PATH); + //Schedule the task service + MDMAPIUtils.scheduleTaskService(MDMAPIUtils.getNotifierFrequency(configuration)); + Response.status(HttpStatus.SC_CREATED); + responseMsg.setMessageFromServer("Tenant configuration saved successfully."); + responseMsg.setStatusCode(HttpStatus.SC_CREATED); + return responseMsg; + } catch (ConfigurationManagementException e) { + String msg = "Error occurred while saving the tenant configuration."; + log.error(msg, e); + throw new MDMAPIException(msg, e); + } + } + + @GET + public TenantConfiguration getConfiguration() throws MDMAPIException { + String msg; + try { + return MDMAPIUtils.getTenantConfigurationManagementService().getConfiguration(MDMAppConstants. + RegistryConstants.GENERAL_CONFIG_RESOURCE_PATH); + } catch (ConfigurationManagementException e) { + msg = "Error occurred while retrieving the tenant configuration."; + log.error(msg, e); + throw new MDMAPIException(msg, e); + } + } + + @PUT + public ResponsePayload updateConfiguration(TenantConfiguration configuration) throws MDMAPIException { + ResponsePayload responseMsg = new ResponsePayload(); + try { + MDMAPIUtils.getTenantConfigurationManagementService().saveConfiguration(configuration, + MDMAppConstants.RegistryConstants.GENERAL_CONFIG_RESOURCE_PATH); + //Schedule the task service + MDMAPIUtils.scheduleTaskService(MDMAPIUtils.getNotifierFrequency(configuration)); + Response.status(HttpStatus.SC_CREATED); + responseMsg.setMessageFromServer("Tenant configuration updated successfully."); + responseMsg.setStatusCode(HttpStatus.SC_CREATED); + return responseMsg; + } catch (ConfigurationManagementException e) { + String msg = "Error occurred while updating the tenant configuration."; + log.error(msg, e); + throw new MDMAPIException(msg, e); + } + } + +} diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/api/DeviceNotification.java b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/api/DeviceNotification.java new file mode 100644 index 0000000000..9a8567572d --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/api/DeviceNotification.java @@ -0,0 +1,108 @@ +/* + * 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.wso2.carbon.mdm.api; + +import org.apache.commons.httpclient.HttpStatus; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.device.mgt.common.notification.mgt.NotificationManagementException; +import org.wso2.carbon.device.mgt.common.notification.mgt.Notification; +import org.wso2.carbon.mdm.api.common.MDMAPIException; +import org.wso2.carbon.mdm.api.util.MDMAPIUtils; +import org.wso2.carbon.mdm.api.util.ResponsePayload; + +import javax.jws.WebService; +import javax.ws.rs.*; +import javax.ws.rs.core.Response; +import java.util.List; + +/** + * DeviceNotification management REST-API implementation. + * All end points support JSON, XMl with content negotiation. + */ +@WebService +@Produces({ "application/json", "application/xml" }) +@Consumes({ "application/json", "application/xml" }) +public class DeviceNotification { + + private static Log log = LogFactory.getLog(Configuration.class); + + @GET + public List getNotifications() throws MDMAPIException { + String msg; + try { + return MDMAPIUtils.getNotificationManagementService().getAllNotifications(); + } catch (NotificationManagementException e) { + msg = "Error occurred while retrieving the notification list."; + log.error(msg, e); + throw new MDMAPIException(msg, e); + } + } + + @GET + @Path("{status}") + public List getNotificationsByStatus(@PathParam("status") Notification.Status status) + throws MDMAPIException { + String msg; + try { + return MDMAPIUtils.getNotificationManagementService().getNotificationsByStatus(status); + } catch (NotificationManagementException e) { + msg = "Error occurred while retrieving the notification list."; + log.error(msg, e); + throw new MDMAPIException(msg, e); + } + } + + @PUT + @Path("{id}/{status}") + public ResponsePayload updateNotificationStatus(@PathParam("id") int id, + @PathParam("status") Notification.Status status) + throws MDMAPIException{ + ResponsePayload responseMsg = new ResponsePayload(); + try { + MDMAPIUtils.getNotificationManagementService().updateNotificationStatus(id, status); + Response.status(HttpStatus.SC_ACCEPTED); + responseMsg.setMessageFromServer("Notification status updated successfully."); + responseMsg.setStatusCode(HttpStatus.SC_ACCEPTED); + return responseMsg; + } catch (NotificationManagementException e) { + String msg = "Error occurred while updating notification status."; + log.error(msg, e); + throw new MDMAPIException(msg, e); + } + } + + @POST + public ResponsePayload addNotification(Notification notification) + throws MDMAPIException{ + ResponsePayload responseMsg = new ResponsePayload(); + try { + MDMAPIUtils.getNotificationManagementService().addNotification(notification); + Response.status(HttpStatus.SC_CREATED); + responseMsg.setMessageFromServer("Notification has added successfully."); + responseMsg.setStatusCode(HttpStatus.SC_CREATED); + return responseMsg; + } catch (NotificationManagementException e) { + String msg = "Error occurred while updating notification status."; + log.error(msg, e); + throw new MDMAPIException(msg, e); + } + } + +} diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/api/Feature.java b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/api/Feature.java new file mode 100644 index 0000000000..1cd357b94a --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/api/Feature.java @@ -0,0 +1,62 @@ +/* + * 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.wso2.carbon.mdm.api; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.device.mgt.common.DeviceManagementException; +import org.wso2.carbon.device.mgt.core.service.DeviceManagementProviderService; +import org.wso2.carbon.mdm.api.common.MDMAPIException; +import org.wso2.carbon.mdm.api.util.MDMAPIUtils; + +import javax.ws.rs.*; +import java.util.List; + +/** + * Features + */ +@Produces({"application/json", "application/xml"}) +@Consumes({"application/json", "application/xml"}) +public class Feature { + private static Log log = LogFactory.getLog(Feature.class); + + /** + * Get all features for Mobile Device Type + * + * @return Feature + * @throws org.wso2.carbon.mdm.api.common.MDMAPIException + * + */ + @GET + @Path("/{type}") + public List getFeatures(@PathParam("type") String type) + throws MDMAPIException { + List features; + DeviceManagementProviderService dmService; + try { + dmService = MDMAPIUtils.getDeviceManagementService(); + features = dmService.getFeatureManager(type).getFeatures(); + } catch (DeviceManagementException e) { + String msg = "Error occurred while retrieving the list of features"; + log.error(msg, e); + throw new MDMAPIException(msg, e); + } + return features; + } + +} diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/api/License.java b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/api/License.java new file mode 100644 index 0000000000..1b1e6c3d0f --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/api/License.java @@ -0,0 +1,99 @@ +/* + * 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.wso2.carbon.mdm.api; + +import org.apache.commons.httpclient.HttpStatus; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.device.mgt.common.DeviceManagementException; +import org.wso2.carbon.mdm.api.common.MDMAPIException; +import org.wso2.carbon.mdm.api.util.MDMAPIUtils; +import org.wso2.carbon.mdm.api.util.ResponsePayload; + +import javax.ws.rs.*; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +/** + * This class represents license related operations. + */ +public class License { + + private static Log log = LogFactory.getLog(License.class); + + /** + * This method returns the license text related to a given device type and language code. + * + * @param deviceType Device type, ex: android, ios + * @param languageCode Language code, ex: en_US + * @return Returns the license text + * @throws MDMAPIException If the device type or language code arguments are not available or invalid. + */ + @GET + @Path ("{deviceType}/{languageCode}") + @Produces ({MediaType.APPLICATION_JSON}) + public Response getLicense(@PathParam ("deviceType") String deviceType, + @PathParam ("languageCode") String languageCode) throws MDMAPIException { + + org.wso2.carbon.device.mgt.common.license.mgt.License license; + ResponsePayload responsePayload = new ResponsePayload(); + try { + license = MDMAPIUtils.getDeviceManagementService().getLicense(deviceType, languageCode); + if (license == null) { + return Response.status(HttpStatus.SC_NOT_FOUND).build(); + } + responsePayload = ResponsePayload.statusCode(HttpStatus.SC_OK). + messageFromServer("License for '" + deviceType + "' was retrieved successfully"). + responseContent(license.getText()). + build(); + } catch (DeviceManagementException e) { + String msg = "Error occurred while retrieving the license configured for '" + deviceType + "' device type"; + log.error(msg, e); + throw new MDMAPIException(msg, e); + } + return Response.status(HttpStatus.SC_OK).entity(responsePayload).build(); + } + + /** + * This method is used to add license to a specific device type. + * + * @param deviceType Device type, ex: android, ios + * @param license License object + * @return Returns the acknowledgement for the action + * @throws MDMAPIException + */ + @POST + @Path ("{deviceType}") + public Response addLicense(@PathParam ("deviceType") String deviceType, + org.wso2.carbon.device.mgt.common.license.mgt.License license) throws MDMAPIException { + + ResponsePayload responsePayload; + try { + MDMAPIUtils.getDeviceManagementService().addLicense(deviceType, license); + responsePayload = ResponsePayload.statusCode(HttpStatus.SC_OK). + messageFromServer("License added successfully for '" + deviceType + "' device type"). + build(); + } catch (DeviceManagementException e) { + String msg = "Error occurred while adding license for '" + deviceType + "' device type"; + log.error(msg, e); + throw new MDMAPIException(msg, e); + } + return Response.status(HttpStatus.SC_OK).entity(responsePayload).build(); + } +} diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/api/MobileDevice.java b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/api/MobileDevice.java new file mode 100644 index 0000000000..be7c9bda30 --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/api/MobileDevice.java @@ -0,0 +1,218 @@ +/* + * Copyright (c) 2016, 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.wso2.carbon.mdm.api; + +import org.apache.commons.httpclient.HttpStatus; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.device.mgt.common.*; +import org.wso2.carbon.device.mgt.core.dto.DeviceType; +import org.wso2.carbon.device.mgt.core.service.DeviceManagementProviderService; +import org.wso2.carbon.mdm.api.common.MDMAPIException; +import org.wso2.carbon.mdm.api.util.MDMAPIUtils; +import org.wso2.carbon.mdm.api.util.ResponsePayload; + +import javax.ws.rs.*; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import java.util.List; + +/** + * Device related operations + */ +public class MobileDevice { + private static Log log = LogFactory.getLog(MobileDevice.class); + + /** + * Get all devices. We have to use accept all the necessary query parameters sent by datatable. + * Hence had to put lot of query params here. + * + * @return Device List + * @throws MDMAPIException + */ + @GET + public Object getAllDevices(@QueryParam("type") String type, @QueryParam("user") String user, + @QueryParam("role") String role, @QueryParam("status") EnrolmentInfo.Status status, + @QueryParam("start") int startIdx, @QueryParam("length") int length, + @QueryParam("device-name") String deviceName, + @QueryParam("ownership") EnrolmentInfo.OwnerShip ownership + ) throws MDMAPIException { + try { + DeviceManagementProviderService service = MDMAPIUtils.getDeviceManagementService(); + //Length > 0 means this is a pagination request. + if (length > 0) { + PaginationRequest paginationRequest = new PaginationRequest(startIdx, length); + paginationRequest.setDeviceName(deviceName); + paginationRequest.setOwner(user); + if (ownership != null) { + paginationRequest.setOwnership(ownership.toString()); + } + if (status != null) { + paginationRequest.setStatus(status.toString()); + } + paginationRequest.setDeviceType(type); + return service.getAllDevices(paginationRequest); + } + + List allDevices = null; + if ((type != null) && !type.isEmpty()) { + allDevices = service.getAllDevices(type); + } else if ((user != null) && !user.isEmpty()) { + allDevices = service.getDevicesOfUser(user); + } else if ((role != null) && !role.isEmpty()) { + allDevices = service.getAllDevicesOfRole(role); + } else if (status != null) { + allDevices = service.getDevicesByStatus(status); + } else if (deviceName != null) { + allDevices = service.getDevicesByName(deviceName); + } else { + allDevices = service.getAllDevices(); + } + return allDevices; + } catch (DeviceManagementException e) { + String msg = "Error occurred while fetching the device list."; + log.error(msg, e); + throw new MDMAPIException(msg, e); + } + } + + /** + * Fetch device details for a given device type and device Id. + * + * @return Device wrapped inside Response + * @throws MDMAPIException + */ + @GET + @Path("view") + @Produces({MediaType.APPLICATION_JSON}) + public Response getDevice(@QueryParam("type") String type, + @QueryParam("id") String id) throws MDMAPIException { + DeviceIdentifier deviceIdentifier = MDMAPIUtils.instantiateDeviceIdentifier(type, id); + DeviceManagementProviderService deviceManagementProviderService = MDMAPIUtils.getDeviceManagementService(); + Device device; + try { + device = deviceManagementProviderService.getDevice(deviceIdentifier); + } catch (DeviceManagementException e) { + String error = "Error occurred while fetching the device information."; + log.error(error, e); + throw new MDMAPIException(error, e); + } + ResponsePayload responsePayload = new ResponsePayload(); + if (device == null) { + responsePayload.setStatusCode(HttpStatus.SC_NOT_FOUND); + responsePayload.setMessageFromServer("Requested device by type: " + + type + " and id: " + id + " does not exist."); + return Response.status(HttpStatus.SC_NOT_FOUND).entity(responsePayload).build(); + } else { + responsePayload.setStatusCode(HttpStatus.SC_OK); + responsePayload.setMessageFromServer("Sending Requested device by type: " + type + " and id: " + id + "."); + responsePayload.setResponseContent(device); + return Response.status(HttpStatus.SC_OK).entity(responsePayload).build(); + } + } + + /** + * Fetch Android device details of a given user. + * + * @param user User Name + * @param tenantDomain tenant domain + * @return Device + * @throws org.wso2.carbon.mdm.api.common.MDMAPIException + */ + @GET + @Path("user/{user}/{tenantDomain}") + public List getDeviceByUser(@PathParam("user") String user, + @PathParam("tenantDomain") String tenantDomain) throws MDMAPIException { + List devices; + try { + devices = MDMAPIUtils.getDeviceManagementService().getDevicesOfUser(user); + if (devices == null) { + Response.status(Response.Status.NOT_FOUND); + } + return devices; + } catch (DeviceManagementException e) { + String msg = "Error occurred while fetching the devices list of given user."; + log.error(msg, e); + throw new MDMAPIException(msg, e); + } + } + + /** + * Get current device count + * + * @return device count + * @throws MDMAPIException + */ + @GET + @Path("count") + public int getDeviceCount() throws MDMAPIException { + try { + return MDMAPIUtils.getDeviceManagementService().getDeviceCount(); + } catch (DeviceManagementException e) { + String msg = "Error occurred while fetching the device count."; + log.error(msg, e); + throw new MDMAPIException(msg, e); + } + } + + /** + * Get the list of devices that matches with the given name. + * + * @param deviceName Device name + * @param tenantDomain Callee tenant domain + * @return list of devices. + * @throws MDMAPIException If some unusual behaviour is observed while fetching the device list + */ + @GET + @Path("name/{name}/{tenantDomain}") + public List getDevicesByName(@PathParam("name") String deviceName, + @PathParam("tenantDomain") String tenantDomain) throws MDMAPIException { + + List devices; + try { + devices = MDMAPIUtils.getDeviceManagementService().getDevicesByName(deviceName); + } catch (DeviceManagementException e) { + String msg = "Error occurred while fetching the devices list of device name."; + log.error(msg, e); + throw new MDMAPIException(msg, e); + } + return devices; + } + + /** + * Get the list of available device types. + * + * @return list of device types. + * @throws MDMAPIException If some unusual behaviour is observed while fetching the device list + */ + @GET + @Path("types") + public List getDeviceTypes() throws MDMAPIException { + + List deviceTypes; + try { + deviceTypes = MDMAPIUtils.getDeviceManagementService().getAvailableDeviceTypes(); + } catch (DeviceManagementException e) { + String msg = "Error occurred while fetching the list of device types."; + log.error(msg, e); + throw new MDMAPIException(msg, e); + } + return deviceTypes; + } +} \ No newline at end of file diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/api/Operation.java b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/api/Operation.java new file mode 100644 index 0000000000..a775318dd2 --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/api/Operation.java @@ -0,0 +1,225 @@ +/* + * Copyright (c) 2016, 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.wso2.carbon.mdm.api; + +import org.apache.commons.httpclient.HttpStatus; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.device.mgt.common.DeviceIdentifier; +import org.wso2.carbon.device.mgt.common.PaginationRequest; +import org.wso2.carbon.device.mgt.common.PaginationResult; +import org.wso2.carbon.device.mgt.common.Platform; +import org.wso2.carbon.device.mgt.common.app.mgt.Application; +import org.wso2.carbon.device.mgt.common.app.mgt.ApplicationManagementException; +import org.wso2.carbon.device.mgt.common.app.mgt.ApplicationManager; +import org.wso2.carbon.device.mgt.common.operation.mgt.OperationManagementException; +import org.wso2.carbon.device.mgt.core.app.mgt.ApplicationManagementProviderService; +import org.wso2.carbon.device.mgt.core.service.DeviceManagementProviderService; +import org.wso2.carbon.mdm.api.common.MDMAPIException; +import org.wso2.carbon.mdm.api.context.DeviceOperationContext; +import org.wso2.carbon.mdm.api.util.MDMAPIUtils; +import org.wso2.carbon.mdm.api.util.MDMAndroidOperationUtil; +import org.wso2.carbon.mdm.api.util.MDMIOSOperationUtil; +import org.wso2.carbon.mdm.api.util.ResponsePayload; +import org.wso2.carbon.mdm.beans.ApplicationWrapper; +import org.wso2.carbon.mdm.beans.MobileApp; +import org.wso2.carbon.utils.multitenancy.MultitenantConstants; + +import javax.ws.rs.*; +import javax.ws.rs.core.Response; +import java.util.ArrayList; +import java.util.List; + +/** + * Operation related REST-API implementation. + */ +@Produces({"application/json", "application/xml"}) +@Consumes({"application/json", "application/xml"}) +public class Operation { + + private static Log log = LogFactory.getLog(Operation.class); + + @GET + public List getAllOperations() + throws MDMAPIException { + List operations; + DeviceManagementProviderService dmService; + try { + dmService = MDMAPIUtils.getDeviceManagementService(); + operations = dmService.getOperations(null); + } catch (OperationManagementException e) { + String msg = "Error occurred while fetching the operations for the device."; + log.error(msg, e); + throw new MDMAPIException(msg, e); + } + return operations; + } + + @GET + @Path("paginate/{type}/{id}") + public PaginationResult getDeviceOperations( + @PathParam("type") String type, @PathParam("id") String id, @QueryParam("start") int startIdx, + @QueryParam("length") int length, @QueryParam("search") String search) + throws MDMAPIException { + PaginationResult operations; + DeviceManagementProviderService dmService; + DeviceIdentifier deviceIdentifier = new DeviceIdentifier(); + PaginationRequest paginationRequest = new PaginationRequest(startIdx, length); + try { + deviceIdentifier.setType(type); + deviceIdentifier.setId(id); + dmService = MDMAPIUtils.getDeviceManagementService(); + operations = dmService.getOperations(deviceIdentifier, paginationRequest); + } catch (OperationManagementException e) { + String msg = "Error occurred while fetching the operations for the device."; + log.error(msg, e); + throw new MDMAPIException(msg, e); + } + return operations; + } + + @GET + @Path("{type}/{id}") + public List getDeviceOperations( + @PathParam("type") String type, @PathParam("id") String id) + throws MDMAPIException { + List operations; + DeviceManagementProviderService dmService; + DeviceIdentifier deviceIdentifier = new DeviceIdentifier(); + try { + deviceIdentifier.setType(type); + deviceIdentifier.setId(id); + dmService = MDMAPIUtils.getDeviceManagementService(); + operations = dmService.getOperations(deviceIdentifier); + } catch (OperationManagementException e) { + String msg = "Error occurred while fetching the operations for the device."; + log.error(msg, e); + throw new MDMAPIException(msg, e); + } + return operations; + } + + @POST + public ResponsePayload addOperation(DeviceOperationContext operationContext) throws MDMAPIException { + DeviceManagementProviderService dmService; + ResponsePayload responseMsg = new ResponsePayload(); + try { + dmService = MDMAPIUtils.getDeviceManagementService(); + int operationId = dmService.addOperation(operationContext.getOperation(), + operationContext.getDevices()); + if (operationId>0) { + Response.status(HttpStatus.SC_CREATED); + responseMsg.setMessageFromServer("Operation has added successfully."); + } + return responseMsg; + } catch (OperationManagementException e) { + String msg = "Error occurred while saving the operation"; + log.error(msg, e); + throw new MDMAPIException(msg, e); + } + } + + @GET + @Path("{type}/{id}/apps") + public List getInstalledApps( + @PathParam("type") String type, + @PathParam("id") String id) + throws MDMAPIException { + List applications; + ApplicationManagementProviderService appManagerConnector; + DeviceIdentifier deviceIdentifier = new DeviceIdentifier(); + try { + deviceIdentifier.setType(type); + deviceIdentifier.setId(id); + appManagerConnector = MDMAPIUtils.getAppManagementService(); + applications = appManagerConnector.getApplicationListForDevice(deviceIdentifier); + } catch (ApplicationManagementException e) { + String msg = "Error occurred while fetching the apps of the device."; + log.error(msg, e); + throw new MDMAPIException(msg, e); + } + return applications; + } + + @POST + @Path("installApp/{tenantDomain}") + public ResponsePayload installApplication(ApplicationWrapper applicationWrapper, + @PathParam("tenantDomain") String tenantDomain) throws MDMAPIException { + ResponsePayload responseMsg = new ResponsePayload(); + ApplicationManager appManagerConnector; + org.wso2.carbon.device.mgt.common.operation.mgt.Operation operation = null; + ArrayList deviceIdentifiers; + try { + appManagerConnector = MDMAPIUtils.getAppManagementService(); + MobileApp mobileApp = applicationWrapper.getApplication(); + + if (applicationWrapper.getDeviceIdentifiers() != null) { + for (DeviceIdentifier deviceIdentifier : applicationWrapper.getDeviceIdentifiers()) { + deviceIdentifiers = new ArrayList(); + + if (deviceIdentifier.getType().equals(Platform.android.toString())) { + operation = MDMAndroidOperationUtil.createInstallAppOperation(mobileApp); + } else if (deviceIdentifier.getType().equals(Platform.ios.toString())) { + operation = MDMIOSOperationUtil.createInstallAppOperation(mobileApp); + } + deviceIdentifiers.add(deviceIdentifier); + } + appManagerConnector.installApplicationForDevices(operation, applicationWrapper.getDeviceIdentifiers()); + } + return responseMsg; + } catch (ApplicationManagementException e) { + String msg = "Error occurred while saving the operation"; + log.error(msg, e); + throw new MDMAPIException(msg, e); + } + } + + @POST + @Path("uninstallApp/{tenantDomain}") + public ResponsePayload uninstallApplication(ApplicationWrapper applicationWrapper, + @PathParam("tenantDomain") String tenantDomain) throws MDMAPIException { + ResponsePayload responseMsg = new ResponsePayload(); + ApplicationManager appManagerConnector; + org.wso2.carbon.device.mgt.common.operation.mgt.Operation operation = null; + ArrayList deviceIdentifiers; + try { + appManagerConnector = MDMAPIUtils.getAppManagementService(); + MobileApp mobileApp = applicationWrapper.getApplication(); + + if (applicationWrapper.getDeviceIdentifiers() != null) { + for (DeviceIdentifier deviceIdentifier : applicationWrapper.getDeviceIdentifiers()) { + deviceIdentifiers = new ArrayList(); + + if (deviceIdentifier.getType().equals(Platform.android.toString())) { + operation = MDMAndroidOperationUtil.createAppUninstallOperation(mobileApp); + } else if (deviceIdentifier.getType().equals(Platform.ios.toString())) { + operation = MDMIOSOperationUtil.createAppUninstallOperation(mobileApp); + } + deviceIdentifiers.add(deviceIdentifier); + } + appManagerConnector.installApplicationForDevices(operation, applicationWrapper.getDeviceIdentifiers()); + } + return responseMsg; + } catch (ApplicationManagementException e) { + String msg = "Error occurred while saving the operation"; + log.error(msg, e); + throw new MDMAPIException(msg, e); + } + } +} \ No newline at end of file diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/api/Policy.java b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/api/Policy.java new file mode 100644 index 0000000000..dea04d31bb --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/api/Policy.java @@ -0,0 +1,475 @@ +/* + * Copyright (c) 2016, 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.wso2.carbon.mdm.api; + +import org.apache.commons.httpclient.HttpStatus; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.device.mgt.common.DeviceIdentifier; +import org.wso2.carbon.mdm.api.common.MDMAPIException; +import org.wso2.carbon.mdm.api.util.MDMAPIUtils; +import org.wso2.carbon.mdm.api.util.ResponsePayload; +import org.wso2.carbon.mdm.beans.PolicyWrapper; +import org.wso2.carbon.mdm.beans.PriorityUpdatedPolicyWrapper; +import org.wso2.carbon.mdm.util.MDMUtil; +import org.wso2.carbon.policy.mgt.common.PolicyAdministratorPoint; +import org.wso2.carbon.policy.mgt.common.PolicyManagementException; +import org.wso2.carbon.policy.mgt.common.PolicyMonitoringTaskException; +import org.wso2.carbon.policy.mgt.common.monitor.ComplianceData; +import org.wso2.carbon.policy.mgt.common.monitor.PolicyComplianceException; +import org.wso2.carbon.policy.mgt.core.PolicyManagerService; +import org.wso2.carbon.policy.mgt.core.task.TaskScheduleService; + +import javax.ws.rs.*; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import java.util.ArrayList; +import java.util.List; + +public class Policy { + private static Log log = LogFactory.getLog(Policy.class); + + @POST + @Path("inactive-policy") + public ResponsePayload addPolicy(PolicyWrapper policyWrapper) throws MDMAPIException { + + PolicyManagerService policyManagementService = MDMAPIUtils.getPolicyManagementService(); + ResponsePayload responseMsg = new ResponsePayload(); + org.wso2.carbon.policy.mgt.common.Policy policy = new org.wso2.carbon.policy.mgt.common.Policy(); + policy.setPolicyName(policyWrapper.getPolicyName()); + policy.setProfileId(policyWrapper.getProfileId()); + policy.setDescription(policyWrapper.getDescription()); + policy.setProfile(MDMUtil.convertProfile(policyWrapper.getProfile())); + policy.setOwnershipType(policyWrapper.getOwnershipType()); + policy.setRoles(policyWrapper.getRoles()); + policy.setUsers(policyWrapper.getUsers()); + policy.setTenantId(policyWrapper.getTenantId()); + policy.setCompliance(policyWrapper.getCompliance()); + + try { + PolicyAdministratorPoint pap = policyManagementService.getPAP(); + pap.addPolicy(policy); + Response.status(HttpStatus.SC_CREATED); + responseMsg.setStatusCode(HttpStatus.SC_CREATED); + responseMsg.setMessageFromServer("Policy has been added successfully."); + return responseMsg; + } catch (PolicyManagementException e) { + String error = "Policy Management related exception"; + log.error(error, e); + throw new MDMAPIException(error, e); + } + } + + @POST + @Path("active-policy") + public ResponsePayload addActivePolicy(PolicyWrapper policyWrapper) throws MDMAPIException { + + PolicyManagerService policyManagementService = MDMAPIUtils.getPolicyManagementService(); + ResponsePayload responseMsg = new ResponsePayload(); + org.wso2.carbon.policy.mgt.common.Policy policy = new org.wso2.carbon.policy.mgt.common.Policy(); + policy.setPolicyName(policyWrapper.getPolicyName()); + policy.setProfileId(policyWrapper.getProfileId()); + policy.setDescription(policyWrapper.getDescription()); + policy.setProfile(MDMUtil.convertProfile(policyWrapper.getProfile())); + policy.setOwnershipType(policyWrapper.getOwnershipType()); + policy.setRoles(policyWrapper.getRoles()); + policy.setUsers(policyWrapper.getUsers()); + policy.setTenantId(policyWrapper.getTenantId()); + policy.setCompliance(policyWrapper.getCompliance()); + policy.setActive(true); + + try { + PolicyAdministratorPoint pap = policyManagementService.getPAP(); + pap.addPolicy(policy); + Response.status(HttpStatus.SC_CREATED); + responseMsg.setStatusCode(HttpStatus.SC_CREATED); + responseMsg.setMessageFromServer("Policy has been added successfully."); + return responseMsg; + } catch (PolicyManagementException e) { + String error = "Policy Management related exception"; + log.error(error, e); + throw new MDMAPIException(error, e); + } + } + + @GET + @Produces({MediaType.APPLICATION_JSON}) + public Response getAllPolicies() throws MDMAPIException { + PolicyManagerService policyManagementService = MDMAPIUtils.getPolicyManagementService(); + List policies; + try { + PolicyAdministratorPoint policyAdministratorPoint = policyManagementService.getPAP(); + policies = policyAdministratorPoint.getPolicies(); + } catch (PolicyManagementException e) { + String error = "Policy Management related exception"; + log.error(error, e); + throw new MDMAPIException(error, e); + } + ResponsePayload responsePayload = new ResponsePayload(); + responsePayload.setStatusCode(HttpStatus.SC_OK); + responsePayload.setMessageFromServer("Sending all retrieved device policies."); + responsePayload.setResponseContent(policies); + return Response.status(HttpStatus.SC_OK).entity(responsePayload).build(); + } + + @GET + @Produces({MediaType.APPLICATION_JSON}) + @Path("{id}") + public Response getPolicy(@PathParam("id") int policyId) throws MDMAPIException { + PolicyManagerService policyManagementService = MDMAPIUtils.getPolicyManagementService(); + final org.wso2.carbon.policy.mgt.common.Policy policy; + try { + PolicyAdministratorPoint policyAdministratorPoint = policyManagementService.getPAP(); + policy = policyAdministratorPoint.getPolicy(policyId); + } catch (PolicyManagementException e) { + String error = "Policy Management related exception"; + log.error(error, e); + throw new MDMAPIException(error, e); + } + if (policy == null){ + ResponsePayload responsePayload = new ResponsePayload(); + responsePayload.setStatusCode(HttpStatus.SC_NOT_FOUND); + responsePayload.setMessageFromServer("Policy for ID " + policyId + " not found."); + return Response.status(HttpStatus.SC_OK).entity(responsePayload).build(); + } + ResponsePayload responsePayload = new ResponsePayload(); + responsePayload.setStatusCode(HttpStatus.SC_OK); + responsePayload.setMessageFromServer("Sending all retrieved device policies."); + responsePayload.setResponseContent(policy); + return Response.status(HttpStatus.SC_OK).entity(responsePayload).build(); + } + + @GET + @Path("count") + public int getPolicyCount() throws MDMAPIException { + PolicyManagerService policyManagementService = MDMAPIUtils.getPolicyManagementService(); + try { + PolicyAdministratorPoint policyAdministratorPoint = policyManagementService.getPAP(); + return policyAdministratorPoint.getPolicyCount(); + } catch (PolicyManagementException e) { + String error = "Policy Management related exception"; + log.error(error, e); + throw new MDMAPIException(error, e); + } + } + + @PUT + @Path("{id}") + public ResponsePayload updatePolicy(PolicyWrapper policyWrapper, @PathParam("id") int policyId) + throws MDMAPIException { +// PolicyManagerService policyManagementService = MDMAPIUtils.getPolicyManagementService(); +// ResponsePayload responseMsg = new ResponsePayload(); +// try { +// PolicyAdministratorPoint pap = policyManagementService.getPAP(); +// org.wso2.carbon.policy.mgt.common.Policy previousPolicy = pap.getPolicy(policyId); +// policy.setProfile(pap.getProfile(previousPolicy.getProfileId())); +// policy.setId(previousPolicy.getId()); +// pap.updatePolicy(policy); +// Response.status(HttpStatus.SC_OK); +// responseMsg.setMessageFromServer("Policy has been updated successfully."); +// return responseMsg; +// } catch (PolicyManagementException e) { +// String error = "Policy Management related exception"; +// log.error(error, e); +// throw new MDMAPIException(error, e); +// } + + + + PolicyManagerService policyManagementService = MDMAPIUtils.getPolicyManagementService(); + ResponsePayload responseMsg = new ResponsePayload(); + org.wso2.carbon.policy.mgt.common.Policy policy = new org.wso2.carbon.policy.mgt.common.Policy(); + policy.setPolicyName(policyWrapper.getPolicyName()); + policy.setId(policyId); + policy.setProfileId(policyWrapper.getProfileId()); + policy.setDescription(policyWrapper.getDescription()); + policy.setProfile(MDMUtil.convertProfile(policyWrapper.getProfile())); + policy.setOwnershipType(policyWrapper.getOwnershipType()); + policy.setRoles(policyWrapper.getRoles()); + policy.setUsers(policyWrapper.getUsers()); + policy.setTenantId(policyWrapper.getTenantId()); + policy.setCompliance(policyWrapper.getCompliance()); + // policy.setActive(true); + + try { + PolicyAdministratorPoint pap = policyManagementService.getPAP(); +// pap.addPolicy(policy); + pap.updatePolicy(policy); +// Response.status(HttpStatus.SC_CREATED); + Response.status(HttpStatus.SC_OK); + responseMsg.setStatusCode(HttpStatus.SC_CREATED); +// responseMsg.setMessageFromServer("Policy has been added successfully."); + responseMsg.setMessageFromServer("Policy has been updated successfully."); + return responseMsg; + } catch (PolicyManagementException e) { + String error = "Policy Management related exception in policy update."; + log.error(error, e); + throw new MDMAPIException(error, e); + } + } + + @PUT + @Path("priorities") + @Consumes({MediaType.APPLICATION_JSON}) + @Produces({MediaType.APPLICATION_JSON}) + public Response updatePolicyPriorities(List priorityUpdatedPolicies) + throws MDMAPIException { + PolicyManagerService policyManagementService = MDMAPIUtils.getPolicyManagementService(); + List policiesToUpdate = + new ArrayList(priorityUpdatedPolicies.size()); + int i; + for (i = 0; i < priorityUpdatedPolicies.size(); i++) { + org.wso2.carbon.policy.mgt.common.Policy policyObj = new org.wso2.carbon.policy.mgt.common.Policy(); + policyObj.setId(priorityUpdatedPolicies.get(i).getId()); + policyObj.setPriorityId(priorityUpdatedPolicies.get(i).getPriority()); + policiesToUpdate.add(policyObj); + } + boolean policiesUpdated; + try { + PolicyAdministratorPoint pap = policyManagementService.getPAP(); + policiesUpdated = pap.updatePolicyPriorities(policiesToUpdate); + } catch (PolicyManagementException e) { + String error = "Exception in updating policy priorities."; + log.error(error, e); + throw new MDMAPIException(error, e); + } + ResponsePayload responsePayload = new ResponsePayload(); + if (policiesUpdated) { + responsePayload.setStatusCode(HttpStatus.SC_OK); + responsePayload.setMessageFromServer("Policy Priorities successfully updated."); + return Response.status(HttpStatus.SC_OK).entity(responsePayload).build(); + } else { + responsePayload.setStatusCode(HttpStatus.SC_BAD_REQUEST); + responsePayload.setMessageFromServer("Policy priorities did not update. Bad Request."); + return Response.status(HttpStatus.SC_BAD_REQUEST).entity(responsePayload).build(); + } + } + + @POST + @Path("bulk-remove") + @Consumes("application/json") + @Produces("application/json") + public Response bulkRemovePolicy(List policyIds) throws MDMAPIException { + PolicyManagerService policyManagementService = MDMAPIUtils.getPolicyManagementService(); + boolean policyDeleted = true; + try { + PolicyAdministratorPoint pap = policyManagementService.getPAP(); + for(int i : policyIds) { + org.wso2.carbon.policy.mgt.common.Policy policy = pap.getPolicy(i); + if(!pap.deletePolicy(policy)){ + policyDeleted = false; + } + } + } catch (PolicyManagementException e) { + String error = "Exception in deleting policies."; + log.error(error, e); + throw new MDMAPIException(error, e); + } + ResponsePayload responsePayload = new ResponsePayload(); + if (policyDeleted) { + responsePayload.setStatusCode(HttpStatus.SC_OK); + responsePayload.setMessageFromServer("Policies have been successfully deleted."); + return Response.status(HttpStatus.SC_OK).entity(responsePayload).build(); + } else { + responsePayload.setStatusCode(HttpStatus.SC_BAD_REQUEST); + responsePayload.setMessageFromServer("Policy does not exist."); + return Response.status(HttpStatus.SC_BAD_REQUEST).entity(responsePayload).build(); + } + } + +// @GET +// @Path("task/{mf}") +// public int taskService(@PathParam("mf") int monitoringFrequency) throws MDMAPIException { +// int policyCount = 0; +// PolicyManagerService policyManagementService = MDMAPIUtils.getPolicyManagementService(); +// try { +// TaskScheduleService taskScheduleService = policyManagementService.getTaskScheduleService(); +// taskScheduleService.startTask(monitoringFrequency); +// return policyCount; +// } catch (PolicyMonitoringTaskException e) { +// String error = "Policy Management related exception"; +// log.error(error, e); +// throw new MDMAPIException(error, e); +// } +// } + + + @PUT + @Produces("application/json") + @Path("activate") + public Response activatePolicy(List policyIds) throws MDMAPIException { + try { + PolicyManagerService policyManagementService = MDMAPIUtils.getPolicyManagementService(); + PolicyAdministratorPoint pap = policyManagementService.getPAP(); + for(int i : policyIds) { + pap.activatePolicy(i); + } + } catch (PolicyManagementException e) { + String error = "Exception in activating policies."; + log.error(error, e); + throw new MDMAPIException(error, e); + } + + ResponsePayload responsePayload = new ResponsePayload(); + responsePayload.setStatusCode(HttpStatus.SC_OK); + responsePayload.setMessageFromServer("Selected policies have been successfully activated."); + return Response.status(HttpStatus.SC_OK).entity(responsePayload).build(); + + } + + + @PUT + @Produces("application/json") + @Path("inactivate") + public Response inactivatePolicy(List policyIds) throws MDMAPIException { + + try { + PolicyManagerService policyManagementService = MDMAPIUtils.getPolicyManagementService(); + PolicyAdministratorPoint pap = policyManagementService.getPAP(); + for(int i : policyIds) { + pap.inactivatePolicy(i); + } + } catch (PolicyManagementException e) { + String error = "Exception in inactivating policies."; + log.error(error, e); + throw new MDMAPIException(error, e); + } + ResponsePayload responsePayload = new ResponsePayload(); + responsePayload.setStatusCode(HttpStatus.SC_OK); + responsePayload.setMessageFromServer("Selected policies have been successfully inactivated."); + return Response.status(HttpStatus.SC_OK).entity(responsePayload).build(); + } + + + @PUT + @Produces("application/json") + @Path("apply-changes") + public Response applyChanges() throws MDMAPIException { + + try { + PolicyManagerService policyManagementService = MDMAPIUtils.getPolicyManagementService(); + PolicyAdministratorPoint pap = policyManagementService.getPAP(); + pap.publishChanges(); + + + } catch (PolicyManagementException e) { + String error = "Exception in applying changes."; + log.error(error, e); + throw new MDMAPIException(error, e); + } + ResponsePayload responsePayload = new ResponsePayload(); + responsePayload.setStatusCode(HttpStatus.SC_OK); + responsePayload.setMessageFromServer("Changes have been successfully updated."); + return Response.status(HttpStatus.SC_OK).entity(responsePayload).build(); + } + + @GET + @Path("start-task/{milliseconds}") + public Response startTaskService(@PathParam("milliseconds") int monitoringFrequency) throws MDMAPIException { + + PolicyManagerService policyManagementService = MDMAPIUtils.getPolicyManagementService(); + try { + TaskScheduleService taskScheduleService = policyManagementService.getTaskScheduleService(); + taskScheduleService.startTask(monitoringFrequency); + + + } catch (PolicyMonitoringTaskException e) { + String error = "Policy Management related exception."; + log.error(error, e); + throw new MDMAPIException(error, e); + } + ResponsePayload responsePayload = new ResponsePayload(); + responsePayload.setStatusCode(HttpStatus.SC_OK); + responsePayload.setMessageFromServer("Policy monitoring service started successfully."); + return Response.status(HttpStatus.SC_OK).entity(responsePayload).build(); + } + + + @GET + @Path("update-task/{milliseconds}") + public Response updateTaskService(@PathParam("milliseconds") int monitoringFrequency) throws MDMAPIException { + + PolicyManagerService policyManagementService = MDMAPIUtils.getPolicyManagementService(); + try { + TaskScheduleService taskScheduleService = policyManagementService.getTaskScheduleService(); + taskScheduleService.updateTask(monitoringFrequency); + + } catch (PolicyMonitoringTaskException e) { + String error = "Policy Management related exception."; + log.error(error, e); + throw new MDMAPIException(error, e); + } + ResponsePayload responsePayload = new ResponsePayload(); + responsePayload.setStatusCode(HttpStatus.SC_OK); + responsePayload.setMessageFromServer("Policy monitoring service updated successfully."); + return Response.status(HttpStatus.SC_OK).entity(responsePayload).build(); + } + + + @GET + @Path("stop-task") + public Response stopTaskService() throws MDMAPIException { + + PolicyManagerService policyManagementService = MDMAPIUtils.getPolicyManagementService(); + try { + TaskScheduleService taskScheduleService = policyManagementService.getTaskScheduleService(); + taskScheduleService.stopTask(); + + } catch (PolicyMonitoringTaskException e) { + String error = "Policy Management related exception."; + log.error(error, e); + throw new MDMAPIException(error, e); + } + ResponsePayload responsePayload = new ResponsePayload(); + responsePayload.setStatusCode(HttpStatus.SC_OK); + responsePayload.setMessageFromServer("Policy monitoring service stopped successfully."); + return Response.status(HttpStatus.SC_OK).entity(responsePayload).build(); + } + + + @GET + @Path("{type}/{id}") + public ComplianceData getComplianceDataOfDevice(@PathParam("type") String type, @PathParam("id") String id) throws + MDMAPIException { + try { + DeviceIdentifier deviceIdentifier = MDMAPIUtils.instantiateDeviceIdentifier(type, id); + PolicyManagerService policyManagementService = MDMAPIUtils.getPolicyManagementService(); + return policyManagementService.getDeviceCompliance(deviceIdentifier); + } catch (PolicyComplianceException e) { + String error = "Error occurred while getting the compliance data."; + log.error(error, e); + throw new MDMAPIException(error, e); + } + } + + @GET + @Path("{type}/{id}/active-policy") + public org.wso2.carbon.policy.mgt.common.Policy getDeviceActivePolicy(@PathParam("type") String type, + @PathParam("id") String id) throws MDMAPIException { + try { + DeviceIdentifier deviceIdentifier = MDMAPIUtils.instantiateDeviceIdentifier(type, id); + PolicyManagerService policyManagementService = MDMAPIUtils.getPolicyManagementService(); + return policyManagementService.getAppliedPolicyToDevice(deviceIdentifier); + } catch (PolicyManagementException e) { + String error = "Error occurred while getting the current policy."; + log.error(error, e); + throw new MDMAPIException(error, e); + } + } +} diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/api/Profile.java b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/api/Profile.java new file mode 100644 index 0000000000..4cf216ec6a --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/api/Profile.java @@ -0,0 +1,89 @@ +/* + * 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.wso2.carbon.mdm.api; + +import org.apache.commons.httpclient.HttpStatus; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.mdm.api.common.MDMAPIException; +import org.wso2.carbon.mdm.api.util.MDMAPIUtils; +import org.wso2.carbon.mdm.api.util.ResponsePayload; +import org.wso2.carbon.policy.mgt.common.PolicyAdministratorPoint; +import org.wso2.carbon.policy.mgt.common.PolicyManagementException; +import org.wso2.carbon.policy.mgt.core.PolicyManagerService; + +import javax.ws.rs.DELETE; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.core.Response; + +public class Profile { + private static Log log = LogFactory.getLog(Profile.class); + + @POST + public org.wso2.carbon.policy.mgt.common.Profile addProfile(org.wso2.carbon.policy.mgt.common.Profile profile) throws MDMAPIException { + PolicyManagerService policyManagementService = MDMAPIUtils.getPolicyManagementService(); + try { + PolicyAdministratorPoint pap = policyManagementService.getPAP(); + profile = pap.addProfile(profile); + Response.status(HttpStatus.SC_CREATED); + return profile; + } catch (PolicyManagementException e) { + String error = "Policy Management related exception"; + log.error(error, e); + throw new MDMAPIException(error, e); + } + } + @POST + @Path("{id}") + public ResponsePayload updateProfile(org.wso2.carbon.policy.mgt.common.Profile profile, @PathParam("id") String profileId) + throws MDMAPIException { + PolicyManagerService policyManagementService = MDMAPIUtils.getPolicyManagementService(); + ResponsePayload responseMsg = new ResponsePayload(); + try { + PolicyAdministratorPoint pap = policyManagementService.getPAP(); + pap.updateProfile(profile); + Response.status(HttpStatus.SC_OK); + responseMsg.setMessageFromServer("Profile has been updated successfully."); + return responseMsg; + } catch (PolicyManagementException e) { + String error = "Policy Management related exception"; + log.error(error, e); + throw new MDMAPIException(error, e); + } + } + @DELETE + @Path("{id}") + public ResponsePayload deleteProfile(@PathParam("id") int profileId) throws MDMAPIException { + PolicyManagerService policyManagementService = MDMAPIUtils.getPolicyManagementService(); + ResponsePayload responseMsg = new ResponsePayload(); + try { + PolicyAdministratorPoint pap = policyManagementService.getPAP(); + org.wso2.carbon.policy.mgt.common.Profile profile = pap.getProfile(profileId); + pap.deleteProfile(profile); + Response.status(HttpStatus.SC_OK); + responseMsg.setMessageFromServer("Profile has been deleted successfully."); + return responseMsg; + } catch (PolicyManagementException e) { + String error = "Policy Management related exception"; + log.error(error, e); + throw new MDMAPIException(error, e); + } + } +} diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/api/Role.java b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/api/Role.java new file mode 100644 index 0000000000..a0adeed6aa --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/api/Role.java @@ -0,0 +1,422 @@ +/* + * Copyright (c) 2016, 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.wso2.carbon.mdm.api; + +import org.apache.commons.httpclient.HttpStatus; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.CarbonConstants; +import org.wso2.carbon.base.MultitenantConstants; +import org.wso2.carbon.mdm.api.common.MDMAPIException; +import org.wso2.carbon.mdm.api.util.MDMAPIUtils; +import org.wso2.carbon.mdm.api.util.ResponsePayload; +import org.wso2.carbon.mdm.beans.RoleWrapper; +import org.wso2.carbon.mdm.util.SetReferenceTransformer; +import org.wso2.carbon.user.api.*; +import org.wso2.carbon.user.core.common.AbstractUserStoreManager; +import org.wso2.carbon.user.mgt.UserRealmProxy; +import org.wso2.carbon.user.mgt.common.UIPermissionNode; +import org.wso2.carbon.user.mgt.common.UserAdminException; + +import javax.ws.rs.*; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class Role { + + private static Log log = LogFactory.getLog(Role.class); + + /** + * Get user roles (except all internal roles) from system. + * + * @return A list of users + * @throws org.wso2.carbon.mdm.api.common.MDMAPIException + */ + @GET + @Produces ({MediaType.APPLICATION_JSON}) + public Response getRoles() throws MDMAPIException { + UserStoreManager userStoreManager = MDMAPIUtils.getUserStoreManager(); + String[] roles; + try { + if (log.isDebugEnabled()) { + log.debug("Getting the list of user roles"); + } + roles = userStoreManager.getRoleNames(); + + } catch (UserStoreException e) { + String msg = "Error occurred while retrieving the list of user roles."; + log.error(msg, e); + throw new MDMAPIException(msg, e); + } + // removing all internal roles and roles created for Service-providers + List filteredRoles = new ArrayList(); + for (String role : roles) { + if (!(role.startsWith("Internal/") || role.startsWith("Application/"))) { + filteredRoles.add(role); + } + } + ResponsePayload responsePayload = new ResponsePayload(); + responsePayload.setStatusCode(HttpStatus.SC_OK); + responsePayload.setMessageFromServer("All user roles were successfully retrieved."); + responsePayload.setResponseContent(filteredRoles); + return Response.status(HttpStatus.SC_OK).entity(responsePayload).build(); + } + + /** + * Get user roles by user store(except all internal roles) from system. + * + * @return A list of users + * @throws org.wso2.carbon.mdm.api.common.MDMAPIException + */ + @GET + @Path ("{userStore}") + @Produces ({MediaType.APPLICATION_JSON}) + public Response getRoles(@PathParam ("userStore") String userStore) throws MDMAPIException { + AbstractUserStoreManager abstractUserStoreManager = (AbstractUserStoreManager) MDMAPIUtils.getUserStoreManager(); + String[] roles; + try { + if (log.isDebugEnabled()) { + log.debug("Getting the list of user roles"); + } + roles = abstractUserStoreManager.getRoleNames(userStore+"/*", -1, false, true, true); + + } catch (UserStoreException e) { + String msg = "Error occurred while retrieving the list of user roles."; + log.error(msg, e); + throw new MDMAPIException(msg, e); + } + // removing all internal roles and roles created for Service-providers + List filteredRoles = new ArrayList(); + for (String role : roles) { + if (!(role.startsWith("Internal/") || role.startsWith("Application/"))) { + filteredRoles.add(role); + } + } + ResponsePayload responsePayload = new ResponsePayload(); + responsePayload.setStatusCode(HttpStatus.SC_OK); + responsePayload.setMessageFromServer("All user roles were successfully retrieved."); + responsePayload.setResponseContent(filteredRoles); + return Response.status(HttpStatus.SC_OK).entity(responsePayload).build(); + } + + /** + * Get user roles by providing a filtering criteria(except all internal roles & system roles) from system. + * + * @return A list of users + * @throws org.wso2.carbon.mdm.api.common.MDMAPIException + */ + @GET + @Path ("search") + @Produces ({MediaType.APPLICATION_JSON}) + public Response getMatchingRoles(@QueryParam ("filter") String filter) throws MDMAPIException { + AbstractUserStoreManager abstractUserStoreManager = (AbstractUserStoreManager) MDMAPIUtils.getUserStoreManager(); + String[] roles; + try { + if (log.isDebugEnabled()) { + log.debug("Getting the list of user roles using filter : " + filter); + } + roles = abstractUserStoreManager.getRoleNames("*" + filter + "*", -1, true, true, true); + + } catch (UserStoreException e) { + String msg = "Error occurred while retrieving the list of user roles using the filter : " + filter; + log.error(msg, e); + throw new MDMAPIException(msg, e); + } + // removing all internal roles and roles created for Service-providers + List filteredRoles = new ArrayList(); + for (String role : roles) { + if (!(role.startsWith("Internal/") || role.startsWith("Application/"))) { + filteredRoles.add(role); + } + } + ResponsePayload responsePayload = new ResponsePayload(); + responsePayload.setStatusCode(HttpStatus.SC_OK); + responsePayload.setMessageFromServer("All matching user roles were successfully retrieved."); + responsePayload.setResponseContent(filteredRoles); + return Response.status(HttpStatus.SC_OK).entity(responsePayload).build(); + } + + /** + * Get role permissions. + * + * @return list of permissions + * @throws MDMAPIException + */ + @GET + @Path ("permissions") + @Produces ({MediaType.APPLICATION_JSON}) + public ResponsePayload getPermissions(@QueryParam ("rolename") String roleName) throws MDMAPIException { + final UserRealm userRealm = MDMAPIUtils.getUserRealm(); + org.wso2.carbon.user.core.UserRealm userRealmCore = null; + final UIPermissionNode rolePermissions; + if (userRealm instanceof org.wso2.carbon.user.core.UserRealm) { + userRealmCore = (org.wso2.carbon.user.core.UserRealm) userRealm; + } + + try { + final UserRealmProxy userRealmProxy = new UserRealmProxy(userRealmCore); + rolePermissions = userRealmProxy.getRolePermissions(roleName, MultitenantConstants.SUPER_TENANT_ID); + UIPermissionNode[] deviceMgtPermissions = new UIPermissionNode[2]; + + for (UIPermissionNode permissionNode : rolePermissions.getNodeList()) { + if (permissionNode.getResourcePath().equals("/permission/admin")) { + for (UIPermissionNode node : permissionNode.getNodeList()) { + if (node.getResourcePath().equals("/permission/admin/device-mgt")) { + deviceMgtPermissions[0] = node; + } else if (node.getResourcePath().equals("/permission/admin/login")) { + deviceMgtPermissions[1] = node; + } + } + } + } + rolePermissions.setNodeList(deviceMgtPermissions); + } catch (UserAdminException e) { + String msg = "Error occurred while retrieving the user role"; + log.error(msg, e); + throw new MDMAPIException(msg, e); + } + ResponsePayload responsePayload = new ResponsePayload(); + responsePayload.setStatusCode(HttpStatus.SC_OK); + responsePayload.setMessageFromServer("All permissions retrieved"); + responsePayload.setResponseContent(rolePermissions); + return responsePayload; + } + + /** + * Get user role of the system + * + * @return user role + * @throws org.wso2.carbon.mdm.api.common.MDMAPIException + */ + @GET + @Path("role") + @Produces ({MediaType.APPLICATION_JSON}) + public ResponsePayload getRole(@QueryParam ("rolename") String roleName) throws MDMAPIException { + final UserStoreManager userStoreManager = MDMAPIUtils.getUserStoreManager(); + final UserRealm userRealm = MDMAPIUtils.getUserRealm(); + org.wso2.carbon.user.core.UserRealm userRealmCore = null; + if (userRealm instanceof org.wso2.carbon.user.core.UserRealm) { + userRealmCore = (org.wso2.carbon.user.core.UserRealm) userRealm; + } + + RoleWrapper roleWrapper = new RoleWrapper(); + try { + final UserRealmProxy userRealmProxy = new UserRealmProxy(userRealmCore); + if (log.isDebugEnabled()) { + log.debug("Getting the list of user roles"); + } + if (userStoreManager.isExistingRole(roleName)) { + roleWrapper.setRoleName(roleName); + roleWrapper.setUsers(userStoreManager.getUserListOfRole(roleName)); + // Get the permission nodes and hand picking only device management and login perms + final UIPermissionNode rolePermissions = + userRealmProxy.getRolePermissions(roleName, MultitenantConstants.SUPER_TENANT_ID); + UIPermissionNode[] deviceMgtPermissions = new UIPermissionNode[2]; + + for (UIPermissionNode permissionNode : rolePermissions.getNodeList()) { + if (permissionNode.getResourcePath().equals("/permission/admin")) { + for (UIPermissionNode node : permissionNode.getNodeList()) { + if (node.getResourcePath().equals("/permission/admin/device-mgt")) { + deviceMgtPermissions[0] = node; + } else if (node.getResourcePath().equals("/permission/admin/login")) { + deviceMgtPermissions[1] = node; + } + } + } + } + rolePermissions.setNodeList(deviceMgtPermissions); + ArrayList permList = new ArrayList(); + iteratePermissions(rolePermissions, permList); + roleWrapper.setPermissionList(rolePermissions); + String[] permListAr = new String[permList.size()]; + roleWrapper.setPermissions(permList.toArray(permListAr)); + } + } catch (UserStoreException e) { + String msg = "Error occurred while retrieving the user role"; + log.error(msg, e); + throw new MDMAPIException(msg, e); + } catch (UserAdminException e) { + String msg = "Error occurred while retrieving the user role"; + log.error(msg, e); + throw new MDMAPIException(msg, e); + } + ResponsePayload responsePayload = new ResponsePayload(); + responsePayload.setStatusCode(HttpStatus.SC_OK); + responsePayload.setMessageFromServer("All user roles were successfully retrieved."); + responsePayload.setResponseContent(roleWrapper); + return responsePayload; + } + + /** + * API is used to persist a new Role + * + * @param roleWrapper + * @return + * @throws MDMAPIException + */ + @POST + @Produces ({MediaType.APPLICATION_JSON}) + public Response addRole(RoleWrapper roleWrapper) throws MDMAPIException { + UserStoreManager userStoreManager = MDMAPIUtils.getUserStoreManager(); + try { + if (log.isDebugEnabled()) { + log.debug("Persisting the role to user store"); + } + Permission[] permissions = null; + if (roleWrapper.getPermissions() != null && roleWrapper.getPermissions().length > 0) { + permissions = new Permission[roleWrapper.getPermissions().length]; + + for (int i = 0; i < permissions.length; i++) { + String permission = roleWrapper.getPermissions()[i]; + permissions[i] = new Permission(permission, CarbonConstants.UI_PERMISSION_ACTION); + } + } + userStoreManager.addRole(roleWrapper.getRoleName(), roleWrapper.getUsers(), permissions); + } catch (UserStoreException e) { + String msg = e.getMessage(); + log.error(msg, e); + throw new MDMAPIException(msg, e); + } + return Response.status(HttpStatus.SC_CREATED).build(); + } + + /** + * API is used to update a role Role + * + * @param roleWrapper + * @return + * @throws MDMAPIException + */ + @PUT + @Produces ({MediaType.APPLICATION_JSON}) + public Response updateRole(@QueryParam ("rolename") String roleName, RoleWrapper roleWrapper) throws + MDMAPIException { + final UserStoreManager userStoreManager = MDMAPIUtils.getUserStoreManager(); + final AuthorizationManager authorizationManager = MDMAPIUtils.getAuthorizationManager(); + String newRoleName = roleWrapper.getRoleName(); + try { + if (log.isDebugEnabled()) { + log.debug("Updating the role to user store"); + } + if (newRoleName != null && !roleName.equals(newRoleName)) { + userStoreManager.updateRoleName(roleName, newRoleName); + } + if (roleWrapper.getUsers() != null) { + SetReferenceTransformer transformer = new SetReferenceTransformer(); + transformer.transform(Arrays.asList(userStoreManager.getUserListOfRole(newRoleName)), + Arrays.asList(roleWrapper.getUsers())); + final String[] usersToAdd = (String[]) + transformer.getObjectsToAdd().toArray(new String[transformer.getObjectsToAdd().size()]); + final String[] usersToDelete = (String[]) + transformer.getObjectsToRemove().toArray(new String[transformer.getObjectsToRemove().size()]); + userStoreManager.updateUserListOfRole(newRoleName, usersToDelete, usersToAdd); + } + if (roleWrapper.getPermissions() != null) { + // Delete all authorizations for the current role before authorizing the permission tree + authorizationManager.clearRoleAuthorization(roleName); + if (roleWrapper.getPermissions().length > 0) { + for (int i = 0; i < roleWrapper.getPermissions().length; i++) { + String permission = roleWrapper.getPermissions()[i]; + authorizationManager.authorizeRole(roleName, permission, CarbonConstants.UI_PERMISSION_ACTION); + } + } + } + } catch (UserStoreException e) { + String msg = e.getMessage(); + log.error(msg, e); + throw new MDMAPIException(msg, e); + } + return Response.status(HttpStatus.SC_OK).build(); + } + + /** + * API is used to delete a role and authorizations + * + * @param roleName + * @return + * @throws MDMAPIException + */ + @DELETE + @Produces ({MediaType.APPLICATION_JSON}) + public Response deleteRole(@QueryParam ("rolename") String roleName) throws MDMAPIException { + final UserStoreManager userStoreManager = MDMAPIUtils.getUserStoreManager(); + final AuthorizationManager authorizationManager = MDMAPIUtils.getAuthorizationManager(); + try { + if (log.isDebugEnabled()) { + log.debug("Deleting the role in user store"); + } + userStoreManager.deleteRole(roleName); + // Delete all authorizations for the current role before deleting + authorizationManager.clearRoleAuthorization(roleName); + } catch (UserStoreException e) { + String msg = "Error occurred while deleting the role: " + roleName; + log.error(msg, e); + throw new MDMAPIException(msg, e); + } + return Response.status(HttpStatus.SC_OK).build(); + } + + /** + * API is used to update users of a role + * + * @param roleName + * @param userList + * @return + * @throws MDMAPIException + */ + @PUT + @Path ("users") + @Produces ({MediaType.APPLICATION_JSON}) + public Response updateUsers(@QueryParam ("rolename") String roleName, List userList) + throws MDMAPIException { + final UserStoreManager userStoreManager = MDMAPIUtils.getUserStoreManager(); + try { + if (log.isDebugEnabled()) { + log.debug("Updating the users of a role"); + } + SetReferenceTransformer transformer = new SetReferenceTransformer(); + transformer.transform(Arrays.asList(userStoreManager.getUserListOfRole(roleName)), + userList); + final String[] usersToAdd = (String[]) + transformer.getObjectsToAdd().toArray(new String[transformer.getObjectsToAdd().size()]); + final String[] usersToDelete = (String[]) + transformer.getObjectsToRemove().toArray(new String[transformer.getObjectsToRemove().size()]); + + userStoreManager.updateUserListOfRole(roleName, usersToDelete, usersToAdd); + } catch (UserStoreException e) { + String msg = "Error occurred while saving the users of the role: " + roleName; + log.error(msg, e); + throw new MDMAPIException(e.getMessage(), e); + } + return Response.status(HttpStatus.SC_OK).build(); + } + + public ArrayList iteratePermissions(UIPermissionNode uiPermissionNode, ArrayList list) { + for (UIPermissionNode permissionNode : uiPermissionNode.getNodeList()) { + list.add(permissionNode.getResourcePath()); + if (permissionNode.getNodeList() != null && permissionNode.getNodeList().length > 0) { + iteratePermissions(permissionNode, list); + } + } + return list; + } +} diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/api/User.java b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/api/User.java new file mode 100644 index 0000000000..16c9ee7ca0 --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/api/User.java @@ -0,0 +1,786 @@ +/* + * Copyright (c) 2016, 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.wso2.carbon.mdm.api; + +import org.apache.commons.codec.binary.Base64; +import org.apache.commons.httpclient.HttpStatus; +import org.apache.commons.lang.StringUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.context.CarbonContext; +import org.wso2.carbon.device.mgt.common.DeviceManagementException; +import org.wso2.carbon.device.mgt.common.EmailMessageProperties; +import org.wso2.carbon.device.mgt.common.PaginationRequest; +import org.wso2.carbon.device.mgt.core.service.DeviceManagementProviderService; +import org.wso2.carbon.mdm.api.common.MDMAPIException; +import org.wso2.carbon.mdm.api.util.MDMAPIUtils; +import org.wso2.carbon.mdm.api.util.ResponsePayload; +import org.wso2.carbon.mdm.beans.UserCredentialWrapper; +import org.wso2.carbon.mdm.beans.UserWrapper; +import org.wso2.carbon.mdm.util.Constants; +import org.wso2.carbon.mdm.util.SetReferenceTransformer; +import org.wso2.carbon.user.api.UserStoreException; +import org.wso2.carbon.user.api.UserStoreManager; + +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.MediaType; +import javax.ws.rs.core.Response; +import java.io.UnsupportedEncodingException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.TreeSet; + +/** + * This class represents the JAX-RS services of User related functionality. + */ +public class User { + + private static Log log = LogFactory.getLog(User.class); + private String ROLE_EVERYONE = "Internal/everyone"; + + /** + * Method to add user to emm-user-store. + * + * @param userWrapper Wrapper object representing input json payload + * @return {Response} Status of the request wrapped inside Response object + * @throws MDMAPIException + */ + @POST + @Consumes({MediaType.APPLICATION_JSON}) + @Produces({MediaType.APPLICATION_JSON}) + public Response addUser(UserWrapper userWrapper) throws MDMAPIException { + UserStoreManager userStoreManager = MDMAPIUtils.getUserStoreManager(); + ResponsePayload responsePayload = new ResponsePayload(); + try { + if (userStoreManager.isExistingUser(userWrapper.getUsername())) { + // if user already exists + if (log.isDebugEnabled()) { + log.debug("User by username: " + userWrapper.getUsername() + + " already exists. Therefore, request made to add user was refused."); + } + // returning response with bad request state + responsePayload.setStatusCode(HttpStatus.SC_CONFLICT); + responsePayload. + setMessageFromServer("User by username: " + userWrapper.getUsername() + + " already exists. Therefore, request made to add user was refused."); + return Response.status(HttpStatus.SC_CONFLICT).entity(responsePayload).build(); + } else { + String initialUserPassword = generateInitialUserPassword(); + Map defaultUserClaims = + buildDefaultUserClaims(userWrapper.getFirstname(), userWrapper.getLastname(), + userWrapper.getEmailAddress()); + // calling addUser method of carbon user api + userStoreManager.addUser(userWrapper.getUsername(), initialUserPassword, + userWrapper.getRoles(), defaultUserClaims, null); + // invite newly added user to enroll device + inviteNewlyAddedUserToEnrollDevice(userWrapper.getUsername(), initialUserPassword); + // Outputting debug message upon successful addition of user + if (log.isDebugEnabled()) { + log.debug("User by username: " + userWrapper.getUsername() + " was successfully added."); + } + // returning response with success state + responsePayload.setStatusCode(HttpStatus.SC_CREATED); + responsePayload.setMessageFromServer("User by username: " + userWrapper.getUsername() + + " was successfully added."); + return Response.status(HttpStatus.SC_CREATED).entity(responsePayload).build(); + } + } catch (UserStoreException e) { + String errorMsg = "Exception in trying to add user by username: " + userWrapper.getUsername(); + log.error(errorMsg, e); + throw new MDMAPIException(errorMsg, e); + } catch (DeviceManagementException e) { + String errorMsg = "Exception in trying to add user by username: " + userWrapper.getUsername(); + log.error(errorMsg, e); + throw new MDMAPIException(errorMsg, e); + } + } + + /** + * Method to get user information from emm-user-store. + * + * @param username User-name of the user + * @return {Response} Status of the request wrapped inside Response object + * @throws MDMAPIException + */ + @GET + @Path("view") + @Produces({MediaType.APPLICATION_JSON}) + public Response getUser(@QueryParam("username") String username) throws MDMAPIException { + UserStoreManager userStoreManager = MDMAPIUtils.getUserStoreManager(); + ResponsePayload responsePayload = new ResponsePayload(); + try { + if (userStoreManager.isExistingUser(username)) { + UserWrapper user = new UserWrapper(); + user.setUsername(username); + user.setEmailAddress(getClaimValue(username, Constants.USER_CLAIM_EMAIL_ADDRESS)); + user.setFirstname(getClaimValue(username, Constants.USER_CLAIM_FIRST_NAME)); + user.setLastname(getClaimValue(username, Constants.USER_CLAIM_LAST_NAME)); + // Outputting debug message upon successful retrieval of user + if (log.isDebugEnabled()) { + log.debug("User by username: " + username + " was found."); + } + responsePayload.setStatusCode(HttpStatus.SC_OK); + responsePayload.setMessageFromServer("User information was retrieved successfully."); + responsePayload.setResponseContent(user); + return Response.status(HttpStatus.SC_OK).entity(responsePayload).build(); + } else { + // Outputting debug message upon trying to remove non-existing user + if (log.isDebugEnabled()) { + log.debug("User by username: " + username + " does not exist."); + } + // returning response with bad request state + responsePayload.setStatusCode(HttpStatus.SC_BAD_REQUEST); + responsePayload.setMessageFromServer( + "User by username: " + username + " does not exist."); + return Response.status(HttpStatus.SC_NOT_FOUND).entity(responsePayload).build(); + } + } catch (UserStoreException e) { + String errorMsg = "Exception in trying to retrieve user by username: " + username; + log.error(errorMsg, e); + throw new MDMAPIException(errorMsg, e); + } + } + + /** + * Update user in user store + * + * @param userWrapper Wrapper object representing input json payload + * @return {Response} Status of the request wrapped inside Response object + * @throws MDMAPIException + */ + @PUT + @Consumes({MediaType.APPLICATION_JSON}) + @Produces({MediaType.APPLICATION_JSON}) + public Response updateUser(UserWrapper userWrapper, @QueryParam("username") String username) + throws MDMAPIException { + UserStoreManager userStoreManager = MDMAPIUtils.getUserStoreManager(); + ResponsePayload responsePayload = new ResponsePayload(); + try { + if (userStoreManager.isExistingUser(userWrapper.getUsername())) { + Map defaultUserClaims = + buildDefaultUserClaims(userWrapper.getFirstname(), userWrapper.getLastname(), + userWrapper.getEmailAddress()); + if (StringUtils.isNotEmpty(userWrapper.getPassword())) { + // Decoding Base64 encoded password + byte[] decodedBytes = Base64.decodeBase64(userWrapper.getPassword()); + userStoreManager.updateCredentialByAdmin(userWrapper.getUsername(), + new String(decodedBytes, "UTF-8")); + log.debug("User credential of username: " + userWrapper.getUsername() + " has been changed"); + } + List listofFilteredRoles = getFilteredRoles(userStoreManager, userWrapper.getUsername()); + final String[] existingRoles = listofFilteredRoles.toArray(new String[listofFilteredRoles.size()]); + + /* + Use the Set theory to find the roles to delete and roles to add + The difference of roles in existingRolesSet and newRolesSet needed to be deleted + new roles to add = newRolesSet - The intersection of roles in existingRolesSet and newRolesSet + */ + final TreeSet existingRolesSet = new TreeSet(); + Collections.addAll(existingRolesSet, existingRoles); + final TreeSet newRolesSet = new TreeSet(); + Collections.addAll(newRolesSet, userWrapper.getRoles()); + existingRolesSet.removeAll(newRolesSet); + // Now we have the roles to delete + String[] rolesToDelete = existingRolesSet.toArray(new String[existingRolesSet.size()]); + List roles = new ArrayList(Arrays.asList(rolesToDelete)); + roles.remove(ROLE_EVERYONE); + rolesToDelete = roles.toArray(new String[0]); + // Clearing and re-initializing the set + existingRolesSet.clear(); + Collections.addAll(existingRolesSet, existingRoles); + newRolesSet.removeAll(existingRolesSet); + // Now we have the roles to add + String[] rolesToAdd = newRolesSet.toArray(new String[newRolesSet.size()]); + userStoreManager.updateRoleListOfUser(userWrapper.getUsername(), rolesToDelete, rolesToAdd); + userStoreManager.setUserClaimValues(userWrapper.getUsername(), defaultUserClaims, null); + // Outputting debug message upon successful addition of user + if (log.isDebugEnabled()) { + log.debug("User by username: " + userWrapper.getUsername() + " was successfully updated."); + } + // returning response with success state + responsePayload.setStatusCode(HttpStatus.SC_CREATED); + responsePayload.setMessageFromServer("User by username: " + userWrapper.getUsername() + + " was successfully updated."); + return Response.status(HttpStatus.SC_CREATED).entity(responsePayload).build(); + } else { + if (log.isDebugEnabled()) { + log.debug("User by username: " + userWrapper.getUsername() + + " doesn't exists. Therefore, request made to update user was refused."); + } + // returning response with bad request state + responsePayload.setStatusCode(HttpStatus.SC_CONFLICT); + responsePayload. + setMessageFromServer("User by username: " + userWrapper.getUsername() + + " doesn't exists. Therefore, request made to update user was refused."); + return Response.status(HttpStatus.SC_CONFLICT).entity(responsePayload).build(); + } + } catch (UserStoreException e) { + String errorMsg = "Exception in trying to update user by username: " + userWrapper.getUsername(); + log.error(errorMsg, e); + throw new MDMAPIException(errorMsg, e); + } catch (UnsupportedEncodingException e) { + String errorMsg = "Exception in trying to update user by username: " + userWrapper.getUsername(); + log.error(errorMsg, e); + throw new MDMAPIException(errorMsg, e); + } + } + + /** + * Private method to be used by addUser() to + * generate an initial user password for a user. + * This will be the password used by a user for his initial login to the system. + * + * @return {string} Initial User Password + */ + private String generateInitialUserPassword() { + int passwordLength = 6; + //defining the pool of characters to be used for initial password generation + String lowerCaseCharset = "abcdefghijklmnopqrstuvwxyz"; + String upperCaseCharset = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + String numericCharset = "0123456789"; + Random randomGenerator = new Random(); + String totalCharset = lowerCaseCharset + upperCaseCharset + numericCharset; + int totalCharsetLength = totalCharset.length(); + StringBuffer initialUserPassword = new StringBuffer(); + for (int i = 0; i < passwordLength; i++) { + initialUserPassword + .append(totalCharset.charAt(randomGenerator.nextInt(totalCharsetLength))); + } + if (log.isDebugEnabled()) { + log.debug("Initial user password is created for new user: " + initialUserPassword); + } + return initialUserPassword.toString(); + } + + /** + * Method to build default user claims. + * + * @param firstname First name of the user + * @param lastname Last name of the user + * @param emailAddress Email address of the user + * @return {Object} Default user claims to be provided + */ + private Map buildDefaultUserClaims(String firstname, String lastname, String emailAddress) { + Map defaultUserClaims = new HashMap(); + defaultUserClaims.put(Constants.USER_CLAIM_FIRST_NAME, firstname); + defaultUserClaims.put(Constants.USER_CLAIM_LAST_NAME, lastname); + defaultUserClaims.put(Constants.USER_CLAIM_EMAIL_ADDRESS, emailAddress); + if (log.isDebugEnabled()) { + log.debug("Default claim map is created for new user: " + defaultUserClaims.toString()); + } + return defaultUserClaims; + } + + /** + * Method to remove user from emm-user-store. + * + * @param username Username of the user + * @return {Response} Status of the request wrapped inside Response object + * @throws MDMAPIException + */ + @DELETE + @Produces({MediaType.APPLICATION_JSON}) + public Response removeUser(@QueryParam("username") String username) throws MDMAPIException { + UserStoreManager userStoreManager = MDMAPIUtils.getUserStoreManager(); + ResponsePayload responsePayload = new ResponsePayload(); + try { + if (userStoreManager.isExistingUser(username)) { + // if user already exists, trying to remove user + userStoreManager.deleteUser(username); + // Outputting debug message upon successful removal of user + if (log.isDebugEnabled()) { + log.debug("User by username: " + username + " was successfully removed."); + } + // returning response with success state + responsePayload.setStatusCode(HttpStatus.SC_OK); + responsePayload.setMessageFromServer( + "User by username: " + username + " was successfully removed."); + return Response.status(HttpStatus.SC_OK).entity(responsePayload).build(); + } else { + // Outputting debug message upon trying to remove non-existing user + if (log.isDebugEnabled()) { + log.debug("User by username: " + username + " does not exist for removal."); + } + // returning response with bad request state + responsePayload.setStatusCode(HttpStatus.SC_BAD_REQUEST); + responsePayload.setMessageFromServer( + "User by username: " + username + " does not exist for removal."); + return Response.status(HttpStatus.SC_BAD_REQUEST).entity(responsePayload).build(); + } + } catch (UserStoreException e) { + String errorMsg = "Exception in trying to remove user by username: " + username; + log.error(errorMsg, e); + throw new MDMAPIException(errorMsg, e); + } + } + + /** + * get all the roles except for the internal/xxx and application/xxx + * + * @param userStoreManager + * @param username + * @return the list of filtered roles + * @throws UserStoreException + */ + private List getFilteredRoles(UserStoreManager userStoreManager, String username) + throws UserStoreException { + String[] roleListOfUser = userStoreManager.getRoleListOfUser(username); + List filteredRoles = new ArrayList(); + for (String role : roleListOfUser) { + if (!(role.startsWith("Internal/") || role.startsWith("Application/"))) { + filteredRoles.add(role); + } + } + return filteredRoles; + } + + /** + * Get user's roles by username + * + * @param username Username of the user + * @return {Response} Status of the request wrapped inside Response object + * @throws MDMAPIException + */ + @GET + @Path("roles") + @Produces({MediaType.APPLICATION_JSON}) + public Response getRoles(@QueryParam("username") String username) throws MDMAPIException { + UserStoreManager userStoreManager = MDMAPIUtils.getUserStoreManager(); + ResponsePayload responsePayload = new ResponsePayload(); + try { + if (userStoreManager.isExistingUser(username)) { + responsePayload.setResponseContent(Arrays.asList(getFilteredRoles(userStoreManager, username))); + // Outputting debug message upon successful removal of user + if (log.isDebugEnabled()) { + log.debug("User by username: " + username + " was successfully removed."); + } + // returning response with success state + responsePayload.setStatusCode(HttpStatus.SC_OK); + responsePayload.setMessageFromServer( + "User roles obtained for user " + username); + return Response.status(HttpStatus.SC_OK).entity(responsePayload).build(); + } else { + // Outputting debug message upon trying to remove non-existing user + if (log.isDebugEnabled()) { + log.debug("User by username: " + username + " does not exist for role retrieval."); + } + // returning response with bad request state + responsePayload.setStatusCode(HttpStatus.SC_BAD_REQUEST); + responsePayload.setMessageFromServer( + "User by username: " + username + " does not exist for role retrieval."); + return Response.status(HttpStatus.SC_BAD_REQUEST).entity(responsePayload).build(); + } + } catch (UserStoreException e) { + String errorMsg = "Exception in trying to retrieve roles for user by username: " + username; + log.error(errorMsg, e); + throw new MDMAPIException(errorMsg, e); + } + } + + /** + * Get the list of all users with all user-related info. + * + * @return A list of users + * @throws MDMAPIException + */ + @GET + @Produces({MediaType.APPLICATION_JSON}) + public Response getAllUsers() throws MDMAPIException { + if (log.isDebugEnabled()) { + log.debug("Getting the list of users with all user-related information"); + } + UserStoreManager userStoreManager = MDMAPIUtils.getUserStoreManager(); + ArrayList userList; + try { + String[] users = userStoreManager.listUsers("*", -1); + userList = new ArrayList(users.length); + UserWrapper user; + for (String username : users) { + user = new UserWrapper(); + user.setUsername(username); + user.setEmailAddress(getClaimValue(username, Constants.USER_CLAIM_EMAIL_ADDRESS)); + user.setFirstname(getClaimValue(username, Constants.USER_CLAIM_FIRST_NAME)); + user.setLastname(getClaimValue(username, Constants.USER_CLAIM_LAST_NAME)); + userList.add(user); + } + } catch (UserStoreException e) { + String msg = "Error occurred while retrieving the list of users"; + log.error(msg, e); + throw new MDMAPIException(msg, e); + } + ResponsePayload responsePayload = new ResponsePayload(); + responsePayload.setStatusCode(HttpStatus.SC_OK); + int count = 0; + if (userList != null) { + count = userList.size(); + } + responsePayload.setMessageFromServer("All users were successfully retrieved. " + + "Obtained user count: " + count); + responsePayload.setResponseContent(userList); + return Response.status(HttpStatus.SC_OK).entity(responsePayload).build(); + } + + /** + * Get the list of all users with all user-related info. + * + * @return A list of users + * @throws MDMAPIException + */ + @GET + @Path("{filter}") + @Produces({MediaType.APPLICATION_JSON}) + public Response getMatchingUsers(@PathParam("filter") String filter) throws MDMAPIException { + if (log.isDebugEnabled()) { + log.debug("Getting the list of users with all user-related information using the filter : " + filter); + } + UserStoreManager userStoreManager = MDMAPIUtils.getUserStoreManager(); + ArrayList userList; + try { + String[] users = userStoreManager.listUsers(filter + "*", -1); + userList = new ArrayList(users.length); + UserWrapper user; + for (String username : users) { + user = new UserWrapper(); + user.setUsername(username); + user.setEmailAddress(getClaimValue(username, Constants.USER_CLAIM_EMAIL_ADDRESS)); + user.setFirstname(getClaimValue(username, Constants.USER_CLAIM_FIRST_NAME)); + user.setLastname(getClaimValue(username, Constants.USER_CLAIM_LAST_NAME)); + userList.add(user); + } + } catch (UserStoreException e) { + String msg = "Error occurred while retrieving the list of users using the filter : " + filter; + log.error(msg, e); + throw new MDMAPIException(msg, e); + } + ResponsePayload responsePayload = new ResponsePayload(); + responsePayload.setStatusCode(HttpStatus.SC_OK); + int count = 0; + if (userList != null) { + count = userList.size(); + } + responsePayload.setMessageFromServer("All users were successfully retrieved. " + + "Obtained user count: " + count); + responsePayload.setResponseContent(userList); + return Response.status(HttpStatus.SC_OK).entity(responsePayload).build(); + } + + /** + * Get the list of user names in the system. + * + * @return A list of user names. + * @throws MDMAPIException + */ + @GET + @Path("view-users") + public Response getAllUsersByUsername(@QueryParam("username") String userName) throws MDMAPIException { + if (log.isDebugEnabled()) { + log.debug("Getting the list of users by name"); + } + UserStoreManager userStoreManager = MDMAPIUtils.getUserStoreManager(); + ArrayList userList; + try { + String[] users = userStoreManager.listUsers("*" + userName + "*", -1); + userList = new ArrayList(users.length); + UserWrapper user; + for (String username : users) { + user = new UserWrapper(); + user.setUsername(username); + user.setEmailAddress(getClaimValue(username, Constants.USER_CLAIM_EMAIL_ADDRESS)); + user.setFirstname(getClaimValue(username, Constants.USER_CLAIM_FIRST_NAME)); + user.setLastname(getClaimValue(username, Constants.USER_CLAIM_LAST_NAME)); + userList.add(user); + } + } catch (UserStoreException e) { + String msg = "Error occurred while retrieving the list of users"; + log.error(msg, e); + throw new MDMAPIException(msg, e); + } + ResponsePayload responsePayload = new ResponsePayload(); + responsePayload.setStatusCode(HttpStatus.SC_OK); + int count = 0; + if (userList != null) { + count = userList.size(); + } + responsePayload.setMessageFromServer("All users by username were successfully retrieved. " + + "Obtained user count: " + count); + responsePayload.setResponseContent(userList); + return Response.status(HttpStatus.SC_OK).entity(responsePayload).build(); + } + + /** + * Get the list of user names in the system. + * + * @return A list of user names. + * @throws MDMAPIException + */ + @GET + @Path("users-by-username") + public Response getAllUserNamesByUsername(@QueryParam("username") String userName) throws MDMAPIException { + if (log.isDebugEnabled()) { + log.debug("Getting the list of users by name"); + } + UserStoreManager userStoreManager = MDMAPIUtils.getUserStoreManager(); + ArrayList userList; + try { + String[] users = userStoreManager.listUsers("*" + userName + "*", -1); + userList = new ArrayList(users.length); + UserWrapper user; + for (String username : users) { + userList.add(username); + } + } catch (UserStoreException e) { + String msg = "Error occurred while retrieving the list of users"; + log.error(msg, e); + throw new MDMAPIException(msg, e); + } + ResponsePayload responsePayload = new ResponsePayload(); + responsePayload.setStatusCode(HttpStatus.SC_OK); + int count = 0; + if (userList != null) { + count = userList.size(); + } + responsePayload.setMessageFromServer("All users by username were successfully retrieved. " + + "Obtained user count: " + count); + responsePayload.setResponseContent(userList); + return Response.status(HttpStatus.SC_OK).entity(responsePayload).build(); + } + + /** + * Gets a claim-value from user-store. + * + * @param username Username of the user + * @param claimUri required ClaimUri + * @return A list of usernames + * @throws MDMAPIException, UserStoreException + */ + private String getClaimValue(String username, String claimUri) throws MDMAPIException, UserStoreException { + UserStoreManager userStoreManager = MDMAPIUtils.getUserStoreManager(); + return userStoreManager.getUserClaimValue(username, claimUri, null); + } + + /** + * Method used to send an invitation email to a new user to enroll a device. + * + * @param username Username of the user + * @throws MDMAPIException, UserStoreException, DeviceManagementException + */ + private void inviteNewlyAddedUserToEnrollDevice(String username, String password) throws + MDMAPIException, + UserStoreException, + DeviceManagementException { + if (log.isDebugEnabled()) { + log.debug("Sending invitation mail to user by username: " + username); + } + String tennentDomain = CarbonContext.getThreadLocalCarbonContext().getTenantDomain(); + if (tennentDomain.equalsIgnoreCase("carbon.super")) { + tennentDomain = ""; + } + if (!username.contains("/")) { + username = "/" + username; + } + String[] usernameBits = username.split("/"); + DeviceManagementProviderService deviceManagementProviderService = MDMAPIUtils.getDeviceManagementService(); + EmailMessageProperties emailMessageProperties = new EmailMessageProperties(); + emailMessageProperties.setUserName(usernameBits[1]); + emailMessageProperties.setDomainName(tennentDomain); + emailMessageProperties.setFirstName(getClaimValue(username, Constants.USER_CLAIM_FIRST_NAME)); + emailMessageProperties.setPassword(password); + String[] mailAddress = new String[1]; + mailAddress[0] = getClaimValue(username, Constants.USER_CLAIM_EMAIL_ADDRESS); + emailMessageProperties.setMailTo(mailAddress); + deviceManagementProviderService.sendRegistrationEmail(emailMessageProperties); + } + + /** + * Method used to send an invitation email to a existing user to enroll a device. + * + * @param usernames Username list of the users to be invited + * @throws MDMAPIException + */ + @POST + @Path("email-invitation") + @Produces({MediaType.APPLICATION_JSON}) + public Response inviteExistingUsersToEnrollDevice(List usernames) throws MDMAPIException { + if (log.isDebugEnabled()) { + log.debug("Sending enrollment invitation mail to existing user."); + } + DeviceManagementProviderService deviceManagementProviderService = MDMAPIUtils.getDeviceManagementService(); + try { + for (int i = 0; i < usernames.size(); i++) { + EmailMessageProperties emailMessageProperties = new EmailMessageProperties(); + emailMessageProperties + .setFirstName(getClaimValue(usernames.get(i), Constants.USER_CLAIM_FIRST_NAME)); + emailMessageProperties.setUserName(usernames.get(i)); + String[] mailAddress = new String[1]; + mailAddress[0] = getClaimValue(usernames.get(i), Constants.USER_CLAIM_EMAIL_ADDRESS); + if (StringUtils.isNotEmpty(mailAddress[0])) { + emailMessageProperties.setMailTo(mailAddress); + deviceManagementProviderService.sendEnrolmentInvitation(emailMessageProperties); + } + } + } catch (UserStoreException e) { + String errorMsg = "Exception in trying to invite user."; + log.error(errorMsg, e); + throw new MDMAPIException(errorMsg, e); + } catch (DeviceManagementException e) { + String errorMsg = "Exception in trying to invite user."; + log.error(errorMsg, e); + throw new MDMAPIException(errorMsg, e); + } + ResponsePayload responsePayload = new ResponsePayload(); + responsePayload.setStatusCode(HttpStatus.SC_OK); + responsePayload.setMessageFromServer("Email invitation was successfully sent to user."); + return Response.status(HttpStatus.SC_OK).entity(responsePayload).build(); + } + + /** + * Get a list of devices based on the username. + * + * @param username Username of the device owner + * @return A list of devices + * @throws MDMAPIException + */ + @GET + @Produces({MediaType.APPLICATION_JSON}) + @Path("devices") + public Object getAllDeviceOfUser(@QueryParam("username") String username, @QueryParam("start") int startIdx, + @QueryParam("length") int length) + throws MDMAPIException { + DeviceManagementProviderService dmService; + try { + dmService = MDMAPIUtils.getDeviceManagementService(); + if (length > 0) { + PaginationRequest request = new PaginationRequest(startIdx, length); + request.setOwner(username); + return dmService.getDevicesOfUser(request); + } + return dmService.getDevicesOfUser(username); + } catch (DeviceManagementException e) { + String errorMsg = "Device management error"; + log.error(errorMsg, e); + throw new MDMAPIException(errorMsg, e); + } + } + + /** + * This method is used to retrieve the user count of the system. + * + * @return returns the count. + * @throws MDMAPIException + */ + @GET + @Path("count") + public int getUserCount() throws MDMAPIException { + try { + String[] users = MDMAPIUtils.getUserStoreManager().listUsers("*", -1); + if (users == null) { + return 0; + } + return users.length; + } catch (UserStoreException e) { + String msg = + "Error occurred while retrieving the list of users that exist within the current tenant"; + log.error(msg, e); + throw new MDMAPIException(msg, e); + } + } + + /** + * API is used to update roles of a user + * + * @param username + * @param userList + * @return + * @throws MDMAPIException + */ + @PUT + @Path("{roleName}/users") + @Produces({MediaType.APPLICATION_JSON}) + public Response updateRoles(@PathParam("username") String username, List userList) + throws MDMAPIException { + final UserStoreManager userStoreManager = MDMAPIUtils.getUserStoreManager(); + try { + if (log.isDebugEnabled()) { + log.debug("Updating the roles of a user"); + } + SetReferenceTransformer transformer = new SetReferenceTransformer(); + transformer.transform(Arrays.asList(userStoreManager.getRoleListOfUser(username)), + userList); + final String[] rolesToAdd = (String[]) + transformer.getObjectsToAdd().toArray(new String[transformer.getObjectsToAdd().size()]); + final String[] rolesToDelete = (String[]) + transformer.getObjectsToRemove().toArray(new String[transformer.getObjectsToRemove().size()]); + + userStoreManager.updateRoleListOfUser(username, rolesToDelete, rolesToAdd); + } catch (UserStoreException e) { + String msg = "Error occurred while saving the roles for user: " + username; + log.error(msg, e); + throw new MDMAPIException(msg, e); + } + return Response.status(HttpStatus.SC_OK).build(); + } + + /** + * Method to change the user password. + * + * @param credentials Wrapper object representing user credentials. + * @return {Response} Status of the request wrapped inside Response object. + * @throws MDMAPIException + */ + @POST + @Path("reset-password") + @Consumes({MediaType.APPLICATION_JSON}) + @Produces({MediaType.APPLICATION_JSON}) + public Response resetPassword(UserCredentialWrapper credentials) throws MDMAPIException { + UserStoreManager userStoreManager = MDMAPIUtils.getUserStoreManager(); + ResponsePayload responsePayload = new ResponsePayload(); + try { + byte[] decodedNewPassword = Base64.decodeBase64(credentials.getNewPassword()); + userStoreManager.updateCredentialByAdmin(credentials.getUsername(), new String( + decodedNewPassword, "UTF-8")); + responsePayload.setStatusCode(HttpStatus.SC_CREATED); + responsePayload.setMessageFromServer("User password by username: " + credentials.getUsername() + + " was successfully changed."); + return Response.status(HttpStatus.SC_CREATED).entity(responsePayload).build(); + } catch (UserStoreException e) { + String errorMsg = "Exception in trying to change the password by username: " + credentials.getUsername(); + log.error(errorMsg, e); + responsePayload.setStatusCode(HttpStatus.SC_BAD_REQUEST); + responsePayload.setMessageFromServer("Old password does not match."); + return Response.status(HttpStatus.SC_BAD_REQUEST).entity(responsePayload).build(); + } catch (UnsupportedEncodingException e) { + String errorMsg = "Exception in trying to change the password by username: " + credentials.getUsername(); + log.error(errorMsg, e); + throw new MDMAPIException(errorMsg, e); + } + } +} diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/api/common/ErrorHandler.java b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/api/common/ErrorHandler.java new file mode 100644 index 0000000000..da8825b177 --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/api/common/ErrorHandler.java @@ -0,0 +1,32 @@ +/* + * 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.wso2.carbon.mdm.api.common; + +import javax.ws.rs.Produces; +import javax.ws.rs.core.Response; +import javax.ws.rs.ext.ExceptionMapper; + +@Produces({ "application/json", "application/xml" }) +public class ErrorHandler implements ExceptionMapper { + + public Response toResponse(MDMAPIException exception) { + ErrorMessage errorMessage = new ErrorMessage(); + errorMessage.setErrorMessage(exception.getErrorMessage()); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(errorMessage).build(); + } +} diff --git a/components/key-mgt/org.wso2.carbon.key.mgt.handler.valve/src/main/java/org/wso2/carbon/key/mgt/handler/valve/APIFaultException.java b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/api/common/ErrorMessage.java similarity index 63% rename from components/key-mgt/org.wso2.carbon.key.mgt.handler.valve/src/main/java/org/wso2/carbon/key/mgt/handler/valve/APIFaultException.java rename to components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/api/common/ErrorMessage.java index f04a64f998..7dbe395532 100644 --- a/components/key-mgt/org.wso2.carbon.key.mgt.handler.valve/src/main/java/org/wso2/carbon/key/mgt/handler/valve/APIFaultException.java +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/api/common/ErrorMessage.java @@ -15,26 +15,27 @@ * specific language governing permissions and limitations * under the License. */ +package org.wso2.carbon.mdm.api.common; -package org.wso2.carbon.key.mgt.handler.valve; -public class APIFaultException extends Exception { +public class ErrorMessage { - private static final long serialVersionUID = 1L; - private int errorCode; + private String errorMessage; + private String errorCode; - public APIFaultException(int errorCode, String message) { - super(message); - this.errorCode = errorCode; + public String getErrorMessage() { + return errorMessage; } - public APIFaultException(int errorCode, String message, Throwable cause) { - super(message, cause); - this.errorCode = errorCode; + public void setErrorMessage(String errorMessage) { + this.errorMessage = errorMessage; } - public int getErrorCode() { + public String getErrorCode() { return errorCode; } + public void setErrorCode(String errorCode) { + this.errorCode = errorCode; + } } diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/api/common/MDMAPIException.java b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/api/common/MDMAPIException.java new file mode 100644 index 0000000000..4139e56f46 --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/api/common/MDMAPIException.java @@ -0,0 +1,59 @@ +/* + * 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.wso2.carbon.mdm.api.common; + +/** + * Custom exception class for handling CDM API related exceptions. + */ +public class MDMAPIException extends Exception { + + private static final long serialVersionUID = 7950151650447893900L; + private String errorMessage; + + public String getErrorMessage() { + return errorMessage; + } + + public void setErrorMessage(String errorMessage) { + this.errorMessage = errorMessage; + } + + public MDMAPIException(String msg, Exception e) { + super(msg, e); + setErrorMessage(msg); + } + + public MDMAPIException(String msg, Throwable cause) { + super(msg, cause); + setErrorMessage(msg); + } + + public MDMAPIException(String msg) { + super(msg); + setErrorMessage(msg); + } + + public MDMAPIException() { + super(); + } + + public MDMAPIException(Throwable cause) { + super(cause); + } +} diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/api/context/DeviceOperationContext.java b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/api/context/DeviceOperationContext.java new file mode 100644 index 0000000000..aef30c70f2 --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/api/context/DeviceOperationContext.java @@ -0,0 +1,54 @@ +/* + * 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.wso2.carbon.mdm.api.context; + +import org.wso2.carbon.device.mgt.common.DeviceIdentifier; +import org.wso2.carbon.device.mgt.common.operation.mgt.Operation; + +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; +import java.util.List; + +/** + * Model object of DeviceOperation. + */ +@XmlRootElement +public class DeviceOperationContext { + + private List devices; + private Operation operation; + + @XmlElement + public List getDevices() { + return devices; + } + + public void setDevices(List devices) { + this.devices = devices; + } + + @XmlElement + public Operation getOperation() { + return operation; + } + + public void setOperation(Operation operation) { + this.operation = operation; + } +} \ No newline at end of file diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/api/util/MDMAPIUtils.java b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/api/util/MDMAPIUtils.java new file mode 100644 index 0000000000..0c474818a3 --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/api/util/MDMAPIUtils.java @@ -0,0 +1,255 @@ +/* + * Copyright (c) 2016, 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.wso2.carbon.mdm.api.util; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.context.CarbonContext; +import org.wso2.carbon.context.PrivilegedCarbonContext; +import org.wso2.carbon.device.mgt.common.DeviceIdentifier; +import org.wso2.carbon.device.mgt.common.PaginationResult; +import org.wso2.carbon.device.mgt.common.configuration.mgt.ConfigurationEntry; +import org.wso2.carbon.device.mgt.common.configuration.mgt.TenantConfiguration; +import org.wso2.carbon.device.mgt.common.configuration.mgt.TenantConfigurationManagementService; +import org.wso2.carbon.device.mgt.common.notification.mgt.NotificationManagementService; +import org.wso2.carbon.device.mgt.core.app.mgt.ApplicationManagementProviderService; +import org.wso2.carbon.device.mgt.core.service.DeviceManagementProviderService; +import org.wso2.carbon.mdm.api.common.MDMAPIException; +import org.wso2.carbon.ntask.core.TaskManager; +import org.wso2.carbon.policy.mgt.common.PolicyMonitoringTaskException; +import org.wso2.carbon.policy.mgt.core.PolicyManagerService; +import org.wso2.carbon.policy.mgt.core.task.TaskScheduleService; +import org.wso2.carbon.policy.mgt.core.util.PolicyManagementConstants; +import org.wso2.carbon.user.api.AuthorizationManager; +import org.wso2.carbon.user.api.UserRealm; +import org.wso2.carbon.user.api.UserStoreException; +import org.wso2.carbon.user.api.UserStoreManager; +import org.wso2.carbon.user.core.service.RealmService; +import org.wso2.carbon.utils.multitenancy.MultitenantConstants; + +import java.util.List; + +/** + * MDMAPIUtils class provides utility function used by CDM REST-API classes. + */ +public class MDMAPIUtils { + + private static final String NOTIFIER_FREQUENCY = "notifierFrequency"; + + private static Log log = LogFactory.getLog(MDMAPIUtils.class); + + public static int getNotifierFrequency(TenantConfiguration tenantConfiguration) { + List configEntryList = tenantConfiguration.getConfiguration(); + if (configEntryList != null && !configEntryList.isEmpty()) { + for(ConfigurationEntry entry : configEntryList) { + if (NOTIFIER_FREQUENCY.equals(entry.getName())) { + return Integer.parseInt((String) entry.getValue()); + } + } + } + return 0; + } + + public static void scheduleTaskService(int notifierFrequency) { + TaskScheduleService taskScheduleService; + try { + taskScheduleService = getPolicyManagementService().getTaskScheduleService(); + if (taskScheduleService.isTaskScheduled()) { + taskScheduleService.updateTask(notifierFrequency); + } else { + taskScheduleService.startTask(notifierFrequency); + } + } catch (PolicyMonitoringTaskException e) { + log.error("Exception occurred while starting the Task service.", e); + } + } + + public static DeviceManagementProviderService getDeviceManagementService() { + PrivilegedCarbonContext ctx = PrivilegedCarbonContext.getThreadLocalCarbonContext(); + DeviceManagementProviderService deviceManagementProviderService = + (DeviceManagementProviderService) ctx.getOSGiService(DeviceManagementProviderService.class, null); + if (deviceManagementProviderService == null) { + String msg = "Device Management provider service has not initialized."; + log.error(msg); + throw new IllegalStateException(msg); + } + return deviceManagementProviderService; + } + + public static int getTenantId(String tenantDomain) throws MDMAPIException { + PrivilegedCarbonContext ctx = PrivilegedCarbonContext.getThreadLocalCarbonContext(); + RealmService realmService = (RealmService) ctx.getOSGiService(RealmService.class, null); + try { + return realmService.getTenantManager().getTenantId(tenantDomain); + } catch (UserStoreException e) { + throw new MDMAPIException( + "Error obtaining tenant id from tenant domain " + tenantDomain); + } + } + + public static UserStoreManager getUserStoreManager() throws MDMAPIException { + RealmService realmService; + UserStoreManager userStoreManager; + try { + PrivilegedCarbonContext ctx = PrivilegedCarbonContext.getThreadLocalCarbonContext(); + realmService = (RealmService) ctx.getOSGiService(RealmService.class, null); + if (realmService == null) { + String msg = "Realm service has not initialized."; + log.error(msg); + throw new IllegalStateException(msg); + } + int tenantId = ctx.getTenantId(); + userStoreManager = realmService.getTenantUserRealm(tenantId).getUserStoreManager(); + } catch (UserStoreException e) { + String msg = "Error occurred while retrieving current user store manager"; + log.error(msg, e); + throw new MDMAPIException(msg, e); + } + return userStoreManager; + } + + /** + * Getting the current tenant's user realm + * + * @return + * @throws MDMAPIException + */ + public static UserRealm getUserRealm() throws MDMAPIException { + RealmService realmService; + UserRealm realm; + try { + //PrivilegedCarbonContext.startTenantFlow(); + PrivilegedCarbonContext ctx = PrivilegedCarbonContext.getThreadLocalCarbonContext(); + //ctx.setTenantDomain(MultitenantConstants.SUPER_TENANT_DOMAIN_NAME); + //ctx.setTenantId(MultitenantConstants.SUPER_TENANT_ID); + realmService = (RealmService) ctx.getOSGiService(RealmService.class, null); + + if (realmService == null) { + String msg = "Realm service not initialized"; + log.error(msg); + throw new MDMAPIException(msg); + } + int tenantId = CarbonContext.getThreadLocalCarbonContext().getTenantId(); + realm = realmService.getTenantUserRealm(tenantId); + } catch (UserStoreException e) { + String msg = "Error occurred while retrieving current user realm"; + log.error(msg, e); + throw new MDMAPIException(msg, e); + } finally { + //PrivilegedCarbonContext.endTenantFlow(); + } + return realm; + } + + public static AuthorizationManager getAuthorizationManager() throws MDMAPIException { + RealmService realmService; + AuthorizationManager authorizationManager; + try { + PrivilegedCarbonContext ctx = PrivilegedCarbonContext.getThreadLocalCarbonContext(); + realmService = (RealmService) ctx.getOSGiService(RealmService.class, null); + if (realmService == null) { + String msg = "Realm service has not initialized."; + log.error(msg); + throw new IllegalStateException(msg); + } + int tenantId = ctx.getTenantId(); + authorizationManager = realmService.getTenantUserRealm(tenantId).getAuthorizationManager(); + } catch (UserStoreException e) { + String msg = "Error occurred while retrieving current Authorization manager."; + log.error(msg, e); + throw new MDMAPIException(msg, e); + } + return authorizationManager; + } + + /** + * This method is used to get the current tenant id. + * + * @return returns the tenant id. + */ + public static int getTenantId() { + return CarbonContext.getThreadLocalCarbonContext().getTenantId(); + } + + public static DeviceIdentifier instantiateDeviceIdentifier(String deviceType, String deviceId) { + DeviceIdentifier deviceIdentifier = new DeviceIdentifier(); + deviceIdentifier.setType(deviceType); + deviceIdentifier.setId(deviceId); + return deviceIdentifier; + } + + public static ApplicationManagementProviderService getAppManagementService() { + PrivilegedCarbonContext ctx = PrivilegedCarbonContext.getThreadLocalCarbonContext(); + ApplicationManagementProviderService applicationManagementProviderService = + (ApplicationManagementProviderService) ctx.getOSGiService(ApplicationManagementProviderService.class, null); + if (applicationManagementProviderService == null) { + String msg = "Application management service has not initialized."; + log.error(msg); + throw new IllegalStateException(msg); + } + return applicationManagementProviderService; + } + + public static PolicyManagerService getPolicyManagementService() { + PolicyManagerService policyManagementService; + PrivilegedCarbonContext ctx = PrivilegedCarbonContext.getThreadLocalCarbonContext(); + policyManagementService = + (PolicyManagerService) ctx.getOSGiService(PolicyManagerService.class, null); + if (policyManagementService == null) { + String msg = "Policy Management service not initialized."; + log.error(msg); + throw new IllegalStateException(msg); + } + return policyManagementService; + } + + public static TenantConfigurationManagementService getTenantConfigurationManagementService() { + PrivilegedCarbonContext ctx = PrivilegedCarbonContext.getThreadLocalCarbonContext(); + TenantConfigurationManagementService tenantConfigurationManagementService = + (TenantConfigurationManagementService) ctx.getOSGiService(TenantConfigurationManagementService.class, null); + if (tenantConfigurationManagementService == null) { + String msg = "Tenant configuration Management service not initialized."; + log.error(msg); + throw new IllegalStateException(msg); + } + return tenantConfigurationManagementService; + } + + public static NotificationManagementService getNotificationManagementService() { + NotificationManagementService notificationManagementService; + PrivilegedCarbonContext ctx = PrivilegedCarbonContext.getThreadLocalCarbonContext(); + notificationManagementService = (NotificationManagementService) ctx.getOSGiService( + NotificationManagementService.class, null); + if (notificationManagementService == null) { + String msg = "Notification Management service not initialized."; + log.error(msg); + throw new IllegalStateException(msg); + } + return notificationManagementService; + } + + public static PaginationResult getPagingResponse(int recordsTotal, int recordsFiltered, int draw, List data) { + PaginationResult pagingResponse = new PaginationResult(); + pagingResponse.setRecordsTotal(recordsTotal); + pagingResponse.setRecordsFiltered(recordsFiltered); + pagingResponse.setDraw(draw); + pagingResponse.setData(data); + return pagingResponse; + } +} diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/api/util/MDMAndroidOperationUtil.java b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/api/util/MDMAndroidOperationUtil.java new file mode 100644 index 0000000000..e50fc0bde6 --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/api/util/MDMAndroidOperationUtil.java @@ -0,0 +1,116 @@ +/* + * 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.wso2.carbon.mdm.api.util; + +import org.wso2.carbon.device.mgt.common.operation.mgt.Operation; +import org.wso2.carbon.device.mgt.core.operation.mgt.ProfileOperation; +import org.wso2.carbon.mdm.api.common.MDMAPIException; +import org.wso2.carbon.mdm.beans.MobileApp; +import org.wso2.carbon.mdm.beans.android.WebApplication; + +/** + * + * This class contains the all the operations related to Android. + */ +public class MDMAndroidOperationUtil { + + /** + * This method is used to create Install Application operation. + * + * @param application MobileApp application + * @return operation + * @throws MDMAPIException + * + */ + public static Operation createInstallAppOperation(MobileApp application) throws MDMAPIException { + + ProfileOperation operation = new ProfileOperation(); + operation.setCode(MDMAppConstants.AndroidConstants.OPCODE_INSTALL_APPLICATION); + operation.setType(Operation.Type.PROFILE); + + switch (application.getType()) { + case ENTERPRISE: + org.wso2.carbon.mdm.beans.android.EnterpriseApplication enterpriseApplication = + new org.wso2.carbon.mdm.beans.android.EnterpriseApplication(); + enterpriseApplication.setType(application.getType().toString()); + enterpriseApplication.setUrl(application.getLocation()); + operation.setPayLoad(enterpriseApplication.toJSON()); + break; + case PUBLIC: + org.wso2.carbon.mdm.beans.android.AppStoreApplication appStoreApplication = + new org.wso2.carbon.mdm.beans.android.AppStoreApplication(); + appStoreApplication.setType(application.getType().toString()); + appStoreApplication.setAppIdentifier(application.getIdentifier()); + operation.setPayLoad(appStoreApplication.toJSON()); + break; + case WEBAPP: + WebApplication webApplication = new WebApplication(); + webApplication.setUrl(application.getLocation()); + webApplication.setName(application.getName()); + webApplication.setType(application.getType().toString()); + operation.setPayLoad(webApplication.toJSON()); + break; + default: + String errorMessage = "Invalid application type."; + throw new MDMAPIException(errorMessage); + } + return operation; + } + + /** + * This method is used to create Uninstall Application operation. + * @param application MobileApp application + * @return operation + * @throws MDMAPIException + */ + public static Operation createAppUninstallOperation(MobileApp application) throws MDMAPIException { + + ProfileOperation operation = new ProfileOperation(); + operation.setCode(MDMAppConstants.AndroidConstants.OPCODE_UNINSTALL_APPLICATION); + operation.setType(Operation.Type.PROFILE); + + switch (application.getType()) { + case ENTERPRISE: + org.wso2.carbon.mdm.beans.android.EnterpriseApplication enterpriseApplication = + new org.wso2.carbon.mdm.beans.android.EnterpriseApplication(); + enterpriseApplication.setType(application.getType().toString()); + enterpriseApplication.setAppIdentifier(application.getAppIdentifier()); + operation.setPayLoad(enterpriseApplication.toJSON()); + break; + case PUBLIC: + org.wso2.carbon.mdm.beans.android.AppStoreApplication appStoreApplication = + new org.wso2.carbon.mdm.beans.android.AppStoreApplication(); + appStoreApplication.setType(application.getType().toString()); + appStoreApplication.setAppIdentifier(application.getAppIdentifier()); + operation.setPayLoad(appStoreApplication.toJSON()); + break; + case WEBAPP: + WebApplication webApplication = new WebApplication(); + webApplication.setUrl(application.getLocation()); + webApplication.setName(application.getName()); + webApplication.setType(application.getType().toString()); + operation.setPayLoad(webApplication.toJSON()); + break; + default: + String errorMessage = "Invalid application type."; + throw new MDMAPIException(errorMessage); + } + return operation; + } + +} diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/api/util/MDMAppConstants.java b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/api/util/MDMAppConstants.java new file mode 100644 index 0000000000..a4a7f60714 --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/api/util/MDMAppConstants.java @@ -0,0 +1,55 @@ +/* + * + * 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. + */ +package org.wso2.carbon.mdm.api.util; + +/** + * This class holds all the constants used for IOS and Android. + */ +public class MDMAppConstants { + + public class IOSConstants { + + private IOSConstants() { + throw new AssertionError(); + } + public static final String IS_REMOVE_APP = "isRemoveApp"; + public static final String IS_PREVENT_BACKUP = "isPreventBackup"; + public static final String I_TUNES_ID = "iTunesId"; + public static final String LABEL = "label"; + public static final String OPCODE_INSTALL_ENTERPRISE_APPLICATION = "INSTALL_ENTERPRISE_APPLICATION"; + public static final String OPCODE_INSTALL_STORE_APPLICATION = "INSTALL_STORE_APPLICATION"; + public static final String OPCODE_INSTALL_WEB_APPLICATION = "WEB_CLIP"; + public static final String OPCODE_REMOVE_APPLICATION = "REMOVE_APPLICATION"; + } + + public class AndroidConstants { + + private AndroidConstants() { + throw new AssertionError(); + } + public static final String OPCODE_INSTALL_APPLICATION = "INSTALL_APPLICATION"; + public static final String OPCODE_UNINSTALL_APPLICATION = "UNINSTALL_APPLICATION"; + } + + public class RegistryConstants { + + private RegistryConstants() { + throw new AssertionError(); + } + public static final String GENERAL_CONFIG_RESOURCE_PATH = "general"; + } +} diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/api/util/MDMIOSOperationUtil.java b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/api/util/MDMIOSOperationUtil.java new file mode 100644 index 0000000000..261555d335 --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/api/util/MDMIOSOperationUtil.java @@ -0,0 +1,106 @@ +/* + * + * 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. + */ +package org.wso2.carbon.mdm.api.util; + +import org.wso2.carbon.device.mgt.common.operation.mgt.Operation; +import org.wso2.carbon.device.mgt.core.operation.mgt.ProfileOperation; +import org.wso2.carbon.mdm.api.common.MDMAPIException; +import org.wso2.carbon.mdm.beans.MobileApp; +import org.wso2.carbon.mdm.beans.ios.WebClip; + +import java.util.Properties; + +/** + * This class contains the all the operations related to IOS. + */ +public class MDMIOSOperationUtil { + + /** + * This method is used to create Install Application operation. + * + * @param application MobileApp application + * @return operation + * @throws MDMAPIException + * + */ + public static Operation createInstallAppOperation(MobileApp application) throws MDMAPIException { + + ProfileOperation operation = new ProfileOperation(); + + switch (application.getType()) { + case ENTERPRISE: + org.wso2.carbon.mdm.beans.ios.EnterpriseApplication enterpriseApplication = + new org.wso2.carbon.mdm.beans.ios.EnterpriseApplication(); + enterpriseApplication.setBundleId(application.getId()); + enterpriseApplication.setIdentifier(application.getIdentifier()); + enterpriseApplication.setManifestURL(application.getLocation()); + + Properties properties = application.getProperties(); + enterpriseApplication.setPreventBackupOfAppData((Boolean) properties. + get(MDMAppConstants.IOSConstants.IS_PREVENT_BACKUP)); + enterpriseApplication.setRemoveAppUponMDMProfileRemoval((Boolean) properties. + get(MDMAppConstants.IOSConstants.IS_REMOVE_APP)); + operation.setCode(MDMAppConstants.IOSConstants.OPCODE_INSTALL_ENTERPRISE_APPLICATION); + operation.setPayLoad(enterpriseApplication.toJSON()); + operation.setType(Operation.Type.COMMAND); + break; + case PUBLIC: + org.wso2.carbon.mdm.beans.ios.AppStoreApplication appStoreApplication = + new org.wso2.carbon.mdm.beans.ios.AppStoreApplication(); + appStoreApplication.setRemoveAppUponMDMProfileRemoval((Boolean) application.getProperties(). + get(MDMAppConstants.IOSConstants.IS_REMOVE_APP)); + appStoreApplication.setIdentifier(application.getIdentifier()); + appStoreApplication.setPreventBackupOfAppData((Boolean) application.getProperties(). + get(MDMAppConstants.IOSConstants.IS_PREVENT_BACKUP)); + appStoreApplication.setBundleId(application.getId()); + appStoreApplication.setiTunesStoreID((Integer) application.getProperties(). + get(MDMAppConstants.IOSConstants.I_TUNES_ID)); + operation.setCode(MDMAppConstants.IOSConstants.OPCODE_INSTALL_STORE_APPLICATION); + operation.setType(Operation.Type.COMMAND); + operation.setPayLoad(appStoreApplication.toJSON()); + break; + case WEBAPP: + WebClip webClip = new WebClip(); + webClip.setIcon(application.getIconImage()); + webClip.setIsRemovable(application.getProperties(). + getProperty(MDMAppConstants.IOSConstants.IS_REMOVE_APP)); + webClip.setLabel(application.getProperties(). + getProperty(MDMAppConstants.IOSConstants.LABEL)); + webClip.setURL(application.getLocation()); + + operation.setCode(MDMAppConstants.IOSConstants.OPCODE_INSTALL_WEB_APPLICATION); + operation.setType(Operation.Type.PROFILE); + operation.setPayLoad(webClip.toJSON()); + break; + } + return operation; + } + + public static Operation createAppUninstallOperation(MobileApp application) throws MDMAPIException{ + + ProfileOperation operation = new ProfileOperation(); + operation.setCode(MDMAppConstants.IOSConstants.OPCODE_REMOVE_APPLICATION); + operation.setType(Operation.Type.PROFILE); + + org.wso2.carbon.mdm.beans.ios.RemoveApplication removeApplication = + new org.wso2.carbon.mdm.beans.ios.RemoveApplication(); + removeApplication.setBundleId(application.getIdentifier()); + operation.setPayLoad(removeApplication.toJSON()); + + return operation; + } +} diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/api/util/ResponsePayload.java b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/api/util/ResponsePayload.java new file mode 100644 index 0000000000..8cc7d3591a --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/api/util/ResponsePayload.java @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2016, 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.wso2.carbon.mdm.api.util; + +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; + +@XmlRootElement +public class ResponsePayload { + + private int statusCode; + private String messageFromServer; + private Object responseContent; + + @XmlElement + public int getStatusCode() { + return statusCode; + } + + public void setStatusCode(int statusCode) { + this.statusCode = statusCode; + } + + @XmlElement + public String getMessageFromServer() { + return messageFromServer; + } + + public void setMessageFromServer(String messageFromServer) { + this.messageFromServer = messageFromServer; + } + + @XmlElement + public Object getResponseContent() { + return responseContent; + } + + public void setResponseContent(Object responseContent) { + this.responseContent = responseContent; + } + + private ResponsePayload.ResponsePayloadBuilder getBuilder() { + return new ResponsePayload.ResponsePayloadBuilder(); + } + + public static ResponsePayload.ResponsePayloadBuilder statusCode(int statusCode) { + ResponsePayload message = new ResponsePayload(); + return message.getBuilder().statusCode(statusCode); + } + + public static ResponsePayload.ResponsePayloadBuilder messageFromServer(String messageFromServer) { + ResponsePayload message = new ResponsePayload(); + return message.getBuilder().messageFromServer(messageFromServer); + } + + public static ResponsePayload.ResponsePayloadBuilder responseContent(String responseContent) { + ResponsePayload message = new ResponsePayload(); + return message.getBuilder().responseContent(responseContent); + } + + public class ResponsePayloadBuilder { + + private int statusCode; + private String messageFromServer; + private Object responseContent; + + public ResponsePayloadBuilder statusCode(int statusCode) { + this.statusCode = statusCode; + return this; + } + + public ResponsePayloadBuilder messageFromServer(String messageFromServer) { + this.messageFromServer = messageFromServer; + return this; + } + + public ResponsePayloadBuilder responseContent(String responseContent) { + this.responseContent = responseContent; + return this; + } + + public ResponsePayload build() { + ResponsePayload payload = new ResponsePayload(); + payload.setStatusCode(statusCode); + payload.setMessageFromServer(messageFromServer); + payload.setResponseContent(responseContent); + return payload; + } + } + +} diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/beans/ApplicationWrapper.java b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/beans/ApplicationWrapper.java new file mode 100644 index 0000000000..ad2db850d0 --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/beans/ApplicationWrapper.java @@ -0,0 +1,63 @@ +/* + * + * 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.wso2.carbon.mdm.beans; + +import org.wso2.carbon.device.mgt.common.DeviceIdentifier; +import java.util.List; + +public class ApplicationWrapper { + + private List userNameList; + private List roleNameList; + private List deviceIdentifiers; + private MobileApp application; + + public MobileApp getApplication() { + return application; + } + + public void setApplication(MobileApp application) { + this.application = application; + } + public List getUserNameList() { + return userNameList; + } + + public void setUserNameList(List userNameList) { + this.userNameList = userNameList; + } + + public List getRoleNameList() { + return roleNameList; + } + + public void setRoleNameList(List roleNameList) { + this.roleNameList = roleNameList; + } + + public List getDeviceIdentifiers() { + return deviceIdentifiers; + } + + public void setDeviceIdentifiers(List deviceIdentifiers) { + this.deviceIdentifiers = deviceIdentifiers; + } + +} diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/beans/MobileApp.java b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/beans/MobileApp.java new file mode 100644 index 0000000000..2f1ef19b3f --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/beans/MobileApp.java @@ -0,0 +1,130 @@ +/* + * 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.wso2.carbon.mdm.beans; + +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; + +/** + * This class represents the generic mobile Application information + * which is used by AppM. + */ +public class MobileApp { + + private String id; + private String name; + private MobileAppTypes type; + private String platform; + private String version; + private String identifier; + private String iconImage; + private String packageName; + private String appIdentifier; + private String location; + private Properties properties; + + public MobileAppTypes getType() { + return type; + } + + public void setType(MobileAppTypes type) { + this.type = type; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getPlatform() { + return platform; + } + + public void setPlatform(String platform) { + this.platform = platform; + } + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + + public String getIdentifier() { + return identifier; + } + + public void setIdentifier(String identifier) { + this.identifier = identifier; + } + + public String getIconImage() { + return iconImage; + } + + public void setIconImage(String iconImage) { + this.iconImage = iconImage; + } + + public String getPackageName() { + return packageName; + } + + public void setPackageName(String packageName) { + this.packageName = packageName; + } + + public String getAppIdentifier() { + return appIdentifier; + } + + public void setAppIdentifier(String appIdentifier) { + this.appIdentifier = appIdentifier; + } + + public String getLocation() { + return location; + } + + public void setLocation(String location) { + this.location = location; + } + + public Properties getProperties() { + return properties; + } + + public void setProperties(Properties properties) { + this.properties = properties; + } + +} diff --git a/components/key-mgt/org.wso2.carbon.key.mgt.handler.valve/src/main/java/org/wso2/carbon/key/mgt/handler/valve/HandlerConstants.java b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/beans/MobileAppTypes.java similarity index 69% rename from components/key-mgt/org.wso2.carbon.key.mgt.handler.valve/src/main/java/org/wso2/carbon/key/mgt/handler/valve/HandlerConstants.java rename to components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/beans/MobileAppTypes.java index dac1646594..151f57c5f9 100644 --- a/components/key-mgt/org.wso2.carbon.key.mgt.handler.valve/src/main/java/org/wso2/carbon/key/mgt/handler/valve/HandlerConstants.java +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/beans/MobileAppTypes.java @@ -15,14 +15,8 @@ * specific language governing permissions and limitations * under the License. */ +package org.wso2.carbon.mdm.beans; -package org.wso2.carbon.key.mgt.handler.valve; - -public class HandlerConstants { - - public static final String HEADER_AUTHORIZATION = "Authorization"; - public static final String TOKEN_NAME_BEARER = "Bearer"; - - public static final String NO_MATCHING_AUTH_SCHEME = "noMatchedAuthScheme"; - +public enum MobileAppTypes { + ENTERPRISE,WEBAPP,PUBLIC } diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/beans/PolicyWrapper.java b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/beans/PolicyWrapper.java new file mode 100644 index 0000000000..931f98a867 --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/beans/PolicyWrapper.java @@ -0,0 +1,126 @@ +/* + * 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.wso2.carbon.mdm.beans; + +import org.wso2.carbon.device.mgt.common.Device; +import java.util.List; + +public class PolicyWrapper { + + private int id; + private Profile profile; + private String policyName; + private String description; + private String compliance; + private List roles; + private String ownershipType; + private List devices; + private List users; + private int tenantId; + private int profileId; + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public Profile getProfile() { + return profile; + } + + public void setProfile(Profile profile) { + this.profile = profile; + } + + public String getCompliance() { + return compliance; + } + + public void setCompliance(String compliance) { + this.compliance = compliance; + } + + public String getPolicyName() { + return policyName; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public void setPolicyName(String policyName) { + this.policyName = policyName; + } + + public List getRoles() { + return roles; + } + + public void setRoles(List roles) { + this.roles = roles; + } + + public String getOwnershipType() { + return ownershipType; + } + + public void setOwnershipType(String ownershipType) { + this.ownershipType = ownershipType; + } + + public List getDevices() { + return devices; + } + + public void setDevices(List devices) { + this.devices = devices; + } + + public List getUsers() { + return users; + } + + public void setUsers(List users) { + this.users = users; + } + + public int getTenantId() { + return tenantId; + } + + public void setTenantId(int tenantId) { + this.tenantId = tenantId; + } + + public int getProfileId() { + return profileId; + } + + public void setProfileId(int profileId) { + this.profileId = profileId; + } + +} diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/beans/PriorityUpdatedPolicyWrapper.java b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/beans/PriorityUpdatedPolicyWrapper.java new file mode 100644 index 0000000000..ed0b7c56e7 --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/beans/PriorityUpdatedPolicyWrapper.java @@ -0,0 +1,41 @@ +/* + * 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.wso2.carbon.mdm.beans; + +public class PriorityUpdatedPolicyWrapper { + + private int id; + private int priority; + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public int getPriority() { + return priority; + } + + public void setPriority(int priority) { + this.priority = priority; + } +} diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/beans/Profile.java b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/beans/Profile.java new file mode 100644 index 0000000000..19e41d8852 --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/beans/Profile.java @@ -0,0 +1,107 @@ +/* +* 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.wso2.carbon.mdm.beans; + + + +import org.wso2.carbon.device.mgt.core.dto.DeviceType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; +import java.sql.Timestamp; +import java.util.List; + +@XmlRootElement +public class Profile { + + private int profileId; + private String profileName; + private int tenantId; + private DeviceType deviceType; + private Timestamp createdDate; + private Timestamp updatedDate; + private List profileFeaturesList; // Features included in the policies. + + public DeviceType getDeviceType() { + return deviceType; + } + + public void setDeviceType(DeviceType deviceType) { + this.deviceType = deviceType; + } + @XmlElement + public int getTenantId() { + return tenantId; + } + + public void setTenantId(int tenantId) { + this.tenantId = tenantId; + } + +/* public List getFeaturesList() { + return featuresList; + } + + public void setFeaturesList(List featuresList) { + this.featuresList = featuresList; + }*/ + @XmlElement + public int getProfileId() { + return profileId; + } + + public void setProfileId(int profileId) { + this.profileId = profileId; + } + + @XmlElement + public String getProfileName() { + return profileName; + } + + public void setProfileName(String profileName) { + this.profileName = profileName; + } + + @XmlElement + public Timestamp getCreatedDate() { + return createdDate; + } + + public void setCreatedDate(Timestamp createdDate) { + this.createdDate = createdDate; + } + + @XmlElement + public Timestamp getUpdatedDate() { + return updatedDate; + } + + public void setUpdatedDate(Timestamp updatedDate) { + this.updatedDate = updatedDate; + } + + @XmlElement + public List getProfileFeaturesList() { + return profileFeaturesList; + } + + public void setProfileFeaturesList(List profileFeaturesList) { + this.profileFeaturesList = profileFeaturesList; + } +} diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/beans/ProfileFeature.java b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/beans/ProfileFeature.java new file mode 100644 index 0000000000..0d56de0900 --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/beans/ProfileFeature.java @@ -0,0 +1,85 @@ +/* +* 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.wso2.carbon.mdm.beans; + +import com.google.gson.Gson; +import java.io.Serializable; +import java.util.LinkedHashMap; + +public class ProfileFeature implements Serializable { + + private int id; + private String featureCode; + private int profileId; + private int deviceTypeId; + private Object content; + private String payLoad; + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getFeatureCode() { + return featureCode; + } + + public void setFeatureCode(String featureCode) { + this.featureCode = featureCode; + } + + public int getProfileId() { + return profileId; + } + + public void setProfileId(int profileId) { + this.profileId = profileId; + } + + public int getDeviceTypeId() { + return deviceTypeId; + } + + public void setDeviceTypeId(int deviceTypeId) { + this.deviceTypeId = deviceTypeId; + } + + + public String getPayLoad() { + Gson gson = new Gson(); + this.payLoad = gson.toJson(content); + return payLoad; + } + + public void setPayLoad(String payLoad) { + this.payLoad = payLoad; + } + + + public Object getContent() { + return content; + } + + public void setContent(Object content) { + this.content = content; + } +} diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/beans/RoleWrapper.java b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/beans/RoleWrapper.java new file mode 100644 index 0000000000..c87640a7c3 --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/beans/RoleWrapper.java @@ -0,0 +1,59 @@ +package org.wso2.carbon.mdm.beans; + +import org.wso2.carbon.user.mgt.common.UIPermissionNode; + +/* + * 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. + */ +public class RoleWrapper { + private String roleName; + private String[] permissions; + private String[] users; + private UIPermissionNode permissionList; + + public String getRoleName() { + return roleName; + } + + public void setRoleName(String roleName) { + this.roleName = roleName; + } + + public String[] getPermissions() { + return permissions; + } + + public void setPermissions(String[] permissions) { + this.permissions = permissions; + } + + public String[] getUsers() { + return users; + } + + public void setUsers(String[] users) { + this.users = users; + } + + public UIPermissionNode getPermissionList() { + return permissionList; + } + + public void setPermissionList(UIPermissionNode permissionList) { + this.permissionList = permissionList; + } +} diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/beans/UserCredentialWrapper.java b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/beans/UserCredentialWrapper.java new file mode 100644 index 0000000000..91e2fd0ff7 --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/beans/UserCredentialWrapper.java @@ -0,0 +1,53 @@ +/* + * 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.wso2.carbon.mdm.beans; + +public class UserCredentialWrapper { + + private String username; + /* + Base64 encoded password + */ + private String oldPassword; + private String newPassword; + + public String getNewPassword() { + return newPassword; + } + + public void setNewPassword(String newPassword) { + this.newPassword = newPassword; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getOldPassword() { + return oldPassword; + } + + public void setOldPassword(String oldPassword) { + this.oldPassword = oldPassword; + } +} diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/beans/UserWrapper.java b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/beans/UserWrapper.java new file mode 100644 index 0000000000..0e4778d23e --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/beans/UserWrapper.java @@ -0,0 +1,87 @@ +/* + * 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.wso2.carbon.mdm.beans; + +public class UserWrapper { + + private String username; + /* + Base64 encoded password + */ + private String password; + private String firstname; + private String lastname; + private String emailAddress; + private String[] roles; + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getFirstname() { + return firstname; + } + + public void setFirstname(String firstname) { + this.firstname = firstname; + } + + public String getLastname() { + return lastname; + } + + public void setLastname(String lastname) { + this.lastname = lastname; + } + + public String getEmailAddress() { + return emailAddress; + } + + public void setEmailAddress(String emailAddress) { + this.emailAddress = emailAddress; + } + + /* + Giving a clone of the array since arrays are mutable + */ + public String[] getRoles() { + String[] copiedRoles = roles; + if (roles != null){ + copiedRoles = roles.clone(); + } + return copiedRoles; + } + + public void setRoles(String[] roles) { + this.roles = roles; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } +} diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/beans/android/AppStoreApplication.java b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/beans/android/AppStoreApplication.java new file mode 100644 index 0000000000..b60ff7cdbd --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/beans/android/AppStoreApplication.java @@ -0,0 +1,52 @@ +/* + * + * 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. + */ +package org.wso2.carbon.mdm.beans.android; + +import com.google.gson.Gson; +import org.wso2.carbon.mdm.api.common.MDMAPIException; +import java.io.IOException; +import java.io.Serializable; + +/** + * This class represents the Appstore Application information. + */ +public class AppStoreApplication implements Serializable { + + private String type; + private String appIdentifier; + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getAppIdentifier() { + return appIdentifier; + } + + public void setAppIdentifier(String appIdentifier) { + this.appIdentifier = appIdentifier; + } + + public String toJSON() throws MDMAPIException { + Gson gson = new Gson(); + return gson.toJson(this); + } +} diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/beans/android/EnterpriseApplication.java b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/beans/android/EnterpriseApplication.java new file mode 100644 index 0000000000..f19dbd44c1 --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/beans/android/EnterpriseApplication.java @@ -0,0 +1,61 @@ +/* + * + * 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. + */ +package org.wso2.carbon.mdm.beans.android; + +import com.google.gson.Gson; +import org.wso2.carbon.mdm.api.common.MDMAPIException; +import java.io.IOException; +import java.io.Serializable; + +/** + * This class represents the Enterprise Application information. + */ +public class EnterpriseApplication implements Serializable { + + private String type; + private String url; + private String appIdentifier; + + public String getAppIdentifier() { + return appIdentifier; + } + + public void setAppIdentifier(String appIdentifier) { + this.appIdentifier = appIdentifier; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public String toJSON() throws MDMAPIException { + Gson gson = new Gson(); + return gson.toJson(this); + } +} diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/beans/android/WebApplication.java b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/beans/android/WebApplication.java new file mode 100644 index 0000000000..7632524c9f --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/beans/android/WebApplication.java @@ -0,0 +1,61 @@ +/* + * + * 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. + */ +package org.wso2.carbon.mdm.beans.android; + +import com.google.gson.Gson; +import org.wso2.carbon.mdm.api.common.MDMAPIException; +import java.io.IOException; +import java.io.Serializable; + +/** + * This class represents the Web Application information. + */ +public class WebApplication implements Serializable { + + private String name; + private String url; + private String type; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String toJSON() throws MDMAPIException { + Gson gson = new Gson(); + return gson.toJson(this); + } +} diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/beans/ios/AppStoreApplication.java b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/beans/ios/AppStoreApplication.java new file mode 100644 index 0000000000..aa6c0a0121 --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/beans/ios/AppStoreApplication.java @@ -0,0 +1,86 @@ +/* + * 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.wso2.carbon.mdm.beans.ios; + +import com.google.gson.Gson; +import org.wso2.carbon.mdm.api.common.MDMAPIException; +import java.io.IOException; +import java.io.Serializable; + +public class AppStoreApplication implements Serializable { + + private String identifier; + private int iTunesStoreID; + private boolean removeAppUponMDMProfileRemoval; + private boolean preventBackupOfAppData; + private String bundleId; + private String UUID; + + public String getUUID() { + return UUID; + } + + public void setUUID(String UUID) { + this.UUID = UUID; + } + + public String getIdentifier() { + return identifier; + } + + public void setIdentifier(String identifier) { + this.identifier = identifier; + } + + public int getiTunesStoreID() { + return iTunesStoreID; + } + + public void setiTunesStoreID(int iTunesStoreID) { + this.iTunesStoreID = iTunesStoreID; + } + + public boolean isRemoveAppUponMDMProfileRemoval() { + return removeAppUponMDMProfileRemoval; + } + + public void setRemoveAppUponMDMProfileRemoval(boolean removeAppUponMDMProfileRemoval) { + this.removeAppUponMDMProfileRemoval = removeAppUponMDMProfileRemoval; + } + + public boolean isPreventBackupOfAppData() { + return preventBackupOfAppData; + } + + public void setPreventBackupOfAppData(boolean preventBackupOfAppData) { + this.preventBackupOfAppData = preventBackupOfAppData; + } + + public String getBundleId() { + return bundleId; + } + + public void setBundleId(String bundleId) { + this.bundleId = bundleId; + } + + public String toJSON() throws MDMAPIException { + Gson gson = new Gson(); + return gson.toJson(this); + } +} diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/beans/ios/EnterpriseApplication.java b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/beans/ios/EnterpriseApplication.java new file mode 100644 index 0000000000..d310779faa --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/beans/ios/EnterpriseApplication.java @@ -0,0 +1,83 @@ +/* + * 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.wso2.carbon.mdm.beans.ios; + +import com.google.gson.Gson; +import org.wso2.carbon.mdm.api.common.MDMAPIException; +import java.io.IOException; +import java.io.Serializable; + +public class EnterpriseApplication implements Serializable { + + private String identifier; + private String manifestURL; + private boolean removeAppUponMDMProfileRemoval; + private boolean preventBackupOfAppData; + private String bundleId; + private String UUID; + + public void setUUID(String UUID) { + this.UUID = UUID; + } + + public String getIdentifier() { + return identifier; + } + + public void setIdentifier(String identifier) { + this.identifier = identifier; + } + + public String getManifestURL() { + return manifestURL; + } + + public void setManifestURL(String manifestURL) { + this.manifestURL = manifestURL; + } + + public boolean isRemoveAppUponMDMProfileRemoval() { + return removeAppUponMDMProfileRemoval; + } + + public void setRemoveAppUponMDMProfileRemoval(boolean removeAppUponMDMProfileRemoval) { + this.removeAppUponMDMProfileRemoval = removeAppUponMDMProfileRemoval; + } + + public boolean isPreventBackupOfAppData() { + return preventBackupOfAppData; + } + + public void setPreventBackupOfAppData(boolean preventBackupOfAppData) { + this.preventBackupOfAppData = preventBackupOfAppData; + } + + public String getBundleId() { + return bundleId; + } + + public void setBundleId(String bundleId) { + this.bundleId = bundleId; + } + + public String toJSON() throws MDMAPIException { + Gson gson = new Gson(); + return gson.toJson(this); + } +} diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/beans/ios/RemoveApplication.java b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/beans/ios/RemoveApplication.java new file mode 100644 index 0000000000..9c15ba6a36 --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/beans/ios/RemoveApplication.java @@ -0,0 +1,24 @@ +package org.wso2.carbon.mdm.beans.ios; + +import com.google.gson.Gson; +import org.wso2.carbon.mdm.api.common.MDMAPIException; +import java.io.IOException; +import java.io.Serializable; + +public class RemoveApplication implements Serializable { + + private String bundleId; + + public String getBundleId() { + return bundleId; + } + + public void setBundleId(String bundleId) { + this.bundleId = bundleId; + } + + public String toJSON() throws MDMAPIException { + Gson gson = new Gson(); + return gson.toJson(this); + } +} diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/beans/ios/WebClip.java b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/beans/ios/WebClip.java new file mode 100644 index 0000000000..4604eab468 --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/beans/ios/WebClip.java @@ -0,0 +1,60 @@ +package org.wso2.carbon.mdm.beans.ios; + +import com.google.gson.Gson; +import org.wso2.carbon.mdm.api.common.MDMAPIException; +import java.io.IOException; + +public class WebClip { + + private String URL; + private String label; + private String icon; + private String isRemovable; + private String UUID; + + public String getUUID() { + return UUID; + } + + public void setUUID(String UUID) { + this.UUID = UUID; + } + + public String getURL() { + return URL; + } + + public void setURL(String URL) { + this.URL = URL; + } + + public String getLabel() { + return label; + } + + public void setLabel(String label) { + this.label = label; + } + + public String getIcon() { + return icon; + } + + public void setIcon(String icon) { + this.icon = icon; + } + + public String getIsRemovable() { + return isRemovable; + } + + public void setIsRemovable(String isRemovable) { + this.isRemovable = isRemovable; + } + + public String toJSON() throws MDMAPIException { + Gson gson = new Gson(); + return gson.toJson(this); + } + +} diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/util/Constants.java b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/util/Constants.java new file mode 100644 index 0000000000..e9caa31d27 --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/util/Constants.java @@ -0,0 +1,30 @@ +/* + * 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.wso2.carbon.mdm.util; + +/** + * Holds the constants used by MDM-Admin web application. + */ +public class Constants { + + public static final String USER_CLAIM_EMAIL_ADDRESS = "http://wso2.org/claims/emailaddress"; + public static final String USER_CLAIM_FIRST_NAME = "http://wso2.org/claims/givenname"; + public static final String USER_CLAIM_LAST_NAME = "http://wso2.org/claims/lastname"; + +} diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/util/MDMUtil.java b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/util/MDMUtil.java new file mode 100644 index 0000000000..d766776cc3 --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/util/MDMUtil.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2016, 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.wso2.carbon.mdm.util; + +import org.wso2.carbon.mdm.beans.ProfileFeature; +import org.wso2.carbon.policy.mgt.common.Profile; + +import java.util.ArrayList; +import java.util.List; + +public class MDMUtil { + + public static Profile convertProfile(org.wso2.carbon.mdm.beans.Profile mdmProfile) { + Profile profile = new Profile(); + profile.setTenantId(mdmProfile.getTenantId()); + profile.setCreatedDate(mdmProfile.getCreatedDate()); + profile.setDeviceType(mdmProfile.getDeviceType()); + + List profileFeatures = + new ArrayList(mdmProfile.getProfileFeaturesList().size()); + for (ProfileFeature mdmProfileFeature : mdmProfile.getProfileFeaturesList()) { + profileFeatures.add(convertProfileFeature(mdmProfileFeature)); + } + profile.setProfileFeaturesList(profileFeatures); + profile.setProfileId(mdmProfile.getProfileId()); + profile.setProfileName(mdmProfile.getProfileName()); + profile.setUpdatedDate(mdmProfile.getUpdatedDate()); + return profile; + } + + public static org.wso2.carbon.policy.mgt.common.ProfileFeature convertProfileFeature(ProfileFeature + mdmProfileFeature) { + + org.wso2.carbon.policy.mgt.common.ProfileFeature profileFeature = + new org.wso2.carbon.policy.mgt.common.ProfileFeature(); + profileFeature.setProfileId(mdmProfileFeature.getProfileId()); + profileFeature.setContent(mdmProfileFeature.getPayLoad()); + profileFeature.setDeviceTypeId(mdmProfileFeature.getDeviceTypeId()); + profileFeature.setFeatureCode(mdmProfileFeature.getFeatureCode()); + profileFeature.setId(mdmProfileFeature.getId()); + return profileFeature; + + } +} \ No newline at end of file diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/util/SetReferenceTransformer.java b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/util/SetReferenceTransformer.java new file mode 100644 index 0000000000..da4136593e --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/carbon/mdm/util/SetReferenceTransformer.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2016, 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.wso2.carbon.mdm.util; + +import java.util.ArrayList; +import java.util.List; +import java.util.TreeSet; + +public class SetReferenceTransformer{ + private List objectsToRemove; + private List objectsToAdd; + + /** + * Use the Set theory to find the objects to delete and objects to add + + The difference of objects in existingSet and newSet needed to be deleted + + new roles to add = newSet - The intersection of roles in existingSet and newSet + * @param currentList + * @param nextList + */ + public void transform(List currentList, List nextList){ + TreeSet existingSet = new TreeSet(currentList); + TreeSet newSet = new TreeSet(nextList); + + existingSet.removeAll(newSet); + + objectsToRemove = new ArrayList(existingSet); + + // Clearing and re-initializing the set + existingSet = new TreeSet(currentList); + + newSet.removeAll(existingSet); + objectsToAdd = new ArrayList(newSet); + } + + public List getObjectsToRemove() { + return objectsToRemove; + } + + public List getObjectsToAdd() { + return objectsToAdd; + } +} \ No newline at end of file diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/mdm/common/Application.java b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/mdm/common/Application.java new file mode 100644 index 0000000000..0a11ad1bc4 --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/mdm/common/Application.java @@ -0,0 +1,119 @@ +/* + * + * 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.wso2.mdm.common; + +import org.wso2.carbon.device.mgt.common.DeviceIdentifier; + +import java.util.List; + +public class Application { + + private String applicationName; + private String appId; + private String locationUrl; + private String imageUrl; + private String platform; + private String version; + private List userNameList; + private List roleNameList; + + public List getDeviceIdentifiers() { + return deviceIdentifiers; + } + + public void setDeviceIdentifiers(List deviceIdentifiers) { + this.deviceIdentifiers = deviceIdentifiers; + } + + private List deviceIdentifiers; + + + public String getApplicationName() { + return applicationName; + } + + public void setApplicationName(String applicationName) { + this.applicationName = applicationName; + } + + + public String getAppId() { + return appId; + } + + public void setAppId(String appId) { + this.appId = appId; + } + + + public String getLocationUrl() { + return locationUrl; + } + + public void setLocationUrl(String locationUrl) { + this.locationUrl = locationUrl; + } + + + public String getImageUrl() { + return imageUrl; + } + + public void setImageUrl(String imageUrl) { + this.imageUrl = imageUrl; + } + + + public String getPlatform() { + return platform; + } + + public void setPlatform(String platform) { + this.platform = platform; + } + + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + + + public List getUserNameList() { + return userNameList; + } + + public void setUserNameList(List userNameList) { + this.userNameList = userNameList; + } + + + public List getRoleNameList() { + return roleNameList; + } + + public void setRoleNameList(List roleNameList) { + this.roleNameList = roleNameList; + } + +} diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/mdm/common/GsonMessageBodyHandler.java b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/mdm/common/GsonMessageBodyHandler.java new file mode 100644 index 0000000000..09b49e6f1e --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/java/org/wso2/mdm/common/GsonMessageBodyHandler.java @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2014, 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.wso2.mdm.common; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + +import javax.ws.rs.Consumes; +import javax.ws.rs.Produces; +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.ext.MessageBodyReader; +import javax.ws.rs.ext.MessageBodyWriter; +import javax.ws.rs.ext.Provider; +import java.io.*; +import java.lang.annotation.Annotation; +import java.lang.reflect.Type; + +import static javax.ws.rs.core.MediaType.APPLICATION_JSON; + +@Provider +@Produces(APPLICATION_JSON) +@Consumes(APPLICATION_JSON) +public class GsonMessageBodyHandler implements MessageBodyWriter, MessageBodyReader { + + private Gson gson; + private static final String UTF_8 = "UTF-8"; + + public boolean isReadable(Class aClass, Type type, Annotation[] annotations, MediaType mediaType) { + return true; + } + + private Gson getGson() { + if (gson == null) { + final GsonBuilder gsonBuilder = new GsonBuilder(); + gson = gsonBuilder.create(); + } + return gson; + } + + public Object readFrom(Class objectClass, Type type, Annotation[] annotations, MediaType mediaType, + MultivaluedMap stringStringMultivaluedMap, InputStream entityStream) + throws IOException, WebApplicationException { + + InputStreamReader reader = new InputStreamReader(entityStream, "UTF-8"); + + try { + return getGson().fromJson(reader, type); + } finally { + reader.close(); + } + } + + public boolean isWriteable(Class aClass, Type type, Annotation[] annotations, MediaType mediaType) { + return true; + } + + public long getSize(Object o, Class aClass, Type type, Annotation[] annotations, MediaType mediaType) { + return -1; + } + + public void writeTo(Object object, Class aClass, Type type, Annotation[] annotations, MediaType mediaType, + MultivaluedMap stringObjectMultivaluedMap, OutputStream entityStream) + throws IOException, WebApplicationException { + + OutputStreamWriter writer = new OutputStreamWriter(entityStream, UTF_8); + try { + Type jsonType = null; + if (type.equals(type)) { + jsonType = type; + } + getGson().toJson(object, jsonType, writer); + } finally { + writer.close(); + } + } +} diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/webapp/META-INF/permissions.xml b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/webapp/META-INF/permissions.xml new file mode 100644 index 0000000000..0e11fe9ba9 --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/webapp/META-INF/permissions.xml @@ -0,0 +1,565 @@ + + + + + + + + + Get all devices + /device-mgt/devices/list + /devices + GET + emm_admin + + + + Get all device types + /device-mgt/devices/types + /devices/types + GET + emm_admin + + + + View device + /device-mgt/devices/view + /devices/view + GET + emm_admin + + + + Get device + /device-mgt/devices/view + /devices/*/* + GET + emm_admin,emm_user + + + + Get users' devices + /device-mgt/devices/view + /devices/user/*/* + GET + emm_admin,emm_user + + + + Get device count + /device-mgt/devices/count + /devices/count + GET + emm_admin + + + + Get all users' devices + /device-mgt/devices/list + /devices/name/*/* + GET + emm_admin,emm_user + + + + + + Get all notifications + /device-mgt/notifications/view + /notifications + GET + emm_admin + + + + Add notification + /device-mgt/notifications/add + /notifications + POST + emm_admin + + + + Update notification + /device-mgt/notifications/view + /notifications/*/* + PUT + emm_admin + + + + Get new notifications + /device-mgt/notifications/view + /notifications/* + GET + emm_admin + + + + + + Get all operations + /device-mgt/operations/view + /operations + GET + emm_admin + + + + Add Operation + /device-mgt/operations/add + /operations + POST + emm_admin + + + + Install Application + /device-mgt/operations/application/install + /operations/installApp/* + POST + emm_admin,emm_user + + + + Uninstall Application + /device-mgt/operations/application/uninstall + /operations/uninstallApp/* + POST + emm_admin,emm_user + + + + Get Applications For Devices + /device-mgt/operations/application/view + /operations/*/*/* + GET + emm_admin,emm_user + + + Get Device Operations + /device-mgt/operations/view + /operations/*/* + GET + emm_admin,emm_user + + + + Get Applications For Device Type + /device-mgt/operations/application/view + /operations/* + GET + emm_admin,emm_user + + + + + + Get features + /device-mgt/features/view + /features/* + GET + emm_admin + + + Get all features + /device-mgt/features/view + /features + GET + emm_admin + + + + + + Get all roles + /device-mgt/roles + /roles + GET + emm_admin + + + Get role permissions + /device-mgt/roles + /roles/permissions + GET + emm_admin + + + Get User roles + /device-mgt/roles + /roles/* + GET + emm_admin + + + Update User roles + /device-mgt/roles/update + /roles + PUT + emm_admin + + + Update a specific role + /device-mgt/roles/update + /roles/* + PUT + emm_admin + + + Update User roles + /device-mgt/roles + /roles/*/users + PUT + emm_admin + + + Save User roles + /device-mgt/roles/add + /roles + POST + emm_admin + + + Delete User roles + /device-mgt/roles/delete + /roles + DELETE + emm_admin + + + + + + Get all users + /device-mgt/users/view + /users + GET + emm_admin + + + Add user + /device-mgt/users/add + /users + POST + emm_admin + + + Get user + /device-mgt/users/view + /users/view + GET + emm_admin + + + Update user + /device-mgt/users/update + /users + PUT + emm_admin + + + Change user password + /device-mgt/users/reset-password + /users/reset-password + POST + emm_admin + + + Delete user + /device-mgt/users/delete + /users + DELETE + emm_admin + + + Get user roles by name + /device-mgt/users/roles + /users/roles + GET + emm_admin + + + Get user roles by name + /device-mgt/users/roles + /roles + GET + emm_admin + + + Save Roles + /device-mgt/users/roles + /roles + POST + emm_admin + + + Get all devices of user + /device-mgt/users/devices + /users/devices + GET + emm_admin + + + Get user roles + /device-mgt/users/roles + /users/*/* + GET + emm_admin + + + Get user count + /device-mgt/users/view + /users/count + GET + emm_admin + + + Get all user names + /device-mgt/users/view + /users/view-users + GET + emm_admin + + + Get all usernames + /device-mgt/users/view + /users/users-by-username + GET + emm_admin + + + Get filtered usernames + /device-mgt/users/view + /users/users-by-username/* + GET + emm_admin + + + Send invitation + /device-mgt/users/invite + /users/email-invitation + POST + emm_admin + + + + + + Add Policy + /device-mgt/policies/add + /policies/inactive-policy + POST + emm_admin + + + Get Policy For Device + /device-mgt/policies/view + /policies/*/* + POST + emm_admin + + + Get Policy For Device By Type + /device-mgt/policies/view + /policies/*/*/* + GET + emm_admin + + + Add Active Policy + /device-mgt/policies/add + /policies/active-policy + POST + emm_admin + + + Bulk Policy Removal + /device-mgt/policies/bulk-remove + /policies/bulk-remove + POST + emm_admin + + + Get Policies + /device-mgt/policies/view + /policies + GET + emm_admin + + + Get Policy + /device-mgt/policies/view + /policies/* + GET + emm_admin + + + Update Policy + /device-mgt/policies/update + /policies/* + PUT + emm_admin + + + Delete Policy + /device-mgt/policies/delete + /policies + DELETE + emm_admin + + + Policy Count + /device-mgt/policies/view + /policies/count + GET + emm_admin + + + Policy priorities + /device-mgt/policies/update + /policies/priorities + PUT + emm_admin + + + Activate Policy + /device-mgt/policies/update + /policies/activate + PUT + emm_admin + + + Inactivate Policy + /device-mgt/policies/update + /policies/inactivate + PUT + emm_admin + + + Apply Policy changes + /device-mgt/policies/update + /policies/apply-changes + PUT + emm_admin + + + Start Task + /device-mgt/policies/task + /policies/start-task/* + GET + emm_admin + + + Update Task + /device-mgt/policies/task + /policies/update-task/* + GET + emm_admin + + + Stop Task + /device-mgt/policies/task + /policies/stop-task + GET + emm_admin + + + Get Policy Compliance + /device-mgt/policies/compliance + /policies/*/* + GET + emm_admin + + + + + + Add profile + /device-mgt/profiles/add + /profiles + POST + emm_admin + + + Update profile + /device-mgt/profiles/update + /profiles/* + PUT + emm_admin + + + Add profile + /device-mgt/profiles/delete + /profiles/* + DELETE + emm_admin + + + + + + + + + + + + + + + + + + + + + + + Get configuration + /device-mgt/tenant/configuration/view + /configuration + GET + emm_admin + + + Add configuration + /device-mgt/tenant/configuration/modify + /configuration + POST + emm_admin + + + Update configuration + /device-mgt/tenant/configuration/modify + /configuration + PUT + emm_admin + + + \ No newline at end of file diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/webapp/META-INF/webapp-classloading.xml b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/webapp/META-INF/webapp-classloading.xml new file mode 100644 index 0000000000..ed2ed21624 --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/webapp/META-INF/webapp-classloading.xml @@ -0,0 +1,35 @@ + + + + + + + + + false + + + CXF,Carbon + diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/webapp/WEB-INF/cxf-servlet.xml b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/webapp/WEB-INF/cxf-servlet.xml new file mode 100644 index 0000000000..dc53a029be --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/webapp/WEB-INF/cxf-servlet.xml @@ -0,0 +1,144 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/webapp/WEB-INF/web.xml b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 0000000000..1ee664987f --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.api/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,74 @@ + + + + Admin-Webapp + + JAX-WS/JAX-RS MDM Endpoint + JAX-WS/JAX-RS Servlet + CXFServlet + + org.apache.cxf.transport.servlet.CXFServlet + + 1 + + + CXFServlet + /* + + + 60 + + + + isAdminService + false + + + doAuthentication + true + + + + + + + MDM-Admin + /* + + + CONFIDENTIAL + + + + diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/pom.xml b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/pom.xml new file mode 100644 index 0000000000..530e3c8b62 --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/pom.xml @@ -0,0 +1,62 @@ + + + + + + + + mobile-base-plugin + org.wso2.carbon.devicemgt-plugins + 2.1.0-SNAPSHOT + ../pom.xml + + + 4.0.0 + org.wso2.carbon.device.mgt.mobile.ui + WSO2 Carbon - EMM Server UI + pom + + + + + maven-assembly-plugin + 2.5.5 + + ${project.artifactId}-${carbon.device.mgt.version} + false + + src/assembly/src.xml + + + + + create-archive + package + + single + + + + + + + + \ No newline at end of file diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/src/assembly/src.xml b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/src/assembly/src.xml new file mode 100644 index 0000000000..2797034e07 --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/src/assembly/src.xml @@ -0,0 +1,36 @@ + + + + src + + zip + + false + ${basedir}/src + + + ${basedir}/src/main/resources/jaggeryapps/devicemgt + / + true + + + \ No newline at end of file diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/src/main/resources/jaggeryapps/devicemgt/app/pages/cdmf.page.platform.configuration/configuration.hbs b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/src/main/resources/jaggeryapps/devicemgt/app/pages/cdmf.page.platform.configuration/configuration.hbs new file mode 100644 index 0000000000..302cc5e9a0 --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/src/main/resources/jaggeryapps/devicemgt/app/pages/cdmf.page.platform.configuration/configuration.hbs @@ -0,0 +1,5 @@ +{{unit "cdmf.unit.ui.title" pageTitle="Platform Configuration"}} + +{{#zone "content"}} + {{unit "cdmf.unit.platform.configuration"}} +{{/zone}} \ No newline at end of file diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/src/main/resources/jaggeryapps/devicemgt/app/pages/cdmf.page.platform.configuration/configuration.json b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/src/main/resources/jaggeryapps/devicemgt/app/pages/cdmf.page.platform.configuration/configuration.json new file mode 100644 index 0000000000..7316b1146c --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/src/main/resources/jaggeryapps/devicemgt/app/pages/cdmf.page.platform.configuration/configuration.json @@ -0,0 +1,5 @@ +{ + "version": "1.0.0", + "uri": "/platform-configuration", + "layout": "cdmf.layout.default" +} \ No newline at end of file diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/src/main/resources/jaggeryapps/devicemgt/app/units/mdm.unit.device.operation-bar/operation-bar.hbs b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/src/main/resources/jaggeryapps/devicemgt/app/units/mdm.unit.device.operation-bar/operation-bar.hbs new file mode 100644 index 0000000000..78813761e7 --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/src/main/resources/jaggeryapps/devicemgt/app/units/mdm.unit.device.operation-bar/operation-bar.hbs @@ -0,0 +1,7 @@ +
      + +{{#zone "bottomJs"}} + + {{js "js/operation-bar.js"}} +{{/zone}} \ No newline at end of file diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/src/main/resources/jaggeryapps/devicemgt/app/units/mdm.unit.device.operation-bar/operation-bar.json b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/src/main/resources/jaggeryapps/devicemgt/app/units/mdm.unit.device.operation-bar/operation-bar.json new file mode 100644 index 0000000000..56a988c763 --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/src/main/resources/jaggeryapps/devicemgt/app/units/mdm.unit.device.operation-bar/operation-bar.json @@ -0,0 +1,4 @@ +{ + "version": "1.0.0", + "extends": "cdmf.unit.device.operation-bar" +} \ No newline at end of file diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/src/main/resources/jaggeryapps/devicemgt/app/units/mdm.unit.device.operation-bar/public/js/operation-bar.js b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/src/main/resources/jaggeryapps/devicemgt/app/units/mdm.unit.device.operation-bar/public/js/operation-bar.js new file mode 100644 index 0000000000..fe6c85bd0c --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/src/main/resources/jaggeryapps/devicemgt/app/units/mdm.unit.device.operation-bar/public/js/operation-bar.js @@ -0,0 +1,195 @@ +/* + * Copyright (c) 2016, 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. + */ + +/* + * Setting-up global variables. + */ + +var operations = '.wr-operations', + modalPopup = '.wr-modalpopup', + modalPopupContent = modalPopup + ' .modalpopup-content', + navHeight = $('#nav').height(), + headerHeight = $('header').height(), + offset = (headerHeight + navHeight), + deviceSelection = '.device-select', + platformTypeConstants = { + "ANDROID": "android", + "IOS": "ios", + "WINDOWS": "windows" + }; + +/* + * Function to get selected devices ID's + */ +function getSelectedDeviceIds() { + var deviceIdentifierList = []; + $(deviceSelection).each(function (index) { + var device = $(this); + var deviceId = device.data('deviceid'); + var deviceType = device.data('type'); + deviceIdentifierList.push({ + "id": deviceId, + "type": deviceType + }); + }); + if (deviceIdentifierList.length == 0) { + var thisTable = $(".DTTT_selected").closest('.dataTables_wrapper').find('.dataTable').dataTable(); + thisTable.api().rows().every(function () { + if ($(this.node()).hasClass('DTTT_selected')) { + var deviceId = $(thisTable.api().row(this).node()).data('deviceid'); + var deviceType = $(thisTable.api().row(this).node()).data('devicetype'); + deviceIdentifierList.push({ + "id": deviceId, + "type": deviceType + }); + } + }); + } + + return deviceIdentifierList; +} + +/* + * On operation click function. + * @param selection: Selected operation + */ +function operationSelect(selection) { + var deviceIdList = getSelectedDeviceIds(); + if (deviceIdList == 0) { + $(modalPopupContent).html($("#errorOperations").html()); + } else { + $(modalPopupContent).addClass("operation-data"); + $(modalPopupContent).html($(operations + " .operation[data-operation-code=" + selection + "]").html()); + $(modalPopupContent).data("operation-code", selection); + } + showPopup(); +} + +function getDevicesByTypes(deviceList) { + var deviceTypes = {}; + $.each(deviceList, function (index, item) { + if (!deviceTypes[item.type]) { + deviceTypes[item.type] = []; + } + if (item.type == platformTypeConstants.ANDROID || + item.type == platformTypeConstants.IOS || item.type == platformTypeConstants.WINDOWS) { + deviceTypes[item.type].push(item.id); + } + }); + return deviceTypes; +} + +function unloadOperationBar() { + $("#showOperationsBtn").addClass("hidden"); + $(".wr-operations").html(""); +} + +function loadOperationBar(deviceType) { + var operationBar = $("#operations-bar"); + var operationBarSrc = operationBar.attr("src"); + var platformType = deviceType; + $.template("operations-bar", operationBarSrc, function (template) { + var serviceURL = "/devicemgt_admin/features/" + platformType; + var successCallback = function (data) { + var viewModel = {}; + data = JSON.parse(data).filter(function (current) { + var iconName; + switch (deviceType) { + case platformTypeConstants.ANDROID: + iconName = operationModule.getAndroidIconForFeature(current.code); + current.type = deviceType; + break; + case platformTypeConstants.WINDOWS: + iconName = operationModule.getWindowsIconForFeature(current.code); + break; + case platformTypeConstants.IOS: + iconName = operationModule.getIOSIconForFeature(current.code); + break; + } + + if (iconName) { + current.icon = iconName; + return current; + } + }); + viewModel.features = data; + var content = template(viewModel); + $(".wr-operations").html(content); + }; + invokerUtil.get(serviceURL, successCallback, function (message) { + $(".wr-operations").html(message); + }); + }); +} + +function runOperation(operationName) { + var deviceIdList = getSelectedDeviceIds(); + var list = getDevicesByTypes(deviceIdList); + + var successCallback = function (data) { + if (operationName == "NOTIFICATION") { + $(modalPopupContent).html($("#messageSuccess").html()); + } else { + $(modalPopupContent).html($("#operationSuccess").html()); + } + showPopup(); + }; + var errorCallback = function (data) { + $(modalPopupContent).html($("#errorOperationUnexpected").html()); + showPopup(); + }; + + var payload, serviceEndPoint; + if (list[platformTypeConstants.IOS]) { + payload = operationModule. + generatePayload(platformTypeConstants.IOS, operationName, list[platformTypeConstants.IOS]); + serviceEndPoint = operationModule.getIOSServiceEndpoint(operationName); + } else if (list[platformTypeConstants.ANDROID]) { + payload = operationModule + .generatePayload(platformTypeConstants.ANDROID, operationName, list[platformTypeConstants.ANDROID]); + serviceEndPoint = operationModule.getAndroidServiceEndpoint(operationName); + } else if (list[platformTypeConstants.WINDOWS]) { + payload = operationModule. + generatePayload(platformTypeConstants.WINDOWS, operationName, list[platformTypeConstants.WINDOWS]); + serviceEndPoint = operationModule.getWindowsServiceEndpoint(operationName); + } + if (operationName == "NOTIFICATION") { + var errorMsgWrapper = "#notification-error-msg"; + var errorMsg = "#notification-error-msg span"; + var message = $("#message").val(); + if (!message) { + $(errorMsg).text("Enter a message. It cannot be empty."); + $(errorMsgWrapper).removeClass("hidden"); + } else { + invokerUtil.post(serviceEndPoint, payload, successCallback, errorCallback); + $(modalPopupContent).removeData(); + hidePopup(); + } + } else { + invokerUtil.post(serviceEndPoint, payload, successCallback, errorCallback); + $(modalPopupContent).removeData(); + hidePopup(); + } +} + +/* + * DOM ready functions. + */ +$(document).ready(function () { + $(operations).show(); +}); diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/src/main/resources/jaggeryapps/devicemgt/app/units/mdm.unit.device.operation-bar/public/templates/hidden-operations-android.hbs b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/src/main/resources/jaggeryapps/devicemgt/app/units/mdm.unit.device.operation-bar/public/templates/hidden-operations-android.hbs new file mode 100644 index 0000000000..ad8959bf0f --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/src/main/resources/jaggeryapps/devicemgt/app/units/mdm.unit.device.operation-bar/public/templates/hidden-operations-android.hbs @@ -0,0 +1,304 @@ +
      + +
      + + +
      +
      + +
      +
      + +
      + Configure +
      +
      +
      + +
      + + + +
      + +
      + + + +
      + +
      + + + +
      + +
      + + + +
      + +
      + + + +
      + +
      + +
      + +
      + +
      + +
      + + Configure +
      +
      +
      + + + +
      +
      + + +
      + +
      + + + +
      + +
      + Configure +
      + +
      + + +
      +
      + +
      + + +
      + +
      +
      + +
      + + +
      + +
      + Install +
      +
      +
      + +
      + + +
      + +
      + + +
      + +
      + Install +
      +
      +
      + +
      + + +
      + +
      + Uninstall +
      +
      +
      + + + +
      +
      + +
      +
      + +
      + Configure +
      +
      +
      + +
      +
      \ No newline at end of file diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/src/main/resources/jaggeryapps/devicemgt/app/units/mdm.unit.device.operation-bar/public/templates/hidden-operations-ios.hbs b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/src/main/resources/jaggeryapps/devicemgt/app/units/mdm.unit.device.operation-bar/public/templates/hidden-operations-ios.hbs new file mode 100644 index 0000000000..4cb006ef07 --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/src/main/resources/jaggeryapps/devicemgt/app/units/mdm.unit.device.operation-bar/public/templates/hidden-operations-ios.hbs @@ -0,0 +1,366 @@ +
      + +
      + + +
      +
      + +
      + +
      + +
      + +
      + +
      + + +
      + +
      +
      + + +
      +
      + + +
      + Install +
      + +
      +
      + +
      + +
      + +
      + +
      + +
      + + +
      + +
      +
      + + +
      +
      + + +
      + Install +
      +
      +
      + +
      + +
      + +
      + Uninstall +
      +
      +
      + + + +
      + + +
      + +
      + +
      + + +
      + +
      + + +
      + + + +
      + +
      + + + +
      + +
      + + + +
      + +
      + + + +
      + +
      + Configure +
      + + + +
      + + +
      + +
      + + + +
      +
      + +
      +
      + Path Prefix +
      +
      +
      + + + +
      + +
      + + + +
      + +
      + +
      + + +
      + +
      + + +
      + +
      + + +
      + +
      + + +
      + + + +
      +
      + +
      +
      + : +
      +
      +
      + + + +
      + +
      + + + +
      + +
      + + + +
      + +
      + +
      + + +
      +
      + + + +
      +
      + +
      +
      + +
      +
      + +
      +
      + +
      +
      + +
      + Configure +
      + + +
      + +
      + +
      + +
      + +
      AirPlay password +
      + +
      + Configure +
      + +
      +
      \ No newline at end of file diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/src/main/resources/jaggeryapps/devicemgt/app/units/mdm.unit.device.operation-bar/public/templates/operations.hbs b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/src/main/resources/jaggeryapps/devicemgt/app/units/mdm.unit.device.operation-bar/public/templates/operations.hbs new file mode 100644 index 0000000000..e2fcd8f2cb --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/src/main/resources/jaggeryapps/devicemgt/app/units/mdm.unit.device.operation-bar/public/templates/operations.hbs @@ -0,0 +1,156 @@ +
      +
      +
      +
      +

      + + + + + Operation cannot be performed ! +

      +

      + Please select a device or a list of devices to perform an operation. +

      + +
      + Ok +
      +
      +
      +
      +
      +
      +
      +
      +
      +

      + + + + + Operation cannot be performed ! +

      +

      + Unexpected error occurred. Please Try again later. +

      + +
      + Ok +
      +
      +
      +
      +
      +
      +
      +
      +
      +

      + + + + + Operation queued successfully ! +

      +

      + Operation has been queued successfully to be sent to the device. +

      + +
      + Ok +
      +
      +
      +
      +
      + +
      +
      +
      +
      +

      + + + + + Message sent successfully ! +

      +

      + Message has been queued to be sent to the device. +

      + +
      + Ok +
      +
      +
      +
      +
      +{{#each features}} + + + {{name}} + +
      +
      +
      +
      +

      + + + + + {{name}} +
      +

      +

      + {{#equal code "WIPE_DATA"}} + {{#equal type "android"}} + Enter PIN code (Optional - This is required only if the device type + is BYOD). +

      + +
      + +
      +
      + {{/equal}} + {{/equal}} + {{#equal code "NOTIFICATION"}} + Type your message below. +

      + + +
      + +
      +
      + {{/equal}} + {{#equal code "CHANGE_LOCK_CODE"}} + Type new lock-code below. +

      + +
      + {{/equal}} + Do you want to perform this operation on selected device(s) ? +
      +

      +
      + Yes + No +
      +
      +
      +
      +
      +{{/each}} +
      \ No newline at end of file diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/src/main/resources/jaggeryapps/devicemgt/app/units/mdm.unit.device.operation-mod/operation-mod.hbs b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/src/main/resources/jaggeryapps/devicemgt/app/units/mdm.unit.device.operation-mod/operation-mod.hbs new file mode 100644 index 0000000000..d193515b46 --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/src/main/resources/jaggeryapps/devicemgt/app/units/mdm.unit.device.operation-mod/operation-mod.hbs @@ -0,0 +1,3 @@ +{{#zone "bottomJs"}} + {{js "js/operation-mod.js"}} +{{/zone}} \ No newline at end of file diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/src/main/resources/jaggeryapps/devicemgt/app/units/mdm.unit.device.operation-mod/operation-mod.json b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/src/main/resources/jaggeryapps/devicemgt/app/units/mdm.unit.device.operation-mod/operation-mod.json new file mode 100644 index 0000000000..62346cb872 --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/src/main/resources/jaggeryapps/devicemgt/app/units/mdm.unit.device.operation-mod/operation-mod.json @@ -0,0 +1,4 @@ +{ + "version": "1.0.0", + "extends": "cdmf.unit.device.operation-mod" +} \ No newline at end of file diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/src/main/resources/jaggeryapps/devicemgt/app/units/mdm.unit.device.operation-mod/public/js/operation-mod.js b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/src/main/resources/jaggeryapps/devicemgt/app/units/mdm.unit.device.operation-mod/public/js/operation-mod.js new file mode 100644 index 0000000000..b35faac5a5 --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/src/main/resources/jaggeryapps/devicemgt/app/units/mdm.unit.device.operation-mod/public/js/operation-mod.js @@ -0,0 +1,1228 @@ +/* + * Copyright (c) 2016, 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. + */ + +var operationModule = function () { + var publicMethods = {}; + var privateMethods = {}; + + // Constants to define platform types available + var platformTypeConstants = { + "ANDROID": "android", + "IOS": "ios", + "WINDOWS": "windows" + }; + + // Constants to define operation types available + var operationTypeConstants = { + "PROFILE": "profile", + "CONFIG": "config", + "COMMAND": "command" + }; + + // Constants to define Android Operation Constants + var androidOperationConstants = { + "PASSCODE_POLICY_OPERATION_CODE": "PASSCODE_POLICY", + "CAMERA_OPERATION_CODE": "CAMERA", + "ENCRYPT_STORAGE_OPERATION_CODE": "ENCRYPT_STORAGE", + "WIFI_OPERATION_CODE": "WIFI", + "WIPE_OPERATION_CODE": "WIPE_DATA", + "NOTIFICATION_OPERATION_CODE": "NOTIFICATION", + "CHANGE_LOCK_CODE_OPERATION_CODE": "CHANGE_LOCK_CODE" + }; + + // Constants to define Windows Operation Constants + var windowsOperationConstants = { + "PASSCODE_POLICY_OPERATION_CODE": "PASSCODE_POLICY", + "CAMERA_OPERATION_CODE": "CAMERA", + "ENCRYPT_STORAGE_OPERATION_CODE": "ENCRYPT_STORAGE", + "NOTIFICATION_OPERATION_CODE": "NOTIFICATION", + "CHANGE_LOCK_CODE_OPERATION_CODE": "CHANGE_LOCK_CODE" + }; + + // Constants to define iOS Operation Constants + var iosOperationConstants = { + "PASSCODE_POLICY_OPERATION_CODE": "PASSCODE_POLICY", + "RESTRICTIONS_OPERATION_CODE": "RESTRICTION", + "WIFI_OPERATION_CODE": "WIFI", + "EMAIL_OPERATION_CODE": "EMAIL", + "AIRPLAY_OPERATION_CODE": "AIR_PLAY", + "LDAP_OPERATION_CODE": "LDAP", + "CALENDAR_OPERATION_CODE": "CALDAV", + "NOTIFICATION_OPERATION_CODE": "NOTIFICATION", + "CALENDAR_SUBSCRIPTION_OPERATION_CODE": "CALENDAR_SUBSCRIPTION", + "APN_OPERATION_CODE": "APN", + "CELLULAR_OPERATION_CODE": "CELLULAR" + }; + + publicMethods.getIOSServiceEndpoint = function (operationCode) { + var featureMap = { + "DEVICE_LOCK": "lock", + "ALARM": "alarm", + "LOCATION": "location", + "NOTIFICATION": "notification", + "AIR_PLAY": "airplay", + "RESTRICTION": "restriction", + "CELLULAR": "cellular", + "WIFI": "wifi", + "INSTALL_STORE_APPLICATION": "storeapplication", + "INSTALL_ENTERPRISE_APPLICATION": "enterpriseapplication", + "REMOVE_APPLICATION": "removeapplication", + "ENTERPRISE_WIPE": "enterprisewipe" + }; + return "/ios/operation/" + featureMap[operationCode]; + }; + + /** + * Convert the ios platform specific code to the generic payload. + * TODO: think of the possibility to follow a pattern to the key name (namespace?) + * @param operationCode + * @param operationPayload + * @returns {{}} + */ + privateMethods.generateGenericPayloadFromIOSPayload = function (operationCode, operationPayload) { + var payload = {}; + operationPayload = JSON.parse(operationPayload); + switch (operationCode) { + case iosOperationConstants["PASSCODE_POLICY_OPERATION_CODE"]: + payload = { + "passcodePolicyForcePIN": operationPayload["forcePIN"], + "passcodePolicyAllowSimple": operationPayload["allowSimple"], + "passcodePolicyRequireAlphanumeric": operationPayload["requireAlphanumeric"], + "passcodePolicyMinLength": operationPayload["minLength"], + "passcodePolicyMinComplexChars": operationPayload["minComplexChars"], + "passcodePolicyMaxPasscodeAgeInDays": operationPayload["maxPINAgeInDays"], + "passcodePolicyPasscodeHistory": operationPayload["pinHistory"], + "passcodePolicyMaxAutoLock": operationPayload["maxInactivity"], + "passcodePolicyGracePeriod": operationPayload["maxGracePeriod"], + "passcodePolicyMaxFailedAttempts": operationPayload["maxFailedAttempts"] + }; + break; + case iosOperationConstants["RESTRICTIONS_OPERATION_CODE"]: + payload = { + "restrictionsAllowAccountModification": operationPayload["allowAccountModification"], + "restrictionsAllowAddingGameCenterFriends": operationPayload["allowAddingGameCenterFriends"], + "restrictionsAllowAirDrop": operationPayload["allowAirDrop"], + "restrictionsAllowAppCellularDataModification": operationPayload["allowAppCellularDataModification"], + "restrictionsAllowAppInstallation": operationPayload["allowAppInstallation"], + "restrictionsAllowAppRemoval": operationPayload["allowAppRemoval"], + "restrictionsAllowAssistant": operationPayload["allowAssistant"], + "restrictionsAllowAssistantUserGeneratedContent": operationPayload["allowAssistantUserGeneratedContent"], + "restrictionsAllowAssistantWhileLocked": operationPayload["allowAssistantWhileLocked"], + "restrictionsAllowBookstore": operationPayload["allowBookstore"], + "restrictionsAllowBookstoreErotica": operationPayload["allowBookstoreErotica"], + "restrictionsAllowCamera": operationPayload["allowCamera"], + "restrictionsAllowChat": operationPayload["allowChat"], + "restrictionsAllowCloudBackup": operationPayload["allowCloudBackup"], + "restrictionsAllowCloudDocumentSync": operationPayload["allowCloudDocumentSync"], + "restrictionsAllowCloudKeychainSync": operationPayload["allowCloudKeychainSync"], + "restrictionsAllowDiagnosticSubmission": operationPayload["allowDiagnosticSubmission"], + "restrictionsAllowExplicitContent": operationPayload["allowExplicitContent"], + "restrictionsAllowFindMyFriendsModification": operationPayload["allowFindMyFriendsModification"], + "restrictionsAllowFingerprintForUnlock": operationPayload["allowFingerprintForUnlock"], + "restrictionsAllowGameCenter": operationPayload["allowGameCenter"], + "restrictionsAllowGlobalBackgroundFetchWhenRoaming": operationPayload["allowGlobalBackgroundFetchWhenRoaming"], + "restrictionsAllowInAppPurchases": operationPayload["allowInAppPurchases"], + "restrictionsAllowLockScreenControlCenter": operationPayload["allowLockScreenControlCenter"], + "restrictionsAllowHostPairing": operationPayload["allowHostPairing"], + "restrictionsAllowLockScreenNotificationsView": operationPayload["allowLockScreenNotificationsView"], + "restrictionsAllowLockScreenTodayView": operationPayload["allowLockScreenTodayView"], + "restrictionsAllowMultiplayerGaming": operationPayload["allowMultiplayerGaming"], + "restrictionsAllowOpenFromManagedToUnmanaged": operationPayload["allowOpenFromManagedToUnmanaged"], + "restrictionsAllowOpenFromUnmanagedToManaged": operationPayload["allowOpenFromUnmanagedToManaged"], + "restrictionsAllowOTAPKIUpdates": operationPayload["allowOTAPKIUpdates"], + "restrictionsAllowPassbookWhileLocked": operationPayload["allowPassbookWhileLocked"], + "restrictionsAllowPhotoStream": operationPayload["allowPhotoStream"], + "restrictionsAllowSafari": operationPayload["allowSafari"], + "restrictionsSafariAllowAutoFill": operationPayload["safariAllowAutoFill"], + "restrictionsSafariForceFraudWarning": operationPayload["safariForceFraudWarning"], + "restrictionsSafariAllowJavaScript": operationPayload["safariAllowJavaScript"], + "restrictionsSafariAllowPopups": operationPayload["safariAllowPopups"], + "restrictionsAllowScreenShot": operationPayload["allowScreenShot"], + "restrictionsAllowSharedStream": operationPayload["allowSharedStream"], + "restrictionsAllowUIConfigurationProfileInstallation": operationPayload["allowUIConfigurationProfileInstallation"], + "restrictionsAllowUntrustedTLSPrompt": operationPayload["allowUntrustedTLSPrompt"], + "restrictionsAllowVideoConferencing": operationPayload["allowVideoConferencing"], + "restrictionsAllowVoiceDialing": operationPayload["allowVoiceDialing"], + "restrictionsAllowYouTube": operationPayload["allowYouTube"], + "restrictionsAllowITunes": operationPayload["allowiTunes"], + "restrictionsForceAssistantProfanityFilter": operationPayload["forceAssistantProfanityFilter"], + "restrictionsForceEncryptedBackup": operationPayload["forceEncryptedBackup"], + "restrictionsForceITunesStorePasswordEntry": operationPayload["forceITunesStorePasswordEntry"], + "restrictionsForceLimitAdTracking": operationPayload["forceLimitAdTracking"], + "restrictionsForceAirPlayOutgoingRequestsPairingPassword": operationPayload["forceAirPlayOutgoingRequestsPairingPassword"], + "restrictionsForceAirPlayIncomingRequestsPairingPassword": operationPayload["forceAirPlayIncomingRequestsPairingPassword"], + "restrictionsAllowManagedAppsCloudSync": operationPayload["allowManagedAppsCloudSync"], + "restrictionsAllowEraseContentAndSettings": operationPayload["allowEraseContentAndSettings"], + "restrictionsAllowSpotlightInternetResults": operationPayload["allowSpotlightInternetResults"], + "restrictionsAllowEnablingRestrictions": operationPayload["allowEnablingRestrictions"], + "restrictionsAllowActivityContinuation": operationPayload["allowActivityContinuation"], + "restrictionsAllowEnterpriseBookBackup": operationPayload["allowEnterpriseBookBackup"], + "restrictionsAllowEnterpriseBookMetadataSync": operationPayload["allowEnterpriseBookMetadataSync"], + "restrictionsAllowPodcasts": operationPayload["allowPodcasts"], + "restrictionsAllowDefinitionLookup": operationPayload["allowDefinitionLookup"], + "restrictionsAllowPredictiveKeyboard": operationPayload["allowPredictiveKeyboard"], + "restrictionsAllowAutoCorrection": operationPayload["allowAutoCorrection"], + "restrictionsAllowSpellCheck": operationPayload["allowSpellCheck"], + "restrictionsSafariAcceptCookies": operationPayload["safariAcceptCookies"], + "restrictionsAutonomousSingleAppModePermittedAppIDs": operationPayload["autonomousSingleAppModePermittedAppIDs"] + }; + break; + case iosOperationConstants["WIFI_OPERATION_CODE"]: + payload = { + "wifiHiddenNetwork": operationPayload["hiddenNetwork"], + "wifiSSID": operationPayload["ssid"], + "wifiAutoJoin": operationPayload["autoJoin"], + "wifiProxyType": operationPayload["proxyType"], + "wifiEncryptionType": operationPayload["encryptionType"], + "wifiIsHotSpot": operationPayload["hotspot"], + "wifiDomainName": operationPayload["domainName"], + "wifiServiceProviderRoamingEnabled": operationPayload["serviceProviderRoamingEnabled"], + "wifiDisplayedOperatorName": operationPayload["displayedOperatorName"], + "wifiRoamingConsortiumOIs": operationPayload["roamingConsortiumOIs"], + "wifiPassword": operationPayload["password"], + "wifiPayloadCertUUID": operationPayload["payloadCertificateUUID"], + "wifiProxyServer": operationPayload["proxyServer"], + "wifiProxyPort": operationPayload["proxyPort"], + "wifiProxyUsername": operationPayload["proxyUsername"], + "wifiProxyPassword": operationPayload["proxyPassword"], + "wifiProxyPACURL": operationPayload["proxyPACURL"], + "wifiProxyPACFallbackAllowed": operationPayload["proxyPACFallbackAllowed"], + "wifiNAIRealmNames": operationPayload["nairealmNames"], + "wifiMCCAndMNCs": operationPayload["mccandMNCs"], + "wifiEAPUsername": operationPayload.clientConfiguration["username"], + "wifiAcceptedEAPTypes": operationPayload.clientConfiguration["acceptEAPTypes"], + "wifiEAPPassword": operationPayload.clientConfiguration["userPassword"], + "wifiEAPOneTimePassword": operationPayload.clientConfiguration["oneTimePassword"], + "wifiPayloadCertificateAnchorUUIDs": operationPayload.clientConfiguration["payloadCertificateAnchorUUID"], + "wifiEAPOuterIdentity": operationPayload.clientConfiguration["outerIdentity"], + "wifiTLSTrustedServerNames": operationPayload.clientConfiguration["tlstrustedServerNames"], + "wifiEAPTLSAllowTrustExceptions": operationPayload.clientConfiguration["tlsallowTrustExceptions"], + "wifiEAPTLSCertIsRequired": operationPayload.clientConfiguration["tlscertificateIsRequired"], + "wifiEAPTLSInnerAuthType": operationPayload.clientConfiguration["ttlsinnerAuthentication"], + "wifiEAPFastUsePAC": operationPayload.clientConfiguration["eapfastusePAC"], + "wifiEAPFastProvisionPAC": operationPayload.clientConfiguration["eapfastprovisionPAC"], + "wifiEAPFastProvisionPACAnonymously": operationPayload.clientConfiguration["eapfastprovisionPACAnonymously"], + "wifiEAPSIMNoOfRands": operationPayload.clientConfiguration["eapsimnumberOfRANDs"] + }; + break; + case iosOperationConstants["EMAIL_OPERATION_CODE"]: + payload = { + "emailAccountDescription": operationPayload["emailAccountDescription"], + "emailAccountName": operationPayload["emailAccountName"], + "emailAccountType": operationPayload["emailAccountType"], + "emailAddress": operationPayload["emailAddress"], + "emailIncomingMailServerAuthentication": operationPayload["incomingMailServerAuthentication"], + "emailIncomingMailServerHostname": operationPayload["incomingMailServerHostName"], + "emailIncomingMailServerPort": operationPayload["incomingMailServerPortNumber"], + "emailIncomingUseSSL": operationPayload["incomingMailServerUseSSL"], + "emailIncomingMailServerUsername": operationPayload["incomingMailServerUsername"], + "emailIncomingMailServerPassword": operationPayload["incomingPassword"], + "emailOutgoingMailServerPassword": operationPayload["outgoingPassword"], + "emailOutgoingPasswordSameAsIncomingPassword": operationPayload["outgoingPasswordSameAsIncomingPassword"], + "emailOutgoingMailServerAuthentication": operationPayload["outgoingMailServerAuthentication"], + "emailOutgoingMailServerHostname": operationPayload["outgoingMailServerHostName"], + "emailOutgoingMailServerPort": operationPayload["outgoingMailServerPortNumber"], + "emailOutgoingUseSSL": operationPayload["outgoingMailServerUseSSL"], + "emailOutgoingMailServerUsername": operationPayload["outgoingMailServerUsername"], + "emailPreventMove": operationPayload["preventMove"], + "emailPreventAppSheet": operationPayload["preventAppSheet"], + "emailDisableMailRecentSyncing": operationPayload["disableMailRecentSyncing"], + "emailIncomingMailServerIMAPPathPrefix": operationPayload["incomingMailServerIMAPPathPrefix"], + "emailSMIMEEnabled": operationPayload["smimeenabled"], + "emailSMIMESigningCertificateUUID": operationPayload["smimesigningCertificateUUID"], + "emailSMIMEEncryptionCertificateUUID": operationPayload["smimeencryptionCertificateUUID"], + "emailSMIMEEnablePerMessageSwitch": operationPayload["smimeenablePerMessageSwitch"] + }; + break; + case iosOperationConstants["AIRPLAY_OPERATION_CODE"]: + payload = { + "airplayDestinations": operationPayload["airPlayDestinations"], + "airplayCredentials": operationPayload["airPlayCredentials"] + }; + break; + case iosOperationConstants["LDAP_OPERATION_CODE"]: + payload = { + "ldapAccountDescription": operationPayload["accountDescription"], + "ldapAccountHostname": operationPayload["accountHostName"], + "ldapUseSSL": operationPayload["accountUseSSL"], + "ldapAccountUsername": operationPayload["accountUsername"], + "ldapAccountPassword": operationPayload["accountPassword"], + "ldapSearchSettings": operationPayload["ldapSearchSettings"] + }; + break; + case iosOperationConstants["CALENDAR_OPERATION_CODE"]: + payload = { + "calendarAccountDescription": operationPayload["accountDescription"], + "calendarAccountHostname": operationPayload["hostName"], + "calendarAccountUsername": operationPayload["username"], + "calendarAccountPassword": operationPayload["password"], + "calendarUseSSL": operationPayload["useSSL"], + "calendarAccountPort": operationPayload["port"], + "calendarPrincipalURL": operationPayload["principalURL"] + }; + break; + case iosOperationConstants["CALENDAR_SUBSCRIPTION_OPERATION_CODE"]: + payload = { + "calendarSubscriptionDescription": operationPayload["accountDescription"], + "calendarSubscriptionHostname": operationPayload["hostName"], + "calendarSubscriptionUsername": operationPayload["username"], + "calendarSubscriptionPassword": operationPayload["password"], + "calendarSubscriptionUseSSL": operationPayload["useSSL"] + }; + break; + case iosOperationConstants["APN_OPERATION_CODE"]: + payload = { + "apnConfigurations": operationPayload["apnConfigurations"] + }; + break; + case iosOperationConstants["CELLULAR_OPERATION_CODE"]: + payload = { + "cellularAttachAPNName": operationPayload["attachAPNName"], + "cellularAuthenticationType": operationPayload["authenticationType"], + "cellularUsername": operationPayload["username"], + "cellularPassword": operationPayload["password"], + "cellularAPNConfigurations": operationPayload["apnConfigurations"] + }; + break; + } + return payload; + }; + + privateMethods.generateIOSOperationPayload = function (operationCode, operationData, deviceList) { + var payload; + var operationType; + switch (operationCode) { + case iosOperationConstants["PASSCODE_POLICY_OPERATION_CODE"]: + operationType = operationTypeConstants["PROFILE"]; + payload = { + "operation": { + "forcePIN": operationData["passcodePolicyForcePIN"], + "allowSimple": operationData["passcodePolicyAllowSimple"], + "requireAlphanumeric": operationData["passcodePolicyRequireAlphanumeric"], + "minLength": operationData["passcodePolicyMinLength"], + "minComplexChars": operationData["passcodePolicyMinComplexChars"], + "maxPINAgeInDays": operationData["passcodePolicyMaxPasscodeAgeInDays"], + "pinHistory": operationData["passcodePolicyPasscodeHistory"], + "maxInactivity": operationData["passcodePolicyMaxAutoLock"], + "maxGracePeriod": operationData["passcodePolicyGracePeriod"], + "maxFailedAttempts": operationData["passcodePolicyMaxFailedAttempts"] + } + }; + break; + case iosOperationConstants["WIFI_OPERATION_CODE"]: + operationType = operationTypeConstants["PROFILE"]; + payload = { + "operation": { + "ssid": operationData["wifiSSID"], + "hiddenNetwork": operationData["wifiHiddenNetwork"], + "autoJoin": operationData["wifiAutoJoin"], + "proxyType": operationData["wifiProxyType"], + "encryptionType": operationData["wifiEncryptionType"], + "hotspot": operationData["wifiIsHotSpot"], + "domainName": operationData["wifiDomainName"], + "serviceProviderRoamingEnabled": operationData["wifiServiceProviderRoamingEnabled"], + "displayedOperatorName": operationData["wifiDisplayedOperatorName"], + "roamingConsortiumOIs": operationData["wifiRoamingConsortiumOIs"], + "password": operationData["wifiPassword"], + "clientConfiguration": { + "username": operationData["wifiEAPUsername"], + "acceptEAPTypes": operationData["wifiAcceptedEAPTypes"], + "userPassword": operationData["wifiEAPPassword"], + "oneTimePassword": operationData["wifiEAPOneTimePassword"], + "payloadCertificateAnchorUUID": operationData["wifiPayloadCertificateAnchorUUIDs"], + "outerIdentity": operationData["wifiEAPOuterIdentity"], + "tlstrustedServerNames": operationData["wifiTLSTrustedServerNames"], + "tlsallowTrustExceptions": operationData["wifiEAPTLSAllowTrustExceptions"], + "tlscertificateIsRequired": operationData["wifiEAPTLSCertIsRequired"], + "ttlsinnerAuthentication": operationData["wifiEAPTLSInnerAuthType"], + "eapfastusePAC": operationData["wifiEAPFastUsePAC"], + "eapfastprovisionPAC": operationData["wifiEAPFastProvisionPAC"], + "eapfastprovisionPACAnonymously": operationData["wifiEAPFastProvisionPACAnonymously"], + "eapsimnumberOfRANDs": operationData["wifiEAPSIMNoOfRands"] + }, + "payloadCertificateUUID": operationData["wifiPayloadCertUUID"], + "proxyServer": operationData["wifiProxyServer"], + "proxyPort": operationData["wifiProxyPort"], + "proxyUsername": operationData["wifiProxyUsername"], + "proxyPassword": operationData["wifiProxyPassword"], + "proxyPACURL": operationData["wifiProxyPACURL"], + "proxyPACFallbackAllowed": operationData["wifiProxyPACFallbackAllowed"], + "nairealmNames": operationData["wifiNAIRealmNames"], + "mccandMNCs": operationData["wifiMCCAndMNCs"] + } + }; + break; + case iosOperationConstants["RESTRICTIONS_OPERATION_CODE"]: + operationType = operationTypeConstants["PROFILE"]; + payload = { + "operation": { + "allowAccountModification": operationData["restrictionsAllowAccountModification"], + "allowAddingGameCenterFriends": operationData["restrictionsAllowAddingGameCenterFriends"], + "allowAirDrop": operationData["restrictionsAllowAirDrop"], + "allowAppCellularDataModification": operationData["restrictionsAllowAppCellularDataModification"], + "allowAppInstallation": operationData["restrictionsAllowAppInstallation"], + "allowAppRemoval": operationData["restrictionsAllowAppRemoval"], + "allowAssistant": operationData["restrictionsAllowAssistant"], + "allowAssistantUserGeneratedContent": operationData["restrictionsAllowAssistantUserGeneratedContent"], + "allowAssistantWhileLocked": operationData["restrictionsAllowAssistantWhileLocked"], + "allowBookstore": operationData["restrictionsAllowBookstore"], + "allowBookstoreErotica": operationData["restrictionsAllowBookstoreErotica"], + "allowCamera": operationData["restrictionsAllowCamera"], + "allowChat": operationData["restrictionsAllowChat"], + "allowCloudBackup": operationData["restrictionsAllowCloudBackup"], + "allowCloudDocumentSync": operationData["restrictionsAllowCloudDocumentSync"], + "allowCloudKeychainSync": operationData["restrictionsAllowCloudKeychainSync"], + "allowDiagnosticSubmission": operationData["restrictionsAllowDiagnosticSubmission"], + "allowExplicitContent": operationData["restrictionsAllowExplicitContent"], + "allowFindMyFriendsModification": operationData["restrictionsAllowFindMyFriendsModification"], + "allowFingerprintForUnlock": operationData["restrictionsAllowFingerprintForUnlock"], + "allowGameCenter": operationData["restrictionsAllowGameCenter"], + "allowGlobalBackgroundFetchWhenRoaming": operationData["restrictionsAllowGlobalBackgroundFetchWhenRoaming"], + "allowInAppPurchases": operationData["restrictionsAllowInAppPurchases"], + "allowLockScreenControlCenter": operationData["restrictionsAllowLockScreenControlCenter"], + "allowHostPairing": operationData["restrictionsAllowHostPairing"], + "allowLockScreenNotificationsView": operationData["restrictionsAllowLockScreenNotificationsView"], + "allowLockScreenTodayView": operationData["restrictionsAllowLockScreenTodayView"], + "allowMultiplayerGaming": operationData["restrictionsAllowMultiplayerGaming"], + "allowOpenFromManagedToUnmanaged": operationData["restrictionsAllowOpenFromManagedToUnmanaged"], + "allowOpenFromUnmanagedToManaged": operationData["restrictionsAllowOpenFromUnmanagedToManaged"], + "allowOTAPKIUpdates": operationData["restrictionsAllowOTAPKIUpdates"], + "allowPassbookWhileLocked": operationData["restrictionsAllowPassbookWhileLocked"], + "allowPhotoStream": operationData["restrictionsAllowPhotoStream"], + "allowSafari": operationData["restrictionsAllowSafari"], + "safariAllowAutoFill": operationData["restrictionsSafariAllowAutoFill"], + "safariForceFraudWarning": operationData["restrictionsSafariForceFraudWarning"], + "safariAllowJavaScript": operationData["restrictionsSafariAllowJavaScript"], + "safariAllowPopups": operationData["restrictionsSafariAllowPopups"], + "allowScreenShot": operationData["restrictionsAllowScreenShot"], + "allowSharedStream": operationData["restrictionsAllowSharedStream"], + "allowUIConfigurationProfileInstallation": operationData["restrictionsAllowUIConfigurationProfileInstallation"], + "allowUntrustedTLSPrompt": operationData["restrictionsAllowUntrustedTLSPrompt"], + "allowVideoConferencing": operationData["restrictionsAllowVideoConferencing"], + "allowVoiceDialing": operationData["restrictionsAllowVoiceDialing"], + "allowYouTube": operationData["restrictionsAllowYouTube"], + "allowiTunes": operationData["restrictionsAllowITunes"], + "forceAssistantProfanityFilter": operationData["restrictionsForceAssistantProfanityFilter"], + "forceEncryptedBackup": operationData["restrictionsForceEncryptedBackup"], + "forceITunesStorePasswordEntry": operationData["restrictionsForceITunesStorePasswordEntry"], + "forceLimitAdTracking": operationData["restrictionsForceLimitAdTracking"], + "forceAirPlayOutgoingRequestsPairingPassword": operationData["restrictionsForceAirPlayOutgoingRequestsPairingPassword"], + "forceAirPlayIncomingRequestsPairingPassword": operationData["restrictionsForceAirPlayIncomingRequestsPairingPassword"], + "allowManagedAppsCloudSync": operationData["restrictionsAllowManagedAppsCloudSync"], + "allowEraseContentAndSettings": operationData["restrictionsAllowEraseContentAndSettings"], + "allowSpotlightInternetResults": operationData["restrictionsAllowSpotlightInternetResults"], + "allowEnablingRestrictions": operationData["restrictionsAllowEnablingRestrictions"], + "allowActivityContinuation": operationData["restrictionsAllowActivityContinuation"], + "allowEnterpriseBookBackup": operationData["restrictionsAllowEnterpriseBookBackup"], + "allowEnterpriseBookMetadataSync": operationData["restrictionsAllowEnterpriseBookMetadataSync"], + "allowPodcasts": operationData["restrictionsAllowPodcasts"], + "allowDefinitionLookup": operationData["restrictionsAllowDefinitionLookup"], + "allowPredictiveKeyboard": operationData["restrictionsAllowPredictiveKeyboard"], + "allowAutoCorrection": operationData["restrictionsAllowAutoCorrection"], + "allowSpellCheck": operationData["restrictionsAllowSpellCheck"], + "safariAcceptCookies": operationData["restrictionsSafariAcceptCookies"], + "autonomousSingleAppModePermittedAppIDs": operationData["restrictionsAutonomousSingleAppModePermittedAppIDs"] + } + }; + break; + case iosOperationConstants["EMAIL_OPERATION_CODE"]: + operationType = operationTypeConstants["PROFILE"]; + payload = { + "operation": { + "emailAccountDescription": operationData["emailAccountDescription"], + "emailAccountName": operationData["emailAccountName"], + "emailAccountType": operationData["emailAccountType"], + "emailAddress": operationData["emailAddress"], + "incomingMailServerAuthentication": operationData["emailIncomingMailServerAuthentication"], + "incomingMailServerHostName": operationData["emailIncomingMailServerHostname"], + "incomingMailServerPortNumber": operationData["emailIncomingMailServerPort"], + "incomingMailServerUseSSL": operationData["emailIncomingUseSSL"], + "incomingMailServerUsername": operationData["emailIncomingMailServerUsername"], + "incomingPassword": operationData["emailIncomingMailServerPassword"], + "outgoingPassword": operationData["emailOutgoingMailServerPassword"], + "outgoingPasswordSameAsIncomingPassword": operationData["emailOutgoingPasswordSameAsIncomingPassword"], + "outgoingMailServerAuthentication": operationData["emailOutgoingMailServerAuthentication"], + "outgoingMailServerHostName": operationData["emailOutgoingMailServerHostname"], + "outgoingMailServerPortNumber": operationData["emailOutgoingMailServerPort"], + "outgoingMailServerUseSSL": operationData["emailOutgoingUseSSL"], + "outgoingMailServerUsername": operationData["emailOutgoingMailServerUsername"], + "preventMove": operationData["emailPreventMove"], + "preventAppSheet": operationData["emailPreventAppSheet"], + "disableMailRecentSyncing": operationData["emailDisableMailRecentSyncing"], + "incomingMailServerIMAPPathPrefix": operationData["emailIncomingMailServerIMAPPathPrefix"], + "smimeenabled": operationData["emailSMIMEEnabled"], + "smimesigningCertificateUUID": operationData["emailSMIMESigningCertificateUUID"], + "smimeencryptionCertificateUUID": operationData["emailSMIMEEncryptionCertificateUUID"], + "smimeenablePerMessageSwitch": operationData["emailSMIMEEnablePerMessageSwitch"] + } + }; + break; + case iosOperationConstants["AIRPLAY_OPERATION_CODE"]: + operationType = operationTypeConstants["PROFILE"]; + payload = { + "operation": { + "airPlayDestinations": operationData["airplayDestinations"], + "airPlayCredentials": operationData["airplayCredentials"] + } + }; + break; + case iosOperationConstants["LDAP_OPERATION_CODE"]: + operationType = operationTypeConstants["PROFILE"]; + payload = { + "operation": { + "accountDescription": operationData["ldapAccountDescription"], + "accountHostName": operationData["ldapAccountHostname"], + "accountUseSSL": operationData["ldapUseSSL"], + "accountUsername": operationData["ldapAccountUsername"], + "accountPassword": operationData["ldapAccountPassword"], + "ldapSearchSettings": operationData["ldapSearchSettings"] + } + }; + break; + case iosOperationConstants["CALENDAR_OPERATION_CODE"]: + operationType = operationTypeConstants["PROFILE"]; + payload = { + "operation": { + "accountDescription": operationData["calendarAccountDescription"], + "hostName": operationData["calendarAccountHostname"], + "username": operationData["calendarAccountUsername"], + "password": operationData["calendarAccountPassword"], + "useSSL": operationData["calendarUseSSL"], + "port": operationData["calendarAccountPort"], + "principalURL": operationData["calendarPrincipalURL"] + } + }; + break; + case iosOperationConstants["CALENDAR_SUBSCRIPTION_OPERATION_CODE"]: + operationType = operationTypeConstants["PROFILE"]; + payload = { + "operation": { + "accountDescription": operationData["calendarSubscriptionDescription"], + "hostName": operationData["calendarSubscriptionHostname"], + "username": operationData["calendarSubscriptionUsername"], + "password": operationData["calendarSubscriptionPassword"], + "useSSL": operationData["calendarSubscriptionUseSSL"] + } + }; + break; + case iosOperationConstants["APN_OPERATION_CODE"]: + operationType = operationTypeConstants["PROFILE"]; + payload = { + "operation": { + "apnConfigurations": operationData["apnConfigurations"] + } + }; + break; + case iosOperationConstants["CELLULAR_OPERATION_CODE"]: + operationType = operationTypeConstants["PROFILE"]; + payload = { + "operation": { + "attachAPNName": operationData["cellularAttachAPNName"], + "authenticationType": operationData["cellularAuthenticationType"], + "username": operationData["cellularUsername"], + "password": operationData["cellularPassword"], + "apnConfigurations": operationData["cellularAPNConfigurations"] + } + }; + break; + case iosOperationConstants["NOTIFICATION_OPERATION_CODE"]: + operationType = operationTypeConstants["PROFILE"]; + payload = { + "operation": { + "message": operationData["message"] + } + }; + break; + default: + // If the operation is neither of above, it is a command operation + operationType = operationTypeConstants["COMMAND"]; + // Operation payload of a command operation is simply an array of device IDs + payload = deviceList; + } + + if (operationType == operationTypeConstants["PROFILE"] && deviceList) { + payload["deviceIDs"] = deviceList; + } + return payload; + }; + + /** + * Convert the android platform specific code to the generic payload. + * TODO: think of the possibility to follow a pattern to the key name (namespace?) + * @param operationCode + * @param operationPayload + * @returns {{}} + */ + privateMethods.generateGenericPayloadFromAndroidPayload = function (operationCode, operationPayload) { + var payload = {}; + operationPayload = JSON.parse(operationPayload); + switch (operationCode) { + case androidOperationConstants["PASSCODE_POLICY_OPERATION_CODE"]: + payload = { + "passcodePolicyAllowSimple": operationPayload["allowSimple"], + "passcodePolicyRequireAlphanumeric": operationPayload["requireAlphanumeric"], + "passcodePolicyMinLength": operationPayload["minLength"], + "passcodePolicyMinComplexChars": operationPayload["minComplexChars"], + "passcodePolicyMaxPasscodeAgeInDays": operationPayload["maxPINAgeInDays"], + "passcodePolicyPasscodeHistory": operationPayload["pinHistory"], + "passcodePolicyMaxFailedAttempts": operationPayload["maxFailedAttempts"] + }; + break; + case androidOperationConstants["CAMERA_OPERATION_CODE"]: + payload = { + "cameraEnabled": operationPayload["enabled"] + }; + break; + case androidOperationConstants["ENCRYPT_STORAGE_OPERATION_CODE"]: + payload = { + "encryptStorageEnabled": operationPayload["encrypted"] + }; + break; + case androidOperationConstants["WIFI_OPERATION_CODE"]: + payload = { + "wifiSSID": operationPayload["ssid"], + "wifiPassword": operationPayload["password"] + }; + break; + } + return payload; + }; + + privateMethods.generateAndroidOperationPayload = function (operationCode, operationData, deviceList) { + var payload; + var operationType; + switch (operationCode) { + case androidOperationConstants["CAMERA_OPERATION_CODE"]: + operationType = operationTypeConstants["PROFILE"]; + payload = { + "operation": { + "enabled": operationData["cameraEnabled"] + } + }; + break; + case androidOperationConstants["CHANGE_LOCK_CODE_OPERATION_CODE"]: + operationType = operationTypeConstants["PROFILE"]; + payload = { + "operation": { + "lockCode": operationData["lockCode"] + } + }; + break; + case androidOperationConstants["ENCRYPT_STORAGE_OPERATION_CODE"]: + operationType = operationTypeConstants["PROFILE"]; + payload = { + "operation": { + "encrypted": operationData["encryptStorageEnabled"] + } + }; + break; + case androidOperationConstants["NOTIFICATION_OPERATION_CODE"]: + operationType = operationTypeConstants["PROFILE"]; + payload = { + "operation": { + "message": operationData["message"] + } + }; + break; + case androidOperationConstants["WIPE_OPERATION_CODE"]: + operationType = operationTypeConstants["PROFILE"]; + payload = { + "operation": { + "pin": operationData["pin"] + } + }; + break; + case androidOperationConstants["WIFI_OPERATION_CODE"]: + operationType = operationTypeConstants["PROFILE"]; + payload = { + "operation": { + "ssid": operationData["wifiSSID"], + "password": operationData["wifiPassword"] + } + }; + break; + case androidOperationConstants["PASSCODE_POLICY_OPERATION_CODE"]: + operationType = operationTypeConstants["PROFILE"]; + payload = { + "operation": { + "allowSimple": operationData["passcodePolicyAllowSimple"], + "requireAlphanumeric": operationData["passcodePolicyRequireAlphanumeric"], + "minLength": operationData["passcodePolicyMinLength"], + "minComplexChars": operationData["passcodePolicyMinComplexChars"], + "maxPINAgeInDays": operationData["passcodePolicyMaxPasscodeAgeInDays"], + "pinHistory": operationData["passcodePolicyPasscodeHistory"], + "maxFailedAttempts": operationData["passcodePolicyMaxFailedAttempts"] + } + }; + break; + default: + // If the operation is neither of above, it is a command operation + operationType = operationTypeConstants["COMMAND"]; + // Operation payload of a command operation is simply an array of device IDs + payload = deviceList; + } + + if (operationType == operationTypeConstants["PROFILE"] && deviceList) { + payload["deviceIDs"] = deviceList; + } + + return payload; + }; + + publicMethods.getAndroidServiceEndpoint = function (operationCode) { + var featureMap = { + "WIFI": "wifi", + "CAMERA": "camera", + "DEVICE_LOCK": "lock", + "DEVICE_LOCATION": "location", + "CLEAR_PASSWORD": "clear-password", + "APPLICATION_LIST": "get-application-list", + "DEVICE_RING": "ring-device", + "DEVICE_REBOOT": "reboot-device", + "UPGRADE_FIRMWARE": "upgrade-firmware", + "DEVICE_MUTE": "mute", + "NOTIFICATION": "notification", + "ENCRYPT_STORAGE": "encrypt", + "CHANGE_LOCK_CODE": "change-lock-code", + "WEBCLIP": "webclip", + "INSTALL_APPLICATION": "install-application", + "UNINSTALL_APPLICATION": "uninstall-application", + "BLACKLIST_APPLICATIONS": "blacklist-applications", + "PASSCODE_POLICY": "password-policy", + "ENTERPRISE_WIPE": "enterprise-wipe", + "WIPE_DATA": "wipe-data" + }; + return "/mdm-android-agent/operation/" + featureMap[operationCode]; + }; + + /** + * Convert the windows platform specific code to the generic payload. + * TODO: think of the possibility to follow a pattern to the key name (namespace?) + * @param operationCode + * @param operationPayload + * @returns {{}} + */ + privateMethods.generateGenericPayloadFromWindowsPayload = function (operationCode, operationPayload) { + var payload = {}; + operationPayload = JSON.parse(operationPayload); + switch (operationCode) { + case windowsOperationConstants["PASSCODE_POLICY_OPERATION_CODE"]: + payload = { + "passcodePolicyAllowSimple": operationPayload["allowSimple"], + "passcodePolicyRequireAlphanumeric": operationPayload["requireAlphanumeric"], + "passcodePolicyMinLength": operationPayload["minLength"], + "passcodePolicyMinComplexChars": operationPayload["minComplexChars"], + "passcodePolicyMaxPasscodeAgeInDays": operationPayload["maxPINAgeInDays"], + "passcodePolicyPasscodeHistory": operationPayload["pinHistory"], + "passcodePolicyMaxFailedAttempts": operationPayload["maxFailedAttempts"] + }; + break; + case windowsOperationConstants["CAMERA_OPERATION_CODE"]: + payload = { + "cameraEnabled": operationPayload["enabled"] + }; + break; + case windowsOperationConstants["ENCRYPT_STORAGE_OPERATION_CODE"]: + payload = { + "encryptStorageEnabled": operationPayload["encrypted"] + }; + break; + } + return payload; + }; + + privateMethods.generateWindowsOperationPayload = function (operationCode, operationData, deviceList) { + var payload; + var operationType; + switch (operationCode) { + case windowsOperationConstants["CAMERA_OPERATION_CODE"]: + operationType = operationTypeConstants["PROFILE"]; + payload = { + "operation": { + "enabled": operationData["cameraEnabled"] + } + }; + break; + case windowsOperationConstants["CHANGE_LOCK_CODE_OPERATION_CODE"]: + operationType = operationTypeConstants["PROFILE"]; + payload = { + "operation": { + "lockCode": operationData["lockCode"] + } + }; + break; + case windowsOperationConstants["ENCRYPT_STORAGE_OPERATION_CODE"]: + operationType = operationTypeConstants["PROFILE"]; + payload = { + "operation": { + "encrypted": operationData["encryptStorageEnabled"] + } + }; + break; + case windowsOperationConstants["NOTIFICATION_OPERATION_CODE"]: + operationType = operationTypeConstants["PROFILE"]; + payload = { + "operation": { + "message": operationData["message"] + } + }; + break; + case windowsOperationConstants["PASSCODE_POLICY_OPERATION_CODE"]: + operationType = operationTypeConstants["PROFILE"]; + payload = { + "operation": { + "allowSimple": operationData["passcodePolicyAllowSimple"], + "requireAlphanumeric": operationData["passcodePolicyRequireAlphanumeric"], + "minLength": operationData["passcodePolicyMinLength"], + "minComplexChars": operationData["passcodePolicyMinComplexChars"], + "maxPINAgeInDays": operationData["passcodePolicyMaxPasscodeAgeInDays"], + "pinHistory": operationData["passcodePolicyPasscodeHistory"], + "maxFailedAttempts": operationData["passcodePolicyMaxFailedAttempts"] + } + }; + break; + default: + // If the operation is neither of above, it is a command operation + operationType = operationTypeConstants["COMMAND"]; + // Operation payload of a command operation is simply an array of device IDs + payload = deviceList; + } + + if (operationType == operationTypeConstants["PROFILE"] && deviceList) { + payload["deviceIDs"] = deviceList; + } + + return payload; + }; + + + publicMethods.getWindowsServiceEndpoint = function (operationCode) { + var featureMap = { + "CAMERA": "camera", + "DEVICE_LOCK": "lock", + "DEVICE_LOCATION": "location", + "CLEAR_PASSWORD": "clear-password", + "APPLICATION_LIST": "get-application-list", + "DEVICE_RING": "ring-device", + "DEVICE_REBOOT": "reboot-device", + "UPGRADE_FIRMWARE": "upgrade-firmware", + "DEVICE_MUTE": "mute", + "LOCK_RESET": "lock-reset", + "NOTIFICATION": "notification", + "ENCRYPT_STORAGE": "encrypt", + "CHANGE_LOCK_CODE": "change-lock-code", + "WEBCLIP": "webclip", + "INSTALL_APPLICATION": "install-application", + "UNINSTALL_APPLICATION": "uninstall-application", + "BLACKLIST_APPLICATIONS": "blacklist-applications", + "PASSCODE_POLICY": "password-policy", + "ENTERPRISE_WIPE": "enterprise-wipe", + "WIPE_DATA": "wipe-data", + "DISENROLL": "disenroll" + }; + return "/mdm-windows-agent/services/windows/operation/" + featureMap[operationCode]; + }; + /** + * Get the icon for the featureCode + * @param operationCode + * @returns icon class + */ + publicMethods.getAndroidIconForFeature = function (operationCode) { + var featureMap = { + "DEVICE_LOCK": "fw-lock", + "DEVICE_LOCATION": "fw-map-location", + "CLEAR_PASSWORD": "fw-key", + "ENTERPRISE_WIPE": "fw-clear", + "WIPE_DATA": "fw-database", + "DEVICE_RING": "fw-dial-up", + "DEVICE_REBOOT": "fw-refresh", + "UPGRADE_FIRMWARE": "fw-up-arrow", + "DEVICE_MUTE": "fw-incoming-call", + "NOTIFICATION": "fw-message", + "CHANGE_LOCK_CODE": "fw-security" + }; + return featureMap[operationCode]; + }; + + /** + * Get the icon for the featureCode + * @param operationCode + * @returns icon class + */ + publicMethods.getWindowsIconForFeature = function (operationCode) { + var featureMap = { + "DEVICE_LOCK": "fw-lock", + "DEVICE_LOCATION": "fw-map-location", + "DISENROLL": "fw-delete", + "WIPE_DATA": "fw-clear", + "DEVICE_RING": "fw-dial-up", + "DEVICE_REBOOT": "fw-refresh", + "UPGRADE_FIRMWARE": "fw-up-arrow", + "DEVICE_MUTE": "fw-incoming-call", + "NOTIFICATION": "fw-message", + "LOCK_RESET": "fw-key" + }; + return featureMap[operationCode]; + }; + + /** + * Get the icon for the featureCode + * @param operationCode + * @returns icon class + */ + publicMethods.getIOSIconForFeature = function (operationCode) { + var featureMap = { + "DEVICE_LOCK": "fw-lock", + "LOCATION": "fw-map-location", + "ENTERPRISE_WIPE": "fw-clear", + "NOTIFICATION": "fw-message", + "ALARM": "fw-dial-up" + }; + return featureMap[operationCode]; + }; + + /** + * Filter a list by a data attribute. + * @param prop + * @param val + * @returns {Array} + */ + $.fn.filterByData = function (prop, val) { + return this.filter( + function () { + return $(this).data(prop) == val; + } + ); + }; + + /** + * Method to generate Platform specific operation payload. + * + * @param platformType Platform Type of the profile + * @param operationCode Operation Codes to generate the profile from + * @param deviceList Optional device list to include in payload body for operations + * @returns {*} + */ + publicMethods.generatePayload = function (platformType, operationCode, deviceList) { + var payload; + var operationData = {}; + // capturing form input data designated by .operationDataKeys + $(".operation-data").filterByData("operation-code", operationCode).find(".operationDataKeys").each( + function () { + var operationDataObj = $(this); + var key = operationDataObj.data("key"); + var value; + if (operationDataObj.is(":text") || operationDataObj.is("textarea") || + operationDataObj.is(":password")) { + value = operationDataObj.val(); + } else if (operationDataObj.is(":checkbox")) { + value = operationDataObj.is(":checked"); + } else if (operationDataObj.is("select")) { + value = operationDataObj.find("option:selected").attr("value"); + } else if (operationDataObj.hasClass("grouped-array-input")) { + value = []; + var childInput; + var childInputValue; + if (operationDataObj.hasClass("one-column-input-array")) { + $(".child-input", this).each(function () { + childInput = $(this); + if (childInput.is(":text") || childInput.is("textarea") || childInput.is(":password")) { + childInputValue = childInput.val(); + } else if (childInput.is(":checkbox")) { + childInputValue = childInput.is(":checked"); + } else if (childInput.is("select")) { + childInputValue = childInput.find("option:selected").attr("value"); + } + // push to value + value.push(childInputValue); + }); + } else if (operationDataObj.hasClass("valued-check-box-array")) { + $(".child-input", this).each(function () { + childInput = $(this); + if (childInput.is(":checked")) { + // get associated value with check-box + childInputValue = childInput.data("value"); + // push to value + value.push(childInputValue); + } + }); + } else if (operationDataObj.hasClass("multi-column-joined-input-array")) { + var columnCount = operationDataObj.data("column-count"); + var inputCount = 0; + var joinedInput; + $(".child-input", this).each(function () { + childInput = $(this); + if (childInput.is(":text") || childInput.is("textarea") || childInput.is(":password")) { + childInputValue = childInput.val(); + } else if (childInput.is(":checkbox")) { + childInputValue = childInput.is(":checked"); + } else if (childInput.is("select")) { + childInputValue = childInput.find("option:selected").attr("value"); + } + inputCount++; + if (inputCount % columnCount == 1) { + // initialize joinedInput value + joinedInput = ""; + // append childInputValue to joinedInput + joinedInput += childInputValue; + } else if ((inputCount % columnCount) >= 2) { + // append childInputValue to joinedInput + joinedInput += childInputValue; + } else { + // append childInputValue to joinedInput + joinedInput += childInputValue; + // push to value + value.push(joinedInput); + } + }); + } else if (operationDataObj.hasClass("multi-column-key-value-pair-array")) { + columnCount = operationDataObj.data("column-count"); + inputCount = 0; + var childInputKey; + var keyValuePairJson; + $(".child-input", this).each(function () { + childInput = $(this); + childInputKey = childInput.data("child-key"); + if (childInput.is(":text") || childInput.is("textarea") || childInput.is(":password")) { + childInputValue = childInput.val(); + } else if (childInput.is(":checkbox")) { + childInputValue = childInput.is(":checked"); + } else if (childInput.is("select")) { + childInputValue = childInput.find("option:selected").attr("value"); + } + inputCount++; + if ((inputCount % columnCount) == 1) { + // initialize keyValuePairJson value + keyValuePairJson = {}; + // set key-value-pair + keyValuePairJson[childInputKey] = childInputValue; + } else if ((inputCount % columnCount) >= 2) { + // set key-value-pair + keyValuePairJson[childInputKey] = childInputValue; + } else { + // set key-value-pair + keyValuePairJson[childInputKey] = childInputValue; + // push to value + value.push(keyValuePairJson); + } + }); + } + } + operationData[key] = value; + } + ); + switch (platformType) { + case platformTypeConstants["ANDROID"]: + payload = privateMethods.generateAndroidOperationPayload(operationCode, operationData, deviceList); + break; + case platformTypeConstants["IOS"]: + payload = privateMethods.generateIOSOperationPayload(operationCode, operationData, deviceList); + break; + case platformTypeConstants["WINDOWS"]: + payload = privateMethods.generateWindowsOperationPayload(operationCode, operationData, deviceList); + break; + } + return payload; + }; + + /** + * Method to populate the Platform specific operation payload. + * + * @param platformType Platform Type of the profile + * @param operationCode Operation Codes to generate the profile from + * @param operationPayload payload + * @returns {*} + */ + publicMethods.populateUI = function (platformType, operationCode, operationPayload) { + var uiPayload; + switch (platformType) { + case platformTypeConstants["ANDROID"]: + uiPayload = privateMethods.generateGenericPayloadFromAndroidPayload(operationCode, operationPayload); + break; + case platformTypeConstants["IOS"]: + uiPayload = privateMethods.generateGenericPayloadFromIOSPayload(operationCode, operationPayload); + break; + case platformTypeConstants["WINDOWS"]: + uiPayload = privateMethods.generateGenericPayloadFromWindowsPayload(operationCode, operationPayload); + break; + } + // capturing form input data designated by .operationDataKeys + $(".operation-data").filterByData("operation-code", operationCode).find(".operationDataKeys").each( + function () { + var operationDataObj = $(this); + //TODO :remove + //operationDataObj.prop('disabled', true) + var key = operationDataObj.data("key"); + // retrieve corresponding input value associated with the key + var value = uiPayload[key]; + // populating input value according to the type of input + if (operationDataObj.is(":text") || + operationDataObj.is("textarea") || + operationDataObj.is(":password")) { + operationDataObj.val(value); + } else if (operationDataObj.is(":checkbox")) { + operationDataObj.prop("checked", value); + } else if (operationDataObj.is("select")) { + operationDataObj.val(value); + /* trigger a change of value, so that if slidable panes exist, + make them slide-down or slide-up accordingly */ + operationDataObj.trigger("change"); + } else if (operationDataObj.hasClass("grouped-array-input")) { + // then value is complex + var i, childInput; + var childInputIndex = 0; + // var childInputValue; + if (operationDataObj.hasClass("one-column-input-array")) { + // generating input fields to populate complex value + for (i = 0; i < value.length; ++i) { + operationDataObj.parent().find("a").filterByData("click-event", "add-form").click(); + } + // traversing through each child input + $(".child-input", this).each(function () { + childInput = $(this); + var childInputValue = value[childInputIndex]; + // populating extracted value in the UI according to the input type + if (childInput.is(":text") || + childInput.is("textarea") || + childInput.is(":password") || + childInput.is("select")) { + childInput.val(childInputValue); + } else if (childInput.is(":checkbox")) { + operationDataObj.prop("checked", childInputValue); + } + // incrementing childInputIndex + childInputIndex++; + }); + } else if (operationDataObj.hasClass("valued-check-box-array")) { + // traversing through each child input + $(".child-input", this).each(function () { + childInput = $(this); + // check if corresponding value of current checkbox exists in the array of values + if (value.indexOf(childInput.data("value")) != -1) { + // if YES, set checkbox as checked + childInput.prop("checked", true); + } + }); + } else if (operationDataObj.hasClass("multi-column-joined-input-array")) { + // generating input fields to populate complex value + for (i = 0; i < value.length; ++i) { + operationDataObj.parent().find("a").filterByData("click-event", "add-form").click(); + } + var columnCount = operationDataObj.data("column-count"); + var multiColumnJoinedInputArrayIndex = 0; + // handling scenarios specifically + if (operationDataObj.attr("id") == "wifi-mcc-and-mncs") { + // traversing through each child input + $(".child-input", this).each(function () { + childInput = $(this); + var multiColumnJoinedInput = value[multiColumnJoinedInputArrayIndex]; + var childInputValue; + if ((childInputIndex % columnCount) == 0) { + childInputValue = multiColumnJoinedInput.substring(3, 0) + } else { + childInputValue = multiColumnJoinedInput.substring(3); + // incrementing childInputIndex + multiColumnJoinedInputArrayIndex++; + } + // populating extracted value in the UI according to the input type + if (childInput.is(":text") || + childInput.is("textarea") || + childInput.is(":password") || + childInput.is("select")) { + childInput.val(childInputValue); + } else if (childInput.is(":checkbox")) { + operationDataObj.prop("checked", childInputValue); + } + // incrementing childInputIndex + childInputIndex++; + }); + } + } else if (operationDataObj.hasClass("multi-column-key-value-pair-array")) { + // generating input fields to populate complex value + for (i = 0; i < value.length; ++i) { + operationDataObj.parent().find("a").filterByData("click-event", "add-form").click(); + } + columnCount = operationDataObj.data("column-count"); + var multiColumnKeyValuePairArrayIndex = 0; + // traversing through each child input + $(".child-input", this).each(function () { + childInput = $(this); + var multiColumnKeyValuePair = value[multiColumnKeyValuePairArrayIndex]; + var childInputKey = childInput.data("child-key"); + var childInputValue = multiColumnKeyValuePair[childInputKey]; + // populating extracted value in the UI according to the input type + if (childInput.is(":text") || + childInput.is("textarea") || + childInput.is(":password") || + childInput.is("select")) { + childInput.val(childInputValue); + } else if (childInput.is(":checkbox")) { + operationDataObj.prop("checked", childInputValue); + } + // incrementing multiColumnKeyValuePairArrayIndex for the next row of inputs + if ((childInputIndex % columnCount) == (columnCount - 1)) { + multiColumnKeyValuePairArrayIndex++; + } + // incrementing childInputIndex + childInputIndex++; + }); + } + } + } + ); + }; + + /** + * generateProfile method is only used for policy-creation UIs. + * + * @param platformType Platform Type of the profile + * @param operationCodes Operation codes to generate the profile from + * @returns {{}} + */ + publicMethods.generateProfile = function (platformType, operationCodes) { + var generatedProfile = {}; + for (var i = 0; i < operationCodes.length; ++i) { + var operationCode = operationCodes[i]; + var payload = publicMethods.generatePayload(platformType, operationCode, null); + generatedProfile[operationCode] = payload["operation"]; + } + return generatedProfile; + }; + + /** + * populateProfile method is used to populate the html ui with saved payload. + * + * @param platformType Platform Type of the profile + * @param payload List of profileFeatures + * @returns [] configuredOperations array + */ + publicMethods.populateProfile = function (platformType, payload) { + var i, configuredOperations = []; + for (i = 0; i < payload.length; ++i) { + var configuredFeature = payload[i]; + var featureCode = configuredFeature["featureCode"]; + var operationPayload = configuredFeature["content"]; + //push the feature-code to the configuration array + configuredOperations.push(featureCode); + publicMethods.populateUI(platformType, featureCode, operationPayload); + } + return configuredOperations; + }; + + return publicMethods; +}(); diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/src/main/resources/jaggeryapps/devicemgt/app/units/mdm.unit.platform.configuration/configuration.hbs b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/src/main/resources/jaggeryapps/devicemgt/app/units/mdm.unit.platform.configuration/configuration.hbs new file mode 100644 index 0000000000..293177871e --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/src/main/resources/jaggeryapps/devicemgt/app/units/mdm.unit.platform.configuration/configuration.hbs @@ -0,0 +1,484 @@ +
      +
      + +
      +
      + General and Platform Specific Server Settings for the Tenant +
      +
      +
      +
      + + +
      + + + +
      +
      +
      + +

      + Policy Compliance Monitoring +
      +

      +
      + + +
      +
      + +
      +
      + + +
      +
      + + + +
      +
      +
      + +

      + Communication Protocol Configuration +
      +

      +
      + + +
      + +
      +
      + + +
      +
      + +
      +
      + + +
      + +
      + + +
      +
      +

      + End User License Agreement ( EULA ) +
      +

      + +
      + +
      +
      + +
      +
      +
      +
      + + + +
      +
      +
      + +

      + iOS SCEP Certificate Configurations +
      +

      + + +
      + + +
      + +
      + + +
      + +
      + + +
      + +
      + + +
      + +
      + + +
      + +

      + iOS Profile Configurations +
      +

      + + +
      + + +
      + +

      + iOS MDM Configurations +
      +

      + + +
      +
      + + +
      + +
      + + +
      + +
      + + +
      + +
      + + +
      + +

      + iOS APNS Configurations +
      +

      + + +
      +
      + + +
      + +
      + + +
      + +
      + + +
      + +

      + End User License Agreement (EULA) +
      +

      + +
      + +
      +
      + +
      +
      +
      +
      + + + +
      +
      +
      + +

      + Device Polling Configuration +
      +

      +
      +
      + + +
      +
      +

      + End User License Agreement ( EULA ) +
      +

      +
      + +
      +
      + +
      +
      +
      +
      + +
      +
      +
      +
      + + + +
      +
      +{{#zone "bottomJs"}} + {{js "js/platform-configuration.js"}} +{{/zone}} diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/src/main/resources/jaggeryapps/devicemgt/app/units/mdm.unit.platform.configuration/configuration.js b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/src/main/resources/jaggeryapps/devicemgt/app/units/mdm.unit.platform.configuration/configuration.js new file mode 100644 index 0000000000..354702f18f --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/src/main/resources/jaggeryapps/devicemgt/app/units/mdm.unit.platform.configuration/configuration.js @@ -0,0 +1,9 @@ +function onRequest(context) { + // var log = new Log("platform-configuration-unit backend js"); + var userModule = require("/app/modules/user.js")["userModule"]; + var typesListResponse = userModule.getPlatforms(); + if (typesListResponse["status"] == "success") { + context["types"] = typesListResponse["content"]; + } + return context; +} \ No newline at end of file diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/src/main/resources/jaggeryapps/devicemgt/app/units/mdm.unit.platform.configuration/configuration.json b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/src/main/resources/jaggeryapps/devicemgt/app/units/mdm.unit.platform.configuration/configuration.json new file mode 100644 index 0000000000..be0496bf61 --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/src/main/resources/jaggeryapps/devicemgt/app/units/mdm.unit.platform.configuration/configuration.json @@ -0,0 +1,4 @@ +{ + "version" : "1.0.0", + "extends": "cdmf.unit.platform.configuration" +} \ No newline at end of file diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/src/main/resources/jaggeryapps/devicemgt/app/units/mdm.unit.platform.configuration/public/js/platform-configuration.js b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/src/main/resources/jaggeryapps/devicemgt/app/units/mdm.unit.platform.configuration/public/js/platform-configuration.js new file mode 100644 index 0000000000..7bbbad0b42 --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/src/main/resources/jaggeryapps/devicemgt/app/units/mdm.unit.platform.configuration/public/js/platform-configuration.js @@ -0,0 +1,878 @@ +/* + * Copyright (c) 2016, 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. + */ + +/** + * Checks if provided input is valid against RegEx input. + * + * @param regExp Regular expression + * @param inputString Input string to check + * @returns {boolean} Returns true if input matches RegEx + */ +function inputIsValid(regExp, inputString) { + return regExp.test(inputString); +} + +/** + * Checks if provided input is valid against RegEx input. + * + * @param regExp Regular expression + * @param inputString Input string to check + * @returns {boolean} Returns true if input matches RegEx + */ +function isPositiveInteger(str) { + return /^\+?(0|[1-9]\d*)$/.test(str); +} + +/** + * Get valid param. + * + * @param certificate + * @param cached param (in the registry) + * @returns {String} Returns the valid param + */ +function validateCertificateParams(param, cachedParam) { + if (param == '' && cachedParam != null) { + return cachedParam; + } else { + return param; + } +} + +/** + * Checks if an email address has the valid format or not. + * + * @param email Email address + * @returns {boolean} true if email has the valid format, otherwise false. + */ +function emailIsValid(email) { + var regExp = /^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/; + return regExp.test(email); +} + +var iOSMDMCertificateName = null; +var iOSMDMCertificate = null; +var iOSAPNSCertificateName = null; +var iOSAPNSCertificate = null; + +var notifierTypeConstants = { + "LOCAL": "1", + "GCM": "2" +}; +// Constants to define platform types available +var platformTypeConstants = { + "ANDROID": "android", + "IOS": "ios", + "WINDOWS": "windows" +}; + +var responseCodes = { + "CREATED": "Created", + "SUCCESS": "201", + "INTERNAL_SERVER_ERROR": "Internal Server Error" +}; + +var configParams = { + "NOTIFIER_TYPE": "notifierType", + "NOTIFIER_FREQUENCY": "notifierFrequency", + "GCM_API_KEY": "gcmAPIKey", + "GCM_SENDER_ID": "gcmSenderId", + "ANDROID_EULA": "androidEula", + "IOS_EULA": "iosEula", + "CONFIG_COUNTRY": "configCountry", + "CONFIG_STATE": "configState", + "CONFIG_LOCALITY": "configLocality", + "CONFIG_ORGANIZATION": "configOrganization", + "CONFIG_ORGANIZATION_UNIT": "configOrganizationUnit", + "MDM_CERT_PASSWORD": "MDMCertPassword", + "MDM_CERT_TOPIC_ID": "MDMCertTopicID", + "APNS_CERT_PASSWORD": "APNSCertPassword", + "MDM_CERT": "MDMCert", + "MDM_CERT_NAME": "MDMCertName", + "APNS_CERT": "APNSCert", + "APNS_CERT_NAME": "APNSCertName", + "ORG_DISPLAY_NAME": "organizationDisplayName", + "GENERAL_EMAIL_HOST": "emailHost", + "GENERAL_EMAIL_PORT": "emailPort", + "GENERAL_EMAIL_USERNAME": "emailUsername", + "GENERAL_EMAIL_PASSWORD": "emailPassword", + "GENERAL_EMAIL_SENDER_ADDRESS": "emailSender", + "GENERAL_EMAIL_TEMPLATE": "emailTemplate", + "COMMON_NAME": "commonName", + "KEYSTORE_PASSWORD": "keystorePassword", + "PRIVATE_KEY_PASSWORD": "privateKeyPassword", + "BEFORE_EXPIRE": "beforeExpire", + "AFTER_EXPIRE": "afterExpire", + "WINDOWS_EULA": "windowsLicense" +}; + +function promptErrorPolicyPlatform(errorMsg) { + var mainErrorMsgWrapper = "#platform-config-main-error-msg"; + var mainErrorMsg = mainErrorMsgWrapper + " span"; + $(mainErrorMsg).text(errorMsg); + $(mainErrorMsgWrapper).show(); +} + +$(document).ready(function () { + + var platformsSupported = $("#typeDiv").attr("typeData"); + $("#gcm-inputs").hide(); + tinymce.init({ + selector: "textarea", + height: 500, + theme: "modern", + plugins: [ + "autoresize", + "advlist autolink lists link image charmap print preview anchor", + "searchreplace visualblocks code fullscreen", + "insertdatetime image table contextmenu paste" + ], + toolbar: "undo redo | styleselect | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | link image" + }); + + var getAndroidConfigAPI = "/mdm-android-agent/configuration"; + var getGeneralConfigAPI = "/devicemgt_admin/configuration"; + var getIosConfigAPI = "/ios/configuration"; + var getWindowsConfigAPI = "/mdm-windows-agent/services/configuration"; + + /** + * Following requests would execute + * on page load event of platform configuration page in WSO2 EMM Console. + * Upon receiving the response, the parameters will be set to the fields, + * in case those configurations are already set. + */ + + if (platformsSupported.indexOf('android') != -1) { + invokerUtil.get( + getAndroidConfigAPI, + function (data) { + data = JSON.parse(data); + if (data != null && data.configuration != null) { + for (var i = 0; i < data.configuration.length; i++) { + var config = data.configuration[i]; + if (config.name == configParams["NOTIFIER_TYPE"]) { + $("#android-config-notifier").val(config.value); + if (config.value != notifierTypeConstants["GCM"]) { + $("#gcm-inputs").hide(); + $("#local-inputs").show(); + } else { + $("#gcm-inputs").show(); + $("#local-inputs").hide(); + } + } else if (config.name == configParams["NOTIFIER_FREQUENCY"]) { + $("input#android-config-notifier-frequency").val(config.value / 1000); + } else if (config.name == configParams["GCM_API_KEY"]) { + $("input#android-config-gcm-api-key").val(config.value); + } else if (config.name == configParams["GCM_SENDER_ID"]) { + $("input#android-config-gcm-sender-id").val(config.value); + } else if (config.name == configParams["ANDROID_EULA"]) { + $("#android-eula").val(config.value); + } + } + } + }, function (data) { + console.log(data); + }); + } + + invokerUtil.get( + getGeneralConfigAPI, + function (data) { + data = JSON.parse(data); + if (data && data.configuration) { + for (var i = 0; i < data.configuration.length; i++) { + var config = data.configuration[i]; + if (config.name == configParams["NOTIFIER_FREQUENCY"]) { + $("input#monitoring-config-frequency").val(config.value / 1000); + } + } + } + }, function (data) { + console.log(data); + }); + + if (platformsSupported.indexOf('windows') != -1) { + invokerUtil.get( + getWindowsConfigAPI, + function (data) { + data = JSON.parse(data); + if (data != null && data.configuration != null) { + for (var i = 0; i < data.configuration.length; i++) { + var config = data.configuration[i]; + if (config.name == configParams["NOTIFIER_FREQUENCY"]) { + $("input#windows-config-notifier-frequency").val(config.value / 1000); + } else if (config.name == configParams["WINDOWS_EULA"]) { + $("#windows-eula").val(config.value); + } + } + } + }, function (data) { + console.log(data); + } + ); + } + + if (platformsSupported.indexOf('ios') != -1) { + invokerUtil.get( + getIosConfigAPI, + function (data) { + data = JSON.parse(data); + if (data != null && data.configuration != null) { + for (var i = 0; i < data.configuration.length; i++) { + var config = data.configuration[i]; + if (config.name == configParams["CONFIG_COUNTRY"]) { + $("input#ios-config-country").val(config.value); + } else if (config.name == configParams["CONFIG_STATE"]) { + $("input#ios-config-state").val(config.value); + } else if (config.name == configParams["CONFIG_LOCALITY"]) { + $("input#ios-config-locality").val(config.value); + } else if (config.name == configParams["CONFIG_ORGANIZATION"]) { + $("input#ios-config-organization").val(config.value); + } else if (config.name == configParams["CONFIG_ORGANIZATION_UNIT"]) { + $("input#ios-config-organization-unit").val(config.value); + } else if (config.name == configParams["MDM_CERT_PASSWORD"]) { + $("input#ios-config-mdm-certificate-password").val(config.value); + } else if (config.name == configParams["MDM_CERT_TOPIC_ID"]) { + $("input#ios-config-mdm-certificate-topic-id").val(config.value); + } else if (config.name == configParams["APNS_CERT_PASSWORD"]) { + $("input#ios-config-apns-certificate-password").val(config.value); + } else if (config.name == configParams["MDM_CERT_NAME"]) { + $("#mdm-cert-file-name").html(config.value); + iOSMDMCertificateName = config.value; + } else if (config.name == configParams["MDM_CERT"]) { + iOSMDMCertificate = config.value; + } else if (config.name == configParams["APNS_CERT_NAME"]) { + $("#apns-cert-file-name").html(config.value); + iOSAPNSCertificateName = config.value; + } else if (config.name == configParams["APNS_CERT"]) { + iOSAPNSCertificate = config.value; + } else if (config.name == configParams["ORG_DISPLAY_NAME"]) { + $("input#ios-org-display-name").val(config.value); + } else if (config.name == configParams["IOS_EULA"]) { + $("#ios-eula").val(config.value); + } + } + } + }, function (data) { + console.log(data); + } + ); + } + + $("select.select2[multiple=multiple]").select2({ + tags: true + }); + + $("#android-config-notifier").change(function () { + var notifierType = $("#android-config-notifier").find("option:selected").attr("value"); + if (notifierType != notifierTypeConstants["GCM"]) { + $("#gcm-inputs").hide(); + $("#local-inputs").show(); + } else { + $("#local-inputs").hide(); + $("#gcm-inputs").show(); + } + }); + + /** + * Following click function would execute + * when a user clicks on "Save" button + * on Android platform configuration page in WSO2 EMM Console. + */ + $("button#save-android-btn").click(function () { + var notifierType = $("#android-config-notifier").find("option:selected").attr("value"); + var notifierFrequency = $("input#android-config-notifier-frequency").val(); + var gcmAPIKey = $("input#android-config-gcm-api-key").val(); + var gcmSenderId = $("input#android-config-gcm-sender-id").val(); + var androidLicense = tinymce.get('android-eula').getContent(); + + var errorMsgWrapper = "#android-config-error-msg"; + var errorMsg = "#android-config-error-msg span"; + if (notifierType == notifierTypeConstants["LOCAL"] && !notifierFrequency) { + $(errorMsg).text("Notifier frequency is a required field. It cannot be empty."); + $(errorMsgWrapper).removeClass("hidden"); + } else if (notifierType == notifierTypeConstants["LOCAL"] && !isPositiveInteger(notifierFrequency)) { + $(errorMsg).text("Provided notifier frequency is invalid. "); + $(errorMsgWrapper).removeClass("hidden"); + } else if (notifierType == notifierTypeConstants["GCM"] && !gcmAPIKey) { + $(errorMsg).text("GCM API Key is a required field. It cannot be empty."); + $(errorMsgWrapper).removeClass("hidden"); + } else if (notifierType == notifierTypeConstants["GCM"] && !gcmSenderId) { + $(errorMsg).text("GCM Sender ID is a required field. It cannot be empty."); + $(errorMsgWrapper).removeClass("hidden"); + } else { + + var addConfigFormData = {}; + var configList = new Array(); + + var type = { + "name": configParams["NOTIFIER_TYPE"], + "value": notifierType, + "contentType": "text" + }; + + var frequency = { + "name": configParams["NOTIFIER_FREQUENCY"], + "value": String(notifierFrequency * 1000), + "contentType": "text" + }; + + var gcmKey = { + "name": configParams["GCM_API_KEY"], + "value": gcmAPIKey, + "contentType": "text" + }; + + var gcmId = { + "name": configParams["GCM_SENDER_ID"], + "value": gcmSenderId, + "contentType": "text" + }; + + var androidEula = { + "name": configParams["ANDROID_EULA"], + "value": androidLicense, + "contentType": "text" + }; + + configList.push(type); + configList.push(frequency); + configList.push(androidEula); + if (notifierType == notifierTypeConstants["GCM"]) { + configList.push(gcmKey); + configList.push(gcmId); + } + + addConfigFormData.type = platformTypeConstants["ANDROID"]; + addConfigFormData.configuration = configList; + + var addConfigAPI = "/mdm-android-agent/configuration"; + + invokerUtil.post( + addConfigAPI, + addConfigFormData, + function (data) { + data = JSON.parse(data); + if (data.responseCode == responseCodes["CREATED"]) { + $("#config-save-form").addClass("hidden"); + $("#record-created-msg").removeClass("hidden"); + } else if (data == 500) { + $(errorMsg).text("Exception occurred at backend."); + $(errorMsgWrapper).removeClass("hidden"); + } else if (data == 403) { + $(errorMsg).text("Action was not permitted."); + $(errorMsgWrapper).removeClass("hidden"); + } else { + $(errorMsg).text("An unexpected error occurred."); + $(errorMsgWrapper).removeClass("hidden"); + } + + + }, function (data) { + data = data.status; + if (data == 500) { + $(errorMsg).text("Exception occurred at backend."); + } else if (data == 403) { + $(errorMsg).text("Action was not permitted."); + } else { + $(errorMsg).text("An unexpected error occurred."); + } + $(errorMsgWrapper).removeClass("hidden"); + } + ); + } + }); + + /** + * Following click function would execute + * when a user clicks on "Save" button + * on General platform configuration page in WSO2 EMM Console. + */ + $("button#save-general-btn").click(function () { + var notifierFrequency = $("input#monitoring-config-frequency").val(); + var errorMsgWrapper = "#email-config-error-msg"; + var errorMsg = "#email-config-error-msg span"; + + if (!notifierFrequency) { + $(errorMsg).text("Monitoring frequency is a required field. It cannot be empty."); + $(errorMsgWrapper).removeClass("hidden"); + } else if (!isPositiveInteger(notifierFrequency)) { + $(errorMsg).text("Provided monitoring frequency is invalid. "); + $(errorMsgWrapper).removeClass("hidden"); + } else { + var addConfigFormData = {}; + var configList = new Array(); + + var monitorFrequency = { + "name": configParams["NOTIFIER_FREQUENCY"], + "value": String((notifierFrequency * 1000)), + "contentType": "text" + }; + + configList.push(monitorFrequency); + addConfigFormData.configuration = configList; + + var addConfigAPI = "/devicemgt_admin/configuration"; + invokerUtil.post( + addConfigAPI, + addConfigFormData, + function (data) { + data = JSON.parse(data); + if (data.statusCode == responseCodes["SUCCESS"]) { + $("#config-save-form").addClass("hidden"); + $("#record-created-msg").removeClass("hidden"); + } else if (data == 500) { + $(errorMsg).text("Exception occurred at backend."); + } else if (data == 403) { + $(errorMsg).text("Action was not permitted."); + } else { + $(errorMsg).text("An unexpected error occurred."); + } + + $(errorMsgWrapper).removeClass("hidden"); + }, function (data) { + data = data.status; + if (data == 500) { + $(errorMsg).text("Exception occurred at backend."); + } else if (data == 403) { + $(errorMsg).text("Action was not permitted."); + } else { + $(errorMsg).text("An unexpected error occurred."); + } + $(errorMsgWrapper).removeClass("hidden"); + } + ); + } + }); + + var errorMsgWrapper = "#ios-config-error-msg"; + var errorMsg = "#ios-config-error-msg span"; + var fileTypes = ['pfx']; + var notSupportedError = false; + + var base64MDMCert = ""; + var fileInputMDMCert = $('#ios-config-mdm-certificate'); + var fileNameMDMCert = ""; + var invalidFormatMDMCert = false; + + var base64APNSCert = ""; + var fileInputAPNSCert = $('#ios-config-apns-certificate'); + var fileNameAPNSCert = ""; + var invalidFormatAPNSCert = false; + + $(fileInputMDMCert).change(function () { + + if (!window.File || !window.FileReader || !window.FileList || !window.Blob) { + $(errorMsg).text("The File APIs are not fully supported in this browser."); + $(errorMsgWrapper).removeClass("hidden"); + notSupportedError = true; + return; + } + + var file = fileInputMDMCert[0].files[0]; + fileNameMDMCert = file.name; + var extension = file.name.split('.').pop().toLowerCase(), + isSuccess = fileTypes.indexOf(extension) > -1; + + if (isSuccess) { + var fileReader = new FileReader(); + fileReader.onload = function (event) { + base64MDMCert = event.target.result; + }; + fileReader.readAsDataURL(file); + invalidFormatMDMCert = false; + } else { + base64MDMCert = ""; + invalidFormatMDMCert = true; + } + }); + + $(fileInputAPNSCert).change(function () { + + if (!window.File || !window.FileReader || !window.FileList || !window.Blob) { + $(errorMsg).text("The File APIs are not fully supported in this browser."); + $(errorMsgWrapper).removeClass("hidden"); + notSupportedError = true; + return; + } + + var file = fileInputAPNSCert[0].files[0]; + fileNameAPNSCert = file.name; + var extension = file.name.split('.').pop().toLowerCase(), + isSuccess = fileTypes.indexOf(extension) > -1; + + if (isSuccess) { + var fileReader = new FileReader(); + fileReader.onload = function (event) { + base64APNSCert = event.target.result; + }; + fileReader.readAsDataURL(file); + invalidFormatAPNSCert = false; + } else { + base64MDMCert = ""; + invalidFormatAPNSCert = true; + } + }); + + $("button#save-ios-btn").click(function () { + + var configCountry = $("#ios-config-country").val(); + var configState = $("#ios-config-state").val(); + var configLocality = $("#ios-config-locality").val(); + var configOrganization = $("#ios-config-organization").val(); + var configOrganizationUnit = $("#ios-config-organization-unit").val(); + var MDMCertPassword = $("#ios-config-mdm-certificate-password").val(); + var MDMCertTopicID = $("#ios-config-mdm-certificate-topic-id").val(); + var APNSCertPassword = $("#ios-config-apns-certificate-password").val(); + var configOrgDisplayName = $("#ios-org-display-name").val(); + var iosLicense = tinymce.get('ios-eula').getContent(); + + fileNameMDMCert = validateCertificateParams(fileNameMDMCert, iOSMDMCertificateName); + fileNameAPNSCert = validateCertificateParams(fileNameAPNSCert, iOSAPNSCertificateName); + base64MDMCert = validateCertificateParams(base64MDMCert, iOSMDMCertificate); + base64APNSCert = validateCertificateParams(base64APNSCert, iOSAPNSCertificate); + + if (!configCountry) { + $(errorMsg).text("SCEP country is a required field. It cannot be empty."); + $(errorMsgWrapper).removeClass("hidden"); + } else if (!configState) { + $(errorMsg).text("SCEP state is a required field. It cannot be empty."); + $(errorMsgWrapper).removeClass("hidden"); + } else if (!configLocality) { + $(errorMsg).text("SCEP locality is a required field. It cannot be empty."); + $(errorMsgWrapper).removeClass("hidden"); + } else if (!configOrganization) { + $(errorMsg).text("SCEP organization is a required field. It cannot be empty."); + $(errorMsgWrapper).removeClass("hidden"); + } else if (!configOrganizationUnit) { + $(errorMsg).text("SCEP organization unit is a required field. It cannot be empty."); + $(errorMsgWrapper).removeClass("hidden"); + } else if (!MDMCertPassword) { + $(errorMsg).text("MDM certificate password is a required field. It cannot be empty."); + $(errorMsgWrapper).removeClass("hidden"); + } else if (!MDMCertTopicID) { + $(errorMsg).text("MDM certificate topic ID is a required field. It cannot be empty."); + $(errorMsgWrapper).removeClass("hidden"); + } else if (!APNSCertPassword) { + $(errorMsg).text("APNS certificate password is a required field. It cannot be empty."); + $(errorMsgWrapper).removeClass("hidden"); + } else if (notSupportedError) { + $(errorMsg).text("The File APIs are not fully supported in this browser."); + $(errorMsgWrapper).removeClass("hidden"); + } else if (invalidFormatMDMCert) { + $(errorMsg).text("MDM certificate needs to be in pfx format."); + $(errorMsgWrapper).removeClass("hidden"); + } else if (base64MDMCert == '') { + $(errorMsg).text("MDM certificate is a required field. It cannot be empty."); + $(errorMsgWrapper).removeClass("hidden"); + } else if (invalidFormatAPNSCert) { + $(errorMsg).text("APNS certificate needs to be in pfx format."); + $(errorMsgWrapper).removeClass("hidden"); + } else if (base64APNSCert == '') { + $(errorMsg).text("APNS certificate is a required field. It cannot be empty."); + $(errorMsgWrapper).removeClass("hidden"); + } else if (!configOrgDisplayName) { + $(errorMsg).text("Organization display name is a required field. It cannot be empty."); + $(errorMsgWrapper).removeClass("hidden"); + } else { + var addConfigFormData = {}; + var configList = new Array(); + + var configCountry = { + "name": configParams["CONFIG_COUNTRY"], + "value": configCountry, + "contentType": "text" + }; + + var configState = { + "name": configParams["CONFIG_STATE"], + "value": configState, + "contentType": "text" + }; + + var configLocality = { + "name": configParams["CONFIG_LOCALITY"], + "value": configLocality, + "contentType": "text" + }; + + var configOrganization = { + "name": configParams["CONFIG_ORGANIZATION"], + "value": configOrganization, + "contentType": "text" + }; + + var configOrganizationUnit = { + "name": configParams["CONFIG_ORGANIZATION_UNIT"], + "value": configOrganizationUnit, + "contentType": "text" + }; + + var MDMCertPassword = { + "name": configParams["MDM_CERT_PASSWORD"], + "value": MDMCertPassword, + "contentType": "text" + }; + + var MDMCertTopicID = { + "name": configParams["MDM_CERT_TOPIC_ID"], + "value": MDMCertTopicID, + "contentType": "text" + }; + + var APNSCertPassword = { + "name": configParams["APNS_CERT_PASSWORD"], + "value": APNSCertPassword, + "contentType": "text" + }; + + var paramBase64MDMCert = { + "name": configParams["MDM_CERT"], + "value": base64MDMCert, + "contentType": "text" + }; + + var MDMCertName = { + "name": configParams["MDM_CERT_NAME"], + "value": fileNameMDMCert, + "contentType": "text" + }; + + var paramBase64APNSCert = { + "name": configParams["APNS_CERT"], + "value": base64APNSCert, + "contentType": "text" + }; + + var APNSCertName = { + "name": configParams["APNS_CERT_NAME"], + "value": fileNameAPNSCert, + "contentType": "text" + }; + + var paramOrganizationDisplayName = { + "name": configParams["ORG_DISPLAY_NAME"], + "value": configOrgDisplayName, + "contentType": "text" + }; + + var iosEula = { + "name": configParams["IOS_EULA"], + "value": iosLicense, + "contentType": "text" + }; + + configList.push(configCountry); + configList.push(configState); + configList.push(configLocality); + configList.push(configOrganization); + configList.push(configOrganizationUnit); + configList.push(MDMCertPassword); + configList.push(MDMCertTopicID); + configList.push(APNSCertPassword); + configList.push(paramBase64MDMCert); + configList.push(MDMCertName); + configList.push(paramBase64APNSCert); + configList.push(APNSCertName); + configList.push(paramOrganizationDisplayName); + configList.push(iosEula); + + addConfigFormData.type = platformTypeConstants["IOS"]; + addConfigFormData.configuration = configList; + + var addConfigAPI = "/ios/configuration"; + + invokerUtil.post( + addConfigAPI, + addConfigFormData, + function (data) { + data = JSON.parse(data); + if (data.responseCode == responseCodes["CREATED"]) { + $("#config-save-form").addClass("hidden"); + $("#record-created-msg").removeClass("hidden"); + } else if (data == 500) { + $(errorMsg).text("Exception occurred at backend."); + } else if (data == 400) { + $(errorMsg).text("Configurations cannot be empty."); + } else { + $(errorMsg).text("An unexpected error occurred."); + } + + $(errorMsgWrapper).removeClass("hidden"); + }, function (data) { + data = data.status; + if (data == 500) { + $(errorMsg).text("Exception occurred at backend."); + } else if (data == 403) { + $(errorMsg).text("Action was not permitted."); + } else { + $(errorMsg).text("An unexpected error occurred."); + } + $(errorMsgWrapper).removeClass("hidden"); + } + ); + } + + }); + + var errorMsgWrapperWindows = "#windows-config-error-msg"; + var errorMsgWindows = "#windows-config-error-msg span"; + var fileTypesWindows = ['jks']; + var notSupportedError = false; + + var base64WindowsMDMCert = ""; + var fileInputWindowsMDMCert = $('#windows-config-mdm-certificate'); + var fileNameWindowsMDMCert = ""; + var invalidFormatWindowsMDMCert = false; + + $(fileInputWindowsMDMCert).change(function () { + + if (!window.File || !window.FileReader || !window.FileList || !window.Blob) { + $(errorMsgWindows).text("The File APIs are not fully supported in this browser."); + $(errorMsgWrapperWindows).removeClass("hidden"); + notSupportedError = true; + return; + } + + var file = fileInputWindowsMDMCert[0].files[0]; + fileNameWindowsMDMCert = file.name; + var extension = file.name.split('.').pop().toLowerCase(), + isSuccess = fileTypesWindows.indexOf(extension) > -1; + + if (isSuccess) { + var fileReader = new FileReader(); + fileReader.onload = function (event) { + base64WindowsMDMCert = event.target.result; + }; + fileReader.readAsDataURL(file); + invalidFormatWindowsMDMCert = false; + } else { + base64MDMCert = ""; + invalidFormatWindowsMDMCert = true; + } + }); + + $("button#save-windows-btn").click(function () { + + var notifierFrequency = $("#windows-config-notifier-frequency").val(); + var windowsLicense = tinymce.get('windows-eula').getContent(); + + if (!notifierFrequency) { + $(errorMsgWindows).text("Polling Interval is a required field. It cannot be empty."); + $(errorMsgWrapperWindows).removeClass("hidden"); + } else if (!windowsLicense) { + $(errorMsgWindows).text("License is a required field. It cannot be empty."); + $(errorMsgWrapperWindows).removeClass("hidden"); + } else if (!$.isNumeric(notifierFrequency)) { + $(errorMsgWindows).text("Provided Notifier frequency is invalid. It must be a number."); + $(errorMsgWrapperWindows).removeClass("hidden"); + } else { + var addConfigFormData = {}; + var configList = new Array(); + + var paramNotifierFrequency = { + "name": configParams["NOTIFIER_FREQUENCY"], + "value": String(notifierFrequency * 1000), + "contentType": "text" + }; + + var windowsEula = { + "name": configParams["WINDOWS_EULA"], + "value": windowsLicense, + "contentType": "text" + }; + + configList.push(paramNotifierFrequency); + configList.push(windowsEula); + + addConfigFormData.type = platformTypeConstants["WINDOWS"]; + addConfigFormData.configuration = configList; + + var addConfigAPI = "/mdm-windows-agent/services/configuration"; + + invokerUtil.post( + addConfigAPI, + addConfigFormData, + function (data) { + data = JSON.parse(data); + if (data.responseCode == responseCodes["CREATED"]) { + $("#config-save-form").addClass("hidden"); + $("#record-created-msg").removeClass("hidden"); + } else if (data == 500) { + $(errorMsg).text("Exception occurred at backend."); + } else if (data == 400) { + $(errorMsg).text("Configurations cannot be empty."); + } else { + $(errorMsg).text("An unexpected error occurred."); + } + + $(errorMsgWrapperWindows).removeClass("hidden"); + }, function (data) { + data = data.status; + if (data == 500) { + $(errorMsg).text("Exception occurred at backend."); + } else if (data == 403) { + $(errorMsg).text("Action was not permitted."); + } else { + $(errorMsg).text("An unexpected error occurred."); + } + $(errorMsgWrapper).removeClass("hidden"); + } + ); + } + + }); +}); + +// Start of HTML embedded invoke methods +var showAdvanceOperation = function (operation, button) { + $(button).addClass('selected'); + $(button).siblings().removeClass('selected'); + var enabledPlatforms = $("#supportedPlatforms"); + var isPluginEnabled = false; + switch (operation) { + case 'ios': + if (enabledPlatforms.data("ios")) { + isPluginEnabled = true; + } + break; + case 'windows': + if (enabledPlatforms.data("windows")) { + isPluginEnabled = true; + } + break; + case 'android': + if (enabledPlatforms.data("android")) { + isPluginEnabled = true; + } + break; + case 'general': + isPluginEnabled = true; + break; + } + if (isPluginEnabled) { + var hiddenOperation = ".wr-hidden-operations-content > div"; + $(hiddenOperation + '[data-operation="' + operation + '"]').show(); + $(hiddenOperation + '[data-operation="' + operation + '"]').siblings().hide(); + } else { + var hiddenOperation = ".wr-hidden-operations-content > div"; + $(hiddenOperation + '[data-operation="error"]').show(); + $(hiddenOperation + '[data-operation="error"]').siblings().hide(); + promptErrorPolicyPlatform("To use " + operation + " related functionalities you need to configure the server " + + "accordingly.Please refer to the user guiled."); + } +}; diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/src/main/resources/jaggeryapps/devicemgt/app/units/mdm.unit.policy.edit/edit.hbs b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/src/main/resources/jaggeryapps/devicemgt/app/units/mdm.unit.policy.edit/edit.hbs new file mode 100644 index 0000000000..4f85e36c7d --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/src/main/resources/jaggeryapps/devicemgt/app/units/mdm.unit.policy.edit/edit.hbs @@ -0,0 +1,223 @@ + +
      +
      + + + + + + + + + +
      +
      +

      EDIT POLICY

      +
      +
      +
      +
      +
      +

      Step 1: Edit current profile

      +
      + +
      +
      +
      + + Loading platform features . . . +
      +
      +
      +
      + +
      +
      +
      +
      + + + +
      +
      +{{#zone "bottomJs"}} + + + + {{js "js/policy-create.js"}} +{{/zone}} + diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/src/main/resources/jaggeryapps/devicemgt/app/units/mdm.unit.policy.edit/edit.js b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/src/main/resources/jaggeryapps/devicemgt/app/units/mdm.unit.policy.edit/edit.js new file mode 100644 index 0000000000..94f7423c18 --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/src/main/resources/jaggeryapps/devicemgt/app/units/mdm.unit.policy.edit/edit.js @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2016, 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("policy-view-edit-unit backend js"); + log.debug("calling policy-view-edit-unit"); + var userModule = require("/app/modules/user.js").userModule; + context.roles = userModule.getRoles().content; + context.users = userModule.getUsersByUsername().content; + return context; +} \ No newline at end of file diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/src/main/resources/jaggeryapps/devicemgt/app/units/mdm.unit.policy.edit/edit.json b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/src/main/resources/jaggeryapps/devicemgt/app/units/mdm.unit.policy.edit/edit.json new file mode 100644 index 0000000000..fd25901297 --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/src/main/resources/jaggeryapps/devicemgt/app/units/mdm.unit.policy.edit/edit.json @@ -0,0 +1,3 @@ +{ + "version" : "1.0.0" +} \ No newline at end of file diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/src/main/resources/jaggeryapps/devicemgt/app/units/mdm.unit.policy.edit/public/js/policy-create.js b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/src/main/resources/jaggeryapps/devicemgt/app/units/mdm.unit.policy.edit/public/js/policy-create.js new file mode 100644 index 0000000000..fafc43318d --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/src/main/resources/jaggeryapps/devicemgt/app/units/mdm.unit.policy.edit/public/js/policy-create.js @@ -0,0 +1,2321 @@ +/* + * Copyright (c) 2016, 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. + */ + +var validateStep = {}; +var skipStep = {}; +var stepForwardFrom = {}; +var stepBackFrom = {}; +var policy = {}; +var configuredOperations = []; +var currentlyEffected = {}; + +// Constants to define platform types available +var platformTypeConstants = { + "ANDROID": "android", + "IOS": "ios", + "WINDOWS": "windows" +}; + +// Constants to define platform types ids. +var platformTypeIds = { + "ANDROID": 1, + "IOS": 3, + "WINDOWS": 2 +}; + +// Constants to define Android Operation Constants +var androidOperationConstants = { + "PASSCODE_POLICY_OPERATION": "passcode-policy", + "PASSCODE_POLICY_OPERATION_CODE": "PASSCODE_POLICY", + "CAMERA_OPERATION": "camera", + "CAMERA_OPERATION_CODE": "CAMERA", + "ENCRYPT_STORAGE_OPERATION": "encrypt-storage", + "ENCRYPT_STORAGE_OPERATION_CODE": "ENCRYPT_STORAGE", + "WIFI_OPERATION": "wifi", + "WIFI_OPERATION_CODE": "WIFI" +}; + +// Constants to define Android Operation Constants +var windowsOperationConstants = { + "PASSCODE_POLICY_OPERATION": "passcode-policy", + "PASSCODE_POLICY_OPERATION_CODE": "PASSCODE_POLICY", + "CAMERA_OPERATION": "camera", + "CAMERA_OPERATION_CODE": "CAMERA", + "ENCRYPT_STORAGE_OPERATION": "encrypt-storage", + "ENCRYPT_STORAGE_OPERATION_CODE": "ENCRYPT_STORAGE" +}; + +// Constants to define iOS Operation Constants +var iosOperationConstants = { + "PASSCODE_POLICY_OPERATION": "passcode-policy", + "PASSCODE_POLICY_OPERATION_CODE": "PASSCODE_POLICY", + "RESTRICTIONS_OPERATION": "restrictions", + "RESTRICTIONS_OPERATION_CODE": "RESTRICTION", + "WIFI_OPERATION": "wifi", + "WIFI_OPERATION_CODE": "WIFI", + "EMAIL_OPERATION": "email", + "EMAIL_OPERATION_CODE": "EMAIL", + "AIRPLAY_OPERATION": "airplay", + "AIRPLAY_OPERATION_CODE": "AIR_PLAY", + "LDAP_OPERATION": "ldap", + "LDAP_OPERATION_CODE": "LDAP", + "CALENDAR_OPERATION": "calendar", + "CALENDAR_OPERATION_CODE": "CALDAV", + "CALENDAR_SUBSCRIPTION_OPERATION": "calendar-subscription", + "CALENDAR_SUBSCRIPTION_OPERATION_CODE": "CALENDAR_SUBSCRIPTION", + "APN_OPERATION": "apn", + "APN_OPERATION_CODE": "APN", + "CELLULAR_OPERATION": "cellular", + "CELLULAR_OPERATION_CODE": "CELLULAR" +}; + +/** + * Method to update the visibility (i.e. disabled or enabled view) + * of grouped input according to the values + * that they currently possess. + * @param domElement HTML grouped-input element with class name "grouped-input" + */ +var updateGroupedInputVisibility = function (domElement) { + if ($(".parent-input:first", domElement).is(":checked")) { + if ($(".grouped-child-input:first", domElement).hasClass("disabled")) { + $(".grouped-child-input:first", domElement).removeClass("disabled"); + } + $(".child-input", domElement).each(function () { + $(this).prop('disabled', false); + }); + } else { + if (!$(".grouped-child-input:first", domElement).hasClass("disabled")) { + $(".grouped-child-input:first", domElement).addClass("disabled"); + } + $(".child-input", domElement).each(function () { + $(this).prop('disabled', true); + }); + } +}; + +skipStep["policy-platform"] = function (policyPayloadObj) { + policy["name"] = policyPayloadObj["policyName"]; + policy["platform"] = policyPayloadObj["profile"]["deviceType"]["name"]; + policy["platformId"] = policyPayloadObj["profile"]["deviceType"]["id"]; + var userRoleInput = $("#user-roles-input"); + var ownershipInput = $("#ownership-input"); + var userInput = $("#users-input"); + var actionInput = $("#action-input"); + var policyNameInput = $("#policy-name-input"); + var policyDescriptionInput = $("#policy-description-input"); + currentlyEffected["roles"] = policyPayloadObj.roles; + currentlyEffected["users"] = policyPayloadObj.users; + userRoleInput.val(currentlyEffected["roles"]).trigger("change"); + userInput.val(currentlyEffected["users"]).trigger("change"); + + if (currentlyEffected["users"].length > 0) { + $("#users-radio-btn").prop("checked", true) + $("#users-select-field").show(); + $("#user-roles-select-field").hide(); + } + ownershipInput.val(policyPayloadObj.ownershipType); + actionInput.val(policyPayloadObj.compliance); + policyNameInput.val(policyPayloadObj["policyName"]); + policyDescriptionInput.val(policyPayloadObj["description"]); + // updating next-page wizard title with selected platform + $("#policy-profile-page-wizard-title").text("EDIT " + policy["platform"] + " POLICY - " + policy["name"]); + + var deviceType = policy["platform"]; + var hiddenOperationsByDeviceType = $("#hidden-operations-" + deviceType); + var hiddenOperationsByDeviceTypeCacheKey = deviceType + "HiddenOperations"; + var hiddenOperationsByDeviceTypeSrc = hiddenOperationsByDeviceType.attr("src"); + + setTimeout( + function () { + $.template(hiddenOperationsByDeviceTypeCacheKey, hiddenOperationsByDeviceTypeSrc, function (template) { + var content = template(); + // pushing profile feature input elements + $(".wr-advance-operations").html(content); + // populating values and getting the list of configured features + var configuredOperations = operationModule. + populateProfile(policy["platform"], policyPayloadObj["profile"]["profileFeaturesList"]); + // updating grouped input visibility according to the populated values + $(".wr-advance-operations li.grouped-input").each(function () { + updateGroupedInputVisibility(this); + }); + // enabling previously configured options of last update + for (var i = 0; i < configuredOperations.length; ++i) { + var configuredOperation = configuredOperations[i]; + $(".operation-data").filterByData("operation-code", configuredOperation). + find(".panel-title .wr-input-control.switch input[type=checkbox]").each(function () { + $(this).click(); + }); + } + }); + }, + 250 // time delayed for the execution of above function, 250 milliseconds + ); +}; + +/** + * Checks if provided number is valid against a range. + * + * @param numberInput Number Input + * @param min Minimum Limit + * @param max Maximum Limit + * @returns {boolean} Returns true if input is within the specified range + */ +var inputIsValidAgainstRange = function (numberInput, min, max) { + return (numberInput == min || (numberInput > min && numberInput < max) || numberInput == max); +}; + +/** + * Checks if provided input is valid against RegEx input. + * + * @param regExp Regular expression + * @param input Input string to check + * @returns {boolean} Returns true if input matches RegEx + */ +var inputIsValidAgainstRegExp = function (regExp, input) { + return regExp.test(input); +}; + +validateStep["policy-profile"] = function () { + var validationStatusArray = []; + var validationStatus; + var operation; + + // starting validation process and updating validationStatus + if (policy["platform"] == platformTypeConstants["ANDROID"]) { + if (configuredOperations.length == 0) { + // updating validationStatus + validationStatus = { + "error": true, + "mainErrorMsg": "You cannot continue. Zero configured features." + }; + // updating validationStatusArray with validationStatus + validationStatusArray.push(validationStatus); + } else { + // validating each and every configured Operation + // Validating PASSCODE_POLICY + if ($.inArray(androidOperationConstants["PASSCODE_POLICY_OPERATION_CODE"], configuredOperations) != -1) { + // if PASSCODE_POLICY is configured + operation = androidOperationConstants["PASSCODE_POLICY_OPERATION"]; + // initializing continueToCheckNextInputs to true + var continueToCheckNextInputs = true; + + // validating first input: passcodePolicyMaxPasscodeAgeInDays + var passcodePolicyMaxPasscodeAgeInDays = $("input#passcode-policy-max-passcode-age-in-days").val(); + if (passcodePolicyMaxPasscodeAgeInDays) { + if (!$.isNumeric(passcodePolicyMaxPasscodeAgeInDays)) { + validationStatus = { + "error": true, + "subErrorMsg": "Provided passcode age is not a number.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } else if (!inputIsValidAgainstRange(passcodePolicyMaxPasscodeAgeInDays, 1, 730)) { + validationStatus = { + "error": true, + "subErrorMsg": "Provided passcode age is not with in the range of 1-to-730.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } + } + + // validating second and last input: passcodePolicyPasscodeHistory + if (continueToCheckNextInputs) { + var passcodePolicyPasscodeHistory = $("input#passcode-policy-passcode-history").val(); + if (passcodePolicyPasscodeHistory) { + if (!$.isNumeric(passcodePolicyPasscodeHistory)) { + validationStatus = { + "error": true, + "subErrorMsg": "Provided passcode history is not a number.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } else if (!inputIsValidAgainstRange(passcodePolicyPasscodeHistory, 1, 50)) { + validationStatus = { + "error": true, + "subErrorMsg": "Provided passcode history is not with in the range of 1-to-50.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } + } + } + + // at-last, if the value of continueToCheckNextInputs is still true + // this means that no error is found + if (continueToCheckNextInputs) { + validationStatus = { + "error": false, + "okFeature": operation + }; + } + + // updating validationStatusArray with validationStatus + validationStatusArray.push(validationStatus); + } + // Validating CAMERA + if ($.inArray(androidOperationConstants["CAMERA_OPERATION_CODE"], configuredOperations) != -1) { + // if CAMERA is configured + operation = androidOperationConstants["CAMERA_OPERATION"]; + // updating validationStatus + validationStatus = { + "error": false, + "okFeature": operation + }; + // updating validationStatusArray with validationStatus + validationStatusArray.push(validationStatus); + } + // Validating ENCRYPT_STORAGE + if ($.inArray(androidOperationConstants["ENCRYPT_STORAGE_OPERATION_CODE"], configuredOperations) != -1) { + // if ENCRYPT_STORAGE is configured + operation = androidOperationConstants["ENCRYPT_STORAGE_OPERATION"]; + // updating validationStatus + validationStatus = { + "error": false, + "okFeature": operation + }; + // updating validationStatusArray with validationStatus + validationStatusArray.push(validationStatus); + } + // Validating WIFI + if ($.inArray(androidOperationConstants["WIFI_OPERATION_CODE"], configuredOperations) != -1) { + // if WIFI is configured + operation = androidOperationConstants["WIFI_OPERATION"]; + // initializing continueToCheckNextInputs to true + continueToCheckNextInputs = true; + + var wifiSSID = $("input#wifi-ssid").val(); + if (!wifiSSID) { + validationStatus = { + "error": true, + "subErrorMsg": "WIFI SSID is not given. You cannot proceed.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } + + // at-last, if the value of continueToCheckNextInputs is still true + // this means that no error is found + if (continueToCheckNextInputs) { + validationStatus = { + "error": false, + "okFeature": operation + }; + } + + // updating validationStatusArray with validationStatus + validationStatusArray.push(validationStatus); + } + } + } + if (policy["platform"] == platformTypeConstants["WINDOWS"]) { + if (configuredOperations.length == 0) { + // updating validationStatus + validationStatus = { + "error": true, + "mainErrorMsg": "You cannot continue. Zero configured features." + }; + // updating validationStatusArray with validationStatus + validationStatusArray.push(validationStatus); + } else { + // validating each and every configured Operation + // Validating PASSCODE_POLICY + if ($.inArray(windowsOperationConstants["PASSCODE_POLICY_OPERATION_CODE"], configuredOperations) != -1) { + // if PASSCODE_POLICY is configured + operation = windowsOperationConstants["PASSCODE_POLICY_OPERATION"]; + // initializing continueToCheckNextInputs to true + var continueToCheckNextInputs = true; + + // validating first input: passcodePolicyMaxPasscodeAgeInDays + var passcodePolicyMaxPasscodeAgeInDays = $("input#passcode-policy-max-passcode-age-in-days").val(); + if (passcodePolicyMaxPasscodeAgeInDays) { + if (!$.isNumeric(passcodePolicyMaxPasscodeAgeInDays)) { + validationStatus = { + "error": true, + "subErrorMsg": "Provided passcode age is not a number.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } else if (!inputIsValidAgainstRange(passcodePolicyMaxPasscodeAgeInDays, 1, 730)) { + validationStatus = { + "error": true, + "subErrorMsg": "Provided passcode age is not with in the range of 1-to-730.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } + } + + // validating second and last input: passcodePolicyPasscodeHistory + if (continueToCheckNextInputs) { + var passcodePolicyPasscodeHistory = $("input#passcode-policy-passcode-history").val(); + if (passcodePolicyPasscodeHistory) { + if (!$.isNumeric(passcodePolicyPasscodeHistory)) { + validationStatus = { + "error": true, + "subErrorMsg": "Provided passcode history is not a number.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } else if (!inputIsValidAgainstRange(passcodePolicyPasscodeHistory, 1, 50)) { + validationStatus = { + "error": true, + "subErrorMsg": "Provided passcode history is not with in the range of 1-to-50.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } + } + } + + // at-last, if the value of continueToCheckNextInputs is still true + // this means that no error is found + if (continueToCheckNextInputs) { + validationStatus = { + "error": false, + "okFeature": operation + }; + } + + // updating validationStatusArray with validationStatus + validationStatusArray.push(validationStatus); + } + // Validating CAMERA + if ($.inArray(windowsOperationConstants["CAMERA_OPERATION_CODE"], configuredOperations) != -1) { + // if CAMERA is configured + operation = windowsOperationConstants["CAMERA_OPERATION"]; + // updating validationStatus + validationStatus = { + "error": false, + "okFeature": operation + }; + // updating validationStatusArray with validationStatus + validationStatusArray.push(validationStatus); + } + // Validating ENCRYPT_STORAGE + if ($.inArray(windowsOperationConstants["ENCRYPT_STORAGE_OPERATION_CODE"], configuredOperations) != -1) { + // if ENCRYPT_STORAGE is configured + operation = windowsOperationConstants["ENCRYPT_STORAGE_OPERATION"]; + // updating validationStatus + validationStatus = { + "error": false, + "okFeature": operation + }; + // updating validationStatusArray with validationStatus + validationStatusArray.push(validationStatus); + } + + } + } else if (policy["platform"] == platformTypeConstants["IOS"]) { + if (configuredOperations.length == 0) { + // updating validationStatus + validationStatus = { + "error": true, + "mainErrorMsg": "You cannot continue. Zero configured features." + }; + // updating validationStatusArray with validationStatus + validationStatusArray.push(validationStatus); + } else { + // validating each and every configured Operation + // Validating PASSCODE_POLICY + if ($.inArray(iosOperationConstants["PASSCODE_POLICY_OPERATION_CODE"], configuredOperations) != -1) { + // if PASSCODE_POLICY is configured + operation = iosOperationConstants["PASSCODE_POLICY_OPERATION"]; + // initializing continueToCheckNextInputs to true + continueToCheckNextInputs = true; + + // validating first input: passcodePolicyMaxPasscodeAgeInDays + passcodePolicyMaxPasscodeAgeInDays = $("input#passcode-policy-max-passcode-age-in-days").val(); + if (passcodePolicyMaxPasscodeAgeInDays) { + if (!$.isNumeric(passcodePolicyMaxPasscodeAgeInDays)) { + validationStatus = { + "error": true, + "subErrorMsg": "Provided passcode age is not a number.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } else if (!inputIsValidAgainstRange(passcodePolicyMaxPasscodeAgeInDays, 1, 730)) { + validationStatus = { + "error": true, + "subErrorMsg": "Provided passcode age is not with in the range of 1-to-730.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } + } + + // validating second and last input: passcodePolicyPasscodeHistory + if (continueToCheckNextInputs) { + passcodePolicyPasscodeHistory = $("input#passcode-policy-passcode-history").val(); + if (passcodePolicyPasscodeHistory) { + if (!$.isNumeric(passcodePolicyPasscodeHistory)) { + validationStatus = { + "error": true, + "subErrorMsg": "Provided passcode history is not a number.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } else if (!inputIsValidAgainstRange(passcodePolicyPasscodeHistory, 1, 50)) { + validationStatus = { + "error": true, + "subErrorMsg": "Provided passcode history is not with in the range of 1-to-50.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } + } + } + + // at-last, if the value of continueToCheckNextInputs is still true + // this means that no error is found + if (continueToCheckNextInputs) { + validationStatus = { + "error": false, + "okFeature": operation + }; + } + + // updating validationStatusArray with validationStatus + validationStatusArray.push(validationStatus); + } + // Validating RESTRICTIONS + if ($.inArray(iosOperationConstants["RESTRICTIONS_OPERATION_CODE"], configuredOperations) != -1) { + // if RESTRICTION is configured + operation = iosOperationConstants["RESTRICTIONS_OPERATION"]; + // initializing continueToCheckNextInputs to true + continueToCheckNextInputs = true; + + // getting input values to be validated + var restrictionsAutonomousSingleAppModePermittedAppIDsGridChildInputs = + "div#restrictions-autonomous-single-app-mode-permitted-app-ids .child-input"; + if ($(restrictionsAutonomousSingleAppModePermittedAppIDsGridChildInputs).length > 0) { + var childInput; + var childInputArray = []; + var emptyChildInputCount = 0; + var duplicatesExist = false; + // looping through each child input + $(restrictionsAutonomousSingleAppModePermittedAppIDsGridChildInputs).each(function () { + childInput = $(this).val(); + childInputArray.push(childInput); + if (!childInput) { + // if child input field is empty + emptyChildInputCount++; + } + }); + // checking for duplicates + var initialChildInputArrayLength = childInputArray.length; + if (emptyChildInputCount == 0 && initialChildInputArrayLength > 1) { + var m, poppedChildInput; + for (m = 0; m < (initialChildInputArrayLength - 1); m++) { + poppedChildInput = childInputArray.pop(); + var n; + for (n = 0; n < childInputArray.length; n++) { + if (poppedChildInput == childInputArray[n]) { + duplicatesExist = true; + break; + } + } + if (duplicatesExist) { + break; + } + } + } + // updating validationStatus + if (emptyChildInputCount > 0) { + // if empty child inputs are present + validationStatus = { + "error": true, + "subErrorMsg": "One or more permitted App ID entries in " + + "Autonomous Single App Mode are empty.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } else if (duplicatesExist) { + validationStatus = { + "error": true, + "subErrorMsg": "Duplicate values exist with permitted App ID entries in " + + "Autonomous Single App Mode.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } + } + + // at-last, if the value of continueToCheckNextInputs is still true + // this means that no error is found + if (continueToCheckNextInputs) { + validationStatus = { + "error": false, + "okFeature": operation + }; + } + + // updating validationStatusArray with validationStatus + validationStatusArray.push(validationStatus); + } + // Validating WIFI + if ($.inArray(iosOperationConstants["WIFI_OPERATION_CODE"], configuredOperations) != -1) { + // if WIFI is configured + operation = iosOperationConstants["WIFI_OPERATION"]; + // initializing continueToCheckNextInputs to true + continueToCheckNextInputs = true; + + // getting input values to be validated + wifiSSID = $("input#wifi-ssid").val(); + var wifiDomainName = $("input#wifi-domain-name").val(); + if (!wifiSSID && !wifiDomainName) { + validationStatus = { + "error": true, + "subErrorMsg": "Both Wi-Fi SSID and Wi-Fi Domain Name are not given. You cannot proceed.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } + + if (continueToCheckNextInputs) { + // getting proxy-setup value + var wifiProxyType = $("select#wifi-proxy-type").find("option:selected").attr("value"); + if (wifiProxyType == "Manual") { + // adds up additional fields to be validated + var wifiProxyServer = $("input#wifi-proxy-server").val(); + if (!wifiProxyServer) { + validationStatus = { + "error": true, + "subErrorMsg": "Wi-Fi Proxy Server is required. You cannot proceed.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } + + if (continueToCheckNextInputs) { + var wifiProxyPort = $("input#wifi-proxy-port").val(); + if (!wifiProxyPort) { + validationStatus = { + "error": true, + "subErrorMsg": "Wi-Fi Proxy Port is required. You cannot proceed.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } else if (!$.isNumeric(wifiProxyPort)) { + validationStatus = { + "error": true, + "subErrorMsg": "Wi-Fi Proxy Port requires a number input.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } else if (!inputIsValidAgainstRange(wifiProxyPort, 0, 65535)) { + validationStatus = { + "error": true, + "subErrorMsg": "Wi-Fi Proxy Port is not within the range " + + "of valid port numbers.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } + } + } + } + + if (continueToCheckNextInputs) { + // getting encryption-type value + var wifiEncryptionType = $("select#wifi-encryption-type").find("option:selected").attr("value"); + if (wifiEncryptionType != "None") { + var wifiPayloadCertificateAnchorUUIDsGridChildInputs = + "div#wifi-payload-certificate-anchor-uuids .child-input"; + if ($(wifiPayloadCertificateAnchorUUIDsGridChildInputs).length > 0) { + emptyChildInputCount = 0; + childInputArray = []; + duplicatesExist = false; + // looping through each child input + $(wifiPayloadCertificateAnchorUUIDsGridChildInputs).each(function () { + childInput = $(this).val(); + childInputArray.push(childInput); + if (!childInput) { + // if child input field is empty + emptyChildInputCount++; + } + }); + // checking for duplicates + initialChildInputArrayLength = childInputArray.length; + if (emptyChildInputCount == 0 && initialChildInputArrayLength > 1) { + for (m = 0; m < (initialChildInputArrayLength - 1); m++) { + poppedChildInput = childInputArray.pop(); + for (n = 0; n < childInputArray.length; n++) { + if (poppedChildInput == childInputArray[n]) { + duplicatesExist = true; + break; + } + } + if (duplicatesExist) { + break; + } + } + } + // updating validationStatus + if (emptyChildInputCount > 0) { + // if empty child inputs are present + validationStatus = { + "error": true, + "subErrorMsg": "One or more Payload Certificate " + + "Anchor UUIDs are empty.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } else if (duplicatesExist) { + validationStatus = { + "error": true, + "subErrorMsg": "Duplicate values exist " + + "with Payload Certificate Anchor UUIDs.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } + } + + if (continueToCheckNextInputs) { + var wifiTLSTrustedServerNamesGridChildInputs = + "div#wifi-tls-trusted-server-names .child-input"; + if ($(wifiTLSTrustedServerNamesGridChildInputs).length > 0) { + emptyChildInputCount = 0; + childInputArray = []; + duplicatesExist = false; + // looping through each child input + $(wifiTLSTrustedServerNamesGridChildInputs).each(function () { + childInput = $(this).val(); + childInputArray.push(childInput); + if (!childInput) { + // if child input field is empty + emptyChildInputCount++; + } + }); + // checking for duplicates + initialChildInputArrayLength = childInputArray.length; + if (emptyChildInputCount == 0 && initialChildInputArrayLength > 1) { + for (m = 0; m < (initialChildInputArrayLength - 1); m++) { + poppedChildInput = childInputArray.pop(); + for (n = 0; n < childInputArray.length; n++) { + if (poppedChildInput == childInputArray[n]) { + duplicatesExist = true; + break; + } + } + if (duplicatesExist) { + break; + } + } + } + // updating validationStatus + if (emptyChildInputCount > 0) { + // if empty child inputs are present + validationStatus = { + "error": true, + "subErrorMsg": "One or more TLS Trusted Server Names are empty.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } else if (duplicatesExist) { + validationStatus = { + "error": true, + "subErrorMsg": "Duplicate values exist " + + "with TLS Trusted Server Names.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } + } + } + } + } + + if (continueToCheckNextInputs) { + var wifiRoamingConsortiumOIsGridChildInputs = "div#wifi-roaming-consortium-ois .child-input"; + if ($(wifiRoamingConsortiumOIsGridChildInputs).length > 0) { + emptyChildInputCount = 0; + var outOfAllowedLengthCount = 0; + var invalidAgainstRegExCount = 0; + childInputArray = []; + duplicatesExist = false; + // looping through each child input + $(wifiRoamingConsortiumOIsGridChildInputs).each(function () { + childInput = $(this).val(); + childInputArray.push(childInput); + if (!childInput) { + // if child input field is empty + emptyChildInputCount++; + } else if (!inputIsValidAgainstLength(childInput, 6, 6) && !inputIsValidAgainstLength(childInput, 10, 10)) { + outOfAllowedLengthCount++; + } else if (!inputIsValidAgainstRegExp(/^[a-fA-F0-9]+$/, childInput)) { + invalidAgainstRegExCount++; + } + }); + // checking for duplicates + initialChildInputArrayLength = childInputArray.length; + if (emptyChildInputCount == 0 && initialChildInputArrayLength > 1) { + for (m = 0; m < (initialChildInputArrayLength - 1); m++) { + poppedChildInput = childInputArray.pop(); + for (n = 0; n < childInputArray.length; n++) { + if (poppedChildInput == childInputArray[n]) { + duplicatesExist = true; + break; + } + } + if (duplicatesExist) { + break; + } + } + } + // updating validationStatus + if (emptyChildInputCount > 0) { + // if empty child inputs are present + validationStatus = { + "error": true, + "subErrorMsg": "One or more Roaming Consortium OIs are empty.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } else if (outOfAllowedLengthCount > 0) { + // if outOfMaxAllowedLength input is present + validationStatus = { + "error": true, + "subErrorMsg": "One or more Roaming Consortium OIs " + + "are out of allowed length.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } else if (invalidAgainstRegExCount > 0) { + // if invalid inputs in terms of hexadecimal format are present + validationStatus = { + "error": true, + "subErrorMsg": "One or more Roaming Consortium OIs " + + "contain non-hexadecimal characters.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } else if (duplicatesExist) { + validationStatus = { + "error": true, + "subErrorMsg": "Duplicate values exist with Roaming Consortium OIs.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } + } + } + + if (continueToCheckNextInputs) { + var wifiNAIRealmNamesGridChildInputs = "div#wifi-nai-realm-names .child-input"; + if ($(wifiNAIRealmNamesGridChildInputs).length > 0) { + emptyChildInputCount = 0; + childInputArray = []; + duplicatesExist = false; + // looping through each child input + $(wifiNAIRealmNamesGridChildInputs).each(function () { + childInput = $(this).val(); + childInputArray.push(childInput); + if (!childInput) { + // if child input field is empty + emptyChildInputCount++; + } + }); + // checking for duplicates + initialChildInputArrayLength = childInputArray.length; + if (emptyChildInputCount == 0 && initialChildInputArrayLength > 1) { + for (m = 0; m < (initialChildInputArrayLength - 1); m++) { + poppedChildInput = childInputArray.pop(); + for (n = 0; n < childInputArray.length; n++) { + if (poppedChildInput == childInputArray[n]) { + duplicatesExist = true; + break; + } + } + if (duplicatesExist) { + break; + } + } + } + // updating validationStatus + if (emptyChildInputCount > 0) { + // if empty child inputs are present + validationStatus = { + "error": true, + "subErrorMsg": "One or more NAI Realm Names are empty.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } else if (duplicatesExist) { + validationStatus = { + "error": true, + "subErrorMsg": "Duplicate values exist with NAI Realm Names.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } + } + } + + if (continueToCheckNextInputs) { + var wifiMCCAndMNCsGridChildInputs = "div#wifi-mcc-and-mncs .child-input"; + if ($(wifiMCCAndMNCsGridChildInputs).length > 0) { + var childInputCount = 0; + var stringPair; + emptyChildInputCount = 0; + outOfAllowedLengthCount = 0; + var notNumericInputCount = 0; + childInputArray = []; + duplicatesExist = false; + // looping through each child input + $(wifiMCCAndMNCsGridChildInputs).each(function () { + childInput = $(this).val(); + // pushing each string pair to childInputArray + childInputCount++; + if (childInputCount % 2 == 1) { + // initialize stringPair value + stringPair = ""; + // append first part of the string + stringPair += childInput; + } else { + // append second part of the string + stringPair += childInput; + childInputArray.push(stringPair); + } + // updating emptyChildInputCount & outOfAllowedLengthCount + if (!childInput) { + // if child input field is empty + emptyChildInputCount++; + } else if (!$.isNumeric(childInput)) { + notNumericInputCount++; + } else if (!inputIsValidAgainstLength(childInput, 3, 3)) { + outOfAllowedLengthCount++; + } + }); + // checking for duplicates + initialChildInputArrayLength = childInputArray.length; + if (emptyChildInputCount == 0 && initialChildInputArrayLength > 1) { + for (m = 0; m < (initialChildInputArrayLength - 1); m++) { + poppedChildInput = childInputArray.pop(); + for (n = 0; n < childInputArray.length; n++) { + if (poppedChildInput == childInputArray[n]) { + duplicatesExist = true; + break; + } + } + if (duplicatesExist) { + break; + } + } + } + // updating validationStatus + if (emptyChildInputCount > 0) { + // if empty child inputs are present + validationStatus = { + "error": true, + "subErrorMsg": "One or more MCC/MNC pairs are empty.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } else if (notNumericInputCount > 0) { + // if notNumeric input is present + validationStatus = { + "error": true, + "subErrorMsg": "One or more MCC/MNC pairs are not numeric.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } else if (outOfAllowedLengthCount > 0) { + // if outOfAllowedLength input is present + validationStatus = { + "error": true, + "subErrorMsg": "One or more MCC/MNC pairs " + + "do not fulfill the accepted length of 6 digits.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } else if (duplicatesExist) { + validationStatus = { + "error": true, + "subErrorMsg": "Duplicate values exist with MCC/MNC pairs.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } + } + } + + // at-last, if the value of continueToCheckNextInputs is still true + // this means that no error is found + if (continueToCheckNextInputs) { + validationStatus = { + "error": false, + "okFeature": operation + }; + } + + // updating validationStatusArray with validationStatus + validationStatusArray.push(validationStatus); + } + // Validating EMAIL + if ($.inArray(iosOperationConstants["EMAIL_OPERATION_CODE"], configuredOperations) != -1) { + // if EMAIL is configured + operation = iosOperationConstants["EMAIL_OPERATION"]; + // initializing continueToCheckNextInputs to true + continueToCheckNextInputs = true; + + var emailAddress = $("input#email-address").val(); + if (emailAddress && !inputIsValidAgainstRegExp(/^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$/, emailAddress)) { + validationStatus = { + "error": true, + "subErrorMsg": "Email Address is not valid.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } + + if (continueToCheckNextInputs) { + var emailIncomingMailServerHostname = $("input#email-incoming-mail-server-hostname").val(); + if (!emailIncomingMailServerHostname) { + validationStatus = { + "error": true, + "subErrorMsg": "Incoming Mail Server Hostname is empty. You cannot proceed.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } + } + + if (continueToCheckNextInputs) { + var emailIncomingMailServerPort = $("input#email-incoming-mail-server-port").val(); + if (emailIncomingMailServerPort && emailIncomingMailServerPort != '') { + if (!$.isNumeric(emailIncomingMailServerPort)) { + validationStatus = { + "error": true, + "subErrorMsg": "Incoming Mail Server Port requires a number input.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } else if (!inputIsValidAgainstRange(emailIncomingMailServerPort, 0, 65535)) { + validationStatus = { + "error": true, + "subErrorMsg": "Incoming Mail Server Port is not within the range " + + "of valid port numbers.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } + } + } + + if (continueToCheckNextInputs) { + var emailOutgoingMailServerHostname = $("input#email-outgoing-mail-server-hostname").val(); + if (!emailOutgoingMailServerHostname) { + validationStatus = { + "error": true, + "subErrorMsg": "Outgoing Mail Server Hostname is empty. You cannot proceed.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } + } + + if (continueToCheckNextInputs) { + var emailOutgoingMailServerPort = $("input#email-outgoing-mail-server-port").val(); + if (emailOutgoingMailServerPort && emailOutgoingMailServerPort != '') { + if (!$.isNumeric(emailOutgoingMailServerPort)) { + validationStatus = { + "error": true, + "subErrorMsg": "Outgoing Mail Server Port requires a number input.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } else if (!inputIsValidAgainstRange(emailOutgoingMailServerPort, 0, 65535)) { + validationStatus = { + "error": true, + "subErrorMsg": "Outgoing Mail Server Port is not within the range " + + "of valid port numbers.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } + } + } + + // at-last, if the value of continueToCheckNextInputs is still true + // this means that no error is found + if (continueToCheckNextInputs) { + validationStatus = { + "error": false, + "okFeature": operation + }; + } + + // updating validationStatusArray with validationStatus + validationStatusArray.push(validationStatus); + } + // Validating AIRPLAY + if ($.inArray(iosOperationConstants["AIRPLAY_OPERATION_CODE"], configuredOperations) != -1) { + // if AIRPLAY is configured + operation = iosOperationConstants["AIRPLAY_OPERATION"]; + // initializing continueToCheckNextInputs to true + continueToCheckNextInputs = true; + + var airplayCredentialsGridChildInputs = "div#airplay-credentials .child-input"; + var airplayDestinationsGridChildInputs = "div#airplay-destinations .child-input"; + if ($(airplayCredentialsGridChildInputs).length == 0 && + $(airplayDestinationsGridChildInputs).length == 0) { + validationStatus = { + "error": true, + "subErrorMsg": "AirPlay settings have zero configurations attached.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } + + if (continueToCheckNextInputs) { + if ($(airplayCredentialsGridChildInputs).length > 0) { + childInputCount = 0; + childInputArray = []; + emptyChildInputCount = 0; + duplicatesExist = false; + // looping through each child input + $(airplayCredentialsGridChildInputs).each(function () { + childInputCount++; + if (childInputCount % 2 == 1) { + // if child input is of first column + childInput = $(this).val(); + childInputArray.push(childInput); + // updating emptyChildInputCount + if (!childInput) { + // if child input field is empty + emptyChildInputCount++; + } + } + }); + // checking for duplicates + initialChildInputArrayLength = childInputArray.length; + if (emptyChildInputCount == 0 && initialChildInputArrayLength > 1) { + for (m = 0; m < (initialChildInputArrayLength - 1); m++) { + poppedChildInput = childInputArray.pop(); + for (n = 0; n < childInputArray.length; n++) { + if (poppedChildInput == childInputArray[n]) { + duplicatesExist = true; + break; + } + } + if (duplicatesExist) { + break; + } + } + } + // updating validationStatus + if (emptyChildInputCount > 0) { + // if empty child inputs are present + validationStatus = { + "error": true, + "subErrorMsg": "One or more Device Names of " + + "AirPlay Credentials are empty.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } else if (duplicatesExist) { + // if duplicate input is present + validationStatus = { + "error": true, + "subErrorMsg": "Duplicate values exist with " + + "Device Names of AirPlay Credentials.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } + } + } + + if (continueToCheckNextInputs) { + if ($(airplayDestinationsGridChildInputs).length > 0) { + childInputArray = []; + emptyChildInputCount = 0; + invalidAgainstRegExCount = 0; + duplicatesExist = false; + // looping through each child input + $(airplayDestinationsGridChildInputs).each(function () { + childInput = $(this).val(); + childInputArray.push(childInput); + // updating emptyChildInputCount + if (!childInput) { + // if child input field is empty + emptyChildInputCount++; + } else if (!inputIsValidAgainstRegExp( + /([a-z|A-Z|0-9][a-z|A-Z|0-9][:]){5}([a-z|A-Z|0-9][a-z|A-Z|0-9])$/, childInput)) { + // if child input field is invalid against RegEx + invalidAgainstRegExCount++ + } + }); + // checking for duplicates + initialChildInputArrayLength = childInputArray.length; + if (emptyChildInputCount == 0 && initialChildInputArrayLength > 1) { + for (m = 0; m < (initialChildInputArrayLength - 1); m++) { + poppedChildInput = childInputArray.pop(); + for (n = 0; n < childInputArray.length; n++) { + if (poppedChildInput == childInputArray[n]) { + duplicatesExist = true; + break; + } + } + if (duplicatesExist) { + break; + } + } + } + // updating validationStatus + if (emptyChildInputCount > 0) { + // if empty child inputs are present + validationStatus = { + "error": true, + "subErrorMsg": "One or more AirPlay Destination fields are empty.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } else if (invalidAgainstRegExCount > 0) { + // if invalidAgainstRegEx inputs are present + validationStatus = { + "error": true, + "subErrorMsg": "One or more AirPlay Destination fields " + + "do not fulfill expected format.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } else if (duplicatesExist) { + // if duplicate input is present + validationStatus = { + "error": true, + "subErrorMsg": "Duplicate values exist with AirPlay Destinations.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } + } + } + + // at-last, if the value of continueToCheckNextInputs is still true + // this means that no error is found + if (continueToCheckNextInputs) { + validationStatus = { + "error": false, + "okFeature": operation + }; + } + + // updating validationStatusArray with validationStatus + validationStatusArray.push(validationStatus); + } + // Validating LDAP + if ($.inArray(iosOperationConstants["LDAP_OPERATION_CODE"], configuredOperations) != -1) { + // if LDAP is configured + operation = iosOperationConstants["LDAP_OPERATION"]; + // initializing continueToCheckNextInputs to true + continueToCheckNextInputs = true; + + var ldapAccountHostname = $("input#ldap-account-hostname").val(); + if (!ldapAccountHostname) { + validationStatus = { + "error": true, + "subErrorMsg": "LDAP Account Hostname URL is empty. You cannot proceed.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } + + if (continueToCheckNextInputs) { + var ldapSearchSettingsGridChildInputs = "div#ldap-search-settings .child-input"; + if ($(ldapSearchSettingsGridChildInputs).length > 0) { + childInputCount = 0; + childInputArray = []; + emptyChildInputCount = 0; + duplicatesExist = false; + // looping through each child input + $(ldapSearchSettingsGridChildInputs).each(function () { + childInputCount++; + if (childInputCount % 3 == 2) { + // if child input is of second column + childInput = $(this).find("option:selected").attr("value"); + stringPair = ""; + stringPair += (childInput + " "); + } else if (childInputCount % 3 == 0) { + // if child input is of third column + childInput = $(this).val(); + stringPair += childInput; + childInputArray.push(stringPair); + // updating emptyChildInputCount + if (!childInput) { + // if child input field is empty + emptyChildInputCount++; + } + } + }); + // checking for duplicates + initialChildInputArrayLength = childInputArray.length; + if (emptyChildInputCount == 0 && initialChildInputArrayLength > 1) { + for (m = 0; m < (initialChildInputArrayLength - 1); m++) { + poppedChildInput = childInputArray.pop(); + for (n = 0; n < childInputArray.length; n++) { + if (poppedChildInput == childInputArray[n]) { + duplicatesExist = true; + break; + } + } + if (duplicatesExist) { + break; + } + } + } + // updating validationStatus + if (emptyChildInputCount > 0) { + // if empty child inputs are present + validationStatus = { + "error": true, + "subErrorMsg": "One or more Search Setting Scope fields are empty.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } else if (duplicatesExist) { + // if duplicate input is present + validationStatus = { + "error": true, + "subErrorMsg": "Duplicate values exist with " + + "Search Setting Search Base and Scope pairs.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } + } + } + + // at-last, if the value of continueToCheckNextInputs is still true + // this means that no error is found + if (continueToCheckNextInputs) { + validationStatus = { + "error": false, + "okFeature": operation + }; + } + + // updating validationStatusArray with validationStatus + validationStatusArray.push(validationStatus); + } + // Validating CALENDAR + if ($.inArray(iosOperationConstants["CALENDAR_OPERATION_CODE"], configuredOperations) != -1) { + // if CALENDAR is configured + operation = iosOperationConstants["CALENDAR_OPERATION"]; + // initializing continueToCheckNextInputs to true + continueToCheckNextInputs = true; + + var calendarAccountHostname = $("input#calendar-account-hostname").val(); + if (!calendarAccountHostname) { + validationStatus = { + "error": true, + "subErrorMsg": "Account Hostname URL is empty. You cannot proceed.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } + + if (continueToCheckNextInputs) { + var calendarAccountPort = $("input#calendar-account-port").val(); + if (!calendarAccountPort) { + validationStatus = { + "error": true, + "subErrorMsg": "Account Port is empty. You cannot proceed.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } else if (!$.isNumeric(calendarAccountPort)) { + validationStatus = { + "error": true, + "subErrorMsg": "Account Port requires a number input.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } else if (!inputIsValidAgainstRange(calendarAccountPort, 0, 65535)) { + validationStatus = { + "error": true, + "subErrorMsg": "Account Port is not within the range " + + "of valid port numbers.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } + } + + // at-last, if the value of continueToCheckNextInputs is still true + // this means that no error is found + if (continueToCheckNextInputs) { + validationStatus = { + "error": false, + "okFeature": operation + }; + } + + // updating validationStatusArray with validationStatus + validationStatusArray.push(validationStatus); + } + // Validating CALENDAR_SUBSCRIPTION + if ($.inArray(iosOperationConstants["CALENDAR_SUBSCRIPTION_OPERATION_CODE"], configuredOperations) != -1) { + // if CALENDAR_SUBSCRIPTION is configured + operation = iosOperationConstants["CALENDAR_SUBSCRIPTION_OPERATION"]; + // initializing continueToCheckNextInputs to true + continueToCheckNextInputs = true; + + var calendarSubscriptionHostname = $("input#calendar-subscription-hostname").val(); + if (!calendarSubscriptionHostname) { + validationStatus = { + "error": true, + "subErrorMsg": "Account Hostname URL is empty. You cannot proceed.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } + + // at-last, if the value of continueToCheckNextInputs is still true + // this means that no error is found + if (continueToCheckNextInputs) { + validationStatus = { + "error": false, + "okFeature": operation + }; + } + + // updating validationStatusArray with validationStatus + validationStatusArray.push(validationStatus); + } + // Validating APN + if ($.inArray(iosOperationConstants["APN_OPERATION_CODE"], configuredOperations) != -1) { + // if APN is configured + operation = iosOperationConstants["APN_OPERATION"]; + // initializing continueToCheckNextInputs to true + continueToCheckNextInputs = true; + + var apnConfigurationsGridChildInputs = "div#apn-configurations .child-input"; + if ($(apnConfigurationsGridChildInputs).length == 0) { + validationStatus = { + "error": true, + "subErrorMsg": "APN Settings have zero configurations attached.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } else if ($(apnConfigurationsGridChildInputs).length > 0) { + childInputCount = 0; + childInputArray = []; + // checking empty APN field count + emptyChildInputCount = 0; + duplicatesExist = false; + // looping through each child input + $(apnConfigurationsGridChildInputs).each(function () { + childInputCount++; + if (childInputCount % 5 == 1) { + // if child input is of first column + childInput = $(this).val(); + childInputArray.push(childInput); + // updating emptyChildInputCount + if (!childInput) { + // if child input field is empty + emptyChildInputCount++; + } + } + }); + // checking for duplicates + initialChildInputArrayLength = childInputArray.length; + if (emptyChildInputCount == 0 && initialChildInputArrayLength > 1) { + for (m = 0; m < (initialChildInputArrayLength - 1); m++) { + poppedChildInput = childInputArray.pop(); + for (n = 0; n < childInputArray.length; n++) { + if (poppedChildInput == childInputArray[n]) { + duplicatesExist = true; + break; + } + } + if (duplicatesExist) { + break; + } + } + } + // updating validationStatus + if (emptyChildInputCount > 0) { + // if empty child inputs are present + validationStatus = { + "error": true, + "subErrorMsg": "One or more APN fields of Configurations are empty.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } else if (duplicatesExist) { + // if duplicate input is present + validationStatus = { + "error": true, + "subErrorMsg": "Duplicate values exist with " + + "APN fields of Configurations.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } + } + + // at-last, if the value of continueToCheckNextInputs is still true + // this means that no error is found + if (continueToCheckNextInputs) { + validationStatus = { + "error": false, + "okFeature": operation + }; + } + + // updating validationStatusArray with validationStatus + validationStatusArray.push(validationStatus); + } + // Validating CELLULAR + if ($.inArray(iosOperationConstants["CELLULAR_OPERATION_CODE"], configuredOperations) != -1) { + // if CELLULAR is configured + operation = iosOperationConstants["CELLULAR_OPERATION"]; + // initializing continueToCheckNextInputs to true + continueToCheckNextInputs = true; + + var cellularAttachAPNName = $("input#cellular-attach-apn-name").val(); + if (!cellularAttachAPNName) { + validationStatus = { + "error": true, + "subErrorMsg": "Cellular Configuration Name is empty. You cannot proceed.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } + + if (continueToCheckNextInputs) { + var cellularAPNConfigurationsGridChildInputs = "div#cellular-apn-configurations .child-input"; + if ($(cellularAPNConfigurationsGridChildInputs).length > 0) { + childInputCount = 0; + childInputArray = []; + // checking empty APN field count + emptyChildInputCount = 0; + duplicatesExist = false; + // looping through each child input + $(cellularAPNConfigurationsGridChildInputs).each(function () { + childInputCount++; + if (childInputCount % 6 == 1) { + // if child input is of first column + childInput = $(this).val(); + childInputArray.push(childInput); + // updating emptyChildInputCount + if (!childInput) { + // if child input field is empty + emptyChildInputCount++; + } + } + }); + // checking for duplicates + initialChildInputArrayLength = childInputArray.length; + if (emptyChildInputCount == 0 && initialChildInputArrayLength > 1) { + for (m = 0; m < (initialChildInputArrayLength - 1); m++) { + poppedChildInput = childInputArray.pop(); + for (n = 0; n < childInputArray.length; n++) { + if (poppedChildInput == childInputArray[n]) { + duplicatesExist = true; + break; + } + } + if (duplicatesExist) { + break; + } + } + } + // updating validationStatus + if (emptyChildInputCount > 0) { + // if empty child inputs are present + validationStatus = { + "error": true, + "subErrorMsg": "One or more APN fields of APN Configurations are empty.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } else if (duplicatesExist) { + // if duplicate input is present + validationStatus = { + "error": true, + "subErrorMsg": "Duplicate values exist with " + + "APN fields of APN Configurations.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } + } + } + + // at-last, if the value of continueToCheckNextInputs is still true + // this means that no error is found + if (continueToCheckNextInputs) { + validationStatus = { + "error": false, + "okFeature": operation + }; + } + + // updating validationStatusArray with validationStatus + validationStatusArray.push(validationStatus); + } + } + } + // ending validation process + + // start taking specific notifying actions upon validation + var wizardIsToBeContinued; + var errorCount = 0; + var mainErrorMsgWrapper, mainErrorMsg, + subErrorMsgWrapper, subErrorMsg, subErrorIcon, subOkIcon, featureConfiguredIcon; + var i; + for (i = 0; i < validationStatusArray.length; i++) { + validationStatus = validationStatusArray[i]; + if (validationStatus["error"]) { + errorCount++; + if (validationStatus["mainErrorMsg"]) { + mainErrorMsgWrapper = "#policy-profile-main-error-msg"; + mainErrorMsg = mainErrorMsgWrapper + " span"; + $(mainErrorMsg).text(validationStatus["mainErrorMsg"]); + $(mainErrorMsgWrapper).removeClass("hidden"); + } else if (validationStatus["subErrorMsg"]) { + subErrorMsgWrapper = "#" + validationStatus["erroneousFeature"] + "-feature-error-msg"; + subErrorMsg = subErrorMsgWrapper + " span"; + subErrorIcon = "#" + validationStatus["erroneousFeature"] + "-error"; + subOkIcon = "#" + validationStatus["erroneousFeature"] + "-ok"; + featureConfiguredIcon = "#" + validationStatus["erroneousFeature"] + "-configured"; + // hiding featureConfiguredState as the first step + if (!$(featureConfiguredIcon).hasClass("hidden")) { + $(featureConfiguredIcon).addClass("hidden"); + } + // updating error state and corresponding messages + $(subErrorMsg).text(validationStatus["subErrorMsg"]); + if ($(subErrorMsgWrapper).hasClass("hidden")) { + $(subErrorMsgWrapper).removeClass("hidden"); + } + if (!$(subOkIcon).hasClass("hidden")) { + $(subOkIcon).addClass("hidden"); + } + if ($(subErrorIcon).hasClass("hidden")) { + $(subErrorIcon).removeClass("hidden"); + } + } + } else { + if (validationStatus["okFeature"]) { + subErrorMsgWrapper = "#" + validationStatus["okFeature"] + "-feature-error-msg"; + subErrorIcon = "#" + validationStatus["okFeature"] + "-error"; + subOkIcon = "#" + validationStatus["okFeature"] + "-ok"; + featureConfiguredIcon = "#" + validationStatus["okFeature"] + "-configured"; + // hiding featureConfiguredState as the first step + if (!$(featureConfiguredIcon).hasClass("hidden")) { + $(featureConfiguredIcon).addClass("hidden"); + } + // updating success state and corresponding messages + if (!$(subErrorMsgWrapper).hasClass("hidden")) { + $(subErrorMsgWrapper).addClass("hidden"); + } + if (!$(subErrorIcon).hasClass("hidden")) { + $(subErrorIcon).addClass("hidden"); + } + if ($(subOkIcon).hasClass("hidden")) { + $(subOkIcon).removeClass("hidden"); + } + } + } + } + + wizardIsToBeContinued = (errorCount == 0); + return wizardIsToBeContinued; +}; + +stepForwardFrom["policy-profile"] = function () { + policy["profile"] = operationModule.generateProfile(policy["platform"], configuredOperations); + // updating next-page wizard title with selected platform + $("#policy-criteria-page-wizard-title").text("EDIT " + policy["platform"] + " POLICY - " + policy["name"]); + // updating ownership type options according to platform + if (policy["platform"] == platformTypeConstants["IOS"] || + policy["platform"] == platformTypeConstants["WINDOWS"]) { + var ownershipTypeSelectOptions = $("#ownership-input"); + ownershipTypeSelectOptions.empty(); + ownershipTypeSelectOptions.append($(""). + attr("value", "BYOD").text("BYOD (Bring Your Own Device)")); + ownershipTypeSelectOptions.attr("disabled", true); + } +}; + +stepForwardFrom["policy-criteria"] = function () { + $("input[type='radio'].select-users-radio").each(function () { + if ($(this).is(':radio')) { + if ($(this).is(":checked")) { + if ($(this).attr("id") == "users-radio-btn") { + policy["selectedUsers"] = $("#users-input").val(); + } else if ($(this).attr("id") == "user-roles-radio-btn") { + policy["selectedUserRoles"] = $("#user-roles-input").val(); + } + } + } + }); + policy["selectedNonCompliantAction"] = $("#action-input").find(":selected").data("action"); + policy["selectedOwnership"] = $("#ownership-input").val(); + // updating next-page wizard title with selected platform + $("#policy-naming-page-wizard-title").text("EDIT " + policy["platform"] + " POLICY - " + policy["name"]); +}; + +/** + * Checks if provided input is valid against provided length range. + * + * @param input Alphanumeric or non-alphanumeric input + * @param minLength Minimum Required Length + * @param maxLength Maximum Required Length + * @returns {boolean} Returns true if input matches the provided minimum length and maximum length + */ +var inputIsValidAgainstLength = function (input, minLength, maxLength) { + var length = input.length; + return (length == minLength || (length > minLength && length < maxLength) || length == maxLength); +}; + +validateStep["policy-criteria"] = function () { + var validationStatus = {}; + var selectedAssignees; + var selectedField = "Role(s)"; + + $("input[type='radio'].select-users-radio").each(function () { + if ($(this).is(":checked")) { + if ($(this).attr("id") == "users-radio-btn") { + selectedAssignees = $("#users-input").val(); + selectedField = "User(s)"; + } else if ($(this).attr("id") == "user-roles-radio-btn") { + selectedAssignees = $("#user-roles-input").val(); + } + return false; + } + }); + + if (selectedAssignees) { + validationStatus["error"] = false; + } else { + validationStatus["error"] = true; + validationStatus["mainErrorMsg"] = selectedField + " is a required field. It cannot be empty"; + } + + var wizardIsToBeContinued; + if (validationStatus["error"]) { + wizardIsToBeContinued = false; + var mainErrorMsgWrapper = "#policy-criteria-main-error-msg"; + var mainErrorMsg = mainErrorMsgWrapper + " span"; + $(mainErrorMsg).text(validationStatus["mainErrorMsg"]); + $(mainErrorMsgWrapper).removeClass("hidden"); + } else { + wizardIsToBeContinued = true; + } + + return wizardIsToBeContinued; +}; + +validateStep["policy-naming"] = function () { + var validationStatus = {}; + + // taking values of inputs to be validated + var policyName = $("input#policy-name-input").val(); + // starting validation process and updating validationStatus + if (!policyName) { + validationStatus["error"] = true; + validationStatus["mainErrorMsg"] = "Policy name is empty. You cannot proceed."; + } else if (!inputIsValidAgainstLength(policyName, 1, 30)) { + validationStatus["error"] = true; + validationStatus["mainErrorMsg"] = + "Policy name exceeds maximum allowed length."; + } else { + validationStatus["error"] = false; + } + // ending validation process + + // start taking specific actions upon validation + var wizardIsToBeContinued; + if (validationStatus["error"]) { + wizardIsToBeContinued = false; + var mainErrorMsgWrapper = "#policy-naming-main-error-msg"; + var mainErrorMsg = mainErrorMsgWrapper + " span"; + $(mainErrorMsg).text(validationStatus["mainErrorMsg"]); + $(mainErrorMsgWrapper).removeClass("hidden"); + } else { + wizardIsToBeContinued = true; + } + + return wizardIsToBeContinued; +}; + +validateStep["policy-naming-publish"] = function () { + var validationStatus = {}; + + // taking values of inputs to be validated + var policyName = $("input#policy-name-input").val(); + // starting validation process and updating validationStatus + if (!policyName) { + validationStatus["error"] = true; + validationStatus["mainErrorMsg"] = "Policy name is empty. You cannot proceed."; + } else if (!inputIsValidAgainstLength(policyName, 1, 30)) { + validationStatus["error"] = true; + validationStatus["mainErrorMsg"] = + "Policy name exceeds maximum allowed length."; + } else { + validationStatus["error"] = false; + } + // ending validation process + + // start taking specific actions upon validation + var wizardIsToBeContinued; + if (validationStatus["error"]) { + wizardIsToBeContinued = false; + var mainErrorMsgWrapper = "#policy-naming-main-error-msg"; + var mainErrorMsg = mainErrorMsgWrapper + " span"; + $(mainErrorMsg).text(validationStatus["mainErrorMsg"]); + $(mainErrorMsgWrapper).removeClass("hidden"); + } else { + wizardIsToBeContinued = true; + } + + return wizardIsToBeContinued; +}; + +stepForwardFrom["policy-naming-publish"] = function () { + policy["policyName"] = $("#policy-name-input").val(); + policy["description"] = $("#policy-description-input").val(); + //All data is collected. Policy can now be updated. + updatePolicy(policy, "publish"); +}; +stepForwardFrom["policy-naming"] = function () { + policy["policyName"] = $("#policy-name-input").val(); + policy["description"] = $("#policy-description-input").val(); + //All data is collected. Policy can now be updated. + updatePolicy(policy, "save"); +}; + +var updatePolicy = function (policy, state) { + var profilePayloads = []; + // traverses key by key in policy["profile"] + var key; + for (key in policy["profile"]) { + if (policy["platformId"] == platformTypeIds["WINDOWS"] && key == windowsOperationConstants["PASSCODE_POLICY_OPERATION_CODE"]) { + policy["profile"][key].enablePassword = true; + } + + if (policy["profile"].hasOwnProperty(key)) { + profilePayloads.push({ + "featureCode": key, + "deviceTypeId": policy["platformId"], + "content": policy["profile"][key] + }); + } + } + + $.each(profilePayloads, function (i, item) { + $.each(item.content, function (key, value) { + if (value === "" || value === undefined) { + item.content[key] = null; + } + }); + }); + + var payload = { + "policyName": policy["policyName"], + "description": policy["description"], + "compliance": policy["selectedNonCompliantAction"], + "ownershipType": policy["selectedOwnership"], + "profile": { + "profileName": policy["policyName"], + "deviceType": { + "id": policy["platformId"] + }, + "profileFeaturesList": profilePayloads + } + }; + + if (policy["selectedUsers"]) { + payload["users"] = policy["selectedUsers"]; + } else if (policy["selectedUserRoles"]) { + payload["roles"] = policy["selectedUserRoles"]; + } else { + payload["users"] = []; + payload["roles"] = []; + } + + var serviceURL = "/devicemgt_admin/policies/" + getParameterByName("id"); + invokerUtil.put( + serviceURL, + payload, + // on success + function () { + if (state == "save") { + var policyList = []; + policyList.push(getParameterByName("id")); + serviceURL = "/devicemgt_admin/policies/inactivate"; + invokerUtil.put( + serviceURL, + policyList, + // on success + function () { + $(".add-policy").addClass("hidden"); + $(".policy-message").removeClass("hidden"); + }, + // on error + function (daa) { + console.log(data); + } + ); + } else if (state == "publish") { + var policyList = []; + policyList.push(getParameterByName("id")); + serviceURL = "/devicemgt_admin/policies/activate"; + invokerUtil.put( + serviceURL, + policyList, + // on success + function () { + $(".add-policy").addClass("hidden"); + $(".policy-naming").addClass("hidden"); + $(".policy-message").removeClass("hidden"); + }, + // on error + function (data) { + console.log(data); + } + ); + } + }, + // on error + function () { + + } + ); +}; + +// Start of HTML embedded invoke methods +var showAdvanceOperation = function (operation, button) { + $(button).addClass('selected'); + $(button).siblings().removeClass('selected'); + var hiddenOperation = ".wr-hidden-operations-content > div"; + $(hiddenOperation + '[data-operation="' + operation + '"]').show(); + $(hiddenOperation + '[data-operation="' + operation + '"]').siblings().hide(); +}; + +/** + * Method to slide down a provided pane upon provided value set. + * + * @param selectElement Select HTML Element to consider + * @param paneID HTML ID of div element to slide down + * @param valueSet Applicable Value Set + */ +var slideDownPaneAgainstValueSet = function (selectElement, paneID, valueSet) { + var selectedValueOnChange = $(selectElement).find("option:selected").val(); + var i, slideDownVotes = 0; + for (i = 0; i < valueSet.length; i++) { + if (selectedValueOnChange == valueSet[i]) { + slideDownVotes++; + } + } + var paneSelector = "#" + paneID; + if (slideDownVotes > 0) { + if (!$(paneSelector).hasClass("expanded")) { + $(paneSelector).addClass("expanded"); + } + $(paneSelector).slideDown(); + } else { + if ($(paneSelector).hasClass("expanded")) { + $(paneSelector).removeClass("expanded"); + } + $(paneSelector).slideUp(); + /** now follows the code to reinitialize all inputs of the slidable pane */ + // reinitializing input fields into the defaults + $(paneSelector + " input").each( + function () { + if ($(this).is("input:text")) { + $(this).val($(this).data("default")); + } else if ($(this).is("input:password")) { + $(this).val(""); + } else if ($(this).is("input:checkbox")) { + $(this).prop("checked", $(this).data("default")); + // if this checkbox is the parent input of a grouped-input + if ($(this).hasClass("parent-input")) { + var groupedInput = $(this).parent().parent().parent(); + updateGroupedInputVisibility(groupedInput); + } + } + } + ); + // reinitializing select fields into the defaults + $(paneSelector + " select").each( + function () { + var defaultOption = $(this).data("default"); + $("option:eq(" + defaultOption + ")", this).prop("selected", "selected"); + } + ); + // collapsing expanded-panes (upon the selection of html-select-options) if any + $(paneSelector + " .expanded").each( + function () { + if ($(this).hasClass("expanded")) { + $(this).removeClass("expanded"); + } + $(this).slideUp(); + } + ); + // removing all entries of grid-input elements if exist + $(paneSelector + " .grouped-array-input").each( + function () { + var gridInputs = $(this).find("[data-add-form-clone]"); + if (gridInputs.length > 0) { + gridInputs.remove(); + } + var helpTexts = $(this).find("[data-help-text=add-form]"); + if (helpTexts.length > 0) { + helpTexts.show(); + } + } + ); + } +}; +// End of HTML embedded invoke methods + + +// Start of functions related to grid-input-view + +/** + * Method to set count id to cloned elements. + * @param {object} addFormContainer + */ +var setId = function (addFormContainer) { + $(addFormContainer).find("[data-add-form-clone]").each(function (i) { + $(this).attr("id", $(this).attr("data-add-form-clone").slice(1) + "-" + (i + 1)); + if ($(this).find(".index").length > 0) { + $(this).find(".index").html(i + 1); + } + }); +}; + +/** + * Method to set count id to cloned elements. + * @param {object} addFormContainer + */ +var showHideHelpText = function (addFormContainer) { + var helpText = "[data-help-text=add-form]"; + if ($(addFormContainer).find("[data-add-form-clone]").length > 0) { + $(addFormContainer).find(helpText).hide(); + } else { + $(addFormContainer).find(helpText).show(); + } +}; + +// End of functions related to grid-input-view + +/** + * This method will return query parameter value given its name. + * @param name Query parameter name + * @returns {string} Query parameter value + */ +var getParameterByName = function (name) { + name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]"); + var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"), + results = regex.exec(location.search); + return results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " ")); +}; + +function formatRepo(user) { + if (user.loading) { + return user.text + } + if (!user.username) { + return; + } + var markup = '
      ' + + '
      ' + + '
      ' + + '
      ' + user.username + '
      '; + if (user.firstname) { + markup += '
      ' + user.firstname + '
      '; + } + if (user.emailAddress) { + markup += '
      ' + user.emailAddress + '
      '; + } + markup += '
      '; + return markup; +} + +function formatRepoSelection(user) { + return user.username || user.text; +} + +$(document).ready(function () { + + // Adding initial state of wizard-steps. + + var policyPayloadObj; + invokerUtil.get( + "/devicemgt_admin/policies/" + getParameterByName("id"), + // on success + function (data) { + data = JSON.parse(data); + policyPayloadObj = data["responseContent"]; + skipStep["policy-platform"](policyPayloadObj); + }, + // on error + function (data) { + console.log(data); + // should be redirected to an error page + } + ); + + $("#users-select-field").hide(); + $("#user-roles-select-field").show(); + + $("input[type='radio'].select-users-radio").change(function () { + if ($("#users-radio-btn").is(":checked")) { + $("#user-roles-select-field").hide(); + $("#users-select-field").show(); + } + if ($("#user-roles-radio-btn").is(":checked")) { + $("#users-select-field").hide(); + $("#user-roles-select-field").show(); + } + }); + + // Support for special input type "ANY" on user(s) & user-role(s) selection + $("#user-roles-input,#user-input").select2({ + "tags": false + }).on("select2:select", function (e) { + if (e.params.data.id == "ANY") { + $(this).val("ANY").trigger("change"); + } else { + $("option[value=ANY]", this).prop("selected", false).parent().trigger("change"); + } + }); + $("#policy-profile-wizard-steps").html($(".wr-steps").html()); + + $("select.select2[multiple=multiple]").select2({ + "tags": false + }); + + // Maintains an array of configured features of the profile + var advanceOperations = ".wr-advance-operations"; + $(advanceOperations).on("click", ".wr-input-control.switch", function (event) { + var operationCode = $(this).parents(".operation-data").data("operation-code"); + var operation = $(this).parents(".operation-data").data("operation"); + var operationDataWrapper = $(this).data("target"); + // prevents event bubbling by figuring out what element it's being called from. + if (event.target.tagName == "INPUT") { + var featureConfiguredIcon; + if ($("input[type='checkbox']", this).is(":checked")) { + configuredOperations.push(operationCode); + // when a feature is enabled, if "zero-configured-features" msg is available, hide that. + var zeroConfiguredOperationsErrorMsg = "#policy-profile-main-error-msg"; + if (!$(zeroConfiguredOperationsErrorMsg).hasClass("hidden")) { + $(zeroConfiguredOperationsErrorMsg).addClass("hidden"); + } + // add configured-state-icon to the feature + featureConfiguredIcon = "#" + operation + "-configured"; + if ($(featureConfiguredIcon).hasClass("hidden")) { + $(featureConfiguredIcon).removeClass("hidden"); + } + } else { + //splicing the array if operation is present. + var index = $.inArray(operationCode, configuredOperations); + if (index != -1) { + configuredOperations.splice(index, 1); + } + // when a feature is disabled, clearing all its current configured, error or success states + var subErrorMsgWrapper = "#" + operation + "-feature-error-msg"; + var subErrorIcon = "#" + operation + "-error"; + var subOkIcon = "#" + operation + "-ok"; + featureConfiguredIcon = "#" + operation + "-configured"; + + if (!$(subErrorMsgWrapper).hasClass("hidden")) { + $(subErrorMsgWrapper).addClass("hidden"); + } + if (!$(subErrorIcon).hasClass("hidden")) { + $(subErrorIcon).addClass("hidden"); + } + if (!$(subOkIcon).hasClass("hidden")) { + $(subOkIcon).addClass("hidden"); + } + if (!$(featureConfiguredIcon).hasClass("hidden")) { + $(featureConfiguredIcon).addClass("hidden"); + } + // reinitializing input fields into the defaults + $(operationDataWrapper + " input").each( + function () { + if ($(this).is("input:text")) { + $(this).val($(this).data("default")); + } else if ($(this).is("input:password")) { + $(this).val(""); + } else if ($(this).is("input:checkbox")) { + $(this).prop("checked", $(this).data("default")); + // if this checkbox is the parent input of a grouped-input + if ($(this).hasClass("parent-input")) { + var groupedInput = $(this).parent().parent().parent(); + updateGroupedInputVisibility(groupedInput); + } + } + } + ); + // reinitializing select fields into the defaults + $(operationDataWrapper + " select").each( + function () { + var defaultOption = $(this).data("default"); + $("option:eq(" + defaultOption + ")", this).prop("selected", "selected"); + } + ); + // collapsing expanded-panes (upon the selection of html-select-options) if any + $(operationDataWrapper + " .expanded").each( + function () { + if ($(this).hasClass("expanded")) { + $(this).removeClass("expanded"); + } + $(this).slideUp(); + } + ); + // removing all entries of grid-input elements if exist + $(operationDataWrapper + " .grouped-array-input").each( + function () { + var gridInputs = $(this).find("[data-add-form-clone]"); + if (gridInputs.length > 0) { + gridInputs.remove(); + } + var helpTexts = $(this).find("[data-help-text=add-form]"); + if (helpTexts.length > 0) { + helpTexts.show(); + } + } + ); + } + } + }); + + // adding support for cloning multiple profiles per feature with cloneable class definitions + $(advanceOperations).on("click", ".multi-view.add.enabled", function () { + // get a copy of .cloneable and create new .cloned div element + var cloned = "

      " + $(".cloneable", $(this).parent().parent()).html() + "
      "; + // append newly created .cloned div element to panel-body + $(this).parent().parent().append(cloned); + // enable remove action of newly cloned div element + $(".cloned", $(this).parent().parent()).each( + function () { + if ($(".multi-view.remove", this).hasClass("disabled")) { + $(".multi-view.remove", this).removeClass("disabled"); + } + if (!$(".multi-view.remove", this).hasClass("enabled")) { + $(".multi-view.remove", this).addClass("enabled"); + } + } + ); + }); + + $(advanceOperations).on("click", ".multi-view.remove.enabled", function () { + $(this).parent().remove(); + }); + + // enabling or disabling grouped-input based on the status of a parent check-box + $(advanceOperations).on("click", ".grouped-input", function () { + updateGroupedInputVisibility(this); + }); + + // add form entry click function for grid inputs + $(advanceOperations).on("click", "[data-click-event=add-form]", function () { + var addFormContainer = $("[data-add-form-container=" + $(this).attr("href") + "]"); + var clonedForm = $("[data-add-form=" + $(this).attr("href") + "]").clone(). + find("[data-add-form-element=clone]").attr("data-add-form-clone", $(this).attr("href")); + + // adding class .child-input to capture text-input-array-values + $("input, select", clonedForm).addClass("child-input"); + + $(addFormContainer).append(clonedForm); + setId(addFormContainer); + showHideHelpText(addFormContainer); + }); + + // remove form entry click function for grid inputs + $(advanceOperations).on("click", "[data-click-event=remove-form]", function () { + var addFormContainer = $("[data-add-form-container=" + $(this).attr("href") + "]"); + + $(this).closest("[data-add-form-element=clone]").remove(); + setId(addFormContainer); + showHideHelpText(addFormContainer); + }); + + $(".wizard-stepper").click(function () { + // button clicked here can be either a continue button or a back button. + var currentStep = $(this).data("current"); + var validationIsRequired = $(this).data("validate"); + var wizardIsToBeContinued; + + if (validationIsRequired) { + wizardIsToBeContinued = validateStep[currentStep](); + } else { + wizardIsToBeContinued = true; + } + + if (wizardIsToBeContinued) { + // When moving back and forth, following code segment will + // remove if there are any visible error-messages. + var errorMsgWrappers = ".alert.alert-danger"; + $(errorMsgWrappers).each( + function () { + if (!$(this).hasClass("hidden")) { + $(this).addClass("hidden"); + } + } + ); + + var nextStep = $(this).data("next"); + var isBackBtn = $(this).data("is-back-btn"); + + // if current button is a continuation... + if (!isBackBtn) { + // initiate stepForwardFrom[*] functions to gather form data. + if (stepForwardFrom[currentStep]) { + stepForwardFrom[currentStep](this); + } + } else { + // initiate stepBackFrom[*] functions to rollback. + if (stepBackFrom[currentStep]) { + stepBackFrom[currentStep](); + } + } + + // following step occurs only at the last stage of the wizard. + if (!nextStep) { + window.location.href = $(this).data("direct"); + } + + // updating next wizard step as current. + $(".itm-wiz").each(function () { + var step = $(this).data("step"); + if (step == nextStep) { + $(this).addClass("itm-wiz-current"); + } else { + $(this).removeClass("itm-wiz-current"); + } + }); + + // adding next update of wizard-steps. + $("#" + nextStep + "-wizard-steps").html($(".wr-steps").html()); + + // hiding current section of the wizard and showing next section. + $("." + currentStep).addClass("hidden"); + $("." + nextStep).removeClass("hidden"); + } + }); + +}); \ No newline at end of file diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/src/main/resources/jaggeryapps/devicemgt/app/units/mdm.unit.policy.edit/public/templates/hidden-operations-android.hbs b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/src/main/resources/jaggeryapps/devicemgt/app/units/mdm.unit.policy.edit/public/templates/hidden-operations-android.hbs new file mode 100644 index 0000000000..d82da3c0f7 --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/src/main/resources/jaggeryapps/devicemgt/app/units/mdm.unit.policy.edit/public/templates/hidden-operations-android.hbs @@ -0,0 +1,457 @@ +
      + + +
      + +
      +
      + +
      + + + +
      + +
      + +
      + +
      + +
      + + +
      + +
      + + +
      + +
      + + +
      + +
      + + +
      + +
      + + +
      +
      +
      +
      + + + +
      +
      + +
      + + Un-check following checkbox in case you need to disable camera. +
      +
      +
      + +
      +
      +
      +
      +
      + + + +
      +
      + +
      + + Un-check following checkbox in case you do not need the device to be encrypted. +
      +
      +
      + +
      +
      +
      +
      +
      + + + +
      +
      + +
      + + + + + + + + + + + + + + + + + Please note that * sign represents required fields of data. +
      +
      + +
      + + +
      +
      + + +
      + +
      +
      +
      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      +
      \ No newline at end of file diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/src/main/resources/jaggeryapps/devicemgt/app/units/mdm.unit.policy.edit/public/templates/hidden-operations-ios.hbs b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/src/main/resources/jaggeryapps/devicemgt/app/units/mdm.unit.policy.edit/public/templates/hidden-operations-ios.hbs new file mode 100644 index 0000000000..aec18bda88 --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/src/main/resources/jaggeryapps/devicemgt/app/units/mdm.unit.policy.edit/public/templates/hidden-operations-ios.hbs @@ -0,0 +1,2923 @@ +
      + + +
      + +
      +
      + +
      + + + +
      + +
      + +
      + +
      + +
      + +
      + +
      + + +
      + +
      + + +
      + +
      + + +
      + +
      + + +
      + +
      + + +
      + +
      + + +
      + +
      + + +
      +
      +
      +
      + + + +
      +
      + +
      + + +
      + + +
      + +
      + + +
      + +
      + +
      + +
      + +
      + +
      + +
      + +
      + +
      + +
      + + +
      + +
      + + +
      + + + +
      + + +
      + + + +
      + +
      + + + + + + + + + + + + + +
      No:Roaming Consortium OI
      + No entries added yet . +
      + + + + + + + + + +
      +
      + +
      + +
      + +
      + + + + + + + + + + + + + +
      No:NAI Realm Name
      + No entries added yet . +
      + + + + + + + + + +
      +
      + +
      + +
      + +
      + + + + + + + + + + + + + + +
      No:Mobile Country Code ( MCC )Mobile Network Code ( MNC )
      + No entries added yet . +
      + + + + + + + + + + +
      +
      +
      +
      +
      + + + +
      +
      + +
      + + + +
      + + +
      + +
      + + +
      + +
      + +
      + + +
      + +
      + +
      + + +
      + +
      + + +
      + +
      + +
      + +
      + +
      + +
      + +
      + +
      + + +
      + +
      + + +
      + +
      + +
      + +
      + +
      + +
      + Incoming Mail Settings : +
      + +
      + + +
      + +
      + +
      + +
      + + +
      + +
      + + +
      + +
      + + +
      + +
      + + +
      + +
      + Outgoing Mail Settings : +
      + +
      + + +
      + +
      + +
      + +
      + + +
      + +
      + + +
      + +
      + + +
      + +
      + +
      + +
      + + +
      + +
      +
      +
      + + + +
      +
      + +
      + + + +
      + +
      + + + + + + + + + + + + + + +
      No:Device NamePassword
      + No entries added yet . +
      + + + + + + + + + + +
      +
      + +
      + +
      + +
      + + + + + + + + + + + + + +
      No:Destination
      + No entries added yet . +
      + + + + + + + + + +
      +
      + +
      +
      +
      + + + +
      +
      + +
      + + + +
      + + +
      + +
      + + +
      + +
      + +
      + +
      + + +
      + +
      + + +
      + +
      + +
      + + + + + + + + + + + + + + + +
      No:DescriptionSearch BaseScope
      + No entries added yet . +
      + + + + + + + + + + + +
      +
      + +
      +
      +
      + + + +
      +
      + +
      + + + +
      + + +
      + +
      + + +
      + +
      + +
      + +
      + + +
      + +
      + + +
      + +
      + + +
      + +
      + + +
      + +
      +
      +
      + + + +
      +
      + +
      + + + +
      + + +
      + +
      + + +
      + +
      + +
      + +
      + + +
      + +
      + + +
      + +
      +
      +
      + + + +
      +
      + +
      + + + +
      + +
      + + + + + + + + + + + + + + + + + +
      No:APNUsernamePasswordProxyPort
      + No entries added yet . +
      + + + + + + + + + + + + + +
      +
      + +
      +
      +
      + + + +
      +
      + +
      + + + +
      + + +
      + +
      + + +
      + +
      + + +
      + +
      + + +
      + +
      + +
      + + + + + + + + + + + + + + + + + + +
      No:APNAuth.TypeUsernamePasswordProxyPort
      + No entries added yet . +
      + + + + + + + + + + + + + + +
      +
      + +
      +
      +
      + + + +
      +
      + +
      + + + + Restrictions on Device Functionality : +
      +
      +
        +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
          +
        • +
          + +
          +
        • +
        • +
          + +
          +
        • +
        • +
          + +
          +
        • +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      +
      +
      + Restrictions on Applications : +
      +
      +
        +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
          +
        • +
          + +
          +
        • +
        • +
          + +
          +
        • +
        • +
          + +
          +
        • +
        • +
          + +
          +
        • +
        • +
          + + +
          +
        • +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      +
      + + + +
      +
      +
      + +
      +
      \ No newline at end of file diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/src/main/resources/jaggeryapps/devicemgt/app/units/mdm.unit.policy.edit/public/templates/hidden-operations-windows.hbs b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/src/main/resources/jaggeryapps/devicemgt/app/units/mdm.unit.policy.edit/public/templates/hidden-operations-windows.hbs new file mode 100644 index 0000000000..3b5bb3bef4 --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/src/main/resources/jaggeryapps/devicemgt/app/units/mdm.unit.policy.edit/public/templates/hidden-operations-windows.hbs @@ -0,0 +1,460 @@ +
      + + +
      + +
      +
      + +
      + + + +
      + +
      + +
      + +
      + +
      + + +
      + +
      + + +
      + +
      + + +
      + +
      + + +
      + +
      + + +
      +
      +
      +
      + + + +
      +
      + +
      + + Un-check following checkbox in case you need to disable camera. +
      +
      +
      + +
      +
      +
      +
      +
      + + + +
      +
      + +
      + + Un-check following checkbox in case you need to disable storage-encryption. +
      +
      +
      + +
      +
      +
      +
      +
      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      +
      \ No newline at end of file diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/src/main/resources/jaggeryapps/devicemgt/app/units/mdm.unit.policy.view/public/js/policy-view.js b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/src/main/resources/jaggeryapps/devicemgt/app/units/mdm.unit.policy.view/public/js/policy-view.js new file mode 100644 index 0000000000..8ca8ff8791 --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/src/main/resources/jaggeryapps/devicemgt/app/units/mdm.unit.policy.view/public/js/policy-view.js @@ -0,0 +1,2256 @@ +/* + * Copyright (c) 2016, 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. + */ + +var validateStep = {}; +var skipStep = {}; +var stepForwardFrom = {}; +var stepBackFrom = {}; +var policy = {}; +var configuredOperations = []; + +// Constants to define platform types available +var platformTypeConstants = { + "ANDROID": "android", + "IOS": "ios", + "WINDOWS": "windows" +}; + +// Constants to define Android Operation Constants +var androidOperationConstants = { + "PASSCODE_POLICY_OPERATION": "passcode-policy", + "PASSCODE_POLICY_OPERATION_CODE": "PASSCODE_POLICY", + "CAMERA_OPERATION": "camera", + "CAMERA_OPERATION_CODE": "CAMERA", + "ENCRYPT_STORAGE_OPERATION": "encrypt-storage", + "ENCRYPT_STORAGE_OPERATION_CODE": "ENCRYPT_STORAGE", + "WIFI_OPERATION": "wifi", + "WIFI_OPERATION_CODE": "WIFI" +}; + +// Constants to define Android Operation Constants +var windowsOperationConstants = { + "PASSCODE_POLICY_OPERATION": "passcode-policy", + "PASSCODE_POLICY_OPERATION_CODE": "PASSCODE_POLICY", + "CAMERA_OPERATION": "camera", + "CAMERA_OPERATION_CODE": "CAMERA", + "ENCRYPT_STORAGE_OPERATION": "encrypt-storage", + "ENCRYPT_STORAGE_OPERATION_CODE": "ENCRYPT_STORAGE" +}; + +// Constants to define iOS Operation Constants +var iosOperationConstants = { + "PASSCODE_POLICY_OPERATION": "passcode-policy", + "PASSCODE_POLICY_OPERATION_CODE": "PASSCODE_POLICY", + "RESTRICTIONS_OPERATION": "restrictions", + "RESTRICTIONS_OPERATION_CODE": "RESTRICTION", + "WIFI_OPERATION": "wifi", + "WIFI_OPERATION_CODE": "WIFI", + "EMAIL_OPERATION": "email", + "EMAIL_OPERATION_CODE": "EMAIL", + "AIRPLAY_OPERATION": "airplay", + "AIRPLAY_OPERATION_CODE": "AIR_PLAY", + "LDAP_OPERATION": "ldap", + "LDAP_OPERATION_CODE": "LDAP", + "CALENDAR_OPERATION": "calendar", + "CALENDAR_OPERATION_CODE": "CALDAV", + "CALENDAR_SUBSCRIPTION_OPERATION": "calendar-subscription", + "CALENDAR_SUBSCRIPTION_OPERATION_CODE": "CALENDAR_SUBSCRIPTION", + "APN_OPERATION": "apn", + "APN_OPERATION_CODE": "APN", + "CELLULAR_OPERATION": "cellular", + "CELLULAR_OPERATION_CODE": "CELLULAR" +}; + +/** + * Method to update the visibility (i.e. disabled or enabled view) + * of grouped input according to the values + * that they currently possess. + * @param domElement HTML grouped-input element with class name "grouped-input" + */ +var updateGroupedInputVisibility = function (domElement) { + if ($(".parent-input:first", domElement).is(":checked")) { + if ($(".grouped-child-input:first", domElement).hasClass("disabled")) { + $(".grouped-child-input:first", domElement).removeClass("disabled"); + } + $(".child-input", domElement).each(function () { + $(this).prop('disabled', false); + }); + } else { + if (!$(".grouped-child-input:first", domElement).hasClass("disabled")) { + $(".grouped-child-input:first", domElement).addClass("disabled"); + } + $(".child-input", domElement).each(function () { + $(this).prop('disabled', true); + }); + } +}; + +skipStep["policy-platform"] = function (policyPayloadObj) { + policy["name"] = policyPayloadObj["policyName"]; + policy["platform"] = policyPayloadObj["profile"]["deviceType"]["name"]; + policy["platformId"] = policyPayloadObj["profile"]["deviceType"]["id"]; + var userRoleInput = $("#user-roles-input"); + var ownershipInput = $("#ownership-input"); + var userInput = $("#users-select-field"); + var actionInput = $("#action-input"); + var policyNameInput = $("#policy-name-input"); + var policyDescriptionInput = $("#policy-description-input"); + userRoleInput.val(policyPayloadObj.roles); + userInput.val(policyPayloadObj.users); + ownershipInput.val(policyPayloadObj.ownershipType); + actionInput.val(policyPayloadObj.compliance); + policyNameInput.val(policyPayloadObj["policyName"]); + policyDescriptionInput.val(policyPayloadObj["description"]); + // updating next-page wizard title with selected platform + $("#policy-heading").text(policy["platform"].toUpperCase() + " POLICY - " + policy["name"].toUpperCase()); + $("#policy-platform").text(policy["platform"].toUpperCase()); + $("#policy-assignment").text(policyPayloadObj.ownershipType); + $("#policy-action").text(policyPayloadObj.compliance.toUpperCase()); + $("#policy-description").text(policyPayloadObj["description"]); + var policyStatus = "Active"; + if (policyPayloadObj["active"] == true && policyPayloadObj["updated"] == true) { + policyStatus = ' Active/Updated'; + } else if (policyPayloadObj["active"] == true && policyPayloadObj["updated"] == false) { + policyStatus = ' Active'; + } else if (policyPayloadObj["active"] == false && policyPayloadObj["updated"] == true) { + policyStatus = ' Inactive/Updated'; + } else if (policyPayloadObj["active"] == false && policyPayloadObj["updated"] == false) { + policyStatus = ' Inactive'; + } + + $("#policy-status").html(policyStatus); + + if (policyPayloadObj.users.length > 0) { + $("#policy-users").text(policyPayloadObj.users.toString().split(",").join(", ")); + } else { + $("#users-row").addClass("hidden"); + } + + if (policyPayloadObj.roles.length > 0) { + $("#policy-roles").text(policyPayloadObj.roles.toString().split(",").join(", ")); + } else { + $("#roles-row").addClass("hidden"); + } + + var deviceType = policy["platform"]; + var hiddenOperationsByDeviceType = $("#hidden-operations-" + deviceType); + var hiddenOperationsByDeviceTypeCacheKey = deviceType + "HiddenOperations"; + var hiddenOperationsByDeviceTypeSrc = hiddenOperationsByDeviceType.attr("src"); + + setTimeout( + function () { + $.template(hiddenOperationsByDeviceTypeCacheKey, hiddenOperationsByDeviceTypeSrc, function (template) { + var content = template(); + // pushing profile feature input elements + $(".wr-advance-operations").html(content); + // populating values and getting the list of configured features + var configuredOperations = operationModule. + populateProfile(policy["platform"], policyPayloadObj["profile"]["profileFeaturesList"]); + // updating grouped input visibility according to the populated values + $(".wr-advance-operations li.grouped-input").each(function () { + updateGroupedInputVisibility(this); + }); + // enabling previously configured options of last update + for (var i = 0; i < configuredOperations.length; ++i) { + var configuredOperation = configuredOperations[i]; + $(".operation-data").filterByData("operation-code", configuredOperation). + find(".panel-title .wr-input-control.switch input[type=checkbox]").each(function () { + $(this).click(); + }); + } + }); + }, + 250 // time delayed for the execution of above function, 250 milliseconds + ); +}; + +/** + * Checks if provided number is valid against a range. + * + * @param numberInput Number Input + * @param min Minimum Limit + * @param max Maximum Limit + * @returns {boolean} Returns true if input is within the specified range + */ +var inputIsValidAgainstRange = function (numberInput, min, max) { + return (numberInput == min || (numberInput > min && numberInput < max) || numberInput == max); +}; + +/** + * Checks if provided input is valid against RegEx input. + * + * @param regExp Regular expression + * @param input Input string to check + * @returns {boolean} Returns true if input matches RegEx + */ +var inputIsValidAgainstRegExp = function (regExp, input) { + return regExp.test(input); +}; + +validateStep["policy-profile"] = function () { + var validationStatusArray = []; + var validationStatus; + var operation; + + // starting validation process and updating validationStatus + if (policy["platform"] == platformTypeConstants["ANDROID"]) { + if (configuredOperations.length == 0) { + // updating validationStatus + validationStatus = { + "error": true, + "mainErrorMsg": "You cannot continue. Zero configured features." + }; + // updating validationStatusArray with validationStatus + validationStatusArray.push(validationStatus); + } else { + // validating each and every configured Operation + // Validating PASSCODE_POLICY + if ($.inArray(androidOperationConstants["PASSCODE_POLICY_OPERATION_CODE"], configuredOperations) != -1) { + // if PASSCODE_POLICY is configured + operation = androidOperationConstants["PASSCODE_POLICY_OPERATION"]; + // initializing continueToCheckNextInputs to true + var continueToCheckNextInputs = true; + + // validating first input: passcodePolicyMaxPasscodeAgeInDays + var passcodePolicyMaxPasscodeAgeInDays = $("input#passcode-policy-max-passcode-age-in-days").val(); + if (passcodePolicyMaxPasscodeAgeInDays) { + if (!$.isNumeric(passcodePolicyMaxPasscodeAgeInDays)) { + validationStatus = { + "error": true, + "subErrorMsg": "Provided passcode age is not a number.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } else if (!inputIsValidAgainstRange(passcodePolicyMaxPasscodeAgeInDays, 1, 730)) { + validationStatus = { + "error": true, + "subErrorMsg": "Provided passcode age is not with in the range of 1-to-730.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } + } + + // validating second and last input: passcodePolicyPasscodeHistory + if (continueToCheckNextInputs) { + var passcodePolicyPasscodeHistory = $("input#passcode-policy-passcode-history").val(); + if (passcodePolicyPasscodeHistory) { + if (!$.isNumeric(passcodePolicyPasscodeHistory)) { + validationStatus = { + "error": true, + "subErrorMsg": "Provided passcode history is not a number.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } else if (!inputIsValidAgainstRange(passcodePolicyPasscodeHistory, 1, 50)) { + validationStatus = { + "error": true, + "subErrorMsg": "Provided passcode history is not with in the range of 1-to-50.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } + } + } + + // at-last, if the value of continueToCheckNextInputs is still true + // this means that no error is found + if (continueToCheckNextInputs) { + validationStatus = { + "error": false, + "okFeature": operation + }; + } + + // updating validationStatusArray with validationStatus + validationStatusArray.push(validationStatus); + } + // Validating CAMERA + if ($.inArray(androidOperationConstants["CAMERA_OPERATION_CODE"], configuredOperations) != -1) { + // if CAMERA is configured + operation = androidOperationConstants["CAMERA_OPERATION"]; + // updating validationStatus + validationStatus = { + "error": false, + "okFeature": operation + }; + // updating validationStatusArray with validationStatus + validationStatusArray.push(validationStatus); + } + // Validating ENCRYPT_STORAGE + if ($.inArray(androidOperationConstants["ENCRYPT_STORAGE_OPERATION_CODE"], configuredOperations) != -1) { + // if ENCRYPT_STORAGE is configured + operation = androidOperationConstants["ENCRYPT_STORAGE_OPERATION"]; + // updating validationStatus + validationStatus = { + "error": false, + "okFeature": operation + }; + // updating validationStatusArray with validationStatus + validationStatusArray.push(validationStatus); + } + // Validating WIFI + if ($.inArray(androidOperationConstants["WIFI_OPERATION_CODE"], configuredOperations) != -1) { + // if WIFI is configured + operation = androidOperationConstants["WIFI_OPERATION"]; + // initializing continueToCheckNextInputs to true + continueToCheckNextInputs = true; + + var wifiSSID = $("input#wifi-ssid").val(); + if (!wifiSSID) { + validationStatus = { + "error": true, + "subErrorMsg": "WIFI SSID is not given. You cannot proceed.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } + + // at-last, if the value of continueToCheckNextInputs is still true + // this means that no error is found + if (continueToCheckNextInputs) { + validationStatus = { + "error": false, + "okFeature": operation + }; + } + + // updating validationStatusArray with validationStatus + validationStatusArray.push(validationStatus); + } + } + } + if (policy["platform"] == platformTypeConstants["WINDOWS"]) { + if (configuredOperations.length == 0) { + // updating validationStatus + validationStatus = { + "error": true, + "mainErrorMsg": "You cannot continue. Zero configured features." + }; + // updating validationStatusArray with validationStatus + validationStatusArray.push(validationStatus); + } else { + // validating each and every configured Operation + // Validating PASSCODE_POLICY + if ($.inArray(windowsOperationConstants["PASSCODE_POLICY_OPERATION_CODE"], configuredOperations) != -1) { + // if PASSCODE_POLICY is configured + operation = windowsOperationConstants["PASSCODE_POLICY_OPERATION"]; + // initializing continueToCheckNextInputs to true + var continueToCheckNextInputs = true; + + // validating first input: passcodePolicyMaxPasscodeAgeInDays + var passcodePolicyMaxPasscodeAgeInDays = $("input#passcode-policy-max-passcode-age-in-days").val(); + if (passcodePolicyMaxPasscodeAgeInDays) { + if (!$.isNumeric(passcodePolicyMaxPasscodeAgeInDays)) { + validationStatus = { + "error": true, + "subErrorMsg": "Provided passcode age is not a number.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } else if (!inputIsValidAgainstRange(passcodePolicyMaxPasscodeAgeInDays, 1, 730)) { + validationStatus = { + "error": true, + "subErrorMsg": "Provided passcode age is not with in the range of 1-to-730.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } + } + + // validating second and last input: passcodePolicyPasscodeHistory + if (continueToCheckNextInputs) { + var passcodePolicyPasscodeHistory = $("input#passcode-policy-passcode-history").val(); + if (passcodePolicyPasscodeHistory) { + if (!$.isNumeric(passcodePolicyPasscodeHistory)) { + validationStatus = { + "error": true, + "subErrorMsg": "Provided passcode history is not a number.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } else if (!inputIsValidAgainstRange(passcodePolicyPasscodeHistory, 1, 50)) { + validationStatus = { + "error": true, + "subErrorMsg": "Provided passcode history is not with in the range of 1-to-50.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } + } + } + + // at-last, if the value of continueToCheckNextInputs is still true + // this means that no error is found + if (continueToCheckNextInputs) { + validationStatus = { + "error": false, + "okFeature": operation + }; + } + + // updating validationStatusArray with validationStatus + validationStatusArray.push(validationStatus); + } + // Validating CAMERA + if ($.inArray(windowsOperationConstants["CAMERA_OPERATION_CODE"], configuredOperations) != -1) { + // if CAMERA is configured + operation = windowsOperationConstants["CAMERA_OPERATION"]; + // updating validationStatus + validationStatus = { + "error": false, + "okFeature": operation + }; + // updating validationStatusArray with validationStatus + validationStatusArray.push(validationStatus); + } + // Validating ENCRYPT_STORAGE + if ($.inArray(windowsOperationConstants["ENCRYPT_STORAGE_OPERATION_CODE"], configuredOperations) != -1) { + // if ENCRYPT_STORAGE is configured + operation = windowsOperationConstants["ENCRYPT_STORAGE_OPERATION"]; + // updating validationStatus + validationStatus = { + "error": false, + "okFeature": operation + }; + // updating validationStatusArray with validationStatus + validationStatusArray.push(validationStatus); + } + + } + } else if (policy["platform"] == platformTypeConstants["IOS"]) { + if (configuredOperations.length == 0) { + // updating validationStatus + validationStatus = { + "error": true, + "mainErrorMsg": "You cannot continue. Zero configured features." + }; + // updating validationStatusArray with validationStatus + validationStatusArray.push(validationStatus); + } else { + // validating each and every configured Operation + // Validating PASSCODE_POLICY + if ($.inArray(iosOperationConstants["PASSCODE_POLICY_OPERATION_CODE"], configuredOperations) != -1) { + // if PASSCODE_POLICY is configured + operation = iosOperationConstants["PASSCODE_POLICY_OPERATION"]; + // initializing continueToCheckNextInputs to true + continueToCheckNextInputs = true; + + // validating first input: passcodePolicyMaxPasscodeAgeInDays + passcodePolicyMaxPasscodeAgeInDays = $("input#passcode-policy-max-passcode-age-in-days").val(); + if (passcodePolicyMaxPasscodeAgeInDays) { + if (!$.isNumeric(passcodePolicyMaxPasscodeAgeInDays)) { + validationStatus = { + "error": true, + "subErrorMsg": "Provided passcode age is not a number.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } else if (!inputIsValidAgainstRange(passcodePolicyMaxPasscodeAgeInDays, 1, 730)) { + validationStatus = { + "error": true, + "subErrorMsg": "Provided passcode age is not with in the range of 1-to-730.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } + } + + // validating second and last input: passcodePolicyPasscodeHistory + if (continueToCheckNextInputs) { + passcodePolicyPasscodeHistory = $("input#passcode-policy-passcode-history").val(); + if (passcodePolicyPasscodeHistory) { + if (!$.isNumeric(passcodePolicyPasscodeHistory)) { + validationStatus = { + "error": true, + "subErrorMsg": "Provided passcode history is not a number.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } else if (!inputIsValidAgainstRange(passcodePolicyPasscodeHistory, 1, 50)) { + validationStatus = { + "error": true, + "subErrorMsg": "Provided passcode history is not with in the range of 1-to-50.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } + } + } + + // at-last, if the value of continueToCheckNextInputs is still true + // this means that no error is found + if (continueToCheckNextInputs) { + validationStatus = { + "error": false, + "okFeature": operation + }; + } + + // updating validationStatusArray with validationStatus + validationStatusArray.push(validationStatus); + } + // Validating RESTRICTIONS + if ($.inArray(iosOperationConstants["RESTRICTIONS_OPERATION_CODE"], configuredOperations) != -1) { + // if RESTRICTION is configured + operation = iosOperationConstants["RESTRICTIONS_OPERATION"]; + // initializing continueToCheckNextInputs to true + continueToCheckNextInputs = true; + + // getting input values to be validated + var restrictionsAutonomousSingleAppModePermittedAppIDsGridChildInputs = + "div#restrictions-autonomous-single-app-mode-permitted-app-ids .child-input"; + if ($(restrictionsAutonomousSingleAppModePermittedAppIDsGridChildInputs).length > 0) { + var childInput; + var childInputArray = []; + var emptyChildInputCount = 0; + var duplicatesExist = false; + // looping through each child input + $(restrictionsAutonomousSingleAppModePermittedAppIDsGridChildInputs).each(function () { + childInput = $(this).val(); + childInputArray.push(childInput); + if (!childInput) { + // if child input field is empty + emptyChildInputCount++; + } + }); + // checking for duplicates + var initialChildInputArrayLength = childInputArray.length; + if (emptyChildInputCount == 0 && initialChildInputArrayLength > 1) { + var m, poppedChildInput; + for (m = 0; m < (initialChildInputArrayLength - 1); m++) { + poppedChildInput = childInputArray.pop(); + var n; + for (n = 0; n < childInputArray.length; n++) { + if (poppedChildInput == childInputArray[n]) { + duplicatesExist = true; + break; + } + } + if (duplicatesExist) { + break; + } + } + } + // updating validationStatus + if (emptyChildInputCount > 0) { + // if empty child inputs are present + validationStatus = { + "error": true, + "subErrorMsg": "One or more permitted App ID entries in " + + "Autonomous Single App Mode are empty.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } else if (duplicatesExist) { + validationStatus = { + "error": true, + "subErrorMsg": "Duplicate values exist with permitted App ID entries in " + + "Autonomous Single App Mode.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } + } + + // at-last, if the value of continueToCheckNextInputs is still true + // this means that no error is found + if (continueToCheckNextInputs) { + validationStatus = { + "error": false, + "okFeature": operation + }; + } + + // updating validationStatusArray with validationStatus + validationStatusArray.push(validationStatus); + } + // Validating WIFI + if ($.inArray(iosOperationConstants["WIFI_OPERATION_CODE"], configuredOperations) != -1) { + // if WIFI is configured + operation = iosOperationConstants["WIFI_OPERATION"]; + // initializing continueToCheckNextInputs to true + continueToCheckNextInputs = true; + + // getting input values to be validated + wifiSSID = $("input#wifi-ssid").val(); + var wifiDomainName = $("input#wifi-domain-name").val(); + if (!wifiSSID && !wifiDomainName) { + validationStatus = { + "error": true, + "subErrorMsg": "Both Wi-Fi SSID and Wi-Fi Domain Name are not given. You cannot proceed.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } + + if (continueToCheckNextInputs) { + // getting proxy-setup value + var wifiProxyType = $("select#wifi-proxy-type").find("option:selected").attr("value"); + if (wifiProxyType == "Manual") { + // adds up additional fields to be validated + var wifiProxyServer = $("input#wifi-proxy-server").val(); + if (!wifiProxyServer) { + validationStatus = { + "error": true, + "subErrorMsg": "Wi-Fi Proxy Server is required. You cannot proceed.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } + + if (continueToCheckNextInputs) { + var wifiProxyPort = $("input#wifi-proxy-port").val(); + if (!wifiProxyPort) { + validationStatus = { + "error": true, + "subErrorMsg": "Wi-Fi Proxy Port is required. You cannot proceed.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } else if (!$.isNumeric(wifiProxyPort)) { + validationStatus = { + "error": true, + "subErrorMsg": "Wi-Fi Proxy Port requires a number input.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } else if (!inputIsValidAgainstRange(wifiProxyPort, 0, 65535)) { + validationStatus = { + "error": true, + "subErrorMsg": "Wi-Fi Proxy Port is not within the range " + + "of valid port numbers.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } + } + } + } + + if (continueToCheckNextInputs) { + // getting encryption-type value + var wifiEncryptionType = $("select#wifi-encryption-type").find("option:selected").attr("value"); + if (wifiEncryptionType != "None") { + var wifiPayloadCertificateAnchorUUIDsGridChildInputs = + "div#wifi-payload-certificate-anchor-uuids .child-input"; + if ($(wifiPayloadCertificateAnchorUUIDsGridChildInputs).length > 0) { + emptyChildInputCount = 0; + childInputArray = []; + duplicatesExist = false; + // looping through each child input + $(wifiPayloadCertificateAnchorUUIDsGridChildInputs).each(function () { + childInput = $(this).val(); + childInputArray.push(childInput); + if (!childInput) { + // if child input field is empty + emptyChildInputCount++; + } + }); + // checking for duplicates + initialChildInputArrayLength = childInputArray.length; + if (emptyChildInputCount == 0 && initialChildInputArrayLength > 1) { + for (m = 0; m < (initialChildInputArrayLength - 1); m++) { + poppedChildInput = childInputArray.pop(); + for (n = 0; n < childInputArray.length; n++) { + if (poppedChildInput == childInputArray[n]) { + duplicatesExist = true; + break; + } + } + if (duplicatesExist) { + break; + } + } + } + // updating validationStatus + if (emptyChildInputCount > 0) { + // if empty child inputs are present + validationStatus = { + "error": true, + "subErrorMsg": "One or more Payload Certificate " + + "Anchor UUIDs are empty.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } else if (duplicatesExist) { + validationStatus = { + "error": true, + "subErrorMsg": "Duplicate values exist " + + "with Payload Certificate Anchor UUIDs.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } + } + + if (continueToCheckNextInputs) { + var wifiTLSTrustedServerNamesGridChildInputs = + "div#wifi-tls-trusted-server-names .child-input"; + if ($(wifiTLSTrustedServerNamesGridChildInputs).length > 0) { + emptyChildInputCount = 0; + childInputArray = []; + duplicatesExist = false; + // looping through each child input + $(wifiTLSTrustedServerNamesGridChildInputs).each(function () { + childInput = $(this).val(); + childInputArray.push(childInput); + if (!childInput) { + // if child input field is empty + emptyChildInputCount++; + } + }); + // checking for duplicates + initialChildInputArrayLength = childInputArray.length; + if (emptyChildInputCount == 0 && initialChildInputArrayLength > 1) { + for (m = 0; m < (initialChildInputArrayLength - 1); m++) { + poppedChildInput = childInputArray.pop(); + for (n = 0; n < childInputArray.length; n++) { + if (poppedChildInput == childInputArray[n]) { + duplicatesExist = true; + break; + } + } + if (duplicatesExist) { + break; + } + } + } + // updating validationStatus + if (emptyChildInputCount > 0) { + // if empty child inputs are present + validationStatus = { + "error": true, + "subErrorMsg": "One or more TLS Trusted Server Names are empty.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } else if (duplicatesExist) { + validationStatus = { + "error": true, + "subErrorMsg": "Duplicate values exist " + + "with TLS Trusted Server Names.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } + } + } + } + } + + if (continueToCheckNextInputs) { + var wifiRoamingConsortiumOIsGridChildInputs = "div#wifi-roaming-consortium-ois .child-input"; + if ($(wifiRoamingConsortiumOIsGridChildInputs).length > 0) { + emptyChildInputCount = 0; + var outOfAllowedLengthCount = 0; + var invalidAgainstRegExCount = 0; + childInputArray = []; + duplicatesExist = false; + // looping through each child input + $(wifiRoamingConsortiumOIsGridChildInputs).each(function () { + childInput = $(this).val(); + childInputArray.push(childInput); + if (!childInput) { + // if child input field is empty + emptyChildInputCount++; + } else if (!inputIsValidAgainstLength(childInput, 6, 6) && !inputIsValidAgainstLength(childInput, 10, 10)) { + outOfAllowedLengthCount++; + } else if (!inputIsValidAgainstRegExp(/^[a-fA-F0-9]+$/, childInput)) { + invalidAgainstRegExCount++; + } + }); + // checking for duplicates + initialChildInputArrayLength = childInputArray.length; + if (emptyChildInputCount == 0 && initialChildInputArrayLength > 1) { + for (m = 0; m < (initialChildInputArrayLength - 1); m++) { + poppedChildInput = childInputArray.pop(); + for (n = 0; n < childInputArray.length; n++) { + if (poppedChildInput == childInputArray[n]) { + duplicatesExist = true; + break; + } + } + if (duplicatesExist) { + break; + } + } + } + // updating validationStatus + if (emptyChildInputCount > 0) { + // if empty child inputs are present + validationStatus = { + "error": true, + "subErrorMsg": "One or more Roaming Consortium OIs are empty.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } else if (outOfAllowedLengthCount > 0) { + // if outOfMaxAllowedLength input is present + validationStatus = { + "error": true, + "subErrorMsg": "One or more Roaming Consortium OIs " + + "are out of allowed length.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } else if (invalidAgainstRegExCount > 0) { + // if invalid inputs in terms of hexadecimal format are present + validationStatus = { + "error": true, + "subErrorMsg": "One or more Roaming Consortium OIs " + + "contain non-hexadecimal characters.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } else if (duplicatesExist) { + validationStatus = { + "error": true, + "subErrorMsg": "Duplicate values exist with Roaming Consortium OIs.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } + } + } + + if (continueToCheckNextInputs) { + var wifiNAIRealmNamesGridChildInputs = "div#wifi-nai-realm-names .child-input"; + if ($(wifiNAIRealmNamesGridChildInputs).length > 0) { + emptyChildInputCount = 0; + childInputArray = []; + duplicatesExist = false; + // looping through each child input + $(wifiNAIRealmNamesGridChildInputs).each(function () { + childInput = $(this).val(); + childInputArray.push(childInput); + if (!childInput) { + // if child input field is empty + emptyChildInputCount++; + } + }); + // checking for duplicates + initialChildInputArrayLength = childInputArray.length; + if (emptyChildInputCount == 0 && initialChildInputArrayLength > 1) { + for (m = 0; m < (initialChildInputArrayLength - 1); m++) { + poppedChildInput = childInputArray.pop(); + for (n = 0; n < childInputArray.length; n++) { + if (poppedChildInput == childInputArray[n]) { + duplicatesExist = true; + break; + } + } + if (duplicatesExist) { + break; + } + } + } + // updating validationStatus + if (emptyChildInputCount > 0) { + // if empty child inputs are present + validationStatus = { + "error": true, + "subErrorMsg": "One or more NAI Realm Names are empty.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } else if (duplicatesExist) { + validationStatus = { + "error": true, + "subErrorMsg": "Duplicate values exist with NAI Realm Names.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } + } + } + + if (continueToCheckNextInputs) { + var wifiMCCAndMNCsGridChildInputs = "div#wifi-mcc-and-mncs .child-input"; + if ($(wifiMCCAndMNCsGridChildInputs).length > 0) { + var childInputCount = 0; + var stringPair; + emptyChildInputCount = 0; + outOfAllowedLengthCount = 0; + var notNumericInputCount = 0; + childInputArray = []; + duplicatesExist = false; + // looping through each child input + $(wifiMCCAndMNCsGridChildInputs).each(function () { + childInput = $(this).val(); + // pushing each string pair to childInputArray + childInputCount++; + if (childInputCount % 2 == 1) { + // initialize stringPair value + stringPair = ""; + // append first part of the string + stringPair += childInput; + } else { + // append second part of the string + stringPair += childInput; + childInputArray.push(stringPair); + } + // updating emptyChildInputCount & outOfAllowedLengthCount + if (!childInput) { + // if child input field is empty + emptyChildInputCount++; + } else if (!$.isNumeric(childInput)) { + notNumericInputCount++; + } else if (!inputIsValidAgainstLength(childInput, 3, 3)) { + outOfAllowedLengthCount++; + } + }); + // checking for duplicates + initialChildInputArrayLength = childInputArray.length; + if (emptyChildInputCount == 0 && initialChildInputArrayLength > 1) { + for (m = 0; m < (initialChildInputArrayLength - 1); m++) { + poppedChildInput = childInputArray.pop(); + for (n = 0; n < childInputArray.length; n++) { + if (poppedChildInput == childInputArray[n]) { + duplicatesExist = true; + break; + } + } + if (duplicatesExist) { + break; + } + } + } + // updating validationStatus + if (emptyChildInputCount > 0) { + // if empty child inputs are present + validationStatus = { + "error": true, + "subErrorMsg": "One or more MCC/MNC pairs are empty.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } else if (notNumericInputCount > 0) { + // if notNumeric input is present + validationStatus = { + "error": true, + "subErrorMsg": "One or more MCC/MNC pairs are not numeric.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } else if (outOfAllowedLengthCount > 0) { + // if outOfAllowedLength input is present + validationStatus = { + "error": true, + "subErrorMsg": "One or more MCC/MNC pairs " + + "do not fulfill the accepted length of 6 digits.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } else if (duplicatesExist) { + validationStatus = { + "error": true, + "subErrorMsg": "Duplicate values exist with MCC/MNC pairs.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } + } + } + + // at-last, if the value of continueToCheckNextInputs is still true + // this means that no error is found + if (continueToCheckNextInputs) { + validationStatus = { + "error": false, + "okFeature": operation + }; + } + + // updating validationStatusArray with validationStatus + validationStatusArray.push(validationStatus); + } + // Validating EMAIL + if ($.inArray(iosOperationConstants["EMAIL_OPERATION_CODE"], configuredOperations) != -1) { + // if EMAIL is configured + operation = iosOperationConstants["EMAIL_OPERATION"]; + // initializing continueToCheckNextInputs to true + continueToCheckNextInputs = true; + + var emailAddress = $("input#email-address").val(); + if (emailAddress && !inputIsValidAgainstRegExp(/^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$/, emailAddress)) { + validationStatus = { + "error": true, + "subErrorMsg": "Email Address is not valid.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } + + if (continueToCheckNextInputs) { + var emailIncomingMailServerHostname = $("input#email-incoming-mail-server-hostname").val(); + if (!emailIncomingMailServerHostname) { + validationStatus = { + "error": true, + "subErrorMsg": "Incoming Mail Server Hostname is empty. You cannot proceed.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } + } + + if (continueToCheckNextInputs) { + var emailIncomingMailServerPort = $("input#email-incoming-mail-server-port").val(); + if (!emailIncomingMailServerPort) { + validationStatus = { + "error": true, + "subErrorMsg": "Incoming Mail Server Port is empty. You cannot proceed.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } else if (!$.isNumeric(emailIncomingMailServerPort)) { + validationStatus = { + "error": true, + "subErrorMsg": "Incoming Mail Server Port requires a number input.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } else if (!inputIsValidAgainstRange(emailIncomingMailServerPort, 0, 65535)) { + validationStatus = { + "error": true, + "subErrorMsg": "Incoming Mail Server Port is not within the range " + + "of valid port numbers.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } + } + + if (continueToCheckNextInputs) { + var emailOutgoingMailServerHostname = $("input#email-outgoing-mail-server-hostname").val(); + if (!emailOutgoingMailServerHostname) { + validationStatus = { + "error": true, + "subErrorMsg": "Outgoing Mail Server Hostname is empty. You cannot proceed.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } + } + + if (continueToCheckNextInputs) { + var emailOutgoingMailServerPort = $("input#email-outgoing-mail-server-port").val(); + if (!emailOutgoingMailServerPort) { + validationStatus = { + "error": true, + "subErrorMsg": "Outgoing Mail Server Port is empty. You cannot proceed.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } else if (!$.isNumeric(emailOutgoingMailServerPort)) { + validationStatus = { + "error": true, + "subErrorMsg": "Outgoing Mail Server Port requires a number input.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } else if (!inputIsValidAgainstRange(emailOutgoingMailServerPort, 0, 65535)) { + validationStatus = { + "error": true, + "subErrorMsg": "Outgoing Mail Server Port is not within the range " + + "of valid port numbers.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } + } + + // at-last, if the value of continueToCheckNextInputs is still true + // this means that no error is found + if (continueToCheckNextInputs) { + validationStatus = { + "error": false, + "okFeature": operation + }; + } + + // updating validationStatusArray with validationStatus + validationStatusArray.push(validationStatus); + } + // Validating AIRPLAY + if ($.inArray(iosOperationConstants["AIRPLAY_OPERATION_CODE"], configuredOperations) != -1) { + // if AIRPLAY is configured + operation = iosOperationConstants["AIRPLAY_OPERATION"]; + // initializing continueToCheckNextInputs to true + continueToCheckNextInputs = true; + + var airplayCredentialsGridChildInputs = "div#airplay-credentials .child-input"; + var airplayDestinationsGridChildInputs = "div#airplay-destinations .child-input"; + if ($(airplayCredentialsGridChildInputs).length == 0 && + $(airplayDestinationsGridChildInputs).length == 0) { + validationStatus = { + "error": true, + "subErrorMsg": "AirPlay settings have zero configurations attached.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } + + if (continueToCheckNextInputs) { + if ($(airplayCredentialsGridChildInputs).length > 0) { + childInputCount = 0; + childInputArray = []; + emptyChildInputCount = 0; + duplicatesExist = false; + // looping through each child input + $(airplayCredentialsGridChildInputs).each(function () { + childInputCount++; + if (childInputCount % 2 == 1) { + // if child input is of first column + childInput = $(this).val(); + childInputArray.push(childInput); + // updating emptyChildInputCount + if (!childInput) { + // if child input field is empty + emptyChildInputCount++; + } + } + }); + // checking for duplicates + initialChildInputArrayLength = childInputArray.length; + if (emptyChildInputCount == 0 && initialChildInputArrayLength > 1) { + for (m = 0; m < (initialChildInputArrayLength - 1); m++) { + poppedChildInput = childInputArray.pop(); + for (n = 0; n < childInputArray.length; n++) { + if (poppedChildInput == childInputArray[n]) { + duplicatesExist = true; + break; + } + } + if (duplicatesExist) { + break; + } + } + } + // updating validationStatus + if (emptyChildInputCount > 0) { + // if empty child inputs are present + validationStatus = { + "error": true, + "subErrorMsg": "One or more Device Names of " + + "AirPlay Credentials are empty.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } else if (duplicatesExist) { + // if duplicate input is present + validationStatus = { + "error": true, + "subErrorMsg": "Duplicate values exist with " + + "Device Names of AirPlay Credentials.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } + } + } + + if (continueToCheckNextInputs) { + if ($(airplayDestinationsGridChildInputs).length > 0) { + childInputArray = []; + emptyChildInputCount = 0; + invalidAgainstRegExCount = 0; + duplicatesExist = false; + // looping through each child input + $(airplayDestinationsGridChildInputs).each(function () { + childInput = $(this).val(); + childInputArray.push(childInput); + // updating emptyChildInputCount + if (!childInput) { + // if child input field is empty + emptyChildInputCount++; + } else if (!inputIsValidAgainstRegExp( + /([a-z|A-Z|0-9][a-z|A-Z|0-9][:]){5}([a-z|A-Z|0-9][a-z|A-Z|0-9])$/, childInput)) { + // if child input field is invalid against RegEx + invalidAgainstRegExCount++ + } + }); + // checking for duplicates + initialChildInputArrayLength = childInputArray.length; + if (emptyChildInputCount == 0 && initialChildInputArrayLength > 1) { + for (m = 0; m < (initialChildInputArrayLength - 1); m++) { + poppedChildInput = childInputArray.pop(); + for (n = 0; n < childInputArray.length; n++) { + if (poppedChildInput == childInputArray[n]) { + duplicatesExist = true; + break; + } + } + if (duplicatesExist) { + break; + } + } + } + // updating validationStatus + if (emptyChildInputCount > 0) { + // if empty child inputs are present + validationStatus = { + "error": true, + "subErrorMsg": "One or more AirPlay Destination fields are empty.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } else if (invalidAgainstRegExCount > 0) { + // if invalidAgainstRegEx inputs are present + validationStatus = { + "error": true, + "subErrorMsg": "One or more AirPlay Destination fields " + + "do not fulfill expected format.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } else if (duplicatesExist) { + // if duplicate input is present + validationStatus = { + "error": true, + "subErrorMsg": "Duplicate values exist with AirPlay Destinations.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } + } + } + + // at-last, if the value of continueToCheckNextInputs is still true + // this means that no error is found + if (continueToCheckNextInputs) { + validationStatus = { + "error": false, + "okFeature": operation + }; + } + + // updating validationStatusArray with validationStatus + validationStatusArray.push(validationStatus); + } + // Validating LDAP + if ($.inArray(iosOperationConstants["LDAP_OPERATION_CODE"], configuredOperations) != -1) { + // if LDAP is configured + operation = iosOperationConstants["LDAP_OPERATION"]; + // initializing continueToCheckNextInputs to true + continueToCheckNextInputs = true; + + var ldapAccountHostname = $("input#ldap-account-hostname").val(); + if (!ldapAccountHostname) { + validationStatus = { + "error": true, + "subErrorMsg": "LDAP Account Hostname URL is empty. You cannot proceed.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } + + if (continueToCheckNextInputs) { + var ldapSearchSettingsGridChildInputs = "div#ldap-search-settings .child-input"; + if ($(ldapSearchSettingsGridChildInputs).length > 0) { + childInputCount = 0; + childInputArray = []; + emptyChildInputCount = 0; + duplicatesExist = false; + // looping through each child input + $(ldapSearchSettingsGridChildInputs).each(function () { + childInputCount++; + if (childInputCount % 3 == 2) { + // if child input is of second column + childInput = $(this).find("option:selected").attr("value"); + stringPair = ""; + stringPair += (childInput + " "); + } else if (childInputCount % 3 == 0) { + // if child input is of third column + childInput = $(this).val(); + stringPair += childInput; + childInputArray.push(stringPair); + // updating emptyChildInputCount + if (!childInput) { + // if child input field is empty + emptyChildInputCount++; + } + } + }); + // checking for duplicates + initialChildInputArrayLength = childInputArray.length; + if (emptyChildInputCount == 0 && initialChildInputArrayLength > 1) { + for (m = 0; m < (initialChildInputArrayLength - 1); m++) { + poppedChildInput = childInputArray.pop(); + for (n = 0; n < childInputArray.length; n++) { + if (poppedChildInput == childInputArray[n]) { + duplicatesExist = true; + break; + } + } + if (duplicatesExist) { + break; + } + } + } + // updating validationStatus + if (emptyChildInputCount > 0) { + // if empty child inputs are present + validationStatus = { + "error": true, + "subErrorMsg": "One or more Search Setting Scope fields are empty.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } else if (duplicatesExist) { + // if duplicate input is present + validationStatus = { + "error": true, + "subErrorMsg": "Duplicate values exist with " + + "Search Setting Search Base and Scope pairs.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } + } + } + + // at-last, if the value of continueToCheckNextInputs is still true + // this means that no error is found + if (continueToCheckNextInputs) { + validationStatus = { + "error": false, + "okFeature": operation + }; + } + + // updating validationStatusArray with validationStatus + validationStatusArray.push(validationStatus); + } + // Validating CALENDAR + if ($.inArray(iosOperationConstants["CALENDAR_OPERATION_CODE"], configuredOperations) != -1) { + // if CALENDAR is configured + operation = iosOperationConstants["CALENDAR_OPERATION"]; + // initializing continueToCheckNextInputs to true + continueToCheckNextInputs = true; + + var calendarAccountHostname = $("input#calendar-account-hostname").val(); + if (!calendarAccountHostname) { + validationStatus = { + "error": true, + "subErrorMsg": "Account Hostname URL is empty. You cannot proceed.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } + + if (continueToCheckNextInputs) { + var calendarAccountPort = $("input#calendar-account-port").val(); + if (!calendarAccountPort) { + validationStatus = { + "error": true, + "subErrorMsg": "Account Port is empty. You cannot proceed.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } else if (!$.isNumeric(calendarAccountPort)) { + validationStatus = { + "error": true, + "subErrorMsg": "Account Port requires a number input.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } else if (!inputIsValidAgainstRange(calendarAccountPort, 0, 65535)) { + validationStatus = { + "error": true, + "subErrorMsg": "Account Port is not within the range " + + "of valid port numbers.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } + } + + // at-last, if the value of continueToCheckNextInputs is still true + // this means that no error is found + if (continueToCheckNextInputs) { + validationStatus = { + "error": false, + "okFeature": operation + }; + } + + // updating validationStatusArray with validationStatus + validationStatusArray.push(validationStatus); + } + // Validating CALENDAR_SUBSCRIPTION + if ($.inArray(iosOperationConstants["CALENDAR_SUBSCRIPTION_OPERATION_CODE"], configuredOperations) != -1) { + // if CALENDAR_SUBSCRIPTION is configured + operation = iosOperationConstants["CALENDAR_SUBSCRIPTION_OPERATION"]; + // initializing continueToCheckNextInputs to true + continueToCheckNextInputs = true; + + var calendarSubscriptionHostname = $("input#calendar-subscription-hostname").val(); + if (!calendarSubscriptionHostname) { + validationStatus = { + "error": true, + "subErrorMsg": "Account Hostname URL is empty. You cannot proceed.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } + + // at-last, if the value of continueToCheckNextInputs is still true + // this means that no error is found + if (continueToCheckNextInputs) { + validationStatus = { + "error": false, + "okFeature": operation + }; + } + + // updating validationStatusArray with validationStatus + validationStatusArray.push(validationStatus); + } + // Validating APN + if ($.inArray(iosOperationConstants["APN_OPERATION_CODE"], configuredOperations) != -1) { + // if APN is configured + operation = iosOperationConstants["APN_OPERATION"]; + // initializing continueToCheckNextInputs to true + continueToCheckNextInputs = true; + + var apnConfigurationsGridChildInputs = "div#apn-configurations .child-input"; + if ($(apnConfigurationsGridChildInputs).length == 0) { + validationStatus = { + "error": true, + "subErrorMsg": "APN Settings have zero configurations attached.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } else if ($(apnConfigurationsGridChildInputs).length > 0) { + childInputCount = 0; + childInputArray = []; + // checking empty APN field count + emptyChildInputCount = 0; + duplicatesExist = false; + // looping through each child input + $(apnConfigurationsGridChildInputs).each(function () { + childInputCount++; + if (childInputCount % 5 == 1) { + // if child input is of first column + childInput = $(this).val(); + childInputArray.push(childInput); + // updating emptyChildInputCount + if (!childInput) { + // if child input field is empty + emptyChildInputCount++; + } + } + }); + // checking for duplicates + initialChildInputArrayLength = childInputArray.length; + if (emptyChildInputCount == 0 && initialChildInputArrayLength > 1) { + for (m = 0; m < (initialChildInputArrayLength - 1); m++) { + poppedChildInput = childInputArray.pop(); + for (n = 0; n < childInputArray.length; n++) { + if (poppedChildInput == childInputArray[n]) { + duplicatesExist = true; + break; + } + } + if (duplicatesExist) { + break; + } + } + } + // updating validationStatus + if (emptyChildInputCount > 0) { + // if empty child inputs are present + validationStatus = { + "error": true, + "subErrorMsg": "One or more APN fields of Configurations are empty.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } else if (duplicatesExist) { + // if duplicate input is present + validationStatus = { + "error": true, + "subErrorMsg": "Duplicate values exist with " + + "APN fields of Configurations.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } + } + + // at-last, if the value of continueToCheckNextInputs is still true + // this means that no error is found + if (continueToCheckNextInputs) { + validationStatus = { + "error": false, + "okFeature": operation + }; + } + + // updating validationStatusArray with validationStatus + validationStatusArray.push(validationStatus); + } + // Validating CELLULAR + if ($.inArray(iosOperationConstants["CELLULAR_OPERATION_CODE"], configuredOperations) != -1) { + // if CELLULAR is configured + operation = iosOperationConstants["CELLULAR_OPERATION"]; + // initializing continueToCheckNextInputs to true + continueToCheckNextInputs = true; + + var cellularAttachAPNName = $("input#cellular-attach-apn-name").val(); + if (!cellularAttachAPNName) { + validationStatus = { + "error": true, + "subErrorMsg": "Cellular Configuration Name is empty. You cannot proceed.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } + + if (continueToCheckNextInputs) { + var cellularAPNConfigurationsGridChildInputs = "div#cellular-apn-configurations .child-input"; + if ($(cellularAPNConfigurationsGridChildInputs).length > 0) { + childInputCount = 0; + childInputArray = []; + // checking empty APN field count + emptyChildInputCount = 0; + duplicatesExist = false; + // looping through each child input + $(cellularAPNConfigurationsGridChildInputs).each(function () { + childInputCount++; + if (childInputCount % 6 == 1) { + // if child input is of first column + childInput = $(this).val(); + childInputArray.push(childInput); + // updating emptyChildInputCount + if (!childInput) { + // if child input field is empty + emptyChildInputCount++; + } + } + }); + // checking for duplicates + initialChildInputArrayLength = childInputArray.length; + if (emptyChildInputCount == 0 && initialChildInputArrayLength > 1) { + for (m = 0; m < (initialChildInputArrayLength - 1); m++) { + poppedChildInput = childInputArray.pop(); + for (n = 0; n < childInputArray.length; n++) { + if (poppedChildInput == childInputArray[n]) { + duplicatesExist = true; + break; + } + } + if (duplicatesExist) { + break; + } + } + } + // updating validationStatus + if (emptyChildInputCount > 0) { + // if empty child inputs are present + validationStatus = { + "error": true, + "subErrorMsg": "One or more APN fields of APN Configurations are empty.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } else if (duplicatesExist) { + // if duplicate input is present + validationStatus = { + "error": true, + "subErrorMsg": "Duplicate values exist with " + + "APN fields of APN Configurations.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } + } + } + + // at-last, if the value of continueToCheckNextInputs is still true + // this means that no error is found + if (continueToCheckNextInputs) { + validationStatus = { + "error": false, + "okFeature": operation + }; + } + + // updating validationStatusArray with validationStatus + validationStatusArray.push(validationStatus); + } + } + } + // ending validation process + + // start taking specific notifying actions upon validation + var wizardIsToBeContinued; + var errorCount = 0; + var mainErrorMsgWrapper, mainErrorMsg, + subErrorMsgWrapper, subErrorMsg, subErrorIcon, subOkIcon, featureConfiguredIcon; + var i; + for (i = 0; i < validationStatusArray.length; i++) { + validationStatus = validationStatusArray[i]; + if (validationStatus["error"]) { + errorCount++; + if (validationStatus["mainErrorMsg"]) { + mainErrorMsgWrapper = "#policy-profile-main-error-msg"; + mainErrorMsg = mainErrorMsgWrapper + " span"; + $(mainErrorMsg).text(validationStatus["mainErrorMsg"]); + $(mainErrorMsgWrapper).removeClass("hidden"); + } else if (validationStatus["subErrorMsg"]) { + subErrorMsgWrapper = "#" + validationStatus["erroneousFeature"] + "-feature-error-msg"; + subErrorMsg = subErrorMsgWrapper + " span"; + subErrorIcon = "#" + validationStatus["erroneousFeature"] + "-error"; + subOkIcon = "#" + validationStatus["erroneousFeature"] + "-ok"; + featureConfiguredIcon = "#" + validationStatus["erroneousFeature"] + "-configured"; + // hiding featureConfiguredState as the first step + if (!$(featureConfiguredIcon).hasClass("hidden")) { + $(featureConfiguredIcon).addClass("hidden"); + } + // updating error state and corresponding messages + $(subErrorMsg).text(validationStatus["subErrorMsg"]); + if ($(subErrorMsgWrapper).hasClass("hidden")) { + $(subErrorMsgWrapper).removeClass("hidden"); + } + if (!$(subOkIcon).hasClass("hidden")) { + $(subOkIcon).addClass("hidden"); + } + if ($(subErrorIcon).hasClass("hidden")) { + $(subErrorIcon).removeClass("hidden"); + } + } + } else { + if (validationStatus["okFeature"]) { + subErrorMsgWrapper = "#" + validationStatus["okFeature"] + "-feature-error-msg"; + subErrorIcon = "#" + validationStatus["okFeature"] + "-error"; + subOkIcon = "#" + validationStatus["okFeature"] + "-ok"; + featureConfiguredIcon = "#" + validationStatus["okFeature"] + "-configured"; + // hiding featureConfiguredState as the first step + if (!$(featureConfiguredIcon).hasClass("hidden")) { + $(featureConfiguredIcon).addClass("hidden"); + } + // updating success state and corresponding messages + if (!$(subErrorMsgWrapper).hasClass("hidden")) { + $(subErrorMsgWrapper).addClass("hidden"); + } + if (!$(subErrorIcon).hasClass("hidden")) { + $(subErrorIcon).addClass("hidden"); + } + if ($(subOkIcon).hasClass("hidden")) { + $(subOkIcon).removeClass("hidden"); + } + } + } + } + + wizardIsToBeContinued = (errorCount == 0); + return wizardIsToBeContinued; +}; + +stepForwardFrom["policy-profile"] = function () { + policy["profile"] = operationModule.generateProfile(policy["platform"], configuredOperations); + // updating next-page wizard title with selected platform + $("#policy-criteria-page-wizard-title").text(policy["platform"] + " POLICY - " + policy["name"]); +}; + +stepForwardFrom["policy-criteria"] = function () { + $("input[type='radio'].select-users-radio").each(function () { + if ($(this).is(':radio')) { + if ($(this).is(":checked")) { + if ($(this).attr("id") == "users-radio-btn") { + policy["selectedUsers"] = $("#users-input").val(); + } else if ($(this).attr("id") == "user-roles-radio-btn") { + policy["selectedUserRoles"] = $("#user-roles-input").val(); + } + } + } + }); + policy["selectedNonCompliantAction"] = $("#action-input").find(":selected").data("action"); + policy["selectedOwnership"] = $("#ownership-input").val(); + // updating next-page wizard title with selected platform + $("#policy-naming-page-wizard-title").text(policy["platform"] + " POLICY - " + policy["name"]); +}; + +/** + * Checks if provided input is valid against provided length range. + * + * @param input Alphanumeric or non-alphanumeric input + * @param minLength Minimum Required Length + * @param maxLength Maximum Required Length + * @returns {boolean} Returns true if input matches the provided minimum length and maximum length + */ +var inputIsValidAgainstLength = function (input, minLength, maxLength) { + var length = input.length; + return (length == minLength || (length > minLength && length < maxLength) || length == maxLength); +}; + +validateStep["policy-naming"] = function () { + var validationStatus = {}; + + // taking values of inputs to be validated + var policyName = $("input#policy-name-input").val(); + // starting validation process and updating validationStatus + if (!policyName) { + validationStatus["error"] = true; + validationStatus["mainErrorMsg"] = "Policy name is empty. You cannot proceed."; + } else if (!inputIsValidAgainstLength(policyName, 1, 30)) { + validationStatus["error"] = true; + validationStatus["mainErrorMsg"] = + "Policy name exceeds maximum allowed length."; + } else { + validationStatus["error"] = false; + } + // ending validation process + + // start taking specific actions upon validation + var wizardIsToBeContinued; + if (validationStatus["error"]) { + wizardIsToBeContinued = false; + var mainErrorMsgWrapper = "#policy-naming-main-error-msg"; + var mainErrorMsg = mainErrorMsgWrapper + " span"; + $(mainErrorMsg).text(validationStatus["mainErrorMsg"]); + $(mainErrorMsgWrapper).removeClass("hidden"); + } else { + wizardIsToBeContinued = true; + } + + return wizardIsToBeContinued; +}; + +validateStep["policy-naming-publish"] = function () { + var validationStatus = {}; + + // taking values of inputs to be validated + var policyName = $("input#policy-name-input").val(); + // starting validation process and updating validationStatus + if (!policyName) { + validationStatus["error"] = true; + validationStatus["mainErrorMsg"] = "Policy name is empty. You cannot proceed."; + } else if (!inputIsValidAgainstLength(policyName, 1, 30)) { + validationStatus["error"] = true; + validationStatus["mainErrorMsg"] = + "Policy name exceeds maximum allowed length."; + } else { + validationStatus["error"] = false; + } + // ending validation process + + // start taking specific actions upon validation + var wizardIsToBeContinued; + if (validationStatus["error"]) { + wizardIsToBeContinued = false; + var mainErrorMsgWrapper = "#policy-naming-main-error-msg"; + var mainErrorMsg = mainErrorMsgWrapper + " span"; + $(mainErrorMsg).text(validationStatus["mainErrorMsg"]); + $(mainErrorMsgWrapper).removeClass("hidden"); + } else { + wizardIsToBeContinued = true; + } + + return wizardIsToBeContinued; +}; + +stepForwardFrom["policy-naming-publish"] = function () { + policy["policyName"] = $("#policy-name-input").val(); + policy["description"] = $("#policy-description-input").val(); + //All data is collected. Policy can now be updated. + updatePolicy(policy, "publish"); +}; +stepForwardFrom["policy-naming"] = function () { + policy["policyName"] = $("#policy-name-input").val(); + policy["description"] = $("#policy-description-input").val(); + //All data is collected. Policy can now be updated. + updatePolicy(policy, "save"); +}; + +var updatePolicy = function (policy, state) { + var profilePayloads = []; + // traverses key by key in policy["profile"] + var key; + for (key in policy["profile"]) { + if (policy["profile"].hasOwnProperty(key)) { + profilePayloads.push({ + "featureCode": key, + "deviceTypeId": policy["platformId"], + "content": policy["profile"][key] + }); + } + } + var payload = { + "policyName": policy["policyName"], + "description": policy["description"], + "compliance": policy["selectedNonCompliantAction"], + "ownershipType": policy["selectedOwnership"], + "profile": { + "profileName": policy["policyName"], + "deviceType": { + "id": policy["platformId"] + }, + "profileFeaturesList": profilePayloads + } + }; + + if (policy["selectedUsers"]) { + payload["users"] = policy["selectedUsers"]; + } else if (policy["selectedUserRoles"]) { + payload["roles"] = policy["selectedUserRoles"]; + } else { + payload["users"] = []; + payload["roles"] = []; + } + var serviceURL = "/devicemgt_admin/policies/" + getParameterByName("id"); + invokerUtil.put( + serviceURL, + payload, + // on success + function () { + if (state == "save") { + var policyList = []; + policyList.push(getParameterByName("id")); + serviceURL = "/devicemgt_admin/policies/inactivate"; + invokerUtil.put( + serviceURL, + policyList, + // on success + function () { + $(".policy-message").removeClass("hidden"); + $(".add-policy").addClass("hidden"); + }, + // on error + function (data) { + console.log(data); + } + ); + } else if (state == "publish") { + var policyList = []; + policyList.push(getParameterByName("id")); + serviceURL = "/devicemgt_admin/policies/activate"; + invokerUtil.put( + serviceURL, + policyList, + // on success + function () { + $(".policy-message").removeClass("hidden"); + $(".add-policy").addClass("hidden"); + }, + // on error + function (data) { + console.log(data); + } + ); + } + }, + // on error + function (data) { + console.log(data); + } + ); +}; + +// Start of HTML embedded invoke methods +var showAdvanceOperation = function (operation, button) { + $(button).addClass('selected'); + $(button).siblings().removeClass('selected'); + var hiddenOperation = ".wr-hidden-operations-content > div"; + $(hiddenOperation + '[data-operation="' + operation + '"]').show(); + $(hiddenOperation + '[data-operation="' + operation + '"]').siblings().hide(); +}; + +/** + * Method to slide down a provided pane upon provided value set. + * + * @param selectElement Select HTML Element to consider + * @param paneID HTML ID of div element to slide down + * @param valueSet Applicable Value Set + */ +var slideDownPaneAgainstValueSet = function (selectElement, paneID, valueSet) { + var selectedValueOnChange = $(selectElement).find("option:selected").val(); + var i, slideDownVotes = 0; + for (i = 0; i < valueSet.length; i++) { + if (selectedValueOnChange == valueSet[i]) { + slideDownVotes++; + } + } + var paneSelector = "#" + paneID; + if (slideDownVotes > 0) { + if (!$(paneSelector).hasClass("expanded")) { + $(paneSelector).addClass("expanded"); + } + $(paneSelector).slideDown(); + } else { + if ($(paneSelector).hasClass("expanded")) { + $(paneSelector).removeClass("expanded"); + } + $(paneSelector).slideUp(); + /* now follows the code to reinitialize all inputs of the slidable pane. + reinitializing input fields into the defaults.*/ + $(paneSelector + " input").each( + function () { + if ($(this).is("input:text")) { + $(this).val($(this).data("default")); + } else if ($(this).is("input:password")) { + $(this).val(""); + } else if ($(this).is("input:checkbox")) { + $(this).prop("checked", $(this).data("default")); + // if this checkbox is the parent input of a grouped-input + if ($(this).hasClass("parent-input")) { + var groupedInput = $(this).parent().parent().parent(); + updateGroupedInputVisibility(groupedInput); + } + } + } + ); + // reinitializing select fields into the defaults + $(paneSelector + " select").each( + function () { + var defaultOption = $(this).data("default"); + $("option:eq(" + defaultOption + ")", this).prop("selected", "selected"); + } + ); + // collapsing expanded-panes (upon the selection of html-select-options) if any + $(paneSelector + " .expanded").each( + function () { + if ($(this).hasClass("expanded")) { + $(this).removeClass("expanded"); + } + $(this).slideUp(); + } + ); + // removing all entries of grid-input elements if exist + $(paneSelector + " .grouped-array-input").each( + function () { + var gridInputs = $(this).find("[data-add-form-clone]"); + if (gridInputs.length > 0) { + gridInputs.remove(); + } + var helpTexts = $(this).find("[data-help-text=add-form]"); + if (helpTexts.length > 0) { + helpTexts.show(); + } + } + ); + } +}; +// End of HTML embedded invoke methods + + +// Start of functions related to grid-input-view + +/** + * Method to set count id to cloned elements. + * @param {object} addFormContainer + */ +var setId = function (addFormContainer) { + $(addFormContainer).find("[data-add-form-clone]").each(function (i) { + $(this).attr("id", $(this).attr("data-add-form-clone").slice(1) + "-" + (i + 1)); + if ($(this).find(".index").length > 0) { + $(this).find(".index").html(i + 1); + } + }); +}; + +/** + * Method to set count id to cloned elements. + * @param {object} addFormContainer + */ +var showHideHelpText = function (addFormContainer) { + var helpText = "[data-help-text=add-form]"; + if ($(addFormContainer).find("[data-add-form-clone]").length > 0) { + $(addFormContainer).find(helpText).hide(); + } else { + $(addFormContainer).find(helpText).show(); + } +}; + +// End of functions related to grid-input-view + +/** + * This method will return query parameter value given its name. + * @param name Query parameter name + * @returns {string} Query parameter value + */ +var getParameterByName = function (name) { + name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]"); + var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"), + results = regex.exec(location.search); + return results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " ")); +}; + +$(document).ready(function () { + $('#appbar-btn-apply-changes').addClass('hidden'); + // Adding initial state of wizard-steps. + $("#policy-profile-wizard-steps").html($(".wr-steps").html()); + + var policyPayloadObj; + invokerUtil.get( + "/devicemgt_admin/policies/" + getParameterByName("id"), + // on success + function (data) { + // console.log("success: " + JSON.stringify(data)); + data = JSON.parse(data); + policyPayloadObj = data["responseContent"]; + skipStep["policy-platform"](policyPayloadObj); + }, + // on error + function (data) { + console.log(data); + // should be redirected to an error page + } + ); + + $("select.select2[multiple=multiple]").select2({ + "tags": true + }); + + $("#users-select-field").hide(); + $("#user-roles-select-field").show(); + + $("input[type='radio'].select-users-radio").change(function () { + if ($("#users-radio-btn").is(":checked")) { + $("#user-roles-select-field").hide(); + $("#users-select-field").show(); + } + if ($("#user-roles-radio-btn").is(":checked")) { + $("#users-select-field").hide(); + $("#user-roles-select-field").show(); + } + }); + + // Support for special input type "ANY" on user(s) & user-role(s) selection + $("#users-input, #user-roles-input").select2({ + "tags": true + }).on("select2:select", function (e) { + if (e.params.data.id == "ANY") { + $(this).val("ANY").trigger("change"); + } else { + $("option[value=ANY]", this).prop("selected", false).parent().trigger("change"); + } + }); + + // Maintains an array of configured features of the profile + var advanceOperations = ".wr-advance-operations"; + $(advanceOperations).on("click", ".wr-input-control.switch", function (event) { + var operationCode = $(this).parents(".operation-data").data("operation-code"); + var operation = $(this).parents(".operation-data").data("operation"); + var operationDataWrapper = $(this).data("target"); + // prevents event bubbling by figuring out what element it's being called from. + if (event.target.tagName == "INPUT") { + var featureConfiguredIcon; + if ($("input[type='checkbox']", this).is(":checked")) { + configuredOperations.push(operationCode); + // when a feature is enabled, if "zero-configured-features" msg is available, hide that. + var zeroConfiguredOperationsErrorMsg = "#policy-profile-main-error-msg"; + if (!$(zeroConfiguredOperationsErrorMsg).hasClass("hidden")) { + $(zeroConfiguredOperationsErrorMsg).addClass("hidden"); + } + // add configured-state-icon to the feature + featureConfiguredIcon = "#" + operation + "-configured"; + if ($(featureConfiguredIcon).hasClass("hidden")) { + $(featureConfiguredIcon).removeClass("hidden"); + } + } else { + //splicing the array if operation is present. + var index = $.inArray(operationCode, configuredOperations); + if (index != -1) { + configuredOperations.splice(index, 1); + } + // when a feature is disabled, clearing all its current configured, error or success states + var subErrorMsgWrapper = "#" + operation + "-feature-error-msg"; + var subErrorIcon = "#" + operation + "-error"; + var subOkIcon = "#" + operation + "-ok"; + featureConfiguredIcon = "#" + operation + "-configured"; + + if (!$(subErrorMsgWrapper).hasClass("hidden")) { + $(subErrorMsgWrapper).addClass("hidden"); + } + if (!$(subErrorIcon).hasClass("hidden")) { + $(subErrorIcon).addClass("hidden"); + } + if (!$(subOkIcon).hasClass("hidden")) { + $(subOkIcon).addClass("hidden"); + } + if (!$(featureConfiguredIcon).hasClass("hidden")) { + $(featureConfiguredIcon).addClass("hidden"); + } + // reinitializing input fields into the defaults + $(operationDataWrapper + " input").each( + function () { + if ($(this).is("input:text")) { + $(this).val($(this).data("default")); + } else if ($(this).is("input:password")) { + $(this).val(""); + } else if ($(this).is("input:checkbox")) { + $(this).prop("checked", $(this).data("default")); + // if this checkbox is the parent input of a grouped-input + if ($(this).hasClass("parent-input")) { + var groupedInput = $(this).parent().parent().parent(); + updateGroupedInputVisibility(groupedInput); + } + } + } + ); + // reinitializing select fields into the defaults + $(operationDataWrapper + " select").each( + function () { + var defaultOption = $(this).data("default"); + $("option:eq(" + defaultOption + ")", this).prop("selected", "selected"); + } + ); + // collapsing expanded-panes (upon the selection of html-select-options) if any + $(operationDataWrapper + " .expanded").each( + function () { + if ($(this).hasClass("expanded")) { + $(this).removeClass("expanded"); + } + $(this).slideUp(); + } + ); + // removing all entries of grid-input elements if exist + $(operationDataWrapper + " .grouped-array-input").each( + function () { + var gridInputs = $(this).find("[data-add-form-clone]"); + if (gridInputs.length > 0) { + gridInputs.remove(); + } + var helpTexts = $(this).find("[data-help-text=add-form]"); + if (helpTexts.length > 0) { + helpTexts.show(); + } + } + ); + } + } + }); + + // adding support for cloning multiple profiles per feature with cloneable class definitions + $(advanceOperations).on("click", ".multi-view.add.enabled", function () { + // get a copy of .cloneable and create new .cloned div element + var cloned = "

      " + $(".cloneable", $(this).parent().parent()).html() + "
      "; + // append newly created .cloned div element to panel-body + $(this).parent().parent().append(cloned); + // enable remove action of newly cloned div element + $(".cloned", $(this).parent().parent()).each( + function () { + if ($(".multi-view.remove", this).hasClass("disabled")) { + $(".multi-view.remove", this).removeClass("disabled"); + } + if (!$(".multi-view.remove", this).hasClass("enabled")) { + $(".multi-view.remove", this).addClass("enabled"); + } + } + ); + }); + + $(advanceOperations).on("click", ".multi-view.remove.enabled", function () { + $(this).parent().remove(); + }); + + // enabling or disabling grouped-input based on the status of a parent check-box + $(advanceOperations).on("click", ".grouped-input", function () { + updateGroupedInputVisibility(this); + }); + + // add form entry click function for grid inputs + $(advanceOperations).on("click", "[data-click-event=add-form]", function () { + var addFormContainer = $("[data-add-form-container=" + $(this).attr("href") + "]"); + var clonedForm = $("[data-add-form=" + $(this).attr("href") + "]").clone(). + find("[data-add-form-element=clone]").attr("data-add-form-clone", $(this).attr("href")); + + // adding class .child-input to capture text-input-array-values + $("input, select", clonedForm).addClass("child-input"); + + $(addFormContainer).append(clonedForm); + setId(addFormContainer); + showHideHelpText(addFormContainer); + }); + + // remove form entry click function for grid inputs + $(advanceOperations).on("click", "[data-click-event=remove-form]", function () { + var addFormContainer = $("[data-add-form-container=" + $(this).attr("href") + "]"); + + $(this).closest("[data-add-form-element=clone]").remove(); + setId(addFormContainer); + showHideHelpText(addFormContainer); + }); + + $(".wizard-stepper").click(function () { + // button clicked here can be either a continue button or a back button. + var currentStep = $(this).data("current"); + var validationIsRequired = $(this).data("validate"); + var wizardIsToBeContinued; + + if (validationIsRequired) { + wizardIsToBeContinued = validateStep[currentStep](); + } else { + wizardIsToBeContinued = true; + } + + if (wizardIsToBeContinued) { + // When moving back and forth, following code segment will + // remove if there are any visible error-messages. + var errorMsgWrappers = ".alert.alert-danger"; + $(errorMsgWrappers).each( + function () { + if (!$(this).hasClass("hidden")) { + $(this).addClass("hidden"); + } + } + ); + + var nextStep = $(this).data("next"); + var isBackBtn = $(this).data("is-back-btn"); + + // if current button is a continuation... + if (!isBackBtn) { + // initiate stepForwardFrom[*] functions to gather form data. + if (stepForwardFrom[currentStep]) { + stepForwardFrom[currentStep](this); + } + } else { + // initiate stepBackFrom[*] functions to rollback. + if (stepBackFrom[currentStep]) { + stepBackFrom[currentStep](); + } + } + + // following step occurs only at the last stage of the wizard. + if (!nextStep) { + window.location.href = $(this).data("direct"); + } + + // updating next wizard step as current. + $(".itm-wiz").each(function () { + var step = $(this).data("step"); + if (step == nextStep) { + $(this).addClass("itm-wiz-current"); + } else { + $(this).removeClass("itm-wiz-current"); + } + }); + + // adding next update of wizard-steps. + $("#" + nextStep + "-wizard-steps").html($(".wr-steps").html()); + + // hiding current section of the wizard and showing next section. + $("." + currentStep).addClass("hidden"); + $("." + nextStep).removeClass("hidden"); + } + }); +}); diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/src/main/resources/jaggeryapps/devicemgt/app/units/mdm.unit.policy.view/public/templates/hidden-operations-android.hbs b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/src/main/resources/jaggeryapps/devicemgt/app/units/mdm.unit.policy.view/public/templates/hidden-operations-android.hbs new file mode 100644 index 0000000000..80932391a9 --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/src/main/resources/jaggeryapps/devicemgt/app/units/mdm.unit.policy.view/public/templates/hidden-operations-android.hbs @@ -0,0 +1,439 @@ +
      + + +
      + +
      +
      + +
      + + + +
      + +
      + +
      + +
      + +
      + + +
      + +
      + + +
      + +
      + + +
      + +
      + + +
      + +
      + + +
      +
      +
      +
      + + + +
      +
      + +
      + + Un-check following checkbox in case you need to disable camera. +
      +
      +
      + +
      +
      +
      +
      +
      + + + +
      +
      + +
      + + Un-check following checkbox in case you do not need the device to be encrypted. +
      +
      +
      + +
      +
      +
      +
      +
      + + + +
      +
      + +
      + + + + + + + + + + + + + + + + + Please note that * sign represents required fields of data. +
      +
      + +
      + + +
      +
      + + +
      + +
      +
      +
      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      +
      \ No newline at end of file diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/src/main/resources/jaggeryapps/devicemgt/app/units/mdm.unit.policy.view/public/templates/hidden-operations-ios.hbs b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/src/main/resources/jaggeryapps/devicemgt/app/units/mdm.unit.policy.view/public/templates/hidden-operations-ios.hbs new file mode 100644 index 0000000000..4aeed0646e --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/src/main/resources/jaggeryapps/devicemgt/app/units/mdm.unit.policy.view/public/templates/hidden-operations-ios.hbs @@ -0,0 +1,2923 @@ +
      + + +
      + +
      +
      + +
      + + + +
      + +
      + +
      + +
      + +
      + +
      + +
      + + +
      + +
      + + +
      + +
      + + +
      + +
      + + +
      + +
      + + +
      + +
      + + +
      + +
      + + +
      +
      +
      +
      + + + +
      +
      + +
      + + +
      + + +
      + +
      + + +
      + +
      + +
      + +
      + +
      + +
      + +
      + +
      + +
      + +
      + + +
      + +
      + + +
      + + + +
      + + +
      + + + +
      + +
      + + + + + + + + + + + + + +
      No:Roaming Consortium OI
      + No entries added yet . +
      + + + + + + + + + +
      +
      + +
      + +
      + +
      + + + + + + + + + + + + + +
      No:NAI Realm Name
      + No entries added yet . +
      + + + + + + + + + +
      +
      + +
      + +
      + +
      + + + + + + + + + + + + + + +
      No:Mobile Country Code ( MCC )Mobile Network Code ( MNC )
      + No entries added yet . +
      + + + + + + + + + + +
      +
      +
      +
      +
      + + + +
      +
      + +
      + + + +
      + + +
      + +
      + + +
      + +
      + +
      + + +
      + +
      + +
      + + +
      + +
      + + +
      + +
      + +
      + +
      + +
      + +
      + +
      + +
      + + +
      + +
      + + +
      + +
      + +
      + +
      + +
      + +
      + Incoming Mail Settings : +
      + +
      + + +
      + +
      + +
      + +
      + + +
      + +
      + + +
      + +
      + + +
      + +
      + + +
      + +
      + Outgoing Mail Settings : +
      + +
      + + +
      + +
      + +
      + +
      + + +
      + +
      + + +
      + +
      + + +
      + +
      + +
      + +
      + + +
      + +
      +
      +
      + + + +
      +
      + +
      + + + +
      + +
      + + + + + + + + + + + + + + +
      No:Device NamePassword
      + No entries added yet . +
      + + + + + + + + + + +
      +
      + +
      + +
      + +
      + + + + + + + + + + + + + +
      No:Destination
      + No entries added yet . +
      + + + + + + + + + +
      +
      + +
      +
      +
      + + + +
      +
      + +
      + + + +
      + + +
      + +
      + + +
      + +
      + +
      + +
      + + +
      + +
      + + +
      + +
      + +
      + + + + + + + + + + + + + + + +
      No:DescriptionSearch BaseScope
      + No entries added yet . +
      + + + + + + + + + + + +
      +
      + +
      +
      +
      + + + +
      +
      + +
      + + + +
      + + +
      + +
      + + +
      + +
      + +
      + +
      + + +
      + +
      + + +
      + +
      + + +
      + +
      + + +
      + +
      +
      +
      + + + +
      +
      + +
      + + + +
      + + +
      + +
      + + +
      + +
      + +
      + +
      + + +
      + +
      + + +
      + +
      +
      +
      + + + +
      +
      + +
      + + + +
      + +
      + + + + + + + + + + + + + + + + + +
      No:APNUsernamePasswordProxyPort
      + No entries added yet . +
      + + + + + + + + + + + + + +
      +
      + +
      +
      +
      + + + +
      +
      + +
      + + + +
      + + +
      + +
      + + +
      + +
      + + +
      + +
      + + +
      + +
      + +
      + + + + + + + + + + + + + + + + + + +
      No:APNAuth.TypeUsernamePasswordProxyPort
      + No entries added yet . +
      + + + + + + + + + + + + + + +
      +
      + +
      +
      +
      + + + +
      +
      + +
      + + + + Restrictions on Device Functionality : +
      +
      +
        +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
          +
        • +
          + +
          +
        • +
        • +
          + +
          +
        • +
        • +
          + +
          +
        • +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      +
      +
      + Restrictions on Applications : +
      +
      +
        +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
          +
        • +
          + +
          +
        • +
        • +
          + +
          +
        • +
        • +
          + +
          +
        • +
        • +
          + +
          +
        • +
        • +
          + + +
          +
        • +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      +
      + + + +
      +
      +
      + +
      +
      \ No newline at end of file diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/src/main/resources/jaggeryapps/devicemgt/app/units/mdm.unit.policy.view/public/templates/hidden-operations-windows.hbs b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/src/main/resources/jaggeryapps/devicemgt/app/units/mdm.unit.policy.view/public/templates/hidden-operations-windows.hbs new file mode 100644 index 0000000000..5febc39721 --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/src/main/resources/jaggeryapps/devicemgt/app/units/mdm.unit.policy.view/public/templates/hidden-operations-windows.hbs @@ -0,0 +1,460 @@ +
      + + +
      + +
      +
      + +
      + + + +
      + +
      + +
      + +
      + +
      + + +
      + +
      + + +
      + +
      + + +
      + +
      + + +
      + +
      + + +
      +
      +
      +
      + + + +
      +
      + +
      + + Un-check following checkbox in case you need to disable camera. +
      +
      +
      + +
      +
      +
      +
      +
      + + + +
      +
      + +
      + + Un-check following checkbox in case you need to disable storage-encryption. +
      +
      +
      + +
      +
      +
      +
      +
      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      +
      \ No newline at end of file diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/src/main/resources/jaggeryapps/devicemgt/app/units/mdm.unit.policy.view/view.hbs b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/src/main/resources/jaggeryapps/devicemgt/app/units/mdm.unit.policy.view/view.hbs new file mode 100644 index 0000000000..223029164c --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/src/main/resources/jaggeryapps/devicemgt/app/units/mdm.unit.policy.view/view.hbs @@ -0,0 +1,87 @@ + + {{#defineZone "policy-profile-top"}} +
      +
      + +
      +
      + {{/defineZone}} + + +
      +
      +
      +
      +
      Policy Overview +
      + {{#defineZone "policy-detail-properties"}} + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      Platform
      Ownership
      Action upon non-compliance
      Status
      Assigned Users
      Assigned Roles
      + {{/defineZone}} +
      Description
      +
      +
      +
      +
      + +
      Profile + Information +
      +
      + +
      +
      +
      + + Loading platform features . . . +
      +
      +
      +
      +
      +
      +
      +
      +
      + +{{#zone "bottomJs"}} + + + + {{js "/js/policy-view.js"}} +{{/zone}} + diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/src/main/resources/jaggeryapps/devicemgt/app/units/mdm.unit.policy.view/view.js b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/src/main/resources/jaggeryapps/devicemgt/app/units/mdm.unit.policy.view/view.js new file mode 100644 index 0000000000..36b130ca07 --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/src/main/resources/jaggeryapps/devicemgt/app/units/mdm.unit.policy.view/view.js @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2016, 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("policy-view-edit-unit backend js"); + log.debug("calling policy-view-edit-unit"); + var userModule = require("/app/modules/user.js").userModule; + context.roles = userModule.getRoles().content; + return context; +} \ No newline at end of file diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/src/main/resources/jaggeryapps/devicemgt/app/units/mdm.unit.policy.view/view.json b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/src/main/resources/jaggeryapps/devicemgt/app/units/mdm.unit.policy.view/view.json new file mode 100644 index 0000000000..fd25901297 --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/src/main/resources/jaggeryapps/devicemgt/app/units/mdm.unit.policy.view/view.json @@ -0,0 +1,3 @@ +{ + "version" : "1.0.0" +} \ No newline at end of file diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/src/main/resources/jaggeryapps/devicemgt/app/units/mdm.unit.policy.wizard/public/js/policy-create.js b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/src/main/resources/jaggeryapps/devicemgt/app/units/mdm.unit.policy.wizard/public/js/policy-create.js new file mode 100644 index 0000000000..a51ae295de --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/src/main/resources/jaggeryapps/devicemgt/app/units/mdm.unit.policy.wizard/public/js/policy-create.js @@ -0,0 +1,2355 @@ +/* + * Copyright (c) 2016, 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. + */ + +var validateStep = {}; +var validateInline = {}; +var stepForwardFrom = {}; +var stepBackFrom = {}; +var policy = {}; +var configuredOperations = []; + +// Constants to define platform types available +var platformTypeConstants = { + "ANDROID": "android", + "IOS": "ios", + "WINDOWS": "windows" +}; + +// Constants to define platform types ids. +var platformIds = { + "ANDROID": 1, + "IOS": 3, + "WINDOWS": 2 +}; + +// Constants to define Android Operation Constants +var androidOperationConstants = { + "PASSCODE_POLICY_OPERATION": "passcode-policy", + "PASSCODE_POLICY_OPERATION_CODE": "PASSCODE_POLICY", + "CAMERA_OPERATION": "camera", + "CAMERA_OPERATION_CODE": "CAMERA", + "ENCRYPT_STORAGE_OPERATION": "encrypt-storage", + "ENCRYPT_STORAGE_OPERATION_CODE": "ENCRYPT_STORAGE", + "WIFI_OPERATION": "wifi", + "WIFI_OPERATION_CODE": "WIFI" +}; + +// Constants to define Android Operation Constants +var windowsOperationConstants = { + "PASSCODE_POLICY_OPERATION": "passcode-policy", + "PASSCODE_POLICY_OPERATION_CODE": "PASSCODE_POLICY", + "CAMERA_OPERATION": "camera", + "CAMERA_OPERATION_CODE": "CAMERA", + "ENCRYPT_STORAGE_OPERATION": "encrypt-storage", + "ENCRYPT_STORAGE_OPERATION_CODE": "ENCRYPT_STORAGE" +}; + +// Constants to define iOS Operation Constants +var iosOperationConstants = { + "PASSCODE_POLICY_OPERATION": "passcode-policy", + "PASSCODE_POLICY_OPERATION_CODE": "PASSCODE_POLICY", + "RESTRICTIONS_OPERATION": "restrictions", + "RESTRICTIONS_OPERATION_CODE": "RESTRICTION", + "WIFI_OPERATION": "wifi", + "WIFI_OPERATION_CODE": "WIFI", + "EMAIL_OPERATION": "email", + "EMAIL_OPERATION_CODE": "EMAIL", + "AIRPLAY_OPERATION": "airplay", + "AIRPLAY_OPERATION_CODE": "AIR_PLAY", + "LDAP_OPERATION": "ldap", + "LDAP_OPERATION_CODE": "LDAP", + "CALENDAR_OPERATION": "calendar", + "CALENDAR_OPERATION_CODE": "CALDAV", + "CALENDAR_SUBSCRIPTION_OPERATION": "calendar-subscription", + "CALENDAR_SUBSCRIPTION_OPERATION_CODE": "CALENDAR_SUBSCRIPTION", + "APN_OPERATION": "apn", + "APN_OPERATION_CODE": "APN", + "CELLULAR_OPERATION": "cellular", + "CELLULAR_OPERATION_CODE": "CELLULAR" +}; + +/** + * Method to update the visibility of grouped input. + * @param domElement HTML grouped-input element with class name "grouped-input" + */ +var updateGroupedInputVisibility = function (domElement) { + if ($(".parent-input:first", domElement).is(":checked")) { + if ($(".grouped-child-input:first", domElement).hasClass("disabled")) { + $(".grouped-child-input:first", domElement).removeClass("disabled"); + } + $(".child-input", domElement).each(function () { + $(this).prop('disabled', false); + }); + } else { + if (!$(".grouped-child-input:first", domElement).hasClass("disabled")) { + $(".grouped-child-input:first", domElement).addClass("disabled"); + } + $(".child-input", domElement).each(function () { + $(this).prop('disabled', true); + }); + } +}; + +var validateInline = {}; +var clearInline = {}; + +var enableInlineError = function (inputField, errorMsg, errorSign) { + var fieldIdentifier = "#" + inputField; + var errorMsgIdentifier = "#" + inputField + " ." + errorMsg; + var errorSignIdentifier = "#" + inputField + " ." + errorSign; + + if (inputField) { + $(fieldIdentifier).addClass(" has-error has-feedback"); + } + + if (errorMsg) { + $(errorMsgIdentifier).removeClass(" hidden"); + } + + if (errorSign) { + $(errorSignIdentifier).removeClass(" hidden"); + } +}; + +var disableInlineError = function (inputField, errorMsg, errorSign) { + var fieldIdentifier = "#" + inputField; + var errorMsgIdentifier = "#" + inputField + " ." + errorMsg; + var errorSignIdentifier = "#" + inputField + " ." + errorSign; + + if (inputField) { + $(fieldIdentifier).removeClass(" has-error has-feedback"); + } + + if (errorMsg) { + $(errorMsgIdentifier).addClass(" hidden"); + } + + if (errorSign) { + $(errorSignIdentifier).addClass(" hidden"); + } +}; + +/** + *clear inline validation messages. + */ +clearInline["policy-name"] = function () { + disableInlineError("plicynameField", "nameEmpty", "nameError"); +}; + + +/** + * Validate if provided policy name is valid against RegEx configures. + */ +validateInline["policy-name"] = function () { + var policyName = $("input#policy-name-input").val(); + if (policyName && inputIsValidAgainstLength(policyName, 1, 30)) { + disableInlineError("plicynameField", "nameEmpty", "nameError"); + } else { + enableInlineError("plicynameField", "nameEmpty", "nameError"); + } +}; + +$("#policy-name-input").focus(function () { + clearInline["policy-name"](); +}); + +$("#policy-name-input").blur(function () { + validateInline["policy-name"](); +}); + +/** + * Checks if provided number is valid against a range. + * + * @param numberInput Number Input + * @param min Minimum Limit + * @param max Maximum Limit + * @returns {boolean} Returns true if input is within the specified range + */ +var inputIsValidAgainstRange = function (numberInput, min, max) { + return (numberInput == min || (numberInput > min && numberInput < max) || numberInput == max); +}; + +/** + * Checks if provided input is valid against RegEx input. + * + * @param regExp Regular expression + * @param input Input string to check + * @returns {boolean} Returns true if input matches RegEx + */ +var inputIsValidAgainstRegExp = function (regExp, input) { + return regExp.test(input); +}; + +validateStep["policy-profile"] = function () { + var validationStatusArray = []; + var validationStatus; + var operation; + + // starting validation process and updating validationStatus + if (policy["platform"] == platformTypeConstants["ANDROID"]) { + if (configuredOperations.length == 0) { + // updating validationStatus + validationStatus = { + "error": true, + "mainErrorMsg": "You cannot continue. Zero configured features." + }; + // updating validationStatusArray with validationStatus + validationStatusArray.push(validationStatus); + } else { + // validating each and every configured Operation + // Validating PASSCODE_POLICY + if ($.inArray(androidOperationConstants["PASSCODE_POLICY_OPERATION_CODE"], configuredOperations) != -1) { + // if PASSCODE_POLICY is configured + operation = androidOperationConstants["PASSCODE_POLICY_OPERATION"]; + // initializing continueToCheckNextInputs to true + var continueToCheckNextInputs = true; + + // validating first input: passcodePolicyMaxPasscodeAgeInDays + var passcodePolicyMaxPasscodeAgeInDays = $("input#passcode-policy-max-passcode-age-in-days").val(); + if (passcodePolicyMaxPasscodeAgeInDays) { + if (!$.isNumeric(passcodePolicyMaxPasscodeAgeInDays)) { + validationStatus = { + "error": true, + "subErrorMsg": "Provided passcode age is not a number.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } else if (!inputIsValidAgainstRange(passcodePolicyMaxPasscodeAgeInDays, 1, 730)) { + validationStatus = { + "error": true, + "subErrorMsg": "Provided passcode age is not with in the range of 1-to-730.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } + } + + // validating second and last input: passcodePolicyPasscodeHistory + if (continueToCheckNextInputs) { + var passcodePolicyPasscodeHistory = $("input#passcode-policy-passcode-history").val(); + if (passcodePolicyPasscodeHistory) { + if (!$.isNumeric(passcodePolicyPasscodeHistory)) { + validationStatus = { + "error": true, + "subErrorMsg": "Provided passcode history is not a number.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } else if (!inputIsValidAgainstRange(passcodePolicyPasscodeHistory, 1, 50)) { + validationStatus = { + "error": true, + "subErrorMsg": "Provided passcode history is not with in the range of 1-to-50.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } + } + } + + // at-last, if the value of continueToCheckNextInputs is still true + // this means that no error is found + if (continueToCheckNextInputs) { + validationStatus = { + "error": false, + "okFeature": operation + }; + } + + // updating validationStatusArray with validationStatus + validationStatusArray.push(validationStatus); + } + // Validating CAMERA + if ($.inArray(androidOperationConstants["CAMERA_OPERATION_CODE"], configuredOperations) != -1) { + // if CAMERA is configured + operation = androidOperationConstants["CAMERA_OPERATION"]; + // updating validationStatus + validationStatus = { + "error": false, + "okFeature": operation + }; + // updating validationStatusArray with validationStatus + validationStatusArray.push(validationStatus); + } + // Validating ENCRYPT_STORAGE + if ($.inArray(androidOperationConstants["ENCRYPT_STORAGE_OPERATION_CODE"], configuredOperations) != -1) { + // if ENCRYPT_STORAGE is configured + operation = androidOperationConstants["ENCRYPT_STORAGE_OPERATION"]; + // updating validationStatus + validationStatus = { + "error": false, + "okFeature": operation + }; + // updating validationStatusArray with validationStatus + validationStatusArray.push(validationStatus); + } + // Validating WIFI + if ($.inArray(androidOperationConstants["WIFI_OPERATION_CODE"], configuredOperations) != -1) { + // if WIFI is configured + operation = androidOperationConstants["WIFI_OPERATION"]; + // initializing continueToCheckNextInputs to true + continueToCheckNextInputs = true; + + var wifiSSID = $("input#wifi-ssid").val(); + if (!wifiSSID) { + validationStatus = { + "error": true, + "subErrorMsg": "WIFI SSID is not given. You cannot proceed.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } + + // at-last, if the value of continueToCheckNextInputs is still true + // this means that no error is found + if (continueToCheckNextInputs) { + validationStatus = { + "error": false, + "okFeature": operation + }; + } + + // updating validationStatusArray with validationStatus + validationStatusArray.push(validationStatus); + } + } + } + if (policy["platform"] == platformTypeConstants["WINDOWS"]) { + if (configuredOperations.length == 0) { + // updating validationStatus + validationStatus = { + "error": true, + "mainErrorMsg": "You cannot continue. Zero configured features." + }; + // updating validationStatusArray with validationStatus + validationStatusArray.push(validationStatus); + } else { + // validating each and every configured Operation + // Validating PASSCODE_POLICY + if ($.inArray(windowsOperationConstants["PASSCODE_POLICY_OPERATION_CODE"], configuredOperations) != -1) { + // if PASSCODE_POLICY is configured + operation = windowsOperationConstants["PASSCODE_POLICY_OPERATION"]; + // initializing continueToCheckNextInputs to true + var continueToCheckNextInputs = true; + + // validating first input: passcodePolicyMaxPasscodeAgeInDays + var passcodePolicyMaxPasscodeAgeInDays = $("input#passcode-policy-max-passcode-age-in-days").val(); + if (passcodePolicyMaxPasscodeAgeInDays) { + if (!$.isNumeric(passcodePolicyMaxPasscodeAgeInDays)) { + validationStatus = { + "error": true, + "subErrorMsg": "Provided passcode age is not a number.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } else if (!inputIsValidAgainstRange(passcodePolicyMaxPasscodeAgeInDays, 1, 730)) { + validationStatus = { + "error": true, + "subErrorMsg": "Provided passcode age is not with in the range of 1-to-730.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } + } + + // validating second and last input: passcodePolicyPasscodeHistory + if (continueToCheckNextInputs) { + var passcodePolicyPasscodeHistory = $("input#passcode-policy-passcode-history").val(); + if (passcodePolicyPasscodeHistory) { + if (!$.isNumeric(passcodePolicyPasscodeHistory)) { + validationStatus = { + "error": true, + "subErrorMsg": "Provided passcode history is not a number.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } else if (!inputIsValidAgainstRange(passcodePolicyPasscodeHistory, 1, 50)) { + validationStatus = { + "error": true, + "subErrorMsg": "Provided passcode history is not with in the range of 1-to-50.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } + } + } + + // at-last, if the value of continueToCheckNextInputs is still true + // this means that no error is found + if (continueToCheckNextInputs) { + validationStatus = { + "error": false, + "okFeature": operation + }; + } + + // updating validationStatusArray with validationStatus + validationStatusArray.push(validationStatus); + } + // Validating CAMERA + if ($.inArray(windowsOperationConstants["CAMERA_OPERATION_CODE"], configuredOperations) != -1) { + // if CAMERA is configured + operation = windowsOperationConstants["CAMERA_OPERATION"]; + // updating validationStatus + validationStatus = { + "error": false, + "okFeature": operation + }; + // updating validationStatusArray with validationStatus + validationStatusArray.push(validationStatus); + } + // Validating ENCRYPT_STORAGE + if ($.inArray(windowsOperationConstants["ENCRYPT_STORAGE_OPERATION_CODE"], configuredOperations) != -1) { + // if ENCRYPT_STORAGE is configured + operation = windowsOperationConstants["ENCRYPT_STORAGE_OPERATION"]; + // updating validationStatus + validationStatus = { + "error": false, + "okFeature": operation + }; + // updating validationStatusArray with validationStatus + validationStatusArray.push(validationStatus); + } + + } + } else if (policy["platform"] == platformTypeConstants["IOS"]) { + if (configuredOperations.length == 0) { + // updating validationStatus + validationStatus = { + "error": true, + "mainErrorMsg": "You cannot continue. Zero configured features." + }; + // updating validationStatusArray with validationStatus + validationStatusArray.push(validationStatus); + } else { + // validating each and every configured Operation + // Validating PASSCODE_POLICY + if ($.inArray(iosOperationConstants["PASSCODE_POLICY_OPERATION_CODE"], configuredOperations) != -1) { + // if PASSCODE_POLICY is configured + operation = iosOperationConstants["PASSCODE_POLICY_OPERATION"]; + // initializing continueToCheckNextInputs to true + continueToCheckNextInputs = true; + + // validating first input: passcodePolicyMaxPasscodeAgeInDays + passcodePolicyMaxPasscodeAgeInDays = $("input#passcode-policy-max-passcode-age-in-days").val(); + if (passcodePolicyMaxPasscodeAgeInDays) { + if (!$.isNumeric(passcodePolicyMaxPasscodeAgeInDays)) { + validationStatus = { + "error": true, + "subErrorMsg": "Provided passcode age is not a number.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } else if (!inputIsValidAgainstRange(passcodePolicyMaxPasscodeAgeInDays, 1, 730)) { + validationStatus = { + "error": true, + "subErrorMsg": "Provided passcode age is not with in the range of 1-to-730.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } + } + + // validating second and last input: passcodePolicyPasscodeHistory + if (continueToCheckNextInputs) { + passcodePolicyPasscodeHistory = $("input#passcode-policy-passcode-history").val(); + if (passcodePolicyPasscodeHistory) { + if (!$.isNumeric(passcodePolicyPasscodeHistory)) { + validationStatus = { + "error": true, + "subErrorMsg": "Provided passcode history is not a number.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } else if (!inputIsValidAgainstRange(passcodePolicyPasscodeHistory, 1, 50)) { + validationStatus = { + "error": true, + "subErrorMsg": "Provided passcode history is not with in the range of 1-to-50.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } + } + } + + // at-last, if the value of continueToCheckNextInputs is still true + // this means that no error is found + if (continueToCheckNextInputs) { + validationStatus = { + "error": false, + "okFeature": operation + }; + } + + // updating validationStatusArray with validationStatus + validationStatusArray.push(validationStatus); + } + // Validating RESTRICTIONS + if ($.inArray(iosOperationConstants["RESTRICTIONS_OPERATION_CODE"], configuredOperations) != -1) { + // if RESTRICTION is configured + operation = iosOperationConstants["RESTRICTIONS_OPERATION"]; + // initializing continueToCheckNextInputs to true + continueToCheckNextInputs = true; + + // getting input values to be validated + var restrictionsAutonomousSingleAppModePermittedAppIDsGridChildInputs = + "div#restrictions-autonomous-single-app-mode-permitted-app-ids .child-input"; + if ($(restrictionsAutonomousSingleAppModePermittedAppIDsGridChildInputs).length > 0) { + var childInput; + var childInputArray = []; + var emptyChildInputCount = 0; + var duplicatesExist = false; + // looping through each child input + $(restrictionsAutonomousSingleAppModePermittedAppIDsGridChildInputs).each(function () { + childInput = $(this).val(); + childInputArray.push(childInput); + if (!childInput) { + // if child input field is empty + emptyChildInputCount++; + } + }); + // checking for duplicates + var initialChildInputArrayLength = childInputArray.length; + if (emptyChildInputCount == 0 && initialChildInputArrayLength > 1) { + var m, poppedChildInput; + for (m = 0; m < (initialChildInputArrayLength - 1); m++) { + poppedChildInput = childInputArray.pop(); + var n; + for (n = 0; n < childInputArray.length; n++) { + if (poppedChildInput == childInputArray[n]) { + duplicatesExist = true; + break; + } + } + if (duplicatesExist) { + break; + } + } + } + // updating validationStatus + if (emptyChildInputCount > 0) { + // if empty child inputs are present + validationStatus = { + "error": true, + "subErrorMsg": "One or more permitted App ID entries in " + + "Autonomous Single App Mode are empty.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } else if (duplicatesExist) { + validationStatus = { + "error": true, + "subErrorMsg": "Duplicate values exist with permitted App ID entries in " + + "Autonomous Single App Mode.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } + } + + // at-last, if the value of continueToCheckNextInputs is still true + // this means that no error is found + if (continueToCheckNextInputs) { + validationStatus = { + "error": false, + "okFeature": operation + }; + } + + // updating validationStatusArray with validationStatus + validationStatusArray.push(validationStatus); + } + // Validating WIFI + if ($.inArray(iosOperationConstants["WIFI_OPERATION_CODE"], configuredOperations) != -1) { + // if WIFI is configured + operation = iosOperationConstants["WIFI_OPERATION"]; + // initializing continueToCheckNextInputs to true + continueToCheckNextInputs = true; + + // getting input values to be validated + wifiSSID = $("input#wifi-ssid").val(); + var wifiDomainName = $("input#wifi-domain-name").val(); + if (!wifiSSID && !wifiDomainName) { + validationStatus = { + "error": true, + "subErrorMsg": "Both Wi-Fi SSID and Wi-Fi Domain Name are not given. You cannot proceed.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } + + if (continueToCheckNextInputs) { + // getting proxy-setup value + var wifiProxyType = $("select#wifi-proxy-type").find("option:selected").attr("value"); + if (wifiProxyType == "Manual") { + // adds up additional fields to be validated + var wifiProxyServer = $("input#wifi-proxy-server").val(); + if (!wifiProxyServer) { + validationStatus = { + "error": true, + "subErrorMsg": "Wi-Fi Proxy Server is required. You cannot proceed.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } + + if (continueToCheckNextInputs) { + var wifiProxyPort = $("input#wifi-proxy-port").val(); + if (!wifiProxyPort) { + validationStatus = { + "error": true, + "subErrorMsg": "Wi-Fi Proxy Port is required. You cannot proceed.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } else if (!$.isNumeric(wifiProxyPort)) { + validationStatus = { + "error": true, + "subErrorMsg": "Wi-Fi Proxy Port requires a number input.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } else if (!inputIsValidAgainstRange(wifiProxyPort, 0, 65535)) { + validationStatus = { + "error": true, + "subErrorMsg": "Wi-Fi Proxy Port is not within the range " + + "of valid port numbers.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } + } + } + } + + if (continueToCheckNextInputs) { + // getting encryption-type value + var wifiEncryptionType = $("select#wifi-encryption-type").find("option:selected").attr("value"); + if (wifiEncryptionType != "None") { + var wifiPayloadCertificateAnchorUUIDsGridChildInputs = + "div#wifi-payload-certificate-anchor-uuids .child-input"; + if ($(wifiPayloadCertificateAnchorUUIDsGridChildInputs).length > 0) { + emptyChildInputCount = 0; + childInputArray = []; + duplicatesExist = false; + // looping through each child input + $(wifiPayloadCertificateAnchorUUIDsGridChildInputs).each(function () { + childInput = $(this).val(); + childInputArray.push(childInput); + if (!childInput) { + // if child input field is empty + emptyChildInputCount++; + } + }); + // checking for duplicates + initialChildInputArrayLength = childInputArray.length; + if (emptyChildInputCount == 0 && initialChildInputArrayLength > 1) { + for (m = 0; m < (initialChildInputArrayLength - 1); m++) { + poppedChildInput = childInputArray.pop(); + for (n = 0; n < childInputArray.length; n++) { + if (poppedChildInput == childInputArray[n]) { + duplicatesExist = true; + break; + } + } + if (duplicatesExist) { + break; + } + } + } + // updating validationStatus + if (emptyChildInputCount > 0) { + // if empty child inputs are present + validationStatus = { + "error": true, + "subErrorMsg": "One or more Payload Certificate " + + "Anchor UUIDs are empty.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } else if (duplicatesExist) { + validationStatus = { + "error": true, + "subErrorMsg": "Duplicate values exist " + + "with Payload Certificate Anchor UUIDs.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } + } + + if (continueToCheckNextInputs) { + var wifiTLSTrustedServerNamesGridChildInputs = + "div#wifi-tls-trusted-server-names .child-input"; + if ($(wifiTLSTrustedServerNamesGridChildInputs).length > 0) { + emptyChildInputCount = 0; + childInputArray = []; + duplicatesExist = false; + // looping through each child input + $(wifiTLSTrustedServerNamesGridChildInputs).each(function () { + childInput = $(this).val(); + childInputArray.push(childInput); + if (!childInput) { + // if child input field is empty + emptyChildInputCount++; + } + }); + // checking for duplicates + initialChildInputArrayLength = childInputArray.length; + if (emptyChildInputCount == 0 && initialChildInputArrayLength > 1) { + for (m = 0; m < (initialChildInputArrayLength - 1); m++) { + poppedChildInput = childInputArray.pop(); + for (n = 0; n < childInputArray.length; n++) { + if (poppedChildInput == childInputArray[n]) { + duplicatesExist = true; + break; + } + } + if (duplicatesExist) { + break; + } + } + } + // updating validationStatus + if (emptyChildInputCount > 0) { + // if empty child inputs are present + validationStatus = { + "error": true, + "subErrorMsg": "One or more TLS Trusted Server Names are empty.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } else if (duplicatesExist) { + validationStatus = { + "error": true, + "subErrorMsg": "Duplicate values exist " + + "with TLS Trusted Server Names.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } + } + } + } + } + + if (continueToCheckNextInputs) { + var wifiRoamingConsortiumOIsGridChildInputs = "div#wifi-roaming-consortium-ois .child-input"; + if ($(wifiRoamingConsortiumOIsGridChildInputs).length > 0) { + emptyChildInputCount = 0; + var outOfAllowedLengthCount = 0; + var invalidAgainstRegExCount = 0; + childInputArray = []; + duplicatesExist = false; + // looping through each child input + $(wifiRoamingConsortiumOIsGridChildInputs).each(function () { + childInput = $(this).val(); + childInputArray.push(childInput); + if (!childInput) { + // if child input field is empty + emptyChildInputCount++; + } else if (!inputIsValidAgainstLength(childInput, 6, 6) && !inputIsValidAgainstLength(childInput, 10, 10)) { + outOfAllowedLengthCount++; + } else if (!inputIsValidAgainstRegExp(/^[a-fA-F0-9]+$/, childInput)) { + invalidAgainstRegExCount++; + } + }); + // checking for duplicates + initialChildInputArrayLength = childInputArray.length; + if (emptyChildInputCount == 0 && initialChildInputArrayLength > 1) { + for (m = 0; m < (initialChildInputArrayLength - 1); m++) { + poppedChildInput = childInputArray.pop(); + for (n = 0; n < childInputArray.length; n++) { + if (poppedChildInput == childInputArray[n]) { + duplicatesExist = true; + break; + } + } + if (duplicatesExist) { + break; + } + } + } + // updating validationStatus + if (emptyChildInputCount > 0) { + // if empty child inputs are present + validationStatus = { + "error": true, + "subErrorMsg": "One or more Roaming Consortium OIs are empty.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } else if (outOfAllowedLengthCount > 0) { + // if outOfMaxAllowedLength input is present + validationStatus = { + "error": true, + "subErrorMsg": "One or more Roaming Consortium OIs " + + "are out of allowed length.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } else if (invalidAgainstRegExCount > 0) { + // if invalid inputs in terms of hexadecimal format are present + validationStatus = { + "error": true, + "subErrorMsg": "One or more Roaming Consortium OIs " + + "contain non-hexadecimal characters.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } else if (duplicatesExist) { + validationStatus = { + "error": true, + "subErrorMsg": "Duplicate values exist with Roaming Consortium OIs.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } + } + } + + if (continueToCheckNextInputs) { + var wifiNAIRealmNamesGridChildInputs = "div#wifi-nai-realm-names .child-input"; + if ($(wifiNAIRealmNamesGridChildInputs).length > 0) { + emptyChildInputCount = 0; + childInputArray = []; + duplicatesExist = false; + // looping through each child input + $(wifiNAIRealmNamesGridChildInputs).each(function () { + childInput = $(this).val(); + childInputArray.push(childInput); + if (!childInput) { + // if child input field is empty + emptyChildInputCount++; + } + }); + // checking for duplicates + initialChildInputArrayLength = childInputArray.length; + if (emptyChildInputCount == 0 && initialChildInputArrayLength > 1) { + for (m = 0; m < (initialChildInputArrayLength - 1); m++) { + poppedChildInput = childInputArray.pop(); + for (n = 0; n < childInputArray.length; n++) { + if (poppedChildInput == childInputArray[n]) { + duplicatesExist = true; + break; + } + } + if (duplicatesExist) { + break; + } + } + } + // updating validationStatus + if (emptyChildInputCount > 0) { + // if empty child inputs are present + validationStatus = { + "error": true, + "subErrorMsg": "One or more NAI Realm Names are empty.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } else if (duplicatesExist) { + validationStatus = { + "error": true, + "subErrorMsg": "Duplicate values exist with NAI Realm Names.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } + } + } + + if (continueToCheckNextInputs) { + var wifiMCCAndMNCsGridChildInputs = "div#wifi-mcc-and-mncs .child-input"; + if ($(wifiMCCAndMNCsGridChildInputs).length > 0) { + var childInputCount = 0; + var stringPair; + emptyChildInputCount = 0; + outOfAllowedLengthCount = 0; + var notNumericInputCount = 0; + childInputArray = []; + duplicatesExist = false; + // looping through each child input + $(wifiMCCAndMNCsGridChildInputs).each(function () { + childInput = $(this).val(); + // pushing each string pair to childInputArray + childInputCount++; + if (childInputCount % 2 == 1) { + // initialize stringPair value + stringPair = ""; + // append first part of the string + stringPair += childInput; + } else { + // append second part of the string + stringPair += childInput; + childInputArray.push(stringPair); + } + // updating emptyChildInputCount & outOfAllowedLengthCount + if (!childInput) { + // if child input field is empty + emptyChildInputCount++; + } else if (!$.isNumeric(childInput)) { + notNumericInputCount++; + } else if (!inputIsValidAgainstLength(childInput, 3, 3)) { + outOfAllowedLengthCount++; + } + }); + // checking for duplicates + initialChildInputArrayLength = childInputArray.length; + if (emptyChildInputCount == 0 && initialChildInputArrayLength > 1) { + for (m = 0; m < (initialChildInputArrayLength - 1); m++) { + poppedChildInput = childInputArray.pop(); + for (n = 0; n < childInputArray.length; n++) { + if (poppedChildInput == childInputArray[n]) { + duplicatesExist = true; + break; + } + } + if (duplicatesExist) { + break; + } + } + } + // updating validationStatus + if (emptyChildInputCount > 0) { + // if empty child inputs are present + validationStatus = { + "error": true, + "subErrorMsg": "One or more MCC/MNC pairs are empty.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } else if (notNumericInputCount > 0) { + // if notNumeric input is present + validationStatus = { + "error": true, + "subErrorMsg": "One or more MCC/MNC pairs are not numeric.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } else if (outOfAllowedLengthCount > 0) { + // if outOfAllowedLength input is present + validationStatus = { + "error": true, + "subErrorMsg": "One or more MCC/MNC pairs " + + "do not fulfill the accepted length of 6 digits.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } else if (duplicatesExist) { + validationStatus = { + "error": true, + "subErrorMsg": "Duplicate values exist with MCC/MNC pairs.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } + } + } + + // at-last, if the value of continueToCheckNextInputs is still true + // this means that no error is found + if (continueToCheckNextInputs) { + validationStatus = { + "error": false, + "okFeature": operation + }; + } + + // updating validationStatusArray with validationStatus + validationStatusArray.push(validationStatus); + } + // Validating EMAIL + if ($.inArray(iosOperationConstants["EMAIL_OPERATION_CODE"], configuredOperations) != -1) { + // if EMAIL is configured + operation = iosOperationConstants["EMAIL_OPERATION"]; + // initializing continueToCheckNextInputs to true + continueToCheckNextInputs = true; + + var emailAddress = $("input#email-address").val(); + if (emailAddress && !inputIsValidAgainstRegExp(/^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$/, emailAddress)) { + validationStatus = { + "error": true, + "subErrorMsg": "Email Address is not valid.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } + + if (continueToCheckNextInputs) { + var emailIncomingMailServerHostname = $("input#email-incoming-mail-server-hostname").val(); + if (!emailIncomingMailServerHostname) { + validationStatus = { + "error": true, + "subErrorMsg": "Incoming Mail Server Hostname is empty. You cannot proceed.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } + } + + if (continueToCheckNextInputs) { + var emailIncomingMailServerPort = $("input#email-incoming-mail-server-port").val(); + if (emailIncomingMailServerPort && emailIncomingMailServerPort != '') { + if (!$.isNumeric(emailIncomingMailServerPort)) { + validationStatus = { + "error": true, + "subErrorMsg": "Incoming Mail Server Port requires a number input.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } else if (!inputIsValidAgainstRange(emailIncomingMailServerPort, 0, 65535)) { + validationStatus = { + "error": true, + "subErrorMsg": "Incoming Mail Server Port is not within the range " + + "of valid port numbers.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } + } + } + + if (continueToCheckNextInputs) { + var emailOutgoingMailServerHostname = $("input#email-outgoing-mail-server-hostname").val(); + if (!emailOutgoingMailServerHostname) { + validationStatus = { + "error": true, + "subErrorMsg": "Outgoing Mail Server Hostname is empty. You cannot proceed.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } + } + + if (continueToCheckNextInputs) { + var emailOutgoingMailServerPort = $("input#email-outgoing-mail-server-port").val(); + if (emailOutgoingMailServerPort) { + if (!$.isNumeric(emailOutgoingMailServerPort)) { + validationStatus = { + "error": true, + "subErrorMsg": "Outgoing Mail Server Port requires a number input.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } else if (!inputIsValidAgainstRange(emailOutgoingMailServerPort, 0, 65535)) { + validationStatus = { + "error": true, + "subErrorMsg": "Outgoing Mail Server Port is not within the range " + + "of valid port numbers.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } + } + } + + // at-last, if the value of continueToCheckNextInputs is still true + // this means that no error is found + if (continueToCheckNextInputs) { + validationStatus = { + "error": false, + "okFeature": operation + }; + } + + // updating validationStatusArray with validationStatus + validationStatusArray.push(validationStatus); + } + // Validating AIRPLAY + if ($.inArray(iosOperationConstants["AIRPLAY_OPERATION_CODE"], configuredOperations) != -1) { + // if AIRPLAY is configured + operation = iosOperationConstants["AIRPLAY_OPERATION"]; + // initializing continueToCheckNextInputs to true + continueToCheckNextInputs = true; + + var airplayCredentialsGridChildInputs = "div#airplay-credentials .child-input"; + var airplayDestinationsGridChildInputs = "div#airplay-destinations .child-input"; + if ($(airplayCredentialsGridChildInputs).length == 0 && + $(airplayDestinationsGridChildInputs).length == 0) { + validationStatus = { + "error": true, + "subErrorMsg": "AirPlay settings have zero configurations attached.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } + + if (continueToCheckNextInputs) { + if ($(airplayCredentialsGridChildInputs).length > 0) { + childInputCount = 0; + childInputArray = []; + emptyChildInputCount = 0; + duplicatesExist = false; + // looping through each child input + $(airplayCredentialsGridChildInputs).each(function () { + childInputCount++; + if (childInputCount % 2 == 1) { + // if child input is of first column + childInput = $(this).val(); + childInputArray.push(childInput); + // updating emptyChildInputCount + if (!childInput) { + // if child input field is empty + emptyChildInputCount++; + } + } + }); + // checking for duplicates + initialChildInputArrayLength = childInputArray.length; + if (emptyChildInputCount == 0 && initialChildInputArrayLength > 1) { + for (m = 0; m < (initialChildInputArrayLength - 1); m++) { + poppedChildInput = childInputArray.pop(); + for (n = 0; n < childInputArray.length; n++) { + if (poppedChildInput == childInputArray[n]) { + duplicatesExist = true; + break; + } + } + if (duplicatesExist) { + break; + } + } + } + // updating validationStatus + if (emptyChildInputCount > 0) { + // if empty child inputs are present + validationStatus = { + "error": true, + "subErrorMsg": "One or more Device Names of " + + "AirPlay Credentials are empty.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } else if (duplicatesExist) { + // if duplicate input is present + validationStatus = { + "error": true, + "subErrorMsg": "Duplicate values exist with " + + "Device Names of AirPlay Credentials.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } + } + } + + if (continueToCheckNextInputs) { + if ($(airplayDestinationsGridChildInputs).length > 0) { + childInputArray = []; + emptyChildInputCount = 0; + invalidAgainstRegExCount = 0; + duplicatesExist = false; + // looping through each child input + $(airplayDestinationsGridChildInputs).each(function () { + childInput = $(this).val(); + childInputArray.push(childInput); + // updating emptyChildInputCount + if (!childInput) { + // if child input field is empty + emptyChildInputCount++; + } else if (!inputIsValidAgainstRegExp( + /([a-z|A-Z|0-9][a-z|A-Z|0-9][:]){5}([a-z|A-Z|0-9][a-z|A-Z|0-9])$/, childInput)) { + // if child input field is invalid against RegEx + invalidAgainstRegExCount++ + } + }); + // checking for duplicates + initialChildInputArrayLength = childInputArray.length; + if (emptyChildInputCount == 0 && initialChildInputArrayLength > 1) { + for (m = 0; m < (initialChildInputArrayLength - 1); m++) { + poppedChildInput = childInputArray.pop(); + for (n = 0; n < childInputArray.length; n++) { + if (poppedChildInput == childInputArray[n]) { + duplicatesExist = true; + break; + } + } + if (duplicatesExist) { + break; + } + } + } + // updating validationStatus + if (emptyChildInputCount > 0) { + // if empty child inputs are present + validationStatus = { + "error": true, + "subErrorMsg": "One or more AirPlay Destination fields are empty.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } else if (invalidAgainstRegExCount > 0) { + // if invalidAgainstRegEx inputs are present + validationStatus = { + "error": true, + "subErrorMsg": "One or more AirPlay Destination fields " + + "do not fulfill expected format.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } else if (duplicatesExist) { + // if duplicate input is present + validationStatus = { + "error": true, + "subErrorMsg": "Duplicate values exist with AirPlay Destinations.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } + } + } + + // at-last, if the value of continueToCheckNextInputs is still true + // this means that no error is found + if (continueToCheckNextInputs) { + validationStatus = { + "error": false, + "okFeature": operation + }; + } + + // updating validationStatusArray with validationStatus + validationStatusArray.push(validationStatus); + } + // Validating LDAP + if ($.inArray(iosOperationConstants["LDAP_OPERATION_CODE"], configuredOperations) != -1) { + // if LDAP is configured + operation = iosOperationConstants["LDAP_OPERATION"]; + // initializing continueToCheckNextInputs to true + continueToCheckNextInputs = true; + + var ldapAccountHostname = $("input#ldap-account-hostname").val(); + if (!ldapAccountHostname) { + validationStatus = { + "error": true, + "subErrorMsg": "LDAP Account Hostname URL is empty. You cannot proceed.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } + + if (continueToCheckNextInputs) { + var ldapSearchSettingsGridChildInputs = "div#ldap-search-settings .child-input"; + if ($(ldapSearchSettingsGridChildInputs).length > 0) { + childInputCount = 0; + childInputArray = []; + emptyChildInputCount = 0; + duplicatesExist = false; + // looping through each child input + $(ldapSearchSettingsGridChildInputs).each(function () { + childInputCount++; + if (childInputCount % 3 == 2) { + // if child input is of second column + childInput = $(this).find("option:selected").attr("value"); + stringPair = ""; + stringPair += (childInput + " "); + } else if (childInputCount % 3 == 0) { + // if child input is of third column + childInput = $(this).val(); + stringPair += childInput; + childInputArray.push(stringPair); + // updating emptyChildInputCount + if (!childInput) { + // if child input field is empty + emptyChildInputCount++; + } + } + }); + // checking for duplicates + initialChildInputArrayLength = childInputArray.length; + if (emptyChildInputCount == 0 && initialChildInputArrayLength > 1) { + for (m = 0; m < (initialChildInputArrayLength - 1); m++) { + poppedChildInput = childInputArray.pop(); + for (n = 0; n < childInputArray.length; n++) { + if (poppedChildInput == childInputArray[n]) { + duplicatesExist = true; + break; + } + } + if (duplicatesExist) { + break; + } + } + } + // updating validationStatus + if (emptyChildInputCount > 0) { + // if empty child inputs are present + validationStatus = { + "error": true, + "subErrorMsg": "One or more Search Setting Scope fields are empty.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } else if (duplicatesExist) { + // if duplicate input is present + validationStatus = { + "error": true, + "subErrorMsg": "Duplicate values exist with " + + "Search Setting Search Base and Scope pairs.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } + } + } + + // at-last, if the value of continueToCheckNextInputs is still true + // this means that no error is found + if (continueToCheckNextInputs) { + validationStatus = { + "error": false, + "okFeature": operation + }; + } + + // updating validationStatusArray with validationStatus + validationStatusArray.push(validationStatus); + } + // Validating CALENDAR + if ($.inArray(iosOperationConstants["CALENDAR_OPERATION_CODE"], configuredOperations) != -1) { + // if CALENDAR is configured + operation = iosOperationConstants["CALENDAR_OPERATION"]; + // initializing continueToCheckNextInputs to true + continueToCheckNextInputs = true; + + var calendarAccountHostname = $("input#calendar-account-hostname").val(); + if (!calendarAccountHostname) { + validationStatus = { + "error": true, + "subErrorMsg": "Account Hostname URL is empty. You cannot proceed.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } + + if (continueToCheckNextInputs) { + var calendarAccountPort = $("input#calendar-account-port").val(); + if (!calendarAccountPort) { + validationStatus = { + "error": true, + "subErrorMsg": "Account Port is empty. You cannot proceed.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } else if (!$.isNumeric(calendarAccountPort)) { + validationStatus = { + "error": true, + "subErrorMsg": "Account Port requires a number input.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } else if (!inputIsValidAgainstRange(calendarAccountPort, 0, 65535)) { + validationStatus = { + "error": true, + "subErrorMsg": "Account Port is not within the range " + + "of valid port numbers.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } + } + + // at-last, if the value of continueToCheckNextInputs is still true + // this means that no error is found + if (continueToCheckNextInputs) { + validationStatus = { + "error": false, + "okFeature": operation + }; + } + + // updating validationStatusArray with validationStatus + validationStatusArray.push(validationStatus); + } + // Validating CALENDAR_SUBSCRIPTION + if ($.inArray(iosOperationConstants["CALENDAR_SUBSCRIPTION_OPERATION_CODE"], configuredOperations) != -1) { + // if CALENDAR_SUBSCRIPTION is configured + operation = iosOperationConstants["CALENDAR_SUBSCRIPTION_OPERATION"]; + // initializing continueToCheckNextInputs to true + continueToCheckNextInputs = true; + + var calendarSubscriptionHostname = $("input#calendar-subscription-hostname").val(); + if (!calendarSubscriptionHostname) { + validationStatus = { + "error": true, + "subErrorMsg": "Account Hostname URL is empty. You cannot proceed.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } + + // at-last, if the value of continueToCheckNextInputs is still true + // this means that no error is found + if (continueToCheckNextInputs) { + validationStatus = { + "error": false, + "okFeature": operation + }; + } + + // updating validationStatusArray with validationStatus + validationStatusArray.push(validationStatus); + } + // Validating APN + if ($.inArray(iosOperationConstants["APN_OPERATION_CODE"], configuredOperations) != -1) { + // if APN is configured + operation = iosOperationConstants["APN_OPERATION"]; + // initializing continueToCheckNextInputs to true + continueToCheckNextInputs = true; + + var apnConfigurationsGridChildInputs = "div#apn-configurations .child-input"; + if ($(apnConfigurationsGridChildInputs).length == 0) { + validationStatus = { + "error": true, + "subErrorMsg": "APN Settings have zero configurations attached.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } else if ($(apnConfigurationsGridChildInputs).length > 0) { + childInputCount = 0; + childInputArray = []; + // checking empty APN field count + emptyChildInputCount = 0; + duplicatesExist = false; + // looping through each child input + $(apnConfigurationsGridChildInputs).each(function () { + childInputCount++; + //if (childInputCount % 5 == 1) { + // if child input is of first column + childInput = $(this).val(); + childInputArray.push(childInput); + // updating emptyChildInputCount + if (!childInput) { + // if child input field is empty + emptyChildInputCount++; + } + //} + }); + // checking for duplicates + initialChildInputArrayLength = childInputArray.length; + if (emptyChildInputCount == 0 && initialChildInputArrayLength > 1) { + for (m = 0; m < (initialChildInputArrayLength - 1); m++) { + poppedChildInput = childInputArray.pop(); + for (n = 0; n < childInputArray.length; n++) { + if (poppedChildInput == childInputArray[n]) { + duplicatesExist = true; + break; + } + } + if (duplicatesExist) { + break; + } + } + } + // updating validationStatus + if (emptyChildInputCount > 0) { + // if empty child inputs are present + validationStatus = { + "error": true, + "subErrorMsg": "One or more APN fields of Configurations are empty.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } else if (duplicatesExist) { + // if duplicate input is present + validationStatus = { + "error": true, + "subErrorMsg": "Duplicate values exist with " + + "APN fields of Configurations.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } + } + + // at-last, if the value of continueToCheckNextInputs is still true + // this means that no error is found + if (continueToCheckNextInputs) { + validationStatus = { + "error": false, + "okFeature": operation + }; + } + + // updating validationStatusArray with validationStatus + validationStatusArray.push(validationStatus); + } + // Validating CELLULAR + if ($.inArray(iosOperationConstants["CELLULAR_OPERATION_CODE"], configuredOperations) != -1) { + // if CELLULAR is configured + operation = iosOperationConstants["CELLULAR_OPERATION"]; + // initializing continueToCheckNextInputs to true + continueToCheckNextInputs = true; + + var cellularAttachAPNName = $("input#cellular-attach-apn-name").val(); + if (!cellularAttachAPNName) { + validationStatus = { + "error": true, + "subErrorMsg": "Cellular Configuration Name is empty. You cannot proceed.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } + + if (continueToCheckNextInputs) { + var cellularAPNConfigurationsGridChildInputs = "div#cellular-apn-configurations .child-input"; + if ($(cellularAPNConfigurationsGridChildInputs).length > 0) { + childInputCount = 0; + childInputArray = []; + // checking empty APN field count + emptyChildInputCount = 0; + duplicatesExist = false; + // looping through each child input + $(cellularAPNConfigurationsGridChildInputs).each(function () { + childInputCount++; + if (childInputCount % 6 == 1) { + // if child input is of first column + childInput = $(this).val(); + childInputArray.push(childInput); + // updating emptyChildInputCount + if (!childInput) { + // if child input field is empty + emptyChildInputCount++; + } + } + }); + // checking for duplicates + initialChildInputArrayLength = childInputArray.length; + if (emptyChildInputCount == 0 && initialChildInputArrayLength > 1) { + for (m = 0; m < (initialChildInputArrayLength - 1); m++) { + poppedChildInput = childInputArray.pop(); + for (n = 0; n < childInputArray.length; n++) { + if (poppedChildInput == childInputArray[n]) { + duplicatesExist = true; + break; + } + } + if (duplicatesExist) { + break; + } + } + } + // updating validationStatus + if (emptyChildInputCount > 0) { + // if empty child inputs are present + validationStatus = { + "error": true, + "subErrorMsg": "One or more APN fields of APN Configurations are empty.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } else if (duplicatesExist) { + // if duplicate input is present + validationStatus = { + "error": true, + "subErrorMsg": "Duplicate values exist with " + + "APN fields of APN Configurations.", + "erroneousFeature": operation + }; + continueToCheckNextInputs = false; + } + } + } + + // at-last, if the value of continueToCheckNextInputs is still true + // this means that no error is found + if (continueToCheckNextInputs) { + validationStatus = { + "error": false, + "okFeature": operation + }; + } + + // updating validationStatusArray with validationStatus + validationStatusArray.push(validationStatus); + } + } + } + // ending validation process + + // start taking specific notifying actions upon validation + var wizardIsToBeContinued; + var errorCount = 0; + var mainErrorMsgWrapper, mainErrorMsg, + subErrorMsgWrapper, subErrorMsg, subErrorIcon, subOkIcon, featureConfiguredIcon; + var i; + for (i = 0; i < validationStatusArray.length; i++) { + validationStatus = validationStatusArray[i]; + if (validationStatus["error"]) { + errorCount++; + if (validationStatus["mainErrorMsg"]) { + mainErrorMsgWrapper = "#policy-profile-main-error-msg"; + mainErrorMsg = mainErrorMsgWrapper + " span"; + $(mainErrorMsg).text(validationStatus["mainErrorMsg"]); + $(mainErrorMsgWrapper).removeClass("hidden"); + } else if (validationStatus["subErrorMsg"]) { + subErrorMsgWrapper = "#" + validationStatus["erroneousFeature"] + "-feature-error-msg"; + subErrorMsg = subErrorMsgWrapper + " span"; + subErrorIcon = "#" + validationStatus["erroneousFeature"] + "-error"; + subOkIcon = "#" + validationStatus["erroneousFeature"] + "-ok"; + featureConfiguredIcon = "#" + validationStatus["erroneousFeature"] + "-configured"; + // hiding featureConfiguredState as the first step + if (!$(featureConfiguredIcon).hasClass("hidden")) { + $(featureConfiguredIcon).addClass("hidden"); + } + // updating error state and corresponding messages + $(subErrorMsg).text(validationStatus["subErrorMsg"]); + if ($(subErrorMsgWrapper).hasClass("hidden")) { + $(subErrorMsgWrapper).removeClass("hidden"); + } + if (!$(subOkIcon).hasClass("hidden")) { + $(subOkIcon).addClass("hidden"); + } + if ($(subErrorIcon).hasClass("hidden")) { + $(subErrorIcon).removeClass("hidden"); + } + } + } else { + if (validationStatus["okFeature"]) { + subErrorMsgWrapper = "#" + validationStatus["okFeature"] + "-feature-error-msg"; + subErrorIcon = "#" + validationStatus["okFeature"] + "-error"; + subOkIcon = "#" + validationStatus["okFeature"] + "-ok"; + featureConfiguredIcon = "#" + validationStatus["okFeature"] + "-configured"; + // hiding featureConfiguredState as the first step + if (!$(featureConfiguredIcon).hasClass("hidden")) { + $(featureConfiguredIcon).addClass("hidden"); + } + // updating success state and corresponding messages + if (!$(subErrorMsgWrapper).hasClass("hidden")) { + $(subErrorMsgWrapper).addClass("hidden"); + } + if (!$(subErrorIcon).hasClass("hidden")) { + $(subErrorIcon).addClass("hidden"); + } + if ($(subOkIcon).hasClass("hidden")) { + $(subOkIcon).removeClass("hidden"); + } + } + } + } + + wizardIsToBeContinued = (errorCount == 0); + return wizardIsToBeContinued; +}; + +stepForwardFrom["policy-profile"] = function () { + policy["profile"] = operationModule.generateProfile(policy["platform"], configuredOperations); + // updating next-page wizard title with selected platform + $("#policy-criteria-page-wizard-title").text("ADD " + policy["platform"] + " POLICY"); + // updating ownership type options according to platform + if (policy["platform"] == platformTypeConstants["IOS"] || + policy["platform"] == platformTypeConstants["WINDOWS"]) { + var ownershipTypeSelectOptions = $("#ownership-input"); + ownershipTypeSelectOptions.empty(); + ownershipTypeSelectOptions.append($(""). + attr("value", "BYOD").text("BYOD (Bring Your Own Device)")); + ownershipTypeSelectOptions.attr("disabled", true); + } +}; + +stepBackFrom["policy-profile"] = function () { + // reinitialize configuredOperations + configuredOperations = []; + // clearing already-loaded platform specific hidden-operations html content from the relevant div + // so that, the wrong content would not be shown at the first glance, in case + // the user selects a different platform + $(".wr-advance-operations").html( + "
      " + + "
      " + + "" + + "Loading Platform Features . . ." + + "
      " + + "
      " + + "
      " + ); +}; + +stepForwardFrom["policy-criteria"] = function () { + $("input[type='radio'].select-users-radio").each(function () { + if ($(this).is(':radio')) { + if ($(this).is(":checked")) { + if ($(this).attr("id") == "users-radio-btn") { + policy["selectedUsers"] = $("#users-input").val(); + } else if ($(this).attr("id") == "user-roles-radio-btn") { + policy["selectedUserRoles"] = $("#user-roles-input").val(); + } + } + } + }); + policy["selectedNonCompliantAction"] = $("#action-input").find(":selected").data("action"); + policy["selectedOwnership"] = $("#ownership-input").val(); + //updating next-page wizard title with selected platform + $("#policy-naming-page-wizard-title").text("ADD " + policy["platform"] + " POLICY"); +}; + +/** + * Checks if provided input is valid against provided length range. + * + * @param input Alphanumeric or non-alphanumeric input + * @param minLength Minimum Required Length + * @param maxLength Maximum Required Length + * @returns {boolean} Returns true if input matches the provided minimum length and maximum length + */ +var inputIsValidAgainstLength = function (input, minLength, maxLength) { + var length = input.length; + return (length == minLength || (length > minLength && length < maxLength) || length == maxLength); +}; + +validateStep["policy-criteria"] = function () { + var validationStatus = {}; + var selectedAssignees; + var selectedField = "Role(s)"; + + $("input[type='radio'].select-users-radio").each(function () { + if ($(this).is(":checked")) { + if ($(this).attr("id") == "users-radio-btn") { + selectedAssignees = $("#users-input").val(); + selectedField = "User(s)"; + } else if ($(this).attr("id") == "user-roles-radio-btn") { + selectedAssignees = $("#user-roles-input").val(); + } + return false; + } + }); + + if (selectedAssignees) { + validationStatus["error"] = false; + } else { + validationStatus["error"] = true; + validationStatus["mainErrorMsg"] = selectedField + " is a required field. It cannot be empty"; + } + + var wizardIsToBeContinued; + if (validationStatus["error"]) { + wizardIsToBeContinued = false; + var mainErrorMsgWrapper = "#policy-criteria-main-error-msg"; + var mainErrorMsg = mainErrorMsgWrapper + " span"; + $(mainErrorMsg).text(validationStatus["mainErrorMsg"]); + $(mainErrorMsgWrapper).removeClass("hidden"); + } else { + wizardIsToBeContinued = true; + } + + return wizardIsToBeContinued; +}; + +validateStep["policy-naming"] = function () { + var validationStatus = {}; + + // taking values of inputs to be validated + var policyName = $("input#policy-name-input").val(); + // starting validation process and updating validationStatus + if (!policyName) { + validationStatus["error"] = true; + validationStatus["mainErrorMsg"] = "Policy name is empty. You cannot proceed."; + } else if (!inputIsValidAgainstLength(policyName, 1, 30)) { + validationStatus["error"] = true; + validationStatus["mainErrorMsg"] = + "Policy name exceeds maximum allowed length."; + } else { + validationStatus["error"] = false; + } + // ending validation process + + // start taking specific actions upon validation + var wizardIsToBeContinued; + if (validationStatus["error"]) { + wizardIsToBeContinued = false; + var mainErrorMsgWrapper = "#policy-naming-main-error-msg"; + var mainErrorMsg = mainErrorMsgWrapper + " span"; + $(mainErrorMsg).text(validationStatus["mainErrorMsg"]); + $(mainErrorMsgWrapper).removeClass("hidden"); + } else { + wizardIsToBeContinued = true; + } + + return wizardIsToBeContinued; +}; + +validateStep["policy-platform"] = function () { + return false; +}; + +validateStep["policy-naming-publish"] = function () { + var validationStatus = {}; + + // taking values of inputs to be validated + var policyName = $("input#policy-name-input").val(); + // starting validation process and updating validationStatus + if (!policyName) { + validationStatus["error"] = true; + validationStatus["mainErrorMsg"] = "Policy name is empty. You cannot proceed."; + } else if (!inputIsValidAgainstLength(policyName, 1, 30)) { + validationStatus["error"] = true; + validationStatus["mainErrorMsg"] = + "Policy name exceeds maximum allowed length."; + } else { + validationStatus["error"] = false; + } + // ending validation process + + // start taking specific actions upon validation + var wizardIsToBeContinued; + if (validationStatus["error"]) { + wizardIsToBeContinued = false; + var mainErrorMsgWrapper = "#policy-naming-main-error-msg"; + var mainErrorMsg = mainErrorMsgWrapper + " span"; + $(mainErrorMsg).text(validationStatus["mainErrorMsg"]); + $(mainErrorMsgWrapper).removeClass("hidden"); + } else { + wizardIsToBeContinued = true; + } + + return wizardIsToBeContinued; +}; + +stepForwardFrom["policy-naming-publish"] = function () { + policy["policyName"] = $("#policy-name-input").val(); + policy["description"] = $("#policy-description-input").val(); + //All data is collected. Policy can now be updated. + savePolicy(policy, "/devicemgt_admin/policies/active-policy"); +}; +stepForwardFrom["policy-naming"] = function () { + policy["policyName"] = $("#policy-name-input").val(); + policy["description"] = $("#policy-description-input").val(); + //All data is collected. Policy can now be updated. + savePolicy(policy, "/devicemgt_admin/policies/inactive-policy"); +}; + +var savePolicy = function (policy, serviceURL) { + var profilePayloads = []; + // traverses key by key in policy["profile"] + var key; + for (key in policy["profile"]) { + if (policy["platformId"] == platformIds["WINDOWS"] && + key == windowsOperationConstants["PASSCODE_POLICY_OPERATION_CODE"]) { + policy["profile"][key].enablePassword = true; + } + if (policy["profile"].hasOwnProperty(key)) { + profilePayloads.push({ + "featureCode": key, + "deviceTypeId": policy["platformId"], + "content": policy["profile"][key] + }); + } + } + + $.each(profilePayloads, function (i, item) { + $.each(item.content, function (key, value) { + if (!value) { + item.content[key] = null; + } + }); + }); + + var payload = { + "policyName": policy["policyName"], + "description": policy["description"], + "compliance": policy["selectedNonCompliantAction"], + "ownershipType": policy["selectedOwnership"], + "profile": { + "profileName": policy["policyName"], + "deviceType": { + "id": policy["platformId"] + }, + "profileFeaturesList": profilePayloads + } + }; + + if (policy["selectedUsers"]) { + payload["users"] = policy["selectedUsers"]; + } else if (policy["selectedUserRoles"]) { + payload["roles"] = policy["selectedUserRoles"]; + } else { + payload["users"] = []; + payload["roles"] = []; + } + + invokerUtil.post( + serviceURL, + payload, + function () { + $(".add-policy").addClass("hidden"); + $(".policy-naming").addClass("hidden"); + $(".policy-message").removeClass("hidden"); + }, + function (data) { + } + ); +}; + +// Start of HTML embedded invoke methods +var showAdvanceOperation = function (operation, button) { + $(button).addClass('selected'); + $(button).siblings().removeClass('selected'); + var hiddenOperation = ".wr-hidden-operations-content > div"; + $(hiddenOperation + '[data-operation="' + operation + '"]').show(); + $(hiddenOperation + '[data-operation="' + operation + '"]').siblings().hide(); +}; + +/** + * Method to slide down a provided pane upon provided value set. + * + * @param selectElement Select HTML Element to consider + * @param paneID HTML ID of div element to slide down + * @param valueSet Applicable Value Set + */ +var slideDownPaneAgainstValueSet = function (selectElement, paneID, valueSet) { + var selectedValueOnChange = $(selectElement).find("option:selected").val(); + var i, slideDownVotes = 0; + for (i = 0; i < valueSet.length; i++) { + if (selectedValueOnChange == valueSet[i]) { + slideDownVotes++; + } + } + var paneSelector = "#" + paneID; + if (slideDownVotes > 0) { + if (!$(paneSelector).hasClass("expanded")) { + $(paneSelector).addClass("expanded"); + } + $(paneSelector).slideDown(); + } else { + if ($(paneSelector).hasClass("expanded")) { + $(paneSelector).removeClass("expanded"); + } + $(paneSelector).slideUp(); + /** now follows the code to reinitialize all inputs of the slidable pane */ + // reinitializing input fields into the defaults + $(paneSelector + " input").each( + function () { + if ($(this).is("input:text")) { + $(this).val($(this).data("default")); + } else if ($(this).is("input:password")) { + $(this).val(""); + } else if ($(this).is("input:checkbox")) { + $(this).prop("checked", $(this).data("default")); + // if this checkbox is the parent input of a grouped-input + if ($(this).hasClass("parent-input")) { + var groupedInput = $(this).parent().parent().parent(); + updateGroupedInputVisibility(groupedInput); + } + } + } + ); + // reinitializing select fields into the defaults + $(paneSelector + " select").each( + function () { + var defaultOption = $(this).data("default"); + $("option:eq(" + defaultOption + ")", this).prop("selected", "selected"); + } + ); + // collapsing expanded-panes (upon the selection of html-select-options) if any + $(paneSelector + " .expanded").each( + function () { + if ($(this).hasClass("expanded")) { + $(this).removeClass("expanded"); + } + $(this).slideUp(); + } + ); + // removing all entries of grid-input elements if exist + $(paneSelector + " .grouped-array-input").each( + function () { + var gridInputs = $(this).find("[data-add-form-clone]"); + if (gridInputs.length > 0) { + gridInputs.remove(); + } + var helpTexts = $(this).find("[data-help-text=add-form]"); + if (helpTexts.length > 0) { + helpTexts.show(); + } + } + ); + } +}; +// End of HTML embedded invoke methods + + +// Start of functions related to grid-input-view + +/** + * Method to set count id to cloned elements. + * @param {object} addFormContainer + */ +var setId = function (addFormContainer) { + $(addFormContainer).find("[data-add-form-clone]").each(function (i) { + $(this).attr("id", $(this).attr("data-add-form-clone").slice(1) + "-" + (i + 1)); + if ($(this).find(".index").length > 0) { + $(this).find(".index").html(i + 1); + } + }); +}; + +/** + * Method to set count id to cloned elements. + * @param {object} addFormContainer + */ +var showHideHelpText = function (addFormContainer) { + var helpText = "[data-help-text=add-form]"; + if ($(addFormContainer).find("[data-add-form-clone]").length > 0) { + $(addFormContainer).find(helpText).hide(); + } else { + $(addFormContainer).find(helpText).show(); + } +}; + +function formatRepo(user) { + if (user.loading) { + return user.text; + } + if (!user.username) { + return; + } + var markup = '
      ' + + '
      ' + + '
      ' + + '
      ' + user.username + '
      '; + if (user.firstname) { + markup += '
      ' + user.firstname + '
      '; + } + if (user.emailAddress) { + markup += '
      ' + user.emailAddress + '
      '; + } + markup += '
      '; + return markup; +} + +function formatRepoSelection(user) { + return user.username || user.text; +} + +function promptErrorPolicyPlatform(errorMsg) { + var mainErrorMsgWrapper = "#policy-platform-main-error-msg"; + var mainErrorMsg = mainErrorMsgWrapper + " span"; + $(mainErrorMsg).text(errorMsg); + $(mainErrorMsgWrapper).removeClass("hidden"); +} + +// End of functions related to grid-input-view + + +$(document).ready(function () { + $("#users-input").select2({ + multiple: true, + tags: false, + ajax: { + url: window.location.origin + "/devicemgt/api/invoker/execute/", + method: "POST", + dataType: 'json', + delay: 250, + id: function (user) { + return user.username; + }, + data: function (params) { + var postData = {}; + postData.actionMethod = "GET"; + postData.actionUrl = "/devicemgt_admin/users"; + postData.actionPayload = JSON.stringify({ + q: params.term, // search term + page: params.page + }); + + return JSON.stringify(postData); + }, + processResults: function (data, page) { + var newData = []; + $.each(data.responseContent, function (index, value) { + value.id = value.username; + newData.push(value); + }); + return { + results: newData + }; + }, + cache: true + }, + escapeMarkup: function (markup) { + return markup; + }, // let our custom formatter work + minimumInputLength: 1, + templateResult: formatRepo, // omitted for brevity, see the source of this page + templateSelection: formatRepoSelection // omitted for brevity, see the source of this page + }); + + $("#loading-content").remove(); + $(".policy-platform").removeClass("hidden"); + // Adding initial state of wizard-steps. + $("#policy-profile-wizard-steps").html($(".wr-steps").html()); + + policy["platform"] = $("#platform").data("platform"); + policy["platformId"] = $("#platform").data("platform-id"); + // updating next-page wizard title with selected platform + $("#policy-profile-page-wizard-title").text("ADD " + policy["platform"] + " POLICY"); + + var deviceType = policy["platform"]; + var hiddenOperationsByDeviceType = $("#hidden-operations-" + deviceType); + var hiddenOperationsByDeviceTypeCacheKey = deviceType + "HiddenOperations"; + var hiddenOperationsByDeviceTypeSrc = hiddenOperationsByDeviceType.attr("src"); + + setTimeout( + function () { + $.template(hiddenOperationsByDeviceTypeCacheKey, hiddenOperationsByDeviceTypeSrc, function (template) { + var content = template(); + $(".wr-advance-operations").html(content); + $(".wr-advance-operations li.grouped-input").each(function () { + updateGroupedInputVisibility(this); + }); + }); + }, + 250 // time delayed for the execution of above function, 250 milliseconds + ); + + $("select.select2[multiple=multiple]").select2({ + "tags": false + }); + + $("#users-select-field").hide(); + $("#user-roles-select-field").show(); + + $("input[type='radio'].select-users-radio").change(function () { + if ($("#users-radio-btn").is(":checked")) { + $("#user-roles-select-field").hide(); + $("#users-select-field").show(); + } + if ($("#user-roles-radio-btn").is(":checked")) { + $("#users-select-field").hide(); + $("#user-roles-select-field").show(); + } + }); + + // Support for special input type "ANY" on user(s) & user-role(s) selection + $("#user-roles-input").select2({ + "tags": false + }).on("select2:select", function (e) { + if (e.params.data.id == "ANY") { + $(this).val("ANY").trigger("change"); + } else { + $("option[value=ANY]", this).prop("selected", false).parent().trigger("change"); + } + }); + + // Maintains an array of configured features of the profile + var advanceOperations = ".wr-advance-operations"; + $(advanceOperations).on("click", ".wr-input-control.switch", function (event) { + var operationCode = $(this).parents(".operation-data").data("operation-code"); + var operation = $(this).parents(".operation-data").data("operation"); + var operationDataWrapper = $(this).data("target"); + // prevents event bubbling by figuring out what element it's being called from. + if (event.target.tagName == "INPUT") { + var featureConfiguredIcon; + if ($("input[type='checkbox']", this).is(":checked")) { + configuredOperations.push(operationCode); + // when a feature is enabled, if "zero-configured-features" msg is available, hide that. + var zeroConfiguredOperationsErrorMsg = "#policy-profile-main-error-msg"; + if (!$(zeroConfiguredOperationsErrorMsg).hasClass("hidden")) { + $(zeroConfiguredOperationsErrorMsg).addClass("hidden"); + } + // add configured-state-icon to the feature + featureConfiguredIcon = "#" + operation + "-configured"; + if ($(featureConfiguredIcon).hasClass("hidden")) { + $(featureConfiguredIcon).removeClass("hidden"); + } + } else { + //splicing the array if operation is present. + var index = $.inArray(operationCode, configuredOperations); + if (index != -1) { + configuredOperations.splice(index, 1); + } + // when a feature is disabled, clearing all its current configured, error or success states + var subErrorMsgWrapper = "#" + operation + "-feature-error-msg"; + var subErrorIcon = "#" + operation + "-error"; + var subOkIcon = "#" + operation + "-ok"; + featureConfiguredIcon = "#" + operation + "-configured"; + + if (!$(subErrorMsgWrapper).hasClass("hidden")) { + $(subErrorMsgWrapper).addClass("hidden"); + } + if (!$(subErrorIcon).hasClass("hidden")) { + $(subErrorIcon).addClass("hidden"); + } + if (!$(subOkIcon).hasClass("hidden")) { + $(subOkIcon).addClass("hidden"); + } + if (!$(featureConfiguredIcon).hasClass("hidden")) { + $(featureConfiguredIcon).addClass("hidden"); + } + // reinitializing input fields into the defaults + $(operationDataWrapper + " input").each( + function () { + if ($(this).is("input:text")) { + $(this).val($(this).data("default")); + } else if ($(this).is("input:password")) { + $(this).val(""); + } else if ($(this).is("input:checkbox")) { + $(this).prop("checked", $(this).data("default")); + // if this checkbox is the parent input of a grouped-input + if ($(this).hasClass("parent-input")) { + var groupedInput = $(this).parent().parent().parent(); + updateGroupedInputVisibility(groupedInput); + } + } + } + ); + // reinitializing select fields into the defaults + $(operationDataWrapper + " select").each( + function () { + var defaultOption = $(this).data("default"); + $("option:eq(" + defaultOption + ")", this).prop("selected", "selected"); + } + ); + // collapsing expanded-panes (upon the selection of html-select-options) if any + $(operationDataWrapper + " .expanded").each( + function () { + if ($(this).hasClass("expanded")) { + $(this).removeClass("expanded"); + } + $(this).slideUp(); + } + ); + // removing all entries of grid-input elements if exist + $(operationDataWrapper + " .grouped-array-input").each( + function () { + var gridInputs = $(this).find("[data-add-form-clone]"); + if (gridInputs.length > 0) { + gridInputs.remove(); + } + var helpTexts = $(this).find("[data-help-text=add-form]"); + if (helpTexts.length > 0) { + helpTexts.show(); + } + } + ); + } + } + }); + + // adding support for cloning multiple profiles per feature with cloneable class definitions + $(advanceOperations).on("click", ".multi-view.add.enabled", function () { + // get a copy of .cloneable and create new .cloned div element + var cloned = "

      " + $(".cloneable", $(this).parent().parent()).html() + "
      "; + // append newly created .cloned div element to panel-body + $(this).parent().parent().append(cloned); + // enable remove action of newly cloned div element + $(".cloned", $(this).parent().parent()).each( + function () { + if ($(".multi-view.remove", this).hasClass("disabled")) { + $(".multi-view.remove", this).removeClass("disabled"); + } + if (!$(".multi-view.remove", this).hasClass("enabled")) { + $(".multi-view.remove", this).addClass("enabled"); + } + } + ); + }); + + $(advanceOperations).on("click", ".multi-view.remove.enabled", function () { + $(this).parent().remove(); + }); + + // enabling or disabling grouped-input based on the status of a parent check-box + $(advanceOperations).on("click", ".grouped-input", function () { + updateGroupedInputVisibility(this); + }); + + // add form entry click function for grid inputs + $(advanceOperations).on("click", "[data-click-event=add-form]", function () { + var addFormContainer = $("[data-add-form-container=" + $(this).attr("href") + "]"); + var clonedForm = $("[data-add-form=" + $(this).attr("href") + "]").clone(). + find("[data-add-form-element=clone]").attr("data-add-form-clone", $(this).attr("href")); + + // adding class .child-input to capture text-input-array-values + $("input, select", clonedForm).addClass("child-input"); + + $(addFormContainer).append(clonedForm); + setId(addFormContainer); + showHideHelpText(addFormContainer); + }); + + // remove form entry click function for grid inputs + $(advanceOperations).on("click", "[data-click-event=remove-form]", function () { + var addFormContainer = $("[data-add-form-container=" + $(this).attr("href") + "]"); + + $(this).closest("[data-add-form-element=clone]").remove(); + setId(addFormContainer); + showHideHelpText(addFormContainer); + }); + + $(".wizard-stepper").click(function () { + // button clicked here can be either a continue button or a back button. + var currentStep = $(this).data("current"); + var validationIsRequired = $(this).data("validate"); + var wizardIsToBeContinued; + + if (validationIsRequired) { + wizardIsToBeContinued = validateStep[currentStep](); + } else { + wizardIsToBeContinued = true; + } + + if (wizardIsToBeContinued) { + // When moving back and forth, following code segment will + // remove if there are any visible error-messages. + var errorMsgWrappers = ".alert.alert-danger"; + $(errorMsgWrappers).each( + function () { + if (!$(this).hasClass("hidden")) { + $(this).addClass("hidden"); + } + } + ); + + var nextStep = $(this).data("next"); + var isBackBtn = $(this).data("is-back-btn"); + + // if current button is a continuation... + if (!isBackBtn) { + // initiate stepForwardFrom[*] functions to gather form data. + if (stepForwardFrom[currentStep]) { + stepForwardFrom[currentStep](this); + } + } else { + // initiate stepBackFrom[*] functions to rollback. + if (stepBackFrom[currentStep]) { + stepBackFrom[currentStep](); + } + } + + // following step occurs only at the last stage of the wizard. + if (!nextStep) { + window.location.href = $(this).data("direct"); + } + + // updating next wizard step as current. + $(".itm-wiz").each(function () { + var step = $(this).data("step"); + if (step == nextStep) { + $(this).addClass("itm-wiz-current"); + } else { + $(this).removeClass("itm-wiz-current"); + } + }); + + // adding next update of wizard-steps. + $("#" + nextStep + "-wizard-steps").html($(".wr-steps").html()); + + // hiding current section of the wizard and showing next section. + $("." + currentStep).addClass("hidden"); + $("." + nextStep).removeClass("hidden"); + } + }); +}); \ No newline at end of file diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/src/main/resources/jaggeryapps/devicemgt/app/units/mdm.unit.policy.wizard/public/templates/hidden-operations-android.hbs b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/src/main/resources/jaggeryapps/devicemgt/app/units/mdm.unit.policy.wizard/public/templates/hidden-operations-android.hbs new file mode 100644 index 0000000000..094f7f73fb --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/src/main/resources/jaggeryapps/devicemgt/app/units/mdm.unit.policy.wizard/public/templates/hidden-operations-android.hbs @@ -0,0 +1,458 @@ +
      + + +
      + +
      +
      + +
      + + + +
      + +
      + +
      + +
      + +
      + + +
      + +
      + + +
      + +
      + + +
      + +
      + + +
      + +
      + + +
      +
      +
      +
      + + + +
      +
      + +
      + + Un-check following checkbox in case you need to disable camera. +
      +
      +
      + +
      +
      +
      +
      +
      + + + +
      +
      + +
      + + Un-check following checkbox in case you do not need the device to be encrypted. +
      +
      +
      + +
      +
      +
      +
      +
      + + + +
      +
      + +
      + + + + + + + + + + + + + + + + + Please note that * sign represents required fields of data. +
      +
      + +
      + + + +
      +
      + + +
      + +
      +
      +
      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      +
      \ No newline at end of file diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/src/main/resources/jaggeryapps/devicemgt/app/units/mdm.unit.policy.wizard/public/templates/hidden-operations-ios.hbs b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/src/main/resources/jaggeryapps/devicemgt/app/units/mdm.unit.policy.wizard/public/templates/hidden-operations-ios.hbs new file mode 100644 index 0000000000..113ce1370f --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/src/main/resources/jaggeryapps/devicemgt/app/units/mdm.unit.policy.wizard/public/templates/hidden-operations-ios.hbs @@ -0,0 +1,2923 @@ +
      + + +
      + +
      +
      + +
      + + + +
      + +
      + +
      + +
      + +
      + +
      + +
      + + +
      + +
      + + +
      + +
      + + +
      + +
      + + +
      + +
      + + +
      + +
      + + +
      + +
      + + +
      +
      +
      +
      + + + +
      +
      + +
      + + +
      + + +
      + +
      + + +
      + +
      + +
      + +
      + +
      + +
      + +
      + +
      + +
      + +
      + + +
      + +
      + + +
      + + + +
      + + +
      + + + +
      + +
      + + + + + + + + + + + + + +
      No:Roaming Consortium OI
      + No entries added yet . +
      + + + + + + + + + +
      +
      + +
      + +
      + +
      + + + + + + + + + + + + + +
      No:NAI Realm Name
      + No entries added yet . +
      + + + + + + + + + +
      +
      + +
      + +
      + +
      + + + + + + + + + + + + + + +
      No:Mobile Country Code ( MCC )Mobile Network Code ( MNC )
      + No entries added yet . +
      + + + + + + + + + + +
      +
      +
      +
      +
      + + + +
      +
      + +
      + + + +
      + + +
      + +
      + + +
      + +
      + +
      + + +
      + +
      + +
      + + +
      + +
      + + +
      + +
      + +
      + +
      + +
      + +
      + +
      + +
      + + +
      + +
      + + +
      + +
      + +
      + +
      + +
      + +
      + Incoming Mail Settings : +
      + +
      + + +
      + +
      + +
      + +
      + + +
      + +
      + + +
      + +
      + + +
      + +
      + + +
      + +
      + Outgoing Mail Settings : +
      + +
      + + +
      + +
      + +
      + +
      + + +
      + +
      + + +
      + +
      + + +
      + +
      + +
      + +
      + + +
      + +
      +
      +
      + + + +
      +
      + +
      + + + +
      + +
      + + + + + + + + + + + + + + +
      No:Device NamePassword
      + No entries added yet . +
      + + + + + + + + + + +
      +
      + +
      + +
      + +
      + + + + + + + + + + + + + +
      No:Destination
      + No entries added yet . +
      + + + + + + + + + +
      +
      + +
      +
      +
      + + + +
      +
      + +
      + + + +
      + + +
      + +
      + + +
      + +
      + +
      + +
      + + +
      + +
      + + +
      + +
      + +
      + + + + + + + + + + + + + + + +
      No:DescriptionSearch BaseScope
      + No entries added yet . +
      + + + + + + + + + + + +
      +
      + +
      +
      +
      + + + +
      +
      + +
      + + + +
      + + +
      + +
      + + +
      + +
      + +
      + +
      + + +
      + +
      + + +
      + +
      + + +
      + +
      + + +
      + +
      +
      +
      + + + +
      +
      + +
      + + + +
      + + +
      + +
      + + +
      + +
      + +
      + +
      + + +
      + +
      + + +
      + +
      +
      +
      + + + +
      +
      + +
      + + + +
      + +
      + + + + + + + + + + + + + + + + + +
      No:APNUsernamePasswordProxyPort
      + No entries added yet . +
      + + + + + + + + + + + + + +
      +
      + +
      +
      +
      + + + +
      +
      + +
      + + + +
      + + +
      + +
      + + +
      + +
      + + +
      + +
      + + +
      + +
      + +
      + + + + + + + + + + + + + + + + + + +
      No:APNAuth.TypeUsernamePasswordProxyPort
      + No entries added yet . +
      + + + + + + + + + + + + + + +
      +
      + +
      +
      +
      + + + +
      +
      + +
      + + + + Restrictions on Device Functionality : +
      +
      +
        +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
          +
        • +
          + +
          +
        • +
        • +
          + +
          +
        • +
        • +
          + +
          +
        • +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      +
      +
      + Restrictions on Applications : +
      +
      +
        +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
          +
        • +
          + +
          +
        • +
        • +
          + +
          +
        • +
        • +
          + +
          +
        • +
        • +
          + +
          +
        • +
        • +
          + + +
          +
        • +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      • +
        + +
        +
      • +
      +
      + + + +
      +
      +
      + +
      +
      \ No newline at end of file diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/src/main/resources/jaggeryapps/devicemgt/app/units/mdm.unit.policy.wizard/public/templates/hidden-operations-windows.hbs b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/src/main/resources/jaggeryapps/devicemgt/app/units/mdm.unit.policy.wizard/public/templates/hidden-operations-windows.hbs new file mode 100644 index 0000000000..3b5bb3bef4 --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/src/main/resources/jaggeryapps/devicemgt/app/units/mdm.unit.policy.wizard/public/templates/hidden-operations-windows.hbs @@ -0,0 +1,460 @@ +
      + + +
      + +
      +
      + +
      + + + +
      + +
      + +
      + +
      + +
      + + +
      + +
      + + +
      + +
      + + +
      + +
      + + +
      + +
      + + +
      +
      +
      +
      + + + +
      +
      + +
      + + Un-check following checkbox in case you need to disable camera. +
      +
      +
      + +
      +
      +
      +
      +
      + + + +
      +
      + +
      + + Un-check following checkbox in case you need to disable storage-encryption. +
      +
      +
      + +
      +
      +
      +
      +
      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      +
      \ No newline at end of file diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/src/main/resources/jaggeryapps/devicemgt/app/units/mdm.unit.policy.wizard/wizard.hbs b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/src/main/resources/jaggeryapps/devicemgt/app/units/mdm.unit.policy.wizard/wizard.hbs new file mode 100644 index 0000000000..08e9ca77dd --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/src/main/resources/jaggeryapps/devicemgt/app/units/mdm.unit.policy.wizard/wizard.hbs @@ -0,0 +1,278 @@ + +
      +
      + + + + + + +
      + + + +
      +
      +

      ADD POLICY

      +
      +
      +
      +
      +
      +

      Step 2: Configure profile

      +
      + + +
      +
      +
      +       + +     + Loading platform features . . . +
      +
      +
      +
      + +
      +
      +
      +
      + + +
      + +{{#zone "bottomJs"}} + + + + {{js "js/policy-create.js"}} +{{/zone}} \ No newline at end of file diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/src/main/resources/jaggeryapps/devicemgt/app/units/mdm.unit.policy.wizard/wizard.js b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/src/main/resources/jaggeryapps/devicemgt/app/units/mdm.unit.policy.wizard/wizard.js new file mode 100644 index 0000000000..2a22006402 --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/src/main/resources/jaggeryapps/devicemgt/app/units/mdm.unit.policy.wizard/wizard.js @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2016, 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 userModule = require("/app/modules/user.js")["userModule"]; + var utility = require('/app/modules/utility.js').utility; + var response = userModule.getRoles(); + var wizardPage = {}; + if (response["status"] == "success") { + wizardPage["roles"] = response["content"]; + } + var deviceType = context.uriParams.deviceType; + var typesListResponse = userModule.getPlatforms(); + if (typesListResponse["status"] == "success") { + for (var type in typesListResponse["content"]) { + if (deviceType == typesListResponse["content"][type]["name"]) { + wizardPage["type"] = typesListResponse["content"][type]; + } + } + } + return wizardPage; +} \ No newline at end of file diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/src/main/resources/jaggeryapps/devicemgt/app/units/mdm.unit.policy.wizard/wizard.json b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/src/main/resources/jaggeryapps/devicemgt/app/units/mdm.unit.policy.wizard/wizard.json new file mode 100644 index 0000000000..fd25901297 --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/src/main/resources/jaggeryapps/devicemgt/app/units/mdm.unit.policy.wizard/wizard.json @@ -0,0 +1,3 @@ +{ + "version" : "1.0.0" +} \ No newline at end of file diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/src/main/resources/jaggeryapps/devicemgt/app/units/mdm.unit.ui.header.logo/logo.hbs b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/src/main/resources/jaggeryapps/devicemgt/app/units/mdm.unit.ui.header.logo/logo.hbs new file mode 100644 index 0000000000..7e30c1cb75 --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/src/main/resources/jaggeryapps/devicemgt/app/units/mdm.unit.ui.header.logo/logo.hbs @@ -0,0 +1 @@ +{{#zone "productName"}}ENTERPRISE MOBILITY MANAGER{{/zone}} \ No newline at end of file diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/src/main/resources/jaggeryapps/devicemgt/app/units/mdm.unit.ui.header.logo/logo.json b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/src/main/resources/jaggeryapps/devicemgt/app/units/mdm.unit.ui.header.logo/logo.json new file mode 100644 index 0000000000..1299a7b1b0 --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.ui/src/main/resources/jaggeryapps/devicemgt/app/units/mdm.unit.ui.header.logo/logo.json @@ -0,0 +1,5 @@ +{ + "version": "1.0.0", + "index": 30, + "extends": "cdmf.unit.ui.header.logo" +} \ No newline at end of file diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.url.printer/pom.xml b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.url.printer/pom.xml new file mode 100644 index 0000000000..bbb1c990af --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.url.printer/pom.xml @@ -0,0 +1,81 @@ + + + + + mobile-base-plugin + org.wso2.carbon.devicemgt-plugins + 2.1.0-SNAPSHOT + ../pom.xml + + + 4.0.0 + org.wso2.carbon.device.mgt.mobile.url.printer + bundle + WSO2 Carbon - Startup Handler That Prints MDM End-User Web-App URL + WSO2 Carbon - Startup Handler That Prints MDM End-User Web-App URL + http://wso2.org + + + + + org.apache.felix + maven-scr-plugin + + + org.apache.felix + maven-bundle-plugin + 1.4.0 + true + + + ${project.artifactId} + ${project.artifactId} + ${carbon.devicemgt.plugins.version} + Bundle Containing The Startup Handler That Prints MDM End-User Web-App URL + org.wso2.carbon.device.mgt.mobile.url.printer.internal + + org.osgi.framework, + org.osgi.service.component, + org.apache.commons.logging, + org.apache.axis2.*;version="${axis2.osgi.version.range}", + org.wso2.carbon.core, + org.wso2.carbon.utils.*, + + + !org.wso2.carbon.device.mgt.mobile.url.printer.internal, + org.wso2.carbon.device.mgt.mobile.url.printer, + + + + + + + + + + org.eclipse.osgi + org.eclipse.osgi + + + org.eclipse.osgi + org.eclipse.osgi.services + + + org.wso2.carbon + org.wso2.carbon.core + + + org.wso2.carbon + org.wso2.carbon.logging + + + org.wso2.carbon + org.wso2.carbon.utils + + + org.apache.axis2.wso2 + axis2 + + + + \ No newline at end of file diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.url.printer/src/main/java/org/wso2/carbon/device/mgt/mobile/url/printer/URLPrinterStartupHandler.java b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.url.printer/src/main/java/org/wso2/carbon/device/mgt/mobile/url/printer/URLPrinterStartupHandler.java new file mode 100644 index 0000000000..1eaad8ad4c --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.url.printer/src/main/java/org/wso2/carbon/device/mgt/mobile/url/printer/URLPrinterStartupHandler.java @@ -0,0 +1,64 @@ +/* + * 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.wso2.carbon.device.mgt.mobile.url.printer; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.core.ServerStartupObserver; +import org.wso2.carbon.device.mgt.mobile.url.printer.internal.URLPrinterDataHolder; +import org.wso2.carbon.utils.CarbonUtils; +import org.wso2.carbon.utils.ConfigurationContextService; +import org.wso2.carbon.utils.NetworkUtils; + +public class URLPrinterStartupHandler implements ServerStartupObserver { + + private static final Log log = LogFactory.getLog(URLPrinterStartupHandler.class); + + @Override + public void completingServerStartup() { + + } + + @Override + public void completedServerStartup() { + log.info("Device Management Console URL : " + this.getEmmUrl()); + } + + private String getEmmUrl() { + // Hostname + String hostName = "localhost"; + try { + hostName = NetworkUtils.getMgtHostName(); + } catch (Exception ignored) { + } + // HTTPS port + String mgtConsoleTransport = CarbonUtils.getManagementTransport(); + ConfigurationContextService configContextService = + URLPrinterDataHolder.getInstance().getConfigurationContextService(); + int port = CarbonUtils.getTransportPort(configContextService, mgtConsoleTransport); + int httpsProxyPort = + CarbonUtils.getTransportProxyPort(configContextService.getServerConfigContext(), + mgtConsoleTransport); + if (httpsProxyPort > 0) { + port = httpsProxyPort; + } + return "https://" + hostName + ":" + port + "/devicemgt"; + } + +} diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.url.printer/src/main/java/org/wso2/carbon/device/mgt/mobile/url/printer/internal/URLPrinterDataHolder.java b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.url.printer/src/main/java/org/wso2/carbon/device/mgt/mobile/url/printer/internal/URLPrinterDataHolder.java new file mode 100644 index 0000000000..0dd8a459d0 --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.url.printer/src/main/java/org/wso2/carbon/device/mgt/mobile/url/printer/internal/URLPrinterDataHolder.java @@ -0,0 +1,42 @@ +/* + * 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.wso2.carbon.device.mgt.mobile.url.printer.internal; + +import org.wso2.carbon.utils.ConfigurationContextService; + +public class URLPrinterDataHolder { + + private ConfigurationContextService configurationContextService; + private static URLPrinterDataHolder thisInstance = new URLPrinterDataHolder(); + + private URLPrinterDataHolder() {} + + public static URLPrinterDataHolder getInstance() { + return thisInstance; + } + + public ConfigurationContextService getConfigurationContextService() { + return configurationContextService; + } + + public void setConfigurationContextService(ConfigurationContextService configurationContextService) { + this.configurationContextService = configurationContextService; + } + +} diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.url.printer/src/main/java/org/wso2/carbon/device/mgt/mobile/url/printer/internal/URLPrinterStartupHandlerServiceComponent.java b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.url.printer/src/main/java/org/wso2/carbon/device/mgt/mobile/url/printer/internal/URLPrinterStartupHandlerServiceComponent.java new file mode 100644 index 0000000000..88a2b2daea --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile.url.printer/src/main/java/org/wso2/carbon/device/mgt/mobile/url/printer/internal/URLPrinterStartupHandlerServiceComponent.java @@ -0,0 +1,73 @@ +/* + * 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.wso2.carbon.device.mgt.mobile.url.printer.internal; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.osgi.framework.BundleContext; +import org.osgi.service.component.ComponentContext; +import org.wso2.carbon.core.ServerStartupObserver; +import org.wso2.carbon.device.mgt.mobile.url.printer.URLPrinterStartupHandler; +import org.wso2.carbon.utils.ConfigurationContextService; + +/** + * @scr.component name="org.wso2.carbon.device.mgt.mobile.url.printer.URLPrinterStartupHandlerServiceComponent" + * immediate="true" + * @scr.reference name="config.context.service" + * interface="org.wso2.carbon.utils.ConfigurationContextService" + * cardinality="0..1" + * policy="dynamic" + * bind="setConfigurationContextService" + * unbind="unsetConfigurationContextService" + */ +public class URLPrinterStartupHandlerServiceComponent { + + private static final Log log = LogFactory.getLog(URLPrinterStartupHandlerServiceComponent.class); + + @SuppressWarnings("unused") + protected void activate(ComponentContext componentContext) { + try { + BundleContext bundleContext = componentContext.getBundleContext(); + /* Registering URL printer start-up handler */ + bundleContext.registerService(ServerStartupObserver.class, new URLPrinterStartupHandler(), null); + } catch (Throwable e) { + log.error("Error occurred while activating URL printer server start-up handler service component", e); + } + } + + @SuppressWarnings("unused") + protected void deactivate(ComponentContext componentContext) { + //do nothing + } + + protected void setConfigurationContextService(ConfigurationContextService configurationContextService) { + if (log.isDebugEnabled()) { + log.debug("Setting ConfigurationContextService"); + } + URLPrinterDataHolder.getInstance().setConfigurationContextService(configurationContextService); + } + + protected void unsetConfigurationContextService(ConfigurationContextService configurationContextService) { + if (log.isDebugEnabled()) { + log.debug("Un-setting ConfigurationContextService"); + } + URLPrinterDataHolder.getInstance().setConfigurationContextService(null); + } + +} diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/pom.xml b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/pom.xml new file mode 100644 index 0000000000..73777a2da2 --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/pom.xml @@ -0,0 +1,172 @@ + + + + + + + mobile-base-plugin + org.wso2.carbon.devicemgt-plugins + 2.1.0-SNAPSHOT + ../pom.xml + + + 4.0.0 + org.wso2.carbon.device.mgt.mobile + bundle + WSO2 Carbon - Mobile Device Management Impl + WSO2 Carbon - Mobile Device Management Implementation + http://wso2.org + + + + + org.apache.felix + maven-scr-plugin + + + org.apache.felix + maven-bundle-plugin + 1.4.0 + true + + + ${project.artifactId} + ${project.artifactId} + ${carbon.devicemgt.plugins.version} + Device Management Mobile Impl Bundle + org.wso2.carbon.device.mgt.mobile.internal + + org.osgi.framework, + org.osgi.service.component, + org.apache.commons.logging, + javax.xml.bind.*, + javax.naming, + javax.sql, + javax.xml.parsers, + org.w3c.dom, + org.wso2.carbon.context, + org.wso2.carbon.utils.*, + org.wso2.carbon.device.mgt.common.*, + org.wso2.carbon.ndatasource.core, + org.wso2.carbon.policy.mgt.common.*, + org.wso2.carbon.registry.core, + org.wso2.carbon.registry.core.service, + org.wso2.carbon.registry.core.session, + org.wso2.carbon.registry.api, + org.wso2.carbon.device.mgt.extensions.license.mgt.registry, + com.google.gson.* + + + !org.wso2.carbon.device.mgt.mobile.internal, + !org.wso2.carbon.device.mgt.mobile.impl, + org.wso2.carbon.device.mgt.mobile.*, + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.18 + + + file:src/test/resources/log4j.properties + + + + src/test/resources/testng.xml + + + + + + + + + org.eclipse.osgi + org.eclipse.osgi + + + org.eclipse.osgi + org.eclipse.osgi.services + + + org.wso2.carbon + org.wso2.carbon.core + + + org.wso2.carbon + org.wso2.carbon.logging + + + org.wso2.carbon + org.wso2.carbon.utils + + + org.wso2.carbon.devicemgt + org.wso2.carbon.device.mgt.common + + + org.wso2.carbon.devicemgt + org.wso2.carbon.device.mgt.extensions + + + org.wso2.carbon + org.wso2.carbon.ndatasource.core + + + org.wso2.carbon.devicemgt + org.wso2.carbon.policy.mgt.common + + + org.wso2.carbon.devicemgt + org.wso2.carbon.policy.mgt.core + + + org.wso2.carbon + org.wso2.carbon.registry.api + + + org.wso2.carbon + org.wso2.carbon.registry.core + + + org.testng + testng + + + org.apache.tomcat.wso2 + jdbc-pool + + + com.h2database.wso2 + h2-database-engine + test + + + com.google.code.gson + gson + + + com.google.android.gcm + gcm-server + + + diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/main/java/org/wso2/carbon/device/mgt/mobile/AbstractMobileOperationManager.java b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/main/java/org/wso2/carbon/device/mgt/mobile/AbstractMobileOperationManager.java new file mode 100644 index 0000000000..1bf5322da4 --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/main/java/org/wso2/carbon/device/mgt/mobile/AbstractMobileOperationManager.java @@ -0,0 +1,41 @@ +/* + * 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.wso2.carbon.device.mgt.mobile; + +import org.wso2.carbon.device.mgt.common.*; +import org.wso2.carbon.device.mgt.common.operation.mgt.Operation; +import org.wso2.carbon.device.mgt.common.operation.mgt.OperationManagementException; +import org.wso2.carbon.device.mgt.common.operation.mgt.OperationManager; + +import java.util.List; + +public abstract class AbstractMobileOperationManager implements OperationManager { + + @Override + public List getOperations(DeviceIdentifier deviceIdentifier) throws OperationManagementException { + return null; + } + + @Override + public int addOperation(Operation operation, List devices) throws + OperationManagementException { + return 1; + } + +} \ No newline at end of file diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/main/java/org/wso2/carbon/device/mgt/mobile/DataSourceListener.java b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/main/java/org/wso2/carbon/device/mgt/mobile/DataSourceListener.java new file mode 100644 index 0000000000..649786c43f --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/main/java/org/wso2/carbon/device/mgt/mobile/DataSourceListener.java @@ -0,0 +1,25 @@ +/* + * 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.wso2.carbon.device.mgt.mobile; + +public interface DataSourceListener { + + void notifyObserver(); + +} diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/main/java/org/wso2/carbon/device/mgt/mobile/DataSourceNotAvailableException.java b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/main/java/org/wso2/carbon/device/mgt/mobile/DataSourceNotAvailableException.java new file mode 100644 index 0000000000..5a9e5b8f48 --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/main/java/org/wso2/carbon/device/mgt/mobile/DataSourceNotAvailableException.java @@ -0,0 +1,52 @@ +/* + * * + * * 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. + * + */ +package org.wso2.carbon.device.mgt.mobile; + +public class DataSourceNotAvailableException extends RuntimeException { + + private String message; + private static final long serialVersionUID = 2021891706072918866L; + + public DataSourceNotAvailableException(String message, Exception nestedException) { + super(message, nestedException); + setErrorMessage(message); + } + + public DataSourceNotAvailableException(String message, Throwable cause) { + super(message, cause); + setErrorMessage(message); + } + + public DataSourceNotAvailableException(String message) { + super(message); + setErrorMessage(message); + } + + public DataSourceNotAvailableException(Throwable cause) { + super(cause); + } + + public String getMessage() { + return message; + } + + public void setErrorMessage(String errorMessage) { + this.message = errorMessage; + } + +} diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/main/java/org/wso2/carbon/device/mgt/mobile/common/MobileDeviceMgtPluginException.java b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/main/java/org/wso2/carbon/device/mgt/mobile/common/MobileDeviceMgtPluginException.java new file mode 100644 index 0000000000..ef2871c286 --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/main/java/org/wso2/carbon/device/mgt/mobile/common/MobileDeviceMgtPluginException.java @@ -0,0 +1,57 @@ +/* + * 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.wso2.carbon.device.mgt.mobile.common; + + +public class MobileDeviceMgtPluginException extends Exception{ + + private static final long serialVersionUID = -2297311387874900305L; + private String errorMessage; + + public String getErrorMessage() { + return errorMessage; + } + + public void setErrorMessage(String errorMessage) { + this.errorMessage = errorMessage; + } + + public MobileDeviceMgtPluginException(String msg, Exception nestedEx) { + super(msg, nestedEx); + setErrorMessage(msg); + } + + public MobileDeviceMgtPluginException(String message, Throwable cause) { + super(message, cause); + setErrorMessage(message); + } + + public MobileDeviceMgtPluginException(String msg) { + super(msg); + setErrorMessage(msg); + } + + public MobileDeviceMgtPluginException() { + super(); + } + + public MobileDeviceMgtPluginException(Throwable cause) { + super(cause); + } + +} diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/main/java/org/wso2/carbon/device/mgt/mobile/common/MobilePluginConstants.java b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/main/java/org/wso2/carbon/device/mgt/mobile/common/MobilePluginConstants.java new file mode 100644 index 0000000000..67e793fa5f --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/main/java/org/wso2/carbon/device/mgt/mobile/common/MobilePluginConstants.java @@ -0,0 +1,29 @@ +/* + * 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.wso2.carbon.device.mgt.mobile.common; + +public class MobilePluginConstants { + public static final String MOBILE_DB_SCRIPTS_FOLDER = "cdm"; + public static final String MOBILE_CONFIG_REGISTRY_ROOT = "/_system/config"; + + public static final String MEDIA_TYPE_XML = "application/xml"; + public static final String CHARSET_UTF8 = "UTF8"; + public static final String LANGUAGE_CODE_ENGLISH_US = "en_US"; + public static final String LANGUAGE_CODE_ENGLISH_UK = "en_UK"; + +} diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/main/java/org/wso2/carbon/device/mgt/mobile/config/MobileDeviceConfigurationManager.java b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/main/java/org/wso2/carbon/device/mgt/mobile/config/MobileDeviceConfigurationManager.java new file mode 100644 index 0000000000..07123434d6 --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/main/java/org/wso2/carbon/device/mgt/mobile/config/MobileDeviceConfigurationManager.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2014, 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.wso2.carbon.device.mgt.mobile.config; + +import org.w3c.dom.Document; +import org.wso2.carbon.device.mgt.common.DeviceManagementException; +import org.wso2.carbon.device.mgt.mobile.util.MobileDeviceManagementUtil; +import org.wso2.carbon.device.mgt.mobile.config.datasource.MobileDataSourceConfig; +import org.wso2.carbon.utils.CarbonUtils; + +import javax.xml.bind.JAXBContext; +import javax.xml.bind.Unmarshaller; +import java.io.File; + +/** + * Class responsible for the mobile device manager configuration initialization. + */ +public class MobileDeviceConfigurationManager { + + private static final String MOBILE_DEVICE_CONFIG_XML_NAME = "mobile-config.xml"; + private static final String MOBILE_DEVICE_PLUGIN_DIRECTORY = "mobile"; + private static final String DEVICE_MGT_PLUGIN_CONFIGS_DIRECTORY = "device-mgt-plugin-configs"; + private MobileDeviceManagementConfig currentMobileDeviceConfig; + private static MobileDeviceConfigurationManager mobileDeviceConfigManager; + + private final String mobileDeviceMgtConfigXMLPath = + CarbonUtils.getEtcCarbonConfigDirPath() + File.separator + + DEVICE_MGT_PLUGIN_CONFIGS_DIRECTORY + + File.separator + + MOBILE_DEVICE_PLUGIN_DIRECTORY + File.separator + MOBILE_DEVICE_CONFIG_XML_NAME; + + public static MobileDeviceConfigurationManager getInstance() { + if (mobileDeviceConfigManager == null) { + synchronized (MobileDeviceConfigurationManager.class) { + if (mobileDeviceConfigManager == null) { + mobileDeviceConfigManager = new MobileDeviceConfigurationManager(); + } + } + } + return mobileDeviceConfigManager; + } + + public synchronized void initConfig() throws DeviceManagementException { + try { + File mobileDeviceMgtConfig = new File(mobileDeviceMgtConfigXMLPath); + Document doc = MobileDeviceManagementUtil.convertToDocument(mobileDeviceMgtConfig); + JAXBContext mobileDeviceMgmtContext = + JAXBContext.newInstance(MobileDeviceManagementConfig.class); + Unmarshaller unmarshaller = mobileDeviceMgmtContext.createUnmarshaller(); + this.currentMobileDeviceConfig = + (MobileDeviceManagementConfig) unmarshaller.unmarshal(doc); + } catch (Exception e) { + throw new DeviceManagementException( + "Error occurred while initializing Mobile Device Management config", e); + } + } + + public MobileDeviceManagementConfig getMobileDeviceManagementConfig() { + return currentMobileDeviceConfig; + } + + +} diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/main/java/org/wso2/carbon/device/mgt/mobile/config/MobileDeviceManagementConfig.java b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/main/java/org/wso2/carbon/device/mgt/mobile/config/MobileDeviceManagementConfig.java new file mode 100644 index 0000000000..ced99f955e --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/main/java/org/wso2/carbon/device/mgt/mobile/config/MobileDeviceManagementConfig.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2014, 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.wso2.carbon.device.mgt.mobile.config; + +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; + +/** + * Represents Mobile Device Mgt configuration. + */ +@XmlRootElement(name = "MobileDeviceMgtConfiguration") +public final class MobileDeviceManagementConfig { + + private MobileDeviceManagementRepository mobileDeviceMgtRepository; + + @XmlElement(name = "ManagementRepository", nillable = false) + public MobileDeviceManagementRepository getMobileDeviceMgtRepository() { + return mobileDeviceMgtRepository; + } + + public void setMobileDeviceMgtRepository( + MobileDeviceManagementRepository mobileDeviceMgtRepository) { + this.mobileDeviceMgtRepository = mobileDeviceMgtRepository; + } + +} diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/main/java/org/wso2/carbon/device/mgt/mobile/config/MobileDeviceManagementRepository.java b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/main/java/org/wso2/carbon/device/mgt/mobile/config/MobileDeviceManagementRepository.java new file mode 100644 index 0000000000..88369dd842 --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/main/java/org/wso2/carbon/device/mgt/mobile/config/MobileDeviceManagementRepository.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2014, 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.wso2.carbon.device.mgt.mobile.config; + +import org.wso2.carbon.device.mgt.mobile.config.datasource.DataSourceConfigAdapter; +import org.wso2.carbon.device.mgt.mobile.config.datasource.MobileDataSourceConfig; + +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; +import java.util.List; +import java.util.Map; + +/** + * Class for holding management repository data. + */ +@XmlRootElement(name = "ManagementRepository") +public class MobileDeviceManagementRepository { + + private Map mobileDataSourceConfigMap; + private List mobileDataSourceConfigs; + + public MobileDataSourceConfig getMobileDataSourceConfig(String provider) { + return mobileDataSourceConfigMap.get(provider); + } + + @XmlElement(name = "DataSourceConfigurations") + @XmlJavaTypeAdapter(DataSourceConfigAdapter.class) + public Map getMobileDataSourceConfigMap() { + return mobileDataSourceConfigMap; + } + + public void setMobileDataSourceConfigMap(Map mobileDataSourceConfigMap) { + this.mobileDataSourceConfigMap = mobileDataSourceConfigMap; + } + + public List getMobileDataSourceConfigs() { + return (List) mobileDataSourceConfigMap.values(); + } + +} diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/main/java/org/wso2/carbon/device/mgt/mobile/config/datasource/DataSourceConfigAdapter.java b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/main/java/org/wso2/carbon/device/mgt/mobile/config/datasource/DataSourceConfigAdapter.java new file mode 100644 index 0000000000..71675785c1 --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/main/java/org/wso2/carbon/device/mgt/mobile/config/datasource/DataSourceConfigAdapter.java @@ -0,0 +1,51 @@ +/* + * 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.wso2.carbon.device.mgt.mobile.config.datasource; + +import javax.xml.bind.annotation.adapters.XmlAdapter; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class DataSourceConfigAdapter + extends XmlAdapter> { + + @Override + public Map unmarshal(MobileDataSourceConfigurations mobileDataSourceConfigurations) + throws Exception { + + Map mobileDataSourceConfigMap = new HashMap(); + for (MobileDataSourceConfig mobileDataSourceConfig : mobileDataSourceConfigurations + .getMobileDataSourceConfigs()) { + mobileDataSourceConfigMap.put(mobileDataSourceConfig.getType(), mobileDataSourceConfig); + } + return mobileDataSourceConfigMap; + } + + @Override + public MobileDataSourceConfigurations marshal(Map mobileDataSourceConfigMap) + throws Exception { + + MobileDataSourceConfigurations mobileDataSourceConfigurations = new MobileDataSourceConfigurations(); + mobileDataSourceConfigurations.setMobileDataSourceConfigs( + (List) mobileDataSourceConfigMap.values()); + + return mobileDataSourceConfigurations; + } +} diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/main/java/org/wso2/carbon/device/mgt/mobile/config/datasource/JNDILookupDefinition.java b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/main/java/org/wso2/carbon/device/mgt/mobile/config/datasource/JNDILookupDefinition.java new file mode 100644 index 0000000000..603adcedd1 --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/main/java/org/wso2/carbon/device/mgt/mobile/config/datasource/JNDILookupDefinition.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2014, 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.wso2.carbon.device.mgt.mobile.config.datasource; + +import javax.xml.bind.annotation.*; +import java.util.List; + +/** + * Class for hold JndiLookupDefinition of mobile-config.xml at parsing with JAXB. + */ +@XmlRootElement(name = "JndiLookupDefinition") +public class JNDILookupDefinition { + + private String jndiName; + private List jndiProperties; + + @XmlElement(name = "Name", nillable = false) + public String getJndiName() { + return jndiName; + } + + public void setJndiName(String jndiName) { + this.jndiName = jndiName; + } + + @XmlElementWrapper(name = "Environment", nillable = false) + @XmlElement(name = "Property", nillable = false) + public List getJndiProperties() { + return jndiProperties; + } + + public void setJndiProperties(List jndiProperties) { + this.jndiProperties = jndiProperties; + } + + @XmlRootElement(name = "Property") + public static class JNDIProperty { + + private String name; + + private String value; + + @XmlAttribute(name = "Name") + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @XmlValue + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + } + +} + diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/main/java/org/wso2/carbon/device/mgt/mobile/config/datasource/MobileDataSourceConfig.java b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/main/java/org/wso2/carbon/device/mgt/mobile/config/datasource/MobileDataSourceConfig.java new file mode 100644 index 0000000000..869aaf12e2 --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/main/java/org/wso2/carbon/device/mgt/mobile/config/datasource/MobileDataSourceConfig.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2014, 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.wso2.carbon.device.mgt.mobile.config.datasource; + +import javax.xml.bind.annotation.XmlAttribute; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; + +/** + * Class for holding data source configuration in mobile-config.xml at parsing with JAXB. + */ +@XmlRootElement(name = "DataSourceConfiguration") +public class MobileDataSourceConfig { + + private JNDILookupDefinition jndiLookupDefinition; + private String type; + + @XmlElement(name = "JndiLookupDefinition", nillable = true) + public JNDILookupDefinition getJndiLookupDefinition() { + return jndiLookupDefinition; + } + + public void setJndiLookupDefinition(JNDILookupDefinition jndiLookupDefinition) { + this.jndiLookupDefinition = jndiLookupDefinition; + } + + @XmlAttribute(name = "type") + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } +} diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/main/java/org/wso2/carbon/device/mgt/mobile/config/datasource/MobileDataSourceConfigurations.java b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/main/java/org/wso2/carbon/device/mgt/mobile/config/datasource/MobileDataSourceConfigurations.java new file mode 100644 index 0000000000..17e80f5bf3 --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/main/java/org/wso2/carbon/device/mgt/mobile/config/datasource/MobileDataSourceConfigurations.java @@ -0,0 +1,41 @@ +/* + * 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.wso2.carbon.device.mgt.mobile.config.datasource; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; +import java.util.List; + +@XmlRootElement(name = "DataSourceConfigurations") +@XmlAccessorType(XmlAccessType.FIELD) +public class MobileDataSourceConfigurations { + + @XmlElement(name = "DataSourceConfiguration", nillable = true) + private List mobileDataSourceConfigs; + + public List getMobileDataSourceConfigs() { + return mobileDataSourceConfigs; + } + + public void setMobileDataSourceConfigs(List mobileDataSourceConfigs) { + this.mobileDataSourceConfigs = mobileDataSourceConfigs; + } + +} diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/main/java/org/wso2/carbon/device/mgt/mobile/dao/AbstractMobileDeviceManagementDAOFactory.java b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/main/java/org/wso2/carbon/device/mgt/mobile/dao/AbstractMobileDeviceManagementDAOFactory.java new file mode 100644 index 0000000000..32bcb8de73 --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/main/java/org/wso2/carbon/device/mgt/mobile/dao/AbstractMobileDeviceManagementDAOFactory.java @@ -0,0 +1,102 @@ +/* + * 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.wso2.carbon.device.mgt.mobile.dao; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.device.mgt.mobile.common.MobileDeviceMgtPluginException; +import org.wso2.carbon.device.mgt.mobile.config.datasource.JNDILookupDefinition; +import org.wso2.carbon.device.mgt.mobile.config.datasource.MobileDataSourceConfig; +import org.wso2.carbon.device.mgt.mobile.dao.util.MobileDeviceManagementDAOUtil; + +import javax.sql.DataSource; +import java.util.HashMap; +import java.util.Hashtable; +import java.util.List; +import java.util.Map; + +/** + * Factory class used to create MobileDeviceManagement related DAO objects. + */ +public abstract class AbstractMobileDeviceManagementDAOFactory implements MobileDeviceManagementDAOFactory { + + private static final Log log = LogFactory.getLog(AbstractMobileDeviceManagementDAOFactory.class); + private static Map dataSourceMap = new HashMap<>(); + private static boolean isInitialized; + + public static void init(Map mobileDataSourceConfigMap) + throws MobileDeviceMgtPluginException { + DataSource dataSource; + for (String pluginType : mobileDataSourceConfigMap.keySet()) { + if (dataSourceMap.get(pluginType) == null) { + dataSource = AbstractMobileDeviceManagementDAOFactory.resolveDataSource(mobileDataSourceConfigMap.get + (pluginType)); + dataSourceMap.put(pluginType, dataSource); + } + } + isInitialized = true; + } + + public static void init(String key, MobileDataSourceConfig mobileDataSourceConfig) throws + MobileDeviceMgtPluginException { + DataSource dataSource = AbstractMobileDeviceManagementDAOFactory.resolveDataSource(mobileDataSourceConfig); + dataSourceMap.put(key, dataSource); + } + + /** + * Resolve data source from the data source definition. + * + * @param config Mobile data source configuration + * @return data source resolved from the data source definition + */ + public static DataSource resolveDataSource(MobileDataSourceConfig config) { + DataSource dataSource = null; + if (config == null) { + throw new RuntimeException("Device Management Repository data source configuration " + + "is null and thus, is not initialized"); + } + JNDILookupDefinition jndiConfig = config.getJndiLookupDefinition(); + if (jndiConfig != null) { + if (log.isDebugEnabled()) { + log.debug("Initializing Device Management Repository data source using the JNDI " + + "Lookup Definition"); + } + List jndiPropertyList = + jndiConfig.getJndiProperties(); + if (jndiPropertyList != null) { + Hashtable jndiProperties = new Hashtable(); + for (JNDILookupDefinition.JNDIProperty prop : jndiPropertyList) { + jndiProperties.put(prop.getName(), prop.getValue()); + } + dataSource = + MobileDeviceManagementDAOUtil + .lookupDataSource(jndiConfig.getJndiName(), jndiProperties); + } else { + dataSource = MobileDeviceManagementDAOUtil + .lookupDataSource(jndiConfig.getJndiName(), null); + } + } + return dataSource; + } + + public static Map getDataSourceMap() { + return dataSourceMap; + } + +} \ No newline at end of file diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/main/java/org/wso2/carbon/device/mgt/mobile/dao/MobileDeviceDAO.java b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/main/java/org/wso2/carbon/device/mgt/mobile/dao/MobileDeviceDAO.java new file mode 100644 index 0000000000..1615942736 --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/main/java/org/wso2/carbon/device/mgt/mobile/dao/MobileDeviceDAO.java @@ -0,0 +1,74 @@ +/* + * 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.wso2.carbon.device.mgt.mobile.dao; + +import org.wso2.carbon.device.mgt.mobile.dto.MobileDevice; +import java.util.List; + +/** + * This class represents the key operations associated with persisting mobile-device related + * information. + */ +public interface MobileDeviceDAO { + + /** + * Fetches a MobileDevice from MDM database. + * + * @param mblDeviceId Id of the Mobile-Device. + * @return MobileDevice corresponding to given device-id. + * @throws MobileDeviceManagementDAOException + */ + MobileDevice getMobileDevice(String mblDeviceId) throws MobileDeviceManagementDAOException; + + /** + * Adds a new MobileDevice to the MDM database. + * + * @param mobileDevice MobileDevice to be added. + * @return The status of the operation. + * @throws MobileDeviceManagementDAOException + */ + boolean addMobileDevice(MobileDevice mobileDevice) throws MobileDeviceManagementDAOException; + + /** + * Updates MobileDevice information in MDM database. + * + * @param mobileDevice MobileDevice to be updated. + * @return The status of the operation. + * @throws MobileDeviceManagementDAOException + */ + boolean updateMobileDevice(MobileDevice mobileDevice) throws MobileDeviceManagementDAOException; + + /** + * Deletes a given MobileDevice from MDM database. + * + * @param mblDeviceId Id of MobileDevice to be deleted. + * @return The status of the operation. + * @throws MobileDeviceManagementDAOException + */ + boolean deleteMobileDevice(String mblDeviceId) throws MobileDeviceManagementDAOException; + + /** + * Fetches all MobileDevices from MDM database. + * + * @return List of MobileDevices. + * @throws MobileDeviceManagementDAOException + */ + List getAllMobileDevices() throws MobileDeviceManagementDAOException; + +} diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/main/java/org/wso2/carbon/device/mgt/mobile/dao/MobileDeviceManagementDAOException.java b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/main/java/org/wso2/carbon/device/mgt/mobile/dao/MobileDeviceManagementDAOException.java new file mode 100644 index 0000000000..0b04818d96 --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/main/java/org/wso2/carbon/device/mgt/mobile/dao/MobileDeviceManagementDAOException.java @@ -0,0 +1,80 @@ +/* + * 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.wso2.carbon.device.mgt.mobile.dao; + +/** + * Custom exception class for mobile device specific data access related exceptions. + */ +public class MobileDeviceManagementDAOException extends Exception { + + private String message; + private static final long serialVersionUID = 2021891706072918865L; + + /** + * Constructs a new MobileDeviceManagementDAOException with the specified detail message and + * nested exception. + * + * @param message error message + * @param nestedException exception + */ + public MobileDeviceManagementDAOException(String message, Exception nestedException) { + super(message, nestedException); + setErrorMessage(message); + } + + /** + * Constructs a new MobileDeviceManagementDAOException with the specified detail message + * and cause. + * + * @param message the detail message. + * @param cause the cause of this exception. + */ + public MobileDeviceManagementDAOException(String message, Throwable cause) { + super(message, cause); + setErrorMessage(message); + } + + /** + * Constructs a new MobileDeviceManagementDAOException with the specified detail message. + * + * @param message the detail message. + */ + public MobileDeviceManagementDAOException(String message) { + super(message); + setErrorMessage(message); + } + + /** + * Constructs a new MobileDeviceManagementDAOException with the specified and cause. + * + * @param cause the cause of this exception. + */ + public MobileDeviceManagementDAOException(Throwable cause) { + super(cause); + } + + public String getMessage() { + return message; + } + + public void setErrorMessage(String errorMessage) { + this.message = errorMessage; + } + +} diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/main/java/org/wso2/carbon/device/mgt/mobile/dao/MobileDeviceManagementDAOFactory.java b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/main/java/org/wso2/carbon/device/mgt/mobile/dao/MobileDeviceManagementDAOFactory.java new file mode 100644 index 0000000000..37a5c311ca --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/main/java/org/wso2/carbon/device/mgt/mobile/dao/MobileDeviceManagementDAOFactory.java @@ -0,0 +1,27 @@ +/* + * 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.wso2.carbon.device.mgt.mobile.dao; + +public interface MobileDeviceManagementDAOFactory { + + MobileDeviceDAO getMobileDeviceDAO(); + + MobileFeatureDAO getMobileFeatureDAO(); + +} diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/main/java/org/wso2/carbon/device/mgt/mobile/dao/MobileFeatureDAO.java b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/main/java/org/wso2/carbon/device/mgt/mobile/dao/MobileFeatureDAO.java new file mode 100644 index 0000000000..55c77424b7 --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/main/java/org/wso2/carbon/device/mgt/mobile/dao/MobileFeatureDAO.java @@ -0,0 +1,110 @@ +/* + * 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.wso2.carbon.device.mgt.mobile.dao; + +import org.wso2.carbon.device.mgt.mobile.dto.MobileFeature; + +import java.util.List; + +/** + * This class represents the key operations associated with persisting mobile feature related + * information. + */ +public interface MobileFeatureDAO { + + /** + * Adds a new MobileFeature to Mobile-Feature table. + * + * @param mobileFeature MobileFeature object that holds data related to the feature to be inserted. + * @return boolean status of the operation. + * @throws MobileDeviceManagementDAOException + */ + boolean addFeature(MobileFeature mobileFeature) throws MobileDeviceManagementDAOException; + + /** + * Adda a list of MobileFeatures to Mobile-Feature table. + * + * @param mobileFeatures List of MobileFeature objects. + * @return boolean status of the operation. + * @throws MobileDeviceManagementDAOException + */ + boolean addFeatures(List mobileFeatures) throws MobileDeviceManagementDAOException; + + /** + * Updates a MobileFeature in Mobile-Feature table. + * + * @param mobileFeature MobileFeature object that holds data has to be updated. + * @return The status of the operation. + * @throws MobileDeviceManagementDAOException + */ + boolean updateFeature(MobileFeature mobileFeature) throws MobileDeviceManagementDAOException; + + /** + * Deletes a MobileFeature from Mobile-Feature table when the feature id is given. + * + * @param mblFeatureId MobileFeature id of the MobileFeature to be deleted. + * @return The status of the operation. + * @throws MobileDeviceManagementDAOException + */ + boolean deleteFeatureById(int mblFeatureId) throws MobileDeviceManagementDAOException; + + /** + * Deletes a MobileFeature from Mobile-Feature table when the feature code is given. + * + * @param mblFeatureCode MobileFeature code of the feature to be deleted. + * @return The status of the operation. + * @throws MobileDeviceManagementDAOException + */ + boolean deleteFeatureByCode(String mblFeatureCode) throws MobileDeviceManagementDAOException; + + /** + * Retrieves a given MobileFeature from Mobile-Feature table when the feature id is given. + * + * @param mblFeatureId Feature id of the feature to be retrieved. + * @return MobileFeature object that holds data of the feature represented by featureId. + * @throws MobileDeviceManagementDAOException + */ + MobileFeature getFeatureById(int mblFeatureId) throws MobileDeviceManagementDAOException; + + /** + * Retrieves a given MobileFeature from Mobile-Feature table when the feature code is given. + * + * @param mblFeatureCode Feature code of the feature to be retrieved. + * @return MobileFeature object that holds data of the feature represented by featureCode. + * @throws MobileDeviceManagementDAOException + */ + MobileFeature getFeatureByCode(String mblFeatureCode) throws MobileDeviceManagementDAOException; + + /** + * Retrieves all MobileFeatures of a MobileDevice type from Mobile-Feature table. + * + * @param deviceType MobileDevice type of the MobileFeatures to be retrieved + * @return MobileFeature object list. + * @throws MobileDeviceManagementDAOException + */ + List getFeatureByDeviceType(String deviceType) throws MobileDeviceManagementDAOException; + + /** + * Retrieve all the MobileFeatures from Mobile-Feature table. + * + * @return MobileFeature object list. + * @throws MobileDeviceManagementDAOException + */ + List getAllFeatures() throws MobileDeviceManagementDAOException; +} diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/main/java/org/wso2/carbon/device/mgt/mobile/dao/impl/MobileFeatureDAOImpl.java b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/main/java/org/wso2/carbon/device/mgt/mobile/dao/impl/MobileFeatureDAOImpl.java new file mode 100644 index 0000000000..86a940fd66 --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/main/java/org/wso2/carbon/device/mgt/mobile/dao/impl/MobileFeatureDAOImpl.java @@ -0,0 +1,336 @@ +/* + * Copyright (c) 2014, 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.wso2.carbon.device.mgt.mobile.dao.impl; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.device.mgt.mobile.dao.MobileDeviceManagementDAOException; +import org.wso2.carbon.device.mgt.mobile.dao.MobileFeatureDAO; +import org.wso2.carbon.device.mgt.mobile.dao.util.MobileDeviceManagementDAOUtil; +import org.wso2.carbon.device.mgt.mobile.dto.MobileFeature; + +import javax.sql.DataSource; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; + +/** + * Implementation of MobileFeatureDAO. + */ +public class MobileFeatureDAOImpl implements MobileFeatureDAO { + + private DataSource dataSource; + private static final Log log = LogFactory.getLog(MobileFeatureDAOImpl.class); + + public MobileFeatureDAOImpl(DataSource dataSource) { + this.dataSource = dataSource; + } + + @Override + public boolean addFeature(MobileFeature mobileFeature) + throws MobileDeviceManagementDAOException { + boolean status = false; + Connection conn = null; + PreparedStatement stmt = null; + try { + conn = this.getConnection(); + String createDBQuery = + "INSERT INTO AD_FEATURE(CODE, NAME, DESCRIPTION, DEVICE_TYPE) VALUES (?, ?, ?, ?)"; + + stmt = conn.prepareStatement(createDBQuery); + stmt.setString(1, mobileFeature.getCode()); + stmt.setString(2, mobileFeature.getName()); + stmt.setString(3, mobileFeature.getDescription()); + stmt.setString(4, mobileFeature.getDeviceType()); + int rows = stmt.executeUpdate(); + if (rows > 0) { + if (log.isDebugEnabled()) { + log.debug("Added a new MobileFeature " + mobileFeature.getCode() + " to the MDM database."); + } + status = true; + } + } catch (SQLException e) { + String msg = "Error occurred while adding feature code - '" + + mobileFeature.getCode() + "' to feature table"; + log.error(msg, e); + throw new MobileDeviceManagementDAOException(msg, e); + } finally { + MobileDeviceManagementDAOUtil.cleanupResources(conn, stmt, null); + } + return status; + } + + @Override + public boolean addFeatures(List mobileFeatures) throws MobileDeviceManagementDAOException { + return false; + } + + @Override + public boolean updateFeature(MobileFeature mobileFeature) + throws MobileDeviceManagementDAOException { + boolean status = false; + Connection conn = null; + PreparedStatement stmt = null; + try { + conn = this.getConnection(); + String updateDBQuery = + "UPDATE AD_FEATURE SET CODE = ?, NAME = ?, DESCRIPTION = ?, DEVICE_TYPE = ?" + + " WHERE ID = ?"; + stmt = conn.prepareStatement(updateDBQuery); + stmt.setString(1, mobileFeature.getCode()); + stmt.setString(2, mobileFeature.getName()); + stmt.setString(3, mobileFeature.getDescription()); + stmt.setString(4, mobileFeature.getDeviceType()); + stmt.setInt(5, mobileFeature.getId()); + int rows = stmt.executeUpdate(); + if (rows > 0) { + status = true; + if (log.isDebugEnabled()) { + log.debug("Updated MobileFeature " + mobileFeature.getCode()); + } + } + } catch (SQLException e) { + String msg = "Error occurred while updating the feature with feature code - '" + + mobileFeature.getId() + "'"; + log.error(msg, e); + throw new MobileDeviceManagementDAOException(msg, e); + } finally { + MobileDeviceManagementDAOUtil.cleanupResources(conn, stmt, null); + } + return status; + } + + @Override + public boolean deleteFeatureByCode(String mblFeatureCode) + throws MobileDeviceManagementDAOException { + boolean status = false; + Connection conn = null; + PreparedStatement stmt = null; + try { + conn = this.getConnection(); + String deleteDBQuery = + "DELETE FROM AD_FEATURE WHERE CODE = ?"; + stmt = conn.prepareStatement(deleteDBQuery); + stmt.setString(1, mblFeatureCode); + int rows = stmt.executeUpdate(); + if (rows > 0) { + status = true; + if (log.isDebugEnabled()) { + log.debug("Deleted MobileFeature code " + mblFeatureCode + " from the MDM database."); + } + } + } catch (SQLException e) { + String msg = "Error occurred while deleting feature with code - " + mblFeatureCode; + log.error(msg, e); + throw new MobileDeviceManagementDAOException(msg, e); + } finally { + MobileDeviceManagementDAOUtil.cleanupResources(conn, stmt, null); + } + return status; + } + + @Override + public boolean deleteFeatureById(int mblFeatureId) + throws MobileDeviceManagementDAOException { + boolean status = false; + Connection conn = null; + PreparedStatement stmt = null; + try { + conn = this.getConnection(); + String deleteDBQuery = + "DELETE FROM AD_FEATURE WHERE ID = ?"; + stmt = conn.prepareStatement(deleteDBQuery); + stmt.setInt(1, mblFeatureId); + int rows = stmt.executeUpdate(); + if (rows > 0) { + status = true; + if (log.isDebugEnabled()) { + log.debug("Deleted MobileFeature id " + mblFeatureId + " from the MDM database."); + } + } + } catch (SQLException e) { + String msg = "Error occurred while deleting feature with id - " + mblFeatureId; + log.error(msg, e); + throw new MobileDeviceManagementDAOException(msg, e); + } finally { + MobileDeviceManagementDAOUtil.cleanupResources(conn, stmt, null); + } + return status; + } + + @Override + public MobileFeature getFeatureByCode(String mblFeatureCode) + throws MobileDeviceManagementDAOException { + Connection conn = null; + PreparedStatement stmt = null; + MobileFeature mobileFeature = null; + ResultSet resultSet = null; + try { + conn = this.getConnection(); + String selectDBQuery = + "SELECT ID, CODE, NAME, DESCRIPTION, DEVICE_TYPE FROM AD_FEATURE " + + "WHERE CODE = ?"; + stmt = conn.prepareStatement(selectDBQuery); + stmt.setString(1, mblFeatureCode); + resultSet = stmt.executeQuery(); + if (resultSet.next()) { + mobileFeature = new MobileFeature(); + mobileFeature.setId(resultSet.getInt(1)); + mobileFeature.setCode(resultSet.getString(2)); + mobileFeature.setName(resultSet.getString(3)); + mobileFeature.setDescription(resultSet.getString(4)); + mobileFeature.setDeviceType(resultSet.getString(5)); + if (log.isDebugEnabled()) { + log.debug("Fetched MobileFeature " + mblFeatureCode + " from the MDM database."); + } + } + } catch (SQLException e) { + String msg = "Error occurred while fetching feature code - '" + mblFeatureCode + "'"; + log.error(msg, e); + throw new MobileDeviceManagementDAOException(msg, e); + } finally { + MobileDeviceManagementDAOUtil.cleanupResources(conn, stmt, resultSet); + } + return mobileFeature; + } + + @Override + public MobileFeature getFeatureById(int mblFeatureId) + throws MobileDeviceManagementDAOException { + Connection conn = null; + PreparedStatement stmt = null; + MobileFeature mobileFeature = null; + ResultSet resultSet = null; + try { + conn = this.getConnection(); + String selectDBQuery = + "SELECT ID, CODE, NAME, DESCRIPTION, DEVICE_TYPE FROM AD_FEATURE" + + " WHERE ID = ?"; + stmt = conn.prepareStatement(selectDBQuery); + stmt.setInt(1, mblFeatureId); + resultSet = stmt.executeQuery(); + if (resultSet.next()) { + mobileFeature = new MobileFeature(); + mobileFeature.setId(resultSet.getInt(1)); + mobileFeature.setCode(resultSet.getString(2)); + mobileFeature.setName(resultSet.getString(3)); + mobileFeature.setDescription(resultSet.getString(4)); + mobileFeature.setDeviceType(resultSet.getString(5)); + if (log.isDebugEnabled()) { + log.debug("Fetched MobileFeatureId" + mblFeatureId + " from the MDM database."); + } + } + } catch (SQLException e) { + String msg = "Error occurred while fetching feature id - '" + mblFeatureId + "'"; + log.error(msg, e); + throw new MobileDeviceManagementDAOException(msg, e); + } finally { + MobileDeviceManagementDAOUtil.cleanupResources(conn, stmt, resultSet); + } + return mobileFeature; + } + + @Override + public List getAllFeatures() throws MobileDeviceManagementDAOException { + Connection conn = null; + PreparedStatement stmt = null; + MobileFeature mobileFeature; + List mobileFeatures = new ArrayList(); + ResultSet resultSet = null; + try { + conn = this.getConnection(); + String selectDBQuery = + "SELECT ID, CODE, NAME, DESCRIPTION, DEVICE_TYPE FROM AD_FEATURE"; + stmt = conn.prepareStatement(selectDBQuery); + resultSet = stmt.executeQuery(); + while (resultSet.next()) { + mobileFeature = new MobileFeature(); + mobileFeature.setId(resultSet.getInt(1)); + mobileFeature.setCode(resultSet.getString(2)); + mobileFeature.setName(resultSet.getString(3)); + mobileFeature.setDescription(resultSet.getString(4)); + mobileFeature.setDeviceType(resultSet.getString(5)); + mobileFeatures.add(mobileFeature); + } + if (log.isDebugEnabled()) { + log.debug("Fetched all MobileFeatures from the MDM database."); + } + return mobileFeatures; + } catch (SQLException e) { + String msg = "Error occurred while fetching all features.'"; + log.error(msg, e); + throw new MobileDeviceManagementDAOException(msg, e); + } finally { + MobileDeviceManagementDAOUtil.cleanupResources(conn, stmt, resultSet); + } + } + + @Override + public List getFeatureByDeviceType(String deviceType) throws MobileDeviceManagementDAOException { + Connection conn = null; + PreparedStatement stmt = null; + MobileFeature mobileFeature; + List mobileFeatures = new ArrayList<>(); + ResultSet resultSet = null; + try { + conn = this.getConnection(); + String selectDBQuery = + "SELECT ID, CODE, NAME, DESCRIPTION, DEVICE_TYPE FROM AD_FEATURE" + + " WHERE DEVICE_TYPE = ?"; + stmt = conn.prepareStatement(selectDBQuery); + stmt.setString(1, deviceType); + resultSet = stmt.executeQuery(); + while (resultSet.next()) { + mobileFeature = new MobileFeature(); + mobileFeature.setId(resultSet.getInt(1)); + mobileFeature.setCode(resultSet.getString(2)); + mobileFeature.setName(resultSet.getString(3)); + mobileFeature.setDescription(resultSet.getString(4)); + mobileFeature.setDeviceType(resultSet.getString(5)); + mobileFeatures.add(mobileFeature); + } + if (log.isDebugEnabled()) { + log.debug("Fetched all MobileFeatures of type " + deviceType + " from the MDM" + + " database."); + } + return mobileFeatures; + } catch (SQLException e) { + String msg = "Error occurred while fetching all features.'"; + log.error(msg, e); + throw new MobileDeviceManagementDAOException(msg, e); + } finally { + MobileDeviceManagementDAOUtil.cleanupResources(conn, stmt, resultSet); + } + } + + private Connection getConnection() throws MobileDeviceManagementDAOException { + try { + return dataSource.getConnection(); + } catch (SQLException e) { + String msg = "Error occurred while obtaining a connection from the mobile specific " + + "datasource."; + log.error(msg, e); + throw new MobileDeviceManagementDAOException(msg, e); + } + } + +} diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/main/java/org/wso2/carbon/device/mgt/mobile/dao/util/MobileDeviceManagementDAOUtil.java b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/main/java/org/wso2/carbon/device/mgt/mobile/dao/util/MobileDeviceManagementDAOUtil.java new file mode 100644 index 0000000000..0f72e82e99 --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/main/java/org/wso2/carbon/device/mgt/mobile/dao/util/MobileDeviceManagementDAOUtil.java @@ -0,0 +1,139 @@ +/* + * 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.wso2.carbon.device.mgt.mobile.dao.util; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.device.mgt.mobile.common.MobileDeviceMgtPluginException; +import org.wso2.carbon.device.mgt.mobile.config.datasource.JNDILookupDefinition; +import org.wso2.carbon.device.mgt.mobile.config.datasource.MobileDataSourceConfig; +import org.wso2.carbon.device.mgt.mobile.util.MobileDeviceManagementSchemaInitializer; + +import javax.naming.InitialContext; +import javax.sql.DataSource; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.Hashtable; +import java.util.List; + +/** + * Utility method required by MobileDeviceManagement DAO classes. + */ +public class MobileDeviceManagementDAOUtil { + + private static final Log log = LogFactory.getLog(MobileDeviceManagementDAOUtil.class); + + public static DataSource lookupDataSource(String dataSourceName, + final Hashtable jndiProperties){ + + try { + if (jndiProperties == null || jndiProperties.isEmpty()) { + return (DataSource) InitialContext.doLookup(dataSourceName); + } + final InitialContext context = new InitialContext(jndiProperties); + return (DataSource) context.lookup(dataSourceName); + } catch (Exception e) { + String msg = "Error in looking up data source: " + e.getMessage(); + log.error(msg, e); + throw new RuntimeException(msg + e.getMessage(), e); + } + } + + public static void cleanupResources(Connection conn, PreparedStatement stmt, ResultSet rs) { + if (rs != null) { + try { + rs.close(); + } catch (SQLException e) { + log.warn("Error occurred while closing result set", e); + } + } + if (stmt != null) { + try { + stmt.close(); + } catch (SQLException e) { + log.warn("Error occurred while closing prepared statement", e); + } + } + if (conn != null) { + try { + conn.close(); + } catch (SQLException e) { + log.warn("Error occurred while closing database connection", e); + } + } + } + + public static void cleanupResources(PreparedStatement stmt, ResultSet rs) { + cleanupResources(null, stmt, rs); + } + + /** + * Creates the mobile device management schema. + * + * @param dataSource Mobile data source + */ + public static void setupMobileDeviceManagementSchema(DataSource dataSource, String pluginType) throws MobileDeviceMgtPluginException { + MobileDeviceManagementSchemaInitializer initializer = + new MobileDeviceManagementSchemaInitializer(dataSource, pluginType); + log.info("Initializing mobile device management repository database schema for : " + pluginType); + try { + initializer.createRegistryDatabase(); + } catch (Exception e) { + throw new MobileDeviceMgtPluginException("Error occurred while initializing Mobile Device " + + "Management database schema", e); + } + } + + + /** + * Resolve data source from the data source definition + * + * @param config data source configuration + * @return data source resolved from the data source definition + */ + private static DataSource resolveDataSource(MobileDataSourceConfig config) { + DataSource dataSource = null; + if (config == null) { + throw new RuntimeException( + "data source configuration " + "is null and " + + "thus, is not initialized"); + } + JNDILookupDefinition jndiConfig = config.getJndiLookupDefinition(); + if (jndiConfig != null) { + if (log.isDebugEnabled()) { + log.debug("Initializing data source using the JNDI " + + "Lookup Definition"); + } + List jndiPropertyList = + jndiConfig.getJndiProperties(); + if (jndiPropertyList != null) { + Hashtable jndiProperties = new Hashtable(); + for (JNDILookupDefinition.JNDIProperty prop : jndiPropertyList) { + jndiProperties.put(prop.getName(), prop.getValue()); + } + dataSource = MobileDeviceManagementDAOUtil.lookupDataSource(jndiConfig.getJndiName(), jndiProperties); + } else { + dataSource = MobileDeviceManagementDAOUtil.lookupDataSource(jndiConfig.getJndiName(), null); + } + } + return dataSource; + } +} diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/main/java/org/wso2/carbon/device/mgt/mobile/dto/MobileDevice.java b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/main/java/org/wso2/carbon/device/mgt/mobile/dto/MobileDevice.java new file mode 100644 index 0000000000..185168da49 --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/main/java/org/wso2/carbon/device/mgt/mobile/dto/MobileDevice.java @@ -0,0 +1,125 @@ +/* + * 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.wso2.carbon.device.mgt.mobile.dto; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; + +/** + * DTO of MobileDevice. + */ +public class MobileDevice implements Serializable { + + private String mobileDeviceId; + private String osVersion; + private String model; + private String vendor; + private String latitude; + private String longitude; + private String imei; + private String imsi; + private String serial; + private Map deviceProperties; + + public MobileDevice() { + this.deviceProperties = new HashMap<>(); + } + + public String getMobileDeviceId() { + return mobileDeviceId; + } + + public void setMobileDeviceId(String mobileDeviceId) { + this.mobileDeviceId = mobileDeviceId; + } + + public String getOsVersion() { + return osVersion; + } + + public void setOsVersion(String osVersion) { + this.osVersion = osVersion; + } + + public String getModel() { + return model; + } + + public void setModel(String model) { + this.model = model; + } + + public String getVendor() { + return vendor; + } + + public void setVendor(String vendor) { + this.vendor = vendor; + } + + public String getLatitude() { + return latitude; + } + + public void setLatitude(String latitude) { + this.latitude = latitude; + } + + public String getLongitude() { + return longitude; + } + + public void setLongitude(String longitude) { + this.longitude = longitude; + } + + public String getImei() { + return imei; + } + + public void setImei(String imei) { + this.imei = imei; + } + + public String getImsi() { + return imsi; + } + + public void setImsi(String imsi) { + this.imsi = imsi; + } + + public String getSerial() { + return serial; + } + + public void setSerial(String serial) { + this.serial = serial; + } + + public Map getDeviceProperties() { + return deviceProperties; + } + + public void setDeviceProperties(Map deviceProperties) { + this.deviceProperties = deviceProperties; + } + +} diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/main/java/org/wso2/carbon/device/mgt/mobile/dto/MobileDeviceOperationMapping.java b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/main/java/org/wso2/carbon/device/mgt/mobile/dto/MobileDeviceOperationMapping.java new file mode 100644 index 0000000000..0ae7e68fa1 --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/main/java/org/wso2/carbon/device/mgt/mobile/dto/MobileDeviceOperationMapping.java @@ -0,0 +1,86 @@ +/* + * 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.wso2.carbon.device.mgt.mobile.dto; + +/** + * DTO of Mobile Device Operation Mappings. + */ +public class MobileDeviceOperationMapping { + + private String deviceId; + private int operationId; + private long sentDate; + private long receivedDate; + private Status status; + + public enum Status { + NEW, INPROGRESS, COMPLETED + } + + public Status getStatus() { + return status; + } + + public void setStatus(Status status) { + this.status = status; + } + + public void setStatus(String status) { + if(Status.NEW.name().equals(status)){ + this.status = Status.NEW; + }else if(Status.INPROGRESS.name().equals(status)){ + this.status = Status.INPROGRESS; + }else if(Status.COMPLETED.name().equals(status)){ + this.status = Status.COMPLETED; + } + } + + public String getDeviceId() { + return deviceId; + } + + public void setDeviceId(String deviceId) { + this.deviceId = deviceId; + } + + public int getOperationId() { + return operationId; + } + + public void setOperationId(int operationId) { + this.operationId = operationId; + } + + public long getSentDate() { + return sentDate; + } + + public void setSentDate(long sentDate) { + this.sentDate = sentDate; + } + + public long getReceivedDate() { + return receivedDate; + } + + public void setReceivedDate(long receivedDate) { + this.receivedDate = receivedDate; + } + +} diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/main/java/org/wso2/carbon/device/mgt/mobile/dto/MobileFeature.java b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/main/java/org/wso2/carbon/device/mgt/mobile/dto/MobileFeature.java new file mode 100644 index 0000000000..9ec5d30b30 --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/main/java/org/wso2/carbon/device/mgt/mobile/dto/MobileFeature.java @@ -0,0 +1,73 @@ +/* + * 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.wso2.carbon.device.mgt.mobile.dto; + +import java.io.Serializable; + +/** + * DTO of Mobile features. + */ +public class MobileFeature implements Serializable { + + private int id; + private String deviceType; + private String code; + private String name; + private String description; + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getCode() { + return code; + } + + public void setCode(String code) { + this.code = code; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getDeviceType() { + return deviceType; + } + + public void setDeviceType(String deviceType) { + this.deviceType = deviceType; + } +} diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/main/java/org/wso2/carbon/device/mgt/mobile/dto/MobileFeatureProperty.java b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/main/java/org/wso2/carbon/device/mgt/mobile/dto/MobileFeatureProperty.java new file mode 100644 index 0000000000..cc99849deb --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/main/java/org/wso2/carbon/device/mgt/mobile/dto/MobileFeatureProperty.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.wso2.carbon.device.mgt.mobile.dto; + +/** + * DTO of Mobile feature property. Represents a property of a mobile feature. + */ +public class MobileFeatureProperty { + + private String property; + private Integer featureID; + + public Integer getFeatureID() { + return featureID; + } + + public void setFeatureID(Integer featureID) { + this.featureID = featureID; + } + + public String getProperty() { + return property; + } + + public void setProperty(String property) { + this.property = property; + } + +} \ No newline at end of file diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/main/java/org/wso2/carbon/device/mgt/mobile/dto/MobileOperation.java b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/main/java/org/wso2/carbon/device/mgt/mobile/dto/MobileOperation.java new file mode 100644 index 0000000000..7c0b9040ce --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/main/java/org/wso2/carbon/device/mgt/mobile/dto/MobileOperation.java @@ -0,0 +1,64 @@ +/* + * 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.wso2.carbon.device.mgt.mobile.dto; + +import java.util.List; + +/** + * DTO of MobileOperation. + */ +public class MobileOperation { + + private int operationId; + private String featureCode; + private long createdDate; + private List properties; + + public int getOperationId() { + return operationId; + } + + public void setOperationId(int operationId) { + this.operationId = operationId; + } + + public List getProperties() { + return properties; + } + + public void setProperties(List properties) { + this.properties = properties; + } + + public String getFeatureCode() { + return featureCode; + } + + public void setFeatureCode(String featureCode) { + this.featureCode = featureCode; + } + + public long getCreatedDate() { + return createdDate; + } + + public void setCreatedDate(long createdDate) { + this.createdDate = createdDate; + } +} \ No newline at end of file diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/main/java/org/wso2/carbon/device/mgt/mobile/dto/MobileOperationProperty.java b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/main/java/org/wso2/carbon/device/mgt/mobile/dto/MobileOperationProperty.java new file mode 100644 index 0000000000..d18126affa --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/main/java/org/wso2/carbon/device/mgt/mobile/dto/MobileOperationProperty.java @@ -0,0 +1,54 @@ +/* + * 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.wso2.carbon.device.mgt.mobile.dto; + +/** + * DTO of Mobile Operation property. + */ +public class MobileOperationProperty { + + private int operationId; + private String property; + private String value; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + public int getOperationId() { + return operationId; + } + + public void setOperationId(int operationId) { + this.operationId = operationId; + } + + public String getProperty() { + return property; + } + + public void setProperty(String property) { + this.property = property; + } + +} \ No newline at end of file diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/main/java/org/wso2/carbon/device/mgt/mobile/internal/MobileDeviceManagementDataHolder.java b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/main/java/org/wso2/carbon/device/mgt/mobile/internal/MobileDeviceManagementDataHolder.java new file mode 100644 index 0000000000..d8943def17 --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/main/java/org/wso2/carbon/device/mgt/mobile/internal/MobileDeviceManagementDataHolder.java @@ -0,0 +1,48 @@ +/* + * 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.wso2.carbon.device.mgt.mobile.internal; + +import org.wso2.carbon.device.mgt.common.spi.DeviceManagementService; +import org.wso2.carbon.registry.core.service.RegistryService; + +/** + * DataHolder class of Mobile plugins component. + */ +public class MobileDeviceManagementDataHolder { + + private RegistryService registryService; + + private static MobileDeviceManagementDataHolder thisInstance = new MobileDeviceManagementDataHolder(); + + private MobileDeviceManagementDataHolder() { + } + + public static MobileDeviceManagementDataHolder getInstance() { + return thisInstance; + } + + public RegistryService getRegistryService() { + return registryService; + } + + public void setRegistryService(RegistryService registryService) { + this.registryService = registryService; + } + +} diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/main/java/org/wso2/carbon/device/mgt/mobile/internal/MobileDeviceManagementServiceComponent.java b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/main/java/org/wso2/carbon/device/mgt/mobile/internal/MobileDeviceManagementServiceComponent.java new file mode 100644 index 0000000000..e08771b3be --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/main/java/org/wso2/carbon/device/mgt/mobile/internal/MobileDeviceManagementServiceComponent.java @@ -0,0 +1,137 @@ +/* + * 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.wso2.carbon.device.mgt.mobile.internal; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.osgi.framework.BundleContext; +import org.osgi.framework.ServiceRegistration; +import org.osgi.service.component.ComponentContext; +import org.wso2.carbon.device.mgt.mobile.common.MobileDeviceMgtPluginException; +import org.wso2.carbon.device.mgt.mobile.config.MobileDeviceConfigurationManager; +import org.wso2.carbon.device.mgt.mobile.config.MobileDeviceManagementConfig; +import org.wso2.carbon.device.mgt.mobile.config.datasource.MobileDataSourceConfig; +import org.wso2.carbon.device.mgt.mobile.dao.AbstractMobileDeviceManagementDAOFactory; +import org.wso2.carbon.device.mgt.mobile.dao.util.MobileDeviceManagementDAOUtil; +import org.wso2.carbon.ndatasource.core.DataSourceService; +import org.wso2.carbon.registry.core.service.RegistryService; + +import java.util.Map; + +/** + * @scr.component name="org.wso2.carbon.device.mgt.mobile.impl.internal.MobileDeviceManagementServiceComponent" + * immediate="true" + * @scr.reference name="org.wso2.carbon.ndatasource" + * interface="org.wso2.carbon.ndatasource.core.DataSourceService" + * cardinality="1..1" + * policy="dynamic" + * bind="setDataSourceService" + * unbind="unsetDataSourceService" + * @scr.reference name="registry.service" + * interface="org.wso2.carbon.registry.core.service.RegistryService" cardinality="0..1" + * policy="dynamic" bind="setRegistryService" unbind="unsetRegistryService" + *

      + * Adding reference to API Manager Configuration service is an unavoidable hack to get rid of NPEs thrown while + * initializing APIMgtDAOs attempting to register APIs programmatically. APIMgtDAO needs to be proper cleaned up + * to avoid as an ideal fix + */ +public class MobileDeviceManagementServiceComponent { + + private static final Log log = LogFactory.getLog(MobileDeviceManagementServiceComponent.class); + + protected void activate(ComponentContext ctx) { + + if (log.isDebugEnabled()) { + log.debug("Activating Mobile Device Management Service Component"); + } + try { + BundleContext bundleContext = ctx.getBundleContext(); + + /* Initialize the data source configuration */ + MobileDeviceConfigurationManager.getInstance().initConfig(); + MobileDeviceManagementConfig config = MobileDeviceConfigurationManager.getInstance() + .getMobileDeviceManagementConfig(); + Map dsConfigMap = + config.getMobileDeviceMgtRepository().getMobileDataSourceConfigMap(); + + AbstractMobileDeviceManagementDAOFactory.init(dsConfigMap); + + String setupOption = System.getProperty("setup"); + if (setupOption != null) { + if (log.isDebugEnabled()) { + log.debug( + "-Dsetup is enabled. Mobile Device management repository schema initialization is about " + + "to begin"); + } + try { + for (String pluginType : dsConfigMap.keySet()) { + MobileDeviceManagementDAOUtil + .setupMobileDeviceManagementSchema(AbstractMobileDeviceManagementDAOFactory.getDataSourceMap + ().get(pluginType), pluginType); + } + } catch (MobileDeviceMgtPluginException e) { + log.error("Exception occurred while initializing mobile device management database schema", e); + } + } + if (log.isDebugEnabled()) { + log.debug("Mobile Device Management Service Component has been successfully activated"); + } + } catch (Throwable e) { + log.error("Error occurred while activating Mobile Device Management Service Component", e); + } + } + + protected void deactivate(ComponentContext ctx) { + if (log.isDebugEnabled()) { + log.debug("De-activating Mobile Device Management Service Component"); + } + try { + if (log.isDebugEnabled()) { + log.debug( + "Mobile Device Management Service Component has been successfully de-activated"); + } + } catch (Throwable e) { + log.error("Error occurred while de-activating Mobile Device Management bundle", e); + } + } + + protected void setDataSourceService(DataSourceService dataSourceService) { + /* This is to avoid mobile device management component getting initialized before the underlying datasources + are registered */ + if (log.isDebugEnabled()) { + log.debug("Data source service set to mobile service component"); + } + } + + protected void unsetDataSourceService(DataSourceService dataSourceService) { + //do nothing + } + + protected void setRegistryService(RegistryService registryService) { + if (log.isDebugEnabled()) { + log.debug("RegistryService acquired"); + } + MobileDeviceManagementDataHolder.getInstance().setRegistryService(registryService); + } + + protected void unsetRegistryService(RegistryService registryService) { + MobileDeviceManagementDataHolder.getInstance().setRegistryService(null); + } + +} diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/main/java/org/wso2/carbon/device/mgt/mobile/util/MobileDeviceManagementSchemaInitializer.java b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/main/java/org/wso2/carbon/device/mgt/mobile/util/MobileDeviceManagementSchemaInitializer.java new file mode 100644 index 0000000000..f0071a85f5 --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/main/java/org/wso2/carbon/device/mgt/mobile/util/MobileDeviceManagementSchemaInitializer.java @@ -0,0 +1,62 @@ +/* + * 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.wso2.carbon.device.mgt.mobile.util; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.device.mgt.mobile.common.MobilePluginConstants; +import org.wso2.carbon.utils.CarbonUtils; +import org.wso2.carbon.utils.dbcreator.DatabaseCreator; + +import javax.sql.DataSource; +import java.io.File; + +/** + * + * Provides methods for initializing the database script. + * + */ +public final class MobileDeviceManagementSchemaInitializer extends DatabaseCreator { + + private static final Log log = LogFactory.getLog(MobileDeviceManagementSchemaInitializer.class); + private static final String setupSQLScriptBaseLocation = + CarbonUtils.getCarbonHome() + File.separator + "dbscripts" + File.separator + + MobilePluginConstants.MOBILE_DB_SCRIPTS_FOLDER + + File.separator + "plugins" + File.separator; + private String pluginType; + + public String getPluginType() { + return pluginType; + } + + public MobileDeviceManagementSchemaInitializer(DataSource dataSource, String pType) { + super(dataSource); + this.pluginType = pType; + } + + protected String getDbScriptLocation(String databaseType) { + String scriptName = databaseType + ".sql"; + String scriptLocation = setupSQLScriptBaseLocation + this.getPluginType() + File.separator + scriptName; + if (log.isDebugEnabled()) { + log.debug("Loading database script from :" + scriptLocation); + } + return scriptLocation; + } + +} diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/main/java/org/wso2/carbon/device/mgt/mobile/util/MobileDeviceManagementUtil.java b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/main/java/org/wso2/carbon/device/mgt/mobile/util/MobileDeviceManagementUtil.java new file mode 100644 index 0000000000..73612a365b --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/main/java/org/wso2/carbon/device/mgt/mobile/util/MobileDeviceManagementUtil.java @@ -0,0 +1,320 @@ +/* + * 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.wso2.carbon.device.mgt.mobile.util; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.w3c.dom.Document; +import org.wso2.carbon.context.PrivilegedCarbonContext; +import org.wso2.carbon.device.mgt.common.Device; +import org.wso2.carbon.device.mgt.common.DeviceManagementConstants; +import org.wso2.carbon.device.mgt.common.DeviceManagementException; +import org.wso2.carbon.device.mgt.common.Feature; +import org.wso2.carbon.device.mgt.common.operation.mgt.Operation; +import org.wso2.carbon.device.mgt.mobile.common.MobileDeviceMgtPluginException; +import org.wso2.carbon.device.mgt.mobile.common.MobilePluginConstants; +import org.wso2.carbon.device.mgt.mobile.dto.*; +import org.wso2.carbon.device.mgt.mobile.internal.MobileDeviceManagementDataHolder; +import org.wso2.carbon.registry.api.RegistryException; +import org.wso2.carbon.registry.api.Resource; +import org.wso2.carbon.registry.core.Registry; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import java.io.File; +import java.util.*; + +/** + * Provides utility methods required by the mobile device management bundle. + */ +public class MobileDeviceManagementUtil { + + private static final Log log = LogFactory.getLog(MobileDeviceManagementUtil.class); + private static final String MOBILE_DEVICE_IMEI = "IMEI"; + private static final String MOBILE_DEVICE_IMSI = "IMSI"; + private static final String MOBILE_DEVICE_VENDOR = "VENDOR"; + private static final String MOBILE_DEVICE_OS_VERSION = "OS_VERSION"; + private static final String MOBILE_DEVICE_MODEL = "DEVICE_MODEL"; + private static final String MOBILE_DEVICE_LATITUDE = "LATITUDE"; + private static final String MOBILE_DEVICE_LONGITUDE = "LONGITUDE"; + private static final String MOBILE_DEVICE_SERIAL = "SERIAL"; + + public static Document convertToDocument(File file) throws DeviceManagementException { + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + factory.setNamespaceAware(true); + try { + DocumentBuilder docBuilder = factory.newDocumentBuilder(); + return docBuilder.parse(file); + } catch (Exception e) { + throw new DeviceManagementException( + "Error occurred while parsing file, while converting " + + "to a org.w3c.dom.Document : " + e.getMessage(), e); + } + } + + private static String getPropertyValue(Device device, String property) { + for (Device.Property prop : device.getProperties()) { + if (property.equals(prop.getName())) { + return prop.getValue(); + } + } + return null; + } + + private static Device.Property getProperty(String property, String value) { + if (property != null) { + Device.Property prop = new Device.Property(); + prop.setName(property); + prop.setValue(value); + return prop; + } + return null; + } + + public static MobileDevice convertToMobileDevice(Device device) { + MobileDevice mobileDevice = null; + if (device != null) { + mobileDevice = new MobileDevice(); + mobileDevice.setMobileDeviceId(device.getDeviceIdentifier()); + mobileDevice.setImei(getPropertyValue(device, MOBILE_DEVICE_IMEI)); + mobileDevice.setImsi(getPropertyValue(device, MOBILE_DEVICE_IMSI)); + mobileDevice.setModel(getPropertyValue(device, MOBILE_DEVICE_MODEL)); + mobileDevice.setOsVersion(getPropertyValue(device, MOBILE_DEVICE_OS_VERSION)); + mobileDevice.setVendor(getPropertyValue(device, MOBILE_DEVICE_VENDOR)); + mobileDevice.setLatitude(getPropertyValue(device, MOBILE_DEVICE_LATITUDE)); + mobileDevice.setLongitude(getPropertyValue(device, MOBILE_DEVICE_LONGITUDE)); + + if (device.getProperties() != null) { + Map deviceProperties = new HashMap(); + for (Device.Property deviceProperty : device.getProperties()) { + deviceProperties.put(deviceProperty.getName(), deviceProperty.getValue()); + } + + mobileDevice.setDeviceProperties(deviceProperties); + } else { + mobileDevice.setDeviceProperties(new HashMap()); + } + } + return mobileDevice; + } + + public static Device convertToDevice(MobileDevice mobileDevice) { + Device device = null; + if (mobileDevice != null) { + device = new Device(); + List propertyList = new ArrayList(); + propertyList.add(getProperty(MOBILE_DEVICE_IMEI, mobileDevice.getImei())); + propertyList.add(getProperty(MOBILE_DEVICE_IMSI, mobileDevice.getImsi())); + propertyList.add(getProperty(MOBILE_DEVICE_MODEL, mobileDevice.getModel())); + propertyList.add(getProperty(MOBILE_DEVICE_OS_VERSION, mobileDevice.getOsVersion())); + propertyList.add(getProperty(MOBILE_DEVICE_VENDOR, mobileDevice.getVendor())); + if(mobileDevice.getLatitude() != null) { + propertyList.add(getProperty(MOBILE_DEVICE_LATITUDE, mobileDevice.getLatitude())); + } + if(mobileDevice.getLongitude() != null) { + propertyList.add(getProperty(MOBILE_DEVICE_LONGITUDE, mobileDevice.getLongitude())); + } + propertyList.add(getProperty(MOBILE_DEVICE_SERIAL, mobileDevice.getSerial())); + + if (mobileDevice.getDeviceProperties() != null) { + for (Map.Entry deviceProperty : mobileDevice.getDeviceProperties() + .entrySet()) { + propertyList + .add(getProperty(deviceProperty.getKey(), deviceProperty.getValue())); + } + } + + device.setProperties(propertyList); + device.setDeviceIdentifier(mobileDevice.getMobileDeviceId()); + } + return device; + } + + public static MobileOperation convertToMobileOperation(Operation operation) { + MobileOperation mobileOperation = new MobileOperation(); + MobileOperationProperty operationProperty; + List properties = new LinkedList(); + mobileOperation.setFeatureCode(operation.getCode()); + mobileOperation.setCreatedDate(new Date().getTime()); + Properties operationProperties = operation.getProperties(); + for (String key : operationProperties.stringPropertyNames()) { + operationProperty = new MobileOperationProperty(); + operationProperty.setProperty(key); + operationProperty.setValue(operationProperties.getProperty(key)); + properties.add(operationProperty); + } + mobileOperation.setProperties(properties); + return mobileOperation; + } + + public static List getMobileOperationIdsFromMobileDeviceOperations( + List mobileDeviceOperationMappings) { + List mobileOperationIds = new ArrayList(mobileDeviceOperationMappings.size()); + for (MobileDeviceOperationMapping mobileDeviceOperationMapping : mobileDeviceOperationMappings) { + mobileOperationIds.add(mobileDeviceOperationMapping.getOperationId()); + } + return mobileOperationIds; + } + + public static Operation convertMobileOperationToOperation(MobileOperation mobileOperation) { + Operation operation = new Operation(); + Properties properties = new Properties(); + operation.setCode(mobileOperation.getFeatureCode()); + for (MobileOperationProperty mobileOperationProperty : mobileOperation.getProperties()) { + properties + .put(mobileOperationProperty.getProperty(), mobileOperationProperty.getValue()); + } + operation.setProperties(properties); + return operation; + } + + public static MobileFeature convertToMobileFeature(Feature feature) { + MobileFeature mobileFeature = new MobileFeature(); + mobileFeature.setName(feature.getName()); + mobileFeature.setCode(feature.getCode()); + mobileFeature.setDescription(feature.getDescription()); + mobileFeature.setDeviceType(feature.getDeviceType()); + return mobileFeature; + } + + public static Feature convertToFeature(MobileFeature mobileFeature) { + Feature feature = new Feature(); + feature.setDescription(mobileFeature.getDescription()); + feature.setDeviceType(mobileFeature.getDeviceType()); + feature.setCode(mobileFeature.getCode()); + feature.setName(mobileFeature.getName()); + return feature; + } + + public static Registry getConfigurationRegistry() throws MobileDeviceMgtPluginException { + try { + int tenantId = PrivilegedCarbonContext.getThreadLocalCarbonContext().getTenantId(); + return MobileDeviceManagementDataHolder.getInstance().getRegistryService() + .getConfigSystemRegistry( + tenantId); + } catch (RegistryException e) { + throw new MobileDeviceMgtPluginException( + "Error in retrieving conf registry instance: " + + e.getMessage(), e); + } + } + + public static Resource getRegistryResource(String path) throws MobileDeviceMgtPluginException { + try { + if(MobileDeviceManagementUtil.getConfigurationRegistry().resourceExists(path)){ + return MobileDeviceManagementUtil.getConfigurationRegistry().get(path); + } + return null; + } catch (RegistryException e) { + throw new MobileDeviceMgtPluginException("Error in retrieving registry resource : " + + e.getMessage(), e); + } + } + + public static boolean putRegistryResource(String path, + Resource resource) + throws MobileDeviceMgtPluginException { + boolean status; + try { + MobileDeviceManagementUtil.getConfigurationRegistry().beginTransaction(); + MobileDeviceManagementUtil.getConfigurationRegistry().put(path, resource); + MobileDeviceManagementUtil.getConfigurationRegistry().commitTransaction(); + status = true; + } catch (RegistryException e) { + throw new MobileDeviceMgtPluginException( + "Error occurred while persisting registry resource : " + + e.getMessage(), e); + } + return status; + } + + public static String getResourcePath(String resourceName, String platform) { + String regPath = ""; + switch (platform) { + case DeviceManagementConstants.MobileDeviceTypes.MOBILE_DEVICE_TYPE_ANDROID: + regPath = MobilePluginConstants.MOBILE_CONFIG_REGISTRY_ROOT + "/" + + DeviceManagementConstants.MobileDeviceTypes.MOBILE_DEVICE_TYPE_ANDROID + + "/" + resourceName; + break; + case DeviceManagementConstants.MobileDeviceTypes.MOBILE_DEVICE_TYPE_WINDOWS: + regPath = MobilePluginConstants.MOBILE_CONFIG_REGISTRY_ROOT + "/" + + DeviceManagementConstants.MobileDeviceTypes.MOBILE_DEVICE_TYPE_WINDOWS + + "/" + resourceName; + break; + case DeviceManagementConstants.MobileDeviceTypes.MOBILE_DEVICE_TYPE_IOS: + regPath = MobilePluginConstants.MOBILE_CONFIG_REGISTRY_ROOT + "/" + + DeviceManagementConstants.MobileDeviceTypes.MOBILE_DEVICE_TYPE_IOS + + "/" + resourceName; + break; + } + return regPath; + } + + public static String getPlatformConfigPath(String platform) { + String regPath = ""; + switch (platform) { + case DeviceManagementConstants.MobileDeviceTypes.MOBILE_DEVICE_TYPE_ANDROID: + regPath = DeviceManagementConstants.MobileDeviceTypes.MOBILE_DEVICE_TYPE_ANDROID; + break; + case DeviceManagementConstants.MobileDeviceTypes.MOBILE_DEVICE_TYPE_WINDOWS: + regPath = DeviceManagementConstants.MobileDeviceTypes.MOBILE_DEVICE_TYPE_WINDOWS; + break; + case DeviceManagementConstants.MobileDeviceTypes.MOBILE_DEVICE_TYPE_IOS: + regPath = DeviceManagementConstants.MobileDeviceTypes.MOBILE_DEVICE_TYPE_IOS; + break; + } + return regPath; + } + + public static boolean createRegistryCollection(String path) + throws MobileDeviceMgtPluginException { + try { + if (! MobileDeviceManagementUtil.getConfigurationRegistry().resourceExists(path)) { + Resource resource = MobileDeviceManagementUtil.getConfigurationRegistry().newCollection(); + MobileDeviceManagementUtil.getConfigurationRegistry().beginTransaction(); + MobileDeviceManagementUtil.getConfigurationRegistry().put(path, resource); + MobileDeviceManagementUtil.getConfigurationRegistry().commitTransaction(); + } + return true; + } catch (MobileDeviceMgtPluginException e) { + throw new MobileDeviceMgtPluginException( + "Error occurred while creating a registry collection : " + + e.getMessage(), e); + } catch (RegistryException e) { + throw new MobileDeviceMgtPluginException( + "Error occurred while creating a registry collection : " + + e.getMessage(), e); + } + } + + public static List getMissingFeatures(List supportedFeatures, List existingFeatures) { + HashMap featureHashMap = new HashMap(); + for (Feature feature: existingFeatures) { + featureHashMap.put(feature.getCode(),feature); + } + List missingFeatures = new ArrayList(); + for (Feature supportedFeature : supportedFeatures) { + if (featureHashMap.get(supportedFeature.getCode()) != null) { + continue; + } + missingFeatures.add(supportedFeature); + } + return missingFeatures; + } +} diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/test/java/org/wso2/carbon/device/mgt/mobile/impl/MobileDeviceManagementConfigTests.java b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/test/java/org/wso2/carbon/device/mgt/mobile/impl/MobileDeviceManagementConfigTests.java new file mode 100644 index 0000000000..461de8a1ad --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/test/java/org/wso2/carbon/device/mgt/mobile/impl/MobileDeviceManagementConfigTests.java @@ -0,0 +1,141 @@ +/* + * 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.wso2.carbon.device.mgt.mobile.impl; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.testng.Assert; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; +import org.wso2.carbon.device.mgt.mobile.config.MobileDeviceManagementConfig; +import org.xml.sax.SAXException; +import org.xml.sax.SAXParseException; + +import javax.xml.XMLConstants; +import javax.xml.bind.JAXBContext; +import javax.xml.bind.JAXBException; +import javax.xml.bind.Unmarshaller; +import javax.xml.validation.Schema; +import javax.xml.validation.SchemaFactory; +import java.io.File; + +/** + * Class for holding unit-tests related to MobileDeviceManagementConfig class. + */ + +public class MobileDeviceManagementConfigTests { + + private static final Log log = LogFactory.getLog(MobileDeviceManagementConfigTests.class); + private static final String MALFORMED_TEST_CONFIG_LOCATION_NO_MGT_REPOSITORY = + "./src/test/resources/config/malformed-mobile-config-no-mgt-repo.xml"; + private static final String MALFORMED_TEST_CONFIG_LOCATION_NO_DS_CONFIG = + "./src/test/resources/config/malformed-mobile-config-no-ds-config.xml"; + private static final String MALFORMED_TEST_CONFIG_LOCATION_NO_JNDI_CONFIG = + "./src/test/resources/config/malformed-mobile-config-no-jndi-config.xml"; + private static final String MALFORMED_TEST_CONFIG_LOCATION_NO_APIS_CONFIG = + "./src/test/resources/config/malformed-mobile-config-no-apis-config.xml"; + private static final String MALFORMED_TEST_CONFIG_LOCATION_NO_API_CONFIG = + "./src/test/resources/config/malformed-mobile-config-no-api-config.xml"; + private static final String MALFORMED_TEST_CONFIG_LOCATION_NO_API_PUBLISHER_CONFIG = + "./src/test/resources/config/malformed-mobile-config-no-api-publisher-config.xml"; + private static final String TEST_CONFIG_SCHEMA_LOCATION = + "./src/test/resources/config/schema/MobileDeviceManagementConfigSchema.xsd"; + + private Schema schema; + + @BeforeClass + private void initSchema() { + File deviceManagementSchemaConfig = + new File(MobileDeviceManagementConfigTests.TEST_CONFIG_SCHEMA_LOCATION); + SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); + try { + schema = factory.newSchema(deviceManagementSchemaConfig); + } catch (SAXException e) { + Assert.fail("Invalid schema found", e); + } + } + + @Test + public void testMandateManagementRepositoryElement() { + File malformedConfig = + new File( + MobileDeviceManagementConfigTests.MALFORMED_TEST_CONFIG_LOCATION_NO_MGT_REPOSITORY); + this.validateMalformedConfig(malformedConfig); + } + + @Test + public void testMandateDataSourceConfigurationElement() { + File malformedConfig = new File( + MobileDeviceManagementConfigTests.MALFORMED_TEST_CONFIG_LOCATION_NO_DS_CONFIG); + this.validateMalformedConfig(malformedConfig); + } + + @Test + public void testMandateJndiLookupDefinitionElement() { + File malformedConfig = new File( + MobileDeviceManagementConfigTests.MALFORMED_TEST_CONFIG_LOCATION_NO_JNDI_CONFIG); + this.validateMalformedConfig(malformedConfig); + } + + @Test + public void testMandateAPIPublisherElement() { + File malformedConfig = new File( + MobileDeviceManagementConfigTests.MALFORMED_TEST_CONFIG_LOCATION_NO_API_PUBLISHER_CONFIG); + this.validateMalformedConfig(malformedConfig); + } + + @Test + public void testMandateAPIsElement() { + File malformedConfig = new File( + MobileDeviceManagementConfigTests.MALFORMED_TEST_CONFIG_LOCATION_NO_APIS_CONFIG); + this.validateMalformedConfig(malformedConfig); + } + + @Test + public void testMandateAPIElement() { + File malformedConfig = new File( + MobileDeviceManagementConfigTests.MALFORMED_TEST_CONFIG_LOCATION_NO_API_CONFIG); + this.validateMalformedConfig(malformedConfig); + } + + /** + * Validates a given malformed-configuration file. + */ + private void validateMalformedConfig(File malformedConfig) { + try { + JAXBContext ctx = JAXBContext.newInstance(MobileDeviceManagementConfig.class); + Unmarshaller um = ctx.createUnmarshaller(); + um.setSchema(this.getSchema()); + um.unmarshal(malformedConfig); + Assert.assertTrue(false); + } catch (JAXBException e) { + Throwable linkedException = e.getLinkedException(); + if (!(linkedException instanceof SAXParseException)) { + log.error("Unexpected error occurred while unmarshalling mobile device management config", e); + Assert.assertTrue(false); + } + Assert.assertTrue(true); + } + } + + private Schema getSchema() { + return schema; + } + +} diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/test/java/org/wso2/carbon/device/mgt/mobile/impl/common/DBTypes.java b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/test/java/org/wso2/carbon/device/mgt/mobile/impl/common/DBTypes.java new file mode 100644 index 0000000000..fa263f3849 --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/test/java/org/wso2/carbon/device/mgt/mobile/impl/common/DBTypes.java @@ -0,0 +1,29 @@ +/* + * 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.wso2.carbon.device.mgt.mobile.impl.common; + + +public enum DBTypes { + Oracle("Oracle"),H2("H2"),MySql("MySql"); + + String dbName ; + DBTypes(String dbStrName) { + dbName = dbStrName; + } +} diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/test/java/org/wso2/carbon/device/mgt/mobile/impl/common/TestDBConfiguration.java b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/test/java/org/wso2/carbon/device/mgt/mobile/impl/common/TestDBConfiguration.java new file mode 100644 index 0000000000..042e105bfe --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/test/java/org/wso2/carbon/device/mgt/mobile/impl/common/TestDBConfiguration.java @@ -0,0 +1,90 @@ +/* + * 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.wso2.carbon.device.mgt.mobile.impl.common; + +import javax.xml.bind.annotation.XmlAttribute; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; + +@XmlRootElement(name = "Type") +public class TestDBConfiguration { + + private String connectionURL; + private String driverClassName; + private String username; + private String password; + + @Override public String toString() { + return "TestDataSourceConfiguration{" + + "ConnectionURL='" + connectionURL + '\'' + + ", DriverClassName='" + driverClassName + '\'' + + ", Username='" + username + '\'' + + ", Password='" + password + '\'' + + ", Type='" + dbType + '\'' + + '}'; + } + + private String dbType; + + @XmlElement(name = "ConnectionURL", nillable = false) + public String getConnectionURL() { + return connectionURL; + } + + public void setConnectionURL(String connectionURL) { + this.connectionURL = connectionURL; + } + + @XmlElement(name = "DriverClassName", nillable = false) + public String getDriverClassName() { + return driverClassName; + } + + public void setDriverClassName(String driverClassName) { + this.driverClassName = driverClassName; + } + + @XmlElement(name = "Username", nillable = false) + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + @XmlElement(name = "Password", nillable = false) + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + @XmlAttribute(name = "name") + public String getType() { + return dbType; + } + + public void setType(String type) { + this.dbType = type; + } + +} diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/test/java/org/wso2/carbon/device/mgt/mobile/impl/common/TestDBConfigurations.java b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/test/java/org/wso2/carbon/device/mgt/mobile/impl/common/TestDBConfigurations.java new file mode 100644 index 0000000000..88092ea410 --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/test/java/org/wso2/carbon/device/mgt/mobile/impl/common/TestDBConfigurations.java @@ -0,0 +1,39 @@ +/* + * 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.wso2.carbon.device.mgt.mobile.impl.common; + +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; +import java.util.List; + +@XmlRootElement(name = "TestDataSourceConfigurations") +public class TestDBConfigurations { + + private List dbTypesList; + + @XmlElement(name = "Type") + public List getDbTypesList() { + return dbTypesList; + } + + public void setDbTypesList(List dbTypesList) { + this.dbTypesList = dbTypesList; + } + +} diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/test/java/org/wso2/carbon/device/mgt/mobile/impl/dao/util/MobileDatabaseUtils.java b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/test/java/org/wso2/carbon/device/mgt/mobile/impl/dao/util/MobileDatabaseUtils.java new file mode 100644 index 0000000000..f454f6f064 --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/test/java/org/wso2/carbon/device/mgt/mobile/impl/dao/util/MobileDatabaseUtils.java @@ -0,0 +1,111 @@ +/* + * 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.wso2.carbon.device.mgt.mobile.impl.dao.util; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.w3c.dom.Document; +import org.wso2.carbon.device.mgt.common.DeviceManagementException; +import org.wso2.carbon.device.mgt.mobile.dao.MobileDeviceManagementDAOException; +import org.wso2.carbon.device.mgt.mobile.impl.common.DBTypes; +import org.wso2.carbon.device.mgt.mobile.impl.common.TestDBConfiguration; +import org.wso2.carbon.device.mgt.mobile.impl.common.TestDBConfigurations; +import org.wso2.carbon.device.mgt.mobile.util.MobileDeviceManagementUtil; + +import javax.xml.bind.JAXBContext; +import javax.xml.bind.JAXBException; +import javax.xml.bind.Unmarshaller; +import java.io.File; +import java.sql.*; + +/** + * This class provides the utility methods needed for DAO related test executions. + */ +public class MobileDatabaseUtils { + + private static final Log log = LogFactory.getLog(MobileDatabaseUtils.class); + public static final String TEST_RESOURCES_DB_CONFIG_FILE = + "src/test/resources/testdbconfig.xml"; + + public static void cleanupResources(Connection conn, Statement stmt, ResultSet rs) { + if (rs != null) { + try { + rs.close(); + } catch (SQLException e) { + log.warn("Error occurred while closing result set", e); + } + } + if (stmt != null) { + try { + stmt.close(); + } catch (SQLException e) { + log.warn("Error occurred while closing prepared statement", e); + } + } + if (conn != null) { + try { + conn.close(); + } catch (SQLException e) { + log.warn("Error occurred while closing database connection", e); + } + } + } + + public static TestDBConfiguration getTestDBConfiguration(DBTypes dbType) throws + MobileDeviceManagementDAOException, + DeviceManagementException { + File deviceMgtConfig = new File(TEST_RESOURCES_DB_CONFIG_FILE); + Document doc; + TestDBConfiguration testDBConfiguration = null; + TestDBConfigurations testDBConfigurations; + + doc = MobileDeviceManagementUtil.convertToDocument(deviceMgtConfig); + JAXBContext testDBContext; + + try { + testDBContext = JAXBContext.newInstance(TestDBConfigurations.class); + Unmarshaller unmarshaller = testDBContext.createUnmarshaller(); + testDBConfigurations = (TestDBConfigurations) unmarshaller.unmarshal(doc); + } catch (JAXBException e) { + throw new MobileDeviceManagementDAOException("Error parsing test db configurations", e); + } + + for (TestDBConfiguration testDBConfiguration1 : testDBConfigurations.getDbTypesList()) { + testDBConfiguration = testDBConfiguration1; + if (testDBConfiguration.getType().equals(dbType.toString())) { + break; + } + } + + return testDBConfiguration; + } + + public static void createH2DB(TestDBConfiguration testDBConf) throws Exception { + Connection conn = null; + Statement stmt = null; + try { + Class.forName(testDBConf.getDriverClassName()); + conn = DriverManager.getConnection(testDBConf.getConnectionURL()); + stmt = conn.createStatement(); + stmt.executeUpdate("RUNSCRIPT FROM './src/test/resources/sql/h2.sql'"); + } finally { + cleanupResources(conn, stmt, null); + } + } +} diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/test/resources/config/malformed-mobile-config-no-api-config.xml b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/test/resources/config/malformed-mobile-config-no-api-config.xml new file mode 100644 index 0000000000..3fad2eceb8 --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/test/resources/config/malformed-mobile-config-no-api-config.xml @@ -0,0 +1,41 @@ + + + + + + + + + jdbc/MobileIOSDM_DS + + + + + jdbc/MobileAndroidDM_DS + + + + + jdbc/MobileWindowsDM_DS + + + + + + diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/test/resources/config/malformed-mobile-config-no-api-publisher-config.xml b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/test/resources/config/malformed-mobile-config-no-api-publisher-config.xml new file mode 100644 index 0000000000..94fb504623 --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/test/resources/config/malformed-mobile-config-no-api-publisher-config.xml @@ -0,0 +1,42 @@ + + + + + + + + jdbc/MobileDM_DS + + + + + + + + enrollment + admin + enrollment + 1.0.0 + http://localhost:9763/ + http,https + + + + + diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/test/resources/config/malformed-mobile-config-no-apis-config.xml b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/test/resources/config/malformed-mobile-config-no-apis-config.xml new file mode 100644 index 0000000000..743fe6c249 --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/test/resources/config/malformed-mobile-config-no-apis-config.xml @@ -0,0 +1,42 @@ + + + + + + + + jdbc/MobileDM_DS + + + + + + + + enrollment + admin + enrollment + 1.0.0 + http://localhost:9763/ + http,https + + + + + diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/test/resources/config/malformed-mobile-config-no-ds-config.xml b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/test/resources/config/malformed-mobile-config-no-ds-config.xml new file mode 100644 index 0000000000..1f1b5ef14d --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/test/resources/config/malformed-mobile-config-no-ds-config.xml @@ -0,0 +1,42 @@ + + + + + + + + jdbc/MobileDM_DS + + + + + + + + enrollment + admin + enrollment + 1.0.0 + http://localhost:9763/ + http,https + + + + + diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/test/resources/config/malformed-mobile-config-no-jndi-config.xml b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/test/resources/config/malformed-mobile-config-no-jndi-config.xml new file mode 100644 index 0000000000..06dc337615 --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/test/resources/config/malformed-mobile-config-no-jndi-config.xml @@ -0,0 +1,42 @@ + + + + + + + + jdbc/MobileDM_DS + + + + + + + + enrollment + admin + enrollment + 1.0.0 + http://localhost:9763/ + http,https + + + + + diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/test/resources/config/malformed-mobile-config-no-mgt-repo.xml b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/test/resources/config/malformed-mobile-config-no-mgt-repo.xml new file mode 100644 index 0000000000..6177426359 --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/test/resources/config/malformed-mobile-config-no-mgt-repo.xml @@ -0,0 +1,42 @@ + + + + + + + + jdbc/MobileDM_DS + + + + + + + + enrollment + admin + enrollment + 1.0.0 + http://localhost:9763/ + http,https + + + + + diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/test/resources/config/schema/MobileDeviceManagementConfigSchema.xsd b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/test/resources/config/schema/MobileDeviceManagementConfigSchema.xsd new file mode 100644 index 0000000000..ff6435e509 --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/test/resources/config/schema/MobileDeviceManagementConfigSchema.xsd @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/test/resources/log4j.properties b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/test/resources/log4j.properties new file mode 100644 index 0000000000..675d63149b --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/test/resources/log4j.properties @@ -0,0 +1,33 @@ +# +# Copyright 2015 WSO2, Inc. (http://wso2.com) +# +# 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. +# + +# +# This is the log4j configuration file used by WSO2 Carbon +# +# IMPORTANT : Please do not remove or change the names of any +# of the Appenders defined here. The layout pattern & log file +# can be changed using the WSO2 Carbon Management Console, and those +# settings will override the settings in this file. +# + +log4j.rootLogger=DEBUG, STD_OUT + +# Redirect log messages to console +log4j.appender.STD_OUT=org.apache.log4j.ConsoleAppender +log4j.appender.STD_OUT.Target=System.out +log4j.appender.STD_OUT.layout=org.apache.log4j.PatternLayout +log4j.appender.STD_OUT.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/test/resources/sql/h2.sql b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/test/resources/sql/h2.sql new file mode 100644 index 0000000000..78b5b7b8b3 --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/test/resources/sql/h2.sql @@ -0,0 +1,54 @@ + +-- ----------------------------------------------------- +-- Table `AD_DEVICE` +-- ----------------------------------------------------- +CREATE TABLE IF NOT EXISTS `AD_DEVICE` ( + `DEVICE_ID` VARCHAR(45) NOT NULL , + `PUSH_TOKEN` VARCHAR(45) NULL DEFAULT NULL , + `IMEI` VARCHAR(45) NULL DEFAULT NULL , + `IMSI` VARCHAR(45) NULL DEFAULT NULL , + `OS_VERSION` VARCHAR(45) NULL DEFAULT NULL , + `DEVICE_MODEL` VARCHAR(45) NULL DEFAULT NULL , + `VENDOR` VARCHAR(45) NULL DEFAULT NULL , + `LATITUDE` VARCHAR(45) NULL DEFAULT NULL, + `LONGITUDE` VARCHAR(45) NULL DEFAULT NULL, + `CHALLENGE` VARCHAR(45) NULL DEFAULT NULL, + `TOKEN` VARCHAR(500) NULL DEFAULT NULL, + `UNLOCK_TOKEN` VARCHAR(500) NULL DEFAULT NULL, + `SERIAL` VARCHAR(45) NULL DEFAULT NULL, + PRIMARY KEY (`DEVICE_ID`) ); + + +-- ----------------------------------------------------- +-- Table `AD_FEATURE` +-- ----------------------------------------------------- +CREATE TABLE IF NOT EXISTS `AD_FEATURE` ( + `ID` INT NOT NULL AUTO_INCREMENT , + `CODE` VARCHAR(45) NOT NULL, + `NAME` VARCHAR(100) NULL , + `DESCRIPTION` VARCHAR(200) NULL , + `DEVICE_TYPE` VARCHAR(50) NULL , + PRIMARY KEY (`ID`) ); + +-- ----------------------------------------------------- +-- Table `AD_OPERATION` +-- ----------------------------------------------------- +CREATE TABLE IF NOT EXISTS `AD_OPERATION` ( + `OPERATION_ID` INT NOT NULL AUTO_INCREMENT , + `FEATURE_CODE` VARCHAR(45) NOT NULL , + `CREATED_DATE` BIGINT NULL , + PRIMARY KEY (`OPERATION_ID`)); + + +-- ----------------------------------------------------- +-- Table `AD_FEATURE_PROPERTY` +-- ----------------------------------------------------- +CREATE TABLE IF NOT EXISTS `AD_FEATURE_PROPERTY` ( + `PROPERTY` VARCHAR(45) NOT NULL , + `FEATURE_ID` INT NOT NULL , + PRIMARY KEY (`PROPERTY`) , + CONSTRAINT `fk_AD_FEATURE_PROPERTY_AD_FEATURE1` + FOREIGN KEY (`FEATURE_ID` ) + REFERENCES `AD_FEATURE` (`ID` ) + ON DELETE NO ACTION + ON UPDATE NO ACTION); diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/test/resources/testdbconfig.xml b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/test/resources/testdbconfig.xml new file mode 100644 index 0000000000..eee04a3245 --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/test/resources/testdbconfig.xml @@ -0,0 +1,24 @@ + + + + + + jdbc:h2:mem:cdm-mobile-test-db;DB_CLOSE_DELAY=-1 + org.h2.Driver + + + + diff --git a/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/test/resources/testng.xml b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/test/resources/testng.xml new file mode 100644 index 0000000000..f73ab880b0 --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/org.wso2.carbon.device.mgt.mobile/src/test/resources/testng.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/components/mobile-plugins/mobile-base-plugin/pom.xml b/components/mobile-plugins/mobile-base-plugin/pom.xml new file mode 100644 index 0000000000..b2912b5625 --- /dev/null +++ b/components/mobile-plugins/mobile-base-plugin/pom.xml @@ -0,0 +1,62 @@ + + + + + + + org.wso2.carbon.devicemgt-plugins + mobile-plugins + 2.1.0-SNAPSHOT + ../pom.xml + + + 4.0.0 + mobile-base-plugin + pom + WSO2 Carbon - Mobile Plugins + http://wso2.org + + + org.wso2.carbon.device.mgt.mobile + org.wso2.carbon.device.mgt.mobile.api + org.wso2.carbon.device.mgt.mobile.ui + org.wso2.carbon.device.mgt.mobile.url.printer + + + + + + + org.apache.felix + maven-scr-plugin + 1.7.2 + + + generate-scr-scrdescriptor + + scr + + + + + + + + + diff --git a/components/key-mgt/pom.xml b/components/mobile-plugins/pom.xml similarity index 79% rename from components/key-mgt/pom.xml rename to components/mobile-plugins/pom.xml index 8a4e5f6191..7c62f920b8 100644 --- a/components/key-mgt/pom.xml +++ b/components/mobile-plugins/pom.xml @@ -17,30 +17,28 @@ ~ under the License. --> - + org.wso2.carbon.devicemgt-plugins carbon-device-mgt-plugins-parent - 1.9.0-SNAPSHOT + 2.1.0-SNAPSHOT ../../pom.xml 4.0.0 org.wso2.carbon.devicemgt-plugins - key-mgt - 1.9.0-SNAPSHOT + mobile-plugins pom - WSO2 Carbon - Oauth Key Management Component + WSO2 Carbon - Mobile Plugins http://wso2.org - org.wso2.carbon.key.mgt.handler.valve + android-plugin + windows-plugin + mobile-base-plugin - @@ -60,5 +58,4 @@ - diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/pom.xml b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/pom.xml new file mode 100644 index 0000000000..8ee9a2c232 --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/pom.xml @@ -0,0 +1,309 @@ + + + + + + windows-plugin + org.wso2.carbon.devicemgt-plugins + 2.1.0-SNAPSHOT + ../pom.xml + + + 4.0.0 + org.wso2.carbon.device.mgt.mobile.windows.agent + WSO2 Carbon - Windows JAX-RS API + Windows JAX-RS API + war + + + + + maven-compiler-plugin + + 1.7 + 1.7 + + 2.3.2 + + + maven-war-plugin + 2.2 + + + ${project.artifactId} + + + + + + + + deploy + + compile + + + org.apache.maven.plugins + maven-antrun-plugin + 1.7 + + + compile + + run + + + + + + + + + + + + + + + + + + + client + + test + + + org.codehaus.mojo + exec-maven-plugin + 1.2.1 + + + test + + java + + + + + + + + + + + + + org.apache.cxf + cxf-rt-frontend-jaxws + + + org.apache.cxf + cxf-rt-frontend-jaxrs + + + org.apache.cxf + cxf-rt-transports-http + + + org.apache.cxf + cxf-rt-bindings-soap + compile + + + org.apache.cxf + cxf-rt-bindings-http + + + org.apache.ws.security + wss4j + + + org.slf4j + slf4j-api + + + compile + + + org.apache.cxf + cxf-rt-rs-extension-providers + compile + + + org.apache.cxf + cxf-rt-ws-security + + + org.slf4j + slf4j-api + + + + + org.apache.wss4j + wss4j-ws-security-common + + + org.slf4j + slf4j-api + + + + + org.apache.ws.commons.axiom + axiom-api + + + org.apache.ws.commons.axiom + axiom-impl + + + log4j + log4j + + + org.springframework + spring-web + + + + com.sun.xml.ws + jaxws-rt + provided + + + com.sun.xml.messaging.saaj + saaj-impl + + + org.bouncycastle + bcpkix-jdk15on + + + org.bouncycastle + bcprov-jdk15on + + + org.codehaus.plexus + plexus-utils + + + com.madgag.spongycastle + pkix + + + com.madgag.spongycastle + prov + + + com.madgag.spongycastle + core + + + commons-codec.wso2 + commons-codec + + + joda-time + joda-time + + + commons-io + commons-io + + + javax.ws.rs + jsr311-api + + + org.testng + testng + + + org.wso2.carbon + org.wso2.carbon.logging + + + + + org.wso2.carbon.devicemgt + org.wso2.carbon.device.mgt.common + provided + + + org.wso2.carbon.identity + org.wso2.carbon.identity.oauth.stub + provided + + + org.wso2.carbon.identity + org.wso2.carbon.identity.oauth + provided + + + javax.cache + cache-api + 0.5 + provided + + + org.wso2.carbon.devicemgt + org.wso2.carbon.device.mgt.core + provided + + + org.wso2.tomcat + tomcat-servlet-api + + + com.google.code.gson + gson + + + org.wso2.carbon.devicemgt + org.wso2.carbon.policy.mgt.core + provided + + + org.wso2.carbon.devicemgt + org.wso2.carbon.certificate.mgt.core + provided + + + org.codehaus.jettison.wso2 + jettison + 1.1.wso2v1 + + + org.json.wso2 + json + 2.0.0.wso2v1 + + + diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/common/PluginConstants.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/common/PluginConstants.java new file mode 100644 index 0000000000..d61ee584de --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/common/PluginConstants.java @@ -0,0 +1,293 @@ +/* + * 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.wso2.carbon.mdm.mobileservices.windows.common; + +/** + * PluginConstants class for Windows plugin. This class has inner classes for containing constants for + * each service. + */ +public final class PluginConstants { + + //Service endpoints + public static final String DISCOVERY_SERVICE_ENDPOINT = + "org.wso2.carbon.mdm.mobileservices.windows.services.discovery.DiscoveryService"; + public static final String CERTIFICATE_ENROLLMENT_SERVICE_ENDPOINT = + "org.wso2.carbon.mdm.mobileservices.windows.services.wstep" + + ".CertificateEnrollmentService"; + public static final String CERTIFICATE_ENROLLMENT_POLICY_SERVICE_ENDPOINT = + "org.wso2.carbon.mdm.mobileservices.windows.services.xcep" + + ".CertificateEnrollmentPolicyService"; + + //Services' target namespaces + public static final String DISCOVERY_SERVICE_TARGET_NAMESPACE = + "http://schemas.microsoft.com/windows/management/2012/01/enrollment"; + public static final String DEVICE_ENROLLMENT_SERVICE_TARGET_NAMESPACE = + "http://schemas.microsoft.com/windows/pki/2009/01/enrollment/RSTRC"; + public static final String CERTIFICATE_ENROLLMENT_POLICY_SERVICE_TARGET_NAMESPACE = + "http://schemas.microsoft.com/windows/pki/2009/01/enrollmentpolicy"; + + //Certificate enrollment service urls and namespaces + public static final String WS_TRUST_TARGET_NAMESPACE = + "http://docs.oasis-open.org/ws-sx/ws-trust/200512"; + public static final String WS_SECURITY_TARGET_NAMESPACE = + "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"; + public static final String SOAP_AUTHORIZATION_TARGET_NAMESPACE = + "http://schemas.xmlsoap.org/ws/2006/12/authorization"; + + //Certificate enrollment policy service urls and namespaces + public static final String ENROLLMENT_POLICY_TARGET_NAMESPACE = + "http://schemas.microsoft.com/windows/pki/2009/01/enrollmentpolicy"; + public static final String REQUEST_WRAPPER_CLASS_NAME = + "com.microsoft.schemas.windows.pki._2009._01.enrollmentpolicy.GetPolicies"; + public static final String RESPONSE_WRAPPER_CLASS_NAME = + "com.microsoft.schemas.windows.pki._2009._01.enrollmentpolicy.GetPoliciesResponse"; + + //Servlet Context attributes names + public static final String CONTEXT_WAP_PROVISIONING_FILE = "WAP_PROVISIONING_FILE"; + public static final String WINDOWS_PLUGIN_PROPERTIES = "WINDOWS_PLUGIN_PROPERTIES"; + + //Message handler constants + public static final String CONTENT_LENGTH = "Content-Length"; + public static final String SECURITY = "Security"; + public static final String CXF_REQUEST_URI = "org.apache.cxf.request.uri"; + + //Web services media types + public static final String SYNCML_MEDIA_TYPE = "application/vnd.syncml.dm+xml;charset=utf-8"; + + /** + * Discovery service related other constants + */ + public final class Discovery { + private Discovery() { + throw new AssertionError(); + } + + public static final String ENROLL_SUBDOMAIN = "https://EnterpriseEnrollment."; + public static final String CERTIFICATE_ENROLLMENT_POLICY_SERVICE_URL = + "/ENROLLMENTSERVER/PolicyEnrollmentWebservice" + + ".svc"; + public static final String CERTIFICATE_ENROLLMENT_SERVICE_URL = + "/ENROLLMENTSERVER/DeviceEnrollmentWebservice" + + ".svc"; + public static final String ONPREMISE_CERTIFICATE_ENROLLMENT_POLICY = + "/ENROLLMENTSERVER/ONPREMISE/" + + "PolicyEnrollmentWebservice.svc"; + public static final String ONPREMISE_CERTIFICATE_ENROLLMENT_SERVICE_URL = + "/ENROLLMENTSERVER/ONPREMISE/DeviceEnrollmentWebservice.svc"; + public static final String WAB_URL = "/mdm/enrollments/windows/login-agent"; + + } + + /** + * Certificate enrolment policy service related constants + */ + public final class CertificateEnrolmentPolicy { + private CertificateEnrolmentPolicy() { + throw new AssertionError(); + } + + public static final int MINIMAL_KEY_LENGTH = 2048; + public static final int POLICY_SCHEMA = 3; + public static final int HASH_ALGORITHM_OID_REFERENCE = 0; + public static final int OID_REFERENCE = 0; + public static final String OID = "1.3.14.3.2.29"; + public static final String OID_DEFAULT_NAME = "szOID_OIWSEC_sha1RSASign"; + public static final int OID_GROUP = 1; + public static final int OID_REFERENCE_ID = 0; + } + + /** + * Certificate enrollment Service related constants + */ + public final class CertificateEnrolment { + private CertificateEnrolment() { + throw new AssertionError(); + } + + public static final String TOKEN_TYPE = + "http://schemas.microsoft.com/5.0.0" + + ".0/ConfigurationManager/Enrollment/DeviceEnrollmentToken"; + public static final String PARM = "parm"; + public static final String TYPE = "type"; + public static final String VALUE = "value"; + public static final String VALUE_TYPE = + "http://schemas.microsoft.com/5.0.0" + + ".0/ConfigurationManager/Enrollment/DeviceEnrollmentProvisionDoc"; + public static final String ENCODING_TYPE = + "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0" + + ".xsd#base64binary"; + public static final String CA_CERT = "cacert"; + public static final String X_509 = "X.509"; + public static final String PROPERTIES_XML = "properties.xml"; + public static final String WAP_PROVISIONING_XML = "wap-provisioning.xml"; + public static final String PROVIDER = "BC"; + public static final String ALGORITHM = "SHA1withRSA"; + public static final String JKS = "JKS"; + public static final String SECURITY = "Security"; + public static final String WSS_SECURITY_UTILITY = + "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0" + + ".xsd"; + public static final String TIMESTAMP_ID = "Id"; + public static final String TIMESTAMP_U = "u"; + public static final String TIMESTAMP = "Timestamp"; + public static final String TIMESTAMP_0 = "_0"; + public static final String CREATED = "Created"; + public static final String EXPIRES = "Expires"; + public static final String UTF_8 = "utf-8"; + } + + /** + * SynclML service related constants + */ + public final class SyncML { + private SyncML() { + throw new AssertionError(); + } + + public static final String SYNCML_SOURCE = "Source"; + public static final String SYNCML_DATA = "Data"; + public static final String SYNCML_CMD = "Cmd"; + public static final String SYNCML_CHAL = "ChallengeTag"; + public static final String SYNCML_CMD_ID = "CmdID"; + public static final String SYNCML_CMD_REF = "CmdRef"; + public static final String SYNCML_MESSAGE_REF = "MsgRef"; + public static final String SYNCML_LOCATION_URI = "LocURI"; + public static final String SYNCML_TARGET_REF = "TargetRef"; + + public static final int SYNCML_FIRST_MESSAGE_ID = 1; + public static final int SYNCML_SECOND_MESSAGE_ID = 2; + public static final int SYNCML_FIRST_SESSION_ID = 1; + public static final int SYNCML_SECOND_SESSION_ID = 2; + public static final int OSVERSION_POSITION = 0; + public static final int DEVICE_ID_POSITION = 0; + public static final int DEVICE_MODEL_POSITION = 2; + public static final int DEVICE_MAN_POSITION = 1; + public static final int DEVICE_MOD_VER_POSITION = 3; + public static final int DEVICE_LANG_POSITION = 4; + public static final int IMSI_POSITION = 1; + public static final int IMEI_POSITION = 2; + public static final int VENDER_POSITION = 4; + public static final int MODEL_POSITION = 5; + public static final int MACADDRESS_POSITION = 7; + public static final int RESOLUTION_POSITION = 8; + public static final int DEVICE_NAME_POSITION = 9; + public static final String SYNCML_DATA_ONE = "1"; + public static final String SYNCML_DATA_ZERO = "0"; + public static final String OS_VERSION = "OS_VERSION"; + public static final String IMSI = "IMSI"; + public static final String IMEI = "IMEI"; + public static final String VENDOR = "VENDOR"; + public static final String MODEL = "DEVICE_MODEL"; + public static final String MAC_ADDRESS = "MAC_ADDRESS"; + public static final String DEVICE_INFO = "DEVICE_INFO"; + public static final String DEVICE_NAME = "DEVICE_NAME"; + public static final String SOFTWARE_VERSION = "SOFTWARE_VERSION"; + public static final String DEV_ID = "DEV_ID"; + public static final String MANUFACTURER = "MANUFACTURER"; + public static final String LANGUAGE = "LANGUAGE"; + public static final String RESOLUTION = "RESOLUTION"; + } + + /** + * Windows device constants. + */ + public final class DeviceConstants { + private DeviceConstants() { + throw new AssertionError(); + } + + public static final String DEVICE_ID_NOT_FOUND = "Device Id not found for device found at %s"; + public static final String DEVICE_ID_SERVICE_NOT_FOUND = + "Issue in retrieving device management service instance for device found at %s"; + } + + /** + * Device Operation codes. + */ + public final class OperationCodes { + private OperationCodes() { + throw new AssertionError(); + } + + public static final String DEVICE_LOCK = "DEVICE_LOCK"; + public static final String DISENROLL = "DISENROLL"; + public static final String DEVICE_RING = "DEVICE_RING"; + public static final String WIPE_DATA = "WIPE_DATA"; + public static final String ENCRYPT_STORAGE = "ENCRYPT_STORAGE"; + public static final String LOCK_RESET = "LOCK_RESET"; + public static final String PIN_CODE = "LOCK_PIN"; + public static final String CAMERA = "CAMERA"; + public static final String PASSCODE_POLICY = "PASSCODE_POLICY"; + public static final String PASSWORD_EXPIRE = "PASSWORD_EXPIRE"; + public static final String PASSWORD_HISTORY = "PASSWORD_HISTORY"; + public static final String MAX_PASSWORD_INACTIVE_TIME = "MAX_PASSWORD_INACTIVE_TIME"; + public static final String MIN_PASSWORD_COMPLEX_CHARACTERS = "MIN_PASSWORD_COMPLEX_CHARACTERS"; + public static final String ALPHANUMERIC_PASSWORD = "ALPHANUMERIC_PASSWORD"; + public static final String SIMPLE_PASSWORD = "SIMPLE_PASSWORD"; + public static final String MIN_PASSWORD_LENGTH = "MIN_PASSWORD_LENGTH"; + public static final String DEVICE_PASSWORD_ENABLE = "DEVICE_PASSWORD_ENABLE"; + public static final String PASSWORD_MAX_FAIL_ATTEMPTS = "PASSWORD_MAX_FAIL_ATTEMPTS"; + public static final String MONITOR = "MONITOR"; + public static final String CAMERA_STATUS = "CAMERA_STATUS"; + public static final String POLICY_BUNDLE = "POLICY_BUNDLE"; + public static final String ENCRYPT_STORAGE_STATUS = "ENCRYPT_STORAGE_STATUS"; + public static final String DEVICE_PASSWORD_STATUS = "DEVICE_PASSWORD_STATUS"; + public static final String DEVICE_PASSCODE_DELETE = "DEVICE_PASSCODE_DELETE"; + } + + /** + * Plugin response status code constants. + */ + public final class StatusCodes { + private StatusCodes() { + throw new AssertionError(); + } + + public static final int MULTI_STATUS_HTTP_CODE = 207; + } + + /** + * Tenant Configuration related constants. + */ + public final class TenantConfigProperties { + private TenantConfigProperties() { + throw new AssertionError(); + } + + public static final String LICENSE_KEY = "windowsLicense"; + public static final String LANGUAGE_US = "en_US"; + public static final String CONTENT_TYPE_TEXT = "text"; + public static final String NOTIFIER_FREQUENCY = "notifierFrequency"; + public static final String DEFAULT_FREQUENCY = "8"; + + } + + /** + * Policy Configuration related constants. + */ + public final class PolicyConfigProperties { + private PolicyConfigProperties() { + throw new AssertionError(); + } + + public static final String POLICY_ENABLE = "enabled"; + public static final String ENCRYPTED_ENABLE = "encrypted"; + public static final String ENABLE_PASSWORD = "enablePassword"; + } +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/common/SyncmlCommandType.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/common/SyncmlCommandType.java new file mode 100644 index 0000000000..6f49692313 --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/common/SyncmlCommandType.java @@ -0,0 +1,34 @@ +/* + * 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.wso2.carbon.mdm.mobileservices.windows.common; + +public enum SyncmlCommandType { + + WIFI("WIFI"); + + private final String commandType; + + SyncmlCommandType(final String commandType) { + this.commandType = commandType; + } + + public String getValue() { + return this.commandType; + } +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/common/beans/CacheEntry.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/common/beans/CacheEntry.java new file mode 100644 index 0000000000..a160cced15 --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/common/beans/CacheEntry.java @@ -0,0 +1,52 @@ +/* + * 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.wso2.carbon.mdm.mobileservices.windows.common.beans; + +/** + * Class for java CacheEntry + */ +public class CacheEntry { + private String deviceID; + private String username; + private String ownership; + + public String getOwnership() { + return ownership; + } + + public void setOwnership(String ownership) { + this.ownership = ownership; + } + + public String getDeviceID() { + return deviceID; + } + + public void setDeviceID(String deviceID) { + this.deviceID = deviceID; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/common/beans/Token.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/common/beans/Token.java new file mode 100644 index 0000000000..cabbbf96db --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/common/beans/Token.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.wso2.carbon.mdm.mobileservices.windows.common.beans; + +/** + * Class for challenge token + */ +public class Token { + private String challengeToken; + + public String getChallengeToken() { + return challengeToken; + } + + public void setChallengeToken(String challengeToken) { + this.challengeToken = challengeToken; + } +} + diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/common/beans/WindowsPluginProperties.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/common/beans/WindowsPluginProperties.java new file mode 100644 index 0000000000..d25b1a5ca4 --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/common/beans/WindowsPluginProperties.java @@ -0,0 +1,92 @@ +/* + * 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.wso2.carbon.mdm.mobileservices.windows.common.beans; + +/** + * Bean class for storing Windows plugin properties after reading the property file. + */ +public class WindowsPluginProperties { + + private String keyStorePassword; + private String privateKeyPassword; + private String commonName; + private String authPolicy; + private String domain; + private int notBeforeDays; + private int notAfterDays; + + public String getKeyStorePassword() { + return keyStorePassword; + } + + public String getPrivateKeyPassword() { + return privateKeyPassword; + } + + public String getCommonName() { + return commonName; + } + + public int getNotBeforeDays() { + return notBeforeDays; + } + + public int getNotAfterDays() { + return notAfterDays; + } + + public void setKeyStorePassword(String keyStorePassword) { + this.keyStorePassword = keyStorePassword; + } + + public void setPrivateKeyPassword(String privateKeyPassword) { + this.privateKeyPassword = privateKeyPassword; + } + + public void setCommonName(String commonName) { + this.commonName = commonName; + } + + public void setNotBeforeDays(int notBeforeDays) { + this.notBeforeDays = notBeforeDays; + } + + public void setNotAfterDays(int notAfterDays) { + this.notAfterDays = notAfterDays; + } + + public String getAuthPolicy() { + return authPolicy; + } + + public void setAuthPolicy(String authPolicy) { + this.authPolicy = authPolicy; + } + + public String getDomain() { + return domain; + } + + public void setDomain(String domain) { + this.domain = domain; + } + + +} + diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/common/exceptions/AuthenticationException.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/common/exceptions/AuthenticationException.java new file mode 100644 index 0000000000..8e827ce773 --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/common/exceptions/AuthenticationException.java @@ -0,0 +1,58 @@ +/* + * 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.wso2.carbon.mdm.mobileservices.windows.common.exceptions; + +/** + * Exception class for authentication failures in windows device enrollment. + */ +public class AuthenticationException extends Exception { + + private String errorMessage; + + public AuthenticationException(String message) { + super(message); + setErrorMessage(message); + } + + public AuthenticationException(String message, Throwable cause) { + super(message, cause); + setErrorMessage(message); + } + + public AuthenticationException(String message, Exception nestedEx) { + super(message, nestedEx); + setErrorMessage(message); + } + + public AuthenticationException(Throwable cause) { + super(cause); + } + + public AuthenticationException() { + super(); + } + + public String getErrorMessage() { + return errorMessage; + } + + public void setErrorMessage(String errorMessage) { + this.errorMessage = errorMessage; + } +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/common/exceptions/BadRequestException.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/common/exceptions/BadRequestException.java new file mode 100644 index 0000000000..fce28a3b51 --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/common/exceptions/BadRequestException.java @@ -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. + */ + +package org.wso2.carbon.mdm.mobileservices.windows.common.exceptions; + +import org.wso2.carbon.mdm.mobileservices.windows.common.util.Message; + +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +/** + * Exception class for bad request failures + */ +public class BadRequestException extends WebApplicationException { + + public BadRequestException(Message message, MediaType mediaType) { + super(Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(message). + type(mediaType).build()); + } +} \ No newline at end of file diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/common/exceptions/CertificateGenerationException.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/common/exceptions/CertificateGenerationException.java new file mode 100644 index 0000000000..ce2f135db7 --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/common/exceptions/CertificateGenerationException.java @@ -0,0 +1,58 @@ +/* + * 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.wso2.carbon.mdm.mobileservices.windows.common.exceptions; + +/** + * Exception class for Certificate generation failures in WSTEP stage. + */ +public class CertificateGenerationException extends Exception { + + private String errorMessage; + + public CertificateGenerationException(String message) { + super(message); + setErrorMessage(message); + } + + public CertificateGenerationException(String message, Throwable cause) { + super(message, cause); + setErrorMessage(message); + } + + public CertificateGenerationException(String message, Exception nestedEx) { + super(message, nestedEx); + setErrorMessage(message); + } + + public CertificateGenerationException(Throwable cause) { + super(cause); + } + + public CertificateGenerationException() { + super(); + } + + public String getErrorMessage() { + return errorMessage; + } + + public void setErrorMessage(String errorMessage) { + this.errorMessage = errorMessage; + } +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/common/exceptions/FileOperationException.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/common/exceptions/FileOperationException.java new file mode 100644 index 0000000000..4fbc043986 --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/common/exceptions/FileOperationException.java @@ -0,0 +1,58 @@ +/* + * 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.wso2.carbon.mdm.mobileservices.windows.common.exceptions; + +/** + * Exception class for file reading/writing failures. + */ +public class FileOperationException extends Exception { + + private String errorMessage; + + public FileOperationException(String message) { + super(message); + setErrorMessage(message); + } + + public FileOperationException(String message, Throwable cause) { + super(message, cause); + setErrorMessage(message); + } + + public FileOperationException(String message, Exception nestedEx) { + super(message, nestedEx); + setErrorMessage(message); + } + + public FileOperationException(Throwable cause) { + super(cause); + } + + public FileOperationException() { + super(); + } + + public String getErrorMessage() { + return errorMessage; + } + + public void setErrorMessage(String errorMessage) { + this.errorMessage = errorMessage; + } +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/common/exceptions/KeyStoreGenerationException.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/common/exceptions/KeyStoreGenerationException.java new file mode 100644 index 0000000000..4a783abda9 --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/common/exceptions/KeyStoreGenerationException.java @@ -0,0 +1,58 @@ +/* + * 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.wso2.carbon.mdm.mobileservices.windows.common.exceptions; + +/** + * Exception class for Keystore generation failures. + */ +public class KeyStoreGenerationException extends Exception { + + private String errorMessage; + + public KeyStoreGenerationException(String message) { + super(message); + setErrorMessage(message); + } + + public KeyStoreGenerationException(String message, Throwable cause) { + super(message, cause); + setErrorMessage(message); + } + + public KeyStoreGenerationException(String message, Exception nestedEx) { + super(message, nestedEx); + setErrorMessage(message); + } + + public KeyStoreGenerationException(Throwable cause) { + super(cause); + } + + public KeyStoreGenerationException() { + super(); + } + + public String getErrorMessage() { + return errorMessage; + } + + public void setErrorMessage(String errorMessage) { + this.errorMessage = errorMessage; + } +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/common/exceptions/MDMAPIException.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/common/exceptions/MDMAPIException.java new file mode 100644 index 0000000000..98553785b4 --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/common/exceptions/MDMAPIException.java @@ -0,0 +1,60 @@ +/* + * 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.wso2.carbon.mdm.mobileservices.windows.common.exceptions; + +/** + * MDMAPIUtils class provides utility function used by CDM REST-API classes. + */ +public class MDMAPIException extends Exception { + + private static final long serialVersionUID = 7950151650447893900L; + private String errorMessage; + + public String getErrorMessage() { + return errorMessage; + } + + public void setErrorMessage(String errorMessage) { + this.errorMessage = errorMessage; + } + + public MDMAPIException(String msg, Exception e) { + super(msg, e); + setErrorMessage(msg); + } + + public MDMAPIException(String msg, Throwable cause) { + super(msg, cause); + setErrorMessage(msg); + } + + public MDMAPIException(String msg) { + super(msg); + setErrorMessage(msg); + } + + public MDMAPIException() { + super(); + } + + public MDMAPIException(Throwable cause) { + super(cause); + } + +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/common/exceptions/SyncmlMessageFormatException.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/common/exceptions/SyncmlMessageFormatException.java new file mode 100644 index 0000000000..e37b8b6b53 --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/common/exceptions/SyncmlMessageFormatException.java @@ -0,0 +1,57 @@ +/* + * 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.wso2.carbon.mdm.mobileservices.windows.common.exceptions; + +/** + * Exception class for bad format syncml message exceptions. + */ +public class SyncmlMessageFormatException extends Exception { + private String errorMessage; + + public SyncmlMessageFormatException(String message) { + super(message); + setErrorMessage(message); + } + + public SyncmlMessageFormatException(String message, Throwable cause) { + super(message, cause); + setErrorMessage(message); + } + + public SyncmlMessageFormatException(String message, Exception nestedEx) { + super(message, nestedEx); + setErrorMessage(message); + } + + public SyncmlMessageFormatException(Throwable cause) { + super(cause); + } + + public SyncmlMessageFormatException() { + super(); + } + + public String getErrorMessage() { + return errorMessage; + } + + public void setErrorMessage(String errorMessage) { + this.errorMessage = errorMessage; + } +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/common/exceptions/SyncmlOperationException.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/common/exceptions/SyncmlOperationException.java new file mode 100644 index 0000000000..409ca57e09 --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/common/exceptions/SyncmlOperationException.java @@ -0,0 +1,58 @@ +/* + * 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.wso2.carbon.mdm.mobileservices.windows.common.exceptions; + +/** + * Exception class for syncml Operation and policy related errors. + */ +public class SyncmlOperationException extends Exception { + + private String errorMessage; + + public SyncmlOperationException(String message) { + super(message); + setErrorMessage(message); + } + + public SyncmlOperationException(String message, Throwable cause) { + super(message, cause); + setErrorMessage(message); + } + + public SyncmlOperationException(String message, Exception nestedEx) { + super(message, nestedEx); + setErrorMessage(message); + } + + public SyncmlOperationException(Throwable cause) { + super(cause); + } + + public SyncmlOperationException() { + super(); + } + + public String getErrorMessage() { + return errorMessage; + } + + public void setErrorMessage(String errorMessage) { + this.errorMessage = errorMessage; + } +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/common/exceptions/WAPProvisioningException.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/common/exceptions/WAPProvisioningException.java new file mode 100644 index 0000000000..d23368332b --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/common/exceptions/WAPProvisioningException.java @@ -0,0 +1,58 @@ +/* + * 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.wso2.carbon.mdm.mobileservices.windows.common.exceptions; + +/** + * Exception class for property/provisioning XML file reading failures. + */ +public class WAPProvisioningException extends Exception { + + private String errorMessage; + + public WAPProvisioningException(String message) { + super(message); + setErrorMessage(message); + } + + public WAPProvisioningException(String message, Throwable cause) { + super(message, cause); + setErrorMessage(message); + } + + public WAPProvisioningException(String message, Exception nestedEx) { + super(message, nestedEx); + setErrorMessage(message); + } + + public WAPProvisioningException(Throwable cause) { + super(cause); + } + + public WAPProvisioningException() { + super(); + } + + public String getErrorMessage() { + return errorMessage; + } + + public void setErrorMessage(String errorMessage) { + this.errorMessage = errorMessage; + } +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/common/exceptions/WindowsConfigurationException.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/common/exceptions/WindowsConfigurationException.java new file mode 100644 index 0000000000..86c49eef23 --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/common/exceptions/WindowsConfigurationException.java @@ -0,0 +1,61 @@ +/* + * 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.wso2.carbon.mdm.mobileservices.windows.common.exceptions; + +/** + * Custom class for windows API configurations. + */ +public class WindowsConfigurationException extends Exception { + + private String errorMessage; + + private static final long serialVersionUID = 7950151650447893900L; + + public String getErrorMessage() { + return errorMessage; + } + + public void setErrorMessage(String errorMessage) { + this.errorMessage = errorMessage; + } + + public WindowsConfigurationException(Throwable cause) { + super(cause); + } + + public WindowsConfigurationException() { + super(); + } + + public WindowsConfigurationException(String msg) { + super(msg); + setErrorMessage(msg); + } + + public WindowsConfigurationException(String msg, Throwable cause) { + super(msg, cause); + setErrorMessage(msg); + } + + public WindowsConfigurationException(String msg, Exception exception) { + super(msg, exception); + setErrorMessage(msg); + } + +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/common/exceptions/WindowsDeviceEnrolmentException.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/common/exceptions/WindowsDeviceEnrolmentException.java new file mode 100644 index 0000000000..4722b76212 --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/common/exceptions/WindowsDeviceEnrolmentException.java @@ -0,0 +1,59 @@ +/* + * 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.wso2.carbon.mdm.mobileservices.windows.common.exceptions; + +/** + * Exception class for general WSTEP messaging/message generation failures. + */ +public class WindowsDeviceEnrolmentException extends Exception { + + private String errorMessage; + + public WindowsDeviceEnrolmentException(String message) { + super(message); + setErrorMessage(message); + } + + public WindowsDeviceEnrolmentException(String message, Throwable cause) { + super(message, cause); + setErrorMessage(message); + } + + public WindowsDeviceEnrolmentException(String message, Exception nestedEx) { + super(message, nestedEx); + setErrorMessage(message); + } + + public WindowsDeviceEnrolmentException(Throwable cause) { + super(cause); + } + + public WindowsDeviceEnrolmentException() { + super(); + } + + public String getErrorMessage() { + return errorMessage; + } + + public void setErrorMessage(String errorMessage) { + this.errorMessage = errorMessage; + } + +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/common/exceptions/WindowsOperationsException.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/common/exceptions/WindowsOperationsException.java new file mode 100644 index 0000000000..14677391d9 --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/common/exceptions/WindowsOperationsException.java @@ -0,0 +1,37 @@ +/* + * 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.wso2.carbon.mdm.mobileservices.windows.common.exceptions; + +import org.wso2.carbon.mdm.mobileservices.windows.common.util.Message; + +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +/** + * Exception Class for windows operations related exceptions + */ +public class WindowsOperationsException extends WebApplicationException { + + public WindowsOperationsException(Message message, MediaType mediaType) { + super(Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(message). + type(mediaType).build()); + } + +} \ No newline at end of file diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/common/util/AuthenticationInfo.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/common/util/AuthenticationInfo.java new file mode 100644 index 0000000000..80458a50a0 --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/common/util/AuthenticationInfo.java @@ -0,0 +1,62 @@ +/* + * 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.wso2.carbon.mdm.mobileservices.windows.common.util; + +/** + * DTO class to hold the information of authenticated user. + */ +public class AuthenticationInfo { + private String message; + private String username; + private String tenantDomain; + private int tenantId = -1; + + public String getUsername() { + return username; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getTenantDomain() { + return tenantDomain; + } + + public void setTenantDomain(String tenantDomain) { + this.tenantDomain = tenantDomain; + } + + public int getTenantId() { + return tenantId; + } + + public void setTenantId(int tenantId) { + this.tenantId = tenantId; + } + +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/common/util/BSTValidator.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/common/util/BSTValidator.java new file mode 100644 index 0000000000..916f2bf7b6 --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/common/util/BSTValidator.java @@ -0,0 +1,162 @@ +/* + * 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.wso2.carbon.mdm.mobileservices.windows.common.util; + +import org.apache.commons.codec.binary.Base64; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.ws.security.WSSecurityException; +import org.apache.ws.security.handler.RequestData; +import org.apache.ws.security.message.token.BinarySecurity; +import org.apache.ws.security.validate.Credential; +import org.apache.ws.security.validate.Validator; +import org.wso2.carbon.context.PrivilegedCarbonContext; +import org.wso2.carbon.device.mgt.common.DeviceManagementException; +import org.wso2.carbon.identity.oauth2.dto.OAuth2TokenValidationRequestDTO; +import org.wso2.carbon.identity.oauth2.dto.OAuth2TokenValidationResponseDTO; +import org.wso2.carbon.mdm.mobileservices.windows.common.PluginConstants; +import org.wso2.carbon.mdm.mobileservices.windows.common.beans.CacheEntry; +import org.wso2.carbon.mdm.mobileservices.windows.common.exceptions.AuthenticationException; +import org.wso2.carbon.mdm.mobileservices.windows.common.exceptions.WindowsDeviceEnrolmentException; +import org.wso2.carbon.utils.multitenancy.MultitenantUtils; + +import java.util.HashMap; + +/** + * Validator class for user authentication checking the default carbon user store. + */ +public class BSTValidator implements Validator { + + private static Log log = LogFactory.getLog(BSTValidator.class); + private static final String BEARER_TOKEN_TYPE = "bearer"; + private static final String RESOURCE_KEY = "resource"; + + /** + * This method validates the binary security token in SOAP message coming from the device. + * + * @param credential - binary security token credential object + * @param requestData - Request data associated with the request + * @return - Credential object if authentication is success, or null if not success + * @throws WSSecurityException + */ + @Override + public Credential validate(Credential credential, RequestData requestData) throws WSSecurityException { + String encodedBinarySecurityToken; + String requestedUri; + Credential returnCredentials = null; + + HashMap msgContext = (HashMap) requestData.getMsgContext(); + requestedUri = msgContext.get(PluginConstants.CXF_REQUEST_URI).toString(); + BinarySecurity binarySecurityTokenObject = credential.getBinarySecurityToken(); + String binarySecurityToken = binarySecurityTokenObject.getElement().getFirstChild().getTextContent(); + Base64 base64 = new Base64(); + encodedBinarySecurityToken = new String(base64.decode(binarySecurityToken)); + AuthenticationInfo authenticationInfo; + try { + authenticationInfo = validateRequest(requestedUri, encodedBinarySecurityToken); + PrivilegedCarbonContext.startTenantFlow(); + PrivilegedCarbonContext privilegedCarbonContext = PrivilegedCarbonContext.getThreadLocalCarbonContext(); + privilegedCarbonContext.setTenantId(authenticationInfo.getTenantId()); + privilegedCarbonContext.setTenantDomain(authenticationInfo.getTenantDomain()); + privilegedCarbonContext.setUsername(authenticationInfo.getUsername()); + + if (authenticate(binarySecurityToken)) { + returnCredentials = credential; + } else { + String msg = "Authentication failure due to invalid binary security token."; + log.error(msg); + throw new WindowsDeviceEnrolmentException(msg); + } + } catch (AuthenticationException e) { + String msg = "Failure occurred in the BST validator."; + log.error(msg, e); + throw new WSSecurityException(msg, e); + } catch (WindowsDeviceEnrolmentException e) { + String msg = "Authentication Failure occurred due to binary security token."; + log.error(msg, e); + throw new WSSecurityException(msg, e); + } + return returnCredentials; + } + + /** + * This method authenticates the user checking the binary security token in the user store. + * + * @param binarySecurityToken - Binary security token received in the SOAP message header + * @return - Authentication status + * @throws AuthenticationException + */ + public boolean authenticate(String binarySecurityToken) throws + AuthenticationException { + + CacheEntry cacheentry = (CacheEntry) DeviceUtil.getCacheEntry(binarySecurityToken); + String username = cacheentry.getUsername(); + return username != null; + } + + /** + * Validate SOAP request token. + * + * @param requestedUri- Requested endpoint URI. + * @param encodedBinarySecurityToken-Binary security token comes from the soap request message. + * @return returns authorized user information. + * @throws WindowsDeviceEnrolmentException + */ + public AuthenticationInfo validateRequest(String requestedUri, String encodedBinarySecurityToken) + throws WindowsDeviceEnrolmentException { + + AuthenticationInfo authenticationInfo = new AuthenticationInfo(); + // Create a OAuth2TokenValidationRequestDTO object for validating access token + OAuth2TokenValidationRequestDTO dto = new OAuth2TokenValidationRequestDTO(); + //Set the access token info + OAuth2TokenValidationRequestDTO.OAuth2AccessToken oAuth2AccessToken = dto.new OAuth2AccessToken(); + oAuth2AccessToken.setTokenType(BSTValidator.BEARER_TOKEN_TYPE); + oAuth2AccessToken.setIdentifier(encodedBinarySecurityToken); + dto.setAccessToken(oAuth2AccessToken); + + //Set the resource context param. This will be used in scope validation. + OAuth2TokenValidationRequestDTO.TokenValidationContextParam + resourceContextParam = dto.new TokenValidationContextParam(); + resourceContextParam.setKey(BSTValidator.RESOURCE_KEY); + resourceContextParam.setValue(requestedUri + ":POST"); + + OAuth2TokenValidationRequestDTO.TokenValidationContextParam[] + tokenValidationContextParams = + new OAuth2TokenValidationRequestDTO.TokenValidationContextParam[1]; + tokenValidationContextParams[0] = resourceContextParam; + dto.setContext(tokenValidationContextParams); + try { + OAuth2TokenValidationResponseDTO oAuth2TokenValidationResponseDTO = + WindowsAPIUtils.getOAuth2TokenValidationService().validate(dto); + if (oAuth2TokenValidationResponseDTO.isValid()) { + String username = oAuth2TokenValidationResponseDTO.getAuthorizedUser(); + authenticationInfo.setUsername(username); + authenticationInfo.setTenantDomain(MultitenantUtils.getTenantDomain(username)); + authenticationInfo.setTenantId(WindowsAPIUtils.getTenantIdOFUser(username)); + } else { + authenticationInfo.setMessage(oAuth2TokenValidationResponseDTO.getErrorMsg()); + } + } catch (DeviceManagementException e) { + String msg = "Authentication failure due to invalid binary security token."; + log.error(msg, e); + throw new WindowsDeviceEnrolmentException(msg, e); + } + return authenticationInfo; + } +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/common/util/ConfigInitializerContextListener.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/common/util/ConfigInitializerContextListener.java new file mode 100644 index 0000000000..b2eda4a4ff --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/common/util/ConfigInitializerContextListener.java @@ -0,0 +1,138 @@ +/* + * 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.wso2.carbon.mdm.mobileservices.windows.common.util; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.w3c.dom.Document; +import org.wso2.carbon.mdm.mobileservices.windows.common.PluginConstants; +import org.wso2.carbon.mdm.mobileservices.windows.common.beans.WindowsPluginProperties; +import org.xml.sax.SAXException; + +import javax.servlet.ServletContext; +import javax.servlet.ServletContextEvent; +import javax.servlet.ServletContextListener; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import java.io.File; +import java.io.IOException; + +/** + * This class performs one time operations. + */ +public class ConfigInitializerContextListener implements ServletContextListener { + + public static final int INITIAL_VALUE = 0; + private static Log log = LogFactory.getLog(ConfigInitializerContextListener.class); + + private enum PropertyName { + PROPERTY_SIGNED_CERT_CN("SignedCertCN"), + PROPERTY_SIGNED_CERT_NOT_BEFORE("SignedCertNotBefore"), + PROPERTY_SIGNED_CERT_NOT_AFTER("SignedCertNotAfter"), + PROPERTY_PASSWORD("Password"), + PROPERTY_PRIVATE_KEY_PASSWORD("PrivateKeyPassword"), + AUTH_POLICY("AuthPolicy"), + DOMAIN("domain"); + + private final String propertyName; + + PropertyName(final String propertyName) { + this.propertyName = propertyName; + } + + public String getValue() { + return this.propertyName; + } + } + + /** + * This method loads wap-provisioning file / property file, sets wap-provisioning file and + * extracted properties as attributes in servlet context. + * + * @param servletContextEvent - Uses when servlet communicating with servlet container. + */ + @Override + public void contextInitialized(ServletContextEvent servletContextEvent) { + + ServletContext servletContext = servletContextEvent.getServletContext(); + File propertyFile = new File(getClass().getClassLoader().getResource( + PluginConstants.CertificateEnrolment.PROPERTIES_XML).getFile()); + DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance(); + DocumentBuilder docBuilder; + Document document = null; + try { + docBuilder = docBuilderFactory.newDocumentBuilder(); + if (docBuilder != null) { + document = docBuilder.parse(propertyFile); + } + } catch (ParserConfigurationException e) { + log.error("Parser configuration failure while reading properties.xml."); + } catch (SAXException e) { + log.error("Parsing error occurred while reading properties.xml."); + } catch (IOException e) { + log.error("File reading error occurred while accessing properties.xml."); + } + + String password = null; + String privateKeyPassword = null; + String signedCertCommonName = null; + String authPolicy = null; + String domain = null; + int signedCertNotBeforeDate = INITIAL_VALUE; + int signedCertNotAfterDate = INITIAL_VALUE; + + if (document != null) { + password = document.getElementsByTagName(PropertyName.PROPERTY_PASSWORD.getValue()).item(0). + getTextContent(); + privateKeyPassword = document.getElementsByTagName(PropertyName.PROPERTY_PRIVATE_KEY_PASSWORD.getValue()). + item(0).getTextContent(); + signedCertCommonName = + document.getElementsByTagName(PropertyName.PROPERTY_SIGNED_CERT_CN.getValue()).item(0). + getTextContent(); + authPolicy = document.getElementsByTagName(PropertyName.AUTH_POLICY.getValue()).item(0). + getTextContent(); + signedCertNotBeforeDate = Integer.valueOf(document.getElementsByTagName( + PropertyName.PROPERTY_SIGNED_CERT_NOT_BEFORE.getValue()).item(0).getTextContent()); + signedCertNotAfterDate = Integer.valueOf(document.getElementsByTagName( + PropertyName.PROPERTY_SIGNED_CERT_NOT_AFTER.getValue()).item(0).getTextContent()); + domain = document.getElementsByTagName(PropertyName.DOMAIN.getValue()).item(0).getTextContent(); + + } + + WindowsPluginProperties properties = new WindowsPluginProperties(); + properties.setKeyStorePassword(password); + properties.setPrivateKeyPassword(privateKeyPassword); + properties.setCommonName(signedCertCommonName); + properties.setNotBeforeDays(signedCertNotBeforeDate); + properties.setNotAfterDays(signedCertNotAfterDate); + properties.setAuthPolicy(authPolicy); + properties.setDomain(domain); + servletContext.setAttribute(PluginConstants.WINDOWS_PLUGIN_PROPERTIES, properties); + + File wapProvisioningFile = new File(getClass().getClassLoader().getResource( + PluginConstants.CertificateEnrolment.WAP_PROVISIONING_XML).getFile()); + servletContext.setAttribute(PluginConstants.CONTEXT_WAP_PROVISIONING_FILE, wapProvisioningFile); + } + + @Override + public void contextDestroyed(ServletContextEvent servletContextEvent) { + } + +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/common/util/DeviceIDHolder.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/common/util/DeviceIDHolder.java new file mode 100644 index 0000000000..99b0a5d2ed --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/common/util/DeviceIDHolder.java @@ -0,0 +1,48 @@ +/* + * 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.wso2.carbon.mdm.mobileservices.windows.common.util; + +import org.wso2.carbon.device.mgt.common.DeviceIdentifier; + +import java.util.List; + +/** + * Class for holding device ids. + */ +public class DeviceIDHolder { + + private List errorDeviceIdList; + private List validDeviceIDList; + + public List getErrorDeviceIdList() { + return errorDeviceIdList; + } + + public void setErrorDeviceIdList(List errorDeviceIdList) { + this.errorDeviceIdList = errorDeviceIdList; + } + + public List getValidDeviceIDList() { + return validDeviceIDList; + } + + public void setValidDeviceIDList(List validDeviceIDList) { + this.validDeviceIDList = validDeviceIDList; + } +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/common/util/DeviceUtil.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/common/util/DeviceUtil.java new file mode 100644 index 0000000000..61d2713b28 --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/common/util/DeviceUtil.java @@ -0,0 +1,81 @@ +/* + * 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.wso2.carbon.mdm.mobileservices.windows.common.util; + +import org.wso2.carbon.device.mgt.common.DeviceManagementException; +import org.wso2.carbon.mdm.mobileservices.windows.common.beans.CacheEntry; + +import javax.cache.Cache; +import javax.cache.CacheConfiguration; +import javax.cache.CacheManager; +import javax.cache.Caching; +import java.util.UUID; +import java.util.concurrent.TimeUnit; + +/** + * Class for generate random token for XCEP and WSTEP + */ +public class DeviceUtil { + + private static final String TOKEN_CACHE_MANAGER = "TOKEN_CACHE_MANAGER"; + private static final String TOKEN_CACHE = "TOKEN_CACHE"; + private static final long CACHE_DURATION = 15l; + private static boolean isContextCacheInitialized = false; + + public static String generateRandomToken() { + return String.valueOf(UUID.randomUUID()); + } + + public static void persistChallengeToken(String token, String deviceID, String username) throws DeviceManagementException { + + Object objCacheEntry = getCacheEntry(token); + CacheEntry cacheEntry; + if (objCacheEntry == null) { + cacheEntry = new CacheEntry(); + cacheEntry.setUsername(username); + } else { + cacheEntry = (CacheEntry) objCacheEntry; + } + if (deviceID != null) { + cacheEntry.setDeviceID(deviceID); + } + getTokenCache().put(token, cacheEntry); + } + + public static void removeToken(String token) { + getTokenCache().remove(token); + } + + public static Object getCacheEntry(String token) { + return getTokenCache().get(token); + } + + private static Cache getTokenCache() { + CacheManager contextCacheManager = Caching.getCacheManager(TOKEN_CACHE_MANAGER). + getCache(TOKEN_CACHE).getCacheManager(); + if (!isContextCacheInitialized) { + isContextCacheInitialized = true; + return contextCacheManager.createCacheBuilder(TOKEN_CACHE_MANAGER).setExpiry( + CacheConfiguration.ExpiryType.MODIFIED, + new CacheConfiguration.Duration(TimeUnit.MINUTES, CACHE_DURATION)).setStoreByValue(false).build(); + } else { + return Caching.getCacheManager(TOKEN_CACHE_MANAGER).getCache(TOKEN_CACHE); + } + } +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/common/util/ErrorHandler.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/common/util/ErrorHandler.java new file mode 100644 index 0000000000..2640302a57 --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/common/util/ErrorHandler.java @@ -0,0 +1,38 @@ +/* + * 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.wso2.carbon.mdm.mobileservices.windows.common.util; + +import org.wso2.carbon.mdm.mobileservices.windows.common.exceptions.WindowsConfigurationException; + +import javax.ws.rs.Produces; +import javax.ws.rs.core.Response; +import javax.ws.rs.ext.ExceptionMapper; + +/** + * Class for handle response exceptions. + */ +@Produces({"application/json", "application/xml"}) +public class ErrorHandler implements ExceptionMapper { + + public Response toResponse(WindowsConfigurationException exception) { + ErrorMessage errorMessage = new ErrorMessage(); + errorMessage.setErrorMessage(exception.getErrorMessage()); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(errorMessage).build(); + } +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/common/util/ErrorMessage.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/common/util/ErrorMessage.java new file mode 100644 index 0000000000..42177b2309 --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/common/util/ErrorMessage.java @@ -0,0 +1,39 @@ +/* + * 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. + */ +/** + * Generate Error Messages for responses. + */ +package org.wso2.carbon.mdm.mobileservices.windows.common.util; + +public class ErrorMessage { + + private String errorMessage; + private String errorCode; + + public String getErrorMessage() { + return errorMessage; + } + + public void setErrorMessage(String errorMessage) { + this.errorMessage = errorMessage; + } + + public String getErrorCode() { + return errorCode; + } +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/common/util/GsonMessageBodyHandler.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/common/util/GsonMessageBodyHandler.java new file mode 100644 index 0000000000..eaed2e11ac --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/common/util/GsonMessageBodyHandler.java @@ -0,0 +1,98 @@ +/* + * 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.wso2.carbon.mdm.mobileservices.windows.common.util; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + +import javax.ws.rs.Consumes; +import javax.ws.rs.Produces; +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.ext.MessageBodyReader; +import javax.ws.rs.ext.MessageBodyWriter; +import javax.ws.rs.ext.Provider; +import java.io.*; +import java.lang.annotation.Annotation; +import java.lang.reflect.Type; + +import static javax.ws.rs.core.MediaType.APPLICATION_JSON; + +/** + * Class for parse Gson Messages. + */ +@Provider +@Produces(APPLICATION_JSON) +@Consumes(APPLICATION_JSON) +public class GsonMessageBodyHandler implements MessageBodyWriter, MessageBodyReader { + private Gson gson; + private static final String UTF_8 = "UTF-8"; + + public boolean isReadable(Class aClass, Type type, Annotation[] annotations, MediaType mediaType) { + return true; + } + + private Gson getGson() { + if (gson == null) { + final GsonBuilder gsonBuilder = new GsonBuilder(); + gson = gsonBuilder.create(); + } + return gson; + } + + public Object readFrom(Class objectClass, Type type, Annotation[] annotations, MediaType mediaType, + MultivaluedMap stringStringMultivaluedMap, InputStream entityStream) + throws IOException, WebApplicationException { + + InputStreamReader reader = new InputStreamReader(entityStream, "UTF-8"); + + try { + return getGson().fromJson(reader, type); + } finally { + reader.close(); + } + } + + public boolean isWriteable(Class aClass, Type type, Annotation[] annotations, MediaType mediaType) { + return true; + } + + public long getSize(Object o, Class aClass, Type type, Annotation[] annotations, MediaType mediaType) { + return -1; + } + + public void writeTo(Object object, Class aClass, Type type, Annotation[] annotations, MediaType mediaType, + MultivaluedMap stringObjectMultivaluedMap, OutputStream entityStream) + throws IOException, WebApplicationException { + + OutputStreamWriter writer = new OutputStreamWriter(entityStream, UTF_8); + try { + Type jsonType; + if (type.equals(type)) { + jsonType = type; + } else { + jsonType = type; + } + getGson().toJson(object, jsonType, writer); + } finally { + writer.close(); + } + } +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/common/util/Message.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/common/util/Message.java new file mode 100644 index 0000000000..c3f2cc793e --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/common/util/Message.java @@ -0,0 +1,49 @@ +/* + * 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.wso2.carbon.mdm.mobileservices.windows.common.util; + +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; + +/** + * Class for generate response Messages. + */ +@XmlRootElement +public class Message { + private String responseCode; + private String responseMessage; + + @XmlElement + public String getResponseMessage() { + return responseMessage; + } + + public void setResponseMessage(String responseMessage) { + this.responseMessage = responseMessage; + } + + @XmlElement + public String getResponseCode() { + return responseCode; + } + + public void setResponseCode(String responseCode) { + this.responseCode = responseCode; + } +} \ No newline at end of file diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/common/util/SOAPSecurityHandler.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/common/util/SOAPSecurityHandler.java new file mode 100644 index 0000000000..679ac010ca --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/common/util/SOAPSecurityHandler.java @@ -0,0 +1,63 @@ +/* + * 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.wso2.carbon.mdm.mobileservices.windows.common.util; + + +import org.wso2.carbon.mdm.mobileservices.windows.common.PluginConstants; + +import javax.xml.namespace.QName; +import javax.xml.ws.handler.MessageContext; +import javax.xml.ws.handler.soap.SOAPHandler; +import javax.xml.ws.handler.soap.SOAPMessageContext; +import java.util.HashSet; +import java.util.Set; + +/** + * Class for handle SOAP message security. + */ +public class SOAPSecurityHandler implements SOAPHandler { + + /** + * This method resolves the security header coming in the SOAP message. + * + * @return - Security Header + */ + @Override + public Set getHeaders() { + QName securityHeader = new QName(PluginConstants.WS_SECURITY_TARGET_NAMESPACE, PluginConstants.SECURITY); + HashSet headers = new HashSet(); + headers.add(securityHeader); + return headers; + } + + @Override + public boolean handleMessage(SOAPMessageContext context) { + return true; + } + + @Override + public boolean handleFault(SOAPMessageContext context) { + return true; + } + + @Override + public void close(MessageContext context) { + + } +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/common/util/UsernameTokenValidator.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/common/util/UsernameTokenValidator.java new file mode 100644 index 0000000000..fb9ab5b633 --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/common/util/UsernameTokenValidator.java @@ -0,0 +1,134 @@ +/* + * 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.wso2.carbon.mdm.mobileservices.windows.common.util; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.ws.security.WSSecurityException; +import org.apache.ws.security.handler.RequestData; +import org.apache.ws.security.validate.Credential; +import org.apache.ws.security.validate.Validator; +import org.wso2.carbon.context.PrivilegedCarbonContext; +import org.wso2.carbon.mdm.mobileservices.windows.common.exceptions.AuthenticationException; +import org.wso2.carbon.mdm.mobileservices.windows.common.exceptions.WindowsDeviceEnrolmentException; +import org.wso2.carbon.user.api.UserRealm; +import org.wso2.carbon.user.api.UserStoreException; +import org.wso2.carbon.user.core.service.RealmService; +import org.wso2.carbon.utils.multitenancy.MultitenantConstants; + +/** + * Validator class for user authentication checking the default carbon user store. + */ +public class UsernameTokenValidator implements Validator { + + private static final int USER_SEGMENT = 0; + private static final int DOMAIN_SEGMENT = 1; + private static final String DELIMITER = "@"; + private static Log log = LogFactory.getLog(UsernameTokenValidator.class); + + /** + * This method validates the username token in SOAP message coming from the device. + * + * @param credential - Username token credentials coming from device + * @param requestData - Request data associated with the request + * @return - Credential object if authentication is success, or null if not success + * @throws WSSecurityException + */ + @Override + public Credential validate(Credential credential, RequestData requestData) throws + WSSecurityException { + + String domainUser = credential.getUsernametoken().getName(); + String[] domainUserArray = domainUser.split(DELIMITER); + Credential returnCredentials; + String user = domainUserArray[USER_SEGMENT]; + String domain = domainUserArray[DOMAIN_SEGMENT]; + String password = credential.getUsernametoken().getPassword(); + + try { + if (authenticate(user, password, domain)) { + returnCredentials = credential; + } else { + String msg = "Authentication failure due to incorrect credentials."; + log.error(msg); + throw new WindowsDeviceEnrolmentException(msg); + } + //Generic exception is caught here as there is no need of taking different actions for + //different exceptions. + } catch (AuthenticationException e) { + String msg = "Failure occurred in the BST validator."; + log.error(msg, e); + throw new WSSecurityException(msg, e); + } catch (WindowsDeviceEnrolmentException e) { + String msg = "Authentication Failure occurred due to binary security token."; + log.error(msg, e); + throw new WSSecurityException(msg, e); + } + return returnCredentials; + } + + /** + * This method authenticate the user checking the carbon default user store. + * + * @param username - Username in username token + * @param password - Password in username token + * @param tenantDomain - Tenant domain is extracted from the username + * @return - Returns boolean representing authentication result + * @throws AuthenticationException + */ + public boolean authenticate(String username, String password, String tenantDomain) throws + AuthenticationException { + + try { + PrivilegedCarbonContext.startTenantFlow(); + PrivilegedCarbonContext ctx = PrivilegedCarbonContext.getThreadLocalCarbonContext(); + ctx.setTenantDomain(MultitenantConstants.SUPER_TENANT_DOMAIN_NAME); + ctx.setTenantId(MultitenantConstants.SUPER_TENANT_ID); + RealmService realmService = (RealmService) ctx.getOSGiService(RealmService.class, null); + + if (realmService == null) { + String msg = "RealmService not initialized."; + log.error(msg); + throw new AuthenticationException(msg); + } + + int tenantId; + if (tenantDomain == null || tenantDomain.trim().isEmpty()) { + tenantId = MultitenantConstants.SUPER_TENANT_ID; + } else { + tenantId = realmService.getTenantManager().getTenantId(tenantDomain); + } + + if (tenantId == MultitenantConstants.INVALID_TENANT_ID) { + String msg = "Invalid tenant domain " + tenantDomain; + log.error(msg); + throw new AuthenticationException(msg); + } + UserRealm userRealm = realmService.getTenantUserRealm(tenantId); + + return userRealm.getUserStoreManager().authenticate(username, password); + } catch (UserStoreException e) { + String msg = "User store is not initialized."; + log.error(msg, e); + throw new AuthenticationException(msg, e); + } finally { + PrivilegedCarbonContext.endTenantFlow(); + } + } +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/common/util/WindowsAPIUtils.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/common/util/WindowsAPIUtils.java new file mode 100644 index 0000000000..e1dfb97e91 --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/common/util/WindowsAPIUtils.java @@ -0,0 +1,210 @@ +/* + * 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.wso2.carbon.mdm.mobileservices.windows.common.util; + + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.base.MultitenantConstants; +import org.wso2.carbon.certificate.mgt.core.service.CertificateManagementService; +import org.wso2.carbon.context.PrivilegedCarbonContext; +import org.wso2.carbon.device.mgt.common.DeviceIdentifier; +import org.wso2.carbon.device.mgt.common.DeviceManagementConstants; +import org.wso2.carbon.device.mgt.common.DeviceManagementException; +import org.wso2.carbon.device.mgt.common.configuration.mgt.TenantConfiguration; +import org.wso2.carbon.device.mgt.common.notification.mgt.NotificationManagementService; +import org.wso2.carbon.device.mgt.common.operation.mgt.Operation; +import org.wso2.carbon.device.mgt.common.operation.mgt.OperationManagementException; +import org.wso2.carbon.device.mgt.core.service.DeviceManagementProviderService; +import org.wso2.carbon.identity.core.util.IdentityTenantUtil; +import org.wso2.carbon.identity.oauth2.OAuth2TokenValidationService; +import org.wso2.carbon.mdm.mobileservices.windows.common.PluginConstants; +import org.wso2.carbon.mdm.mobileservices.windows.common.exceptions.MDMAPIException; +import org.wso2.carbon.policy.mgt.core.PolicyManagerService; +import org.wso2.carbon.user.api.TenantManager; +import org.wso2.carbon.user.api.UserStoreException; +import org.wso2.carbon.user.api.UserStoreManager; +import org.wso2.carbon.user.core.service.RealmService; +import org.wso2.carbon.utils.multitenancy.MultitenantUtils; + +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import java.util.List; + +/** + * Class for get Windows API utilities. + */ +public class WindowsAPIUtils { + + private static Log log = LogFactory.getLog(WindowsAPIUtils.class); + + public static DeviceIdentifier convertToDeviceIdentifierObject(String deviceId) { + DeviceIdentifier identifier = new DeviceIdentifier(); + identifier.setId(deviceId); + identifier.setType(DeviceManagementConstants.MobileDeviceTypes.MOBILE_DEVICE_TYPE_WINDOWS); + return identifier; + } + + public static CertificateManagementService getCertificateManagementService() { + CertificateManagementService cmService; + PrivilegedCarbonContext.startTenantFlow(); + PrivilegedCarbonContext ctx = PrivilegedCarbonContext.getThreadLocalCarbonContext(); + ctx.setTenantDomain(MultitenantConstants.SUPER_TENANT_DOMAIN_NAME); + ctx.setTenantId(MultitenantConstants.SUPER_TENANT_ID); + cmService = + (CertificateManagementService)ctx.getOSGiService(DeviceManagementProviderService.class, null); + PrivilegedCarbonContext.endTenantFlow(); + return cmService; + } + + public static DeviceManagementProviderService getDeviceManagementService() { + PrivilegedCarbonContext ctx = PrivilegedCarbonContext.getThreadLocalCarbonContext(); + DeviceManagementProviderService deviceManagementProviderService = + (DeviceManagementProviderService) ctx.getOSGiService(DeviceManagementProviderService.class, null); + if (deviceManagementProviderService == null) { + String msg = "Device Management service has not initialized."; + log.error(msg); + throw new IllegalStateException(msg); + } + return deviceManagementProviderService; + } + + + public static UserStoreManager getUserStoreManager() throws MDMAPIException { + RealmService realmService; + UserStoreManager userStoreManager; + try { + PrivilegedCarbonContext ctx = PrivilegedCarbonContext.getThreadLocalCarbonContext(); + realmService = (RealmService) ctx.getOSGiService(RealmService.class, null); + if (realmService == null) { + String msg = "Realm service has not initialized."; + throw new IllegalStateException(msg); + } + int tenantId = ctx.getTenantId(); + userStoreManager = realmService.getTenantUserRealm(tenantId).getUserStoreManager(); + } catch (UserStoreException e) { + String msg = "Error occurred while retrieving current user store manager"; + throw new MDMAPIException(msg, e); + } + return userStoreManager; + } + + public static NotificationManagementService getNotificationManagementService() { + NotificationManagementService notificationManagementService; + PrivilegedCarbonContext ctx = PrivilegedCarbonContext.getThreadLocalCarbonContext(); + notificationManagementService = (NotificationManagementService) ctx.getOSGiService( + NotificationManagementService.class, null); + if (notificationManagementService == null) { + String msg = "Notification Management service not initialized."; + log.error(msg); + throw new IllegalStateException(msg); + } + return notificationManagementService; + } + + public static MediaType getResponseMediaType(String acceptHeader) { + MediaType responseMediaType; + if (MediaType.WILDCARD.equals(acceptHeader)) { + responseMediaType = MediaType.APPLICATION_JSON_TYPE; + } else { + responseMediaType = MediaType.valueOf(acceptHeader); + } + return responseMediaType; + } + + public static Response getOperationResponse(List deviceIDs, Operation operation, + Message message, MediaType responseMediaType) + throws DeviceManagementException, OperationManagementException { + WindowsDeviceUtils deviceUtils = new WindowsDeviceUtils(); + DeviceIDHolder deviceIDHolder = deviceUtils.validateDeviceIdentifiers(deviceIDs, + message, responseMediaType); + getDeviceManagementService().addOperation(operation, deviceIDHolder.getValidDeviceIDList()); + if (!deviceIDHolder.getErrorDeviceIdList().isEmpty()) { + return javax.ws.rs.core.Response.status(PluginConstants.StatusCodes. + MULTI_STATUS_HTTP_CODE).type( + responseMediaType).entity(deviceUtils. + convertErrorMapIntoErrorMessage(deviceIDHolder.getErrorDeviceIdList())).build(); + } + return javax.ws.rs.core.Response.status(javax.ws.rs.core.Response.Status.CREATED). + type(responseMediaType).build(); + } + + public static PolicyManagerService getPolicyManagerService() { + PrivilegedCarbonContext ctx = PrivilegedCarbonContext.getThreadLocalCarbonContext(); + PolicyManagerService policyManagerService = (PolicyManagerService) ctx.getOSGiService( + PolicyManagerService.class, null); + if (policyManagerService == null) { + String msg = "Policy Manager service has not initialized"; + log.error(msg); + throw new IllegalStateException(msg); + } + return policyManagerService; + } + + public static void updateOperation(String deviceId, Operation operation) + throws OperationManagementException { + DeviceIdentifier deviceIdentifier = new DeviceIdentifier(); + deviceIdentifier.setId(deviceId); + deviceIdentifier.setType(DeviceManagementConstants.MobileDeviceTypes.MOBILE_DEVICE_TYPE_WINDOWS); + getDeviceManagementService().updateOperation(deviceIdentifier, operation); + } + + public static TenantConfiguration getTenantConfiguration() throws DeviceManagementException { + return getDeviceManagementService().getConfiguration( + DeviceManagementConstants.MobileDeviceTypes.MOBILE_DEVICE_TYPE_WINDOWS); + } + + public static int getTenantIdOFUser(String username) throws DeviceManagementException { + int tenantId = 0; + String domainName = MultitenantUtils.getTenantDomain(username); + if (domainName != null) { + try { + TenantManager tenantManager = IdentityTenantUtil.getRealmService().getTenantManager(); + tenantId = tenantManager.getTenantId(domainName); + } catch (UserStoreException e) { + String errorMsg = "Error when getting the tenant id from the tenant domain : " + + domainName; + log.error(errorMsg, e); + throw new DeviceManagementException(errorMsg, e); + } + } + return tenantId; + } + + public static OAuth2TokenValidationService getOAuth2TokenValidationService() { + PrivilegedCarbonContext ctx = PrivilegedCarbonContext.getThreadLocalCarbonContext(); + OAuth2TokenValidationService oAuth2TokenValidationService = + (OAuth2TokenValidationService) ctx.getOSGiService(OAuth2TokenValidationService.class, null); + if (oAuth2TokenValidationService == null) { + String msg = "OAuth2TokenValidation service has not initialized."; + log.error(msg); + throw new IllegalStateException(msg); + } + return oAuth2TokenValidationService; + } + + public static void startTenantFlow(String userName) { + PrivilegedCarbonContext.startTenantFlow(); + PrivilegedCarbonContext privilegedCarbonContext = PrivilegedCarbonContext. + getThreadLocalCarbonContext(); + privilegedCarbonContext.setTenantId(MultitenantConstants.SUPER_TENANT_ID); + privilegedCarbonContext.setTenantDomain(MultitenantConstants.SUPER_TENANT_DOMAIN_NAME); + privilegedCarbonContext.setUsername(userName); + } +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/common/util/WindowsDeviceUtils.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/common/util/WindowsDeviceUtils.java new file mode 100644 index 0000000000..2d38ac2cbc --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/common/util/WindowsDeviceUtils.java @@ -0,0 +1,85 @@ +/* + * 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.wso2.carbon.mdm.mobileservices.windows.common.util; + +import org.apache.commons.lang.StringUtils; +import org.wso2.carbon.device.mgt.common.Device; +import org.wso2.carbon.device.mgt.common.DeviceIdentifier; +import org.wso2.carbon.device.mgt.common.DeviceManagementConstants; +import org.wso2.carbon.device.mgt.common.DeviceManagementException; +import org.wso2.carbon.mdm.mobileservices.windows.common.PluginConstants; +import org.wso2.carbon.mdm.mobileservices.windows.common.exceptions.BadRequestException; + +import javax.ws.rs.core.MediaType; +import java.util.ArrayList; +import java.util.List; + +/** + * Class for get windows device utilities. + */ +public class WindowsDeviceUtils { + + private static final String COMMA_SEPARATION_PATTERN = ", "; + + public DeviceIDHolder validateDeviceIdentifiers(List deviceIDs, + Message message, MediaType responseMediaType) { + if (deviceIDs == null) { + message.setResponseMessage("Device identifier list is empty"); + throw new BadRequestException(message, responseMediaType); + } + List errorDeviceIdList = new ArrayList(); + List validDeviceIDList = new ArrayList(); + int deviceIDCounter = 0; + + for (String deviceID : deviceIDs) { + deviceIDCounter++; + if (deviceID == null || deviceID.isEmpty()) { + errorDeviceIdList.add(String.format(PluginConstants.DeviceConstants.DEVICE_ID_NOT_FOUND, + deviceIDCounter)); + continue; + } + try { + DeviceIdentifier deviceIdentifier = new DeviceIdentifier(); + deviceIdentifier.setId(deviceID); + deviceIdentifier.setType(DeviceManagementConstants.MobileDeviceTypes. + MOBILE_DEVICE_TYPE_WINDOWS); + Device device = WindowsAPIUtils.getDeviceManagementService(). + getDevice(deviceIdentifier); + if (device == null || device.getDeviceIdentifier() == null || + device.getDeviceIdentifier().isEmpty()) { + errorDeviceIdList.add(String.format(PluginConstants.DeviceConstants.DEVICE_ID_NOT_FOUND, + deviceIDCounter)); + continue; + } + validDeviceIDList.add(deviceIdentifier); + } catch (DeviceManagementException e) { + errorDeviceIdList.add(String.format(PluginConstants.DeviceConstants.DEVICE_ID_SERVICE_NOT_FOUND, + deviceIDCounter)); + } + } + DeviceIDHolder deviceIDHolder = new DeviceIDHolder(); + deviceIDHolder.setValidDeviceIDList(validDeviceIDList); + deviceIDHolder.setErrorDeviceIdList(errorDeviceIdList); + return deviceIDHolder; + } + + public String convertErrorMapIntoErrorMessage(List errorDeviceIdList) { + return StringUtils.join(errorDeviceIdList.iterator(), COMMA_SEPARATION_PATTERN); + } +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/operations/AddTag.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/operations/AddTag.java new file mode 100644 index 0000000000..d5adbd9e2f --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/operations/AddTag.java @@ -0,0 +1,68 @@ +/* + * 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.wso2.carbon.mdm.mobileservices.windows.operations; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.wso2.carbon.mdm.mobileservices.windows.operations.util.Constants; + +import java.util.Iterator; +import java.util.List; + +/** + * Configurations that needs to be added on the device. + */ +public class AddTag { + int commandId = -1; + List items; + + public int getCommandId() { + return commandId; + } + + public void setCommandId(int commandId) { + this.commandId = commandId; + } + + public List getItems() { + return items; + } + + public void setItems(List items) { + this.items = items; + } + + public void buildAddElement(Document doc, Element rootElement) { + if (getItems() != null) { + Element add = doc.createElement(Constants.ADD); + rootElement.appendChild(add); + if (getCommandId() != -1) { + Element commandId = doc.createElement(Constants.COMMAND_ID); + commandId.appendChild(doc.createTextNode(String.valueOf(getCommandId()))); + add.appendChild(commandId); + } + for (Iterator itemIterator = getItems().iterator(); itemIterator.hasNext(); ) { + Item item = itemIterator.next(); + if (item != null) { + item.buildItemElement(doc, add); + } + } + } + } +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/operations/Alert.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/operations/Alert.java new file mode 100644 index 0000000000..0a056669c4 --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/operations/Alert.java @@ -0,0 +1,63 @@ +/* + * 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.wso2.carbon.mdm.mobileservices.windows.operations; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.wso2.carbon.mdm.mobileservices.windows.operations.util.Constants; + +/** + * Inform an event occurred from device to server. + */ +public class Alert { + + int commandId = -1; + String data; + + public int getCommandId() { + return commandId; + } + + public void setCommandId(int commandId) { + this.commandId = commandId; + } + + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } + + public void buildAlertElement(Document doc, Element rootElement) { + Element alert = doc.createElement(Constants.ALERT); + rootElement.appendChild(alert); + if (getCommandId() != -1) { + Element commandId = doc.createElement(Constants.COMMAND_ID); + commandId.appendChild(doc.createTextNode(String.valueOf(getCommandId()))); + alert.appendChild(commandId); + } + if (getData() != null) { + Element data = doc.createElement(Constants.DATA); + data.appendChild(doc.createTextNode(getData())); + alert.appendChild(data); + } + } +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/operations/AtomicTag.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/operations/AtomicTag.java new file mode 100644 index 0000000000..75ad00c038 --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/operations/AtomicTag.java @@ -0,0 +1,85 @@ +/* + * 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.wso2.carbon.mdm.mobileservices.windows.operations; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.wso2.carbon.mdm.mobileservices.windows.operations.util.Constants; + +import java.util.Iterator; +import java.util.List; + +/** + * Wrapper for other SyncML elements. + */ +public class AtomicTag { + int commandId = -1; + List adds; + List replaces; + + public List getReplaces() { + return replaces; + } + + public void setReplaces(List replaces) { + this.replaces = replaces; + } + + public List getAdds() { + return adds; + } + + public void setAdds(List adds) { + this.adds = adds; + } + + public int getCommandId() { + return commandId; + } + + public void setCommandId(int commandId) { + this.commandId = commandId; + } + + public void buildAtomicElement(Document doc, Element rootElement) { + Element atomic = doc.createElement(Constants.ATOMIC); + if (getAdds() != null) { + rootElement.appendChild(atomic); + if (getCommandId() != -1) { + Element commandId = doc.createElement(Constants.COMMAND_ID); + commandId.appendChild(doc.createTextNode(String.valueOf(getCommandId()))); + atomic.appendChild(commandId); + } + for (Iterator addIterator = getAdds().iterator(); addIterator.hasNext(); ) { + AddTag add = addIterator.next(); + if (add != null) { + add.buildAddElement(doc, atomic); + } + } + } + if (getReplaces() != null) { + for (Iterator replaceIterator = getReplaces().iterator(); replaceIterator.hasNext(); ) { + Replace add = replaceIterator.next(); + if (add != null) { + add.buildReplaceElement(doc, atomic); + } + } + } + } +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/operations/ChallengeTag.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/operations/ChallengeTag.java new file mode 100644 index 0000000000..763032014e --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/operations/ChallengeTag.java @@ -0,0 +1,46 @@ +/* + * 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.wso2.carbon.mdm.mobileservices.windows.operations; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.wso2.carbon.mdm.mobileservices.windows.operations.util.Constants; + +/** + * Challenge data pass through the device and Device Management server for the security purpose. + */ +public class ChallengeTag { + MetaTag meta; + + public MetaTag getMeta() { + return meta; + } + + public void setMeta(MetaTag meta) { + this.meta = meta; + } + + public void buildChallElement(Document doc, Element rootElement) { + Element chal = doc.createElement(Constants.CHALLENGE); + rootElement.appendChild(chal); + if (getMeta() != null) { + getMeta().buildMetaElement(doc, chal); + } + } +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/operations/Credential.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/operations/Credential.java new file mode 100644 index 0000000000..a595e5ac8f --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/operations/Credential.java @@ -0,0 +1,60 @@ +/* + * 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.wso2.carbon.mdm.mobileservices.windows.operations; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.wso2.carbon.mdm.mobileservices.windows.operations.util.Constants; + +/** + * Credentials passed between the device and the server for security purposes. + */ +public class Credential { + MetaTag meta; + String data; + + public MetaTag getMeta() { + return meta; + } + + public void setMeta(MetaTag meta) { + this.meta = meta; + } + + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } + + public void buildCredentialElement(Document doc, Element rootElement) { + Element credentials = doc.createElement(Constants.CREDENTIAL); + rootElement.appendChild(credentials); + if (getMeta() != null) { + getMeta().buildMetaElement(doc, credentials); + } + if (getData() != null) { + Element data = doc.createElement(Constants.DATA); + data.appendChild(doc.createTextNode(getData())); + credentials.appendChild(data); + } + } +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/operations/DeleteTag.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/operations/DeleteTag.java new file mode 100644 index 0000000000..d899240ea6 --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/operations/DeleteTag.java @@ -0,0 +1,68 @@ +/* + * 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.wso2.carbon.mdm.mobileservices.windows.operations; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.wso2.carbon.mdm.mobileservices.windows.operations.util.Constants; + +import java.util.Iterator; +import java.util.List; + +/** + * Configurations that need to be delete on Device. + */ +public class DeleteTag { + int commandId = -1; + List items; + + public int getCommandId() { + return commandId; + } + + public void setCommandId(int commandId) { + this.commandId = commandId; + } + + public List getItems() { + return items; + } + + public void setItems(List items) { + this.items = items; + } + + public void buildDeleteElement(Document doc, Element rootElement) { + if (getItems() != null) { + Element delete = doc.createElement(Constants.DELETE); + rootElement.appendChild(delete); + if (getCommandId() != -1) { + Element commandId = doc.createElement(Constants.COMMAND_ID); + commandId.appendChild(doc.createTextNode(String.valueOf(getCommandId()))); + delete.appendChild(commandId); + } + for (Iterator itemIterator = getItems().iterator(); itemIterator.hasNext(); ) { + Item item = itemIterator.next(); + if (item != null) { + item.buildItemElement(doc, delete); + } + } + } + } +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/operations/ExecuteTag.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/operations/ExecuteTag.java new file mode 100644 index 0000000000..90a95924f2 --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/operations/ExecuteTag.java @@ -0,0 +1,69 @@ +/* + * 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.wso2.carbon.mdm.mobileservices.windows.operations; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.wso2.carbon.mdm.mobileservices.windows.operations.util.Constants; + +import java.util.Iterator; +import java.util.List; + +/** + * Commands that needs to be executed on the device. + */ +public class ExecuteTag { + int commandId = -1; + List items; + + public int getCommandId() { + return commandId; + } + + public void setCommandId(int commandId) { + this.commandId = commandId; + } + + public List getItems() { + return items; + } + + public void setItems(List items) { + this.items = items; + } + + public void buildExecElement(Document doc, Element rootElement) { + if (getItems() != null) { + Element exec = doc.createElement(Constants.EXECUTE); + rootElement.appendChild(exec); + if (getCommandId() != -1) { + Element commandId = doc.createElement(Constants.COMMAND_ID); + commandId.appendChild(doc.createTextNode(String.valueOf(getCommandId()))); + exec.appendChild(commandId); + } + for (Iterator itemIterator = getItems().iterator(); itemIterator.hasNext(); ) { + Item item = itemIterator.next(); + if (item != null) { + item.buildItemElement(doc, exec); + } + } + } + } + +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/operations/Get.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/operations/Get.java new file mode 100644 index 0000000000..6666e329a2 --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/operations/Get.java @@ -0,0 +1,71 @@ +/* + * 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.wso2.carbon.mdm.mobileservices.windows.operations; + +import org.wso2.carbon.mdm.mobileservices.windows.operations.util.Constants; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +import java.util.Iterator; +import java.util.List; + +/** + * Data that needs to be retrieved from the device. + */ +public class Get { + int commandId = -1; + List items; + + public int getCommandId() { + return commandId; + } + + public void setCommandId(int commandId) { + this.commandId = commandId; + } + + public List getItems() { + return items; + } + + public void setItems(List items) { + this.items = items; + } + + public void buildGetElement(Document doc, Element rootElement) { + if (getItems() != null) { + Element get = doc.createElement(Constants.GET); + rootElement.appendChild(get); + if (getCommandId() != -1) { + Element commandId = doc.createElement(Constants.COMMAND_ID); + commandId.appendChild(doc.createTextNode(String.valueOf(getCommandId()))); + get.appendChild(commandId); + } + if (getItems() != null) { + for (Iterator itemIterator = getItems().iterator(); itemIterator.hasNext(); ) { + Item item = itemIterator.next(); + if (item != null) { + item.buildItemElement(doc, get); + } + } + } + } + } + +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/operations/Item.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/operations/Item.java new file mode 100644 index 0000000000..54cf5f65cf --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/operations/Item.java @@ -0,0 +1,90 @@ +/* + * 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.wso2.carbon.mdm.mobileservices.windows.operations; + +import org.wso2.carbon.mdm.mobileservices.windows.operations.util.Constants; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +/** + * Represents an items that should be retrieved from the device or a command. + */ +public class Item { + + Target target; + Source source; + String data; + MetaTag meta; + + public MetaTag getMeta() { + return meta; + } + + public void setMeta(MetaTag meta) { + this.meta = meta; + } + + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } + + public Source getSource() { + return source; + } + + public void setSource(Source source) { + this.source = source; + } + + public Target getTarget() { + return target; + } + + public void setTarget(Target target) { + this.target = target; + } + + public void buildItemElement(Document doc, Element rootElement) { + Element item = doc.createElement(Constants.ITEM); + rootElement.appendChild(item); + + if (getTarget() != null || getSource() != null) { + + if (getTarget() != null) { + getTarget().buildTargetElement(doc, item); + } + if (getSource() != null) { + getSource().buildSourceElement(doc, item); + } + } + if (getData() != null) { + Element data = doc.createElement(Constants.DATA); + data.appendChild(doc.createTextNode(getData())); + item.appendChild(data); + } + if (getMeta() != null) { + getMeta().buildMetaElement(doc, item); + } + + } +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/operations/MetaTag.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/operations/MetaTag.java new file mode 100644 index 0000000000..fd22b710f3 --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/operations/MetaTag.java @@ -0,0 +1,88 @@ +/* + * 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.wso2.carbon.mdm.mobileservices.windows.operations; + +import org.w3c.dom.Attr; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.wso2.carbon.mdm.mobileservices.windows.operations.util.Constants; + +/** + * MetaTag data related to credentials. + */ +public class MetaTag { + + String format; + String type; + String nextNonce; + + public String getNextNonce() { + return nextNonce; + } + + public void setNextNonce(String nextNonce) { + this.nextNonce = nextNonce; + } + + public String getFormat() { + return format; + } + + public void setFormat(String format) { + this.format = format; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public void buildMetaElement(Document doc, Element rootElement) { + Element meta = doc.createElement(Constants.META); + rootElement.appendChild(meta); + if (getFormat() != null) { + Element format = doc.createElement(Constants.FORMAT); + format.appendChild(doc.createTextNode(getFormat())); + Attr attr = doc.createAttribute(Constants.XMLNS); + attr.setValue(Constants.META_NAMESPACE); + format.setAttributeNode(attr); + meta.appendChild(format); + } + if (getType() != null) { + Element type = doc.createElement(Constants.TYPE); + type.appendChild(doc.createTextNode(getType())); + Attr attr = doc.createAttribute(Constants.XMLNS); + attr.setValue(Constants.META_NAMESPACE); + type.setAttributeNode(attr); + meta.appendChild(type); + } + if (getNextNonce() != null) { + Element nextNonce = doc.createElement(Constants.NEXTNONCE); + nextNonce.appendChild(doc.createTextNode(getNextNonce())); + Attr attr = doc.createAttribute(Constants.XMLNS); + attr.setValue(Constants.META_NAMESPACE); + nextNonce.setAttributeNode(attr); + meta.appendChild(nextNonce); + } + + } +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/operations/Replace.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/operations/Replace.java new file mode 100644 index 0000000000..4a8058cca3 --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/operations/Replace.java @@ -0,0 +1,70 @@ +/* + * 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.wso2.carbon.mdm.mobileservices.windows.operations; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.wso2.carbon.mdm.mobileservices.windows.operations.util.Constants; + +import java.util.Iterator; +import java.util.List; + +/** + * Commands sent from the device. + */ +public class Replace { + int commandId = -1; + List items; + + public int getCommandId() { + return commandId; + } + + public void setCommandId(int commandId) { + this.commandId = commandId; + } + + public List getItems() { + return items; + } + + public void setItems(List items) { + this.items = items; + } + + public void buildReplaceElement(Document doc, Element rootElement) { + if (getItems() != null) { + Element replace = doc.createElement(Constants.REPLACE); + rootElement.appendChild(replace); + if (getCommandId() != -1) { + Element commandId = doc.createElement(Constants.COMMAND_ID); + commandId.appendChild(doc.createTextNode(String.valueOf(getCommandId()))); + replace.appendChild(commandId); + } + if (getItems() != null) { + for (Iterator itemIterator = getItems().iterator(); itemIterator.hasNext(); ) { + Item item = itemIterator.next(); + if (item != null) { + item.buildItemElement(doc, replace); + } + } + } + } + } +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/operations/Results.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/operations/Results.java new file mode 100644 index 0000000000..a310bd125d --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/operations/Results.java @@ -0,0 +1,96 @@ +/* + * 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.wso2.carbon.mdm.mobileservices.windows.operations; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.wso2.carbon.mdm.mobileservices.windows.operations.util.Constants; + +import java.util.Iterator; +import java.util.List; + +/** + * Results sent for the requests made to the device. + */ +public class Results { + int commandId = -1; + int messageReference = -1; + int commandReference = -1; + List item; + + public int getCommandId() { + return commandId; + } + + public void setCommandId(int commandId) { + this.commandId = commandId; + } + + public int getMessageReference() { + return messageReference; + } + + public void setMessageReference(int messageReference) { + this.messageReference = messageReference; + } + + public int getCommandReference() { + return commandReference; + } + + public void setCommandReference(int commandReference) { + this.commandReference = commandReference; + } + + public List getItem() { + return item; + } + + public void setItem(List item) { + this.item = item; + } + + public void buildResultElement(Document doc, Element rootElement) { + Element results = doc.createElement(Constants.RESULTS); + rootElement.appendChild(results); + if (getCommandId() != -1) { + Element commandId = doc.createElement(Constants.COMMAND_ID); + commandId.appendChild(doc.createTextNode(String.valueOf(getCommandId()))); + results.appendChild(commandId); + } + if (getMessageReference() != -1) { + Element messageReference = doc.createElement(Constants.MESSAGE_REFERENCE); + messageReference.appendChild(doc.createTextNode(String.valueOf(getMessageReference()))); + results.appendChild(messageReference); + } + if (getCommandReference() != -1) { + Element messageReference = doc.createElement(Constants.COMMAND_REFERENCE); + messageReference.appendChild(doc.createTextNode(String.valueOf(getCommandReference()))); + results.appendChild(messageReference); + } + if (getItem() != null) { + for (Iterator itemIterator = getItem().iterator(); itemIterator.hasNext(); ) { + Item item = itemIterator.next(); + if (item != null) { + item.buildItemElement(doc, results); + } + } + } + } +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/operations/SequenceTag.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/operations/SequenceTag.java new file mode 100644 index 0000000000..4295128024 --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/operations/SequenceTag.java @@ -0,0 +1,117 @@ +/* + * 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.wso2.carbon.mdm.mobileservices.windows.operations; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.wso2.carbon.mdm.mobileservices.windows.operations.util.Constants; + +import java.util.Iterator; +import java.util.List; + +/** + * Sequence data that use to execute tag list + */ +public class SequenceTag { + + int commandId; + ExecuteTag exec; + Get get; + DeleteTag deleteTag; + AtomicTag atomicTag; + List replaces; + + public DeleteTag getDeleteTag() { + return deleteTag; + } + + public void setDeleteTag(DeleteTag deleteTag) { + this.deleteTag = deleteTag; + } + + public List getReplaces() { + return replaces; + } + + public void setReplaces(List replaces) { + this.replaces = replaces; + } + + public AtomicTag getAtomicTag() { + return atomicTag; + } + + public void setAtomicTag(AtomicTag atomicTag) { + this.atomicTag = atomicTag; + } + + public ExecuteTag getExec() { + return exec; + } + + public void setExec(ExecuteTag exec) { + this.exec = exec; + } + + public int getCommandId() { + return commandId; + } + + public void setCommandId(int commandId) { + this.commandId = commandId; + } + + public Get getGet() { + return get; + } + + public void setGet(Get get) { + this.get = get; + } + + public void buildSequenceElement(Document doc, Element rootElement) { + Element sequence = doc.createElement(Constants.SEQUENCE); + rootElement.appendChild(sequence); + if (getCommandId() != -1) { + Element commandId = doc.createElement(Constants.COMMAND_ID); + commandId.appendChild(doc.createTextNode(String.valueOf(getCommandId()))); + sequence.appendChild(commandId); + } + if (getExec() != null) { + getExec().buildExecElement(doc, sequence); + } + if (getGet() != null) { + getGet().buildGetElement(doc, sequence); + } + if (getReplaces() != null) { + for (Iterator replaceIterator = getReplaces().iterator(); replaceIterator.hasNext(); ) { + Replace replace = replaceIterator.next(); + if (replace != null) { + replace.buildReplaceElement(doc, sequence); + } + } + } + if (getAtomicTag() != null) { + getAtomicTag().buildAtomicElement(doc, sequence); + } + if (getDeleteTag() != null) { + getDeleteTag().buildDeleteElement(doc, sequence); + } + } +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/operations/Source.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/operations/Source.java new file mode 100644 index 0000000000..af0f5cbb38 --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/operations/Source.java @@ -0,0 +1,63 @@ +/* + * 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.wso2.carbon.mdm.mobileservices.windows.operations; + +import org.wso2.carbon.mdm.mobileservices.windows.operations.util.Constants; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +/** + * Source details of syncml header's. + */ +public class Source { + + private String LocURI; + private String LocName; + + public String getLocURI() { + return LocURI; + } + + public void setLocURI(String locURI) { + LocURI = locURI; + } + + public String getLocName() { + return LocName; + } + + public void setLocName(String locName) { + LocName = locName; + } + + public void buildSourceElement(Document doc, Element rootElement) { + Element target = doc.createElement(Constants.SOURCE); + rootElement.appendChild(target); + if (getLocURI() != null) { + Element locURI = doc.createElement(Constants.LOC_URI); + locURI.appendChild(doc.createTextNode(getLocURI())); + target.appendChild(locURI); + } + if (getLocName() != null) { + Element locName = doc.createElement(Constants.LOC_NAME); + locName.appendChild(doc.createTextNode(getLocName())); + target.appendChild(locName); + } + } +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/operations/Status.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/operations/Status.java new file mode 100644 index 0000000000..31c209aef3 --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/operations/Status.java @@ -0,0 +1,144 @@ +/* + * 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.wso2.carbon.mdm.mobileservices.windows.operations; + +import org.wso2.carbon.mdm.mobileservices.windows.operations.util.Constants; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +/** + * Status of a previously sent message to device; + */ +public class Status { + int commandId = -1; + int messageReference = -1; + int commandReference = -1; + String command; + String targetReference; + String data; + ChallengeTag challenge; + + public Status(int commandId, int messageReference, int commandReference, String command, + String targetReference, String data) { + this.commandId = commandId; + this.messageReference = messageReference; + this.commandReference = commandReference; + this.command = command; + this.targetReference = targetReference; + this.data = data; + } + + public Status() { + } + + public ChallengeTag getChallenge() { + return challenge; + } + + public void setChallenge(ChallengeTag challenge) { + this.challenge = challenge; + } + + public String getTargetReference() { + return targetReference; + } + + public void setTargetReference(String targetReference) { + this.targetReference = targetReference; + } + + public int getCommandId() { + return commandId; + } + + public void setCommandId(int commandId) { + this.commandId = commandId; + } + + public int getMessageReference() { + return messageReference; + } + + public void setMessageReference(int messageReference) { + this.messageReference = messageReference; + } + + public int getCommandReference() { + return commandReference; + } + + public void setCommandReference(int commandReference) { + this.commandReference = commandReference; + } + + public String getCommand() { + return command; + } + + public void setCommand(String command) { + this.command = command; + } + + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } + + public void buildStatusElement(Document doc, Element rootElement) { + Element status = doc.createElement(Constants.STATUS); + rootElement.appendChild(status); + if (getCommandId() != -1) { + Element commandId = doc.createElement(Constants.COMMAND_ID); + commandId.appendChild(doc.createTextNode(String.valueOf(getCommandId()))); + status.appendChild(commandId); + } + if (getMessageReference() != -1) { + Element msgReference = doc.createElement(Constants.MESSAGE_REFERENCE); + msgReference.appendChild(doc.createTextNode(String.valueOf(getMessageReference()))); + status.appendChild(msgReference); + } + if (getCommandReference() != -1) { + Element commandReference = doc.createElement(Constants.COMMAND_REFERENCE); + commandReference.appendChild(doc.createTextNode(String.valueOf(getCommandReference()))); + status.appendChild(commandReference); + } + if (getCommand() != null) { + Element command = doc.createElement(Constants.COMMAND); + command.appendChild(doc.createTextNode(getCommand())); + status.appendChild(command); + } + if (getTargetReference() != null) { + Element targetReference = doc.createElement(Constants.TARGET_REFERENCE); + targetReference.appendChild(doc.createTextNode(getTargetReference())); + status.appendChild(targetReference); + } + if (getChallenge() != null) { + getChallenge().buildChallElement(doc, status); + } + if (getData() != null) { + Element data = doc.createElement(Constants.DATA); + data.appendChild(doc.createTextNode(getData())); + status.appendChild(data); + } + } + +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/operations/SyncmlBody.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/operations/SyncmlBody.java new file mode 100644 index 0000000000..ac132907af --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/operations/SyncmlBody.java @@ -0,0 +1,144 @@ +/* + * 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.wso2.carbon.mdm.mobileservices.windows.operations; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.wso2.carbon.mdm.mobileservices.windows.operations.util.Constants; + +import java.util.Iterator; +import java.util.List; + +/** + * Represents the body details of a syncml. + */ +public class SyncmlBody { + Get getCommands; + List exec; + List status; + Alert alert; + Replace replace; + Results results; + SequenceTag sequence; + AtomicTag atomicTag; + + public AtomicTag getAtomicTag() { + return atomicTag; + } + + public void setAtomicTag(AtomicTag atomicTag) { + this.atomicTag = atomicTag; + } + + public SequenceTag getSequence() { + return sequence; + } + + public void setSequence(SequenceTag sequence) { + this.sequence = sequence; + } + + public List getExec() { + return exec; + } + + public void setExec(List exec) { + this.exec = exec; + } + + public Results getResults() { + return results; + } + + public void setResults(Results results) { + this.results = results; + } + + public Replace getReplace() { + return replace; + } + + public void setReplace(Replace replace) { + this.replace = replace; + } + + public List getStatus() { + return status; + } + + public void setStatus(List status) { + this.status = status; + } + + public Alert getAlert() { + return alert; + } + + public void setAlert(Alert alert) { + this.alert = alert; + } + + public Get getGet() { + return getCommands; + } + + public void setGet(Get get) { + this.getCommands = get; + } + + public void buildBodyElement(Document doc, Element rootElement) { + + Element syncBody = doc.createElement(Constants.SYNC_BODY); + rootElement.appendChild(syncBody); + if (getStatus() != null) { + for (int x = 0; x < getStatus().size(); x++) { + if (getStatus().get(x) != null) { + getStatus().get(x).buildStatusElement(doc, syncBody); + } + } + } + if (getAlert() != null) { + getAlert().buildAlertElement(doc, syncBody); + } + if (getResults() != null) { + getResults().buildResultElement(doc, syncBody); + } + if (getGet() != null) { + getGet().buildGetElement(doc, syncBody); + } + if (getReplace() != null) { + getReplace().buildReplaceElement(doc, syncBody); + } + if (getExec() != null) { + for (Iterator execIterator = getExec().iterator(); execIterator.hasNext(); ) { + ExecuteTag exec = execIterator.next(); + if (exec != null) { + exec.buildExecElement(doc, syncBody); + } + } + } + if (getSequence() != null) { + getSequence().buildSequenceElement(doc, syncBody); + } + if (getAtomicTag() != null) { + getAtomicTag().buildAtomicElement(doc, syncBody); + } + syncBody.appendChild(doc.createElement(Constants.FINAL)); + } +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/operations/SyncmlDocument.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/operations/SyncmlDocument.java new file mode 100644 index 0000000000..284897abe3 --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/operations/SyncmlDocument.java @@ -0,0 +1,55 @@ +/* + * 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.wso2.carbon.mdm.mobileservices.windows.operations; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +/** + * Represents a base format of a syncml document + */ +public class SyncmlDocument { + SyncmlHeader header; + SyncmlBody body; + + public SyncmlHeader getHeader() { + return header; + } + + public void setHeader(SyncmlHeader header) { + this.header = header; + } + + public SyncmlBody getBody() { + return body; + } + + public void setBody(SyncmlBody body) { + this.body = body; + } + + public void buildDocument(Document doc, Element rootElement) { + if (getHeader() != null) { + getHeader().buildSyncmlHeaderElement(doc, rootElement); + } + if (getBody() != null) { + getBody().buildBodyElement(doc, rootElement); + } + } +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/operations/SyncmlHeader.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/operations/SyncmlHeader.java new file mode 100644 index 0000000000..e0fc9deab6 --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/operations/SyncmlHeader.java @@ -0,0 +1,114 @@ +/* + * 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.wso2.carbon.mdm.mobileservices.windows.operations; + +import org.wso2.carbon.mdm.mobileservices.windows.operations.util.Constants; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +/** + * Represents the header details of a syncml. + */ +public class SyncmlHeader { + private int sessionId = -1; + private int MsgID = -1; + private Target target; + private Source source; + private Credential credential; + private String hexadecimalSessionId; + + public String getHexadecimalSessionId() { + return hexadecimalSessionId; + } + + public void setHexadecimalSessionId(String hexSessionId) { + this.hexadecimalSessionId = hexSessionId; + } + + public Credential getCredential() { + return credential; + } + + public void setCredential(Credential credential) { + this.credential = credential; + } + + public int getSessionId() { + return sessionId; + } + + public void setSessionId(int sessionId) { + this.sessionId = sessionId; + } + + public int getMsgID() { + return MsgID; + } + + public void setMsgID(int msgID) { + this.MsgID = msgID; + } + + public Target getTarget() { + return target; + } + + public void setTarget(Target target) { + this.target = target; + } + + public Source getSource() { + return source; + } + + public void setSource(Source source) { + this.source = source; + } + + public void buildSyncmlHeaderElement(Document doc, Element rootElement) { + Element syncHdr = doc.createElement(Constants.SYNC_HDR); + rootElement.appendChild(syncHdr); + Element verDTD = doc.createElement(Constants.VER_DTD); + verDTD.appendChild(doc.createTextNode(Constants.VER_DTD_VALUE)); + syncHdr.appendChild(verDTD); + + Element verProtocol = doc.createElement(Constants.VER_PROTOCOL); + verProtocol.appendChild(doc.createTextNode(Constants.VER_PROTOCOL_VALUE)); + syncHdr.appendChild(verProtocol); + if (getHexadecimalSessionId() != null) { + Element sessionId = doc.createElement(Constants.SESSION_ID); + sessionId.appendChild(doc.createTextNode(getHexadecimalSessionId())); + syncHdr.appendChild(sessionId); + } + if (getMsgID() != -1) { + Element msgId = doc.createElement(Constants.MESSAGE_ID); + msgId.appendChild(doc.createTextNode(String.valueOf(getMsgID()))); + syncHdr.appendChild(msgId); + } + if (getTarget() != null) { + getTarget().buildTargetElement(doc, syncHdr); + } + if (getSource() != null) { + getSource().buildSourceElement(doc, syncHdr); + } + if (getCredential() != null) { + getCredential().buildCredentialElement(doc, syncHdr); + } + } +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/operations/Target.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/operations/Target.java new file mode 100644 index 0000000000..03efb3ee19 --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/operations/Target.java @@ -0,0 +1,63 @@ +/* + * 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.wso2.carbon.mdm.mobileservices.windows.operations; + +import org.wso2.carbon.mdm.mobileservices.windows.operations.util.Constants; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +/** + * Target details of syncml header's. + */ +public class Target { + + private String LocURI; + private String LocName; + + public String getLocURI() { + return LocURI; + } + + public void setLocURI(String locURI) { + LocURI = locURI; + } + + public String getLocName() { + return LocName; + } + + public void setLocName(String locName) { + LocName = locName; + } + + public void buildTargetElement(Document doc, Element rootElement) { + Element target = doc.createElement(Constants.TARGET); + rootElement.appendChild(target); + if (getLocURI() != null) { + Element locURI = doc.createElement(Constants.LOC_URI); + locURI.appendChild(doc.createTextNode(getLocURI())); + target.appendChild(locURI); + } + if (getLocName() != null) { + Element locName = doc.createElement(Constants.LOC_NAME); + locName.appendChild(doc.createTextNode(getLocName())); + target.appendChild(locName); + } + } +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/operations/WindowsOperationException.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/operations/WindowsOperationException.java new file mode 100644 index 0000000000..d4202f660c --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/operations/WindowsOperationException.java @@ -0,0 +1,58 @@ +/* + * 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.wso2.carbon.mdm.mobileservices.windows.operations; + +/** + * Exceptions related to operation retrieval and syncml generation + */ +public class WindowsOperationException extends Exception { + + private static final long serialVersionUID = 5435636243242623629L; + private String errorMessage; + + public String getErrorMessage() { + return errorMessage; + } + + public void setErrorMessage(String errorMessage) { + this.errorMessage = errorMessage; + } + + public WindowsOperationException(String errorMessage) { + super(errorMessage); + } + + public WindowsOperationException(String errorMessage, Throwable throwable) { + super(errorMessage, throwable); + setErrorMessage(errorMessage); + } + + public WindowsOperationException(String msg, Exception nestedEx) { + super(msg, nestedEx); + setErrorMessage(msg); + } + + public WindowsOperationException() { + super(); + } + + public WindowsOperationException(Throwable throwable) { + super(throwable); + } +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/operations/util/Constants.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/operations/util/Constants.java new file mode 100644 index 0000000000..6c4443165b --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/operations/util/Constants.java @@ -0,0 +1,98 @@ +/* + * 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.wso2.carbon.mdm.mobileservices.windows.operations.util; + +/** + * Constant values used in syncml generator. + */ +public class Constants { + + public static final String PROVIDER_ID = "MobiCDMServer"; + public static final String SERVER_SECRET = "dummy"; + public static final String INITIAL_NONCE = "ZHVtbXk="; + public static final String DISENROLL_ALERT_DATA = "1226"; + public static final String INITIAL_ALERT_DATA = "1201"; + public static final int EMPTY = 0; + + public static final String SYNCML_ROOT_ELEMENT_NAME = "SyncML"; + public static final String XMLNS_SYNCML = "SYNCML:SYNCML1.2"; + public static final String UTF_8 = "UTF-8"; + public static final String MD5 = "MD5"; + public static final String YES = "yes"; + public static final String ERROR = "Error"; + public static final String INFORMATION = "information"; + + public static final String EXECUTE = "Exec"; + public static final String ATOMIC = "Atomic"; + public static final String ADD = "Add"; + public static final String COMMAND_ID = "CmdID"; + public static final String GET = "Get"; + public static final String DELETE = "Delete"; + public static final String ITEM = "Item"; + public static final String SOURCE = "Source"; + public static final String LOC_URI = "LocURI"; + public static final String LOC_NAME = "LocName"; + public static final String MESSAGE_REFERENCE = "MsgRef"; + public static final String COMMAND_REFERENCE = "CmdRef"; + public static final String COMMAND = "Cmd"; + public static final String TARGET_REFERENCE = "TargetRef"; + public static final String DATA = "Data"; + public static final String STATUS = "Status"; + public static final String SYNC_BODY = "SyncBody"; + public static final String SYNC_HDR = "SyncHdr"; + public static final String VER_DTD = "VerDTD"; + public static final String VER_PROTOCOL = "VerProto"; + public static final String SESSION_ID = "SessionID"; + public static final String MESSAGE_ID = "MsgID"; + public static final String TARGET = "Target"; + public static final String VER_DTD_VALUE = "1.2"; + public static final String VER_PROTOCOL_VALUE = "DM/1.2"; + public static final String ALERT = "Alert"; + public static final String FINAL = "Final"; + public static final String REPLACE = "Replace"; + public static final String META = "Meta"; + public static final String CREDENTIAL = "Cred"; + public static final String FORMAT = "Format"; + public static final String TYPE = "Type"; + public static final String NEXTNONCE = "NextNonce"; + public static final String CHALLENGE = "chal"; + public static final String META_NAMESPACE = "syncml:metinf"; + public static final String XMLNS = "xmlns"; + public static final String RESULTS = "Results"; + public static final String CRED_FORMAT = "b64"; + public static final String CRED_TYPE = "syncml:auth-md5"; + public static final String SEQUENCE = "Sequence"; + public static final String META_FORMAT_INT = "int"; + public static final String META_FORMAT_CHARACTER = "chr"; + + /** + * SynclML service related constants + */ + public final class SyncMLResponseCodes { + public static final String AUTHENTICATION_ACCEPTED = "212"; + public static final String ACCEPTED = "200"; + public static final String ACCEPTED_FOR_PROCESSING = "202"; + public static final String PIN_NOTFOUND = "405"; + public static final String CREDENTIALS_MISSING = "407"; + public static final String INVALID_CREDENTIALS = "401"; + public static final String LOCKRESET_NOTIFICATION = "Error occurred in Device Lock Operation. " + + "Please trigger lock-reset operation."; + } + +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/operations/util/DeviceInfo.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/operations/util/DeviceInfo.java new file mode 100644 index 0000000000..052ff48e2e --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/operations/util/DeviceInfo.java @@ -0,0 +1,92 @@ +/* + * 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.wso2.carbon.mdm.mobileservices.windows.operations.util; + +import org.wso2.carbon.device.mgt.common.operation.mgt.Operation; +import org.wso2.carbon.mdm.mobileservices.windows.common.PluginConstants; + +import java.util.ArrayList; +import java.util.List; + +/** + * Class generate Info type operation list. + */ +public class DeviceInfo { + public List getDeviceInfo() { + + List deviceInfoOperations = new ArrayList<>(); + + Operation osVersion = new Operation(); + osVersion.setCode(PluginConstants.SyncML.SOFTWARE_VERSION); + osVersion.setType(Operation.Type.INFO); + deviceInfoOperations.add(osVersion); + + Operation imsi = new Operation(); + imsi.setCode(PluginConstants.SyncML.IMSI); + imsi.setType(Operation.Type.INFO); + deviceInfoOperations.add(imsi); + + Operation imei = new Operation(); + imei.setCode(PluginConstants.SyncML.IMEI); + imei.setType(Operation.Type.INFO); + deviceInfoOperations.add(imei); + + Operation deviceID = new Operation(); + deviceID.setCode(PluginConstants.SyncML.DEV_ID); + deviceID.setType(Operation.Type.INFO); + deviceInfoOperations.add(deviceID); + + Operation manufacturer = new Operation(); + manufacturer.setCode(PluginConstants.SyncML.MANUFACTURER); + manufacturer.setType(Operation.Type.INFO); + deviceInfoOperations.add(manufacturer); + + Operation model = new Operation(); + model.setCode(PluginConstants.SyncML.MODEL); + model.setType(Operation.Type.INFO); + deviceInfoOperations.add(model); + + Operation language = new Operation(); + language.setCode(PluginConstants.SyncML.LANGUAGE); + language.setType(Operation.Type.INFO); + deviceInfoOperations.add(language); + + Operation vendor = new Operation(); + vendor.setCode(PluginConstants.SyncML.VENDOR); + vendor.setType(Operation.Type.INFO); + deviceInfoOperations.add(vendor); + + Operation macaddress = new Operation(); + macaddress.setCode(PluginConstants.SyncML.MAC_ADDRESS); + macaddress.setType(Operation.Type.INFO); + deviceInfoOperations.add(macaddress); + + Operation resolution = new Operation(); + resolution.setCode(PluginConstants.SyncML.RESOLUTION); + resolution.setType(Operation.Type.INFO); + deviceInfoOperations.add(resolution); + + Operation deviceName = new Operation(); + deviceName.setCode(PluginConstants.SyncML.DEVICE_NAME); + deviceName.setType(Operation.Type.INFO); + deviceInfoOperations.add(deviceName); + + return deviceInfoOperations; + } +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/operations/util/OperationCode.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/operations/util/OperationCode.java new file mode 100644 index 0000000000..c586cf619b --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/operations/util/OperationCode.java @@ -0,0 +1,112 @@ +/* + * 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.wso2.carbon.mdm.mobileservices.windows.operations.util; + +/** + * Maps operation codes to device specific format. + */ +public class OperationCode { + public static enum Info { + DEV_ID("./DevInfo/DevId"), + MANUFACTURER("./DevInfo/Man"), + DEVICE_MODEL("./DevInfo/Mod"), + DM_VERSION("./DevInfo/DmV"), + LANGUAGE("./DevInfo/Lang"), + IMSI("./Vendor/MSFT/DeviceInstanceService/Identity/Identity1/IMSI"), + IMEI("./Vendor/MSFT/DeviceInstanceService/Identity/Identity1/IMEI"), + SOFTWARE_VERSION("./DevDetail/SwV"), + VENDER("./DevDetail/OEM"), + MAC_ADDRESS("./DevDetail/Ext/WLANMACAddress"), + RESOLUTION("./DevDetail/Ext/Microsoft/Resolution"), + DEVICE_NAME("./DevDetail/Ext/Microsoft/DeviceName"), + CHANNEL_URI("./Vendor/MSFT/DMClient/Provider/MobiCDMServer/Push/ChannelURI"), + LOCK_PIN("./Vendor/MSFT/RemoteLock/NewPINValue"), + LOCK_RESET("./Vendor/MSFT/RemoteLock/LockAndResetPIN"), + CAMERA("./Vendor/MSFT/PolicyManager/My/Camera/AllowCamera"), + CAMERA_STATUS("./Vendor/MSFT/PolicyManager/Device/Camera/AllowCamera"), + ENCRYPT_STORAGE_STATUS("./Vendor/MSFT/PolicyManager/Device/Security/RequireDeviceEncryption"), + DEVICE_PASSWORD_STATUS("./Vendor/MSFT/PolicyManager/Device/DeviceLock/DevicePasswordEnabled"), + DEVICE_PASSCODE_DELETE("./Vendor/MSFT/PolicyManager/My/DeviceLock"), + LONGITUDE("./Vendor/MSFT/RemoteFind/Location/Longitude"), + LATITUDE("./Vendor/MSFT/RemoteFind/Location/Latitude"); + + private final String code; + + Info(String code) { + this.code = code; + } + + public String getCode() { + return this.code; + } + + } + + public static enum Command { + DEVICE_RING("./Vendor/MSFT/RemoteRing/Ring"), + DEVICE_LOCK("./Vendor/MSFT/RemoteLock/Lock"), + WIPE_DATA("./Vendor/MSFT/RemoteWipe/doWipe"), + DISENROLL("./Vendor/MSFT/DMClient/Unenroll"), + LOCK_RESET("./Vendor/MSFT/RemoteLock/LockAndResetPIN"), + CAMERA("./Vendor/MSFT/PolicyManager/My/Camera/AllowCamera"), + ENCRYPT_STORAGE("./Vendor/MSFT/PolicyManager/My/Security/RequireDeviceEncryption"), + CAMERA_STATUS("./Vendor/MSFT/PolicyManager/Device/Camera/AllowCamera"), + ENCRYPT_STORAGE_STATUS("./Vendor/MSFT/PolicyManager/Device/Security/RequireDeviceEncryption"), + DEVICE_PASSWORD_ENABLE("./Vendor/MSFT/PolicyManager/My/DeviceLock/DevicePasswordEnabled"); + + private final String code; + + Command(String code) { + this.code = code; + } + + public String getCode() { + return this.code; + } + + } + + public static enum Configure { + WIFI("./Vendor/MSFT/WiFi/Profile/MyNetwork/WlanXml"), + CAMERA("./Vendor/MSFT/PolicyManager/My/Camera/AllowCamera"), + CAMERA_STATUS("./Vendor/MSFT/PolicyManager/Device/Camera/AllowCamera"), + ENCRYPT_STORAGE("./Vendor/MSFT/PolicyManager/My/Security/RequireDeviceEncryption"), + ENCRYPT_STORAGE_STATUS("./Vendor/MSFT/PolicyManager/Device/Security/RequireDeviceEncryption"), + PASSWORD_MAX_FAIL_ATTEMPTS("./Vendor/MSFT/PolicyManager/My/DeviceLock/MaxDevicePasswordFailedAttempts"), + DEVICE_PASSWORD_ENABLE("./Vendor/MSFT/PolicyManager/My/DeviceLock/DevicePasswordEnabled"), + SIMPLE_PASSWORD("./Vendor/MSFT/PolicyManager/My/DeviceLock/AllowSimpleDevicePassword"), + MIN_PASSWORD_LENGTH("./Vendor/MSFT/PolicyManager/My/DeviceLock/MinDevicePasswordLength"), + Alphanumeric_PASSWORD("./Vendor/MSFT/PolicyManager/My/DeviceLock/AlphanumericDevicePasswordRequired"), + PASSWORD_EXPIRE("./Vendor/MSFT/PolicyManager/My/DeviceLock/DevicePasswordExpiration"), + PASSWORD_HISTORY("./Vendor/MSFT/PolicyManager/My/DeviceLock/DevicePasswordHistory"), + MAX_PASSWORD_INACTIVE_TIME("./Vendor/MSFT/PolicyManager/My/DeviceLock/MaxInactivityTimeDeviceLock"), + MIN_PASSWORD_COMPLEX_CHARACTERS("./Vendor/MSFT/PolicyManager/My/DeviceLock/MinDevicePasswordComplexCharacters"); + + private final String code; + + Configure(String code) { + this.code = code; + } + + public String getCode() { + return this.code; + } + + } +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/operations/util/OperationReply.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/operations/util/OperationReply.java new file mode 100644 index 0000000000..289bf3a941 --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/operations/util/OperationReply.java @@ -0,0 +1,772 @@ +/* + * 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.wso2.carbon.mdm.mobileservices.windows.operations.util; + +import com.google.gson.Gson; +import org.json.JSONException; +import org.json.JSONObject; +import org.wso2.carbon.device.mgt.common.DeviceIdentifier; +import org.wso2.carbon.device.mgt.common.operation.mgt.Operation; +import org.wso2.carbon.mdm.mobileservices.windows.common.PluginConstants; +import org.wso2.carbon.mdm.mobileservices.windows.common.SyncmlCommandType; +import org.wso2.carbon.mdm.mobileservices.windows.common.exceptions.SyncmlMessageFormatException; +import org.wso2.carbon.mdm.mobileservices.windows.common.exceptions.SyncmlOperationException; +import org.wso2.carbon.mdm.mobileservices.windows.common.util.WindowsAPIUtils; +import org.wso2.carbon.mdm.mobileservices.windows.operations.*; +import org.wso2.carbon.mdm.mobileservices.windows.services.syncml.beans.PasscodePolicy; +import org.wso2.carbon.mdm.mobileservices.windows.services.syncml.beans.Wifi; +import org.wso2.carbon.policy.mgt.common.FeatureManagementException; +import org.wso2.carbon.policy.mgt.common.PolicyManagementException; +import org.wso2.carbon.policy.mgt.common.ProfileFeature; + +import java.util.ArrayList; +import java.util.List; + +import static org.wso2.carbon.mdm.mobileservices.windows.common.util.WindowsAPIUtils.convertToDeviceIdentifierObject; +import static org.wso2.carbon.mdm.mobileservices.windows.operations.util.OperationCode.*; + +/** + * Used to generate a reply to a receiving syncml from a device. + */ +public class OperationReply { + + private SyncmlDocument syncmlDocument; + private SyncmlDocument replySyncmlDocument; + private int headerCommandId = 1; + private static final int HEADER_STATUS_ID = 0; + private static final String RESULTS_COMMAND_TEXT = "Results"; + private static final String HEADER_COMMAND_TEXT = "SyncHdr"; + private static final String ALERT_COMMAND_TEXT = "Alert"; + private static final String REPLACE_COMMAND_TEXT = "Replace"; + private static final String GET_COMMAND_TEXT = "Get"; + private static final String EXEC_COMMAND_TEXT = "Exec"; + private List operations; + Gson gson = new Gson(); + + public OperationReply(SyncmlDocument syncmlDocument, List operations) { + this.syncmlDocument = syncmlDocument; + replySyncmlDocument = new SyncmlDocument(); + this.operations = operations; + } + + public OperationReply(SyncmlDocument syncmlDocument) { + this.syncmlDocument = syncmlDocument; + replySyncmlDocument = new SyncmlDocument(); + } + + public SyncmlDocument generateReply() throws SyncmlMessageFormatException, SyncmlOperationException { + generateHeader(); + generateBody(); + return replySyncmlDocument; + } + + private void generateHeader() throws SyncmlMessageFormatException { + String nextnonceValue = Constants.INITIAL_NONCE; + SyncmlHeader sourceHeader = syncmlDocument.getHeader(); + SyncmlHeader header = new SyncmlHeader(); + header.setMsgID(sourceHeader.getMsgID()); + header.setHexadecimalSessionId(Integer.toHexString(sourceHeader.getSessionId())); + Target target = new Target(); + target.setLocURI(sourceHeader.getSource().getLocURI()); + header.setTarget(target); + + Source source = new Source(); + source.setLocURI(sourceHeader.getTarget().getLocURI()); + header.setSource(source); + + Credential cred = new Credential(); + if (sourceHeader.getCredential() == null) { + MetaTag meta = new MetaTag(); + meta.setFormat(Constants.CRED_FORMAT); + meta.setType(Constants.CRED_TYPE); + cred.setMeta(meta); + } else { + cred.setMeta(sourceHeader.getCredential().getMeta()); + } + SyncmlBody sourcebody = syncmlDocument.getBody(); + List statuses = sourcebody.getStatus(); + + for (Status status : statuses) { + if (HEADER_COMMAND_TEXT.equals(status.getCommand()) && + status.getChallenge() != null) { + nextnonceValue = status.getChallenge().getMeta().getNextNonce(); + } + } + cred.setData(new SyncmlCredentials().generateCredData(nextnonceValue)); + header.setCredential(cred); + + replySyncmlDocument.setHeader(header); + } + + private void generateBody() throws SyncmlMessageFormatException, SyncmlOperationException { + SyncmlBody syncmlBody = generateStatuses(); + try { + appendOperations(syncmlBody); + } catch (PolicyManagementException e) { + throw new SyncmlOperationException("Error occurred while retrieving policy operations.", e); + } catch (FeatureManagementException e) { + throw new SyncmlOperationException("Error occurred while retrieving effective policy operations.", e); + } catch (JSONException e) { + throw new SyncmlMessageFormatException("Error Occurred while parsing operation object.", e); + } + replySyncmlDocument.setBody(syncmlBody); + } + + private SyncmlBody generateStatuses() { + SyncmlBody sourceSyncmlBody = syncmlDocument.getBody(); + SyncmlHeader sourceHeader = syncmlDocument.getHeader(); + Status headerStatus; + SyncmlBody syncmlBodyReply = new SyncmlBody(); + List statuses = new ArrayList<>(); + List sourceStatuses = sourceSyncmlBody.getStatus(); + if (sourceStatuses.isEmpty()) { + headerStatus = + new Status(headerCommandId, sourceHeader.getMsgID(), HEADER_STATUS_ID, + HEADER_COMMAND_TEXT, sourceHeader.getSource().getLocURI(), + String.valueOf(Constants.SyncMLResponseCodes.AUTHENTICATION_ACCEPTED)); + statuses.add(headerStatus); + } else { + for (Status sourceStatus : sourceStatuses) { + if (sourceStatus.getChallenge() != null && HEADER_COMMAND_TEXT.equals(sourceStatus.getCommand())) { + + headerStatus = + new Status(headerCommandId, sourceHeader.getMsgID(), HEADER_STATUS_ID, + HEADER_COMMAND_TEXT, sourceHeader.getSource().getLocURI(), + String.valueOf(Constants.SyncMLResponseCodes.AUTHENTICATION_ACCEPTED)); + statuses.add(headerStatus); + } + } + } + if (sourceSyncmlBody.getResults() != null) { + int ResultCommandId = ++headerCommandId; + Status resultStatus = new Status(ResultCommandId, sourceHeader.getMsgID(), + sourceSyncmlBody.getResults().getCommandId(), RESULTS_COMMAND_TEXT, null, + String.valueOf(Constants.SyncMLResponseCodes.ACCEPTED)); + statuses.add(resultStatus); + } + if (sourceSyncmlBody.getAlert() != null) { + int alertCommandId = ++headerCommandId; + Status alertStatus = new Status(alertCommandId, + sourceHeader.getMsgID(), + sourceSyncmlBody.getAlert().getCommandId(), + ALERT_COMMAND_TEXT, null, + String.valueOf(Constants.SyncMLResponseCodes.ACCEPTED)); + statuses.add(alertStatus); + } + if (sourceSyncmlBody.getReplace() != null) { + int replaceCommandId = ++headerCommandId; + Status replaceStatus = new Status(replaceCommandId, sourceHeader.getMsgID(), + sourceSyncmlBody.getReplace().getCommandId(), REPLACE_COMMAND_TEXT, null, + String.valueOf(Constants.SyncMLResponseCodes.ACCEPTED) + ); + statuses.add(replaceStatus); + } + if (sourceSyncmlBody.getExec() != null) { + List Executes = sourceSyncmlBody.getExec(); + for (ExecuteTag exec : Executes) { + int execCommandId = ++headerCommandId; + Status execStatus = new Status(execCommandId, sourceHeader.getMsgID(), + exec.getCommandId(), EXEC_COMMAND_TEXT, null, String.valueOf( + Constants.SyncMLResponseCodes.ACCEPTED)); + statuses.add(execStatus); + } + } + if (sourceSyncmlBody.getGet() != null) { + int getCommandId = ++headerCommandId; + Status execStatus = new Status(getCommandId, sourceHeader.getMsgID(), sourceSyncmlBody + .getGet().getCommandId(), GET_COMMAND_TEXT, null, String.valueOf( + Constants.SyncMLResponseCodes.ACCEPTED)); + statuses.add(execStatus); + } + syncmlBodyReply.setStatus(statuses); + return syncmlBodyReply; + } + + private void appendOperations(SyncmlBody syncmlBody) throws PolicyManagementException, + FeatureManagementException, JSONException, SyncmlOperationException { + Get getElement = new Get(); + List getElements = new ArrayList<>(); + List executeElements = new ArrayList<>(); + AtomicTag atomicTagElement = new AtomicTag(); + List addElements = new ArrayList<>(); + Replace replaceElement = new Replace(); + List replaceItems = new ArrayList<>(); + SequenceTag monitorSequence = new SequenceTag(); + + if (operations != null) { + for (Operation operation : operations) { + Operation.Type type = operation.getType(); + switch (type) { + case POLICY: + if (this.syncmlDocument.getBody().getAlert() != null) { + if (this.syncmlDocument.getBody().getAlert().getData().equals + (Constants.INITIAL_ALERT_DATA)) { + SequenceTag policySequence = new SequenceTag(); + policySequence = buildSequence(operation, policySequence); + syncmlBody.setSequence(policySequence); + } + } + break; + case CONFIG: + List addConfigurations = appendAddConfiguration(operation); + for (AddTag addConfiguration : addConfigurations) { + addElements.add(addConfiguration); + } + break; + case MESSAGE: + + break; + case INFO: + Item itemGet = appendGetInfo(operation); + getElements.add(itemGet); + break; + case COMMAND: + if (operation.getCode().equals(PluginConstants + .OperationCodes.DEVICE_LOCK)) { + ExecuteTag execElement = executeCommand(operation); + executeElements.add(execElement); + } + if (operation.getCode().equals(PluginConstants + .OperationCodes.DEVICE_RING)) { + ExecuteTag execElement = executeCommand(operation); + executeElements.add(execElement); + } + if (operation.getCode().equals(PluginConstants + .OperationCodes.DISENROLL)) { + ExecuteTag execElement = executeCommand(operation); + executeElements.add(execElement); + } + if (operation.getCode().equals(PluginConstants + .OperationCodes.WIPE_DATA)) { + ExecuteTag execElement = executeCommand(operation); + executeElements.add(execElement); + } + if (operation.getCode().equals(PluginConstants + .OperationCodes.LOCK_RESET)) { + SequenceTag sequenceElement = new SequenceTag(); + SequenceTag sequence = buildSequence(operation, sequenceElement); + syncmlBody.setSequence(sequence); + } + if (operation.getCode().equals(PluginConstants + .OperationCodes.MONITOR)) { + + Get monitorGetElement = new Get(); + List monitorItems; + List profileFeatures; + + if (this.syncmlDocument.getBody().getAlert() != null) { + if (this.syncmlDocument.getBody().getAlert().getData().equals + (Constants.INITIAL_ALERT_DATA)) { + + monitorSequence.setCommandId(operation.getId()); + DeviceIdentifier deviceIdentifier = convertToDeviceIdentifierObject( + syncmlDocument.getHeader().getSource().getLocURI()); + try { + profileFeatures = WindowsAPIUtils.getPolicyManagerService(). + getEffectiveFeatures(deviceIdentifier); + } catch (FeatureManagementException e) { + throw new SyncmlOperationException("Error in getting effective policy.", e); + } + monitorItems = buildMonitorOperation(profileFeatures); + if (!monitorItems.isEmpty()) { + monitorGetElement.setCommandId(operation.getId()); + monitorGetElement.setItems(monitorItems); + } + monitorSequence.setGet(monitorGetElement); + syncmlBody.setSequence(monitorSequence); + } + } + } + break; + } + } + } + if (!replaceItems.isEmpty()) { + replaceElement.setCommandId(300); + replaceElement.setItems(replaceItems); + } + if (!getElements.isEmpty()) { + getElement.setCommandId(75); + getElement.setItems(getElements); + } + if (!addElements.isEmpty()) { + atomicTagElement.setCommandId(400); + atomicTagElement.setAdds(addElements); + } + syncmlBody.setGet(getElement); + syncmlBody.setExec(executeElements); + syncmlBody.setAtomicTag(atomicTagElement); + syncmlBody.setReplace(replaceElement); + } + + private Item appendExecInfo(Operation operation) { + Item item = new Item(); + String operationCode = operation.getCode(); + for (Command command : Command.values()) { + if (operationCode != null && operationCode.equals(command.name())) { + Target target = new Target(); + target.setLocURI(command.getCode()); + if (operation.getCode().equals(PluginConstants + .OperationCodes.DISENROLL)) { + MetaTag meta = new MetaTag(); + meta.setFormat(Constants.META_FORMAT_CHARACTER); + item.setMeta(meta); + item.setData(Constants.PROVIDER_ID); + } + item.setTarget(target); + } + } + return item; + } + + private Item appendGetInfo(Operation operation) { + Item item = new Item(); + String operationCode = operation.getCode(); + for (Info info : Info.values()) { + if (operationCode != null && operationCode.equals(info.name())) { + Target target = new Target(); + target.setLocURI(info.getCode()); + item.setTarget(target); + } + } + if ((operationCode != null) && operationCode.equals( + PluginConstants.OperationCodes.LOCK_RESET)) { + operation.setCode(PluginConstants.OperationCodes.PIN_CODE); + for (Info getInfo : Info.values()) { + if (operation.getCode().equals(getInfo.name())) { + Target target = new Target(); + target.setLocURI(getInfo.getCode()); + item.setTarget(target); + } + } + } + return item; + } + + private Item appendReplaceInfo(Operation operation) throws JSONException { + String policyAllowData = "1"; + String policyDisallowData = "0"; + Item item = new Item(); + Target target = new Target(); + String operationCode = operation.getCode(); + JSONObject payload = new JSONObject(operation.getPayLoad().toString()); + for (Command command : Command.values()) { + + if (operationCode != null && operationCode.equals(command.name())) { + target.setLocURI(command.getCode()); + + if (operation.getCode().equals(PluginConstants + .OperationCodes.CAMERA)) { + + if (payload.getBoolean("enabled")) { + MetaTag meta = new MetaTag(); + meta.setFormat(Constants.META_FORMAT_INT); + item.setTarget(target); + item.setMeta(meta); + item.setData(policyAllowData); + } else { + MetaTag meta = new MetaTag(); + meta.setFormat(Constants.META_FORMAT_INT); + item.setTarget(target); + item.setMeta(meta); + item.setData(policyDisallowData); + } + } + if (operation.getCode().equals(PluginConstants + .OperationCodes.ENCRYPT_STORAGE)) { + + if (payload.getBoolean("encrypted")) { + MetaTag meta = new MetaTag(); + meta.setFormat(Constants.META_FORMAT_INT); + item.setTarget(target); + item.setMeta(meta); + item.setData(policyAllowData); + } else { + MetaTag meta = new MetaTag(); + meta.setFormat(Constants.META_FORMAT_INT); + item.setTarget(target); + item.setMeta(meta); + item.setData(policyDisallowData); + } + } + } + } + return item; + } + + private List appendAddInfo(Operation operation) throws WindowsOperationException { + + List addList = new ArrayList<>(); + Gson gson = new Gson(); + + if (operation.getCode().equals(PluginConstants.OperationCodes.PASSCODE_POLICY)) { + + PasscodePolicy passcodeObject = gson.fromJson((String) operation.getPayLoad(), PasscodePolicy.class); + + for (Configure configure : Configure.values()) { + + if (operation.getCode() != null && PluginConstants.OperationCodes.PASSWORD_MAX_FAIL_ATTEMPTS. + equals(configure.name())) { + AddTag add = generatePasscodePolicyData(configure, passcodeObject.getMaxFailedAttempts()); + addList.add(add); + } + if (operation.getCode() != null && (PluginConstants.OperationCodes.DEVICE_PASSWORD_ENABLE. + equals(configure.name()) || PluginConstants.OperationCodes.SIMPLE_PASSWORD. + equals(configure.name()) || PluginConstants.OperationCodes.ALPHANUMERIC_PASSWORD. + equals(configure.name()))) { + AddTag add = generatePasscodeBooleanData(operation, configure); + addList.add(add); + } + if (operation.getCode() != null && PluginConstants.OperationCodes.MIN_PASSWORD_LENGTH. + equals(configure.name())) { + AddTag add = generatePasscodePolicyData(configure, passcodeObject.getMinLength()); + addList.add(add); + } + if (operation.getCode() != null && PluginConstants.OperationCodes.PASSWORD_EXPIRE. + equals(configure.name())) { + AddTag add = generatePasscodePolicyData(configure, passcodeObject.getMaxPINAgeInDays()); + addList.add(add); + } + if (operation.getCode() != null && PluginConstants.OperationCodes.PASSWORD_HISTORY. + equals(configure.name())) { + int pinHistory = passcodeObject.getPinHistory(); + AddTag add = generatePasscodePolicyData(configure, pinHistory); + addList.add(add); + } + if (operation.getCode() != null && PluginConstants.OperationCodes.MAX_PASSWORD_INACTIVE_TIME. + equals(configure.name())) { + AddTag add = generatePasscodePolicyData(configure, passcodeObject.getMaxInactiveTime()); + addList.add(add); + } + if (operation.getCode() != null && PluginConstants.OperationCodes.MIN_PASSWORD_COMPLEX_CHARACTERS. + equals(configure.name())) { + int complexChars = passcodeObject.getMinComplexChars(); + AddTag add = generatePasscodePolicyData(configure, complexChars); + addList.add(add); + } + } + } + return addList; + } + + private List appendAddConfiguration(Operation operation) { + + List addList = new ArrayList<>(); + Gson gson = new Gson(); + + if (SyncmlCommandType.WIFI.getValue().equals(operation.getCode())) { + AddTag add = new AddTag(); + String operationCode = operation.getCode(); + Wifi wifiObject = gson.fromJson((String) operation.getPayLoad(), Wifi.class); + String data = "<?xml version="1.0"?><WLANProfile" + + "xmlns="http://www.microsoft.com/networking/WLAN/profile/v1"><name>" + + wifiObject.getNetworkName() + "</name><SSIDConfig><SSID><name>" + + wifiObject.getSsid() + "</name></SSID></SSIDConfig><connectionType>" + + wifiObject.getConnectionType() + "</connectionType><connectionMode>" + + wifiObject.getConnectionMode() + "</connectionMode><MSM><security><" + + "authEncryption><authentication>" + wifiObject.getAuthentication() + + "</authentication><encryption>" + wifiObject.getEncryption() + + "</encryption></authEncryption><sharedKey><keyType>" + + wifiObject.getKeyType() + "</keyType><protected>" + wifiObject.getProtection() + + "</protected><keyMaterial>" + wifiObject.getKeyMaterial() + + "</keyMaterial></sharedKey></security></MSM></WLANProfile>"; + + MetaTag meta = new MetaTag(); + meta.setFormat(Constants.META_FORMAT_CHARACTER); + List items = new ArrayList<>(); + + for (Configure configure : Configure.values()) { + if (operationCode != null && operationCode.equals(configure.name())) { + Target target = new Target(); + target.setLocURI(configure.getCode()); + items.get(0).setTarget(target); + } + } + items.get(0).setMeta(meta); + items.get(0).setData(data); + + add.setCommandId(301); + add.setItems(items); + addList.add(add); + return addList; + } + return null; + } + + public ExecuteTag executeCommand(Operation operation) { + ExecuteTag execElement = new ExecuteTag(); + execElement.setCommandId(operation.getId()); + List itemsExec = new ArrayList<>(); + Item itemExec = appendExecInfo(operation); + itemsExec.add(itemExec); + execElement.setItems(itemsExec); + return execElement; + } + + public SequenceTag buildSequence(Operation operation, SequenceTag sequenceElement) throws + JSONException, SyncmlOperationException { + + sequenceElement.setCommandId(operation.getId()); + List replaceItems = new ArrayList<>(); + + if (operation.getCode().equals(PluginConstants.OperationCodes.LOCK_RESET)) { + ExecuteTag execElement = executeCommand(operation); + Get getElements = new Get(); + getElements.setCommandId(operation.getId()); + List getItems = new ArrayList<>(); + Item itemGets = appendGetInfo(operation); + getItems.add(itemGets); + getElements.setItems(getItems); + + sequenceElement.setExec(execElement); + sequenceElement.setGet(getElements); + return sequenceElement; + + } else if (operation.getCode().equals(PluginConstants.OperationCodes.POLICY_BUNDLE)) { + List policyOperations; + try { + policyOperations = (List) operation.getPayLoad(); + } catch (ClassCastException e) { + throw new ClassCastException(); + } + for (Operation policy : policyOperations) { + if (policy.getCode().equals(PluginConstants.OperationCodes.CAMERA)) { + Replace replaceCameraConfig = new Replace(); + Item cameraItem; + List cameraItems = new ArrayList<>(); + try { + cameraItem = appendReplaceInfo(policy); + cameraItems.add(cameraItem); + } catch (JSONException e) { + throw new SyncmlOperationException("Error occurred while parsing payload object to json.", e); + } + replaceCameraConfig.setCommandId(operation.getId()); + replaceCameraConfig.setItems(cameraItems); + replaceItems.add(replaceCameraConfig); + } + if (policy.getCode().equals(PluginConstants.OperationCodes.ENCRYPT_STORAGE)) { + + Replace replaceStorageConfig = new Replace(); + Item storageItem; + List storageItems = new ArrayList<>(); + try { + storageItem = appendReplaceInfo(policy); + storageItems.add(storageItem); + } catch (JSONException e) { + throw new SyncmlOperationException("Error occurred while parsing payload object to json.", e); + } + replaceStorageConfig.setCommandId(operation.getId()); + replaceStorageConfig.setItems(storageItems); + replaceItems.add(replaceStorageConfig); + + } + if (policy.getCode().equals(PluginConstants.OperationCodes.PASSCODE_POLICY)) { + AtomicTag atomicTagElement = new AtomicTag(); + List addConfig; + try { + addConfig = appendAddInfo(policy); + atomicTagElement.setAdds(addConfig); + atomicTagElement.setCommandId(operation.getId()); + + sequenceElement.setAtomicTag(atomicTagElement); + } catch (WindowsOperationException e) { + throw new SyncmlOperationException("Error occurred while generating operation payload.", e); + } + } + } + if (!replaceItems.isEmpty()) { + sequenceElement.setReplaces(replaceItems); + } + return sequenceElement; + + } else { + return null; + } + } + + public List buildMonitorOperation(List effectiveMonitoringFeature) { + List monitorItems = new ArrayList<>(); + Operation monitorOperation; + for (ProfileFeature profileFeature : effectiveMonitoringFeature) { + + if (profileFeature.getFeatureCode().equals(PluginConstants + .OperationCodes.CAMERA)) { + String cameraStatus = PluginConstants + .OperationCodes.CAMERA_STATUS; + + monitorOperation = new Operation(); + monitorOperation.setCode(cameraStatus); + Item item = appendGetInfo(monitorOperation); + monitorItems.add(item); + } + if (profileFeature.getFeatureCode().equals(PluginConstants + .OperationCodes.ENCRYPT_STORAGE)) { + String encryptStorageStatus = PluginConstants + .OperationCodes.ENCRYPT_STORAGE_STATUS; + + monitorOperation = new Operation(); + monitorOperation.setCode(encryptStorageStatus); + Item item = appendGetInfo(monitorOperation); + monitorItems.add(item); + } + if (profileFeature.getFeatureCode().equals(PluginConstants + .OperationCodes.PASSCODE_POLICY)) { + String passcodeStatus = PluginConstants + .OperationCodes.DEVICE_PASSWORD_STATUS; + + monitorOperation = new Operation(); + monitorOperation.setCode(passcodeStatus); + Item item = appendGetInfo(monitorOperation); + monitorItems.add(item); + } + } + return monitorItems; + } + + public List buildDeleteInfo(Operation operation) { + List deleteItems = new ArrayList<>(); + Item deleteItem = new Item(); + Target target = new Target(); + String operationCode = operation.getCode(); + if (operation.getCode().equals(PluginConstants.OperationCodes.PASSCODE_POLICY)) { + operation.setCode(PluginConstants.OperationCodes.DEVICE_PASSCODE_DELETE); + for (Command command : Command.values()) { + + if (operationCode != null && operationCode.equals(command.name())) { + target.setLocURI(command.getCode()); + deleteItem.setTarget(target); + } + } + } + return deleteItems; + } + + public AddTag generatePasscodePolicyData(Configure configure, int policyData) { + String attempt = String.valueOf(policyData); + AddTag add = new AddTag(); + List itemList = new ArrayList<>(); + Item item = new Item(); + Target target = new Target(); + target.setLocURI(configure.getCode()); + MetaTag meta = new MetaTag(); + meta.setFormat(Constants.META_FORMAT_INT); + item.setTarget(target); + item.setMeta(meta); + item.setData(attempt); + itemList.add(item); + add.setCommandId(90); + add.setItems(itemList); + return add; + } + + public AddTag generatePasscodeBooleanData(Operation operation, Configure configure) { + Target target = new Target(); + MetaTag meta = new MetaTag(); + AddTag add = new AddTag(); + + PasscodePolicy passcodePolicy = gson.fromJson((String) operation.getPayLoad(), PasscodePolicy.class); + if (operation.getCode() != null && (PluginConstants.OperationCodes.DEVICE_PASSWORD_ENABLE. + equals(configure.name()))) { + if (passcodePolicy.isEnablePassword()) { + target.setLocURI(configure.getCode()); + meta.setFormat(Constants.META_FORMAT_INT); + List itemList = new ArrayList<>(); + Item item = new Item(); + item.setTarget(target); + item.setMeta(meta); + item.setData("0"); + itemList.add(item); + + add.setCommandId(operation.getId()); + add.setItems(itemList); + + } else { + target.setLocURI(configure.getCode()); + meta.setFormat(Constants.META_FORMAT_INT); + List itemList = new ArrayList<>(); + Item item = new Item(); + item.setTarget(target); + item.setMeta(meta); + item.setData("1"); + itemList.add(item); + add.setCommandId(operation.getId()); + add.setItems(itemList); + + } + } + if (PluginConstants.OperationCodes.ALPHANUMERIC_PASSWORD. + equals(configure.name())) { + if (passcodePolicy.isRequireAlphanumeric()) { + Item item = new Item(); + target.setLocURI(configure.getCode()); + meta.setFormat(Constants.META_FORMAT_INT); + List itemList = new ArrayList<>(); + item.setTarget(target); + item.setMeta(meta); + item.setData("1"); + itemList.add(item); + add.setCommandId(operation.getId()); + add.setItems(itemList); + } else { + target.setLocURI(configure.getCode()); + meta.setFormat(Constants.META_FORMAT_INT); + List itemList = new ArrayList<>(); + Item item = new Item(); + item.setTarget(target); + item.setMeta(meta); + item.setData("0"); + itemList.add(item); + add.setCommandId(operation.getId()); + add.setItems(itemList); + } + } + if (PluginConstants.OperationCodes.SIMPLE_PASSWORD. + equals(configure.name())) { + if (passcodePolicy.isAllowSimple()) { + Item item = new Item(); + target.setLocURI(configure.getCode()); + meta.setFormat(Constants.META_FORMAT_INT); + List itemList = new ArrayList<>(); + item.setTarget(target); + item.setMeta(meta); + item.setData("1"); + itemList.add(item); + add.setCommandId(operation.getId()); + add.setItems(itemList); + + } else { + Item item = new Item(); + target.setLocURI(configure.getCode()); + meta.setFormat(Constants.META_FORMAT_INT); + List itemList = new ArrayList<>(); + item.setTarget(target); + item.setMeta(meta); + item.setData("0"); + itemList.add(item); + add.setCommandId(operation.getId()); + add.setItems(itemList); + } + } + return add; + } + +} + + + diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/operations/util/OperationUtils.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/operations/util/OperationUtils.java new file mode 100644 index 0000000000..4f223df0c3 --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/operations/util/OperationUtils.java @@ -0,0 +1,505 @@ +/* + * 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.wso2.carbon.mdm.mobileservices.windows.operations.util; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.json.JSONException; +import org.json.JSONObject; +import org.wso2.carbon.device.mgt.common.DeviceIdentifier; +import org.wso2.carbon.device.mgt.common.DeviceManagementException; +import org.wso2.carbon.device.mgt.common.FeatureManagementException; +import org.wso2.carbon.device.mgt.common.notification.mgt.Notification; +import org.wso2.carbon.device.mgt.common.notification.mgt.NotificationManagementException; +import org.wso2.carbon.device.mgt.common.notification.mgt.NotificationManagementService; +import org.wso2.carbon.device.mgt.common.operation.mgt.Operation; +import org.wso2.carbon.device.mgt.common.operation.mgt.OperationManagementException; +import org.wso2.carbon.mdm.mobileservices.windows.common.PluginConstants; +import org.wso2.carbon.mdm.mobileservices.windows.common.exceptions.WindowsDeviceEnrolmentException; +import org.wso2.carbon.mdm.mobileservices.windows.common.util.WindowsAPIUtils; +import org.wso2.carbon.mdm.mobileservices.windows.operations.*; +import org.wso2.carbon.mdm.mobileservices.windows.services.syncml.beans.Profile; +import org.wso2.carbon.policy.mgt.common.ProfileFeature; +import org.wso2.carbon.policy.mgt.common.monitor.ComplianceFeature; +import org.wso2.carbon.policy.mgt.common.monitor.PolicyComplianceException; + +import java.util.ArrayList; +import java.util.List; + +import static org.wso2.carbon.mdm.mobileservices.windows.common.util.WindowsAPIUtils.convertToDeviceIdentifierObject; + +/** + * Class contains Operation related utilities. + */ +public class OperationUtils { + private static Log log = LogFactory.getLog(OperationUtils.class); + List pendingDataOperations; + + + /** + * Update the operations using device status payload. + * + * @param status Client side status for the specific operations + * @param syncmlDocument syncml payload for operation status which parse through the syncml engine + * @param deviceIdentifier specific device identifier for each device + * @throws OperationManagementException + * @throws DeviceManagementException + */ + public void updateDeviceOperations(Status status, SyncmlDocument syncmlDocument, + DeviceIdentifier deviceIdentifier) + throws OperationManagementException, DeviceManagementException, NotificationManagementException, + WindowsOperationException { + + pendingDataOperations = WindowsAPIUtils.getDeviceManagementService() + .getOperationsByDeviceAndStatus(deviceIdentifier, Operation.Status.PENDING); + if (status.getData().equals(Constants.SyncMLResponseCodes.ACCEPTED) || status.getData().equals + (Constants.SyncMLResponseCodes.ACCEPTED_FOR_PROCESSING)) { + for (Operation operation : pendingDataOperations) { + if (operation.getId() == status.getCommandReference()) { + operation.setStatus(Operation.Status.COMPLETED); + } + } + updateOperations(syncmlDocument.getHeader().getSource().getLocURI(), pendingDataOperations); + } else if (status.getData().equals(Constants.SyncMLResponseCodes.PIN_NOTFOUND)) { + for (Operation operation : pendingDataOperations) { + if (operation.getId() == status.getCommandReference() && operation. + getCode().equals(String.valueOf(OperationCode.Command.DEVICE_LOCK))) { + operation.setStatus(Operation.Status.ERROR); + updateOperations(syncmlDocument.getHeader().getSource().getLocURI(), pendingDataOperations); + try { + NotificationManagementService nmService = WindowsAPIUtils.getNotificationManagementService(); + Notification lockResetNotification = new Notification(); + lockResetNotification.setOperationId(status.getCommandReference()); + lockResetNotification.setStatus(String.valueOf(Notification.Status.NEW)); + lockResetNotification.setDeviceIdentifier(deviceIdentifier); + lockResetNotification.setDescription( + Constants.SyncMLResponseCodes.LOCKRESET_NOTIFICATION); + nmService.addNotification(lockResetNotification); + } catch (NotificationManagementException e) { + throw new WindowsOperationException("Failure occurred in getting notification service", e); + } + } + } + } + } + + /** + * Update operation statuses + * + * @param deviceId specific device Id + * @param operations operation list to be update + * @throws OperationManagementException + */ + public void updateOperations(String deviceId, + List operations) + throws OperationManagementException { + + for (Operation operation : operations) { + WindowsAPIUtils.updateOperation(deviceId, operation); + if (log.isDebugEnabled()) { + log.debug("Updating operation '" + operation.toString() + "'"); + } + } + } + + /** + * Update Status of the lock operation. + * + * @param status Status of the operation. + * @param syncmlDocument parsed syncml payload. + * @param deviceIdentifier Device Id. + * @throws OperationManagementException + * @throws DeviceManagementException + * @throws NotificationManagementException + */ + public void lockOperationUpdate(Status status, SyncmlDocument syncmlDocument, DeviceIdentifier deviceIdentifier) + throws OperationManagementException, DeviceManagementException, NotificationManagementException { + + pendingDataOperations = WindowsAPIUtils.getDeviceManagementService() + .getOperationsByDeviceAndStatus(deviceIdentifier, Operation.Status.PENDING); + if (status.getData().equals(Constants.SyncMLResponseCodes.ACCEPTED)) { + for (Operation operation : pendingDataOperations) { + if (operation.getCode().equals(OperationCode.Command.DEVICE_LOCK.getCode()) + && operation.getId() == status.getCommandReference()) { + operation.setStatus(Operation.Status.COMPLETED); + new OperationUtils().updateOperations(syncmlDocument.getHeader().getSource().getLocURI(), + pendingDataOperations); + } + } + } + if (status.getData().equals(Constants.SyncMLResponseCodes.PIN_NOTFOUND)) { + for (Operation operation : pendingDataOperations) { + + if (operation.getCode().equals(OperationCode.Command.DEVICE_LOCK.getCode()) && + operation.getId() == status.getCommandReference()) { + operation.setStatus(Operation.Status.ERROR); + new OperationUtils().updateOperations(syncmlDocument.getHeader().getSource().getLocURI(), + pendingDataOperations); + try { + NotificationManagementService nmService = WindowsAPIUtils.getNotificationManagementService(); + Notification lockResetNotification = new Notification(); + lockResetNotification.setOperationId(status.getCommandReference()); + lockResetNotification.setStatus(String.valueOf(Notification.Status.NEW)); + lockResetNotification.setDeviceIdentifier(deviceIdentifier); + lockResetNotification.setDescription(Constants.SyncMLResponseCodes.LOCKRESET_NOTIFICATION); + + nmService.addNotification(lockResetNotification); + } catch (NotificationManagementException e) { + String msg = "Failure occurred in getting notification service"; + log.error(msg, e); + throw new NotificationManagementException(msg, e); + } + } + } + } + } + + /*** + * Update status of the ring operation. + * + * @param status Ring status of the device. + * @param syncmlDocument Parsed syncml payload from the syncml engine. + * @param deviceIdentifier specific device id to be update. + * @throws OperationManagementException + * @throws DeviceManagementException + */ + public void ring(Status status, SyncmlDocument syncmlDocument, + DeviceIdentifier deviceIdentifier) + throws OperationManagementException, DeviceManagementException { + + if (status.getData().equals(Constants.SyncMLResponseCodes.ACCEPTED)) { + pendingDataOperations = WindowsAPIUtils.getDeviceManagementService() + .getOperationsByDeviceAndStatus(deviceIdentifier, Operation.Status.PENDING); + for (Operation operation : pendingDataOperations) { + if (operation.getCode().equals(OperationCode.Command.DEVICE_RING) && + (operation.getId() == status.getCommandReference())) { + operation.setStatus(Operation.Status.COMPLETED); + new OperationUtils().updateOperations(syncmlDocument.getHeader().getSource().getLocURI(), + pendingDataOperations); + } + } + } + } + + /*** + * Update the status of the DataWipe operation. + * + * @param status Status of the datawipe. + * @param syncmlDocument Parsed syncml payload from the syncml engine. + * @param deviceIdentifier specific device id to be wiped. + * @throws OperationManagementException + * @throws DeviceManagementException + */ + public void dataWipe(Status status, SyncmlDocument syncmlDocument, + DeviceIdentifier deviceIdentifier) + throws OperationManagementException, DeviceManagementException { + + if (status.getData().equals(Constants.SyncMLResponseCodes.ACCEPTED)) { + pendingDataOperations = WindowsAPIUtils.getDeviceManagementService() + .getOperationsByDeviceAndStatus(deviceIdentifier, Operation.Status.PENDING); + for (Operation operation : pendingDataOperations) { + + if (operation.getCode().equals(OperationCode.Command.WIPE_DATA) && + (operation.getId() == status.getCommandReference())) { + operation.setStatus(Operation.Status.COMPLETED); + updateOperations(syncmlDocument.getHeader().getSource().getLocURI(), + pendingDataOperations); + } + } + } + } + + /** + * Get pending operations. + * + * @param syncmlDocument SyncmlDocument object which creates from the syncml engine using syncml payload + * @return Return list of pending operations. + * @throws OperationManagementException + * @throws DeviceManagementException + * @throws FeatureManagementException + * @throws PolicyComplianceException + * @throws NotificationManagementException + */ + public List getPendingOperations(SyncmlDocument syncmlDocument) + throws OperationManagementException, DeviceManagementException, FeatureManagementException, + PolicyComplianceException, NotificationManagementException, WindowsDeviceEnrolmentException, + WindowsOperationException { + + List pendingOperations; + DeviceIdentifier deviceIdentifier = convertToDeviceIdentifierObject( + syncmlDocument.getHeader().getSource().getLocURI()); + UpdateUriOperations(syncmlDocument); + generateComplianceFeatureStatus(syncmlDocument); + + pendingOperations = WindowsAPIUtils.getDeviceManagementService().getPendingOperations(deviceIdentifier); + return pendingOperations; + } + + /** + * Set compliance of the feature according to the device status for the specific feature. + * + * @param activeFeature + * @param deviceFeature + * @return Returns setting up compliance feature. + */ + public ComplianceFeature setComplianceFeatures(ProfileFeature activeFeature, Profile deviceFeature) { + ComplianceFeature complianceFeature = new ComplianceFeature(); + complianceFeature.setFeature(activeFeature); + complianceFeature.setFeatureCode(activeFeature.getFeatureCode()); + complianceFeature.setCompliance(deviceFeature.isCompliance()); + return complianceFeature; + } + + /** + * Update the completed/Error status of the operation which have the URI of the operation code in the syncml payload. + * + * @param syncmlDocument SyncmlDocument object generated from the the syncml engine. + * @throws DeviceManagementException + * @throws NotificationManagementException + * @throws OperationManagementException + */ + public void UpdateUriOperations(SyncmlDocument syncmlDocument) throws DeviceManagementException, + NotificationManagementException, OperationManagementException, WindowsOperationException { + DeviceIdentifier deviceIdentifier = convertToDeviceIdentifierObject( + syncmlDocument.getHeader().getSource().getLocURI()); + List statuses = syncmlDocument.getBody().getStatus(); + OperationUtils operationUtils = new OperationUtils(); + + for (Status status : statuses) { + + if (status.getCommand().equals(Constants.EXECUTE)) { + if (status.getTargetReference() == null) { + operationUtils.updateDeviceOperations(status, syncmlDocument, deviceIdentifier); + } else { + if (status.getTargetReference().equals(OperationCode.Command.DEVICE_LOCK)) { + operationUtils.lockOperationUpdate(status, syncmlDocument, deviceIdentifier); + } + if (status.getTargetReference().equals(OperationCode.Command.DEVICE_RING)) { + operationUtils.ring(status, syncmlDocument, deviceIdentifier); + } + if (status.getTargetReference().equals(OperationCode.Command.WIPE_DATA)) { + operationUtils.dataWipe(status, syncmlDocument, deviceIdentifier); + } + } + } + if (status.getCommand().equals(Constants.SEQUENCE)) { + if (status.getData().equals(Constants.SyncMLResponseCodes.ACCEPTED)) { + + pendingDataOperations = WindowsAPIUtils.getDeviceManagementService() + .getOperationsByDeviceAndStatus(deviceIdentifier, Operation.Status.PENDING); + for (Operation operation : pendingDataOperations) { + if (operation.getCode().equals(PluginConstants.OperationCodes.POLICY_BUNDLE) && + operation.getId() == status.getCommandReference()) { + operation.setStatus(Operation.Status.COMPLETED); + } + if (operation.getCode().equals(PluginConstants.OperationCodes.MONITOR) && + operation.getId() == status.getCommandReference()) { + operation.setStatus(Operation.Status.COMPLETED); + } + } + operationUtils.updateOperations(syncmlDocument.getHeader().getSource().getLocURI(), + pendingDataOperations); + } else { + pendingDataOperations = WindowsAPIUtils.getDeviceManagementService() + .getOperationsByDeviceAndStatus(deviceIdentifier, Operation.Status.PENDING); + for (Operation operation : pendingDataOperations) { + + if (operation.getCode().equals(PluginConstants.OperationCodes.POLICY_BUNDLE) && + operation.getId() == status.getCommandReference()) { + operation.setStatus(Operation.Status.ERROR); + } + if (operation.getCode().equals(PluginConstants.OperationCodes.MONITOR) && + operation.getId() == status.getCommandReference()) { + operation.setStatus(Operation.Status.ERROR); + } + } + operationUtils.updateOperations(syncmlDocument.getHeader().getSource().getLocURI(), + pendingDataOperations); + } + } + } + } + + /** + * Generate status of the features that have been activated on the device. + * + * @param syncmlDocument syncmlDocument object pasrsed from the syncml engine. + * @return device statuses for the activated features + * @throws NotificationManagementException + */ + public List generateDeviceOperationStatusObject(SyncmlDocument syncmlDocument) throws + NotificationManagementException, WindowsOperationException { + + DeviceIdentifier deviceIdentifier = convertToDeviceIdentifierObject( + syncmlDocument.getHeader().getSource().getLocURI()); + String lockUri = null; + Results result = syncmlDocument.getBody().getResults(); + + List profiles = new ArrayList<>(); + if (result != null) { + List results = result.getItem(); + for (OperationCode.Info info : OperationCode.Info.values()) { + if (PluginConstants.OperationCodes.PIN_CODE.equals(info + .name())) { + lockUri = info.getCode(); + } + } + for (Item item : results) { + for (OperationCode.Info info : OperationCode.Info.values()) { + if (item.getSource().getLocURI().equals(info.getCode()) && info.name().equals( + PluginConstants.OperationCodes.CAMERA_STATUS)) { + Profile cameraProfile = new Profile(); + cameraProfile.setFeatureCode(PluginConstants.OperationCodes.CAMERA); + cameraProfile.setData(item.getData()); + if (item.getData().equals(PluginConstants.SyncML.SYNCML_DATA_ONE)) { + cameraProfile.setEnable(true); + } else { + cameraProfile.setEnable(false); + } + profiles.add(cameraProfile); + } + if (item.getSource().getLocURI().equals(info.getCode()) && info.name().equals( + PluginConstants.OperationCodes.ENCRYPT_STORAGE_STATUS)) { + Profile encryptStorage = new Profile(); + encryptStorage.setFeatureCode(PluginConstants.OperationCodes.ENCRYPT_STORAGE); + encryptStorage.setData(item.getData()); + if (item.getData().equals(PluginConstants.SyncML.SYNCML_DATA_ONE)) { + encryptStorage.setEnable(true); + } else { + encryptStorage.setEnable(false); + } + profiles.add(encryptStorage); + } + if (item.getSource().getLocURI().equals(info.getCode()) && info.name().equals( + PluginConstants.OperationCodes.DEVICE_PASSWORD_STATUS)) { + Profile encryptStorage = new Profile(); + encryptStorage.setFeatureCode(PluginConstants.OperationCodes.PASSCODE_POLICY); + encryptStorage.setData(item.getData()); + if (item.getData().equals(PluginConstants.SyncML.SYNCML_DATA_ZERO)) { + encryptStorage.setEnable(true); + } else { + encryptStorage.setEnable(false); + } + profiles.add(encryptStorage); + } + if (!item.getData().isEmpty() && item.getSource().getLocURI().equals(lockUri)) { + String pinValue = item.getData(); + NotificationManagementService nmService = WindowsAPIUtils.getNotificationManagementService(); + Notification notification = new Notification(); + notification.setDescription("Auto generated DevicePin : " + pinValue); + notification.setOperationId(result.getCommandReference()); + notification.setDeviceIdentifier(deviceIdentifier); + notification.setStatus(String.valueOf(Notification.Status.NEW)); + try { + nmService.addNotification(notification); + } catch (NotificationManagementException e) { + String msg = "Failure Occurred in getting notification service."; + log.error(msg, e); + throw new WindowsOperationException(msg, e); + } + break; + } + } + } + } + return profiles; + } + + /** + * Generate Compliance Features + * + * @param syncmlDocument syncmlDocument object parsed from the syncml engine. + * @throws NotificationManagementException + * @throws FeatureManagementException + * @throws PolicyComplianceException + */ + public void generateComplianceFeatureStatus(SyncmlDocument syncmlDocument) throws NotificationManagementException, + FeatureManagementException, PolicyComplianceException, WindowsDeviceEnrolmentException, + WindowsOperationException { + List profiles = generateDeviceOperationStatusObject(syncmlDocument); + DeviceIdentifier deviceIdentifier = convertToDeviceIdentifierObject( + syncmlDocument.getHeader().getSource().getLocURI()); + boolean isCompliance = false; + if (profiles.size() != Constants.EMPTY) { + try { + List profileFeatures = WindowsAPIUtils.getPolicyManagerService().getEffectiveFeatures( + deviceIdentifier); + List complianceFeatures = new ArrayList<>(); + for (ProfileFeature activeFeature : profileFeatures) { + JSONObject policyContent = new JSONObject(activeFeature.getContent().toString()); + + for (Profile deviceFeature : profiles) { + if (deviceFeature.getFeatureCode().equals(activeFeature.getFeatureCode()) && + deviceFeature.getFeatureCode().equals(PluginConstants.OperationCodes.CAMERA)) { + if (policyContent.getBoolean(PluginConstants.PolicyConfigProperties. + POLICY_ENABLE) == (deviceFeature.isEnable())) { + isCompliance = true; + deviceFeature.setCompliance(isCompliance); + } else { + deviceFeature.setCompliance(isCompliance); + } + ComplianceFeature complianceFeature = setComplianceFeatures(activeFeature, deviceFeature); + complianceFeatures.add(complianceFeature); + } + if (deviceFeature.getFeatureCode().equals(activeFeature.getFeatureCode()) && + deviceFeature.getFeatureCode().equals(PluginConstants.OperationCodes. + ENCRYPT_STORAGE)) { + if (policyContent.getBoolean(PluginConstants.PolicyConfigProperties. + ENCRYPTED_ENABLE) == (deviceFeature.isEnable())) { + isCompliance = true; + deviceFeature.setCompliance(isCompliance); + } else { + deviceFeature.setCompliance(isCompliance); + } + ComplianceFeature complianceFeature = setComplianceFeatures(activeFeature, deviceFeature); + complianceFeatures.add(complianceFeature); + } + if (deviceFeature.getFeatureCode().equals(activeFeature.getFeatureCode()) && + deviceFeature.getFeatureCode().equals(PluginConstants.OperationCodes. + PASSCODE_POLICY)) { + if (policyContent.getBoolean(PluginConstants.PolicyConfigProperties. + ENABLE_PASSWORD) == (deviceFeature.isEnable())) { + isCompliance = true; + deviceFeature.setCompliance(isCompliance); + } else { + deviceFeature.setCompliance(isCompliance); + } + ComplianceFeature complianceFeature = setComplianceFeatures(activeFeature, deviceFeature); + complianceFeatures.add(complianceFeature); + } + } + } + WindowsAPIUtils.getPolicyManagerService().checkPolicyCompliance(deviceIdentifier, complianceFeatures); + } catch (org.wso2.carbon.policy.mgt.common.FeatureManagementException e) { + String msg = "Error occurred while getting effective policy."; + log.error(msg, e); + throw new FeatureManagementException(msg, e); + } catch (JSONException e) { + String msg = "Error occurred while parsing json object."; + log.error(msg); + throw new WindowsDeviceEnrolmentException(msg, e); + } catch (PolicyComplianceException e) { + String msg = "Error occurred while setting up policy compliance."; + log.error(msg, e); + throw new PolicyComplianceException(msg, e); + } + } + + } +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/operations/util/SyncmlCredentials.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/operations/util/SyncmlCredentials.java new file mode 100644 index 0000000000..c807a23b1d --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/operations/util/SyncmlCredentials.java @@ -0,0 +1,70 @@ +/* + * 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.wso2.carbon.mdm.mobileservices.windows.operations.util; + +import org.apache.commons.codec.binary.Base64; +import org.wso2.carbon.mdm.mobileservices.windows.common.exceptions.SyncmlMessageFormatException; + +import java.io.UnsupportedEncodingException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +/** + * Class for generate security token for client and server. + */ +public class SyncmlCredentials { + + public String generateCredData(String nextNonce) throws SyncmlMessageFormatException { + MessageDigest digest; + String usrPwdNonceHash; + String nonce; + try { + nonce = new String(Base64.decodeBase64(nextNonce), Constants.UTF_8); + digest = MessageDigest.getInstance(Constants.MD5); + String usrPwd = Constants.PROVIDER_ID + ":" + Constants.SERVER_SECRET; + String usrPwdHash = Base64.encodeBase64String(digest.digest(usrPwd.getBytes(Constants.UTF_8))); + String usrPwdNonce = usrPwdHash + ":" + nonce; + usrPwdNonceHash = Base64.encodeBase64String(digest.digest(usrPwdNonce.getBytes(Constants.UTF_8))); + } catch (UnsupportedEncodingException e) { + throw new SyncmlMessageFormatException("Problem occurred in encoding credentials data.", e); + } catch (NoSuchAlgorithmException e) { + throw new SyncmlMessageFormatException("Problem occurred in generating password hash.", e); + } + return usrPwdNonceHash; + } + + public String generateRST(String username, String password) throws SyncmlMessageFormatException { + MessageDigest digest; + String usrPwdNonceHash; + String nonce; + try { + nonce = new String(Base64.decodeBase64(Constants.INITIAL_NONCE), Constants.UTF_8); + digest = MessageDigest.getInstance(Constants.MD5); + String usrPwd = username + ":" + password; + String usrPwdHash = Base64.encodeBase64String(digest.digest(usrPwd.getBytes(Constants.UTF_8))); + String usrPwdNonce = usrPwdHash + ":" + nonce; + usrPwdNonceHash = Base64.encodeBase64String(digest.digest(usrPwdNonce.getBytes(Constants.UTF_8))); + } catch (UnsupportedEncodingException e) { + throw new SyncmlMessageFormatException("Problem occurred in encoding credentials data.", e); + } catch (NoSuchAlgorithmException e) { + throw new SyncmlMessageFormatException("Problem occurred in generating password hash.", e); + } + return usrPwdNonceHash; + } +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/operations/util/SyncmlGenerator.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/operations/util/SyncmlGenerator.java new file mode 100644 index 0000000000..9591ca673a --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/operations/util/SyncmlGenerator.java @@ -0,0 +1,100 @@ +/* + * 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.wso2.carbon.mdm.mobileservices.windows.operations.util; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.wso2.carbon.mdm.mobileservices.windows.common.exceptions.SyncmlOperationException; +import org.wso2.carbon.mdm.mobileservices.windows.operations.SyncmlBody; +import org.wso2.carbon.mdm.mobileservices.windows.operations.SyncmlDocument; +import org.wso2.carbon.mdm.mobileservices.windows.operations.SyncmlHeader; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.*; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; +import java.io.StringWriter; + +/** + * Generates the response syncml xml file that should be sent to the phone. + */ +public class SyncmlGenerator { + + private static Log log = LogFactory.getLog(SyncmlGenerator.class); + + public String generatePayload(SyncmlDocument syncmlDocument) throws SyncmlOperationException { + Document doc = generateDocument(); + Element rootElement = createRootElement(doc); + SyncmlHeader header = syncmlDocument.getHeader(); + header.buildSyncmlHeaderElement(doc, rootElement); + SyncmlBody body = syncmlDocument.getBody(); + body.buildBodyElement(doc, rootElement); + return transformDocument(doc); + } + + private static Document generateDocument() throws SyncmlOperationException { + DocumentBuilderFactory documentFactory = DocumentBuilderFactory.newInstance(); + DocumentBuilder docBuilder; + try { + docBuilder = documentFactory.newDocumentBuilder(); + } catch (ParserConfigurationException e) { + String message = "Error while generating a new document of syncml"; + log.error(message, e); + throw new SyncmlOperationException(message, e); + } + return docBuilder.newDocument(); + } + + private static Element createRootElement(Document document) { + Element rootElement = document.createElementNS(Constants.XMLNS_SYNCML, + Constants.SYNCML_ROOT_ELEMENT_NAME); + document.appendChild(rootElement); + return rootElement; + } + + private String transformDocument(Document document) throws SyncmlOperationException { + DOMSource domSource = new DOMSource(document); + TransformerFactory transformerFactory = TransformerFactory.newInstance(); + Transformer transformer; + try { + transformer = transformerFactory.newTransformer(); + } catch (TransformerConfigurationException e) { + String message = "Error while retrieving a new transformer"; + log.error(message, e); + throw new SyncmlOperationException(message, e); + } + transformer.setOutputProperty(OutputKeys.ENCODING, Constants.UTF_8); + transformer.setOutputProperty(OutputKeys.INDENT, Constants.YES); + + StringWriter stringWriter = new StringWriter(); + StreamResult streamResult = new StreamResult(stringWriter); + try { + transformer.transform(domSource, streamResult); + } catch (TransformerException e) { + String message = "Error while transforming document to a string"; + log.error(message, e); + throw new SyncmlOperationException(message, e); + } + return stringWriter.toString(); + } +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/operations/util/SyncmlParser.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/operations/util/SyncmlParser.java new file mode 100644 index 0000000000..836aa50be4 --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/operations/util/SyncmlParser.java @@ -0,0 +1,458 @@ +/* + * 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.wso2.carbon.mdm.mobileservices.windows.operations.util; + +import org.w3c.dom.Document; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.wso2.carbon.mdm.mobileservices.windows.common.PluginConstants; +import org.wso2.carbon.mdm.mobileservices.windows.common.exceptions.SyncmlMessageFormatException; +import org.wso2.carbon.mdm.mobileservices.windows.operations.*; + +import java.util.ArrayList; +import java.util.IllegalFormatCodePointException; +import java.util.List; + +/** + * Parses the receiving SyncML payload and generates the SyncML document object from it. + */ +public class SyncmlParser { + + private static String commandId; + private static String messageReference; + private static String commandReference; + private static final String SYNC_HEADER = "SyncHdr"; + private static final String SYNC_BODY = "SyncBody"; + + private enum SyncMLHeaderParameter { + MSG_ID("MsgID"), + SESSION_ID("SessionID"), + TARGET("Target"), + SOURCE("Source"), + CRED("Cred"); + private final String parameterName; + + SyncMLHeaderParameter(final String parameterName) { + this.parameterName = parameterName; + } + + public String getValue() { + return this.parameterName; + } + } + + private enum SycMLCommandType { + ALERT("Alert"), + REPLACE("Replace"), + STATUS("Status"), + RESULTS("Results"); + private final String commandName; + + SycMLCommandType(final String commandName) { + this.commandName = commandName; + } + + public String getValue() { + return this.commandName; + } + } + + + /** + * Parses the raw SyncML payload and generates a SyncmlDocument object using the parsed XML contents. + * + * @param syncmlPayload - Received SyncML XML payload + * @return - SyncmlDocument object generated from the received payload + */ + public static SyncmlDocument parseSyncmlPayload(Document syncmlPayload) throws SyncmlMessageFormatException { + SyncmlDocument syncmlDocument = new SyncmlDocument(); + if (syncmlPayload.getElementsByTagName(SYNC_HEADER) == null) { + throw new SyncmlMessageFormatException(); + } + NodeList syncHeaderList = syncmlPayload.getElementsByTagName(SYNC_HEADER); + Node syncHeader = syncHeaderList.item(0); + SyncmlHeader header = generateSyncmlHeader(syncHeader); + + NodeList syncBodyList = syncmlPayload.getElementsByTagName(SYNC_BODY); + Node syncBody = syncBodyList.item(0); + SyncmlBody body = generateSyncmlBody(syncBody); + + syncmlDocument.setHeader(header); + syncmlDocument.setBody(body); + return syncmlDocument; + } + + /** + * Generates SyncmlHeader object by extracting properties of passed XML node. + * + * @param syncHeader - XML node which represents SyncML header + * @return - SyncmlHeader object + */ + private static SyncmlHeader generateSyncmlHeader(Node syncHeader) { + + String sessionID = null; + String messageID = null; + Target target = null; + Source source = null; + Credential credential = null; + SyncmlHeader header = new SyncmlHeader(); + + NodeList headerElements = syncHeader.getChildNodes(); + for (int i = 0; i < headerElements.getLength(); i++) { + Node node = headerElements.item(i); + + if (node.getNodeType() == Node.ELEMENT_NODE) { + String nodeName = node.getNodeName(); + + if (SyncMLHeaderParameter.MSG_ID.getValue().equals(nodeName)) { + if (node.getTextContent().trim() == null) { + throw new IllegalFormatCodePointException(2); + } else { + messageID = node.getTextContent().trim(); + } + } else if (SyncMLHeaderParameter.SESSION_ID.getValue().equals(nodeName)) { + if (node.getTextContent().trim() == null) { + throw new IllegalFormatCodePointException(2); + } else { + sessionID = node.getTextContent().trim(); + } + } else if (SyncMLHeaderParameter.TARGET.getValue().equals(nodeName)) { + if (node.getTextContent().trim() == null) { + throw new IllegalFormatCodePointException(2); + } else { + target = generateTarget(node); + } + } else if (SyncMLHeaderParameter.SOURCE.getValue().equals(nodeName)) { + if (node.getTextContent().trim() == null) { + throw new IllegalFormatCodePointException(2); + } else { + source = generateSource(node); + } + } else if (SyncMLHeaderParameter.CRED.getValue().equals(nodeName)) { + if (node.getTextContent().trim() == null) { + throw new IllegalFormatCodePointException(2); + } else { + credential = generateCredential(node); + } + } + } + } + header.setMsgID(Integer.valueOf(messageID)); + // Syncml message contains a sessionID which is Hexadecimal value.Hexadecimal sessionID parse as a integer value. + header.setSessionId(Integer.valueOf(sessionID, 16)); + header.setTarget(target); + header.setSource(source); + header.setCredential(credential); + return header; + } + + /** + * Generates SyncmlBody object by extracting properties of passed XML node. + * + * @param syncBody - XML node which represents SyncML body + * @return - SyncmlBody object + */ + private static SyncmlBody generateSyncmlBody(Node syncBody) { + + Alert alert = null; + Replace replace = null; + Results results = null; + List status = new ArrayList<>(); + NodeList bodyElements = syncBody.getChildNodes(); + + for (int i = 0; i < bodyElements.getLength(); i++) { + Node node = bodyElements.item(i); + + if (node.getNodeType() == Node.ELEMENT_NODE) { + String nodeName = node.getNodeName(); + + if (SycMLCommandType.ALERT.getValue().equals(nodeName)) { + alert = generateAlert(node); + } else if (SycMLCommandType.REPLACE.getValue().equals(nodeName)) { + replace = generateReplace(node); + } else if (SycMLCommandType.STATUS.getValue().equals(nodeName)) { + status.add(generateStatus(node)); + } else if (SycMLCommandType.RESULTS.getValue().equals(nodeName)) { + results = generateResults(node); + } + } + } + SyncmlBody body = new SyncmlBody(); + body.setAlert(alert); + body.setReplace(replace); + body.setStatus(status); + body.setResults(results); + return body; + } + + /** + * Generates Source object by extracting properties of passed XML node. + * + * @param node - XML node which represents Source + * @return - Source object + */ + private static Source generateSource(Node node) { + + Source source = new Source(); + Node sourceURIItem = node.getChildNodes().item(0); + Node sourceNameItem = node.getChildNodes().item(1); + String sourceURI = null; + String sourceName = null; + + if (sourceURIItem != null) { + sourceURI = sourceURIItem.getTextContent().trim(); + } + if (sourceNameItem != null) { + sourceName = sourceNameItem.getTextContent().trim(); + } + source.setLocURI(sourceURI); + source.setLocName(sourceName); + return source; + } + + /** + * Generates Target object by extracting properties of passed XML node. + * + * @param node - XML node which represents Target + * @return - Target object + */ + private static Target generateTarget(Node node) { + + Target target = new Target(); + Node targetURIItem = node.getChildNodes().item(0); + Node targetNameItem = node.getChildNodes().item(1); + String targetURI = null; + String targetName = null; + + if (targetURIItem != null) { + targetURI = targetURIItem.getTextContent().trim(); + } + if (targetNameItem != null) { + targetName = targetNameItem.getTextContent().trim(); + } + target.setLocURI(targetURI); + target.setLocName(targetName); + return target; + } + + /** + * Generates Results object by extracting properties of passed XML node. + * + * @param node - XML node which represents Results + * @return - Results object + */ + private static Results generateResults(Node node) { + + Results results = new Results(); + List item = new ArrayList<>(); + + if (node.getNodeType() == Node.ELEMENT_NODE) { + + NodeList nodelist = node.getChildNodes(); + + for (int i = 0; i < nodelist.getLength(); i++) { + String nodeName = nodelist.item(i).getNodeName(); + + switch (nodeName) { + case Constants.COMMAND_ID: + commandId = node.getChildNodes().item(i).getTextContent().trim(); + break; + case Constants.MESSAGE_REFERENCE: + messageReference = node.getChildNodes().item(i).getTextContent().trim(); + break; + case Constants.COMMAND_REFERENCE: + commandReference = node.getChildNodes().item(i).getTextContent().trim(); + break; + case Constants.ITEM: + item.add(generateItem(node.getChildNodes().item(i))); + break; + } + } + results.setCommandId(Integer.valueOf(commandId)); + results.setMessageReference(Integer.valueOf(messageReference)); + results.setCommandReference(Integer.valueOf(commandReference)); + results.setItem(item); + } + return results; + } + + /** + * Generates Status object by extracting properties of passed XML node. + * + * @param node - XML node which represents Status + * @return - Status object + */ + private static Status generateStatus(Node node) { + + Status status = new Status(); + for (int x = 0; x < node.getChildNodes().getLength(); x++) { + String nodeName = node.getChildNodes().item(x).getNodeName(); + switch (nodeName) { + case PluginConstants.SyncML.SYNCML_CMD_ID: + String commandId = node.getChildNodes().item(x).getTextContent().trim(); + status.setCommandId(Integer.valueOf(commandId)); + break; + case PluginConstants.SyncML.SYNCML_MESSAGE_REF: + String messageReference = node.getChildNodes().item(x).getTextContent().trim(); + status.setMessageReference(Integer.valueOf(messageReference)); + break; + case PluginConstants.SyncML.SYNCML_CMD_REF: + String commandReference = node.getChildNodes().item(x).getTextContent().trim(); + status.setCommandReference(Integer.valueOf(commandReference)); + break; + case PluginConstants.SyncML.SYNCML_CMD: + String command = node.getChildNodes().item(x).getTextContent().trim(); + status.setCommand(command); + break; + case PluginConstants.SyncML.SYNCML_CHAL: + NodeList chalNodes = node.getChildNodes().item(x).getChildNodes(); + MetaTag meta = new MetaTag(); + ChallengeTag chal = new ChallengeTag(); + String format = chalNodes.item(0).getFirstChild().getTextContent(); + meta.setFormat(format); + String type = chalNodes.item(0).getFirstChild().getNextSibling().getTextContent(); + meta.setType(type); + String nonce = chalNodes.item(0).getFirstChild().getNextSibling().getNextSibling().getTextContent(); + meta.setNextNonce(nonce); + chal.setMeta(meta); + status.setChallenge(chal); + break; + case PluginConstants.SyncML.SYNCML_DATA: + String data = node.getChildNodes().item(x).getTextContent().trim(); + status.setData(data); + break; + case PluginConstants.SyncML.SYNCML_TARGET_REF: + String targetReference = node.getChildNodes().item(x).getTextContent().trim(); + status.setTargetReference(targetReference); + break; + } + } + return status; + } + + /** + * Generates Replace object by extracting properties of passed XML node. + * + * @param node - XML node which represents Replace + * @return - Replace object + */ + private static Replace generateReplace(Node node) { + + Replace replace = new Replace(); + String commandId = node.getChildNodes().item(0).getTextContent().trim(); + List items = new ArrayList<>(); + for (int i = 0; i < node.getChildNodes().getLength() - 1; i++) { + items.add(generateItem(node.getChildNodes().item(i + 1))); + } + replace.setCommandId(Integer.valueOf(commandId)); + replace.setItems(items); + return replace; + } + + /** + * Generates Alert object by extracting properties of passed XML node. + * + * @param node - XML node which represents Alert + * @return - Alert object + */ + private static Alert generateAlert(Node node) { + Alert alert = new Alert(); + String commandID = node.getChildNodes().item(0).getTextContent().trim(); + String data = node.getChildNodes().item(1).getTextContent().trim(); + alert.setCommandId(Integer.valueOf(commandID)); + alert.setData(data); + return alert; + } + + /** + * Generates Item object by extracting properties of passed XML node. + * + * @param node - XML node which represents Item + * @return - Item object + */ + private static Item generateItem(Node node) { + Item item = new Item(); + Source source = new Source(); + String data; + String nodeName; + String childNodeName; + String locUri; + for (int x = 0; x < node.getChildNodes().getLength(); x++) { + if (node.getChildNodes().item(x).getNodeName() != null) { + nodeName = node.getChildNodes().item(x).getNodeName(); + } else { + throw new IllegalFormatCodePointException(2); + } + if (nodeName == PluginConstants.SyncML.SYNCML_SOURCE) { + if (node.getChildNodes().item(x).getChildNodes().item(x).getNodeName() != null) { + childNodeName = node.getChildNodes().item(x).getChildNodes().item(x).getNodeName(); + } else { + throw new IllegalFormatCodePointException(2); + } + if (childNodeName == PluginConstants.SyncML.SYNCML_LOCATION_URI) { + if (node.getChildNodes().item(x).getChildNodes().item(x).getTextContent().trim() != null) { + locUri = node.getChildNodes().item(x).getChildNodes().item(x).getTextContent().trim(); + } else { + throw new IllegalFormatCodePointException(2); + } + source.setLocURI(locUri); + item.setSource(source); + } + } else if (nodeName == PluginConstants.SyncML.SYNCML_DATA) { + if (node.getChildNodes().item(x).getTextContent().trim() != null) { + data = node.getChildNodes().item(x).getTextContent().trim(); + } else { + throw new IllegalFormatCodePointException(2); + } + item.setData(data); + } + } + return item; + } + + /** + * Generates Credential object by extracting properties of passed XML node. + * + * @param node - XML node which represents Credential + * @return - Credential object + */ + private static Credential generateCredential(Node node) { + Credential credential = new Credential(); + MetaTag meta = generateMeta(node.getChildNodes().item(0)); + String data = node.getChildNodes().item(1).getTextContent().trim(); + credential.setMeta(meta); + credential.setData(data); + return credential; + } + + /** + * Generates MetaTag object by extracting properties of passed XML node. + * + * @param node - XML node which represents MetaTag + * @return - MetaTag object + */ + private static MetaTag generateMeta(Node node) { + MetaTag meta = new MetaTag(); + String format = node.getChildNodes().item(0).getTextContent().trim(); + String type = node.getChildNodes().item(1).getTextContent().trim(); + meta.setFormat(format); + meta.setType(type); + return meta; + } +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/adminoperations/Operations.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/adminoperations/Operations.java new file mode 100644 index 0000000000..b96831f4b0 --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/adminoperations/Operations.java @@ -0,0 +1,57 @@ +/* + * 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.wso2.carbon.mdm.mobileservices.windows.services.adminoperations; + +import org.wso2.carbon.mdm.mobileservices.windows.common.exceptions.WindowsDeviceEnrolmentException; + +import javax.ws.rs.*; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import java.util.List; + +/** + * Interface for Admin operations persisting. This interface accepts operations added via UI. + */ +@Path("/operation") +@Consumes(MediaType.APPLICATION_JSON) +@Produces(MediaType.APPLICATION_JSON) + +public interface Operations { + + @POST + @Path("/devicelock") + Response lock(@HeaderParam("Accept") String headerParam, List deviceids) throws WindowsDeviceEnrolmentException; + + @POST + @Path("/devicedisenroll") + Response disenroll(@HeaderParam("Accept") String headerParam, List deviceids) throws WindowsDeviceEnrolmentException; + + @POST + @Path("/devicewipe") + Response wipe(@HeaderParam("Accept") String headerParam, List deviceids) throws WindowsDeviceEnrolmentException; + + @POST + @Path("/devicering") + Response ring(@HeaderParam("Accept") String headerParam, List deviceids) throws WindowsDeviceEnrolmentException; + + @POST + @Path("/lockreset") + Response lockReset(@HeaderParam("Accept") String acceptHeader, List deviceids) + throws WindowsDeviceEnrolmentException; +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/adminoperations/beans/Device.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/adminoperations/beans/Device.java new file mode 100644 index 0000000000..79a4af79c7 --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/adminoperations/beans/Device.java @@ -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. + */ + +package org.wso2.carbon.mdm.mobileservices.windows.services.adminoperations.beans; + +/** + * Class for get device IDS. + */ +public class Device { + + private String id; + + public String getID() { + return id; + } + + public void setID(String id) { + this.id = id; + } + +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/adminoperations/beans/Disenrollment.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/adminoperations/beans/Disenrollment.java new file mode 100644 index 0000000000..85054033b2 --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/adminoperations/beans/Disenrollment.java @@ -0,0 +1,42 @@ +/* + * 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.wso2.carbon.mdm.mobileservices.windows.services.adminoperations.beans; + +import java.io.Serializable; + +/** + * Class for dis-enrollment operation + */ +public class Disenrollment implements Serializable { + + private boolean enabled; + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + private boolean isEnable() { + return enabled; + } + +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/adminoperations/beans/OperationRequest.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/adminoperations/beans/OperationRequest.java new file mode 100644 index 0000000000..0745aa53ae --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/adminoperations/beans/OperationRequest.java @@ -0,0 +1,49 @@ +/* + * 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.wso2.carbon.mdm.mobileservices.windows.services.adminoperations.beans; + +import org.wso2.carbon.mdm.mobileservices.windows.services.syncml.beans.BasicOperation; + +import java.util.List; + +/** + * Class for set basic operations. + */ +public class OperationRequest { + + private List deviceList; + private BasicOperation basicOperation; + + public BasicOperation getBasicOperation() { + return basicOperation; + } + + public void setBasicOperation(BasicOperation basicOperation) { + this.basicOperation = basicOperation; + } + + public List getDeviceList() { + return deviceList; + } + + public void setDeviceList(List deviceList) { + this.deviceList = deviceList; + } + +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/adminoperations/beans/OperationResponse.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/adminoperations/beans/OperationResponse.java new file mode 100644 index 0000000000..d1ecce0e61 --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/adminoperations/beans/OperationResponse.java @@ -0,0 +1,48 @@ +/* + * 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.wso2.carbon.mdm.mobileservices.windows.services.adminoperations.beans; + +/** + * This bean class is for credentials coming from wab page at federated authentication step. + */ +public class OperationResponse { + + private String errorCode; + private String statusCode; + + public OperationResponse() { + + } + + public String getErrorCode() { + return errorCode; + } + + public String getStatusCode() { + return statusCode; + } + + public void setErrorCode(String errorCode) { + this.errorCode = errorCode; + } + + public void setStatusCode(String statusCode) { + this.statusCode = statusCode; + } +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/adminoperations/beans/StorageEncryption.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/adminoperations/beans/StorageEncryption.java new file mode 100644 index 0000000000..9aaf4ce6f8 --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/adminoperations/beans/StorageEncryption.java @@ -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. + */ + +package org.wso2.carbon.mdm.mobileservices.windows.services.adminoperations.beans; + +import java.io.Serializable; + +/** + * This class represents the information of encrypt operation. + */ +public class StorageEncryption implements Serializable { + private boolean encrypted; + + public boolean isEncrypted() { + return encrypted; + } + + public void setEncrypted(boolean encrypted) { + this.encrypted = encrypted; + } +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/adminoperations/beans/wrapper/DisenrollmentBeanWrapper.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/adminoperations/beans/wrapper/DisenrollmentBeanWrapper.java new file mode 100644 index 0000000000..5f683e8846 --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/adminoperations/beans/wrapper/DisenrollmentBeanWrapper.java @@ -0,0 +1,50 @@ +/* + * 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.wso2.carbon.mdm.mobileservices.windows.services.adminoperations.beans.wrapper; + +import org.wso2.carbon.mdm.mobileservices.windows.services.adminoperations.beans.Disenrollment; + +import java.util.List; + +/** + * Class is used to wrap dis-enrollment bean with the device + */ +public class DisenrollmentBeanWrapper { + + private Disenrollment operation; + + private List deviceId; + + public Disenrollment getOperation() { + return operation; + } + + public void setOperation(Disenrollment operation) { + this.operation = operation; + } + + public List getDeviceId() { + return deviceId; + } + + public void setDeviceId(List deviceId) { + this.deviceId = deviceId; + } + +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/adminoperations/beans/wrapper/EncryptBeanWrapper.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/adminoperations/beans/wrapper/EncryptBeanWrapper.java new file mode 100644 index 0000000000..72181661a8 --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/adminoperations/beans/wrapper/EncryptBeanWrapper.java @@ -0,0 +1,48 @@ +/* + * 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.wso2.carbon.mdm.mobileservices.windows.services.adminoperations.beans.wrapper; + +import org.wso2.carbon.mdm.mobileservices.windows.services.adminoperations.beans.StorageEncryption; + +import java.util.List; + +/** + * This class is used to wrap the Encrypt bean with devices. + */ +public class EncryptBeanWrapper { + + private StorageEncryption operation; + private List deviceIDs; + + public StorageEncryption getOperation() { + return operation; + } + + public void setOperation(StorageEncryption operation) { + this.operation = operation; + } + + public List getDeviceIDs() { + return deviceIDs; + } + + public void setDeviceIDs(List deviceIDs) { + this.deviceIDs = deviceIDs; + } +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/adminoperations/impl/OperationsImpl.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/adminoperations/impl/OperationsImpl.java new file mode 100644 index 0000000000..2a7cd96beb --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/adminoperations/impl/OperationsImpl.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.wso2.carbon.mdm.mobileservices.windows.services.adminoperations.impl; + +import com.ibm.wsdl.OperationImpl; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.device.mgt.common.DeviceManagementException; +import org.wso2.carbon.device.mgt.common.operation.mgt.Operation; +import org.wso2.carbon.device.mgt.common.operation.mgt.OperationManagementException; +import org.wso2.carbon.device.mgt.core.operation.mgt.CommandOperation; +import org.wso2.carbon.mdm.mobileservices.windows.common.PluginConstants; +import org.wso2.carbon.mdm.mobileservices.windows.common.exceptions.WindowsDeviceEnrolmentException; +import org.wso2.carbon.mdm.mobileservices.windows.common.exceptions.WindowsOperationsException; +import org.wso2.carbon.mdm.mobileservices.windows.common.util.Message; +import org.wso2.carbon.mdm.mobileservices.windows.common.util.WindowsAPIUtils; +import org.wso2.carbon.mdm.mobileservices.windows.services.adminoperations.Operations; + +import javax.ws.rs.HeaderParam; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import java.util.List; + +/** + * Implementation class of operations interface. Each method in this class receives the operations comes via UI + * and persists those in the correct format. + */ +public class OperationsImpl implements Operations { + + private static Log log = LogFactory.getLog(OperationImpl.class); + + /** + * REST endpoint for the Device Lock operation + * + * @param acceptHeader header value of the request POST message. + * @param deviceIDs list of device ids to be add device lockOperationUpdate operation. + * @return Response object for client. + * @throws WindowsDeviceEnrolmentException + */ + @POST + @Path("/lock") + public Response lock(@HeaderParam("Accept") String acceptHeader, List deviceIDs) + throws WindowsDeviceEnrolmentException { + if (log.isDebugEnabled()) { + log.debug("Invoking windows device lockOperationUpdate operation"); + } + MediaType responseMediaType = WindowsAPIUtils.getResponseMediaType(acceptHeader); + Message message = new Message(); + try { + CommandOperation operation = new CommandOperation(); + operation.setCode(PluginConstants.OperationCodes.DEVICE_LOCK); + operation.setType(Operation.Type.COMMAND); + operation.setEnabled(true); + return WindowsAPIUtils.getOperationResponse(deviceIDs, operation, message, responseMediaType); + } catch (OperationManagementException e) { + String errorMessage = "Issue in retrieving operation management service instance"; + message.setResponseMessage(errorMessage); + message.setResponseCode(Response.Status.INTERNAL_SERVER_ERROR.toString()); + log.error(errorMessage, e); + throw new WindowsOperationsException(message, responseMediaType); + } catch (DeviceManagementException e) { + String errorMessage = "Issue in retrieving device management service instance"; + message.setResponseMessage(errorMessage); + message.setResponseCode(Response.Status.INTERNAL_SERVER_ERROR.toString()); + log.error(errorMessage, e); + throw new WindowsOperationsException(message, responseMediaType); + } + } + + /** + * REST end point for device dis-enrollment. + * + * @param acceptHeader POST message header value. + * @param deviceIDs device ids to be dis-enrolled. + * @return Response object to the client. + * @throws WindowsDeviceEnrolmentException + */ + @POST + @Path("/disenroll") + public Response disenroll(@HeaderParam("Accept") String acceptHeader, List deviceIDs) + throws WindowsDeviceEnrolmentException { + + MediaType responseMediaType = WindowsAPIUtils.getResponseMediaType(acceptHeader); + Message message = new Message(); + CommandOperation operation = new CommandOperation(); + operation.setCode(PluginConstants.OperationCodes.DISENROLL); + operation.setType(Operation.Type.COMMAND); + operation.setEnabled(true); + try { + return WindowsAPIUtils.getOperationResponse(deviceIDs, operation, message, responseMediaType); + } catch (OperationManagementException e) { + String errorMessage = "Issue in retrieving operation management service instance"; + message.setResponseMessage(errorMessage); + message.setResponseCode(Response.Status.INTERNAL_SERVER_ERROR.toString()); + log.error(errorMessage, e); + throw new WindowsOperationsException(message, responseMediaType); + } catch (DeviceManagementException e) { + String errorMessage = "Issue in retrieving device management service instance"; + message.setResponseMessage(errorMessage); + message.setResponseCode(Response.Status.INTERNAL_SERVER_ERROR.toString()); + log.error(errorMessage, e); + throw new WindowsOperationsException(message, responseMediaType); + } + } + + /** + * REST Endpoint for the Device wipe. + * + * @param acceptHeader POST message header value. + * @param deviceids device ids to be wiped. + * @return Response object for the client. + * @throws WindowsDeviceEnrolmentException + */ + @POST + @Path("/wipe-data") + public Response wipe(@HeaderParam("Accept") String acceptHeader, List deviceids) + throws WindowsDeviceEnrolmentException { + + if (log.isDebugEnabled()) { + log.debug("Invoking windows wipe-data device operation"); + } + MediaType responseMediaType = WindowsAPIUtils.getResponseMediaType(acceptHeader); + Message message = new Message(); + CommandOperation operation = new CommandOperation(); + operation.setCode(PluginConstants.OperationCodes.WIPE_DATA); + operation.setType(Operation.Type.COMMAND); + try { + return WindowsAPIUtils.getOperationResponse(deviceids, operation, message, responseMediaType); + } catch (OperationManagementException e) { + String errorMessage = "Issue in retrieving operation management service instance"; + message.setResponseMessage(errorMessage); + message.setResponseCode(Response.Status.INTERNAL_SERVER_ERROR.toString()); + log.error(errorMessage, e); + throw new WindowsOperationsException(message, responseMediaType); + } catch (DeviceManagementException e) { + String errorMessage = "Issue in retrieving device management service instance"; + message.setResponseMessage(errorMessage); + message.setResponseCode(Response.Status.INTERNAL_SERVER_ERROR.toString()); + log.error(errorMessage, e); + throw new WindowsOperationsException(message, responseMediaType); + } + } + + /** + * REST end point for the device ring. + * + * @param acceptHeader post message header value. + * @param deviceIDs device ids to be ring. + * @return Response object for the client. + * @throws WindowsDeviceEnrolmentException + */ + @POST + @Path("/ring-device") + public Response ring(@HeaderParam("Accept") String acceptHeader, List deviceIDs) + throws WindowsDeviceEnrolmentException { + + if (log.isDebugEnabled()) { + log.debug("Invoking Windows ring-device device operation"); + } + MediaType responseMediaType = WindowsAPIUtils.getResponseMediaType(acceptHeader); + Message message = new Message(); + try { + CommandOperation operation = new CommandOperation(); + operation.setCode(PluginConstants.OperationCodes.DEVICE_RING); + operation.setType(Operation.Type.COMMAND); + return WindowsAPIUtils.getOperationResponse(deviceIDs, operation, message, responseMediaType); + } catch (OperationManagementException e) { + String errorMessage = "Issue in retrieving operation management service instance"; + message.setResponseMessage(errorMessage); + message.setResponseCode(Response.Status.INTERNAL_SERVER_ERROR.toString()); + log.error(errorMessage, e); + throw new WindowsOperationsException(message, responseMediaType); + } catch (DeviceManagementException e) { + String errorMessage = "Issue in retrieving device management service instance"; + message.setResponseMessage(errorMessage); + message.setResponseCode(Response.Status.INTERNAL_SERVER_ERROR.toString()); + log.error(errorMessage, e); + throw new WindowsOperationsException(message, responseMediaType); + } + } + + /** + * REST endpoint for the device lockOperationUpdate reset. + * Lock reset have to be done, when device user does not set PIN for the lockOperationUpdate screen. + * Admin set lockOperationUpdate operation for the specific device,If the device is in above scenario, + * admin will be notified.since + * admin have to set lockOperationUpdate reset operation to the device so that automatically generate PIN value for the + * lockOperationUpdate screen. + * + * @param acceptHeader POST message header value. + * @param deviceIDs Device ids to be lockOperationUpdate reset. + * @return Response object for the client. + * @throws WindowsDeviceEnrolmentException + */ + @POST + @Path("/lock-reset") + public Response lockReset(@HeaderParam("Accept") String acceptHeader, List deviceIDs) + throws WindowsDeviceEnrolmentException { + + if (log.isDebugEnabled()) { + log.debug("Invoking windows device lockReset storage operation"); + } + MediaType responseMediaType = WindowsAPIUtils.getResponseMediaType(acceptHeader); + Message message = new Message(); + try { + CommandOperation operation = new CommandOperation(); + operation.setCode(PluginConstants.OperationCodes.LOCK_RESET); + operation.setType(Operation.Type.COMMAND); + return WindowsAPIUtils.getOperationResponse(deviceIDs, operation, message, responseMediaType); + } catch (OperationManagementException e) { + String errorMessage = "Issue in retrieving operation management service instance"; + message.setResponseMessage(errorMessage); + message.setResponseCode(Response.Status.INTERNAL_SERVER_ERROR.toString()); + log.error(errorMessage, e); + throw new WindowsOperationsException(message, responseMediaType); + } catch (DeviceManagementException e) { + String errorMessage = "Issue in retrieving device management service instance"; + message.setResponseMessage(errorMessage); + message.setResponseCode(Response.Status.INTERNAL_SERVER_ERROR.toString()); + log.error(errorMessage, e); + throw new WindowsOperationsException(message, responseMediaType); + } + } +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/adminoperations/util/OperationStore.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/adminoperations/util/OperationStore.java new file mode 100644 index 0000000000..fe009c1f42 --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/adminoperations/util/OperationStore.java @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2014, 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.wso2.carbon.mdm.mobileservices.windows.services.adminoperations.util; + +import com.google.gson.Gson; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.context.PrivilegedCarbonContext; +import org.wso2.carbon.device.mgt.common.DeviceIdentifier; +import org.wso2.carbon.device.mgt.common.DeviceManagementConstants; +import org.wso2.carbon.device.mgt.common.DeviceManagementException; +import org.wso2.carbon.device.mgt.common.operation.mgt.Operation; +import org.wso2.carbon.device.mgt.common.operation.mgt.OperationManagementException; +import org.wso2.carbon.device.mgt.core.operation.mgt.ConfigOperation; +import org.wso2.carbon.device.mgt.core.service.DeviceManagementProviderService; +import org.wso2.carbon.mdm.mobileservices.windows.common.SyncmlCommandType; +import org.wso2.carbon.mdm.mobileservices.windows.common.exceptions.WindowsDeviceEnrolmentException; +import org.wso2.carbon.mdm.mobileservices.windows.services.adminoperations.beans.Device; +import org.wso2.carbon.mdm.mobileservices.windows.services.adminoperations.beans.OperationRequest; +import org.wso2.carbon.mdm.mobileservices.windows.services.syncml.beans.Wifi; + +import java.util.ArrayList; +import java.util.List; + +public class OperationStore { + + private static Log log = LogFactory.getLog(OperationStore.class); + + public static boolean storeOperation(OperationRequest operationRequest, Operation.Type type, + String commandType) throws + WindowsDeviceEnrolmentException { + + List devices = operationRequest.getDeviceList(); + List deviceIdentifiers = new ArrayList(); + DeviceIdentifier deviceIdentifier = new DeviceIdentifier(); + + Operation operation = transformBasicOperation(operationRequest, type, commandType); + + for (int i = 0; i < devices.size(); i++) { + try { + deviceIdentifier.setType(DeviceManagementConstants.MobileDeviceTypes.MOBILE_DEVICE_TYPE_WINDOWS); + deviceIdentifier.setId(devices.get(i).getID()); + deviceIdentifiers.add(deviceIdentifier); + getDeviceManagementServiceProvider().getDevice(deviceIdentifier); + + } catch (DeviceManagementException e) { + log.error("Cannot validate device ID: " + devices.get(i).getID()); + deviceIdentifiers.remove(i); + } + } + try { + getDeviceManagementServiceProvider().addOperation(operation, deviceIdentifiers); + } catch (OperationManagementException e) { + String msg = "Failure occurred while storing command operation."; + log.error(msg); + return false; + } + return true; + } + + private static DeviceManagementProviderService getDeviceManagementServiceProvider() { + DeviceManagementProviderService deviceManager; + PrivilegedCarbonContext ctx = PrivilegedCarbonContext.getThreadLocalCarbonContext(); + deviceManager = + (DeviceManagementProviderService) ctx.getOSGiService(DeviceManagementProviderService.class, null); + + if (deviceManager == null) { + String msg = "Device management service is not initialized."; + log.error(msg); + } + return deviceManager; + } + + private static Operation transformBasicOperation(OperationRequest operationRequest, Operation.Type type, + String commandType) throws WindowsDeviceEnrolmentException { + + Operation operation = new Operation(); + operation.setCode(commandType); + operation.setType(type); + Gson gson = new Gson(); + + if (commandType == SyncmlCommandType.WIFI.getValue()) { + + operation = new ConfigOperation(); + operation.setCode(commandType); + operation.setType(type); + + Wifi wifiObject = (Wifi) operationRequest.getBasicOperation(); + operation.setPayLoad(gson.toJson(wifiObject)); + } else { + // no operation..... + } + + return operation; + } +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/authbst/BSTProvider.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/authbst/BSTProvider.java new file mode 100644 index 0000000000..42660faeae --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/authbst/BSTProvider.java @@ -0,0 +1,40 @@ +/* + * 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.wso2.carbon.mdm.mobileservices.windows.services.authbst; + +import org.wso2.carbon.mdm.mobileservices.windows.common.exceptions.WindowsDeviceEnrolmentException; +import org.wso2.carbon.mdm.mobileservices.windows.services.authbst.beans.Credentials; + +import javax.ws.rs.Consumes; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +/** + * Interface for handling authentication request comes via MDM login page. + */ +@Path("/bst") +public interface BSTProvider { + + @POST + @Consumes(MediaType.APPLICATION_JSON) + @Path("/authentication") + Response getBST(Credentials credentials) throws WindowsDeviceEnrolmentException; +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/authbst/beans/Credentials.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/authbst/beans/Credentials.java new file mode 100644 index 0000000000..24be2d06cd --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/authbst/beans/Credentials.java @@ -0,0 +1,87 @@ +/* + * 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.wso2.carbon.mdm.mobileservices.windows.services.authbst.beans; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; + +/** + * This bean class is for credentials coming from wab page at federated authentication step. + */ +@XmlRootElement(name = "credentials") +@XmlAccessorType(XmlAccessType.FIELD) +public class Credentials { + + @XmlElement(required = true, name = "username") + private String username; + @XmlElement(required = true, name = "email") + private String email; + @XmlElement(required = true, name = "password") + private String password; + @XmlElement(required = true, name = "ownership") + private String ownership; + @XmlElement(required = true, name = "token") + private String usertoken; + + public Credentials() { + + } + + public String getUsertoken() { + return usertoken; + } + + public void setUsertoken(String usertoken) { + this.usertoken = usertoken; + } + + public String getOwnership() { + return ownership; + } + + public void setOwnership(String ownership) { + this.ownership = ownership; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public void setUsername(String username) { + this.username = username; + } + + public void setPassword(String password) { + this.password = password; + } + + public String getUsername() { + return username; + } + + public String getPassword() { + return password; + } +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/authbst/impl/BSTProviderImpl.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/authbst/impl/BSTProviderImpl.java new file mode 100644 index 0000000000..9f8bd7b8a3 --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/authbst/impl/BSTProviderImpl.java @@ -0,0 +1,131 @@ +/* + * 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.wso2.carbon.mdm.mobileservices.windows.services.authbst.impl; + +import org.apache.commons.codec.binary.Base64; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.json.JSONException; +import org.json.JSONObject; +import org.wso2.carbon.context.PrivilegedCarbonContext; +import org.wso2.carbon.device.mgt.common.DeviceManagementException; +import org.wso2.carbon.mdm.mobileservices.windows.common.beans.Token; +import org.wso2.carbon.mdm.mobileservices.windows.common.exceptions.AuthenticationException; +import org.wso2.carbon.mdm.mobileservices.windows.common.exceptions.WindowsDeviceEnrolmentException; +import org.wso2.carbon.mdm.mobileservices.windows.common.util.DeviceUtil; +import org.wso2.carbon.mdm.mobileservices.windows.services.authbst.BSTProvider; +import org.wso2.carbon.mdm.mobileservices.windows.services.authbst.beans.Credentials; +import org.wso2.carbon.user.api.UserRealm; +import org.wso2.carbon.user.api.UserStoreException; +import org.wso2.carbon.user.core.service.RealmService; +import org.wso2.carbon.utils.multitenancy.MultitenantConstants; + +import javax.ws.rs.core.Response; + +/** + * Implementation class of BSTProvider interface which authenticates the credentials comes via MDM login page. + */ +public class BSTProviderImpl implements BSTProvider { + + private static Log log = LogFactory.getLog(BSTProviderImpl.class); + private static final String DELIMITER = "@"; + + /** + * This method validates the device user, checking passed credentials and returns the corresponding + * binary security token which is used in XCEP and WSTEP stages for authentication. + * + * @param credentials - Credential object passes from the wab page + * @return - Response with binary security token + */ + @Override + public Response getBST(Credentials credentials) throws WindowsDeviceEnrolmentException { + + String domainUser = credentials.getUsername(); + String userToken = credentials.getUsertoken(); + String encodedToken; + try { + Token tokenBean = new Token(); + tokenBean.setChallengeToken(userToken); + Base64 base64 = new Base64(); + encodedToken = base64.encodeToString(userToken.getBytes()); + DeviceUtil.persistChallengeToken(encodedToken, null, domainUser); + JSONObject tokenContent = new JSONObject(); + tokenContent.put("UserToken", userToken); + return Response.ok().entity(tokenContent.toString()).build(); + } catch (DeviceManagementException e) { + String msg = "Failure occurred in generating challenge token."; + log.error(msg, e); + throw new WindowsDeviceEnrolmentException(msg, e); + } catch (JSONException e) { + String msg = "Failure occurred in generating challenge token Json."; + log.error(msg, e); + throw new WindowsDeviceEnrolmentException(msg, e); + } + } + + /** + * This method authenticate the user checking the carbon default user store. + * + * @param username - Username in username token + * @param password - Password in username token + * @param tenantDomain - Tenant domain is extracted from the username + * @return - Returns boolean representing authentication result + * @throws AuthenticationException + */ + private boolean authenticate(String username, String password, String tenantDomain) throws + AuthenticationException { + + try { + PrivilegedCarbonContext.startTenantFlow(); + PrivilegedCarbonContext ctx = PrivilegedCarbonContext.getThreadLocalCarbonContext(); + ctx.setTenantDomain(MultitenantConstants.SUPER_TENANT_DOMAIN_NAME); + ctx.setTenantId(MultitenantConstants.SUPER_TENANT_ID); + RealmService realmService = (RealmService) ctx.getOSGiService(RealmService.class, null); + + if (realmService == null) { + String msg = "RealmService not initialized."; + log.error(msg); + throw new AuthenticationException(msg); + } + + int tenantId; + if (tenantDomain == null || tenantDomain.trim().isEmpty()) { + tenantId = MultitenantConstants.SUPER_TENANT_ID; + } else { + tenantId = realmService.getTenantManager().getTenantId(tenantDomain); + } + + if (tenantId == MultitenantConstants.INVALID_TENANT_ID) { + String msg = "Invalid tenant domain " + tenantDomain; + log.error(msg); + throw new AuthenticationException(msg); + } + UserRealm userRealm = realmService.getTenantUserRealm(tenantId); + + return userRealm.getUserStoreManager().authenticate(username, password); + } catch (UserStoreException e) { + String msg = "User store is not initialized."; + log.error(msg, e); + throw new AuthenticationException(msg, e); + } finally { + PrivilegedCarbonContext.endTenantFlow(); + } + } + +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/configurationmgtservice/ConfigurationMgtService.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/configurationmgtservice/ConfigurationMgtService.java new file mode 100644 index 0000000000..ae8e5f8817 --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/configurationmgtservice/ConfigurationMgtService.java @@ -0,0 +1,181 @@ +/* + * 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.wso2.carbon.mdm.mobileservices.windows.services.configurationmgtservice; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.device.mgt.common.DeviceManagementConstants; +import org.wso2.carbon.device.mgt.common.DeviceManagementException; +import org.wso2.carbon.device.mgt.common.configuration.mgt.ConfigurationEntry; +import org.wso2.carbon.device.mgt.common.configuration.mgt.TenantConfiguration; +import org.wso2.carbon.device.mgt.common.license.mgt.License; +import org.wso2.carbon.mdm.mobileservices.windows.common.PluginConstants; +import org.wso2.carbon.mdm.mobileservices.windows.common.exceptions.WindowsConfigurationException; +import org.wso2.carbon.mdm.mobileservices.windows.common.util.Message; +import org.wso2.carbon.mdm.mobileservices.windows.common.util.WindowsAPIUtils; + +import javax.jws.WebService; +import javax.ws.rs.*; +import javax.ws.rs.core.Response; +import java.util.List; + +/** + * Windows Platform Configuration REST-API implementation. + * All end points supports JSON, XMl with content negotiation. + */ +@WebService +@Produces({"application/json", "application/xml"}) +@Consumes({"application/json", "application/xml"}) +public class ConfigurationMgtService { + + private static Log log = LogFactory.getLog(ConfigurationMgtService.class); + + /** + * Save Tenant configurations. + * + * @param configuration Tenant Configurations to be saved. + * @return Message type object for the provide save status. + * @throws WindowsConfigurationException + */ + @POST + public Message ConfigureSettings(TenantConfiguration configuration) throws WindowsConfigurationException { + Message responseMsg = new Message(); + ConfigurationEntry licenseEntry = null; + String message; + + try { + configuration.setType(DeviceManagementConstants.MobileDeviceTypes.MOBILE_DEVICE_TYPE_WINDOWS); + if (!configuration.getConfiguration().isEmpty()) { + List configs = configuration.getConfiguration(); + for (ConfigurationEntry entry : configs) { + if (PluginConstants.TenantConfigProperties.LICENSE_KEY.equals(entry.getName())) { + License license = new License(); + license.setName(DeviceManagementConstants.MobileDeviceTypes.MOBILE_DEVICE_TYPE_WINDOWS); + license.setLanguage(PluginConstants.TenantConfigProperties.LANGUAGE_US); + license.setVersion("1.0.0"); + license.setText(entry.getValue().toString()); + WindowsAPIUtils.getDeviceManagementService().addLicense(DeviceManagementConstants. + MobileDeviceTypes.MOBILE_DEVICE_TYPE_WINDOWS, license); + licenseEntry = entry; + } + } + + if (licenseEntry != null) { + configs.remove(licenseEntry); + } + configuration.setConfiguration(configs); + WindowsAPIUtils.getDeviceManagementService().saveConfiguration(configuration); + Response.status(Response.Status.CREATED); + responseMsg.setResponseMessage("Windows platform configuration saved successfully."); + responseMsg.setResponseCode(Response.Status.CREATED.toString()); + return responseMsg; + } + else { + Response.status(Response.Status.BAD_REQUEST); + responseMsg.setResponseMessage("Windows platform configuration can not be saved."); + responseMsg.setResponseCode(Response.Status.CREATED.toString()); + } + } catch (DeviceManagementException e) { + message = "Error Occurred in while configuring Windows Platform."; + log.error(message, e); + throw new WindowsConfigurationException(message, e); + } + return responseMsg; + } + + /** + * Retrieve Tenant configurations according to the device type. + * + * @return Tenant configuration object contains specific tenant configurations. + * @throws WindowsConfigurationException + */ + @GET + public TenantConfiguration getConfiguration() throws WindowsConfigurationException { + String msg; + TenantConfiguration tenantConfiguration = null; + try { + if (WindowsAPIUtils.getDeviceManagementService(). + getConfiguration(DeviceManagementConstants.MobileDeviceTypes.MOBILE_DEVICE_TYPE_WINDOWS) != null) { + tenantConfiguration = WindowsAPIUtils.getDeviceManagementService(). + getConfiguration(DeviceManagementConstants.MobileDeviceTypes.MOBILE_DEVICE_TYPE_WINDOWS); + List configs = tenantConfiguration.getConfiguration(); + ConfigurationEntry entry = new ConfigurationEntry(); + License license = WindowsAPIUtils.getDeviceManagementService().getLicense( + DeviceManagementConstants.MobileDeviceTypes.MOBILE_DEVICE_TYPE_WINDOWS, PluginConstants. + TenantConfigProperties.LANGUAGE_US); + if(license != null) { + entry.setContentType(PluginConstants.TenantConfigProperties.CONTENT_TYPE_TEXT); + entry.setName(PluginConstants.TenantConfigProperties.LICENSE_KEY); + entry.setValue(license.getText()); + configs.add(entry); + tenantConfiguration.setConfiguration(configs); + } + } + } catch (DeviceManagementException e) { + msg = "Error occurred while retrieving the Windows tenant configuration"; + log.error(msg, e); + throw new WindowsConfigurationException(msg, e); + } + return tenantConfiguration; + } + + /** + * Update Tenant Configurations for the specific Device type. + * + * @param configuration Tenant configurations to be updated. + * @return Response message. + * @throws WindowsConfigurationException + */ + @PUT + public Message updateConfiguration(TenantConfiguration configuration) throws WindowsConfigurationException { + String message; + Message responseMsg = new Message(); + ConfigurationEntry licenseEntry = null; + try { + configuration.setType(DeviceManagementConstants.MobileDeviceTypes.MOBILE_DEVICE_TYPE_WINDOWS); + List configs = configuration.getConfiguration(); + for (ConfigurationEntry entry : configs) { + if (PluginConstants.TenantConfigProperties.LICENSE_KEY.equals(entry.getName())) { + License license = new License(); + license.setName(DeviceManagementConstants.MobileDeviceTypes.MOBILE_DEVICE_TYPE_WINDOWS); + license.setLanguage(PluginConstants.TenantConfigProperties.LANGUAGE_US); + license.setVersion("1.0.0"); + license.setText(entry.getValue().toString()); + WindowsAPIUtils.getDeviceManagementService().addLicense(DeviceManagementConstants. + MobileDeviceTypes.MOBILE_DEVICE_TYPE_WINDOWS, license); + licenseEntry = entry; + } + } + + if (licenseEntry != null) { + configs.remove(licenseEntry); + } + configuration.setConfiguration(configs); + WindowsAPIUtils.getDeviceManagementService().saveConfiguration(configuration); + Response.status(Response.Status.CREATED); + responseMsg.setResponseMessage("Windows platform configuration succeeded."); + responseMsg.setResponseCode(Response.Status.CREATED.toString()); + } catch (DeviceManagementException e) { + message = "Error occurred while modifying configuration settings of Windows platform."; + log.error(message, e); + throw new WindowsConfigurationException(message, e); + } + return responseMsg; + } +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/devicemgtservice/DeviceManagementService.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/devicemgtservice/DeviceManagementService.java new file mode 100644 index 0000000000..4e702f75ec --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/devicemgtservice/DeviceManagementService.java @@ -0,0 +1,158 @@ +/* + * 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.wso2.carbon.mdm.mobileservices.windows.services.devicemgtservice; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.device.mgt.common.Device; +import org.wso2.carbon.device.mgt.common.DeviceIdentifier; +import org.wso2.carbon.device.mgt.common.DeviceManagementConstants; +import org.wso2.carbon.device.mgt.common.DeviceManagementException; +import org.wso2.carbon.device.mgt.common.license.mgt.License; +import org.wso2.carbon.mdm.mobileservices.windows.common.exceptions.WindowsConfigurationException; +import org.wso2.carbon.mdm.mobileservices.windows.common.util.Message; +import org.wso2.carbon.mdm.mobileservices.windows.common.util.WindowsAPIUtils; + +import javax.jws.WebService; +import javax.ws.rs.*; +import javax.ws.rs.core.Response; +import java.util.List; + +/** + * Windows Device Management REST-API implementation. + * All end points supports JSON, XMl with content negotiation. + */ +@WebService +@Produces({"application/json", "application/xml"}) +@Consumes({"application/json", "application/xml"}) +public class DeviceManagementService { + + private static Log log = LogFactory.getLog(DeviceManagementService.class); + + /** + * Get all devices.Returns list of Windows devices registered in MDM. + * + * @return Device List + * @throws WindowsConfigurationException + */ + @GET + public List getAllDevices() + throws WindowsConfigurationException { + String msg; + List devices; + + try { + devices = WindowsAPIUtils.getDeviceManagementService(). + getAllDevices(DeviceManagementConstants.MobileDeviceTypes.MOBILE_DEVICE_TYPE_WINDOWS); + } catch (DeviceManagementException e) { + msg = "Error occurred while fetching the device list."; + log.error(msg, e); + throw new WindowsConfigurationException(msg, e); + } + return devices; + } + + /** + * Fetch Windows device details of a given device Id. + * + * @param id Device Id + * @return Device + * @throws WindowsConfigurationException + */ + @GET + @Path("{id}") + public org.wso2.carbon.device.mgt.common.Device getDevice(@PathParam("id") String id) + throws WindowsConfigurationException { + + String msg; + org.wso2.carbon.device.mgt.common.Device device; + + try { + DeviceIdentifier deviceIdentifier = WindowsAPIUtils.convertToDeviceIdentifierObject(id); + device = WindowsAPIUtils.getDeviceManagementService().getDevice(deviceIdentifier); + if (device == null) { + Response.status(Response.Status.NOT_FOUND); + } + } catch (DeviceManagementException deviceMgtEx) { + msg = "Error occurred while fetching the device information."; + log.error(msg, deviceMgtEx); + throw new WindowsConfigurationException(msg, deviceMgtEx); + } + return device; + } + + /** + * Update Windows device details of given device id. + * + * @param id Device Id + * @param device Device Details + * @return Message + * @throws WindowsConfigurationException + */ + @PUT + @Path("{id}") + public Message updateDevice(@PathParam("id") String id, Device device) + throws WindowsConfigurationException { + String msg; + Message responseMessage = new Message(); + DeviceIdentifier deviceIdentifier = new DeviceIdentifier(); + deviceIdentifier.setId(id); + deviceIdentifier + .setType(DeviceManagementConstants.MobileDeviceTypes.MOBILE_DEVICE_TYPE_WINDOWS); + boolean result; + try { + device.setType(DeviceManagementConstants.MobileDeviceTypes.MOBILE_DEVICE_TYPE_WINDOWS); + result = WindowsAPIUtils.getDeviceManagementService() + .updateDeviceInfo(deviceIdentifier, device); + if (result) { + Response.status(Response.Status.ACCEPTED); + responseMessage.setResponseMessage("Device information has modified successfully."); + } else { + Response.status(Response.Status.NOT_MODIFIED); + responseMessage.setResponseMessage("Device not found for the update."); + } + } catch (DeviceManagementException e) { + msg = "Error occurred while modifying the device information."; + log.error(msg, e); + throw new WindowsConfigurationException(msg, e); + } + return responseMessage; + } + + @GET + @Path("license") + @Produces("application/json") + public License getLicense() throws WindowsConfigurationException { + License license; + + try { + license = + WindowsAPIUtils.getDeviceManagementService().getLicense( + DeviceManagementConstants.MobileDeviceTypes.MOBILE_DEVICE_TYPE_WINDOWS, + DeviceManagementConstants.LanguageCodes.LANGUAGE_CODE_ENGLISH_US); + } catch (DeviceManagementException e) { + String msg = "Error occurred while retrieving the license configured for Windows device enrollment"; + log.error(msg, e); + throw new WindowsConfigurationException(msg, e); + } + return license; + } + +} + diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/discovery/DiscoveryService.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/discovery/DiscoveryService.java new file mode 100644 index 0000000000..798403331d --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/discovery/DiscoveryService.java @@ -0,0 +1,62 @@ +/* + * 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.wso2.carbon.mdm.mobileservices.windows.services.discovery; + +import org.wso2.carbon.mdm.mobileservices.windows.common.PluginConstants; +import org.wso2.carbon.mdm.mobileservices.windows.services.discovery.beans.DiscoveryRequest; +import org.wso2.carbon.mdm.mobileservices.windows.services.discovery.beans.DiscoveryResponse; + +import javax.jws.WebMethod; +import javax.jws.WebParam; +import javax.jws.WebResult; +import javax.jws.WebService; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.core.Response; +import javax.xml.ws.BindingType; +import javax.xml.ws.RequestWrapper; +import javax.xml.ws.ResponseWrapper; +import javax.xml.ws.soap.SOAPBinding; + +/** + * Interface for Discovery service related operations. + */ +@WebService(targetNamespace = PluginConstants.DISCOVERY_SERVICE_TARGET_NAMESPACE, + name = "IDiscoveryService") +@BindingType(value = SOAPBinding.SOAP12HTTP_BINDING) +public interface DiscoveryService { + + @POST + @RequestWrapper(localName = "Discover", targetNamespace = PluginConstants.DISCOVERY_SERVICE_TARGET_NAMESPACE) + @WebMethod(operationName = "Discover") + @ResponseWrapper(localName = "DiscoverResponse", targetNamespace = PluginConstants.DISCOVERY_SERVICE_TARGET_NAMESPACE) + void discover( + @WebParam(name = "request", targetNamespace = PluginConstants.DISCOVERY_SERVICE_TARGET_NAMESPACE) + DiscoveryRequest request, + @WebParam(mode = WebParam.Mode.OUT, name = "DiscoverResult", + targetNamespace = PluginConstants.DISCOVERY_SERVICE_TARGET_NAMESPACE) + javax.xml.ws.Holder response + ); + + @GET + @WebMethod + @WebResult() + Response discoverGet(); + +} \ No newline at end of file diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/discovery/beans/DiscoveryRequest.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/discovery/beans/DiscoveryRequest.java new file mode 100644 index 0000000000..9acd44f0fc --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/discovery/beans/DiscoveryRequest.java @@ -0,0 +1,61 @@ +/* + * 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.wso2.carbon.mdm.mobileservices.windows.services.discovery.beans; + +import javax.xml.bind.annotation.*; +import java.io.Serializable; + +@XmlAccessorType(XmlAccessType.FIELD) +@XmlType(name = "DiscoveryRequest") +@SuppressWarnings("unused") +public class DiscoveryRequest implements Serializable { + + @XmlElement(name = "EmailAddress", required = true) + private String emailId; + + @XmlElement(name = "RequestVersion") + private String version; + + @XmlElement(name = "DeviceType") + private String deviceType; + + public String getEmailId() { + return emailId; + } + + public String getVersion() { + return version; + } + + public void setEmailId(String emailId) { + this.emailId = emailId; + } + + public void setVersion(String version) { + this.version = version; + } + + public String getDeviceType() { + return deviceType; + } + + public void setDeviceType(String deviceType) { + this.deviceType = deviceType; + } +} \ No newline at end of file diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/discovery/beans/DiscoveryResponse.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/discovery/beans/DiscoveryResponse.java new file mode 100644 index 0000000000..66d597f6a8 --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/discovery/beans/DiscoveryResponse.java @@ -0,0 +1,73 @@ +/* + * 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.wso2.carbon.mdm.mobileservices.windows.services.discovery.beans; + +import javax.xml.bind.annotation.*; +import java.io.Serializable; + +@XmlAccessorType(XmlAccessType.FIELD) +@XmlType(name = "DiscoveryResponse") +@SuppressWarnings("unused") +public class DiscoveryResponse implements Serializable { + + @XmlElement(name = "AuthPolicy") + private String authPolicy; + + @XmlElement(name = "EnrollmentPolicyServiceUrl") + private String enrollmentPolicyServiceUrl; + + @XmlElement(name = "EnrollmentServiceUrl") + private String enrollmentServiceUrl; + + @XmlElement(name = "AuthenticationServiceUrl") + private String authenticationServiceUrl; + + public void setAuthenticationServiceUrl(String authenticationServiceUrl) { + this.authenticationServiceUrl = authenticationServiceUrl; + } + + public String getAuthenticationServiceUrl() { + return authenticationServiceUrl; + } + + public String getAuthPolicy() { + return authPolicy; + } + + public String getEnrollmentPolicyServiceUrl() { + return enrollmentPolicyServiceUrl; + } + + public String getEnrollmentServiceUrl() { + return enrollmentServiceUrl; + } + + public void setAuthPolicy(String authPolicy) { + this.authPolicy = authPolicy; + } + + public void setEnrollmentPolicyServiceUrl(String enrollmentPolicyServiceUrl) { + this.enrollmentPolicyServiceUrl = enrollmentPolicyServiceUrl; + } + + public void setEnrollmentServiceUrl(String enrollmentServiceUrl) { + this.enrollmentServiceUrl = enrollmentServiceUrl; + } + +} \ No newline at end of file diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/discovery/beans/package-info.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/discovery/beans/package-info.java new file mode 100644 index 0000000000..df5d15392c --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/discovery/beans/package-info.java @@ -0,0 +1,22 @@ +/* +* 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. +*/ + +@javax.xml.bind.annotation.XmlSchema(namespace = + "http://schemas.microsoft.com/windows/management/2012/01/enrollment", + elementFormDefault = javax.xml.bind.annotation.XmlNsForm.QUALIFIED) +package org.wso2.carbon.mdm.mobileservices.windows.services.discovery.beans; diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/discovery/impl/DiscoveryServiceImpl.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/discovery/impl/DiscoveryServiceImpl.java new file mode 100644 index 0000000000..f15057a66a --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/discovery/impl/DiscoveryServiceImpl.java @@ -0,0 +1,112 @@ +/* + * 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.wso2.carbon.mdm.mobileservices.windows.services.discovery.impl; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.mdm.mobileservices.windows.common.PluginConstants; +import org.wso2.carbon.mdm.mobileservices.windows.common.beans.WindowsPluginProperties; +import org.wso2.carbon.mdm.mobileservices.windows.services.discovery.beans.DiscoveryRequest; +import org.wso2.carbon.mdm.mobileservices.windows.services.discovery.DiscoveryService; +import org.wso2.carbon.mdm.mobileservices.windows.services.discovery.beans.DiscoveryResponse; + +import javax.annotation.Resource; +import javax.jws.WebService; +import javax.servlet.ServletContext; +import javax.ws.rs.core.Response; +import javax.xml.ws.BindingType; +import javax.xml.ws.Holder; +import javax.xml.ws.WebServiceContext; +import javax.xml.ws.handler.MessageContext; +import javax.xml.ws.soap.Addressing; +import javax.xml.ws.soap.SOAPBinding; + +/** + * Implementation class of Discovery Request. This class implements the first two services + * of device enrolment stage. + */ +@WebService(endpointInterface = PluginConstants.DISCOVERY_SERVICE_ENDPOINT, targetNamespace = PluginConstants + .DISCOVERY_SERVICE_TARGET_NAMESPACE) +@Addressing(enabled = true, required = true) +@BindingType(value = SOAPBinding.SOAP12HTTP_BINDING) +public class DiscoveryServiceImpl implements DiscoveryService { + + public static final String FEDERATED = "Federated"; + private static Log log = LogFactory.getLog(DiscoveryServiceImpl.class); + @Resource + private WebServiceContext context; + + /** + * This method returns the OnPremise AuthPolicy and next two endpoint the mobile device should + * call if this response to received successfully at the device end. This method is called by + * device immediately after the first GET method calling for the same endpoint. + * + * @param discoveryRequest - Request bean comes via mobile phone + * @param response - DiscoveryResponse bean for response + */ + @Override + public void discover(DiscoveryRequest discoveryRequest, Holder response) { + + ServletContext ctx = (ServletContext) context.getMessageContext().get(MessageContext.SERVLET_CONTEXT); + WindowsPluginProperties windowsPluginProperties = (WindowsPluginProperties) ctx.getAttribute( + PluginConstants.WINDOWS_PLUGIN_PROPERTIES); + + DiscoveryResponse discoveryResponse = new DiscoveryResponse(); + if (FEDERATED.equals(windowsPluginProperties.getAuthPolicy())) { + discoveryResponse.setAuthPolicy(windowsPluginProperties.getAuthPolicy()); + discoveryResponse.setEnrollmentPolicyServiceUrl(PluginConstants.Discovery.ENROLL_SUBDOMAIN + + windowsPluginProperties.getDomain() + PluginConstants.Discovery. + CERTIFICATE_ENROLLMENT_POLICY_SERVICE_URL); + discoveryResponse.setEnrollmentServiceUrl(PluginConstants.Discovery.ENROLL_SUBDOMAIN + + windowsPluginProperties.getDomain() + PluginConstants.Discovery. + CERTIFICATE_ENROLLMENT_SERVICE_URL); + discoveryResponse.setAuthenticationServiceUrl(PluginConstants.Discovery.ENROLL_SUBDOMAIN + + windowsPluginProperties.getDomain() + PluginConstants.Discovery.WAB_URL); + } else { + discoveryResponse.setAuthPolicy(windowsPluginProperties.getAuthPolicy()); + discoveryResponse.setEnrollmentPolicyServiceUrl(PluginConstants.Discovery.ENROLL_SUBDOMAIN + + windowsPluginProperties.getDomain() + PluginConstants.Discovery. + ONPREMISE_CERTIFICATE_ENROLLMENT_POLICY); + discoveryResponse.setEnrollmentServiceUrl(PluginConstants.Discovery.ENROLL_SUBDOMAIN + + windowsPluginProperties.getDomain() + PluginConstants.Discovery. + ONPREMISE_CERTIFICATE_ENROLLMENT_SERVICE_URL); + discoveryResponse.setAuthenticationServiceUrl(null); + } + response.value = discoveryResponse; + + if (log.isDebugEnabled()) { + log.debug("Discovery service end point was triggered via POST method"); + } + } + + /** + * This is the first method called through device. The device checks the availability of the + * Service end point by calling this method. + * + * @return - HTTP 200OK message + */ + @Override + public Response discoverGet() { + + if (log.isDebugEnabled()) { + log.debug("Discovery service end point was triggered via GET method."); + } + return Response.ok().build(); + } +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/policymgtservice/PolicyMgtService.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/policymgtservice/PolicyMgtService.java new file mode 100644 index 0000000000..cf013845fa --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/policymgtservice/PolicyMgtService.java @@ -0,0 +1,71 @@ +/* + * 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.wso2.carbon.mdm.mobileservices.windows.services.policymgtservice; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.device.mgt.common.DeviceIdentifier; +import org.wso2.carbon.mdm.mobileservices.windows.common.exceptions.WindowsConfigurationException; +import org.wso2.carbon.mdm.mobileservices.windows.common.util.Message; +import org.wso2.carbon.mdm.mobileservices.windows.common.util.WindowsAPIUtils; +import org.wso2.carbon.policy.mgt.common.Policy; +import org.wso2.carbon.policy.mgt.common.PolicyManagementException; +import org.wso2.carbon.policy.mgt.core.PolicyManagerService; + +import javax.jws.WebService; +import javax.ws.rs.*; +import javax.ws.rs.core.Response; + +/** + * Endpoint for Enforce Effective Policy. + */ +@WebService +@Produces({"application/json", "application/xml"}) +@Consumes({"application/json", "application/xml"}) +public class PolicyMgtService { + private static Log log = LogFactory.getLog(PolicyMgtService.class); + + @GET + @Path("{id}") + public Message getEffectivePolicy(@HeaderParam("Accept") String acceptHeader, + @PathParam("id") String id) throws WindowsConfigurationException { + + DeviceIdentifier deviceIdentifier = WindowsAPIUtils.convertToDeviceIdentifierObject(id); + Message responseMessage = new Message(); + Policy policy; + try { + PolicyManagerService policyManagerService = WindowsAPIUtils.getPolicyManagerService(); + policy = policyManagerService.getEffectivePolicy(deviceIdentifier); + if (policy == null) { + responseMessage.setResponseCode(Response.Status.NO_CONTENT.toString()); + responseMessage.setResponseMessage("No effective policy found"); + return responseMessage; + } else { + responseMessage.setResponseCode(Response.Status.OK.toString()); + responseMessage.setResponseMessage("Effective policy added to operation"); + return responseMessage; + } + + } catch (PolicyManagementException e) { + String msg = "Error occurred while getting the policy."; + log.error(msg, e); + throw new WindowsConfigurationException(msg, e); + } + } +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/syncml/SyncmlService.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/syncml/SyncmlService.java new file mode 100644 index 0000000000..9e79626e86 --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/syncml/SyncmlService.java @@ -0,0 +1,48 @@ +/* + * 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.wso2.carbon.mdm.mobileservices.windows.services.syncml; + +import org.w3c.dom.Document; +import org.wso2.carbon.device.mgt.common.notification.mgt.NotificationManagementException; +import org.wso2.carbon.mdm.mobileservices.windows.common.PluginConstants; +import org.wso2.carbon.mdm.mobileservices.windows.common.exceptions.WindowsConfigurationException; +import org.wso2.carbon.mdm.mobileservices.windows.common.exceptions.WindowsDeviceEnrolmentException; +import org.wso2.carbon.mdm.mobileservices.windows.operations.WindowsOperationException; + +import javax.ws.rs.Consumes; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +/** + * Interface for Syncml message flow. + */ +@Path("/devicemanagement") +public interface SyncmlService { + + @Path("/request") + @POST + @Consumes({PluginConstants.SYNCML_MEDIA_TYPE, MediaType.APPLICATION_XML}) + @Produces(PluginConstants.SYNCML_MEDIA_TYPE) + Response getResponse(Document request) throws WindowsDeviceEnrolmentException, WindowsOperationException, + NotificationManagementException, WindowsConfigurationException; + +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/syncml/beans/BasicOperation.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/syncml/beans/BasicOperation.java new file mode 100644 index 0000000000..53d265472b --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/syncml/beans/BasicOperation.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.wso2.carbon.mdm.mobileservices.windows.services.syncml.beans; + +/** + * Bean for get basic operations. + */ +public class BasicOperation { + + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/syncml/beans/PasscodePolicy.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/syncml/beans/PasscodePolicy.java new file mode 100644 index 0000000000..51fc06cf45 --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/syncml/beans/PasscodePolicy.java @@ -0,0 +1,107 @@ +/* + * 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.wso2.carbon.mdm.mobileservices.windows.services.syncml.beans; + +/** + * Bean for device lockOperationUpdate screen passcode policy. + */ +public class PasscodePolicy extends BasicOperation { + + private int maxFailedAttempts; + private int minLength; + private int pinHistory; + private int minComplexChars; + private int maxPINAgeInDays; + private boolean requireAlphanumeric; + private boolean allowSimple; + private boolean enablePassword; + private int maxInactiveTime; + + public int getMaxInactiveTime() { + return maxInactiveTime; + } + + public void setMaxInactiveTime(int maxInactiveTime) { + this.maxInactiveTime = maxInactiveTime; + } + + public boolean isEnablePassword() { + return enablePassword; + } + + public void setEnablePassword(boolean enablePassword) { + this.enablePassword = enablePassword; + } + + public int getMaxFailedAttempts() { + return maxFailedAttempts; + } + + public void setMaxFailedAttempts(int maxFailedAttempts) { + this.maxFailedAttempts = maxFailedAttempts; + } + + public int getMinLength() { + return minLength; + } + + public void setMinLength(int minLength) { + this.minLength = minLength; + } + + public int getPinHistory() { + return pinHistory; + } + + public void setPinHistory(int pinHistory) { + this.pinHistory = pinHistory; + } + + public int getMinComplexChars() { + return minComplexChars; + } + + public void setMinComplexChars(int minComplexChars) { + this.minComplexChars = minComplexChars; + } + + public int getMaxPINAgeInDays() { + return maxPINAgeInDays; + } + + public void setMaxPINAgeInDays(int maxPINAgeInDays) { + this.maxPINAgeInDays = maxPINAgeInDays; + } + + public boolean isRequireAlphanumeric() { + return requireAlphanumeric; + } + + public void setRequireAlphanumeric(boolean requireAlphanumeric) { + this.requireAlphanumeric = requireAlphanumeric; + } + + public boolean isAllowSimple() { + return allowSimple; + } + + public void setAllowSimple(boolean allowSimple) { + this.allowSimple = allowSimple; + } +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/syncml/beans/Profile.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/syncml/beans/Profile.java new file mode 100644 index 0000000000..ad3a702e6a --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/syncml/beans/Profile.java @@ -0,0 +1,62 @@ +/* + * 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.wso2.carbon.mdm.mobileservices.windows.services.syncml.beans; + +/** + * Class for contains device active policy. + */ +public class Profile { + + String featureCode; + String data; + boolean enable; + boolean compliance; + + public boolean isCompliance() { + return compliance; + } + + public void setCompliance(boolean compliance) { + this.compliance = compliance; + } + + public boolean isEnable() { + return enable; + } + + public void setEnable(boolean enable) { + this.enable = enable; + } + + public String getFeatureCode() { + return featureCode; + } + + public void setFeatureCode(String featureCode) { + this.featureCode = featureCode; + } + + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/syncml/beans/Wifi.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/syncml/beans/Wifi.java new file mode 100644 index 0000000000..99979637ff --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/syncml/beans/Wifi.java @@ -0,0 +1,107 @@ +/* + * 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.wso2.carbon.mdm.mobileservices.windows.services.syncml.beans; + +/** + * Bean for WIFI configurations. + */ +public class Wifi extends BasicOperation { + + private String networkName; + private String ssid; + private String connectionType; + private String connectionMode; + private String authentication; + private String encryption; + private String keyType; + private String protection; + private String keyMaterial; + + public String getNetworkName() { + return networkName; + } + + public void setNetworkName(String networkName) { + this.networkName = networkName; + } + + public String getSsid() { + return ssid; + } + + public void setSsid(String ssid) { + this.ssid = ssid; + } + + public String getConnectionType() { + return connectionType; + } + + public void setConnectionType(String connectionType) { + this.connectionType = connectionType; + } + + public String getConnectionMode() { + return connectionMode; + } + + public void setConnectionMode(String connectionMode) { + this.connectionMode = connectionMode; + } + + public String getAuthentication() { + return authentication; + } + + public void setAuthentication(String authentication) { + this.authentication = authentication; + } + + public String getEncryption() { + return encryption; + } + + public void setEncryption(String encryption) { + this.encryption = encryption; + } + + public String getKeyType() { + return keyType; + } + + public void setKeyType(String keyType) { + this.keyType = keyType; + } + + public String getProtection() { + return protection; + } + + public void setProtection(String protection) { + this.protection = protection; + } + + public String getKeyMaterial() { + return keyMaterial; + } + + public void setKeyMaterial(String keyMaterial) { + this.keyMaterial = keyMaterial; + } +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/syncml/impl/SyncmlServiceImpl.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/syncml/impl/SyncmlServiceImpl.java new file mode 100644 index 0000000000..0a260ff755 --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/syncml/impl/SyncmlServiceImpl.java @@ -0,0 +1,462 @@ +/* + * 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.wso2.carbon.mdm.mobileservices.windows.services.syncml.impl; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.json.JSONException; +import org.w3c.dom.Document; +import org.wso2.carbon.context.PrivilegedCarbonContext; +import org.wso2.carbon.device.mgt.common.*; +import org.wso2.carbon.device.mgt.common.notification.mgt.NotificationManagementException; +import org.wso2.carbon.device.mgt.common.operation.mgt.Operation; +import org.wso2.carbon.device.mgt.common.operation.mgt.OperationManagementException; +import org.wso2.carbon.mdm.mobileservices.windows.common.PluginConstants; +import org.wso2.carbon.mdm.mobileservices.windows.common.beans.CacheEntry; +import org.wso2.carbon.mdm.mobileservices.windows.common.exceptions.SyncmlMessageFormatException; +import org.wso2.carbon.mdm.mobileservices.windows.common.exceptions.SyncmlOperationException; +import org.wso2.carbon.mdm.mobileservices.windows.common.exceptions.WindowsConfigurationException; +import org.wso2.carbon.mdm.mobileservices.windows.common.exceptions.WindowsDeviceEnrolmentException; +import org.wso2.carbon.mdm.mobileservices.windows.common.util.DeviceUtil; +import org.wso2.carbon.mdm.mobileservices.windows.common.util.WindowsAPIUtils; +import org.wso2.carbon.mdm.mobileservices.windows.operations.*; +import org.wso2.carbon.mdm.mobileservices.windows.operations.util.*; +import org.wso2.carbon.mdm.mobileservices.windows.services.syncml.SyncmlService; +import org.wso2.carbon.policy.mgt.common.PolicyManagementException; +import org.wso2.carbon.policy.mgt.common.monitor.PolicyComplianceException; +import org.wso2.carbon.policy.mgt.core.PolicyManagerService; + +import javax.ws.rs.core.Response; +import java.util.ArrayList; +import java.util.List; + +import static org.wso2.carbon.mdm.mobileservices.windows.common.util.WindowsAPIUtils.convertToDeviceIdentifierObject; + +/** + * Implementing class of SyncmlImpl interface. + */ +public class SyncmlServiceImpl implements SyncmlService { + + private static Log log = LogFactory.getLog(SyncmlServiceImpl.class); + + /** + * This method is used to generate and return Device object from the received information at + * the Syncml step. + * + * @param deviceID - Unique device ID received from the Device + * @param osVersion - Device OS version + * @param imsi - Device IMSI + * @param imei - Device IMEI + * @param manufacturer - Device Manufacturer name + * @param model - Device Model + * @return - Generated device object + */ + private Device generateDevice(String type, String deviceID, String osVersion, String imsi, + String imei, String manufacturer, String model, String user) { + + Device generatedDevice = new Device(); + + Device.Property OSVersionProperty = new Device.Property(); + OSVersionProperty.setName(PluginConstants.SyncML.OS_VERSION); + OSVersionProperty.setValue(osVersion); + + Device.Property IMSEIProperty = new Device.Property(); + IMSEIProperty.setName(PluginConstants.SyncML.IMSI); + IMSEIProperty.setValue(imsi); + + Device.Property IMEIProperty = new Device.Property(); + IMEIProperty.setName(PluginConstants.SyncML.IMEI); + IMEIProperty.setValue(imei); + + Device.Property DevManProperty = new Device.Property(); + DevManProperty.setName(PluginConstants.SyncML.VENDOR); + DevManProperty.setValue(manufacturer); + + Device.Property DevModProperty = new Device.Property(); + DevModProperty.setName(PluginConstants.SyncML.MODEL); + DevModProperty.setValue(model); + + List propertyList = new ArrayList<>(); + propertyList.add(OSVersionProperty); + propertyList.add(IMSEIProperty); + propertyList.add(IMEIProperty); + propertyList.add(DevManProperty); + propertyList.add(DevModProperty); + + EnrolmentInfo enrolmentInfo = new EnrolmentInfo(); + enrolmentInfo.setOwner(user); + enrolmentInfo.setOwnership(EnrolmentInfo.OwnerShip.BYOD); + enrolmentInfo.setStatus(EnrolmentInfo.Status.ACTIVE); + + generatedDevice.setEnrolmentInfo(enrolmentInfo); + generatedDevice.setDeviceIdentifier(deviceID); + generatedDevice.setProperties(propertyList); + generatedDevice.setType(type); + + return generatedDevice; + } + + /** + * Method for calling SyncML engine for producing the Syncml response. For the first SyncML message comes from + * the device, this method produces a response to retrieve device information for enrolling the device. + * + * @param request - SyncML request + * @return - SyncML response + * @throws WindowsOperationException + * @throws WindowsDeviceEnrolmentException + */ + @Override + public Response getResponse(Document request) + throws WindowsDeviceEnrolmentException, WindowsOperationException, NotificationManagementException, + WindowsConfigurationException { + int msgId; + int sessionId; + String user; + String token; + String response; + SyncmlDocument syncmlDocument; + List deviceInfoOperations; + List pendingOperations; + OperationUtils operationUtils = new OperationUtils(); + DeviceInfo deviceInfo = new DeviceInfo(); + + try { + if (SyncmlParser.parseSyncmlPayload(request) != null) { + try { + syncmlDocument = SyncmlParser.parseSyncmlPayload(request); + } catch (SyncmlMessageFormatException e) { + String msg = "Error occurred due to bad syncml format."; + log.error(msg, e); + throw new SyncmlMessageFormatException(msg, e); + } + SyncmlHeader syncmlHeader = syncmlDocument.getHeader(); + sessionId = syncmlHeader.getSessionId(); + user = syncmlHeader.getSource().getLocName(); + DeviceIdentifier deviceIdentifier = convertToDeviceIdentifierObject(syncmlHeader.getSource() + .getLocURI()); + msgId = syncmlHeader.getMsgID(); + if ((PluginConstants.SyncML.SYNCML_FIRST_MESSAGE_ID == msgId) && + (PluginConstants.SyncML.SYNCML_FIRST_SESSION_ID == sessionId)) { + token = syncmlHeader.getCredential().getData(); + CacheEntry cacheToken = (CacheEntry) DeviceUtil.getCacheEntry(token); + + if (cacheToken.getUsername().equals(user)) { + + if (enrollDevice(request)) { + deviceInfoOperations = deviceInfo.getDeviceInfo(); + try { + response = generateReply(syncmlDocument, deviceInfoOperations); + PolicyManagerService policyManagerService = WindowsAPIUtils.getPolicyManagerService(); + policyManagerService.getEffectivePolicy(deviceIdentifier); + return Response.status(Response.Status.OK).entity(response).build(); + } catch (PolicyManagementException e) { + String msg = "Error occurred in while getting effective policy."; + log.error(msg, e); + throw new WindowsConfigurationException(msg, e); + } catch (SyncmlOperationException e) { + String msg = "Error occurred in while generating hash value."; + log.error(msg, e); + throw new WindowsOperationException(msg, e); + } + + } else { + String msg = "Error occurred in device enrollment."; + log.error(msg); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(msg).build(); + } + } else { + String msg = "Authentication failure due to incorrect credentials."; + log.error(msg); + return Response.status(Response.Status.UNAUTHORIZED).entity(msg).build(); + } + } else if (PluginConstants.SyncML.SYNCML_SECOND_MESSAGE_ID == msgId && + PluginConstants.SyncML.SYNCML_FIRST_SESSION_ID == sessionId) { + + if (enrollDevice(request)) { + try { + return Response.ok().entity(generateReply(syncmlDocument, null)).build(); + } catch (SyncmlOperationException e) { + String msg = "Error occurred in while getting effective feature"; + log.error(msg, e); + throw new WindowsOperationException(msg, e); + } + } else { + String msg = "Error occurred in modify enrollment."; + log.error(msg); + return Response.status(Response.Status.NOT_MODIFIED).entity(msg).build(); + } + } else if (sessionId >= PluginConstants.SyncML.SYNCML_SECOND_SESSION_ID) { + if ((syncmlDocument.getBody().getAlert() != null)) { + if (!syncmlDocument.getBody().getAlert().getData().equals(Constants.DISENROLL_ALERT_DATA)) { + try { + pendingOperations = operationUtils.getPendingOperations(syncmlDocument); + return Response.ok().entity(generateReply(syncmlDocument, pendingOperations)).build(); + } catch (OperationManagementException e) { + String msg = "Cannot access operation management service."; + log.error(msg, e); + throw new WindowsOperationException(msg, e); + } catch (DeviceManagementException e) { + String msg = "Cannot access Device management service."; + log.error(msg, e); + throw new WindowsOperationException(msg, e); + } catch (FeatureManagementException e) { + String msg = "Error occurred in getting effective features. "; + log.error(msg, e); + throw new WindowsOperationException(msg, e); + } catch (PolicyComplianceException e) { + String msg = "Error occurred in setting policy compliance."; + log.error(msg, e); + throw new WindowsConfigurationException(msg, e); + } catch (NotificationManagementException e) { + String msg = "Error occurred in while getting notification service"; + throw new WindowsOperationException(msg, e); + } catch (SyncmlOperationException e) { + String msg = "Error occurred in while encoding hash value."; + log.error(msg, e); + throw new WindowsOperationException(msg, e); + } + } else { + try { + if (WindowsAPIUtils.getDeviceManagementService().getDevice(deviceIdentifier) != null) { + WindowsAPIUtils.getDeviceManagementService().disenrollDevice(deviceIdentifier); + return Response.ok().entity(generateReply(syncmlDocument, null)).build(); + } else { + String msg = "Enrolled device can not be found in the server."; + log.error(msg); + return Response.status(Response.Status.NOT_FOUND).entity(msg).build(); + } + } catch (DeviceManagementException e) { + String msg = "Failure occurred in dis-enrollment flow."; + log.error(msg, e); + throw new WindowsOperationException(msg, e); + } catch (SyncmlOperationException e) { + String msg = "Error occurred in while generating hash value."; + log.error(msg, e); + throw new WindowsOperationException(msg, e); + } + } + } else { + try { + pendingOperations = operationUtils.getPendingOperations(syncmlDocument); + return Response.ok().entity(generateReply(syncmlDocument, pendingOperations)) + .build(); + } catch (OperationManagementException e) { + String msg = "Cannot access operation management service."; + log.error(msg, e); + throw new WindowsOperationException(msg, e); + } catch (DeviceManagementException e) { + String msg = "Cannot access Device management service."; + log.error(msg, e); + throw new WindowsOperationException(msg, e); + } catch (FeatureManagementException e) { + String msg = "Error occurred in getting effective features. "; + log.error(msg, e); + throw new WindowsConfigurationException(msg, e); + } catch (PolicyComplianceException e) { + String msg = "Error occurred in setting policy compliance."; + log.error(msg, e); + throw new WindowsConfigurationException(msg, e); + } catch (NotificationManagementException e) { + String msg = "Error occurred in while getting notification service."; + log.error(msg, e); + throw new WindowsOperationException(msg, e); + } catch (SyncmlOperationException e) { + String msg = "Error occurred in while getting effective feature."; + log.error(msg, e); + throw new WindowsConfigurationException(msg, e); + } + } + } else { + String msg = "Failure occurred in Device request message."; + log.error(msg); + return Response.status(Response.Status.BAD_REQUEST).entity(msg).build(); + } + } + } catch (SyncmlMessageFormatException e) { + String msg = "Error occurred in parsing syncml request."; + log.error(msg, e); + throw new WindowsOperationException(msg, e); + } + return null; + } + + /** + * Enroll phone device + * + * @param request Device syncml request for the server side. + * @return enroll state + * @throws WindowsDeviceEnrolmentException + * @throws WindowsOperationException + */ + private boolean enrollDevice(Document request) throws WindowsDeviceEnrolmentException, + WindowsOperationException { + + String osVersion; + String imsi = null; + String imei = null; + String devID; + String devMan; + String devMod; + String devLang; + String vender; + String macAddress; + String resolution; + String modVersion; + boolean status = false; + String user; + String deviceName; + int msgID; + SyncmlDocument syncmlDocument; + + try { + syncmlDocument = SyncmlParser.parseSyncmlPayload(request); + msgID = syncmlDocument.getHeader().getMsgID(); + if (msgID == PluginConstants.SyncML.SYNCML_FIRST_MESSAGE_ID) { + Replace replace = syncmlDocument.getBody().getReplace(); + List itemList = replace.getItems(); + devID = itemList.get(PluginConstants.SyncML.DEVICE_ID_POSITION).getData(); + devMan = itemList.get(PluginConstants.SyncML.DEVICE_MAN_POSITION).getData(); + devMod = itemList.get(PluginConstants.SyncML.DEVICE_MODEL_POSITION).getData(); + modVersion = itemList.get(PluginConstants.SyncML.DEVICE_MOD_VER_POSITION).getData(); + devLang = itemList.get(PluginConstants.SyncML.DEVICE_LANG_POSITION).getData(); + user = syncmlDocument.getHeader().getSource().getLocName(); + + if (log.isDebugEnabled()) { + log.debug( + "OS Version:" + modVersion + ", DevID: " + devID + ", DevMan: " + devMan + + ", DevMod: " + devMod + ", DevLang: " + devLang); + } + Device generateDevice = generateDevice(DeviceManagementConstants.MobileDeviceTypes. + MOBILE_DEVICE_TYPE_WINDOWS, devID, modVersion, imsi, imei, devMan, devMod, user); + status = WindowsAPIUtils.getDeviceManagementService().enrollDevice(generateDevice); + WindowsAPIUtils.startTenantFlow(user); + return status; + + } else if (msgID == PluginConstants.SyncML.SYNCML_SECOND_MESSAGE_ID) { + List itemList = syncmlDocument.getBody().getResults().getItem(); + osVersion = itemList.get(PluginConstants.SyncML.OSVERSION_POSITION).getData(); + imsi = itemList.get(PluginConstants.SyncML.IMSI_POSITION).getData(); + imei = itemList.get(PluginConstants.SyncML.IMEI_POSITION).getData(); + vender = itemList.get(PluginConstants.SyncML.VENDER_POSITION).getData(); + devMod = itemList.get(PluginConstants.SyncML.MODEL_POSITION).getData(); + macAddress = itemList.get(PluginConstants.SyncML.MACADDRESS_POSITION).getData(); + resolution = itemList.get(PluginConstants.SyncML.RESOLUTION_POSITION).getData(); + deviceName = itemList.get(PluginConstants.SyncML.DEVICE_NAME_POSITION).getData(); + DeviceIdentifier deviceIdentifier = convertToDeviceIdentifierObject(syncmlDocument. + getHeader().getSource().getLocURI()); + Device existingDevice = WindowsAPIUtils.getDeviceManagementService().getDevice(deviceIdentifier); + + if (!existingDevice.getProperties().isEmpty()) { + List existingProperties = new ArrayList<>(); + + Device.Property imeiProperty = new Device.Property(); + imeiProperty.setName(PluginConstants.SyncML.IMEI); + imeiProperty.setValue(imei); + existingProperties.add(imeiProperty); + + Device.Property osVersionProperty = new Device.Property(); + osVersionProperty.setName(PluginConstants.SyncML.OS_VERSION); + osVersionProperty.setValue(osVersion); + existingProperties.add(osVersionProperty); + + Device.Property imsiProperty = new Device.Property(); + imsiProperty.setName(PluginConstants.SyncML.IMSI); + imsiProperty.setValue(imsi); + existingProperties.add(imsiProperty); + + Device.Property venderProperty = new Device.Property(); + venderProperty.setName(PluginConstants.SyncML.VENDOR); + venderProperty.setValue(vender); + existingProperties.add(venderProperty); + + Device.Property macAddressProperty = new Device.Property(); + macAddressProperty.setName(PluginConstants.SyncML.MAC_ADDRESS); + macAddressProperty.setValue(macAddress); + existingProperties.add(macAddressProperty); + + Device.Property resolutionProperty = new Device.Property(); + resolutionProperty.setName(PluginConstants.SyncML.DEVICE_INFO); + resolutionProperty.setValue(resolution); + existingProperties.add(resolutionProperty); + + Device.Property deviceNameProperty = new Device.Property(); + deviceNameProperty.setName(PluginConstants.SyncML.DEVICE_NAME); + deviceNameProperty.setValue(deviceName); + existingProperties.add(deviceNameProperty); + + Device.Property deviceModelProperty = new Device.Property(); + deviceNameProperty.setName(PluginConstants.SyncML.MODEL); + deviceNameProperty.setValue(devMod); + existingProperties.add(deviceModelProperty); + + existingDevice.setProperties(existingProperties); + existingDevice.setDeviceIdentifier(syncmlDocument.getHeader().getSource().getLocURI()); + existingDevice.setType(DeviceManagementConstants.MobileDeviceTypes.MOBILE_DEVICE_TYPE_WINDOWS); + status = WindowsAPIUtils.getDeviceManagementService().modifyEnrollment(existingDevice); + // call effective policy for the enrolling device. + PolicyManagerService policyManagerService = WindowsAPIUtils.getPolicyManagerService(); + policyManagerService.getEffectivePolicy(deviceIdentifier); + return status; + } + } + } catch (DeviceManagementException e) { + String msg = "Failure occurred in enrolling device."; + log.error(msg, e); + throw new WindowsDeviceEnrolmentException(msg, e); + } catch (SyncmlMessageFormatException e) { + String msg = "Error occurred in bad format of the syncml payload."; + log.error(msg, e); + throw new WindowsOperationException(msg, e); + } catch (PolicyManagementException e) { + String msg = "Error occurred in getting effective policy."; + log.error(msg, e); + throw new WindowsOperationException(msg, e); + } finally { + PrivilegedCarbonContext.endTenantFlow(); + } + return status; + } + + /** + * Generate Device payloads. + * + * @param syncmlDocument parsed syncml payload from the syncml engine. + * @param operations operations for generate payload. + * @return String type syncml payload. + * @throws WindowsOperationException + * @throws JSONException + * @throws PolicyManagementException + * @throws org.wso2.carbon.policy.mgt.common.FeatureManagementException + */ + public String generateReply(SyncmlDocument syncmlDocument, List operations) + throws SyncmlMessageFormatException, SyncmlOperationException { + + OperationReply operationReply; + SyncmlGenerator generator; + SyncmlDocument syncmlResponse; + if (operations == null) { + operationReply = new OperationReply(syncmlDocument); + } else { + operationReply = new OperationReply(syncmlDocument, operations); + } + syncmlResponse = operationReply.generateReply(); + generator = new SyncmlGenerator(); + return generator.generatePayload(syncmlResponse); + } +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/syncml/util/PolicyManager.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/syncml/util/PolicyManager.java new file mode 100644 index 0000000000..403b44f88d --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/syncml/util/PolicyManager.java @@ -0,0 +1,54 @@ +/* + * 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.wso2.carbon.mdm.mobileservices.windows.services.syncml.util; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.device.mgt.common.DeviceIdentifier; +import org.wso2.carbon.mdm.mobileservices.windows.common.exceptions.WindowsConfigurationException; +import org.wso2.carbon.mdm.mobileservices.windows.common.util.WindowsAPIUtils; +import org.wso2.carbon.policy.mgt.common.Policy; +import org.wso2.carbon.policy.mgt.common.PolicyManagementException; +import org.wso2.carbon.policy.mgt.core.PolicyManagerService; + +/** + * implementation for PolicyManager + */ +public class PolicyManager { + + private static Log log = LogFactory.getLog(PolicyManager.class); + + public Policy getEffectivePolicy(DeviceIdentifier deviceIdentifier) throws WindowsConfigurationException { + Policy policy; + PolicyManagerService policyManagerService = WindowsAPIUtils.getPolicyManagerService(); + try { + policy = policyManagerService.getEffectivePolicy(deviceIdentifier); + if (policy != null) { + return policy; + } else { + return null; + } + } catch (PolicyManagementException e) { + String msg = "Error occurred while getting policy."; + log.error(msg, e); + throw new WindowsConfigurationException(); + + } + } +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/syncml/util/SyncmlUtils.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/syncml/util/SyncmlUtils.java new file mode 100644 index 0000000000..90275b3cd1 --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/syncml/util/SyncmlUtils.java @@ -0,0 +1,49 @@ +/* + * 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.wso2.carbon.mdm.mobileservices.windows.services.syncml.util; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.context.PrivilegedCarbonContext; +import org.wso2.carbon.device.mgt.core.service.DeviceManagementProviderService; + +/** + * Class for generate Device object from the received data. + */ +public class SyncmlUtils { + + private static Log log = LogFactory.getLog(SyncmlUtils.class); + + /** + * This method returns Device Management Object for certain tasks such as Device enrollment etc. + * + * @return DeviceManagementServiceObject + */ + public static DeviceManagementProviderService getDeviceManagementService() { + try { + PrivilegedCarbonContext context = PrivilegedCarbonContext.getThreadLocalCarbonContext(); + + return (DeviceManagementProviderService) context.getOSGiService(DeviceManagementProviderService.class, + null); + } finally { + PrivilegedCarbonContext.endTenantFlow(); + } + } + +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/wstep/CertificateEnrollmentService.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/wstep/CertificateEnrollmentService.java new file mode 100644 index 0000000000..e517672db0 --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/wstep/CertificateEnrollmentService.java @@ -0,0 +1,62 @@ +/* + * 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.wso2.carbon.mdm.mobileservices.windows.services.wstep; + +import org.wso2.carbon.mdm.mobileservices.windows.common.PluginConstants; +import org.wso2.carbon.mdm.mobileservices.windows.common.exceptions.WAPProvisioningException; +import org.wso2.carbon.mdm.mobileservices.windows.common.exceptions.WindowsDeviceEnrolmentException; +import org.wso2.carbon.mdm.mobileservices.windows.services.wstep.beans.AdditionalContext; +import org.wso2.carbon.mdm.mobileservices.windows.services.wstep.beans.RequestSecurityTokenResponse; + +import javax.jws.WebMethod; +import javax.jws.WebParam; +import javax.jws.WebService; +import javax.xml.ws.BindingType; +import javax.xml.ws.RequestWrapper; +import javax.xml.ws.ResponseWrapper; +import javax.xml.ws.soap.SOAPBinding; +import java.io.UnsupportedEncodingException; + +/** + * Interface of WSTEP implementation. + */ +@WebService(targetNamespace = PluginConstants.DEVICE_ENROLLMENT_SERVICE_TARGET_NAMESPACE, name = "wstep") +@BindingType(value = SOAPBinding.SOAP12HTTP_BINDING) +public interface CertificateEnrollmentService { + + @RequestWrapper(localName = "RequestSecurityToken", targetNamespace = PluginConstants + .WS_TRUST_TARGET_NAMESPACE) + @WebMethod(operationName = "RequestSecurityToken") + @ResponseWrapper(localName = "RequestSecurityTokenResponseCollection", targetNamespace = + PluginConstants.WS_TRUST_TARGET_NAMESPACE) + public void requestSecurityToken( + @WebParam(name = "TokenType", targetNamespace = PluginConstants.WS_TRUST_TARGET_NAMESPACE) + String tokenType, + @WebParam(name = "RequestType", targetNamespace = PluginConstants.WS_TRUST_TARGET_NAMESPACE) + String requestType, + @WebParam(name = "BinarySecurityToken", targetNamespace = PluginConstants + .WS_SECURITY_TARGET_NAMESPACE) + String binarySecurityToken, + @WebParam(name = "AdditionalContext", targetNamespace = PluginConstants + .SOAP_AUTHORIZATION_TARGET_NAMESPACE) + AdditionalContext additionalContext, + @WebParam(mode = WebParam.Mode.OUT, name = "RequestSecurityTokenResponse", + targetNamespace = PluginConstants.WS_TRUST_TARGET_NAMESPACE) + javax.xml.ws.Holder response) throws + WindowsDeviceEnrolmentException, UnsupportedEncodingException, WAPProvisioningException; +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/wstep/beans/AdditionalContext.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/wstep/beans/AdditionalContext.java new file mode 100644 index 0000000000..8850d5afd3 --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/wstep/beans/AdditionalContext.java @@ -0,0 +1,48 @@ +/* + * 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.wso2.carbon.mdm.mobileservices.windows.services.wstep.beans; + +import org.wso2.carbon.mdm.mobileservices.windows.common.PluginConstants; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlType; +import java.util.ArrayList; +import java.util.List; + +@XmlAccessorType(XmlAccessType.FIELD) +@XmlType(name = "OIDCollection", namespace = PluginConstants.SOAP_AUTHORIZATION_TARGET_NAMESPACE, + propOrder = {"contextitem"}) +@SuppressWarnings("unused") +public class AdditionalContext { + + @XmlElement(name = "ContextItem", required = true, + namespace = PluginConstants.SOAP_AUTHORIZATION_TARGET_NAMESPACE) + + protected List contextitem; + + public List getcontextitem() { + if (contextitem == null) { + contextitem = new ArrayList(); + } + return this.contextitem; + } +} + diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/wstep/beans/BinarySecurityToken.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/wstep/beans/BinarySecurityToken.java new file mode 100644 index 0000000000..544c4cb79f --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/wstep/beans/BinarySecurityToken.java @@ -0,0 +1,67 @@ +/* + * 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.wso2.carbon.mdm.mobileservices.windows.services.wstep.beans; + +import org.wso2.carbon.mdm.mobileservices.windows.common.PluginConstants; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlAttribute; +import javax.xml.bind.annotation.XmlType; +import javax.xml.bind.annotation.XmlValue; + +@XmlAccessorType(XmlAccessType.FIELD) +@XmlType(name = "BinarySecurityToken", namespace = PluginConstants.WS_SECURITY_TARGET_NAMESPACE, + propOrder = {"ValueType", "EncodingType"}) +@SuppressWarnings("unused") +public class BinarySecurityToken { + + @XmlAttribute(name = "ValueType") + protected String ValueType; + @XmlAttribute(name = "EncodingType") + protected String EncodingType; + @XmlValue + protected String Token; + + public void setValueType(String valuetype) { + this.ValueType = valuetype; + } + + public String getValueType() { + return this.ValueType; + } + + public void setEncodingType(String encodingtype) { + this.EncodingType = encodingtype; + } + + public String getEncodingType() { + return this.EncodingType; + } + + public void setToken(String token) { + this.Token = token; + } + + public String getToken() { + return this.Token; + } + +} + diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/wstep/beans/ContextItem.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/wstep/beans/ContextItem.java new file mode 100644 index 0000000000..bfc450e55c --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/wstep/beans/ContextItem.java @@ -0,0 +1,44 @@ +/* + * 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.wso2.carbon.mdm.mobileservices.windows.services.wstep.beans; + +import org.wso2.carbon.mdm.mobileservices.windows.common.PluginConstants; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlType; + +@XmlAccessorType(XmlAccessType.FIELD) +@XmlType(name = "ContextItem", namespace = PluginConstants.SOAP_AUTHORIZATION_TARGET_NAMESPACE, + propOrder = {"Value"}) +public class ContextItem { + + @XmlElement(required = true, namespace = PluginConstants.SOAP_AUTHORIZATION_TARGET_NAMESPACE) + protected String Value; + + public String getValue() { + return Value; + } + + public void setValue(String value) { + Value = value; + } + +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/wstep/beans/RequestSecurityToken.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/wstep/beans/RequestSecurityToken.java new file mode 100644 index 0000000000..74be950d16 --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/wstep/beans/RequestSecurityToken.java @@ -0,0 +1,42 @@ +/* + * 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.wso2.carbon.mdm.mobileservices.windows.services.wstep.beans; + +import org.wso2.carbon.mdm.mobileservices.windows.common.PluginConstants; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlType; + +@XmlAccessorType(XmlAccessType.FIELD) +@XmlType(name = "RequestedSecurityToken", namespace = PluginConstants.WS_TRUST_TARGET_NAMESPACE, + propOrder = {"binarySecurityToken"}) +@SuppressWarnings("unused") +public class RequestSecurityToken { + + @XmlElement(name = "BinarySecurityToken", required = true, + namespace = PluginConstants.WS_SECURITY_TARGET_NAMESPACE) + + protected BinarySecurityToken binarySecurityToken; + + public void setBinarySecurityToken(BinarySecurityToken binarysecuritytoken) { + this.binarySecurityToken = binarysecuritytoken; + } +} \ No newline at end of file diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/wstep/beans/RequestSecurityTokenResponse.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/wstep/beans/RequestSecurityTokenResponse.java new file mode 100644 index 0000000000..174cb0bc00 --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/wstep/beans/RequestSecurityTokenResponse.java @@ -0,0 +1,68 @@ +/* + * 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.wso2.carbon.mdm.mobileservices.windows.services.wstep.beans; + +import org.wso2.carbon.mdm.mobileservices.windows.common.PluginConstants; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlType; +import java.io.Serializable; + +@XmlAccessorType(XmlAccessType.FIELD) +@XmlType(name = "RequestSecurityTokenResponse", namespace = PluginConstants.WS_TRUST_TARGET_NAMESPACE, + propOrder = {"TokenType", "RequestedSecurityToken", "RequestID"}) +@SuppressWarnings("unused") +public class RequestSecurityTokenResponse implements Serializable { + + @XmlElement(name = "TokenType", namespace = PluginConstants.WS_TRUST_TARGET_NAMESPACE) + private String TokenType; + + @XmlElement(name = "RequestedSecurityToken", required = true, + namespace = PluginConstants.WS_TRUST_TARGET_NAMESPACE) + private RequestedSecurityToken RequestedSecurityToken; + + @XmlElement(name = "RequestID", namespace = PluginConstants.ENROLLMENT_POLICY_TARGET_NAMESPACE) + private int RequestID; + + public String getTokenType() { + return TokenType; + } + + public void setTokenType(String tokenType) { + TokenType = tokenType; + } + + public RequestedSecurityToken getRequestedSecurityToken() { + return RequestedSecurityToken; + } + + public void setRequestedSecurityToken(RequestedSecurityToken requestedSecurityToken) { + RequestedSecurityToken = requestedSecurityToken; + } + + public int getRequestID() { + return RequestID; + } + + public void setRequestID(int requestID) { + RequestID = requestID; + } +} \ No newline at end of file diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/wstep/beans/RequestedSecurityToken.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/wstep/beans/RequestedSecurityToken.java new file mode 100644 index 0000000000..c448c14ce5 --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/wstep/beans/RequestedSecurityToken.java @@ -0,0 +1,42 @@ +/* + * 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.wso2.carbon.mdm.mobileservices.windows.services.wstep.beans; + +import org.wso2.carbon.mdm.mobileservices.windows.common.PluginConstants; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlType; + +@XmlAccessorType(XmlAccessType.FIELD) +@XmlType(name = "RequestedSecurityToken", namespace = PluginConstants.WS_TRUST_TARGET_NAMESPACE, + propOrder = {"binarySecurityToken"}) +public class RequestedSecurityToken { + + @XmlElement(name = "BinarySecurityToken", required = true, + namespace = PluginConstants.WS_SECURITY_TARGET_NAMESPACE) + + protected BinarySecurityToken binarySecurityToken; + + public void setBinarySecurityToken(BinarySecurityToken binarysecuritytoken) { + this.binarySecurityToken = binarysecuritytoken; + } +} + diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/wstep/beans/package-info.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/wstep/beans/package-info.java new file mode 100644 index 0000000000..47dd029497 --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/wstep/beans/package-info.java @@ -0,0 +1,27 @@ +/* + * 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. + */ + +@XmlSchema(namespace = "http://www.w3.org/2003/05/soap-envelope", + xmlns = { + @XmlNs(prefix = "", namespaceURI = "http://www.w3.org/2003/05/soap-envelope") + }, elementFormDefault = javax.xml.bind.annotation.XmlNsForm.QUALIFIED) + +package org.wso2.carbon.mdm.mobileservices.windows.services.wstep.beans; + +import javax.xml.bind.annotation.XmlNs; +import javax.xml.bind.annotation.XmlSchema; \ No newline at end of file diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/wstep/impl/CertificateEnrollmentServiceImpl.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/wstep/impl/CertificateEnrollmentServiceImpl.java new file mode 100644 index 0000000000..f88600b78f --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/wstep/impl/CertificateEnrollmentServiceImpl.java @@ -0,0 +1,381 @@ +/* + * 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.wso2.carbon.mdm.mobileservices.windows.services.wstep.impl; + +import org.apache.commons.codec.binary.Base64; +import org.apache.commons.codec.digest.DigestUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.cxf.headers.Header; +import org.apache.cxf.helpers.CastUtils; +import org.apache.cxf.jaxws.context.WrappedMessageContext; +import org.apache.cxf.message.Message; +import org.w3c.dom.*; +import org.wso2.carbon.certificate.mgt.core.exception.KeystoreException; +import org.wso2.carbon.certificate.mgt.core.service.CertificateManagementServiceImpl; +import org.wso2.carbon.context.PrivilegedCarbonContext; +import org.wso2.carbon.device.mgt.common.DeviceManagementException; +import org.wso2.carbon.device.mgt.common.configuration.mgt.ConfigurationEntry; +import org.wso2.carbon.device.mgt.common.configuration.mgt.TenantConfiguration; +import org.wso2.carbon.mdm.mobileservices.windows.common.PluginConstants; +import org.wso2.carbon.mdm.mobileservices.windows.common.beans.CacheEntry; +import org.wso2.carbon.mdm.mobileservices.windows.common.exceptions.CertificateGenerationException; +import org.wso2.carbon.mdm.mobileservices.windows.common.exceptions.SyncmlMessageFormatException; +import org.wso2.carbon.mdm.mobileservices.windows.common.exceptions.WAPProvisioningException; +import org.wso2.carbon.mdm.mobileservices.windows.common.exceptions.WindowsDeviceEnrolmentException; +import org.wso2.carbon.mdm.mobileservices.windows.common.util.DeviceUtil; +import org.wso2.carbon.mdm.mobileservices.windows.common.util.WindowsAPIUtils; +import org.wso2.carbon.mdm.mobileservices.windows.operations.util.SyncmlCredentials; +import org.wso2.carbon.mdm.mobileservices.windows.services.wstep.CertificateEnrollmentService; +import org.wso2.carbon.mdm.mobileservices.windows.services.wstep.beans.AdditionalContext; +import org.wso2.carbon.mdm.mobileservices.windows.services.wstep.beans.BinarySecurityToken; +import org.wso2.carbon.mdm.mobileservices.windows.services.wstep.beans.RequestSecurityTokenResponse; +import org.wso2.carbon.mdm.mobileservices.windows.services.wstep.beans.RequestedSecurityToken; +import org.xml.sax.SAXException; + +import javax.annotation.Resource; +import javax.jws.WebService; +import javax.servlet.ServletContext; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; +import javax.xml.ws.BindingType; +import javax.xml.ws.Holder; +import javax.xml.ws.WebServiceContext; +import javax.xml.ws.handler.MessageContext; +import javax.xml.ws.soap.Addressing; +import javax.xml.ws.soap.SOAPBinding; +import java.io.File; +import java.io.IOException; +import java.io.StringWriter; +import java.io.UnsupportedEncodingException; +import java.security.cert.CertificateEncodingException; +import java.security.cert.X509Certificate; +import java.util.List; + +/** + * Implementation class of CertificateEnrollmentService interface. This class implements MS-WSTEP + * protocol. + */ +@WebService(endpointInterface = PluginConstants.CERTIFICATE_ENROLLMENT_SERVICE_ENDPOINT, + targetNamespace = PluginConstants.DEVICE_ENROLLMENT_SERVICE_TARGET_NAMESPACE) +@Addressing(enabled = true, required = true) +@BindingType(value = SOAPBinding.SOAP12HTTP_BINDING) +public class CertificateEnrollmentServiceImpl implements CertificateEnrollmentService { + + private static final int REQUEST_ID = 0; + private static final int CA_CERTIFICATE_POSITION = 0; + private static final int SIGNED_CERTIFICATE_POSITION = 1; + private static final int APPAUTH_USERNAME_POSITION = 21; + private static final int APPAUTH_PASSWORD_POSITION = 22; + private static final int POLLING_FREQUENCY_POSITION = 27; + private static Log log = LogFactory.getLog(CertificateEnrollmentServiceImpl.class); + private X509Certificate rootCACertificate; + private String pollingFrequency; + + @Resource + private WebServiceContext context; + + /** + * This method implements MS-WSTEP for Certificate Enrollment Service. + * + * @param tokenType - Device Enrolment Token type is received via device + * @param requestType - WS-Trust request type + * @param binarySecurityToken - CSR from device + * @param additionalContext - Device type and OS version is received + * @param response - Response will include wap-provisioning xml + */ + @Override + public void requestSecurityToken(String tokenType, String requestType, + String binarySecurityToken, + AdditionalContext additionalContext, + Holder response) throws + WindowsDeviceEnrolmentException, UnsupportedEncodingException, WAPProvisioningException { + + String headerBinarySecurityToken = null; + List
      headers = getHeaders(); + for (Header headerElement : headers != null ? headers : null) { + String nodeName = headerElement.getName().getLocalPart(); + if (nodeName.equals(PluginConstants.SECURITY)) { + Element element = (Element) headerElement.getObject(); + headerBinarySecurityToken = element.getFirstChild().getNextSibling().getFirstChild().getTextContent(); + } + } + List tenantConfigurations = null; + try { + if (getTenantConfigurationData() != null) { + tenantConfigurations = getTenantConfigurationData(); + for (ConfigurationEntry configurationEntry : tenantConfigurations) { + if (configurationEntry.getName().equals(PluginConstants.TenantConfigProperties. + NOTIFIER_FREQUENCY)) { + pollingFrequency = configurationEntry.getValue().toString(); + } else { + pollingFrequency = PluginConstants.TenantConfigProperties.DEFAULT_FREQUENCY; + } + } + } else { + pollingFrequency = PluginConstants.TenantConfigProperties.DEFAULT_FREQUENCY; + String msg = "Tenant configurations are not initialized yet."; + log.error(msg); + } + } catch (DeviceManagementException e) { + String msg = "Error occurred in while getting tenant configurations."; + log.error(msg); + throw new WindowsDeviceEnrolmentException(msg, e); + } + ServletContext ctx = + (ServletContext) context.getMessageContext().get(MessageContext.SERVLET_CONTEXT); + File wapProvisioningFile = (File) ctx.getAttribute(PluginConstants.CONTEXT_WAP_PROVISIONING_FILE); + + if (log.isDebugEnabled()) { + log.debug("Received CSR from Device:" + binarySecurityToken); + } + String wapProvisioningFilePath = wapProvisioningFile.getPath(); + RequestSecurityTokenResponse requestSecurityTokenResponse = + new RequestSecurityTokenResponse(); + requestSecurityTokenResponse.setTokenType(PluginConstants.CertificateEnrolment.TOKEN_TYPE); + String encodedWap; + try { + encodedWap = prepareWapProvisioningXML(binarySecurityToken, + wapProvisioningFilePath, headerBinarySecurityToken); + RequestedSecurityToken requestedSecurityToken = new RequestedSecurityToken(); + BinarySecurityToken binarySecToken = new BinarySecurityToken(); + binarySecToken.setValueType(PluginConstants.CertificateEnrolment.VALUE_TYPE); + binarySecToken.setEncodingType(PluginConstants.CertificateEnrolment.ENCODING_TYPE); + binarySecToken.setToken(encodedWap); + requestedSecurityToken.setBinarySecurityToken(binarySecToken); + requestSecurityTokenResponse.setRequestedSecurityToken(requestedSecurityToken); + requestSecurityTokenResponse.setRequestID(REQUEST_ID); + response.value = requestSecurityTokenResponse; + } catch (CertificateGenerationException e) { + String msg = "Problem occurred in generating certificate."; + log.error(msg, e); + throw new WindowsDeviceEnrolmentException(msg, e); + } catch (WAPProvisioningException e) { + String msg = "Problem occurred in generating wap-provisioning file."; + log.error(msg, e); + throw new WAPProvisioningException(msg, e); + } finally { + PrivilegedCarbonContext.endTenantFlow(); + } + } + + /** + * Method used to Convert the Document object into a String. + * + * @param document - Wap provisioning XML document + * @return - String representation of wap provisioning XML document + * @throws TransformerException + */ + private String convertDocumentToString(Document document) throws TransformerException { + DOMSource DOMSource = new DOMSource(document); + StringWriter stringWriter = new StringWriter(); + StreamResult streamResult = new StreamResult(stringWriter); + TransformerFactory transformerFactory = TransformerFactory.newInstance(); + Transformer transformer = transformerFactory.newTransformer(); + transformer.transform(DOMSource, streamResult); + + return stringWriter.toString(); + } + + /** + * This method prepares the wap-provisioning file by including relevant certificates etc + * + * @param binarySecurityToken - CSR from device + * @param wapProvisioningFilePath - File path of wap-provisioning file + * @return - base64 encoded final wap-provisioning file as a String + * @throws CertificateGenerationException + * @throws org.wso2.carbon.mdm.mobileservices.windows.common.exceptions.WAPProvisioningException + */ + public String prepareWapProvisioningXML( + String binarySecurityToken, + String wapProvisioningFilePath, String headerBst) throws CertificateGenerationException, + WAPProvisioningException, WindowsDeviceEnrolmentException { + + String rootCertEncodedString; + String signedCertEncodedString; + X509Certificate signedCertificate; + + CertificateManagementServiceImpl impl = CertificateManagementServiceImpl.getInstance(); + Base64 base64Encoder = new Base64(); + try { + rootCACertificate = (X509Certificate) impl.getCACertificate(); + rootCertEncodedString = base64Encoder.encodeToString(rootCACertificate.getEncoded()); + } catch (KeystoreException e) { + String msg = "CA certificate cannot be generated"; + log.error(msg, e); + throw new CertificateGenerationException(msg, e); + } catch (CertificateEncodingException e) { + String msg = "CA certificate cannot be encoded."; + log.error(msg, e); + throw new CertificateGenerationException(msg, e); + } + + try { + signedCertificate = impl.getSignedCertificateFromCSR(binarySecurityToken); + signedCertEncodedString = base64Encoder.encodeToString(signedCertificate.getEncoded()); + } catch (CertificateEncodingException e) { + String msg = "Singed certificate cannot be encoded."; + log.error(msg, e); + throw new CertificateGenerationException(msg, e); + } catch (KeystoreException e) { + String msg = "CA certificate cannot be generated"; + log.error(msg, e); + throw new CertificateGenerationException(msg, e); + } + DocumentBuilderFactory domFactory = DocumentBuilderFactory.newInstance(); + DocumentBuilder builder; + String wapProvisioningString = null; + try { + builder = domFactory.newDocumentBuilder(); + + Document document = builder.parse(wapProvisioningFilePath); + NodeList wapParm = document.getElementsByTagName(PluginConstants.CertificateEnrolment.PARM); + Node caCertificatePosition = wapParm.item(CA_CERTIFICATE_POSITION); + + //Adding SHA1 CA certificate finger print to wap-provisioning xml. + caCertificatePosition.getParentNode().getAttributes().getNamedItem(PluginConstants. + CertificateEnrolment.TYPE).setTextContent(String.valueOf( + DigestUtils.sha512Hex(rootCACertificate.getEncoded())).toUpperCase()); + //Adding encoded CA certificate to wap-provisioning file after removing new line + // characters. + NamedNodeMap rootCertAttributes = caCertificatePosition.getAttributes(); + Node rootCertNode = + rootCertAttributes.getNamedItem(PluginConstants.CertificateEnrolment.VALUE); + rootCertEncodedString = rootCertEncodedString.replaceAll("\n", ""); + rootCertNode.setTextContent(rootCertEncodedString); + + if (log.isDebugEnabled()) { + log.debug("Root certificate: " + rootCertEncodedString); + } + + Node signedCertificatePosition = wapParm.item(SIGNED_CERTIFICATE_POSITION); + + //Adding SHA1 signed certificate finger print to wap-provisioning xml. + signedCertificatePosition.getParentNode().getAttributes().getNamedItem(PluginConstants. + CertificateEnrolment.TYPE).setTextContent(String.valueOf( + DigestUtils.sha512Hex(signedCertificate.getEncoded())).toUpperCase()); + + //Adding encoded signed certificate to wap-provisioning file after removing new line + // characters. + NamedNodeMap clientCertAttributes = signedCertificatePosition.getAttributes(); + Node clientEncodedNode = + clientCertAttributes.getNamedItem(PluginConstants.CertificateEnrolment.VALUE); + signedCertEncodedString = signedCertEncodedString.replaceAll("\n", ""); + + clientEncodedNode.setTextContent(signedCertEncodedString); + if (log.isDebugEnabled()) { + log.debug("Signed certificate: " + signedCertEncodedString); + } + + // Adding user name auth token to wap-provisioning xml + Node userNameAuthPosition = wapParm.item(APPAUTH_USERNAME_POSITION); + NamedNodeMap appServerAttribute = userNameAuthPosition.getAttributes(); + Node authNameNode = appServerAttribute.getNamedItem(PluginConstants.CertificateEnrolment.VALUE); + CacheEntry cacheEntry = (CacheEntry) DeviceUtil.getCacheEntry(headerBst); + String userName = cacheEntry.getUsername(); + authNameNode.setTextContent(cacheEntry.getUsername()); + DeviceUtil.removeToken(headerBst); + String password = DeviceUtil.generateRandomToken(); + Node passwordAuthPosition = wapParm.item(APPAUTH_PASSWORD_POSITION); + NamedNodeMap appSrvPasswordAttribute = passwordAuthPosition.getAttributes(); + Node authPasswordNode = appSrvPasswordAttribute.getNamedItem(PluginConstants.CertificateEnrolment.VALUE); + authPasswordNode.setTextContent(password); + String requestSecurityTokenResponse = new SyncmlCredentials().generateRST(userName, password); + DeviceUtil.persistChallengeToken(requestSecurityTokenResponse, null, userName); + + // Get device polling frequency from the tenant Configurations. + Node numberOfFirstRetries = wapParm.item(POLLING_FREQUENCY_POSITION); + NamedNodeMap pollingAttributes = numberOfFirstRetries.getAttributes(); + Node pollValue = pollingAttributes.getNamedItem(PluginConstants.CertificateEnrolment.VALUE); + pollValue.setTextContent(pollingFrequency); + if (log.isDebugEnabled()) { + log.debug("Username: " + userName + "Password: " + requestSecurityTokenResponse); + } + wapProvisioningString = convertDocumentToString(document); + } catch (ParserConfigurationException e) { + String msg = "Problem occurred in parsing wap-provisioning.xml file."; + log.error(msg, e); + throw new WAPProvisioningException(msg, e); + } catch (DeviceManagementException e) { + String msg = "Error occurred in while getting CA and Root certificates."; + log.error(msg, e); + throw new WindowsDeviceEnrolmentException(msg, e); + } catch (CertificateEncodingException e) { + String msg = "Error occurred in while encoding certificates."; + log.error(msg, e); + throw new WindowsDeviceEnrolmentException(msg, e); + } catch (UnsupportedEncodingException e) { + String msg = "Error occurred in while encoding wap-provisioning file."; + log.error(msg, e); + throw new WindowsDeviceEnrolmentException(msg, e); + } catch (SAXException e) { + String msg = "Error occurred in while parsing wap-provisioning.xml file."; + log.error(msg, e); + throw new WAPProvisioningException(msg, e); + } catch (TransformerException e) { + String msg = "Error occurred in while transforming wap-provisioning.xml file."; + log.error(msg, e); + throw new WAPProvisioningException(msg, e); + } catch (IOException e) { + String msg = "Error occurred in while getting wap-provisioning.xml file."; + log.error(msg, e); + throw new WAPProvisioningException(msg, e); + } catch (SyncmlMessageFormatException e) { + String msg = "Error occurred in while getting CA and Root certificates."; + log.error(msg, e); + throw new WindowsDeviceEnrolmentException(msg, e); + } + return base64Encoder.encodeToString(wapProvisioningString.getBytes()); + } + + /** + * This method get the soap request header contents + * + * @return Header object type,soap header tag list + */ + private List
      getHeaders() { + MessageContext messageContext = context.getMessageContext(); + if (messageContext == null || !(messageContext instanceof WrappedMessageContext)) { + return null; + } + Message message = ((WrappedMessageContext) messageContext).getWrappedMessage(); + return CastUtils.cast((List) message.get(Header.HEADER_LIST)); + } + + /** + * This method is used to get tenant configurations. + * + * @return List of Configurations entries. + * @throws DeviceManagementException + */ + private List getTenantConfigurationData() throws DeviceManagementException { + if (WindowsAPIUtils.getTenantConfiguration() != null) { + TenantConfiguration configuration = WindowsAPIUtils.getTenantConfiguration(); + return configuration.getConfiguration(); + } else { + return null; + } + } +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/wstep/util/CertificateSigningService.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/wstep/util/CertificateSigningService.java new file mode 100644 index 0000000000..855b96fd91 --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/wstep/util/CertificateSigningService.java @@ -0,0 +1,143 @@ +/* + * 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.wso2.carbon.mdm.mobileservices.windows.services.wstep.util; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.bouncycastle.asn1.x509.BasicConstraints; +import org.bouncycastle.asn1.x509.ExtendedKeyUsage; +import org.bouncycastle.asn1.x509.Extension; +import org.bouncycastle.asn1.x509.KeyPurposeId; +import org.bouncycastle.asn1.x509.KeyUsage; +import org.bouncycastle.cert.CertIOException; +import org.bouncycastle.cert.X509v3CertificateBuilder; +import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; +import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder; +import org.bouncycastle.operator.ContentSigner; +import org.bouncycastle.operator.OperatorCreationException; +import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; +import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequest; +import org.wso2.carbon.mdm.mobileservices.windows.common.PluginConstants; +import org.wso2.carbon.mdm.mobileservices.windows.common.exceptions.CertificateGenerationException; +import org.wso2.carbon.mdm.mobileservices.windows.common.exceptions.WAPProvisioningException; + +import javax.security.auth.x500.X500Principal; +import java.math.BigInteger; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.SecureRandom; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.Date; +import java.util.List; + +/** + * Class for generating signed certificate for CSR form device. + */ +public class CertificateSigningService { + + private static final long MILLI_SECONDS = 1000L * 60 * 60 * 24; + + private enum PropertyIndex { + COMMON_NAME_INDEX(0), + NOT_BEFORE_DAYS_INDEX(1), + NOT_AFTER_DAYS_INDEX(2); + + private final int itemPosition; + private PropertyIndex(final int itemPosition) { + this.itemPosition = itemPosition; + } + public int getValue() { + return this.itemPosition; + } + } + + private static Log log = LogFactory.getLog(CertificateSigningService.class); + + /** + * Implement certificate signing task using CSR received from the device and the MDM server key + * store. + * @param jcaRequest - CSR from the device + * @param privateKey - Private key of CA certificate in MDM server + * @param caCert - CA certificate in MDM server + * @param certParameterList - Parameter list for Signed certificate generation + * @return - Signed certificate for CSR from device + * @throws CertificateGenerationException + * @throws org.wso2.carbon.mdm.mobileservices.windows.common.exceptions.WAPProvisioningException + */ + public static X509Certificate signCSR(JcaPKCS10CertificationRequest jcaRequest, + PrivateKey privateKey, X509Certificate caCert, + List certParameterList) throws + CertificateGenerationException, WAPProvisioningException { + + String commonName = + (String) certParameterList.get(PropertyIndex.COMMON_NAME_INDEX.getValue()); + int notBeforeDays = + (Integer) certParameterList.get(PropertyIndex.NOT_BEFORE_DAYS_INDEX.getValue()); + int notAfterDays = + (Integer) certParameterList.get(PropertyIndex.NOT_AFTER_DAYS_INDEX.getValue()); + X509v3CertificateBuilder certificateBuilder; + X509Certificate signedCertificate; + + try { + ContentSigner signer; + BigInteger serialNumber = BigInteger.valueOf(new SecureRandom(). + nextInt(Integer.MAX_VALUE)); + Date notBeforeDate = new Date(System.currentTimeMillis() - + (MILLI_SECONDS * notBeforeDays)); + Date notAfterDate = new Date(System.currentTimeMillis() + + (MILLI_SECONDS * notAfterDays)); + certificateBuilder = + new JcaX509v3CertificateBuilder(caCert, serialNumber, notBeforeDate, notAfterDate, + new X500Principal(commonName), + jcaRequest.getPublicKey()); + + //Adding extensions to the signed certificate. + certificateBuilder.addExtension(Extension.keyUsage, true, + new KeyUsage(KeyUsage.digitalSignature)); + certificateBuilder.addExtension(Extension.extendedKeyUsage, false, + new ExtendedKeyUsage(KeyPurposeId.id_kp_clientAuth)); + certificateBuilder.addExtension(Extension.basicConstraints, true, + new BasicConstraints(false)); + + signer = new JcaContentSignerBuilder(PluginConstants.CertificateEnrolment.ALGORITHM). + setProvider(PluginConstants.CertificateEnrolment.PROVIDER).build(privateKey); + + signedCertificate = new JcaX509CertificateConverter().setProvider( + PluginConstants.CertificateEnrolment.PROVIDER).getCertificate( + certificateBuilder.build(signer)); + } catch (InvalidKeyException e) { + throw new CertificateGenerationException("CSR's public key is invalid", e); + } catch (NoSuchAlgorithmException e) { + throw new CertificateGenerationException("Certificate cannot be generated", e); + } + catch (CertIOException e) { + throw new CertificateGenerationException( + "Cannot add extension(s) to signed certificate", e); + } + catch (OperatorCreationException e) { + throw new CertificateGenerationException("Content signer cannot be created", e); + } + catch (CertificateException e) { + throw new CertificateGenerationException("Signed certificate cannot be generated", e); + } + return signedCertificate; + } +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/wstep/util/KeyStoreGenerator.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/wstep/util/KeyStoreGenerator.java new file mode 100644 index 0000000000..2e02c9faae --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/wstep/util/KeyStoreGenerator.java @@ -0,0 +1,93 @@ +/* + * 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.wso2.carbon.mdm.mobileservices.windows.services.wstep.util; + +import org.wso2.carbon.mdm.mobileservices.windows.common.PluginConstants; +import org.wso2.carbon.mdm.mobileservices.windows.common.exceptions.KeyStoreGenerationException; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import java.io.FileInputStream; +import java.io.IOException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateException; + +/** + * Class for MDM Keystore operations. + */ +public class KeyStoreGenerator { + + private static final Log log = LogFactory.getLog(KeyStoreGenerator.class); + + /** + * This method loads the MDM keystore. + * @param keyStore - MDM Keystore + * @param keyStorePassword - Keystore Password + * @param keyStorePath - Keystore path + * @throws KeyStoreGenerationException + */ + public static void loadToStore(KeyStore keyStore, + char[] keyStorePassword, + String keyStorePath) throws KeyStoreGenerationException { + + FileInputStream fileInputStream = null; + + try { + if (keyStorePath != null) { + fileInputStream = new FileInputStream(keyStorePath); + keyStore.load(fileInputStream, keyStorePassword); + } + } catch (NoSuchAlgorithmException e) { + throw new KeyStoreGenerationException( + "Requested cryptographic algorithm is not available in the environment.", e); + } catch (CertificateException e) { + throw new KeyStoreGenerationException("Error working with certificate related to, " + + keyStorePath, e); + } catch (IOException e) { + throw new KeyStoreGenerationException("File error while working with file, " + + keyStorePath, e); + } finally { + try { + if (fileInputStream != null) { + fileInputStream.close(); + } + } catch (IOException e) { + throw new KeyStoreGenerationException("File error while closing the file, " + + keyStorePath,e); + } + } + } + + /** + * This method is for retrieving instance of Key Store. + * @return Keystore object + * @throws KeyStoreGenerationException + */ + public static KeyStore getKeyStore() throws KeyStoreGenerationException { + try { + return KeyStore.getInstance(PluginConstants.CertificateEnrolment.JKS); + } catch (KeyStoreException e) { + String msg = "KeyStore error while creating new JKS."; + log.error(msg, e); + throw new KeyStoreGenerationException(msg, e); + } + } +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/wstep/util/MessageHandler.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/wstep/util/MessageHandler.java new file mode 100644 index 0000000000..9d4ae0118e --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/wstep/util/MessageHandler.java @@ -0,0 +1,207 @@ +/* + * 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.wso2.carbon.mdm.mobileservices.windows.services.wstep.util; + +import org.wso2.carbon.mdm.mobileservices.windows.common.PluginConstants; +import org.joda.time.DateTime; +import org.joda.time.format.ISODateTimeFormat; + +import javax.ws.rs.core.Response; +import javax.xml.namespace.QName; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import javax.xml.soap.Name; +import javax.xml.soap.SOAPEnvelope; +import javax.xml.soap.SOAPException; +import javax.xml.soap.SOAPFactory; +import javax.xml.soap.SOAPHeader; +import javax.xml.soap.SOAPHeaderElement; +import javax.xml.soap.SOAPMessage; +import javax.xml.ws.handler.MessageContext; +import javax.xml.ws.handler.soap.SOAPHandler; +import javax.xml.ws.handler.soap.SOAPMessageContext; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.*; + +/** + * Class responsible for adding Timestamp security header in SOAP message and adding Content-length + * in the HTTP header for avoiding HTTP chunking. + */ +public class MessageHandler implements SOAPHandler { + + public static final String TIME_ZONE = "Z"; + public static final int VALIDITY_TIME = 5; + public static final int TIMESTAMP_END_INDEX = 6; + public static final int TIMESTAMP_BEGIN_INDEX = 0; + private static Log log = LogFactory.getLog(MessageHandler.class); + + /** + * This method resolves the security header coming in the SOAP message. + * @return - Security Header + */ + @Override + public Set getHeaders() { + QName securityHeader = new QName(PluginConstants.WS_SECURITY_TARGET_NAMESPACE, PluginConstants.SECURITY); + HashSet headers = new HashSet(); + headers.add(securityHeader); + return headers; + } + + /** + * This method adds Timestamp for SOAP header, and adds Content-length for HTTP header for + * avoiding HTTP chunking. + * + * @param context - Context of the SOAP Message + */ + @Override + public boolean handleMessage(SOAPMessageContext context) { + + Boolean outBoundProperty = (Boolean) context.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY); + + if (outBoundProperty) { + SOAPMessage message = context.getMessage(); + SOAPHeader header = null; + SOAPEnvelope envelope = null; + try { + header = message.getSOAPHeader(); + envelope = message.getSOAPPart().getEnvelope(); + } catch (SOAPException e) { + Response.serverError().entity("SOAP message content cannot be read.").build(); + } + try { + if ((header == null) && (envelope != null)) { + header = envelope.addHeader(); + } + } catch (SOAPException e) { + Response.serverError().entity("SOAP header cannot be added.").build(); + } + + SOAPFactory soapFactory = null; + try { + soapFactory = SOAPFactory.newInstance(); + } catch (SOAPException e) { + Response.serverError().entity("Cannot get an instance of SOAP factory.").build(); + } + + QName qNamesSecurity = new QName(PluginConstants.WS_SECURITY_TARGET_NAMESPACE, + PluginConstants.CertificateEnrolment.SECURITY); + SOAPHeaderElement Security = null; + Name attributeName = null; + try { + if (header != null) { + Security = header.addHeaderElement(qNamesSecurity); + } + if (soapFactory != null) { + attributeName = + soapFactory.createName(PluginConstants.CertificateEnrolment.TIMESTAMP_ID, + PluginConstants.CertificateEnrolment.TIMESTAMP_U, + PluginConstants.CertificateEnrolment + .WSS_SECURITY_UTILITY); + } + } catch (SOAPException e) { + Response.serverError().entity("Security header cannot be added.").build(); + } + + QName qNameTimestamp = new QName(PluginConstants.CertificateEnrolment.WSS_SECURITY_UTILITY, + PluginConstants.CertificateEnrolment.TIMESTAMP); + SOAPHeaderElement timestamp = null; + try { + if (header != null) { + timestamp = header.addHeaderElement(qNameTimestamp); + timestamp.addAttribute(attributeName, + PluginConstants.CertificateEnrolment.TIMESTAMP_0); + } + } catch (SOAPException e) { + Response.serverError().entity("Exception while adding timestamp header.").build(); + } + DateTime dateTime = new DateTime(); + DateTime expiredDateTime = dateTime.plusMinutes(VALIDITY_TIME); + String createdISOTime = dateTime.toString(ISODateTimeFormat.dateTime()); + String expiredISOTime = expiredDateTime.toString(ISODateTimeFormat.dateTime()); + createdISOTime = createdISOTime.substring(TIMESTAMP_BEGIN_INDEX, + createdISOTime.length() - + TIMESTAMP_END_INDEX); + createdISOTime = createdISOTime + TIME_ZONE; + expiredISOTime = expiredISOTime.substring(TIMESTAMP_BEGIN_INDEX, + expiredISOTime.length() - + TIMESTAMP_END_INDEX); + expiredISOTime = expiredISOTime + TIME_ZONE; + QName qNameCreated = new QName(PluginConstants.CertificateEnrolment.WSS_SECURITY_UTILITY, + PluginConstants.CertificateEnrolment.CREATED); + SOAPHeaderElement SOAPHeaderCreated = null; + + try { + if (header != null) { + SOAPHeaderCreated = header.addHeaderElement(qNameCreated); + SOAPHeaderCreated.addTextNode(createdISOTime); + } + } catch (SOAPException e) { + Response.serverError().entity("Exception while creating SOAP header.").build(); + } + QName qNameExpires = new QName(PluginConstants.CertificateEnrolment.WSS_SECURITY_UTILITY, + PluginConstants.CertificateEnrolment.EXPIRES); + SOAPHeaderElement SOAPHeaderExpires = null; + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + String messageString = null; + try { + if (header != null) { + SOAPHeaderExpires = header.addHeaderElement(qNameExpires); + SOAPHeaderExpires.addTextNode(expiredISOTime); + } + if ((timestamp != null) && (Security != null)) { + timestamp.addChildElement(SOAPHeaderCreated); + timestamp.addChildElement(SOAPHeaderExpires); + Security.addChildElement(timestamp); + } + message.saveChanges(); + message.writeTo(outputStream); + messageString = new String(outputStream.toByteArray(), + PluginConstants.CertificateEnrolment.UTF_8); + } catch (SOAPException e) { + Response.serverError().entity("Exception while creating timestamp SOAP header.") + .build(); + } catch (IOException e) { + Response.serverError().entity("Exception while writing message to output stream.") + .build(); + } + + Map> headers = + (Map>) context.get(MessageContext.HTTP_REQUEST_HEADERS); + headers = new HashMap>(); + if (messageString != null) { + headers.put(PluginConstants.CONTENT_LENGTH, Arrays.asList(String.valueOf( + messageString.length()))); + } + context.put(MessageContext.HTTP_REQUEST_HEADERS, headers); + } + return true; + } + + @Override + public boolean handleFault(SOAPMessageContext context) { + return true; + } + + @Override + public void close(MessageContext context) { + } +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/xcep/CertificateEnrollmentPolicyService.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/xcep/CertificateEnrollmentPolicyService.java new file mode 100644 index 0000000000..8e08c05292 --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/xcep/CertificateEnrollmentPolicyService.java @@ -0,0 +1,70 @@ +/* + * 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.wso2.carbon.mdm.mobileservices.windows.services.xcep; + +import org.wso2.carbon.mdm.mobileservices.windows.common.PluginConstants; +import org.wso2.carbon.mdm.mobileservices.windows.services.xcep.beans.CACollection; +import org.wso2.carbon.mdm.mobileservices.windows.services.xcep.beans.Client; +import org.wso2.carbon.mdm.mobileservices.windows.services.xcep.beans.OIDCollection; +import org.wso2.carbon.mdm.mobileservices.windows.services.xcep.beans.ObjectFactory; +import org.wso2.carbon.mdm.mobileservices.windows.services.xcep.beans.RequestFilter; +import org.wso2.carbon.mdm.mobileservices.windows.services.xcep.beans.Response; + +import javax.jws.WebMethod; +import javax.jws.WebParam; +import javax.jws.WebService; +import javax.xml.bind.annotation.XmlSeeAlso; +import javax.xml.ws.BindingType; +import javax.xml.ws.Holder; +import javax.xml.ws.RequestWrapper; +import javax.xml.ws.ResponseWrapper; +import javax.xml.ws.soap.SOAPBinding; + +/** + * Interface for MS-XCEP implementation. + */ +@WebService(targetNamespace = PluginConstants.CERTIFICATE_ENROLLMENT_POLICY_SERVICE_TARGET_NAMESPACE, + name = "IPolicy") +@BindingType(value = SOAPBinding.SOAP12HTTP_BINDING) +@XmlSeeAlso({ ObjectFactory.class }) +public interface CertificateEnrollmentPolicyService { + + @RequestWrapper(localName = "GetPolicies", targetNamespace = PluginConstants. + ENROLLMENT_POLICY_TARGET_NAMESPACE, className = PluginConstants.REQUEST_WRAPPER_CLASS_NAME) + @WebMethod(operationName = "GetPolicies") + @ResponseWrapper(localName = "GetPoliciesResponse", targetNamespace = PluginConstants. + ENROLLMENT_POLICY_TARGET_NAMESPACE, className = PluginConstants. + RESPONSE_WRAPPER_CLASS_NAME) void getPolicies( + @WebParam(name = "client", targetNamespace = PluginConstants. + ENROLLMENT_POLICY_TARGET_NAMESPACE) + Client client, + @WebParam(name = "requestFilter", targetNamespace = PluginConstants. + ENROLLMENT_POLICY_TARGET_NAMESPACE) + RequestFilter requestFilter, + @WebParam(mode = WebParam.Mode.OUT, name = "response", targetNamespace = PluginConstants. + ENROLLMENT_POLICY_TARGET_NAMESPACE) + Holder response, + @WebParam(mode = WebParam.Mode.OUT, name = "cAs", targetNamespace = PluginConstants. + ENROLLMENT_POLICY_TARGET_NAMESPACE) + Holder caCollection, + @WebParam(mode = WebParam.Mode.OUT, name = "oIDs", targetNamespace = PluginConstants. + ENROLLMENT_POLICY_TARGET_NAMESPACE) + Holder oidCollection + ); +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/xcep/beans/Attributes.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/xcep/beans/Attributes.java new file mode 100644 index 0000000000..e39ba5c98a --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/xcep/beans/Attributes.java @@ -0,0 +1,451 @@ +/* + * 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.wso2.carbon.mdm.mobileservices.windows.services.xcep.beans; + +import org.w3c.dom.Element; + +import javax.xml.bind.annotation.*; +import java.util.ArrayList; +import java.util.List; + +/** + *

      Java class for Attributes complex type. + *

      + *

      The following schema fragment specifies the expected content contained within this class. + *

      + *

      + * <complexType name="Attributes">
      + *   <complexContent>
      + *     <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
      + *       <sequence>
      + *         <element ref="{http://schemas.microsoft
      + *         .com/windows/pki/2009/01/enrollmentpolicy}commonName"/>
      + *         <element name="policySchema" type="{http://www.w3.org/2001/XMLSchema}unsignedInt"/>
      + *         <element name="certificateValidity" type="{http://schemas.microsoft
      + *         .com/windows/pki/2009/01/enrollmentpolicy}CertificateValidity"/>
      + *         <element name="permission" type="{http://schemas.microsoft
      + *         .com/windows/pki/2009/01/enrollmentpolicy}EnrollmentPermission"/>
      + *         <element name="privateKeyAttributes" type="{http://schemas.microsoft
      + *         .com/windows/pki/2009/01/enrollmentpolicy}PrivateKeyAttributes"/>
      + *         <element name="revision" type="{http://schemas.microsoft
      + *         .com/windows/pki/2009/01/enrollmentpolicy}Revision"/>
      + *         <element name="supersededPolicies" type="{http://schemas.microsoft
      + *         .com/windows/pki/2009/01/enrollmentpolicy}SupersededPolicies"/>
      + *         <element name="privateKeyFlags" type="{http://www.w3.org/2001/XMLSchema}unsignedInt"/>
      + *         <element name="subjectNameFlags" type="{http://www.w3
      + *         .org/2001/XMLSchema}unsignedInt"/>
      + *         <element name="enrollmentFlags" type="{http://www.w3.org/2001/XMLSchema}unsignedInt"/>
      + *         <element name="generalFlags" type="{http://www.w3.org/2001/XMLSchema}unsignedInt"/>
      + *         <element name="hashAlgorithmOIDReference" type="{http://www.w3
      + *         .org/2001/XMLSchema}int"/>
      + *         <element name="rARequirements" type="{http://schemas.microsoft
      + *         .com/windows/pki/2009/01/enrollmentpolicy}RARequirements"/>
      + *         <element name="keyArchivalAttributes" type="{http://schemas.microsoft
      + *         .com/windows/pki/2009/01/enrollmentpolicy}KeyArchivalAttributes"/>
      + *         <element name="extensions" type="{http://schemas.microsoft
      + *         .com/windows/pki/2009/01/enrollmentpolicy}ExtensionCollection"/>
      + *         <any processContents='lax' maxOccurs="unbounded" minOccurs="0"/>
      + *       </sequence>
      + *     </restriction>
      + *   </complexContent>
      + * </complexType>
      + * 
      + */ +@XmlAccessorType(XmlAccessType.FIELD) +@XmlType(name = "Attributes", propOrder = { + "commonName", + "policySchema", + "certificateValidity", + "permission", + "privateKeyAttributes", + "revision", + "supersededPolicies", + "privateKeyFlags", + "subjectNameFlags", + "enrollmentFlags", + "generalFlags", + "hashAlgorithmOIDReference", + "raRequirements", + "keyArchivalAttributes", + "extensions", + "any" +}) +@SuppressWarnings("unused") +public class Attributes { + + @XmlElement(required = true) + protected String commonName; + @XmlSchemaType(name = "unsignedInt") + protected long policySchema; + @XmlElement(required = true) + protected CertificateValidity certificateValidity; + @XmlElement(required = true) + protected EnrollmentPermission permission; + @XmlElement(required = true) + protected PrivateKeyAttributes privateKeyAttributes; + @XmlElement(required = true) + protected Revision revision; + @XmlElement(required = true, nillable = true) + protected SupersededPolicies supersededPolicies; + @XmlElement(required = true, type = Long.class, nillable = true) + @XmlSchemaType(name = "unsignedInt") + protected Long privateKeyFlags; + @XmlElement(required = true, type = Long.class, nillable = true) + @XmlSchemaType(name = "unsignedInt") + protected Long subjectNameFlags; + @XmlElement(required = true, type = Long.class, nillable = true) + @XmlSchemaType(name = "unsignedInt") + protected Long enrollmentFlags; + @XmlElement(required = true, type = Long.class, nillable = true) + @XmlSchemaType(name = "unsignedInt") + protected Long generalFlags; + @XmlElement(required = true, type = Integer.class, nillable = true) + protected Integer hashAlgorithmOIDReference; + @XmlElement(name = "rARequirements", required = true, nillable = true) + protected RARequirements raRequirements; + @XmlElement(required = true, nillable = true) + protected KeyArchivalAttributes keyArchivalAttributes; + @XmlElement(required = true, nillable = true) + protected ExtensionCollection extensions; + @XmlAnyElement(lax = true) + protected List any; + + /** + * Gets the value of the commonName property. + * + * @return possible object is + * {@link String } + */ + public String getCommonName() { + return commonName; + } + + /** + * Sets the value of the commonName property. + * + * @param value allowed object is + * {@link String } + */ + public void setCommonName(String value) { + this.commonName = value; + } + + /** + * Gets the value of the policySchema property. + */ + public long getPolicySchema() { + return policySchema; + } + + /** + * Sets the value of the policySchema property. + */ + public void setPolicySchema(long value) { + this.policySchema = value; + } + + /** + * Gets the value of the certificateValidity property. + * + * @return possible object is + * {@link CertificateValidity } + */ + public CertificateValidity getCertificateValidity() { + return certificateValidity; + } + + /** + * Sets the value of the certificateValidity property. + * + * @param value allowed object is + * {@link CertificateValidity } + */ + public void setCertificateValidity(CertificateValidity value) { + this.certificateValidity = value; + } + + /** + * Gets the value of the permission property. + * + * @return possible object is + * {@link EnrollmentPermission } + */ + public EnrollmentPermission getPermission() { + return permission; + } + + /** + * Sets the value of the permission property. + * + * @param value allowed object is + * {@link EnrollmentPermission } + */ + public void setPermission(EnrollmentPermission value) { + this.permission = value; + } + + /** + * Gets the value of the privateKeyAttributes property. + * + * @return possible object is + * {@link PrivateKeyAttributes } + */ + public PrivateKeyAttributes getPrivateKeyAttributes() { + return privateKeyAttributes; + } + + /** + * Sets the value of the privateKeyAttributes property. + * + * @param value allowed object is + * {@link PrivateKeyAttributes } + */ + public void setPrivateKeyAttributes(PrivateKeyAttributes value) { + this.privateKeyAttributes = value; + } + + /** + * Gets the value of the revision property. + * + * @return possible object is + * {@link Revision } + */ + public Revision getRevision() { + return revision; + } + + /** + * Sets the value of the revision property. + * + * @param value allowed object is + * {@link Revision } + */ + public void setRevision(Revision value) { + this.revision = value; + } + + /** + * Gets the value of the supersededPolicies property. + * + * @return possible object is + * {@link SupersededPolicies } + */ + public SupersededPolicies getSupersededPolicies() { + return supersededPolicies; + } + + /** + * Sets the value of the supersededPolicies property. + * + * @param value allowed object is + * {@link SupersededPolicies } + */ + public void setSupersededPolicies(SupersededPolicies value) { + this.supersededPolicies = value; + } + + /** + * Gets the value of the privateKeyFlags property. + * + * @return possible object is + * {@link Long } + */ + public Long getPrivateKeyFlags() { + return privateKeyFlags; + } + + /** + * Sets the value of the privateKeyFlags property. + * + * @param value allowed object is + * {@link Long } + */ + public void setPrivateKeyFlags(Long value) { + this.privateKeyFlags = value; + } + + /** + * Gets the value of the subjectNameFlags property. + * + * @return possible object is + * {@link Long } + */ + public Long getSubjectNameFlags() { + return subjectNameFlags; + } + + /** + * Sets the value of the subjectNameFlags property. + * + * @param value allowed object is + * {@link Long } + */ + public void setSubjectNameFlags(Long value) { + this.subjectNameFlags = value; + } + + /** + * Gets the value of the enrollmentFlags property. + * + * @return possible object is + * {@link Long } + */ + public Long getEnrollmentFlags() { + return enrollmentFlags; + } + + /** + * Sets the value of the enrollmentFlags property. + * + * @param value allowed object is + * {@link Long } + */ + public void setEnrollmentFlags(Long value) { + this.enrollmentFlags = value; + } + + /** + * Gets the value of the generalFlags property. + * + * @return possible object is + * {@link Long } + */ + public Long getGeneralFlags() { + return generalFlags; + } + + /** + * Sets the value of the generalFlags property. + * + * @param value allowed object is + * {@link Long } + */ + public void setGeneralFlags(Long value) { + this.generalFlags = value; + } + + /** + * Gets the value of the hashAlgorithmOIDReference property. + * + * @return possible object is + * {@link Integer } + */ + public Integer getHashAlgorithmOIDReference() { + return hashAlgorithmOIDReference; + } + + /** + * Sets the value of the hashAlgorithmOIDReference property. + * + * @param value allowed object is + * {@link Integer } + */ + public void setHashAlgorithmOIDReference(Integer value) { + this.hashAlgorithmOIDReference = value; + } + + /** + * Gets the value of the raRequirements property. + * + * @return possible object is + * {@link RARequirements } + */ + public RARequirements getRARequirements() { + return raRequirements; + } + + /** + * Sets the value of the raRequirements property. + * + * @param value allowed object is + * {@link RARequirements } + */ + public void setRARequirements(RARequirements value) { + this.raRequirements = value; + } + + /** + * Gets the value of the keyArchivalAttributes property. + * + * @return possible object is + * {@link KeyArchivalAttributes } + */ + public KeyArchivalAttributes getKeyArchivalAttributes() { + return keyArchivalAttributes; + } + + /** + * Sets the value of the keyArchivalAttributes property. + * + * @param value allowed object is + * {@link KeyArchivalAttributes } + */ + public void setKeyArchivalAttributes(KeyArchivalAttributes value) { + this.keyArchivalAttributes = value; + } + + /** + * Gets the value of the extensions property. + * + * @return possible object is + * {@link ExtensionCollection } + */ + public ExtensionCollection getExtensions() { + return extensions; + } + + /** + * Sets the value of the extensions property. + * + * @param value allowed object is + * {@link ExtensionCollection } + */ + public void setExtensions(ExtensionCollection value) { + this.extensions = value; + } + + /** + * Gets the value of the any property. + *

      + *

      + * This accessor method returns a reference to the live list, + * not a snapshot. Therefore any modification you make to the + * returned list will be present inside the JAXB object. + * This is why there is not a set method for the any property. + *

      + *

      + * For example, to add a new item, do as follows: + *

      +	 *    getAny().add(newItem);
      +	 * 
      + *

      + *

      + *

      + * Objects of the following type(s) are allowed in the list + * {@link Object } + * {@link Element } + */ + public List getAny() { + if (any == null) { + any = new ArrayList(); + } + return this.any; + } + +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/xcep/beans/CA.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/xcep/beans/CA.java new file mode 100644 index 0000000000..4b643f089c --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/xcep/beans/CA.java @@ -0,0 +1,166 @@ +/* + * 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.wso2.carbon.mdm.mobileservices.windows.services.xcep.beans; + +import org.w3c.dom.Element; + +import javax.xml.bind.annotation.*; +import java.util.ArrayList; +import java.util.List; + +/** + *

      Java class for CA complex type. + *

      + *

      The following schema fragment specifies the expected content contained within this class. + *

      + *

      + * <complexType name="CA">
      + *   <complexContent>
      + *     <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
      + *       <sequence>
      + *         <element name="uris" type="{http://schemas.microsoft
      + *         .com/windows/pki/2009/01/enrollmentpolicy}CAURICollection"/>
      + *         <element name="certificate" type="{http://www.w3.org/2001/XMLSchema}base64Binary"/>
      + *         <element name="enrollPermission" type="{http://www.w3.org/2001/XMLSchema}boolean"/>
      + *         <element name="cAReferenceID" type="{http://www.w3.org/2001/XMLSchema}int"/>
      + *         <any processContents='lax' maxOccurs="unbounded" minOccurs="0"/>
      + *       </sequence>
      + *     </restriction>
      + *   </complexContent>
      + * </complexType>
      + * 
      + */ +@XmlAccessorType(XmlAccessType.FIELD) +@XmlType(name = "CA", propOrder = { + "uris", + "certificate", + "enrollPermission", + "caReferenceID", + "any" +}) +@SuppressWarnings("unused") +public class CA { + + @XmlElement(required = true) + protected CAURICollection uris; + @XmlElement(required = true) + protected byte[] certificate; + protected boolean enrollPermission; + @XmlElement(name = "cAReferenceID") + protected int caReferenceID; + @XmlAnyElement(lax = true) + protected List any; + + /** + * Gets the value of the uris property. + * + * @return possible object is + * {@link CAURICollection } + */ + public CAURICollection getUris() { + return uris; + } + + /** + * Sets the value of the uris property. + * + * @param value allowed object is + * {@link CAURICollection } + */ + public void setUris(CAURICollection value) { + this.uris = value; + } + + /** + * Gets the value of the certificate property. + * + * @return possible object is + * byte[] + */ + public byte[] getCertificate() { + return certificate; + } + + /** + * Sets the value of the certificate property. + * + * @param value allowed object is + * byte[] + */ + public void setCertificate(byte[] value) { + this.certificate = value; + } + + /** + * Gets the value of the enrollPermission property. + */ + public boolean isEnrollPermission() { + return enrollPermission; + } + + /** + * Sets the value of the enrollPermission property. + */ + public void setEnrollPermission(boolean value) { + this.enrollPermission = value; + } + + /** + * Gets the value of the caReferenceID property. + */ + public int getCAReferenceID() { + return caReferenceID; + } + + /** + * Sets the value of the caReferenceID property. + */ + public void setCAReferenceID(int value) { + this.caReferenceID = value; + } + + /** + * Gets the value of the any property. + *

      + *

      + * This accessor method returns a reference to the live list, + * not a snapshot. Therefore any modification you make to the + * returned list will be present inside the JAXB object. + * This is why there is not a set method for the any property. + *

      + *

      + * For example, to add a new item, do as follows: + *

      +	 *    getAny().add(newItem);
      +	 * 
      + *

      + *

      + *

      + * Objects of the following type(s) are allowed in the list + * {@link Object } + * {@link Element } + */ + public List getAny() { + if (any == null) { + any = new ArrayList(); + } + return this.any; + } + +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/xcep/beans/CACollection.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/xcep/beans/CACollection.java new file mode 100644 index 0000000000..cc94b04db7 --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/xcep/beans/CACollection.java @@ -0,0 +1,83 @@ +/* + * 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.wso2.carbon.mdm.mobileservices.windows.services.xcep.beans; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlType; +import java.util.ArrayList; +import java.util.List; + +/** + *

      Java class for CACollection complex type. + *

      + *

      The following schema fragment specifies the expected content contained within this class. + *

      + *

      + * <complexType name="CACollection">
      + *   <complexContent>
      + *     <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
      + *       <sequence>
      + *         <element name="cA" type="{http://schemas.microsoft
      + *         .com/windows/pki/2009/01/enrollmentpolicy}CA" maxOccurs="unbounded"/>
      + *       </sequence>
      + *     </restriction>
      + *   </complexContent>
      + * </complexType>
      + * 
      + */ +@XmlAccessorType(XmlAccessType.FIELD) +@XmlType(name = "CACollection", propOrder = { + "ca" +}) +@SuppressWarnings("unused") +public class CACollection { + + @XmlElement(name = "cA", required = true) + protected List ca; + + /** + * Gets the value of the ca property. + *

      + *

      + * This accessor method returns a reference to the live list, + * not a snapshot. Therefore any modification you make to the + * returned list will be present inside the JAXB object. + * This is why there is not a set method for the ca property. + *

      + *

      + * For example, to add a new item, do as follows: + *

      +	 *    getCA().add(newItem);
      +	 * 
      + *

      + *

      + *

      + * Objects of the following type(s) are allowed in the list + * {@link CA } + */ + public List getCA() { + if (ca == null) { + ca = new ArrayList(); + } + return this.ca; + } + +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/xcep/beans/CAReferenceCollection.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/xcep/beans/CAReferenceCollection.java new file mode 100644 index 0000000000..6199b4ee32 --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/xcep/beans/CAReferenceCollection.java @@ -0,0 +1,83 @@ +/* + * 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.wso2.carbon.mdm.mobileservices.windows.services.xcep.beans; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlType; +import java.util.ArrayList; +import java.util.List; + +/** + *

      Java class for CAReferenceCollection complex type. + *

      + *

      The following schema fragment specifies the expected content contained within this class. + *

      + *

      + * <complexType name="CAReferenceCollection">
      + *   <complexContent>
      + *     <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
      + *       <sequence>
      + *         <element name="cAReference" type="{http://www.w3.org/2001/XMLSchema}int"
      + *         maxOccurs="unbounded"/>
      + *       </sequence>
      + *     </restriction>
      + *   </complexContent>
      + * </complexType>
      + * 
      + */ +@XmlAccessorType(XmlAccessType.FIELD) +@XmlType(name = "CAReferenceCollection", propOrder = { + "caReference" +}) +@SuppressWarnings("unused") +public class CAReferenceCollection { + + @XmlElement(name = "cAReference", type = Integer.class) + protected List caReference; + + /** + * Gets the value of the caReference property. + *

      + *

      + * This accessor method returns a reference to the live list, + * not a snapshot. Therefore any modification you make to the + * returned list will be present inside the JAXB object. + * This is why there is not a set method for the caReference property. + *

      + *

      + * For example, to add a new item, do as follows: + *

      +	 *    getCAReference().add(newItem);
      +	 * 
      + *

      + *

      + *

      + * Objects of the following type(s) are allowed in the list + * {@link Integer } + */ + public List getCAReference() { + if (caReference == null) { + caReference = new ArrayList(); + } + return this.caReference; + } + +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/xcep/beans/CAURI.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/xcep/beans/CAURI.java new file mode 100644 index 0000000000..ee33ba0790 --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/xcep/beans/CAURI.java @@ -0,0 +1,168 @@ +/* + * 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.wso2.carbon.mdm.mobileservices.windows.services.xcep.beans; + +import org.w3c.dom.Element; + +import javax.xml.bind.annotation.*; +import java.util.ArrayList; +import java.util.List; + +/** + *

      Java class for CAURI complex type. + *

      + *

      The following schema fragment specifies the expected content contained within this class. + *

      + *

      + * <complexType name="CAURI">
      + *   <complexContent>
      + *     <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
      + *       <sequence>
      + *         <element name="clientAuthentication" type="{http://www.w3
      + *         .org/2001/XMLSchema}unsignedInt"/>
      + *         <element name="uri" type="{http://www.w3.org/2001/XMLSchema}anyURI"/>
      + *         <element name="priority" type="{http://www.w3.org/2001/XMLSchema}unsignedInt"/>
      + *         <element name="renewalOnly" type="{http://www.w3.org/2001/XMLSchema}boolean"/>
      + *         <any processContents='lax' maxOccurs="unbounded" minOccurs="0"/>
      + *       </sequence>
      + *     </restriction>
      + *   </complexContent>
      + * </complexType>
      + * 
      + */ +@XmlAccessorType(XmlAccessType.FIELD) +@XmlType(name = "CAURI", propOrder = { + "clientAuthentication", + "uri", + "priority", + "renewalOnly", + "any" +}) +@SuppressWarnings("unused") +public class CAURI { + + @XmlSchemaType(name = "unsignedInt") + protected long clientAuthentication; + @XmlElement(required = true) + @XmlSchemaType(name = "anyURI") + protected String uri; + @XmlElement(required = true, type = Long.class, nillable = true) + @XmlSchemaType(name = "unsignedInt") + protected Long priority; + protected boolean renewalOnly; + @XmlAnyElement(lax = true) + protected List any; + + /** + * Gets the value of the clientAuthentication property. + */ + public long getClientAuthentication() { + return clientAuthentication; + } + + /** + * Sets the value of the clientAuthentication property. + */ + public void setClientAuthentication(long value) { + this.clientAuthentication = value; + } + + /** + * Gets the value of the uri property. + * + * @return possible object is + * {@link String } + */ + public String getUri() { + return uri; + } + + /** + * Sets the value of the uri property. + * + * @param value allowed object is + * {@link String } + */ + public void setUri(String value) { + this.uri = value; + } + + /** + * Gets the value of the priority property. + * + * @return possible object is + * {@link Long } + */ + public Long getPriority() { + return priority; + } + + /** + * Sets the value of the priority property. + * + * @param value allowed object is + * {@link Long } + */ + public void setPriority(Long value) { + this.priority = value; + } + + /** + * Gets the value of the renewalOnly property. + */ + public boolean isRenewalOnly() { + return renewalOnly; + } + + /** + * Sets the value of the renewalOnly property. + */ + public void setRenewalOnly(boolean value) { + this.renewalOnly = value; + } + + /** + * Gets the value of the any property. + *

      + *

      + * This accessor method returns a reference to the live list, + * not a snapshot. Therefore any modification you make to the + * returned list will be present inside the JAXB object. + * This is why there is not a set method for the any property. + *

      + *

      + * For example, to add a new item, do as follows: + *

      +	 *    getAny().add(newItem);
      +	 * 
      + *

      + *

      + *

      + * Objects of the following type(s) are allowed in the list + * {@link Object } + * {@link Element } + */ + public List getAny() { + if (any == null) { + any = new ArrayList(); + } + return this.any; + } + +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/xcep/beans/CAURICollection.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/xcep/beans/CAURICollection.java new file mode 100644 index 0000000000..6278311da8 --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/xcep/beans/CAURICollection.java @@ -0,0 +1,83 @@ +/* + * 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.wso2.carbon.mdm.mobileservices.windows.services.xcep.beans; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlType; +import java.util.ArrayList; +import java.util.List; + +/** + *

      Java class for CAURICollection complex type. + *

      + *

      The following schema fragment specifies the expected content contained within this class. + *

      + *

      + * <complexType name="CAURICollection">
      + *   <complexContent>
      + *     <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
      + *       <sequence>
      + *         <element name="cAURI" type="{http://schemas.microsoft
      + *         .com/windows/pki/2009/01/enrollmentpolicy}CAURI" maxOccurs="unbounded"/>
      + *       </sequence>
      + *     </restriction>
      + *   </complexContent>
      + * </complexType>
      + * 
      + */ +@XmlAccessorType(XmlAccessType.FIELD) +@XmlType(name = "CAURICollection", propOrder = { + "cauri" +}) +@SuppressWarnings("unused") +public class CAURICollection { + + @XmlElement(name = "cAURI", required = true) + protected List cauri; + + /** + * Gets the value of the cauri property. + *

      + *

      + * This accessor method returns a reference to the live list, + * not a snapshot. Therefore any modification you make to the + * returned list will be present inside the JAXB object. + * This is why there is not a set method for the cauri property. + *

      + *

      + * For example, to add a new item, do as follows: + *

      +	 *    getCAURI().add(newItem);
      +	 * 
      + *

      + *

      + *

      + * Objects of the following type(s) are allowed in the list + * {@link CAURI } + */ + public List getCAURI() { + if (cauri == null) { + cauri = new ArrayList(); + } + return this.cauri; + } + +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/xcep/beans/CertificateEnrollmentPolicy.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/xcep/beans/CertificateEnrollmentPolicy.java new file mode 100644 index 0000000000..4ce348190a --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/xcep/beans/CertificateEnrollmentPolicy.java @@ -0,0 +1,149 @@ +/* + * 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.wso2.carbon.mdm.mobileservices.windows.services.xcep.beans; + +import org.w3c.dom.Element; + +import javax.xml.bind.annotation.*; +import java.util.ArrayList; +import java.util.List; + +/** + *

      Java class for CertificateEnrollmentPolicy complex type. + *

      + *

      The following schema fragment specifies the expected content contained within this class. + *

      + *

      + * <complexType name="CertificateEnrollmentPolicy">
      + *   <complexContent>
      + *     <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
      + *       <sequence>
      + *         <element name="policyOIDReference" type="{http://www.w3.org/2001/XMLSchema}int"/>
      + *         <element name="cAs" type="{http://schemas.microsoft
      + *         .com/windows/pki/2009/01/enrollmentpolicy}CAReferenceCollection"/>
      + *         <element name="attributes" type="{http://schemas.microsoft
      + *         .com/windows/pki/2009/01/enrollmentpolicy}Attributes"/>
      + *         <any processContents='lax' maxOccurs="unbounded" minOccurs="0"/>
      + *       </sequence>
      + *     </restriction>
      + *   </complexContent>
      + * </complexType>
      + * 
      + */ +@XmlAccessorType(XmlAccessType.FIELD) +@XmlType(name = "CertificateEnrollmentPolicy", propOrder = { + "policyOIDReference", + "cAs", + "attributes", + "any" +}) +@SuppressWarnings("unused") +public class CertificateEnrollmentPolicy { + + protected int policyOIDReference; + @XmlElement(required = true, nillable = true) + protected CAReferenceCollection cAs; + @XmlElement(required = true) + protected Attributes attributes; + @XmlAnyElement(lax = true) + protected List any; + + /** + * Gets the value of the policyOIDReference property. + */ + public int getPolicyOIDReference() { + return policyOIDReference; + } + + /** + * Sets the value of the policyOIDReference property. + */ + public void setPolicyOIDReference(int value) { + this.policyOIDReference = value; + } + + /** + * Gets the value of the cAs property. + * + * @return possible object is + * {@link CAReferenceCollection } + */ + public CAReferenceCollection getCAs() { + return cAs; + } + + /** + * Sets the value of the cAs property. + * + * @param value allowed object is + * {@link CAReferenceCollection } + */ + public void setCAs(CAReferenceCollection value) { + this.cAs = value; + } + + /** + * Gets the value of the attributes property. + * + * @return possible object is + * {@link Attributes } + */ + public Attributes getAttributes() { + return attributes; + } + + /** + * Sets the value of the attributes property. + * + * @param value allowed object is + * {@link Attributes } + */ + public void setAttributes(Attributes value) { + this.attributes = value; + } + + /** + * Gets the value of the any property. + *

      + *

      + * This accessor method returns a reference to the live list, + * not a snapshot. Therefore any modification you make to the + * returned list will be present inside the JAXB object. + * This is why there is not a set method for the any property. + *

      + *

      + * For example, to add a new item, do as follows: + *

      +	 *    getAny().add(newItem);
      +	 * 
      + *

      + *

      + *

      + * Objects of the following type(s) are allowed in the list + * {@link Object } + * {@link Element } + */ + public List getAny() { + if (any == null) { + any = new ArrayList(); + } + return this.any; + } + +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/xcep/beans/CertificateValidity.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/xcep/beans/CertificateValidity.java new file mode 100644 index 0000000000..41d77542c0 --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/xcep/beans/CertificateValidity.java @@ -0,0 +1,99 @@ +/* + * 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.wso2.carbon.mdm.mobileservices.windows.services.xcep.beans; + +import javax.xml.bind.annotation.*; +import java.math.BigInteger; + +/** + *

      Java class for CertificateValidity complex type. + *

      + *

      The following schema fragment specifies the expected content contained within this class. + *

      + *

      + * <complexType name="CertificateValidity">
      + *   <complexContent>
      + *     <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
      + *       <sequence>
      + *         <element name="validityPeriodSeconds" type="{http://www.w3
      + *         .org/2001/XMLSchema}unsignedLong"/>
      + *         <element name="renewalPeriodSeconds" type="{http://www.w3
      + *         .org/2001/XMLSchema}unsignedLong"/>
      + *       </sequence>
      + *     </restriction>
      + *   </complexContent>
      + * </complexType>
      + * 
      + */ +@XmlAccessorType(XmlAccessType.FIELD) +@XmlType(name = "CertificateValidity", propOrder = { + "validityPeriodSeconds", + "renewalPeriodSeconds" +}) +@SuppressWarnings("unused") +public class CertificateValidity { + + @XmlElement(required = true) + @XmlSchemaType(name = "unsignedLong") + protected BigInteger validityPeriodSeconds; + @XmlElement(required = true) + @XmlSchemaType(name = "unsignedLong") + protected BigInteger renewalPeriodSeconds; + + /** + * Gets the value of the validityPeriodSeconds property. + * + * @return possible object is + * {@link BigInteger } + */ + public BigInteger getValidityPeriodSeconds() { + return validityPeriodSeconds; + } + + /** + * Sets the value of the validityPeriodSeconds property. + * + * @param value allowed object is + * {@link BigInteger } + */ + public void setValidityPeriodSeconds(BigInteger value) { + this.validityPeriodSeconds = value; + } + + /** + * Gets the value of the renewalPeriodSeconds property. + * + * @return possible object is + * {@link BigInteger } + */ + public BigInteger getRenewalPeriodSeconds() { + return renewalPeriodSeconds; + } + + /** + * Sets the value of the renewalPeriodSeconds property. + * + * @param value allowed object is + * {@link BigInteger } + */ + public void setRenewalPeriodSeconds(BigInteger value) { + this.renewalPeriodSeconds = value; + } + +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/xcep/beans/Client.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/xcep/beans/Client.java new file mode 100644 index 0000000000..685bfd5221 --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/xcep/beans/Client.java @@ -0,0 +1,136 @@ +/* + * 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.wso2.carbon.mdm.mobileservices.windows.services.xcep.beans; + +import org.w3c.dom.Element; + +import javax.xml.bind.annotation.*; +import javax.xml.bind.annotation.adapters.CollapsedStringAdapter; +import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; +import javax.xml.datatype.XMLGregorianCalendar; +import java.util.ArrayList; +import java.util.List; + +/** + *

      Java class for Client complex type. + *

      + *

      The following schema fragment specifies the expected content contained within this class. + *

      + *

      + * <complexType name="Client">
      + *   <complexContent>
      + *     <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
      + *       <sequence>
      + *         <element name="lastUpdate" type="{http://www.w3.org/2001/XMLSchema}dateTime"/>
      + *         <element name="preferredLanguage" type="{http://www.w3.org/2001/XMLSchema}language"/>
      + *         <any processContents='lax' maxOccurs="unbounded" minOccurs="0"/>
      + *       </sequence>
      + *     </restriction>
      + *   </complexContent>
      + * </complexType>
      + * 
      + */ +@XmlAccessorType(XmlAccessType.FIELD) +@XmlType(name = "Client", propOrder = { + "lastUpdate", + "preferredLanguage", + "any" +}) +@SuppressWarnings("unused") +public class Client { + + @XmlElement(required = true, nillable = true) + @XmlSchemaType(name = "dateTime") + protected XMLGregorianCalendar lastUpdate; + @XmlElement(required = true, nillable = true) + @XmlJavaTypeAdapter(CollapsedStringAdapter.class) + @XmlSchemaType(name = "language") + protected String preferredLanguage; + @XmlAnyElement(lax = true) + protected List any; + + /** + * Gets the value of the lastUpdate property. + * + * @return possible object is + * {@link XMLGregorianCalendar } + */ + public XMLGregorianCalendar getLastUpdate() { + return lastUpdate; + } + + /** + * Sets the value of the lastUpdate property. + * + * @param value allowed object is + * {@link XMLGregorianCalendar } + */ + public void setLastUpdate(XMLGregorianCalendar value) { + this.lastUpdate = value; + } + + /** + * Gets the value of the preferredLanguage property. + * + * @return possible object is + * {@link String } + */ + public String getPreferredLanguage() { + return preferredLanguage; + } + + /** + * Sets the value of the preferredLanguage property. + * + * @param value allowed object is + * {@link String } + */ + public void setPreferredLanguage(String value) { + this.preferredLanguage = value; + } + + /** + * Gets the value of the any property. + *

      + *

      + * This accessor method returns a reference to the live list, + * not a snapshot. Therefore any modification you make to the + * returned list will be present inside the JAXB object. + * This is why there is not a set method for the any property. + *

      + *

      + * For example, to add a new item, do as follows: + *

      +	 *    getAny().add(newItem);
      +	 * 
      + *

      + *

      + *

      + * Objects of the following type(s) are allowed in the list + * {@link Object } + * {@link Element } + */ + public List getAny() { + if (any == null) { + any = new ArrayList(); + } + return this.any; + } + +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/xcep/beans/CryptoProviders.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/xcep/beans/CryptoProviders.java new file mode 100644 index 0000000000..d483b7c471 --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/xcep/beans/CryptoProviders.java @@ -0,0 +1,87 @@ +/* + * 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.wso2.carbon.mdm.mobileservices.windows.services.xcep.beans; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlType; +import java.util.ArrayList; +import java.util.List; + +/** + *

      Java class for CryptoProviders complex type. + *

      + *

      The following schema fragment specifies the expected content contained within this class. + *

      + *

      + * <complexType name="CryptoProviders">
      + *   <complexContent>
      + *     <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
      + *       <sequence>
      + *         <element name="provider" type="{http://www.w3.org/2001/XMLSchema}string"
      + *         maxOccurs="unbounded"/>
      + *       </sequence>
      + *     </restriction>
      + *   </complexContent>
      + * </complexType>
      + * 
      + */ +@XmlAccessorType(XmlAccessType.FIELD) +@XmlType(name = "CryptoProviders", propOrder = { + "provider" +}) +@SuppressWarnings("unused") +public class CryptoProviders { + + @XmlElement(required = true) + protected List provider; + + /** + * Instantiate provider list in the constructor + */ + public CryptoProviders() { + this.provider = new ArrayList(); + } + + /** + * Gets the value of the provider property. + *

      + *

      + * This accessor method returns a reference to the live list, + * not a snapshot. Therefore any modification you make to the + * returned list will be present inside the JAXB object. + * This is why there is not a set method for the provider property. + *

      + *

      + * For example, to add a new item, do as follows: + *

      +	 *    getProvider().add(newItem);
      +	 * 
      + *

      + *

      + *

      + * Objects of the following type(s) are allowed in the list + * {@link String } + */ + public List getProvider() { + return this.provider; + } + +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/xcep/beans/EnrollmentPermission.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/xcep/beans/EnrollmentPermission.java new file mode 100644 index 0000000000..3a05b9d282 --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/xcep/beans/EnrollmentPermission.java @@ -0,0 +1,82 @@ +/* + * 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.wso2.carbon.mdm.mobileservices.windows.services.xcep.beans; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlType; + +/** + *

      Java class for EnrollmentPermission complex type. + *

      + *

      The following schema fragment specifies the expected content contained within this class. + *

      + *

      + * <complexType name="EnrollmentPermission">
      + *   <complexContent>
      + *     <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
      + *       <sequence>
      + *         <element name="enroll" type="{http://www.w3.org/2001/XMLSchema}boolean"/>
      + *         <element name="autoEnroll" type="{http://www.w3.org/2001/XMLSchema}boolean"/>
      + *       </sequence>
      + *     </restriction>
      + *   </complexContent>
      + * </complexType>
      + * 
      + */ +@XmlAccessorType(XmlAccessType.FIELD) +@XmlType(name = "EnrollmentPermission", propOrder = { + "enroll", + "autoEnroll" +}) +@SuppressWarnings("unused") +public class EnrollmentPermission { + + protected boolean enroll; + protected boolean autoEnroll; + + /** + * Gets the value of the enroll property. + */ + public boolean isEnroll() { + return enroll; + } + + /** + * Sets the value of the enroll property. + */ + public void setEnroll(boolean value) { + this.enroll = value; + } + + /** + * Gets the value of the autoEnroll property. + */ + public boolean isAutoEnroll() { + return autoEnroll; + } + + /** + * Sets the value of the autoEnroll property. + */ + public void setAutoEnroll(boolean value) { + this.autoEnroll = value; + } + +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/xcep/beans/Extension.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/xcep/beans/Extension.java new file mode 100644 index 0000000000..7647d853ac --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/xcep/beans/Extension.java @@ -0,0 +1,108 @@ +/* + * 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.wso2.carbon.mdm.mobileservices.windows.services.xcep.beans; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlType; + +/** + *

      Java class for Extension complex type. + *

      + *

      The following schema fragment specifies the expected content contained within this class. + *

      + *

      + * <complexType name="Extension">
      + *   <complexContent>
      + *     <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
      + *       <sequence>
      + *         <element name="oIDReference" type="{http://www.w3.org/2001/XMLSchema}int"/>
      + *         <element name="critical" type="{http://www.w3.org/2001/XMLSchema}boolean"/>
      + *         <element name="value" type="{http://www.w3.org/2001/XMLSchema}base64Binary"/>
      + *       </sequence>
      + *     </restriction>
      + *   </complexContent>
      + * </complexType>
      + * 
      + */ +@XmlAccessorType(XmlAccessType.FIELD) +@XmlType(name = "Extension", propOrder = { + "oidReference", + "critical", + "value" +}) +@SuppressWarnings("unused") +public class Extension { + + @XmlElement(name = "oIDReference") + protected int oidReference; + protected boolean critical; + @XmlElement(required = true, nillable = true) + protected byte[] value; + + /** + * Gets the value of the oidReference property. + */ + public int getOIDReference() { + return oidReference; + } + + /** + * Sets the value of the oidReference property. + */ + public void setOIDReference(int value) { + this.oidReference = value; + } + + /** + * Gets the value of the critical property. + */ + public boolean isCritical() { + return critical; + } + + /** + * Sets the value of the critical property. + */ + public void setCritical(boolean value) { + this.critical = value; + } + + /** + * Gets the value of the value property. + * + * @return possible object is + * byte[] + */ + public byte[] getValue() { + return value; + } + + /** + * Sets the value of the value property. + * + * @param value allowed object is + * byte[] + */ + public void setValue(byte[] value) { + this.value = value; + } + +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/xcep/beans/ExtensionCollection.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/xcep/beans/ExtensionCollection.java new file mode 100644 index 0000000000..4e5422cbc2 --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/xcep/beans/ExtensionCollection.java @@ -0,0 +1,87 @@ +/* + * 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.wso2.carbon.mdm.mobileservices.windows.services.xcep.beans; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlType; +import java.util.ArrayList; +import java.util.List; + +/** + *

      Java class for ExtensionCollection complex type. + *

      + *

      The following schema fragment specifies the expected content contained within this class. + *

      + *

      + * <complexType name="ExtensionCollection">
      + *   <complexContent>
      + *     <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
      + *       <sequence>
      + *         <element name="extension" type="{http://schemas.microsoft
      + *         .com/windows/pki/2009/01/enrollmentpolicy}Extension" maxOccurs="unbounded"/>
      + *       </sequence>
      + *     </restriction>
      + *   </complexContent>
      + * </complexType>
      + * 
      + */ +@XmlAccessorType(XmlAccessType.FIELD) +@XmlType(name = "ExtensionCollection", propOrder = { + "extension" +}) +@SuppressWarnings("unused") +public class ExtensionCollection { + + @XmlElement(required = true) + protected List extension; + + /** + * Instantiate extension list in the constructor + */ + public ExtensionCollection() { + this.extension = new ArrayList(); + } + + /** + * Gets the value of the extension property. + *

      + *

      + * This accessor method returns a reference to the live list, + * not a snapshot. Therefore any modification you make to the + * returned list will be present inside the JAXB object. + * This is why there is not a set method for the extension property. + *

      + *

      + * For example, to add a new item, do as follows: + *

      +	 *    getExtension().add(newItem);
      +	 * 
      + *

      + *

      + *

      + * Objects of the following type(s) are allowed in the list + * {@link Extension } + */ + public List getExtension() { + return this.extension; + } + +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/xcep/beans/FilterOIDCollection.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/xcep/beans/FilterOIDCollection.java new file mode 100644 index 0000000000..a50dc51f24 --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/xcep/beans/FilterOIDCollection.java @@ -0,0 +1,87 @@ +/* + * 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.wso2.carbon.mdm.mobileservices.windows.services.xcep.beans; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlType; +import java.util.ArrayList; +import java.util.List; + +/** + *

      Java class for FilterOIDCollection complex type. + *

      + *

      The following schema fragment specifies the expected content contained within this class. + *

      + *

      + * <complexType name="FilterOIDCollection">
      + *   <complexContent>
      + *     <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
      + *       <sequence>
      + *         <element name="oid" type="{http://www.w3.org/2001/XMLSchema}string"
      + *         maxOccurs="unbounded"/>
      + *       </sequence>
      + *     </restriction>
      + *   </complexContent>
      + * </complexType>
      + * 
      + */ +@XmlAccessorType(XmlAccessType.FIELD) +@XmlType(name = "FilterOIDCollection", propOrder = { + "oid" +}) +@SuppressWarnings("unused") +public class FilterOIDCollection { + + @XmlElement(required = true) + protected List oid; + + /** + * Instantiate oid list in the constructor + */ + public FilterOIDCollection() { + this.oid = new ArrayList(); + } + + /** + * Gets the value of the oid property. + *

      + *

      + * This accessor method returns a reference to the live list, + * not a snapshot. Therefore any modification you make to the + * returned list will be present inside the JAXB object. + * This is why there is not a set method for the oid property. + *

      + *

      + * For example, to add a new item, do as follows: + *

      +	 *    getOid().add(newItem);
      +	 * 
      + *

      + *

      + *

      + * Objects of the following type(s) are allowed in the list + * {@link String } + */ + public List getOid() { + return this.oid; + } + +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/xcep/beans/GetPolicies.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/xcep/beans/GetPolicies.java new file mode 100644 index 0000000000..c312533f62 --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/xcep/beans/GetPolicies.java @@ -0,0 +1,101 @@ +/* + * 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.wso2.carbon.mdm.mobileservices.windows.services.xcep.beans; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.XmlType; + +/** + *

      Java class for anonymous complex type. + *

      + *

      The following schema fragment specifies the expected content contained within this class. + *

      + *

      + * <complexType>
      + *   <complexContent>
      + *     <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
      + *       <sequence>
      + *         <element name="client" type="{http://schemas.microsoft
      + *         .com/windows/pki/2009/01/enrollmentpolicy}Client"/>
      + *         <element name="requestFilter" type="{http://schemas.microsoft
      + *         .com/windows/pki/2009/01/enrollmentpolicy}RequestFilter"/>
      + *       </sequence>
      + *     </restriction>
      + *   </complexContent>
      + * </complexType>
      + * 
      + */ +@XmlAccessorType(XmlAccessType.FIELD) +@XmlType(name = "", propOrder = { + "client", + "requestFilter" +}) +@XmlRootElement(name = "GetPolicies") +@SuppressWarnings("unused") +public class GetPolicies { + + @XmlElement(required = true) + protected Client client; + @XmlElement(required = true, nillable = true) + protected RequestFilter requestFilter; + + /** + * Gets the value of the client property. + * + * @return possible object is + * {@link Client } + */ + public Client getClient() { + return client; + } + + /** + * Sets the value of the client property. + * + * @param value allowed object is + * {@link Client } + */ + public void setClient(Client value) { + this.client = value; + } + + /** + * Gets the value of the requestFilter property. + * + * @return possible object is + * {@link RequestFilter } + */ + public RequestFilter getRequestFilter() { + return requestFilter; + } + + /** + * Sets the value of the requestFilter property. + * + * @param value allowed object is + * {@link RequestFilter } + */ + public void setRequestFilter(RequestFilter value) { + this.requestFilter = value; + } + +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/xcep/beans/GetPoliciesResponse.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/xcep/beans/GetPoliciesResponse.java new file mode 100644 index 0000000000..d15b4c8b82 --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/xcep/beans/GetPoliciesResponse.java @@ -0,0 +1,126 @@ +/* + * 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.wso2.carbon.mdm.mobileservices.windows.services.xcep.beans; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.XmlType; + +/** + *

      Java class for anonymous complex type. + *

      + *

      The following schema fragment specifies the expected content contained within this class. + *

      + *

      + * <complexType>
      + *   <complexContent>
      + *     <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
      + *       <sequence>
      + *         <element name="response" type="{http://schemas.microsoft
      + *         .com/windows/pki/2009/01/enrollmentpolicy}Response"/>
      + *         <element name="cAs" type="{http://schemas.microsoft
      + *         .com/windows/pki/2009/01/enrollmentpolicy}CACollection"/>
      + *         <element name="oIDs" type="{http://schemas.microsoft
      + *         .com/windows/pki/2009/01/enrollmentpolicy}OIDCollection"/>
      + *       </sequence>
      + *     </restriction>
      + *   </complexContent>
      + * </complexType>
      + * 
      + */ +@XmlAccessorType(XmlAccessType.FIELD) +@XmlType(name = "", propOrder = { + "response", + "cAs", + "oiDs" +}) +@XmlRootElement(name = "GetPoliciesResponse") +@SuppressWarnings("unused") +public class GetPoliciesResponse { + + @XmlElement(required = true, nillable = true) + protected Response response; + @XmlElement(required = true, nillable = true) + protected CACollection cAs; + @XmlElement(name = "oIDs", required = true, nillable = true) + protected OIDCollection oiDs; + + /** + * Gets the value of the response property. + * + * @return possible object is + * {@link Response } + */ + public Response getResponse() { + return response; + } + + /** + * Sets the value of the response property. + * + * @param value allowed object is + * {@link Response } + */ + public void setResponse(Response value) { + this.response = value; + } + + /** + * Gets the value of the cAs property. + * + * @return possible object is + * {@link CACollection } + */ + public CACollection getCAs() { + return cAs; + } + + /** + * Sets the value of the cAs property. + * + * @param value allowed object is + * {@link CACollection } + */ + public void setCAs(CACollection value) { + this.cAs = value; + } + + /** + * Gets the value of the oiDs property. + * + * @return possible object is + * {@link OIDCollection } + */ + public OIDCollection getOIDs() { + return oiDs; + } + + /** + * Sets the value of the oiDs property. + * + * @param value allowed object is + * {@link OIDCollection } + */ + public void setOIDs(OIDCollection value) { + this.oiDs = value; + } + +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/xcep/beans/KeyArchivalAttributes.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/xcep/beans/KeyArchivalAttributes.java new file mode 100644 index 0000000000..d48ebb386f --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/xcep/beans/KeyArchivalAttributes.java @@ -0,0 +1,86 @@ +/* + * 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.wso2.carbon.mdm.mobileservices.windows.services.xcep.beans; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlSchemaType; +import javax.xml.bind.annotation.XmlType; + +/** + *

      Java class for KeyArchivalAttributes complex type. + *

      + *

      The following schema fragment specifies the expected content contained within this class. + *

      + *

      + * <complexType name="KeyArchivalAttributes">
      + *   <complexContent>
      + *     <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
      + *       <sequence>
      + *         <element name="symmetricAlgorithmOIDReference" type="{http://www.w3
      + *         .org/2001/XMLSchema}int"/>
      + *         <element name="symmetricAlgorithmKeyLength" type="{http://www.w3
      + *         .org/2001/XMLSchema}unsignedInt"/>
      + *       </sequence>
      + *     </restriction>
      + *   </complexContent>
      + * </complexType>
      + * 
      + */ +@XmlAccessorType(XmlAccessType.FIELD) +@XmlType(name = "KeyArchivalAttributes", propOrder = { + "symmetricAlgorithmOIDReference", + "symmetricAlgorithmKeyLength" +}) +@SuppressWarnings("unused") +public class KeyArchivalAttributes { + + protected int symmetricAlgorithmOIDReference; + @XmlSchemaType(name = "unsignedInt") + protected long symmetricAlgorithmKeyLength; + + /** + * Gets the value of the symmetricAlgorithmOIDReference property. + */ + public int getSymmetricAlgorithmOIDReference() { + return symmetricAlgorithmOIDReference; + } + + /** + * Sets the value of the symmetricAlgorithmOIDReference property. + */ + public void setSymmetricAlgorithmOIDReference(int value) { + this.symmetricAlgorithmOIDReference = value; + } + + /** + * Gets the value of the symmetricAlgorithmKeyLength property. + */ + public long getSymmetricAlgorithmKeyLength() { + return symmetricAlgorithmKeyLength; + } + + /** + * Sets the value of the symmetricAlgorithmKeyLength property. + */ + public void setSymmetricAlgorithmKeyLength(long value) { + this.symmetricAlgorithmKeyLength = value; + } + +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/xcep/beans/OID.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/xcep/beans/OID.java new file mode 100644 index 0000000000..7415bc68a9 --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/xcep/beans/OID.java @@ -0,0 +1,166 @@ +/* + * 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.wso2.carbon.mdm.mobileservices.windows.services.xcep.beans; + +import org.w3c.dom.Element; + +import javax.xml.bind.annotation.*; +import java.util.ArrayList; +import java.util.List; + +/** + *

      Java class for OID complex type. + *

      + *

      The following schema fragment specifies the expected content contained within this class. + *

      + *

      + * <complexType name="OID">
      + *   <complexContent>
      + *     <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
      + *       <sequence>
      + *         <element name="value" type="{http://www.w3.org/2001/XMLSchema}string"/>
      + *         <element name="group" type="{http://www.w3.org/2001/XMLSchema}unsignedInt"/>
      + *         <element name="oIDReferenceID" type="{http://www.w3.org/2001/XMLSchema}int"/>
      + *         <element name="defaultName" type="{http://www.w3.org/2001/XMLSchema}string"/>
      + *         <any processContents='lax' maxOccurs="unbounded" minOccurs="0"/>
      + *       </sequence>
      + *     </restriction>
      + *   </complexContent>
      + * </complexType>
      + * 
      + */ +@XmlAccessorType(XmlAccessType.FIELD) +@XmlType(name = "OID", propOrder = { + "value", + "group", + "oidReferenceID", + "defaultName", + "any" +}) +@SuppressWarnings("unused") +public class OID { + + @XmlElement(required = true) + protected String value; + @XmlSchemaType(name = "unsignedInt") + protected long group; + @XmlElement(name = "oIDReferenceID") + protected int oidReferenceID; + @XmlElement(required = true, nillable = true) + protected String defaultName; + @XmlAnyElement(lax = true) + protected List any; + + /** + * Gets the value of the value property. + * + * @return possible object is + * {@link String } + */ + public String getValue() { + return value; + } + + /** + * Sets the value of the value property. + * + * @param value allowed object is + * {@link String } + */ + public void setValue(String value) { + this.value = value; + } + + /** + * Gets the value of the group property. + */ + public long getGroup() { + return group; + } + + /** + * Sets the value of the group property. + */ + public void setGroup(long value) { + this.group = value; + } + + /** + * Gets the value of the oidReferenceID property. + */ + public int getOIDReferenceID() { + return oidReferenceID; + } + + /** + * Sets the value of the oidReferenceID property. + */ + public void setOIDReferenceID(int value) { + this.oidReferenceID = value; + } + + /** + * Gets the value of the defaultName property. + * + * @return possible object is + * {@link String } + */ + public String getDefaultName() { + return defaultName; + } + + /** + * Sets the value of the defaultName property. + * + * @param value allowed object is + * {@link String } + */ + public void setDefaultName(String value) { + this.defaultName = value; + } + + /** + * Gets the value of the any property. + *

      + *

      + * This accessor method returns a reference to the live list, + * not a snapshot. Therefore any modification you make to the + * returned list will be present inside the JAXB object. + * This is why there is not a set method for the any property. + *

      + *

      + * For example, to add a new item, do as follows: + *

      +	 *    getAny().add(newItem);
      +	 * 
      + *

      + *

      + *

      + * Objects of the following type(s) are allowed in the list + * {@link Object } + * {@link Element } + */ + public List getAny() { + if (any == null) { + any = new ArrayList(); + } + return this.any; + } + +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/xcep/beans/OIDCollection.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/xcep/beans/OIDCollection.java new file mode 100644 index 0000000000..54861d807e --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/xcep/beans/OIDCollection.java @@ -0,0 +1,83 @@ +/* + * 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.wso2.carbon.mdm.mobileservices.windows.services.xcep.beans; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlType; +import java.util.ArrayList; +import java.util.List; + +/** + *

      Java class for OIDCollection complex type. + *

      + *

      The following schema fragment specifies the expected content contained within this class. + *

      + *

      + * <complexType name="OIDCollection">
      + *   <complexContent>
      + *     <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
      + *       <sequence>
      + *         <element name="oID" type="{http://schemas.microsoft
      + *         .com/windows/pki/2009/01/enrollmentpolicy}OID" maxOccurs="unbounded"/>
      + *       </sequence>
      + *     </restriction>
      + *   </complexContent>
      + * </complexType>
      + * 
      + */ +@XmlAccessorType(XmlAccessType.FIELD) +@XmlType(name = "OIDCollection", propOrder = { + "oid" +}) +@SuppressWarnings("unused") +public class OIDCollection { + + @XmlElement(name = "oID", required = true) + protected List oid; + + /** + * Gets the value of the oid property. + *

      + *

      + * This accessor method returns a reference to the live list, + * not a snapshot. Therefore any modification you make to the + * returned list will be present inside the JAXB object. + * This is why there is not a set method for the oid property. + *

      + *

      + * For example, to add a new item, do as follows: + *

      +	 *    getOID().add(newItem);
      +	 * 
      + *

      + *

      + *

      + * Objects of the following type(s) are allowed in the list + * {@link OID } + */ + public List getOID() { + if (oid == null) { + oid = new ArrayList(); + } + return this.oid; + } + +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/xcep/beans/OIDReferenceCollection.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/xcep/beans/OIDReferenceCollection.java new file mode 100644 index 0000000000..9d095532ef --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/xcep/beans/OIDReferenceCollection.java @@ -0,0 +1,83 @@ +/* + * 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.wso2.carbon.mdm.mobileservices.windows.services.xcep.beans; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlType; +import java.util.ArrayList; +import java.util.List; + +/** + *

      Java class for OIDReferenceCollection complex type. + *

      + *

      The following schema fragment specifies the expected content contained within this class. + *

      + *

      + * <complexType name="OIDReferenceCollection">
      + *   <complexContent>
      + *     <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
      + *       <sequence>
      + *         <element name="oIDReference" type="{http://www.w3.org/2001/XMLSchema}int"
      + *         maxOccurs="unbounded"/>
      + *       </sequence>
      + *     </restriction>
      + *   </complexContent>
      + * </complexType>
      + * 
      + */ +@XmlAccessorType(XmlAccessType.FIELD) +@XmlType(name = "OIDReferenceCollection", propOrder = { + "oidReference" +}) +@SuppressWarnings("unused") +public class OIDReferenceCollection { + + @XmlElement(name = "oIDReference", type = Integer.class) + protected List oidReference; + + /** + * Gets the value of the oidReference property. + *

      + *

      + * This accessor method returns a reference to the live list, + * not a snapshot. Therefore any modification you make to the + * returned list will be present inside the JAXB object. + * This is why there is not a set method for the oidReference property. + *

      + *

      + * For example, to add a new item, do as follows: + *

      +	 *    getOIDReference().add(newItem);
      +	 * 
      + *

      + *

      + *

      + * Objects of the following type(s) are allowed in the list + * {@link Integer } + */ + public List getOIDReference() { + if (oidReference == null) { + oidReference = new ArrayList(); + } + return this.oidReference; + } + +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/xcep/beans/ObjectFactory.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/xcep/beans/ObjectFactory.java new file mode 100644 index 0000000000..83db64243f --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/xcep/beans/ObjectFactory.java @@ -0,0 +1,253 @@ +/* + * 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.wso2.carbon.mdm.mobileservices.windows.services.xcep.beans; + +import javax.xml.bind.JAXBElement; +import javax.xml.bind.annotation.XmlElementDecl; +import javax.xml.bind.annotation.XmlRegistry; +import javax.xml.namespace.QName; + +/** + * This object contains factory methods for each + * Java content interface and Java element interface + * generated in the com.microsoft.schemas.windows.pki._2009._01.enrollmentpolicy package. + *

      An ObjectFactory allows you to programatically + * construct new instances of the Java representation + * for XML content. The Java representation of XML + * content can consist of schema derived interfaces + * and classes representing the binding of schema + * type definitions, element declarations and model + * groups. Factory methods for each of these are + * provided in this class. + */ +@XmlRegistry +@SuppressWarnings("unused") +public class ObjectFactory { + + private final static QName _CommonName_QNAME = + new QName("http://schemas.microsoft.com/windows/pki/2009/01/enrollmentpolicy", + "commonName"); + + /** + * Create a new ObjectFactory that can be used to create new instances of schema derived + * classes for package: com.microsoft.schemas.windows.pki._2009._01.enrollmentpolicy + */ + public ObjectFactory() { + } + + /** + * Create an instance of {@link GetPolicies } + */ + public GetPolicies createGetPolicies() { + return new GetPolicies(); + } + + /** + * Create an instance of {@link Client } + */ + public Client createClient() { + return new Client(); + } + + /** + * Create an instance of {@link RequestFilter } + */ + public RequestFilter createRequestFilter() { + return new RequestFilter(); + } + + /** + * Create an instance of {@link GetPoliciesResponse } + */ + public GetPoliciesResponse createGetPoliciesResponse() { + return new GetPoliciesResponse(); + } + + /** + * Create an instance of {@link Response } + */ + public Response createResponse() { + return new Response(); + } + + /** + * Create an instance of {@link CACollection } + */ + public CACollection createCACollection() { + return new CACollection(); + } + + /** + * Create an instance of {@link OIDCollection } + */ + public OIDCollection createOIDCollection() { + return new OIDCollection(); + } + + /** + * Create an instance of {@link SupersededPolicies } + */ + public SupersededPolicies createSupersededPolicies() { + return new SupersededPolicies(); + } + + /** + * Create an instance of {@link OID } + */ + public OID createOID() { + return new OID(); + } + + /** + * Create an instance of {@link ExtensionCollection } + */ + public ExtensionCollection createExtensionCollection() { + return new ExtensionCollection(); + } + + /** + * Create an instance of {@link Attributes } + */ + public Attributes createAttributes() { + return new Attributes(); + } + + /** + * Create an instance of {@link EnrollmentPermission } + */ + public EnrollmentPermission createEnrollmentPermission() { + return new EnrollmentPermission(); + } + + /** + * Create an instance of {@link CAReferenceCollection } + */ + public CAReferenceCollection createCAReferenceCollection() { + return new CAReferenceCollection(); + } + + /** + * Create an instance of {@link CertificateValidity } + */ + public CertificateValidity createCertificateValidity() { + return new CertificateValidity(); + } + + /** + * Create an instance of {@link CAURICollection } + */ + public CAURICollection createCAURICollection() { + return new CAURICollection(); + } + + /** + * Create an instance of {@link PolicyCollection } + */ + public PolicyCollection createPolicyCollection() { + return new PolicyCollection(); + } + + /** + * Create an instance of {@link Revision } + */ + public Revision createRevision() { + return new Revision(); + } + + /** + * Create an instance of {@link OIDReferenceCollection } + */ + public OIDReferenceCollection createOIDReferenceCollection() { + return new OIDReferenceCollection(); + } + + /** + * Create an instance of {@link CA } + */ + public CA createCA() { + return new CA(); + } + + /** + * Create an instance of {@link CertificateEnrollmentPolicy } + */ + public CertificateEnrollmentPolicy createCertificateEnrollmentPolicy() { + return new CertificateEnrollmentPolicy(); + } + + /** + * Create an instance of {@link CryptoProviders } + */ + public CryptoProviders createCryptoProviders() { + return new CryptoProviders(); + } + + /** + * Create an instance of {@link Extension } + */ + public Extension createExtension() { + return new Extension(); + } + + /** + * Create an instance of {@link FilterOIDCollection } + */ + public FilterOIDCollection createFilterOIDCollection() { + return new FilterOIDCollection(); + } + + /** + * Create an instance of {@link CAURI } + */ + public CAURI createCAURI() { + return new CAURI(); + } + + /** + * Create an instance of {@link RARequirements } + */ + public RARequirements createRARequirements() { + return new RARequirements(); + } + + /** + * Create an instance of {@link PrivateKeyAttributes } + */ + public PrivateKeyAttributes createPrivateKeyAttributes() { + return new PrivateKeyAttributes(); + } + + /** + * Create an instance of {@link KeyArchivalAttributes } + */ + public KeyArchivalAttributes createKeyArchivalAttributes() { + return new KeyArchivalAttributes(); + } + + /** + * Create an instance of {@link JAXBElement }{@code <}{@link String }{@code >}} + */ + @XmlElementDecl(namespace = + "http://schemas.microsoft.com/windows/pki/2009/01/enrollmentpolicy" + + "", name = "commonName") + public JAXBElement createCommonName(String value) { + return new JAXBElement(_CommonName_QNAME, String.class, null, value); + } + +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/xcep/beans/PolicyCollection.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/xcep/beans/PolicyCollection.java new file mode 100644 index 0000000000..2dbb7f7e06 --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/xcep/beans/PolicyCollection.java @@ -0,0 +1,84 @@ +/* + * 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.wso2.carbon.mdm.mobileservices.windows.services.xcep.beans; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlType; +import java.util.ArrayList; +import java.util.List; + +/** + *

      Java class for PolicyCollection complex type. + *

      + *

      The following schema fragment specifies the expected content contained within this class. + *

      + *

      + * <complexType name="PolicyCollection">
      + *   <complexContent>
      + *     <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
      + *       <sequence>
      + *         <element name="policy" type="{http://schemas.microsoft
      + *         .com/windows/pki/2009/01/enrollmentpolicy}CertificateEnrollmentPolicy"
      + *         maxOccurs="unbounded"/>
      + *       </sequence>
      + *     </restriction>
      + *   </complexContent>
      + * </complexType>
      + * 
      + */ +@XmlAccessorType(XmlAccessType.FIELD) +@XmlType(name = "PolicyCollection", propOrder = { + "policy" +}) +@SuppressWarnings("unused") +public class PolicyCollection { + + @XmlElement(required = true) + protected List policy; + + /** + * Gets the value of the policy property. + *

      + *

      + * This accessor method returns a reference to the live list, + * not a snapshot. Therefore any modification you make to the + * returned list will be present inside the JAXB object. + * This is why there is not a set method for the policy property. + *

      + *

      + * For example, to add a new item, do as follows: + *

      +	 *    getPolicy().add(newItem);
      +	 * 
      + *

      + *

      + *

      + * Objects of the following type(s) are allowed in the list + * {@link CertificateEnrollmentPolicy } + */ + public List getPolicy() { + if (policy == null) { + policy = new ArrayList(); + } + return this.policy; + } + +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/xcep/beans/PrivateKeyAttributes.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/xcep/beans/PrivateKeyAttributes.java new file mode 100644 index 0000000000..9ecd50ecf5 --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/xcep/beans/PrivateKeyAttributes.java @@ -0,0 +1,193 @@ +/* + * 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.wso2.carbon.mdm.mobileservices.windows.services.xcep.beans; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlSchemaType; +import javax.xml.bind.annotation.XmlType; + +/** + *

      Java class for PrivateKeyAttributes complex type. + *

      + *

      The following schema fragment specifies the expected content contained within this class. + *

      + *

      + * <complexType name="PrivateKeyAttributes">
      + *   <complexContent>
      + *     <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
      + *       <sequence>
      + *         <element name="minimalKeyLength" type="{http://www.w3
      + *         .org/2001/XMLSchema}unsignedInt"/>
      + *         <element name="keySpec" type="{http://www.w3.org/2001/XMLSchema}unsignedInt"/>
      + *         <element name="keyUsageProperty" type="{http://www.w3
      + *         .org/2001/XMLSchema}unsignedInt"/>
      + *         <element name="permissions" type="{http://www.w3.org/2001/XMLSchema}string"/>
      + *         <element name="algorithmOIDReference" type="{http://www.w3.org/2001/XMLSchema}int"/>
      + *         <element name="cryptoProviders" type="{http://schemas.microsoft
      + *         .com/windows/pki/2009/01/enrollmentpolicy}CryptoProviders"/>
      + *       </sequence>
      + *     </restriction>
      + *   </complexContent>
      + * </complexType>
      + * 
      + */ +@XmlAccessorType(XmlAccessType.FIELD) +@XmlType(name = "PrivateKeyAttributes", propOrder = { + "minimalKeyLength", + "keySpec", + "keyUsageProperty", + "permissions", + "algorithmOIDReference", + "cryptoProviders" +}) +@SuppressWarnings("unused") +public class PrivateKeyAttributes { + + @XmlSchemaType(name = "unsignedInt") + protected long minimalKeyLength; + @XmlElement(required = true, type = Long.class, nillable = true) + @XmlSchemaType(name = "unsignedInt") + protected Long keySpec; + @XmlElement(required = true, type = Long.class, nillable = true) + @XmlSchemaType(name = "unsignedInt") + protected Long keyUsageProperty; + @XmlElement(required = true, nillable = true) + protected String permissions; + @XmlElement(required = true, type = Integer.class, nillable = true) + protected Integer algorithmOIDReference; + @XmlElement(required = true, nillable = true) + protected CryptoProviders cryptoProviders; + + /** + * Gets the value of the minimalKeyLength property. + */ + public long getMinimalKeyLength() { + return minimalKeyLength; + } + + /** + * Sets the value of the minimalKeyLength property. + */ + public void setMinimalKeyLength(long value) { + this.minimalKeyLength = value; + } + + /** + * Gets the value of the keySpec property. + * + * @return possible object is + * {@link Long } + */ + public Long getKeySpec() { + return keySpec; + } + + /** + * Sets the value of the keySpec property. + * + * @param value allowed object is + * {@link Long } + */ + public void setKeySpec(Long value) { + this.keySpec = value; + } + + /** + * Gets the value of the keyUsageProperty property. + * + * @return possible object is + * {@link Long } + */ + public Long getKeyUsageProperty() { + return keyUsageProperty; + } + + /** + * Sets the value of the keyUsageProperty property. + * + * @param value allowed object is + * {@link Long } + */ + public void setKeyUsageProperty(Long value) { + this.keyUsageProperty = value; + } + + /** + * Gets the value of the permissions property. + * + * @return possible object is + * {@link String } + */ + public String getPermissions() { + return permissions; + } + + /** + * Sets the value of the permissions property. + * + * @param value allowed object is + * {@link String } + */ + public void setPermissions(String value) { + this.permissions = value; + } + + /** + * Gets the value of the algorithmOIDReference property. + * + * @return possible object is + * {@link Integer } + */ + public Integer getAlgorithmOIDReference() { + return algorithmOIDReference; + } + + /** + * Sets the value of the algorithmOIDReference property. + * + * @param value allowed object is + * {@link Integer } + */ + public void setAlgorithmOIDReference(Integer value) { + this.algorithmOIDReference = value; + } + + /** + * Gets the value of the cryptoProviders property. + * + * @return possible object is + * {@link CryptoProviders } + */ + public CryptoProviders getCryptoProviders() { + return cryptoProviders; + } + + /** + * Sets the value of the cryptoProviders property. + * + * @param value allowed object is + * {@link CryptoProviders } + */ + public void setCryptoProviders(CryptoProviders value) { + this.cryptoProviders = value; + } + +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/xcep/beans/RARequirements.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/xcep/beans/RARequirements.java new file mode 100644 index 0000000000..d5ff3788b3 --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/xcep/beans/RARequirements.java @@ -0,0 +1,119 @@ +/* + * 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.wso2.carbon.mdm.mobileservices.windows.services.xcep.beans; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlSchemaType; +import javax.xml.bind.annotation.XmlType; + +/** + *

      Java class for RARequirements complex type. + *

      + *

      The following schema fragment specifies the expected content contained within this class. + *

      + *

      + * <complexType name="RARequirements">
      + *   <complexContent>
      + *     <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
      + *       <sequence>
      + *         <element name="rASignatures" type="{http://www.w3.org/2001/XMLSchema}unsignedInt"/>
      + *         <element name="rAEKUs" type="{http://schemas.microsoft
      + *         .com/windows/pki/2009/01/enrollmentpolicy}OIDReferenceCollection"/>
      + *         <element name="rAPolicies" type="{http://schemas.microsoft
      + *         .com/windows/pki/2009/01/enrollmentpolicy}OIDReferenceCollection"/>
      + *       </sequence>
      + *     </restriction>
      + *   </complexContent>
      + * </complexType>
      + * 
      + */ +@XmlAccessorType(XmlAccessType.FIELD) +@XmlType(name = "RARequirements", propOrder = { + "raSignatures", + "raekUs", + "raPolicies" +}) +@SuppressWarnings("unused") +public class RARequirements { + + @XmlElement(name = "rASignatures") + @XmlSchemaType(name = "unsignedInt") + protected long raSignatures; + @XmlElement(name = "rAEKUs", required = true, nillable = true) + protected OIDReferenceCollection raekUs; + @XmlElement(name = "rAPolicies", required = true, nillable = true) + protected OIDReferenceCollection raPolicies; + + /** + * Gets the value of the raSignatures property. + */ + public long getRASignatures() { + return raSignatures; + } + + /** + * Sets the value of the raSignatures property. + */ + public void setRASignatures(long value) { + this.raSignatures = value; + } + + /** + * Gets the value of the raekUs property. + * + * @return possible object is + * {@link OIDReferenceCollection } + */ + public OIDReferenceCollection getRAEKUs() { + return raekUs; + } + + /** + * Sets the value of the raekUs property. + * + * @param value allowed object is + * {@link OIDReferenceCollection } + */ + public void setRAEKUs(OIDReferenceCollection value) { + this.raekUs = value; + } + + /** + * Gets the value of the raPolicies property. + * + * @return possible object is + * {@link OIDReferenceCollection } + */ + public OIDReferenceCollection getRAPolicies() { + return raPolicies; + } + + /** + * Sets the value of the raPolicies property. + * + * @param value allowed object is + * {@link OIDReferenceCollection } + */ + public void setRAPolicies(OIDReferenceCollection value) { + this.raPolicies = value; + } + +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/xcep/beans/RequestFilter.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/xcep/beans/RequestFilter.java new file mode 100644 index 0000000000..b1e4198064 --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/xcep/beans/RequestFilter.java @@ -0,0 +1,107 @@ +/* + * 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.wso2.carbon.mdm.mobileservices.windows.services.xcep.beans; + +import org.w3c.dom.Element; + +import javax.xml.bind.annotation.*; +import java.util.ArrayList; +import java.util.List; + +/** + *

      Java class for RequestFilter complex type. + *

      + *

      The following schema fragment specifies the expected content contained within this class. + *

      + *

      + * <complexType name="RequestFilter">
      + *   <complexContent>
      + *     <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
      + *       <sequence>
      + *         <element name="policyOIDs" type="{http://schemas.microsoft
      + *         .com/windows/pki/2009/01/enrollmentpolicy}FilterOIDCollection"/>
      + *         <any processContents='lax' maxOccurs="unbounded" minOccurs="0"/>
      + *       </sequence>
      + *     </restriction>
      + *   </complexContent>
      + * </complexType>
      + * 
      + */ +@XmlAccessorType(XmlAccessType.FIELD) +@XmlType(name = "RequestFilter", propOrder = { + "policyOIDs", + "any" +}) +@SuppressWarnings("unused") +public class RequestFilter { + + @XmlElement(required = true, nillable = true) + protected FilterOIDCollection policyOIDs; + @XmlAnyElement(lax = true) + protected List any; + + /** + * Gets the value of the policyOIDs property. + * + * @return possible object is + * {@link FilterOIDCollection } + */ + public FilterOIDCollection getPolicyOIDs() { + return policyOIDs; + } + + /** + * Sets the value of the policyOIDs property. + * + * @param value allowed object is + * {@link FilterOIDCollection } + */ + public void setPolicyOIDs(FilterOIDCollection value) { + this.policyOIDs = value; + } + + /** + * Gets the value of the any property. + *

      + *

      + * This accessor method returns a reference to the live list, + * not a snapshot. Therefore any modification you make to the + * returned list will be present inside the JAXB object. + * This is why there is not a set method for the any property. + *

      + *

      + * For example, to add a new item, do as follows: + *

      +	 *    getAny().add(newItem);
      +	 * 
      + *

      + *

      + *

      + * Objects of the following type(s) are allowed in the list + * {@link Object } + * {@link Element } + */ + public List getAny() { + if (any == null) { + any = new ArrayList(); + } + return this.any; + } + +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/xcep/beans/Response.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/xcep/beans/Response.java new file mode 100644 index 0000000000..90246f03c6 --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/xcep/beans/Response.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.wso2.carbon.mdm.mobileservices.windows.services.xcep.beans; + +import org.w3c.dom.Element; + +import javax.xml.bind.annotation.*; +import java.util.ArrayList; +import java.util.List; + +/** + *

      Java class for Response complex type. + *

      + *

      The following schema fragment specifies the expected content contained within this class. + *

      + *

      + * <complexType name="Response">
      + *   <complexContent>
      + *     <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
      + *       <sequence>
      + *         <element name="policyID" type="{http://www.w3.org/2001/XMLSchema}string"/>
      + *         <element name="policyFriendlyName" type="{http://www.w3.org/2001/XMLSchema}string"/>
      + *         <element name="nextUpdateHours" type="{http://www.w3.org/2001/XMLSchema}unsignedInt"/>
      + *         <element name="policiesNotChanged" type="{http://www.w3.org/2001/XMLSchema}boolean"/>
      + *         <element name="policies" type="{http://schemas.microsoft
      + *         .com/windows/pki/2009/01/enrollmentpolicy}PolicyCollection"/>
      + *         <any processContents='lax' maxOccurs="unbounded" minOccurs="0"/>
      + *       </sequence>
      + *     </restriction>
      + *   </complexContent>
      + * </complexType>
      + * 
      + */ +@XmlAccessorType(XmlAccessType.FIELD) +@XmlType(name = "Response", propOrder = { + "policyID", + "policyFriendlyName", + "nextUpdateHours", + "policiesNotChanged", + "policies", + "any" +}) +@SuppressWarnings("unused") +public class Response { + + @XmlElement(required = true) + protected String policyID; + @XmlElement(required = true, nillable = true) + protected String policyFriendlyName; + @XmlElement(required = true, type = Long.class, nillable = true) + @XmlSchemaType(name = "unsignedInt") + protected Long nextUpdateHours; + @XmlElement(required = true, type = Boolean.class, nillable = true) + protected Boolean policiesNotChanged; + @XmlElement(required = true, nillable = true) + protected PolicyCollection policies; + @XmlAnyElement(lax = true) + protected List any; + + /** + * Gets the value of the policyID property. + * + * @return possible object is + * {@link String } + */ + public String getPolicyID() { + return policyID; + } + + /** + * Sets the value of the policyID property. + * + * @param value allowed object is + * {@link String } + */ + public void setPolicyID(String value) { + this.policyID = value; + } + + /** + * Gets the value of the policyFriendlyName property. + * + * @return possible object is + * {@link String } + */ + public String getPolicyFriendlyName() { + return policyFriendlyName; + } + + /** + * Sets the value of the policyFriendlyName property. + * + * @param value allowed object is + * {@link String } + */ + public void setPolicyFriendlyName(String value) { + this.policyFriendlyName = value; + } + + /** + * Gets the value of the nextUpdateHours property. + * + * @return possible object is + * {@link Long } + */ + public Long getNextUpdateHours() { + return nextUpdateHours; + } + + /** + * Sets the value of the nextUpdateHours property. + * + * @param value allowed object is + * {@link Long } + */ + public void setNextUpdateHours(Long value) { + this.nextUpdateHours = value; + } + + /** + * Gets the value of the policiesNotChanged property. + * + * @return possible object is + * {@link Boolean } + */ + public Boolean isPoliciesNotChanged() { + return policiesNotChanged; + } + + /** + * Sets the value of the policiesNotChanged property. + * + * @param value allowed object is + * {@link Boolean } + */ + public void setPoliciesNotChanged(Boolean value) { + this.policiesNotChanged = value; + } + + /** + * Gets the value of the policies property. + * + * @return possible object is + * {@link PolicyCollection } + */ + public PolicyCollection getPolicies() { + return policies; + } + + /** + * Sets the value of the policies property. + * + * @param value allowed object is + * {@link PolicyCollection } + */ + public void setPolicies(PolicyCollection value) { + this.policies = value; + } + + /** + * Gets the value of the any property. + *

      + *

      + * This accessor method returns a reference to the live list, + * not a snapshot. Therefore any modification you make to the + * returned list will be present inside the JAXB object. + * This is why there is not a set method for the any property. + *

      + *

      + * For example, to add a new item, do as follows: + *

      +	 *    getAny().add(newItem);
      +	 * 
      + *

      + *

      + *

      + * Objects of the following type(s) are allowed in the list + * {@link Object } + * {@link Element } + */ + public List getAny() { + if (any == null) { + any = new ArrayList(); + } + return this.any; + } + +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/xcep/beans/Revision.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/xcep/beans/Revision.java new file mode 100644 index 0000000000..e838b4b269 --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/xcep/beans/Revision.java @@ -0,0 +1,85 @@ +/* + * 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.wso2.carbon.mdm.mobileservices.windows.services.xcep.beans; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlSchemaType; +import javax.xml.bind.annotation.XmlType; + +/** + *

      Java class for Revision complex type. + *

      + *

      The following schema fragment specifies the expected content contained within this class. + *

      + *

      + * <complexType name="Revision">
      + *   <complexContent>
      + *     <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
      + *       <sequence>
      + *         <element name="majorRevision" type="{http://www.w3.org/2001/XMLSchema}unsignedInt"/>
      + *         <element name="minorRevision" type="{http://www.w3.org/2001/XMLSchema}unsignedInt"/>
      + *       </sequence>
      + *     </restriction>
      + *   </complexContent>
      + * </complexType>
      + * 
      + */ +@XmlAccessorType(XmlAccessType.FIELD) +@XmlType(name = "Revision", propOrder = { + "majorRevision", + "minorRevision" +}) +@SuppressWarnings("unused") +public class Revision { + + @XmlSchemaType(name = "unsignedInt") + protected long majorRevision; + @XmlSchemaType(name = "unsignedInt") + protected long minorRevision; + + /** + * Gets the value of the majorRevision property. + */ + public long getMajorRevision() { + return majorRevision; + } + + /** + * Sets the value of the majorRevision property. + */ + public void setMajorRevision(long value) { + this.majorRevision = value; + } + + /** + * Gets the value of the minorRevision property. + */ + public long getMinorRevision() { + return minorRevision; + } + + /** + * Sets the value of the minorRevision property. + */ + public void setMinorRevision(long value) { + this.minorRevision = value; + } + +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/xcep/beans/SupersededPolicies.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/xcep/beans/SupersededPolicies.java new file mode 100644 index 0000000000..722950ceae --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/xcep/beans/SupersededPolicies.java @@ -0,0 +1,83 @@ +/* + * 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.wso2.carbon.mdm.mobileservices.windows.services.xcep.beans; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlType; +import java.util.ArrayList; +import java.util.List; + +/** + *

      Java class for SupersededPolicies complex type. + *

      + *

      The following schema fragment specifies the expected content contained within this class. + *

      + *

      + * <complexType name="SupersededPolicies">
      + *   <complexContent>
      + *     <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
      + *       <sequence>
      + *         <element ref="{http://schemas.microsoft
      + *         .com/windows/pki/2009/01/enrollmentpolicy}commonName" maxOccurs="unbounded"/>
      + *       </sequence>
      + *     </restriction>
      + *   </complexContent>
      + * </complexType>
      + * 
      + */ +@XmlAccessorType(XmlAccessType.FIELD) +@XmlType(name = "SupersededPolicies", propOrder = { + "commonName" +}) +@SuppressWarnings("unused") +public class SupersededPolicies { + + @XmlElement(required = true) + protected List commonName; + + /** + * Gets the value of the commonName property. + *

      + *

      + * This accessor method returns a reference to the live list, + * not a snapshot. Therefore any modification you make to the + * returned list will be present inside the JAXB object. + * This is why there is not a set method for the commonName property. + *

      + *

      + * For example, to add a new item, do as follows: + *

      +	 *    getCommonName().add(newItem);
      +	 * 
      + *

      + *

      + *

      + * Objects of the following type(s) are allowed in the list + * {@link String } + */ + public List getCommonName() { + if (commonName == null) { + commonName = new ArrayList(); + } + return this.commonName; + } + +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/xcep/beans/package-info.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/xcep/beans/package-info.java new file mode 100644 index 0000000000..f3c0f5b58a --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/xcep/beans/package-info.java @@ -0,0 +1,23 @@ +/* + * 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. + */ + +@javax.xml.bind.annotation.XmlSchema(namespace = "http://schemas.microsoft" + + ".com/windows/pki/2009/01/enrollmentpolicy", + elementFormDefault = javax.xml.bind.annotation.XmlNsForm.QUALIFIED) + +package org.wso2.carbon.mdm.mobileservices.windows.services.xcep.beans; diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/xcep/impl/CertificateEnrollmentPolicyServiceImpl.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/xcep/impl/CertificateEnrollmentPolicyServiceImpl.java new file mode 100644 index 0000000000..25d909f927 --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/java/org/wso2/carbon/mdm/mobileservices/windows/services/xcep/impl/CertificateEnrollmentPolicyServiceImpl.java @@ -0,0 +1,101 @@ +/* + * 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.wso2.carbon.mdm.mobileservices.windows.services.xcep.impl; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.context.PrivilegedCarbonContext; +import org.wso2.carbon.mdm.mobileservices.windows.common.PluginConstants; +import org.wso2.carbon.mdm.mobileservices.windows.services.xcep.CertificateEnrollmentPolicyService; +import org.wso2.carbon.mdm.mobileservices.windows.services.xcep.beans.*; + +import javax.jws.WebService; +import javax.xml.ws.BindingType; +import javax.xml.ws.Holder; +import javax.xml.ws.soap.Addressing; +import javax.xml.ws.soap.SOAPBinding; + +/** + * Implementation class for CertificateEnrollmentPolicyService. + */ +@WebService(endpointInterface = PluginConstants.CERTIFICATE_ENROLLMENT_POLICY_SERVICE_ENDPOINT, + targetNamespace = PluginConstants.CERTIFICATE_ENROLLMENT_POLICY_SERVICE_TARGET_NAMESPACE) +@Addressing(enabled = true, required = true) +@BindingType(value = SOAPBinding.SOAP12HTTP_BINDING) +public class CertificateEnrollmentPolicyServiceImpl implements CertificateEnrollmentPolicyService { + + private static Log log = LogFactory.getLog(CertificateEnrollmentPolicyServiceImpl.class); + + /** + * This method implements the MS-XCEP protocol for certificate enrollment policy service. + * + * @param client - Included lastUpdate and preferredLanguage tags + * @param requestFilter - Policy constrain tag + * @param response - Response which includes minimal key length, hash algorithm, policy + * schema, policy OID reference + * @param caCollection - Contains the issuers for the certificate enrollment policies + * @param oidCollection - Contains the collection of OIDs for the response + */ + @Override + public void getPolicies(Client client, RequestFilter requestFilter, + Holder response, Holder caCollection, + Holder oidCollection) { + + if (log.isDebugEnabled()) { + log.debug("Enrolment certificate policy end point was triggered by device."); + } + + Response responseElement = new Response(); + OIDCollection oidCollectionElement = new OIDCollection(); + CACollection caCollectionElement = new CACollection(); + + PolicyCollection policyCollectionElement = new PolicyCollection(); + + CertificateEnrollmentPolicy certEnrollmentPolicyElement = new CertificateEnrollmentPolicy(); + Attributes attributeElement = new Attributes(); + PrivateKeyAttributes privateKeyAttributeElement = new PrivateKeyAttributes(); + + privateKeyAttributeElement. + setMinimalKeyLength(PluginConstants.CertificateEnrolmentPolicy.MINIMAL_KEY_LENGTH); + + attributeElement.setPolicySchema(PluginConstants.CertificateEnrolmentPolicy.POLICY_SCHEMA); + attributeElement.setPrivateKeyAttributes(privateKeyAttributeElement); + attributeElement.setHashAlgorithmOIDReference(PluginConstants.CertificateEnrolmentPolicy. + HASH_ALGORITHM_OID_REFERENCE); + certEnrollmentPolicyElement.setPolicyOIDReference(PluginConstants.CertificateEnrolmentPolicy. + OID_REFERENCE); + certEnrollmentPolicyElement.setAttributes(attributeElement); + policyCollectionElement.getPolicy().add(certEnrollmentPolicyElement); + responseElement.setPolicies(policyCollectionElement); + response.value = responseElement; + + OID oidElement = new OID(); + oidElement.setValue(PluginConstants.CertificateEnrolmentPolicy.OID); + oidElement.setGroup(PluginConstants.CertificateEnrolmentPolicy.OID_GROUP); + oidElement.setOIDReferenceID(PluginConstants.CertificateEnrolmentPolicy.OID_REFERENCE_ID); + oidElement.setDefaultName(PluginConstants.CertificateEnrolmentPolicy.OID_DEFAULT_NAME); + + oidCollectionElement.getOID().add(oidElement); + caCollection.value = caCollectionElement; + oidCollection.value = oidCollectionElement; + + PrivilegedCarbonContext.endTenantFlow(); + + } +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/resources/properties.xml b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/resources/properties.xml new file mode 100644 index 0000000000..7df287f688 --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/resources/properties.xml @@ -0,0 +1,26 @@ + + + Federated + wso2carbon + cacert + CN=mdmcn + 3 + 300 + wso2.com + \ No newline at end of file diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/resources/wap-provisioning.xml b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/resources/wap-provisioning.xml new file mode 100644 index 0000000000..e6729e0f4c --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/resources/wap-provisioning.xml @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/resources/wso2mdm.jks b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/resources/wso2mdm.jks new file mode 100644 index 0000000000..66b68ea395 Binary files /dev/null and b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/resources/wso2mdm.jks differ diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/webapp/META-INF/permissions.xml b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/webapp/META-INF/permissions.xml new file mode 100644 index 0000000000..feb34b0981 --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/webapp/META-INF/permissions.xml @@ -0,0 +1,142 @@ + + + + + + + + + Lock + /device-mgt/windows/operation/lock + /services/windows/operation/lock + POST + emm_admin,emm_user + + + + Enterprise Wipe + /device-mgt/windows/operation/wipe-data + /services/windows/operation/wipe-data + POST + emm_admin + + + + Ring + /device-mgt/windows/operation/ring-device + /services/windows/operation/ring-device + POST + emm_admin,emm_user + + + + Disenroll + /device-mgt/windows/operation/disenroll + /services/windows/operation/disenroll + POST + emm_admin,emm_user + + + + LockReset + /device-mgt/windows/operation/lock-reset + /services/windows/operation/lock-reset + POST + emm_admin,emm_user + + + + + Get CSR Policy + /device-mgt/windows/devices/getPolicy + /services/certificatepolicy/xcep + POST + emm_windows_agent + + + Get BST + /device-mgt/windows/devices/getBST + /services/federated/bst/authentication + POST + emm_windows_agent + + + Provide CSR + /device-mgt/windows/devices/requestSecurityToken + /services/deviceenrolment/wstep + POST + emm_windows_agent + + + GetLicense + /device-mgt/windows/devices/license + /services/device/license + GET + emm_windows_agent + + + + View Policies + /device-mgt/windows/policies/view + /services/policy/* + GET + emm_admin + + + + View Policy Features + /device-mgt/windows/policies/view + /services/policy/features/* + GET + emm_admin + + + + View Tenant configuration + /device-mgt/windows/tenant/configuration + /services/configuration + GET + emm_admin + + + + Add Tenant configuration + /device-mgt/windows/tenant/configuration + /services/configuration + POST + emm_admin + + + + Update Tenant configuration + /device-mgt/windows/tenant/configuration + /services/configuration + PUT + emm_admin + + \ No newline at end of file diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/webapp/META-INF/webapp-classloading.xml b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/webapp/META-INF/webapp-classloading.xml new file mode 100644 index 0000000000..0371f7c294 --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/webapp/META-INF/webapp-classloading.xml @@ -0,0 +1,35 @@ + + + + + + + + + false + + + Carbon + diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/webapp/WEB-INF/cxf-servlet.xml b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/webapp/WEB-INF/cxf-servlet.xml new file mode 100644 index 0000000000..23cd504ba6 --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/webapp/WEB-INF/cxf-servlet.xml @@ -0,0 +1,207 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/webapp/WEB-INF/web.xml b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 0000000000..93b441ed4f --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,63 @@ + + + + Windows-Agent-Webapp + + + contextConfigLocation + /WEB-INF/cxf-servlet.xml + + + + org.wso2.carbon.mdm.mobileservices.windows.common.util.ConfigInitializerContextListener + + + + + JAX-WS/JAX-RS-windows Endpoint + JAX-WS/JAX-RS-windows Servlet + JAXServlet-windows + + org.apache.cxf.transport.servlet.CXFServlet + + 1 + + + JAXServlet-windows + /services/* + + + 60 + + + + doAuthentication + true + + + nonSecuredEndPoints + /services/discovery/get,/services/discovery/post,/services/certificatepolicy/xcep, + ,/services/deviceenrolment/wstep,/services/syncml/devicemanagement/request + + \ No newline at end of file diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/webapp/servicelist.css b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/webapp/servicelist.css new file mode 100644 index 0000000000..e6eacadbb0 --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows.agent/src/main/webapp/servicelist.css @@ -0,0 +1,125 @@ +@CHARSET "ISO-8859-1"; + +/* http://meyerweb.com/eric/tools/css/reset/ + v2.0 | 20110126 + License: none (public domain) +*/ + +html, body, div, span, applet, object, iframe, +h1, h2, h3, h4, h5, h6, p, blockquote, pre, +a, abbr, acronym, address, big, cite, code, +del, dfn, em, img, ins, kbd, q, s, samp, +small, strike, strong, sub, sup, tt, var, +b, u, i, center, +dl, dt, dd, ol, ul, li, +fieldset, form, label, legend, +table, caption, tbody, tfoot, thead, tr, th, td, +article, aside, canvas, details, embed, +figure, figcaption, footer, header, hgroup, +menu, nav, output, ruby, section, summary, +time, mark, audio, video { + margin: 0; + padding: 0; + border: 0; + font-size: 100%; + font: inherit; + vertical-align: baseline; +} + +/* HTML5 display-role reset for older browsers */ +article, aside, details, figcaption, figure, +footer, header, hgroup, menu, nav, section { + display: block; +} + +html { + background: #efefef; +} + +body { + line-height: 1; + width: 960px; + margin: auto; + background: white; + padding: 10px; + box-shadow: 0px 0px 5px #CCC; + font-family: "Lucida Grande", "Lucida Sans", "Microsoft Sans Serif", "Lucida Sans Unicode", "Verdana", "Sans-serif", "trebuchet ms" !important; + +} + +ol, ul { + list-style: none; +} + +blockquote, q { + quotes: none; +} + +blockquote:before, blockquote:after, +q:before, q:after { + content: ''; + content: none; +} + +table { + border-collapse: collapse; + border-spacing: 0; + width: 960px; + border: solid 1px #ccc; +} + +table a { + font-size: 12px; + color: #1e90ff; + padding: 7px; + float: left;; +} + +.heading { + font-size: 18px; + margin-top: 20px; + float: left; + color: #0067B1; + margin-bottom: 20px; + padding-top: 20px; +} + +.field { + font-weight: normal; + width: 120px; + font-size: 12px; + float: left; + padding: 7px; + clear: left; +} + +.value { + font-weight: bold; + font-size: 12px; + float: left; + padding: 7px; + clear: right; +} + +.porttypename { + font-weight: bold; + font-size: 14px; +} + +UL { + margin-top: 0; +} + +LI { + font-weight: normal; + font-size: 12px; + margin-top: 10px; +} + +TD { + border: 1px solid #ccc; + vertical-align: text-top; + padding: 5px; +} + + diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows/pom.xml b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows/pom.xml new file mode 100644 index 0000000000..2440f0fffc --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows/pom.xml @@ -0,0 +1,176 @@ + + + + + + + windows-plugin + org.wso2.carbon.devicemgt-plugins + 2.1.0-SNAPSHOT + ../pom.xml + + + 4.0.0 + org.wso2.carbon.device.mgt.mobile.windows + bundle + WSO2 Carbon - Mobile Device Management Windows Impl + WSO2 Carbon - Mobile Device Management Windows Implementation + http://wso2.org + + + + + org.apache.felix + maven-scr-plugin + + + org.apache.felix + maven-bundle-plugin + 1.4.0 + true + + + ${project.artifactId} + ${project.artifactId} + ${carbon.devicemgt.plugins.version} + Device Management Mobile Windows Impl Bundle + org.wso2.carbon.device.mgt.mobile.windows.internal + + org.osgi.framework, + org.osgi.service.component, + org.apache.commons.logging, + javax.xml.bind.*, + javax.naming, + javax.sql, + javax.xml.bind.annotation.*, + javax.xml.parsers.*;resolution:=optional, + org.w3c.dom, + org.wso2.carbon.core, + org.wso2.carbon.context, + org.wso2.carbon.utils.*, + org.wso2.carbon.device.mgt.common.*, + org.wso2.carbon.device.mgt.mobile.*, + org.wso2.carbon.ndatasource.core, + org.wso2.carbon.policy.mgt.common.*, + org.wso2.carbon.policy.mgt.core.*, + org.wso2.carbon.registry.core, + org.wso2.carbon.registry.core.exceptions, + org.wso2.carbon.registry.core.service, + org.wso2.carbon.registry.core.session, + org.wso2.carbon.registry.api, + org.wso2.carbon.device.mgt.extensions.license.mgt.registry, + com.google.gson.* + + + !org.wso2.carbon.device.mgt.mobile.windows.internal, + org.wso2.carbon.device.mgt.mobile.windows.* + + + + + + + + + + + + + + + + + + + + + + + + org.wso2.carbon.devicemgt-plugins + org.wso2.carbon.device.mgt.mobile + + + org.eclipse.osgi + org.eclipse.osgi + + + org.eclipse.osgi + org.eclipse.osgi.services + + + org.wso2.carbon + org.wso2.carbon.core + + + org.wso2.carbon + org.wso2.carbon.logging + + + org.wso2.carbon + org.wso2.carbon.utils + + + org.wso2.carbon.devicemgt + org.wso2.carbon.device.mgt.common + + + org.wso2.carbon.devicemgt + org.wso2.carbon.device.mgt.extensions + + + org.wso2.carbon + org.wso2.carbon.ndatasource.core + + + org.wso2.carbon.devicemgt + org.wso2.carbon.policy.mgt.common + + + org.wso2.carbon.devicemgt + org.wso2.carbon.policy.mgt.core + + + org.wso2.carbon + org.wso2.carbon.registry.api + + + org.wso2.carbon + org.wso2.carbon.registry.core + + + org.testng + testng + + + org.apache.tomcat.wso2 + jdbc-pool + + + com.h2database.wso2 + h2-database-engine + test + + + com.google.code.gson + gson + + + diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows/src/main/java/org/wso2/carbon/device/mgt/mobile/windows/impl/WindowsDeviceManagementService.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows/src/main/java/org/wso2/carbon/device/mgt/mobile/windows/impl/WindowsDeviceManagementService.java new file mode 100644 index 0000000000..36d3bb9633 --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows/src/main/java/org/wso2/carbon/device/mgt/mobile/windows/impl/WindowsDeviceManagementService.java @@ -0,0 +1,108 @@ +/* + * 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.wso2.carbon.device.mgt.mobile.windows.impl; + +import org.wso2.carbon.device.mgt.common.DeviceIdentifier; +import org.wso2.carbon.device.mgt.common.DeviceManagementException; +import org.wso2.carbon.device.mgt.common.DeviceManager; +import org.wso2.carbon.device.mgt.common.app.mgt.Application; +import org.wso2.carbon.device.mgt.common.app.mgt.ApplicationManagementException; +import org.wso2.carbon.device.mgt.common.app.mgt.ApplicationManager; +import org.wso2.carbon.device.mgt.common.operation.mgt.Operation; +import org.wso2.carbon.device.mgt.common.spi.DeviceManagementService; + +import java.util.List; + +/** + * This represents the Windows implementation of DeviceManagerService. + */ +public class WindowsDeviceManagementService implements DeviceManagementService { + + private DeviceManager deviceManager; + public static final String DEVICE_TYPE_WINDOWS = "windows"; + public static final String DEVICE_TYPE_TENANT = "carbon.super"; + + @Override + public String getType() { + return WindowsDeviceManagementService.DEVICE_TYPE_WINDOWS; + } + + @Override + public String getProviderTenantDomain() { + return DEVICE_TYPE_TENANT; + } + + @Override + public boolean isSharedWithAllTenants() { + return true; + } + + @Override + public void init() throws DeviceManagementException { + this.deviceManager = new WindowsDeviceManager(); + } + + @Override + public DeviceManager getDeviceManager() { + return deviceManager; + } + + @Override + public ApplicationManager getApplicationManager() { + return null; + } + + @Override public void notifyOperationToDevices(Operation operation, List deviceIdentifiers) + throws DeviceManagementException { + + } + + @Override + public Application[] getApplications(String s, int i, int i2) throws ApplicationManagementException { + return new Application[0]; + } + + @Override + public void updateApplicationStatus(DeviceIdentifier deviceIdentifier, Application application, + String s) throws ApplicationManagementException { + + } + + @Override + public String getApplicationStatus(DeviceIdentifier deviceIdentifier, + Application application) throws ApplicationManagementException { + return null; + } + + @Override public void installApplicationForDevices(Operation operation, List deviceIdentifiers) + throws ApplicationManagementException { + + } + + @Override public void installApplicationForUsers(Operation operation, List strings) + throws ApplicationManagementException { + + } + + @Override public void installApplicationForUserRoles(Operation operation, List strings) + throws ApplicationManagementException { + + } + +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows/src/main/java/org/wso2/carbon/device/mgt/mobile/windows/impl/WindowsDeviceManager.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows/src/main/java/org/wso2/carbon/device/mgt/mobile/windows/impl/WindowsDeviceManager.java new file mode 100644 index 0000000000..f3864fe4a4 --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows/src/main/java/org/wso2/carbon/device/mgt/mobile/windows/impl/WindowsDeviceManager.java @@ -0,0 +1,308 @@ +/* + * 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.wso2.carbon.device.mgt.mobile.windows.impl; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.device.mgt.common.*; +import org.wso2.carbon.device.mgt.common.configuration.mgt.TenantConfiguration; +import org.wso2.carbon.device.mgt.common.license.mgt.License; +import org.wso2.carbon.device.mgt.common.license.mgt.LicenseManagementException; +import org.wso2.carbon.device.mgt.common.license.mgt.LicenseManager; +import org.wso2.carbon.device.mgt.extensions.license.mgt.registry.RegistryBasedLicenseManager; +import org.wso2.carbon.device.mgt.mobile.common.MobileDeviceMgtPluginException; +import org.wso2.carbon.device.mgt.mobile.common.MobilePluginConstants; +import org.wso2.carbon.device.mgt.mobile.dao.AbstractMobileDeviceManagementDAOFactory; +import org.wso2.carbon.device.mgt.mobile.dao.MobileDeviceManagementDAOException; +import org.wso2.carbon.device.mgt.mobile.dto.MobileDevice; +import org.wso2.carbon.device.mgt.mobile.windows.impl.dao.WindowsDAOFactory; +import org.wso2.carbon.device.mgt.mobile.windows.impl.util.WindowsPluginUtils; +import org.wso2.carbon.device.mgt.mobile.util.MobileDeviceManagementUtil; +import org.wso2.carbon.registry.api.RegistryException; +import org.wso2.carbon.registry.api.Resource; + +import javax.xml.bind.JAXBContext; +import javax.xml.bind.JAXBException; +import javax.xml.bind.Marshaller; +import javax.xml.bind.Unmarshaller; +import java.io.StringReader; +import java.io.StringWriter; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.List; + +public class WindowsDeviceManager implements DeviceManager { + + private AbstractMobileDeviceManagementDAOFactory daoFactory; + private LicenseManager licenseManager; + private FeatureManager featureManager = new WindowsFeatureManager(); + private static final Log log = LogFactory.getLog(WindowsDeviceManagementService.class); + + public WindowsDeviceManager() { + this.daoFactory = new WindowsDAOFactory(); + this.licenseManager = new RegistryBasedLicenseManager(); + + License defaultLicense = WindowsPluginUtils.getDefaultLicense(); + + try { + if (licenseManager.getLicense(WindowsDeviceManagementService.DEVICE_TYPE_WINDOWS, + MobilePluginConstants.LANGUAGE_CODE_ENGLISH_US) == null) { + licenseManager.addLicense(WindowsDeviceManagementService.DEVICE_TYPE_WINDOWS, defaultLicense); + } + featureManager.addSupportedFeaturesToDB(); + } catch (LicenseManagementException e) { + log.error("Error occurred while adding default license for Windows devices", e); + } catch (DeviceManagementException e) { + log.error("Error occurred while adding supported device features for Windows platform", e); + } + } + + @Override + public FeatureManager getFeatureManager() { + return featureManager; + } + + @Override + public boolean saveConfiguration(TenantConfiguration tenantConfiguration) + throws DeviceManagementException { + boolean status; + Resource resource; + try { + if (log.isDebugEnabled()) { + log.debug("Persisting windows configurations in Registry"); + } + String resourcePath = MobileDeviceManagementUtil.getPlatformConfigPath( + DeviceManagementConstants. + MobileDeviceTypes.MOBILE_DEVICE_TYPE_WINDOWS); + StringWriter writer = new StringWriter(); + JAXBContext context = JAXBContext.newInstance(TenantConfiguration.class); + Marshaller marshaller = context.createMarshaller(); + marshaller.marshal(tenantConfiguration, writer); + + resource = MobileDeviceManagementUtil.getConfigurationRegistry().newResource(); + resource.setContent(writer.toString()); + resource.setMediaType(MobilePluginConstants.MEDIA_TYPE_XML); + MobileDeviceManagementUtil.putRegistryResource(resourcePath, resource); + status = true; + } catch (MobileDeviceMgtPluginException e) { + throw new DeviceManagementException( + "Error occurred while retrieving the Registry instance : " + e.getMessage(), e); + } catch (RegistryException e) { + throw new DeviceManagementException( + "Error occurred while persisting the Registry resource of Windows configuration : " + e.getMessage(), e); + } catch (JAXBException e) { + throw new DeviceManagementException( + "Error occurred while parsing the Windows configuration : " + e.getMessage(), e); + } + return status; + } + + @Override + public TenantConfiguration getConfiguration() throws DeviceManagementException { + Resource resource; + try { + String windowsTenantRegistryPath = + MobileDeviceManagementUtil.getPlatformConfigPath(DeviceManagementConstants. + MobileDeviceTypes.MOBILE_DEVICE_TYPE_WINDOWS); + resource = MobileDeviceManagementUtil.getRegistryResource(windowsTenantRegistryPath); + if (resource != null) { + JAXBContext context = JAXBContext.newInstance(TenantConfiguration.class); + Unmarshaller unmarshaller = context.createUnmarshaller(); + return (TenantConfiguration) unmarshaller.unmarshal( + new StringReader(new String((byte[]) resource.getContent(), Charset. + forName(MobilePluginConstants.CHARSET_UTF8)))); + } + return null; + } catch (MobileDeviceMgtPluginException e) { + throw new DeviceManagementException( + "Error occurred while retrieving the Registry instance : " + e.getMessage(), e); + } catch (JAXBException e) { + throw new DeviceManagementException( + "Error occurred while parsing the Windows configuration : " + e.getMessage(), e); + } catch (RegistryException e) { + throw new DeviceManagementException( + "Error occurred while retrieving the Registry resource of Windows configuration : " + e.getMessage(), e); + } + } + + @Override + public boolean modifyEnrollment(Device device) throws DeviceManagementException { + boolean status; + MobileDevice mobileDevice = MobileDeviceManagementUtil.convertToMobileDevice(device); + try { + if (log.isDebugEnabled()) { + log.debug("Modifying the Windows device enrollment data"); + } + WindowsDAOFactory.beginTransaction(); + status = daoFactory.getMobileDeviceDAO().updateMobileDevice(mobileDevice); + WindowsDAOFactory.commitTransaction(); + } catch (MobileDeviceManagementDAOException e) { + WindowsDAOFactory.rollbackTransaction(); + throw new DeviceManagementException("Error while updating the enrollment of the Windows device : " + + device.getDeviceIdentifier(), e); + } finally { + WindowsDAOFactory.closeConnection(); + } + return status; + } + + @Override + public boolean disenrollDevice(DeviceIdentifier deviceId) throws DeviceManagementException { + //Here we don't have anything specific to do. Hence returning. + return true; + } + + @Override + public boolean isEnrolled(DeviceIdentifier deviceId) throws DeviceManagementException { + boolean isEnrolled = false; + try { + if (log.isDebugEnabled()) { + log.debug("Checking the enrollment of Windows device : " + deviceId.getId()); + } + MobileDevice mobileDevice = + daoFactory.getMobileDeviceDAO().getMobileDevice(deviceId.getId()); + if (mobileDevice != null) { + isEnrolled = true; + } + } catch (MobileDeviceManagementDAOException e) { + String msg = "Error while checking the enrollment status of Windows device : " + + deviceId.getId(); + throw new DeviceManagementException(msg, e); + } + return isEnrolled; + } + + @Override + public boolean isActive(DeviceIdentifier deviceId) throws DeviceManagementException { + return true; + } + + @Override + public boolean setActive(DeviceIdentifier deviceId, boolean status) + throws DeviceManagementException { + return true; + } + + public List getAllDevices() throws DeviceManagementException { + List devices = null; + try { + if (log.isDebugEnabled()) { + log.debug("Fetching the details of all Windows devices"); + } + WindowsDAOFactory.openConnection(); + List mobileDevices = daoFactory.getMobileDeviceDAO().getAllMobileDevices(); + if (mobileDevices != null) { + devices = new ArrayList<>(mobileDevices.size()); + for (MobileDevice mobileDevice : mobileDevices) { + devices.add(MobileDeviceManagementUtil.convertToDevice(mobileDevice)); + } + } + } catch (MobileDeviceManagementDAOException e) { + throw new DeviceManagementException("Error occurred while fetching all Windows devices", e); + } finally { + WindowsDAOFactory.closeConnection(); + } + return devices; + } + + @Override + public Device getDevice(DeviceIdentifier deviceId) throws DeviceManagementException { + Device device = null; + try { + if (log.isDebugEnabled()) { + log.debug("Getting the details of Windows device : '" + deviceId.getId() + "'"); + } + WindowsDAOFactory.openConnection(); + MobileDevice mobileDevice = daoFactory.getMobileDeviceDAO(). + getMobileDevice(deviceId.getId()); + device = MobileDeviceManagementUtil.convertToDevice(mobileDevice); + } catch (MobileDeviceManagementDAOException e) { + throw new DeviceManagementException( + "Error occurred while fetching the Windows device: '" + deviceId.getId() + "'", e); + } finally { + WindowsDAOFactory.closeConnection(); + } + return device; + } + + @Override + public boolean setOwnership(DeviceIdentifier deviceId, String ownershipType) + throws DeviceManagementException { + return true; + } + + @Override + public boolean isClaimable(DeviceIdentifier deviceIdentifier) throws DeviceManagementException { + return false; + } + + @Override + public boolean setStatus(DeviceIdentifier deviceIdentifier, String currentUser, + EnrolmentInfo.Status status) throws DeviceManagementException { + return false; + } + + @Override + public License getLicense(String languageCode) throws LicenseManagementException { + return licenseManager.getLicense(WindowsDeviceManagementService.DEVICE_TYPE_WINDOWS, languageCode); + } + + @Override + public void addLicense(License license) throws LicenseManagementException { + licenseManager.addLicense(WindowsDeviceManagementService.DEVICE_TYPE_WINDOWS, license); + } + + @Override + public boolean requireDeviceAuthorization() { + return false; + } + + @Override + public boolean updateDeviceInfo(DeviceIdentifier deviceIdentifier, + Device device) throws DeviceManagementException { + return true; + } + + @Override + public boolean enrollDevice(Device device) throws DeviceManagementException { + boolean status = false; + MobileDevice mobileDevice = MobileDeviceManagementUtil.convertToMobileDevice(device); + try { + if (log.isDebugEnabled()) { + log.debug("Enrolling a new windows device : " + device.getDeviceIdentifier()); + } + boolean isEnrolled = this.isEnrolled( + new DeviceIdentifier(device.getDeviceIdentifier(), device.getType())); + if (isEnrolled) { + this.modifyEnrollment(device); + } else { + WindowsDAOFactory.beginTransaction(); + status = daoFactory.getMobileDeviceDAO().addMobileDevice(mobileDevice); + WindowsDAOFactory.commitTransaction(); + } + } catch (MobileDeviceManagementDAOException e) { + WindowsDAOFactory.rollbackTransaction(); + String msg = + "Error while enrolling the windows device : " + device.getDeviceIdentifier(); + throw new DeviceManagementException(msg, e); + } + return status; + } + +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows/src/main/java/org/wso2/carbon/device/mgt/mobile/windows/impl/WindowsFeatureManager.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows/src/main/java/org/wso2/carbon/device/mgt/mobile/windows/impl/WindowsFeatureManager.java new file mode 100644 index 0000000000..6b501376a7 --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows/src/main/java/org/wso2/carbon/device/mgt/mobile/windows/impl/WindowsFeatureManager.java @@ -0,0 +1,196 @@ +/* + * 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.wso2.carbon.device.mgt.mobile.windows.impl; + +import org.wso2.carbon.device.mgt.common.DeviceManagementException; +import org.wso2.carbon.device.mgt.common.Feature; +import org.wso2.carbon.device.mgt.common.FeatureManager; +import org.wso2.carbon.device.mgt.mobile.dao.MobileDeviceManagementDAOException; +import org.wso2.carbon.device.mgt.mobile.dao.MobileDeviceManagementDAOFactory; +import org.wso2.carbon.device.mgt.mobile.dao.MobileFeatureDAO; +import org.wso2.carbon.device.mgt.mobile.dto.MobileFeature; +import org.wso2.carbon.device.mgt.mobile.windows.impl.dao.WindowsDAOFactory; +import org.wso2.carbon.device.mgt.mobile.util.MobileDeviceManagementUtil; + +import java.util.ArrayList; +import java.util.List; + +public class WindowsFeatureManager implements FeatureManager { + + private MobileFeatureDAO featureDAO; + + public WindowsFeatureManager() { + MobileDeviceManagementDAOFactory daoFactory = new WindowsDAOFactory(); + this.featureDAO = daoFactory.getMobileFeatureDAO(); + } + + @Override + public boolean addFeature(Feature feature) throws DeviceManagementException { + try { + WindowsDAOFactory.beginTransaction(); + MobileFeature mobileFeature = MobileDeviceManagementUtil.convertToMobileFeature(feature); + featureDAO.addFeature(mobileFeature); + WindowsDAOFactory.commitTransaction(); + return true; + } catch (MobileDeviceManagementDAOException e) { + WindowsDAOFactory.rollbackTransaction(); + throw new DeviceManagementException("Error occurred while adding the feature", e); + } finally { + WindowsDAOFactory.closeConnection(); + } + } + + @Override + public boolean addFeatures(List features) throws DeviceManagementException { + List mobileFeatures = new ArrayList(features.size()); + for (Feature feature : features) { + mobileFeatures.add(MobileDeviceManagementUtil.convertToMobileFeature(feature)); + } + try { + WindowsDAOFactory.beginTransaction(); + featureDAO.addFeatures(mobileFeatures); + WindowsDAOFactory.commitTransaction(); + return true; + } catch (MobileDeviceManagementDAOException e) { + WindowsDAOFactory.rollbackTransaction(); + throw new DeviceManagementException("Error occurred while adding the features", e); + } finally { + WindowsDAOFactory.closeConnection(); + } + } + + @Override + public Feature getFeature(String name) throws DeviceManagementException { + try { + WindowsDAOFactory.openConnection(); + MobileFeature mobileFeature = featureDAO.getFeatureByCode(name); + Feature feature = MobileDeviceManagementUtil.convertToFeature(mobileFeature); + return feature; + } catch (MobileDeviceManagementDAOException e) { + throw new DeviceManagementException("Error occurred while retrieving the feature", e); + } finally { + WindowsDAOFactory.closeConnection(); + } + } + + @Override + public List getFeatures() throws DeviceManagementException { + + try { + WindowsDAOFactory.openConnection(); + List mobileFeatures = featureDAO.getAllFeatures(); + List featureList = new ArrayList(mobileFeatures.size()); + for (MobileFeature mobileFeature : mobileFeatures) { + featureList.add(MobileDeviceManagementUtil.convertToFeature(mobileFeature)); + } + return featureList; + } catch (MobileDeviceManagementDAOException e) { + throw new DeviceManagementException("Error occurred while retrieving the list of features registered for " + + "Windows platform", e); + } finally { + WindowsDAOFactory.closeConnection(); + } + } + + @Override + public boolean removeFeature(String code) throws DeviceManagementException { + boolean status; + try { + WindowsDAOFactory.beginTransaction(); + featureDAO.deleteFeatureByCode(code); + WindowsDAOFactory.commitTransaction(); + status = true; + } catch (MobileDeviceManagementDAOException e) { + WindowsDAOFactory.rollbackTransaction(); + throw new DeviceManagementException("Error occurred while removing the feature", e); + } finally { + WindowsDAOFactory.closeConnection(); + } + return status; + } + + @Override + public boolean addSupportedFeaturesToDB() throws DeviceManagementException { + synchronized (this) { + List supportedFeatures = getSupportedFeatures(); + List existingFeatures = this.getFeatures(); + List missingFeatures = MobileDeviceManagementUtil. + getMissingFeatures(supportedFeatures, existingFeatures); + if (missingFeatures.size() > 0) { + return this.addFeatures(missingFeatures); + } + return true; + } + } + + /** + * Get supported Windows features. + * + * @return Supported features. + */ + public static List getSupportedFeatures() { + List supportedFeatures = new ArrayList(); + Feature feature = new Feature(); + feature.setCode("DEVICE_LOCK"); + feature.setName("Device Lock"); + feature.setDescription("Lock the device"); + supportedFeatures.add(feature); + feature = new Feature(); + feature.setCode("CAMERA"); + feature.setName("camera"); + feature.setDescription("Enable or disable camera"); + supportedFeatures.add(feature); + feature = new Feature(); + feature.setCode("DEVICE_INFO"); + feature.setName("Device info"); + feature.setDescription("Request device information"); + supportedFeatures.add(feature); + feature = new Feature(); + feature.setCode("WIPE_DATA"); + feature.setName("Wipe Data"); + feature.setDescription("Factory reset the device"); + supportedFeatures.add(feature); + feature = new Feature(); + feature.setCode("ENCRYPT_STORAGE"); + feature.setName("Encrypt storage"); + feature.setDescription("Encrypt storage"); + supportedFeatures.add(feature); + feature = new Feature(); + feature.setCode("DEVICE_RING"); + feature.setName("Ring"); + feature.setDescription("Ring the device"); + supportedFeatures.add(feature); + feature = new Feature(); + feature.setCode("PASSCODE_POLICY"); + feature.setName("Password Policy"); + feature.setDescription("Set passcode policy"); + supportedFeatures.add(feature); + feature = new Feature(); + feature.setCode("DISENROLL"); + feature.setName("DisEnroll"); + feature.setDescription("DisEnroll the device"); + supportedFeatures.add(feature); + feature = new Feature(); + feature.setCode("LOCK_RESET"); + feature.setName("LockReset"); + feature.setDescription("Lock Reset device"); + supportedFeatures.add(feature); + return supportedFeatures; + } +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows/src/main/java/org/wso2/carbon/device/mgt/mobile/windows/impl/WindowsPolicyMonitoringService.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows/src/main/java/org/wso2/carbon/device/mgt/mobile/windows/impl/WindowsPolicyMonitoringService.java new file mode 100644 index 0000000000..3c9c0f3abd --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows/src/main/java/org/wso2/carbon/device/mgt/mobile/windows/impl/WindowsPolicyMonitoringService.java @@ -0,0 +1,73 @@ +/* + * 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.wso2.carbon.device.mgt.mobile.windows.impl; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.device.mgt.common.Device; +import org.wso2.carbon.device.mgt.common.DeviceIdentifier; +import org.wso2.carbon.device.mgt.common.DeviceManagementConstants; +import org.wso2.carbon.policy.mgt.common.Policy; +import org.wso2.carbon.policy.mgt.common.monitor.ComplianceData; +import org.wso2.carbon.policy.mgt.common.monitor.ComplianceFeature; +import org.wso2.carbon.policy.mgt.common.monitor.PolicyComplianceException; +import org.wso2.carbon.policy.mgt.common.spi.PolicyMonitoringService; + +import java.util.ArrayList; +import java.util.List; + +public class WindowsPolicyMonitoringService implements PolicyMonitoringService { + + private static Log log = LogFactory.getLog(WindowsPolicyMonitoringService.class); + + @Override + public void notifyDevices(List list) throws PolicyComplianceException { + + } + + @Override + public ComplianceData checkPolicyCompliance(DeviceIdentifier deviceIdentifier, Policy policy, Object compliancePayload) + throws PolicyComplianceException { + if (log.isDebugEnabled()) { + log.debug("checking policy compliance status of device '" + deviceIdentifier.getId() + "'"); + } + List complianceFeatures = (List) compliancePayload; + List nonComplianceFeatures = new ArrayList<>(); + ComplianceData complianceData = new ComplianceData(); + + if (policy == null || compliancePayload == null) { + return complianceData; + } + + for (ComplianceFeature complianceFeature : complianceFeatures) { + if (!complianceFeature.isCompliant()) { + complianceData.setStatus(false); + nonComplianceFeatures.add(complianceFeature); + break; + } + } + complianceData.setComplianceFeatures(nonComplianceFeatures); + return complianceData; + } + + @Override + public String getType() { + return DeviceManagementConstants.MobileDeviceTypes.MOBILE_DEVICE_TYPE_WINDOWS; + } +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows/src/main/java/org/wso2/carbon/device/mgt/mobile/windows/impl/dao/WindowsDAOFactory.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows/src/main/java/org/wso2/carbon/device/mgt/mobile/windows/impl/dao/WindowsDAOFactory.java new file mode 100644 index 0000000000..6e99fc6b9c --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows/src/main/java/org/wso2/carbon/device/mgt/mobile/windows/impl/dao/WindowsDAOFactory.java @@ -0,0 +1,131 @@ +/* + * 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.wso2.carbon.device.mgt.mobile.windows.impl.dao; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.device.mgt.common.DeviceManagementConstants; +import org.wso2.carbon.device.mgt.mobile.dao.AbstractMobileDeviceManagementDAOFactory; +import org.wso2.carbon.device.mgt.mobile.dao.MobileDeviceDAO; +import org.wso2.carbon.device.mgt.mobile.dao.MobileDeviceManagementDAOException; +import org.wso2.carbon.device.mgt.mobile.dao.MobileFeatureDAO; +import org.wso2.carbon.device.mgt.mobile.windows.impl.dao.impl.WindowsDeviceDAOImpl; +import org.wso2.carbon.device.mgt.mobile.windows.impl.dao.impl.WindowsFeatureDAOImpl; + +import javax.sql.DataSource; +import java.sql.Connection; +import java.sql.SQLException; + +public class WindowsDAOFactory extends AbstractMobileDeviceManagementDAOFactory { + + private static final Log log = LogFactory.getLog(WindowsDAOFactory.class); + protected static DataSource dataSource; + private static ThreadLocal currentConnection = new ThreadLocal<>(); + + public WindowsDAOFactory() { + this.dataSource = getDataSourceMap().get(DeviceManagementConstants.MobileDeviceTypes.MOBILE_DEVICE_TYPE_WINDOWS); + } + + @Override + public MobileDeviceDAO getMobileDeviceDAO() { + return new WindowsDeviceDAOImpl(); + } + + @Override + public MobileFeatureDAO getMobileFeatureDAO() { + return new WindowsFeatureDAOImpl(); + } + + public static void beginTransaction() throws MobileDeviceManagementDAOException { + try { + Connection conn = dataSource.getConnection(); + conn.setAutoCommit(false); + currentConnection.set(conn); + } catch (SQLException e) { + throw new MobileDeviceManagementDAOException("Error occurred while retrieving datasource connection", e); + } + } + + public static void openConnection() throws MobileDeviceManagementDAOException { + if (currentConnection.get() == null) { + Connection conn; + try { + conn = dataSource.getConnection(); + currentConnection.set(conn); + } catch (SQLException e) { + throw new MobileDeviceManagementDAOException + ("Error occurred while retrieving data source connection", e); + } + } + } + + public static Connection getConnection() throws MobileDeviceManagementDAOException { + if (currentConnection.get() == null) { + try { + currentConnection.set(dataSource.getConnection()); + } catch (SQLException e) { + throw new MobileDeviceManagementDAOException + ("Error occurred while retrieving data source connection", e); + } + } + return currentConnection.get(); + } + + public static void commitTransaction() { + 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) { + log.error("Error occurred while committing the transaction", e); + } + } + + public static void closeConnection() { + Connection con = currentConnection.get(); + try { + con.close(); + } catch (SQLException e) { + log.error("Error occurred while close the connection"); + } + currentConnection.remove(); + } + + public static void rollbackTransaction() { + 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) { + log.warn("Error occurred while roll-backing the transaction", e); + } + } +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows/src/main/java/org/wso2/carbon/device/mgt/mobile/windows/impl/dao/WindowsFeatureManagementDAOException.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows/src/main/java/org/wso2/carbon/device/mgt/mobile/windows/impl/dao/WindowsFeatureManagementDAOException.java new file mode 100644 index 0000000000..1dd62d2875 --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows/src/main/java/org/wso2/carbon/device/mgt/mobile/windows/impl/dao/WindowsFeatureManagementDAOException.java @@ -0,0 +1,80 @@ +/* + * 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.wso2.carbon.device.mgt.mobile.windows.impl.dao; + +import org.wso2.carbon.device.mgt.mobile.dao.MobileDeviceManagementDAOException; + +/** + * Implement Exception class for Windows Device Features. + */ +public class WindowsFeatureManagementDAOException extends MobileDeviceManagementDAOException { + + private String message; + private static final long serialVersionUID = 2021891706072918865L; + + /** + * Constructs a new MobileDeviceManagementDAOException with the specified detail message and + * nested exception. + * + * @param message error message + * @param nestedException exception + */ + public WindowsFeatureManagementDAOException(String message, Exception nestedException) { + super(message, nestedException); + } + + /** + * Constructs a new MobileDeviceManagementDAOException with the specified detail message + * and cause. + * + * @param message the detail message. + * @param cause the cause of this exception. + */ + public WindowsFeatureManagementDAOException(String message, Throwable cause) { + super(message, cause); + setErrorMessage(message); + } + + /** + * Constructs a new MobileDeviceManagementDAOException with the specified detail message. + * + * @param message the detail message. + */ + public WindowsFeatureManagementDAOException(String message) { + super(message); + setErrorMessage(message); + } + + /** + * Constructs a new MobileDeviceManagementDAOException with the specified and cause. + * + * @param cause the cause of this exception. + */ + public WindowsFeatureManagementDAOException(Throwable cause) { + super(cause); + } + + public String getMessage() { + return message; + } + + public void setErrorMessage(String errorMessage) { + this.message = errorMessage; + } +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows/src/main/java/org/wso2/carbon/device/mgt/mobile/windows/impl/dao/impl/WindowsDeviceDAOImpl.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows/src/main/java/org/wso2/carbon/device/mgt/mobile/windows/impl/dao/impl/WindowsDeviceDAOImpl.java new file mode 100644 index 0000000000..f65d42fc7a --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows/src/main/java/org/wso2/carbon/device/mgt/mobile/windows/impl/dao/impl/WindowsDeviceDAOImpl.java @@ -0,0 +1,255 @@ +/* + * 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.wso2.carbon.device.mgt.mobile.windows.impl.dao.impl; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.device.mgt.mobile.dao.MobileDeviceDAO; +import org.wso2.carbon.device.mgt.mobile.dao.MobileDeviceManagementDAOException; +import org.wso2.carbon.device.mgt.mobile.dao.util.MobileDeviceManagementDAOUtil; +import org.wso2.carbon.device.mgt.mobile.dto.MobileDevice; +import org.wso2.carbon.device.mgt.mobile.windows.impl.dao.WindowsDAOFactory; +import org.wso2.carbon.device.mgt.mobile.windows.impl.util.WindowsPluginConstants; + +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 MobileDeviceDAO for Windows Devices. + */ +public class WindowsDeviceDAOImpl implements MobileDeviceDAO { + + private static final Log log = LogFactory.getLog(WindowsDeviceDAOImpl.class); + + @Override + public MobileDevice getMobileDevice(String mblDeviceId) throws MobileDeviceManagementDAOException { + Connection conn; + PreparedStatement stmt = null; + ResultSet rs = null; + MobileDevice mobileDevice = null; + try { + conn = WindowsDAOFactory.getConnection(); + String selectDBQuery = + "SELECT DEVICE_ID, CHANNEL_URI, DEVICE_INFO, IMEI, IMSI, " + + "OS_VERSION, DEVICE_MODEL, VENDOR, LATITUDE, LONGITUDE, SERIAL, MAC_ADDRESS," + + " OS_VERSION, DEVICE_NAME " + + "FROM WIN_DEVICE WHERE DEVICE_ID = ?"; + stmt = conn.prepareStatement(selectDBQuery); + stmt.setString(1, mblDeviceId); + rs = stmt.executeQuery(); + + while (rs.next()) { + mobileDevice = new MobileDevice(); + mobileDevice.setMobileDeviceId(rs.getString(WindowsPluginConstants.DEVICE_ID)); + mobileDevice.setImei(rs.getString(WindowsPluginConstants.IMEI)); + mobileDevice.setImsi(rs.getString(WindowsPluginConstants.IMSI)); + mobileDevice.setModel(rs.getString(WindowsPluginConstants.DEVICE_MODEL)); + mobileDevice.setVendor(rs.getString(WindowsPluginConstants.VENDOR)); + mobileDevice.setLatitude(rs.getString(WindowsPluginConstants.LATITUDE)); + mobileDevice.setLongitude(rs.getString(WindowsPluginConstants.LONGITUDE)); + mobileDevice.setSerial(rs.getString(WindowsPluginConstants.SERIAL)); + mobileDevice.setOsVersion(rs.getString(WindowsPluginConstants.LATITUDE)); + + Map propertyMap = new HashMap<>(); + propertyMap.put(WindowsPluginConstants.CHANNEL_URI, rs.getString(WindowsPluginConstants.CHANNEL_URI)); + propertyMap.put(WindowsPluginConstants.DEVICE_INFO, rs.getString(WindowsPluginConstants.DEVICE_INFO)); + propertyMap.put(WindowsPluginConstants.MAC_ADDRESS, rs.getString(WindowsPluginConstants.MAC_ADDRESS)); + propertyMap.put(WindowsPluginConstants.DEVICE_NAME, rs.getString(WindowsPluginConstants.DEVICE_NAME)); + + mobileDevice.setDeviceProperties(propertyMap); + } + if (log.isDebugEnabled()) { + log.debug("All Windows device details have fetched from Windows database."); + } + return mobileDevice; + } catch (SQLException e) { + throw new MobileDeviceManagementDAOException("Error occurred while fetching all Windows device data", e); + } finally { + MobileDeviceManagementDAOUtil.cleanupResources(stmt, rs); + } + } + + @Override + public boolean addMobileDevice(MobileDevice mobileDevice) throws MobileDeviceManagementDAOException { + boolean status = false; + Connection conn; + PreparedStatement stmt = null; + try { + conn = WindowsDAOFactory.getConnection(); + String createDBQuery = + "INSERT INTO WIN_DEVICE(DEVICE_ID, CHANNEL_URI, DEVICE_INFO, IMEI, " + + "IMSI, OS_VERSION, DEVICE_MODEL, VENDOR, LATITUDE, LONGITUDE, SERIAL, " + + "MAC_ADDRESS, DEVICE_NAME) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; + + stmt = conn.prepareStatement(createDBQuery); + stmt.setString(1, mobileDevice.getMobileDeviceId()); + + Map properties = mobileDevice.getDeviceProperties(); + stmt.setString(2, properties.get(WindowsPluginConstants.CHANNEL_URI)); + stmt.setString(3, properties.get(WindowsPluginConstants.DEVICE_INFO)); + stmt.setString(4, mobileDevice.getImei()); + stmt.setString(5, mobileDevice.getImsi()); + stmt.setString(6, mobileDevice.getOsVersion()); + stmt.setString(7, mobileDevice.getModel()); + stmt.setString(8, mobileDevice.getVendor()); + stmt.setString(9, mobileDevice.getLatitude()); + stmt.setString(10, mobileDevice.getLongitude()); + stmt.setString(11, mobileDevice.getSerial()); + stmt.setString(12, properties.get(WindowsPluginConstants.MAC_ADDRESS)); + stmt.setString(13, properties.get(WindowsPluginConstants.DEVICE_NAME)); + int rows = stmt.executeUpdate(); + if (rows > 0) { + status = true; + if (log.isDebugEnabled()) { + log.debug("Windows device " + mobileDevice.getMobileDeviceId() + " data has been" + + " added to the Windows database."); + } + } + } catch (SQLException e) { + throw new MobileDeviceManagementDAOException("Error occurred while adding the Windows device '" + + mobileDevice.getMobileDeviceId() + "' to the Windows db.", e); + } finally { + MobileDeviceManagementDAOUtil.cleanupResources(stmt, null); + } + return status; + } + + @Override + public boolean updateMobileDevice(MobileDevice mobileDevice) throws MobileDeviceManagementDAOException { + boolean status = false; + Connection conn; + PreparedStatement stmt = null; + try { + conn = WindowsDAOFactory.getConnection(); + String updateDBQuery = + "UPDATE WIN_DEVICE SET CHANNEL_URI = ?, DEVICE_INFO = ?, IMEI = ?, IMSI = ?, " + + "OS_VERSION = ?, DEVICE_MODEL = ?, VENDOR = ?, LATITUDE = ?, LONGITUDE = ?, " + + "SERIAL = ?, MAC_ADDRESS = ?, DEVICE_NAME = ? WHERE DEVICE_ID = ?"; + + stmt = conn.prepareStatement(updateDBQuery); + + Map properties = mobileDevice.getDeviceProperties(); + stmt.setString(1, properties.get(WindowsPluginConstants.CHANNEL_URI)); + stmt.setString(2, properties.get(WindowsPluginConstants.DEVICE_INFO)); + stmt.setString(3, mobileDevice.getImei()); + stmt.setString(4, mobileDevice.getImsi()); + stmt.setString(5, mobileDevice.getOsVersion()); + stmt.setString(6, mobileDevice.getModel()); + stmt.setString(7, mobileDevice.getVendor()); + stmt.setString(8, mobileDevice.getLatitude()); + stmt.setString(9, mobileDevice.getLongitude()); + stmt.setString(10, mobileDevice.getSerial()); + stmt.setString(11, properties.get(WindowsPluginConstants.MAC_ADDRESS)); + stmt.setString(12, properties.get(WindowsPluginConstants.DEVICE_NAME)); + stmt.setString(13, mobileDevice.getMobileDeviceId()); + int rows = stmt.executeUpdate(); + if (rows > 0) { + status = true; + if (log.isDebugEnabled()) { + log.debug("Windows device " + mobileDevice.getMobileDeviceId() + " data has been" + + " modified."); + } + } + } catch (SQLException e) { + throw new MobileDeviceManagementDAOException("Error occurred while modifying the Windows device '" + + mobileDevice.getMobileDeviceId() + "' data.", e); + } finally { + MobileDeviceManagementDAOUtil.cleanupResources(stmt, null); + } + return status; + } + + @Override + public boolean deleteMobileDevice(String mblDeviceId) throws MobileDeviceManagementDAOException { + boolean status = false; + Connection conn; + PreparedStatement stmt = null; + try { + conn = WindowsDAOFactory.getConnection(); + String deleteDBQuery = "DELETE FROM WIN_DEVICE WHERE DEVICE_ID = ?"; + stmt = conn.prepareStatement(deleteDBQuery); + stmt.setString(1, mblDeviceId); + int rows = stmt.executeUpdate(); + if (rows > 0) { + status = true; + if (log.isDebugEnabled()) { + log.debug("Windows device " + mblDeviceId + " data has deleted" + + " from the windows database."); + } + } + } catch (SQLException e) { + throw new MobileDeviceManagementDAOException("Error occurred while deleting windows device '" + + mblDeviceId + "'", e); + } finally { + MobileDeviceManagementDAOUtil.cleanupResources(stmt, null); + } + return status; + } + + @Override + public List getAllMobileDevices() throws MobileDeviceManagementDAOException { + Connection conn; + PreparedStatement stmt = null; + ResultSet rs = null; + MobileDevice mobileDevice; + List mobileDevices = new ArrayList<>(); + try { + conn = WindowsDAOFactory.getConnection(); + String selectDBQuery = + "SELECT DEVICE_ID, CHANNEL_URI, DEVICE_INFO, IMEI, IMSI, " + + "OS_VERSION, DEVICE_MODEL, VENDOR, LATITUDE, LONGITUDE, SERIAL, MAC_ADDRESS," + + " OS_VERSION, DEVICE_NAME " + + "FROM WIN_DEVICE"; + stmt = conn.prepareStatement(selectDBQuery); + rs = stmt.executeQuery(); + + while (rs.next()) { + mobileDevice = new MobileDevice(); + mobileDevice.setMobileDeviceId(rs.getString(WindowsPluginConstants.DEVICE_ID)); + mobileDevice.setVendor(rs.getString(WindowsPluginConstants.IMEI)); + mobileDevice.setLatitude(rs.getString(WindowsPluginConstants.IMSI)); + mobileDevice.setLongitude(rs.getString(WindowsPluginConstants.OS_VERSION)); + mobileDevice.setImei(rs.getString(WindowsPluginConstants.DEVICE_MODEL)); + mobileDevice.setImsi(rs.getString(WindowsPluginConstants.VENDOR)); + mobileDevice.setOsVersion(rs.getString(WindowsPluginConstants.LATITUDE)); + + Map propertyMap = new HashMap<>(); + propertyMap.put(WindowsPluginConstants.CHANNEL_URI, rs.getString(WindowsPluginConstants.CHANNEL_URI)); + propertyMap.put(WindowsPluginConstants.DEVICE_INFO, rs.getString(WindowsPluginConstants.DEVICE_INFO)); + propertyMap.put(WindowsPluginConstants.DEVICE_NAME, rs.getString(WindowsPluginConstants.DEVICE_NAME)); + mobileDevice.setDeviceProperties(propertyMap); + mobileDevices.add(mobileDevice); + } + if (log.isDebugEnabled()) { + log.debug("All Windows device details have fetched from Windows database."); + } + return mobileDevices; + } catch (SQLException e) { + throw new MobileDeviceManagementDAOException("Error occurred while fetching all Windows device data", e); + } finally { + MobileDeviceManagementDAOUtil.cleanupResources(stmt, rs); + } + } + +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows/src/main/java/org/wso2/carbon/device/mgt/mobile/windows/impl/dao/impl/WindowsFeatureDAOImpl.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows/src/main/java/org/wso2/carbon/device/mgt/mobile/windows/impl/dao/impl/WindowsFeatureDAOImpl.java new file mode 100644 index 0000000000..a1e42cdff3 --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows/src/main/java/org/wso2/carbon/device/mgt/mobile/windows/impl/dao/impl/WindowsFeatureDAOImpl.java @@ -0,0 +1,271 @@ +/* + * 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.wso2.carbon.device.mgt.mobile.windows.impl.dao.impl; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.device.mgt.common.DeviceManagementConstants; +import org.wso2.carbon.device.mgt.mobile.dao.MobileDeviceManagementDAOException; +import org.wso2.carbon.device.mgt.mobile.dao.MobileFeatureDAO; +import org.wso2.carbon.device.mgt.mobile.dao.util.MobileDeviceManagementDAOUtil; +import org.wso2.carbon.device.mgt.mobile.dto.MobileFeature; +import org.wso2.carbon.device.mgt.mobile.windows.impl.dao.WindowsDAOFactory; +import org.wso2.carbon.device.mgt.mobile.windows.impl.dao.WindowsFeatureManagementDAOException; +import org.wso2.carbon.device.mgt.mobile.windows.impl.util.WindowsPluginConstants; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; + +/** + * Implement MobileFeatureDAO for Windows devices. + */ +public class WindowsFeatureDAOImpl implements MobileFeatureDAO { + + private static final Log log = LogFactory.getLog(WindowsFeatureDAOImpl.class); + + public WindowsFeatureDAOImpl() { + + } + + @Override + public boolean addFeature(MobileFeature mobileFeature) throws MobileDeviceManagementDAOException { + PreparedStatement stmt = null; + boolean status; + Connection conn; + try { + conn = WindowsDAOFactory.getConnection(); + String sql = "INSERT INTO WIN_FEATURE(CODE, NAME, DESCRIPTION) VALUES (?, ?, ?)"; + stmt = conn.prepareStatement(sql); + stmt.setString(1, mobileFeature.getCode()); + stmt.setString(2, mobileFeature.getName()); + stmt.setString(3, mobileFeature.getDescription()); + stmt.executeUpdate(); + status = true; + } catch (SQLException e) { + throw new WindowsFeatureManagementDAOException( + "Error occurred while adding windows feature '" + + mobileFeature.getName() + "' into the metadata repository", e); + } finally { + MobileDeviceManagementDAOUtil.cleanupResources(stmt, null); + } + return status; + } + + @Override + public boolean addFeatures(List mobileFeatures) throws MobileDeviceManagementDAOException { + PreparedStatement stmt = null; + boolean status = false; + Connection conn; + try { + conn = WindowsDAOFactory.getConnection(); + stmt = conn.prepareStatement("INSERT INTO WIN_FEATURE(CODE, NAME, DESCRIPTION) VALUES (?, ?, ?)"); + for (MobileFeature mobileFeature : mobileFeatures) { + stmt.setString(1, mobileFeature.getCode()); + stmt.setString(2, mobileFeature.getName()); + stmt.setString(3, mobileFeature.getDescription()); + stmt.addBatch(); + } + stmt.executeBatch(); + status = true; + } catch (SQLException e) { + throw new WindowsFeatureManagementDAOException( + "Error occurred while adding windows features into the metadata repository", e); + } finally { + MobileDeviceManagementDAOUtil.cleanupResources(stmt, null); + } + return status; + } + + @Override + public boolean updateFeature(MobileFeature mobileFeature) throws MobileDeviceManagementDAOException { + boolean status = false; + Connection conn; + PreparedStatement stmt = null; + try { + conn = WindowsDAOFactory.getConnection(); + String updateDBQuery = + "UPDATE WIN_FEATURE SET NAME = ?, DESCRIPTION = ?" + + "WHERE CODE = ?"; + stmt = conn.prepareStatement(updateDBQuery); + stmt.setString(1, mobileFeature.getName()); + stmt.setString(2, mobileFeature.getDescription()); + stmt.setString(3, mobileFeature.getCode()); + int rows = stmt.executeUpdate(); + if (rows > 0) { + status = true; + if (log.isDebugEnabled()) { + log.debug("Windows Feature " + mobileFeature.getCode() + " data has been " + + "modified."); + } + } + } catch (SQLException e) { + throw new WindowsFeatureManagementDAOException("Error occurred while updating the Windows Feature '" + + mobileFeature.getCode() + "' to the Windows db.", e); + } finally { + MobileDeviceManagementDAOUtil.cleanupResources(stmt, null); + } + return status; + } + + @Override + public boolean deleteFeatureById(int mblFeatureId) throws MobileDeviceManagementDAOException { + PreparedStatement stmt = null; + boolean status = false; + Connection conn; + try { + conn = WindowsDAOFactory.getConnection(); + String sql = "DELETE FROM WIN_FEATURE WHERE ID = ?"; + stmt = conn.prepareStatement(sql); + stmt.setInt(1, mblFeatureId); + stmt.execute(); + status = true; + } catch (SQLException e) { + throw new WindowsFeatureManagementDAOException( + "Error occurred while deleting windows feature '" + + mblFeatureId + "' from Windows database.", e); + } finally { + MobileDeviceManagementDAOUtil.cleanupResources(stmt, null); + } + return status; + } + + @Override + public boolean deleteFeatureByCode(String mblFeatureCode) throws MobileDeviceManagementDAOException { + PreparedStatement stmt = null; + boolean status = false; + Connection conn; + try { + conn = WindowsDAOFactory.getConnection(); + String sql = "DELETE FROM WIN_FEATURE WHERE CODE = ?"; + stmt = conn.prepareStatement(sql); + stmt.setString(1, mblFeatureCode); + stmt.execute(); + status = true; + } catch (SQLException e) { + throw new WindowsFeatureManagementDAOException( + "Error occurred while deleting windows feature '" + + mblFeatureCode + "' from Windows database.", e); + } finally { + MobileDeviceManagementDAOUtil.cleanupResources(stmt, null); + } + return status; + } + + @Override + public MobileFeature getFeatureById(int mblFeatureId) throws MobileDeviceManagementDAOException { + PreparedStatement stmt = null; + ResultSet rs = null; + Connection conn; + try { + conn = WindowsDAOFactory.getConnection(); + String sql = "SELECT ID, CODE, NAME, DESCRIPTION FROM WIN_FEATURE WHERE ID = ?"; + stmt = conn.prepareStatement(sql); + stmt.setInt(1, mblFeatureId); + rs = stmt.executeQuery(); + MobileFeature mobileFeature = null; + if (rs.next()) { + mobileFeature = new MobileFeature(); + mobileFeature.setId(rs.getInt(WindowsPluginConstants.WINDOWS_FEATURE_ID)); + mobileFeature.setCode(rs.getString(WindowsPluginConstants.WINDOWS_FEATURE_CODE)); + mobileFeature.setName(rs.getString(WindowsPluginConstants.WINDOWS_FEATURE_NAME)); + mobileFeature.setDescription(rs.getString(WindowsPluginConstants.WINDOWS_FEATURE_DESCRIPTION)); + mobileFeature.setDeviceType( + DeviceManagementConstants.MobileDeviceTypes.MOBILE_DEVICE_TYPE_WINDOWS); + } + return mobileFeature; + } catch (SQLException e) { + throw new WindowsFeatureManagementDAOException( + "Error occurred while retrieving windows feature '" + + mblFeatureId + "' from the Windows database.", e); + } finally { + MobileDeviceManagementDAOUtil.cleanupResources(stmt, rs); + } + } + + @Override + public MobileFeature getFeatureByCode(String mblFeatureCode) throws MobileDeviceManagementDAOException { + PreparedStatement stmt = null; + ResultSet rs = null; + Connection conn; + try { + conn = WindowsDAOFactory.getConnection(); + String sql = "SELECT ID, CODE, NAME, DESCRIPTION FROM WIN_FEATURE WHERE CODE = ?"; + stmt = conn.prepareStatement(sql); + stmt.setString(1, mblFeatureCode); + rs = stmt.executeQuery(); + MobileFeature mobileFeature = null; + if (rs.next()) { + mobileFeature = new MobileFeature(); + mobileFeature.setId(rs.getInt(WindowsPluginConstants.WINDOWS_FEATURE_ID)); + mobileFeature.setCode(rs.getString(WindowsPluginConstants.WINDOWS_FEATURE_CODE)); + mobileFeature.setName(rs.getString(WindowsPluginConstants.WINDOWS_FEATURE_NAME)); + mobileFeature.setDescription(rs.getString(WindowsPluginConstants.WINDOWS_FEATURE_DESCRIPTION)); + mobileFeature.setDeviceType( + DeviceManagementConstants.MobileDeviceTypes.MOBILE_DEVICE_TYPE_WINDOWS); + } + return mobileFeature; + } catch (SQLException e) { + throw new WindowsFeatureManagementDAOException( + "Error occurred while retrieving windows feature '" + + mblFeatureCode + "' from the Windows database.", e); + } finally { + MobileDeviceManagementDAOUtil.cleanupResources(stmt, rs); + } + } + + @Override + public List getFeatureByDeviceType(String deviceType) throws MobileDeviceManagementDAOException { + return this.getAllFeatures(); + } + + @Override + public List getAllFeatures() throws MobileDeviceManagementDAOException { + PreparedStatement stmt = null; + ResultSet rs = null; + Connection conn; + List features = new ArrayList<>(); + try { + conn = WindowsDAOFactory.getConnection(); + String sql = "SELECT ID, CODE, NAME, DESCRIPTION FROM WIN_FEATURE"; + stmt = conn.prepareStatement(sql); + rs = stmt.executeQuery(); + MobileFeature mobileFeature; + while (rs.next()) { + mobileFeature = new MobileFeature(); + mobileFeature.setId(rs.getInt(WindowsPluginConstants.WINDOWS_FEATURE_ID)); + mobileFeature.setCode(rs.getString(WindowsPluginConstants.WINDOWS_FEATURE_CODE)); + mobileFeature.setName(rs.getString(WindowsPluginConstants.WINDOWS_FEATURE_NAME)); + mobileFeature.setDescription(rs.getString(WindowsPluginConstants.WINDOWS_FEATURE_DESCRIPTION)); + mobileFeature.setDeviceType( + DeviceManagementConstants.MobileDeviceTypes.MOBILE_DEVICE_TYPE_WINDOWS); + features.add(mobileFeature); + } + return features; + } catch (SQLException e) { + throw new WindowsFeatureManagementDAOException("Error occurred while retrieving all " + + "windows features from the Windows database.", e); + } finally { + MobileDeviceManagementDAOUtil.cleanupResources(stmt, rs); + } + } +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows/src/main/java/org/wso2/carbon/device/mgt/mobile/windows/impl/util/WindowsPluginConstants.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows/src/main/java/org/wso2/carbon/device/mgt/mobile/windows/impl/util/WindowsPluginConstants.java new file mode 100644 index 0000000000..7596871c98 --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows/src/main/java/org/wso2/carbon/device/mgt/mobile/windows/impl/util/WindowsPluginConstants.java @@ -0,0 +1,48 @@ +/* + * 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.wso2.carbon.device.mgt.mobile.windows.impl.util; + +/** + * Define constance used by Windows plugin. + */ +public class WindowsPluginConstants { + + //properties related to database table WINDOWS_DEVICE + public static final String DEVICE_ID = "DEVICE_ID"; + public static final String CHANNEL_URI = "CHANNEL_URI"; + public static final String DEVICE_INFO = "DEVICE_INFO"; + public static final String IMEI = "IMEI"; + public static final String IMSI = "IMSI"; + public static final String OS_VERSION = "OS_VERSION"; + public static final String DEVICE_MODEL = "DEVICE_MODEL"; + public static final String VENDOR = "VENDOR"; + public static final String LATITUDE = "LATITUDE"; + public static final String LONGITUDE = "LONGITUDE"; + public static final String SERIAL = "SERIAL"; + public static final String MAC_ADDRESS = "MAC_ADDRESS"; + public static final String DEVICE_NAME = "DEVICE_NAME"; + + //Properties related to WIN_FEATURE table + public static final String WINDOWS_FEATURE_ID = "ID"; + public static final String WINDOWS_FEATURE_CODE = "CODE"; + public static final String WINDOWS_FEATURE_NAME = "NAME"; + public static final String WINDOWS_FEATURE_DESCRIPTION = "DESCRIPTION"; + + +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows/src/main/java/org/wso2/carbon/device/mgt/mobile/windows/impl/util/WindowsPluginUtils.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows/src/main/java/org/wso2/carbon/device/mgt/mobile/windows/impl/util/WindowsPluginUtils.java new file mode 100644 index 0000000000..2c18646cef --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows/src/main/java/org/wso2/carbon/device/mgt/mobile/windows/impl/util/WindowsPluginUtils.java @@ -0,0 +1,58 @@ +/* + * 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.wso2.carbon.device.mgt.mobile.windows.impl.util; + +import org.wso2.carbon.device.mgt.common.license.mgt.License; +import org.wso2.carbon.device.mgt.mobile.windows.impl.WindowsDeviceManagementService; + +/** + * Contains utility method used by Windows plugin. + */ +public class WindowsPluginUtils { + + public static License getDefaultLicense() { + License license = new License(); + license.setName(WindowsDeviceManagementService.DEVICE_TYPE_WINDOWS); + license.setLanguage("en_US"); + license.setVersion("1.0.0"); + license.setText("This End User License Agreement (\"Agreement\") is a legal agreement between you (\"You\") " + + "and WSO2, Inc., regarding the enrollment of Your personal mobile device (\"Device\") in SoR's " + + "mobile device management program, and the loading to and removal from Your Device and Your use " + + "of certain applications and any associated software and user documentation, whether provided in " + + "\"online\" or electronic format, used in connection with the operation of or provision of services " + + "to WSO2, Inc., BY SELECTING \"I ACCEPT\" DURING INSTALLATION, YOU ARE ENROLLING YOUR DEVICE, AND " + + "THEREBY AUTHORIZING SOR OR ITS AGENTS TO INSTALL, UPDATE AND REMOVE THE APPS FROM YOUR DEVICE AS " + + "DESCRIBED IN THIS AGREEMENT. YOU ARE ALSO EXPLICITLY ACKNOWLEDGING AND AGREEING THAT (1) THIS IS " + + "A BINDING CONTRACT AND (2) YOU HAVE READ AND AGREE TO THE TERMS OF THIS AGREEMENT.\n" + + "\n" + + "IF YOU DO NOT ACCEPT THESE TERMS, DO NOT ENROLL YOUR DEVICE AND DO NOT PROCEED ANY FURTHER.\n" + + "\n" + + "You agree that: (1) You understand and agree to be bound by the terms and conditions contained " + + "in this Agreement, and (2) You are at least 21 years old and have the legal capacity to enter " + + "into this Agreement as defined by the laws of Your jurisdiction. SoR shall have the right, " + + "without prior notice, to terminate or suspend (i) this Agreement, (ii) the enrollment of Your " + + "Device, or (iii) the functioning of the Apps in the event of a violation of this Agreement or " + + "the cessation of Your relationship with SoR (including termination of Your employment if You are " + + "an employee or expiration or termination of Your applicable franchise or supply agreement if You " + + "are a franchisee of or supplier to the WSO2 WSO2, Inc., system). SoR expressly reserves all " + + "rights not expressly granted herein."); + return license; + } +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows/src/main/java/org/wso2/carbon/device/mgt/mobile/windows/impl/util/WindowsUtils.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows/src/main/java/org/wso2/carbon/device/mgt/mobile/windows/impl/util/WindowsUtils.java new file mode 100644 index 0000000000..7fba2f5cab --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows/src/main/java/org/wso2/carbon/device/mgt/mobile/windows/impl/util/WindowsUtils.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.wso2.carbon.device.mgt.mobile.windows.impl.util; + +import java.util.Map; + +/** + * Contains utility methods used by Windows plugin. + */ +public class WindowsUtils { + public static String getDeviceProperty(Map deviceProperties, String property) { + + String deviceProperty = deviceProperties.get(property); + if (deviceProperty == null) { + return null; + } + return deviceProperty; + } +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows/src/main/java/org/wso2/carbon/device/mgt/mobile/windows/internal/WindowsDeviceManagementDataHolder.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows/src/main/java/org/wso2/carbon/device/mgt/mobile/windows/internal/WindowsDeviceManagementDataHolder.java new file mode 100644 index 0000000000..600aecc1c6 --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows/src/main/java/org/wso2/carbon/device/mgt/mobile/windows/internal/WindowsDeviceManagementDataHolder.java @@ -0,0 +1,47 @@ +/* + * 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.wso2.carbon.device.mgt.mobile.windows.internal; + +import org.wso2.carbon.registry.core.service.RegistryService; + +/** + * DataHolder class of Mobile plugins component. + */ +public class WindowsDeviceManagementDataHolder { + + private RegistryService registryService; + + private static WindowsDeviceManagementDataHolder thisInstance = new WindowsDeviceManagementDataHolder(); + + private WindowsDeviceManagementDataHolder() { + } + + public static WindowsDeviceManagementDataHolder getInstance() { + return thisInstance; + } + + public RegistryService getRegistryService() { + return registryService; + } + + public void setRegistryService(RegistryService registryService) { + this.registryService = registryService; + } + +} diff --git a/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows/src/main/java/org/wso2/carbon/device/mgt/mobile/windows/internal/WindowsDeviceManagementServiceComponent.java b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows/src/main/java/org/wso2/carbon/device/mgt/mobile/windows/internal/WindowsDeviceManagementServiceComponent.java new file mode 100644 index 0000000000..5fd45c61a6 --- /dev/null +++ b/components/mobile-plugins/windows-plugin/org.wso2.carbon.device.mgt.mobile.windows/src/main/java/org/wso2/carbon/device/mgt/mobile/windows/internal/WindowsDeviceManagementServiceComponent.java @@ -0,0 +1,121 @@ +/* + * 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.wso2.carbon.device.mgt.mobile.windows.internal; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.osgi.framework.BundleContext; +import org.osgi.framework.ServiceRegistration; +import org.osgi.service.component.ComponentContext; +import org.wso2.carbon.device.mgt.common.spi.DeviceManagementService; +import org.wso2.carbon.device.mgt.mobile.windows.impl.WindowsDeviceManagementService; +import org.wso2.carbon.device.mgt.mobile.windows.impl.WindowsPolicyMonitoringService; +import org.wso2.carbon.ndatasource.core.DataSourceService; +import org.wso2.carbon.policy.mgt.common.spi.PolicyMonitoringService; +import org.wso2.carbon.registry.core.service.RegistryService; + +/** + * @scr.component name="org.wso2.carbon.device.mgt.mobile.windows.impl.internal.WindowsDeviceManagementServiceComponent" + * immediate="true" + * @scr.reference name="org.wso2.carbon.ndatasource" + * interface="org.wso2.carbon.ndatasource.core.DataSourceService" + * cardinality="1..1" + * policy="dynamic" + * bind="setDataSourceService" + * unbind="unsetDataSourceService" + * @scr.reference name="registry.service" + * interface="org.wso2.carbon.registry.core.service.RegistryService" cardinality="0..1" + * policy="dynamic" bind="setRegistryService" unbind="unsetRegistryService" + *

      + * Adding reference to API Manager Configuration service is an unavoidable hack to get rid of NPEs thrown while + * initializing APIMgtDAOs attempting to register APIs programmatically. APIMgtDAO needs to be proper cleaned up + * to avoid as an ideal fix + */ +public class WindowsDeviceManagementServiceComponent { + + private ServiceRegistration androidServiceRegRef; + + private static final Log log = LogFactory.getLog(WindowsDeviceManagementServiceComponent.class); + + protected void activate(ComponentContext ctx) { + + if (log.isDebugEnabled()) { + log.debug("Activating Android Mobile Device Management Service Component"); + } + try { + BundleContext bundleContext = ctx.getBundleContext(); + + androidServiceRegRef = + bundleContext.registerService(DeviceManagementService.class.getName(), + new WindowsDeviceManagementService(), null); + + // Policy management service + + bundleContext.registerService(PolicyMonitoringService.class, + new WindowsPolicyMonitoringService(), null); + + if (log.isDebugEnabled()) { + log.debug("Android Mobile Device Management Service Component has been successfully activated"); + } + } catch (Throwable e) { + log.error("Error occurred while activating Android Mobile Device Management Service Component", e); + } + } + + protected void deactivate(ComponentContext ctx) { + if (log.isDebugEnabled()) { + log.debug("De-activating Android Mobile Device Management Service Component"); + } + try { + if (androidServiceRegRef != null) { + androidServiceRegRef.unregister(); + } + if (log.isDebugEnabled()) { + log.debug( + "Android Mobile Device Management Service Component has been successfully de-activated"); + } + } catch (Throwable e) { + log.error("Error occurred while de-activating Android Mobile Device Management bundle", e); + } + } + + protected void setDataSourceService(DataSourceService dataSourceService) { + /* This is to avoid mobile device management component getting initialized before the underlying datasources + are registered */ + if (log.isDebugEnabled()) { + log.debug("Data source service set to android mobile service component"); + } + } + + protected void unsetDataSourceService(DataSourceService dataSourceService) { + //do nothing + } + + protected void setRegistryService(RegistryService registryService) { + if (log.isDebugEnabled()) { + log.debug("RegistryService acquired"); + } + WindowsDeviceManagementDataHolder.getInstance().setRegistryService(registryService); + } + + protected void unsetRegistryService(RegistryService registryService) { + WindowsDeviceManagementDataHolder.getInstance().setRegistryService(null); + } + +} diff --git a/components/mobile-plugins/windows-plugin/pom.xml b/components/mobile-plugins/windows-plugin/pom.xml new file mode 100644 index 0000000000..e5de76e618 --- /dev/null +++ b/components/mobile-plugins/windows-plugin/pom.xml @@ -0,0 +1,61 @@ + + + + + + + org.wso2.carbon.devicemgt-plugins + mobile-plugins + 2.1.0-SNAPSHOT + ../pom.xml + + + 4.0.0 + org.wso2.carbon.devicemgt-plugins + windows-plugin + pom + WSO2 Carbon - Mobile Plugins + http://wso2.org + + + org.wso2.carbon.device.mgt.mobile.windows + org.wso2.carbon.device.mgt.mobile.windows.agent + + + + + + + org.apache.felix + maven-scr-plugin + 1.7.2 + + + generate-scr-scrdescriptor + + scr + + + + + + + + + diff --git a/pom.xml b/pom.xml index f1e904679c..0848df9627 100644 --- a/pom.xml +++ b/pom.xml @@ -18,7 +18,8 @@ --> - + 4.0.0 org.wso2.carbon.devicemgt-plugins @@ -37,6 +38,8 @@ components/device-mgt + + features/device-mgt @@ -245,6 +248,250 @@ zip ${carbon.device.mgt.version} + + org.wso2.carbon.devicemgt + org.wso2.carbon.certificate.mgt.core + ${carbon.device.mgt.version} + + + org.wso2.carbon.devicemgt + org.wso2.carbon.device.mgt.analytics + ${carbon.device.mgt.version} + provided + + + org.wso2.carbon.devicemgt + org.wso2.carbon.dynamic.client.registration + ${carbon.device.mgt.version} + + + org.wso2.carbon.devicemgt + org.wso2.carbon.device.mgt.jwt.client.extension + ${carbon.devicemgt.version} + + + org.wso2.carbon.devicemgt + org.wso2.carbon.apimgt.application.extension + ${carbon.devicemgt.version} + + + org.wso2.carbon.devicemgt + org.wso2.carbon.apimgt.webapp.publisher + ${carbon.devicemgt.version} + + + org.wso2.carbon.devicemgt + org.wso2.carbon.apimgt.annotations + ${carbon.devicemgt.version} + + + + org.wso2.carbon.commons + org.wso2.carbon.databridge.commons + ${carbon.commons.version} + + + org.wso2.carbon.analytics-common + org.wso2.carbon.databridge.core + ${carbon.analytics.common.version} + + + org.wso2.carbon.analytics-common + org.wso2.carbon.databridge.agent + ${carbon.analytics.common.version} + + + commons-codec + commons-codec + + + + + org.wso2.carbon.analytics-common + org.wso2.carbon.databridge.commons + ${carbon.analytics.common.version} + + + + + + + + + org.wso2.carbon.devicemgt-plugins + org.wso2.carbon.device.mgt.iot + ${carbon.devicemgt.plugins.version} + + + + org.wso2.carbon.devicemgt-plugins + org.wso2.carbon.device.mgt.iot.api + ${carbon.devicemgt.plugins.version} + war + + + + + + + org.wso2.carbon.devicemgt-plugins + org.wso2.carbon.device.mgt.iot.androidsense.plugin + ${carbon.devicemgt.plugins.version} + + + org.wso2.carbon.devicemgt-plugins + org.wso2.carbon.device.mgt.iot.androidsense.controller.api + ${carbon.devicemgt.plugins.version} + war + + + org.wso2.carbon.devicemgt-plugins + org.wso2.carbon.device.mgt.iot.androidsense.manager.api + ${carbon.devicemgt.plugins.version} + war + + + + + + + org.wso2.carbon.devicemgt-plugins + org.wso2.carbon.device.mgt.iot.arduino.plugin.impl + ${carbon.devicemgt.plugins.version} + + + org.wso2.carbon.devicemgt-plugins + org.wso2.carbon.device.mgt.iot.arduino.service.impl + ${carbon.devicemgt.plugins.version} + war + + + org.wso2.carbon.devicemgt-plugins + org.wso2.carbon.device.mgt.iot.arduino.manager.service.impl + ${carbon.devicemgt.plugins.version} + war + + + org.wso2.carbon.devicemgt-plugins + org.wso2.carbon.device.mgt.iot.arduino.controller.service.impl + ${carbon.devicemgt.plugins.version} + war + + + + + + org.wso2.carbon.devicemgt-plugins + org.wso2.carbon.device.mgt.iot.digitaldisplay.plugin + ${carbon.devicemgt.plugins.version} + + + org.wso2.carbon.devicemgt-plugins + org.wso2.carbon.device.mgt.iot.digitaldisplay.manager.api + ${carbon.devicemgt.plugins.version} + war + + + org.wso2.carbon.devicemgt-plugins + org.wso2.carbon.device.mgt.iot.digitaldisplay.controller.api + ${carbon.devicemgt.plugins.version} + war + + + + + + + org.wso2.carbon.devicemgt-plugins + org.wso2.carbon.device.mgt.iot.droneanalyzer.plugin + ${carbon.devicemgt.plugins.version} + + + + org.wso2.carbon.devicemgt-plugins + org.wso2.carbon.device.mgt.iot.droneanalyzer.manager.api + ${carbon.devicemgt.plugins.version} + war + + + + org.wso2.carbon.devicemgt-plugins + org.wso2.carbon.device.mgt.iot.droneanalyzer.controller.api + ${carbon.devicemgt.plugins.version} + war + + + + + + + org.wso2.carbon.devicemgt-plugins + org.wso2.carbon.device.mgt.iot.raspberrypi.plugin.impl + ${carbon.devicemgt.plugins.version} + + + org.wso2.carbon.devicemgt-plugins + org.wso2.carbon.device.mgt.iot.raspberrypi.manager.service.impl + ${carbon.devicemgt.plugins.version} + war + + + org.wso2.carbon.devicemgt-plugins + org.wso2.carbon.device.mgt.iot.raspberrypi.controller.service.impl + ${carbon.devicemgt.plugins.version} + war + + + + + + + org.wso2.carbon.devicemgt-plugins + org.wso2.carbon.device.mgt.iot.virtualfirealarm.plugin.impl + ${carbon.devicemgt.plugins.version} + + + + org.wso2.carbon.devicemgt-plugins + org.wso2.carbon.device.mgt.iot.virtualfirealarm.manager.service.impl + ${carbon.devicemgt.plugins.version} + war + + + + org.wso2.carbon.devicemgt-plugins + org.wso2.carbon.device.mgt.iot.virtualfirealarm.controller.service.impl + ${carbon.devicemgt.plugins.version} + war + + + + org.wso2.carbon.devicemgt-plugins + org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.impl + ${carbon.devicemgt.plugins.version} + + + + org.wso2.carbon.devicemgt-plugins + org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.advanced.impl + ${carbon.devicemgt.plugins.version} + + + + + + org.wso2.carbon.devicemgt-plugins + org.wso2.carbon.device.mgt.iot.camera.plugin.impl + ${carbon.devicemgt.plugins.version} + + + + + + org.wso2.carbon.analytics + org.wso2.carbon.analytics.datasource.commons + ${carbon.analytics.version} + @@ -257,6 +504,16 @@ org.wso2.carbon.device.mgt.mobile.url.printer ${carbon.mobile.device.mgt.version} + + org.wso2.carbon.devicemgt-plugins + org.wso2.carbon.device.mgt.mobile + ${carbon.mobile.device.mgt.version} + + + org.wso2.carbon.devicemgt-plugins + org.wso2.carbon.device.mgt.mobile.android + ${carbon.mobile.device.mgt.version} + @@ -305,6 +562,11 @@ jdbc-pool ${orbit.tomcat.jdbc.pooling.version} + + org.wso2.tomcat + tomcat-servlet-api + ${orbit.version.tomcat-servlet-api} + + + org.igniterealtime.smack.wso2 + smack + ${smack.wso2.version} + + + org.igniterealtime.smack.wso2 + smackx + ${smackx.wso2.version} + + + + + org.eclipse.jetty + jetty-server + ${jetty.version} + + + + + org.apache.cxf + cxf-rt-frontend-jaxws + ${cxf.version} + + + org.apache.cxf + cxf-rt-frontend-jaxrs + ${cxf.version} + + + org.apache.cxf + cxf-rt-transports-http + ${cxf.version} + + + javax.ws.rs + jsr311-api + ${javax.ws.rs.version} + + + + + commons-httpclient.wso2 + commons-httpclient + ${orbit.version.commons-httpclient} + + + commons-codec.wso2 + commons-codec + ${commons-codec.wso2.version} + + + org.codehaus.jackson + jackson-core-asl + ${jackson.version} + + + org.codehaus.jackson + jackson-jaxrs + ${jackson.version} + + + + + org.apache.cxf + cxf-rt-bindings-soap + ${cxf.version} + + + org.apache.cxf + cxf-rt-bindings-http + ${cxf.bindings.version} + + + org.apache.ws.security + wss4j + ${wss4j.security.version} + + + org.apache.cxf + cxf-rt-rs-extension-providers + ${cxf.version} + + + org.apache.cxf + cxf-rt-ws-security + ${cxf.version} + + + org.apache.wss4j + wss4j-ws-security-common + ${wss4j.security.common.version} + + + org.apache.ws.commons.axiom + axiom-api + ${axiom-api.version} + + + org.apache.ws.commons.axiom + axiom-impl + ${axiom-api.version} + + + log4j + log4j + ${log4j.version} + + + org.springframework + spring-web + ${spring-web.version} + + + org.springframework.ws + spring-ws-security + ${spring-ws-security.version} + + + org.springframework + spring-context + ${spring-web.version} + + + com.sun.xml.ws + jaxws-rt + ${jaxws-rt.version} + + + com.sun.xml.messaging.saaj + saaj-impl + ${saaj-impl.version} + + + org.bouncycastle + bcpkix-jdk15on + ${bcpkix-jdk15on.version} + + + org.bouncycastle + bcprov-jdk15on + ${bcpkix-jdk15on.version} + + + org.codehaus.plexus + plexus-utils + ${plexus-utils.version} + + + com.madgag.spongycastle + pkix + ${spongycastle.version} + + + com.madgag.spongycastle + prov + ${spongycastle.version} + + + com.madgag.spongycastle + core + ${spongycastle.version} + + + joda-time + joda-time + ${joda-time.version} + + + + commons-io + commons-io + ${commons-io.version} + + + + org.wso2.carbon.identity + org.wso2.carbon.identity.oauth.stub + ${carbon.identity.version} + provided + + + org.wso2.carbon.identity + org.wso2.carbon.identity.oauth + ${carbon.identity.version} + provided + + + + commons-collections + commons-collections + ${commons-collections.version} + + + commons-configuration + commons-configuration + ${commons-configuration.version} + + + + org.wso2.carbon.commons + org.wso2.carbon.user.mgt + ${carbon.commons.version} + + + + junit + junit + test + ${junit.version} + + @@ -552,6 +1045,7 @@ 1.3 + 4.8.2 4.3.1 6.8 @@ -566,34 +1060,58 @@ 2.7.16 + 2.5.11 1.9.0 1.1.1 + + 1.1.0-SNAPSHOT + + + 2.1.0-SNAPSHOT + 4.4.8 4.6.0 - + 5.0.7 - + 4.5.0 - + 4.4.8 - + 4.5.8 - + 1.1.0-SNAPSHOT [0.8.0, 2.0.0) + + 5.0.3 + 2.1.0-SNAPSHOT + + 3.0.4.wso2v1 + 3.0.4.wso2v1 + 1.0.2 + + + 8.1.3.v20120416 + + + 5.0.11-SNAPSHOT + [5.0.11,6.0.0) + 1.0.6-SNAPSHOT + [1.0.6,2.0.0] + 1.51.0.0 1.49 @@ -602,8 +1120,33 @@ 2.2.4 1.0.2 - - github-scm + 3.1.0.wso2v2 + 1.4.0.wso2v1 + + 6.0 + + 1.2.14 + 1.2.17 + 3.0.5.RELEASE + 2.1.0.RELEASE + 2.2.8 + 1.3.18 + 1.49 + 3.0.21 + 2.2 + 1.6.17 + 2.0.0 + + 2.4 + 3.0.0.wso2v1 + 3.2.2 + 1.8 + + 7.0.59.wso2v1 + + + + github-scm @@ -715,6 +1258,11 @@ build-helper-maven-plugin 1.8 + + org.apache.maven.plugins + maven-war-plugin + 2.2 + @@ -798,5 +1346,5 @@ GCM Server repository - GitHub https://github.com/slorber/gcm-server-repository/raw/master/releases/ - +