├── .gitignore ├── .travis.yml ├── LICENSE.md ├── Makefile ├── README.md ├── auth_test.sh ├── config ├── engine.config └── sasl.config ├── doc └── overview.edoc ├── include ├── common.hrl ├── debug.hrl ├── field_restrictions.hrl ├── json.hrl ├── poller.hrl ├── pubsub.hrl ├── rabbit_common │ └── include │ │ ├── gm_specs.hrl │ │ ├── rabbit.hrl │ │ ├── rabbit_framing.hrl │ │ └── rabbit_msg_store.hrl └── state.hrl ├── javascripts ├── package.json └── receive.js ├── priv ├── dispatch.conf └── www │ └── stream.html ├── rebar ├── rebar.config ├── scripts ├── install.sh ├── python │ ├── README.md │ ├── cgi-bin │ │ ├── humidity.py │ │ └── temperature.py │ ├── financialstreams │ ├── gamestreams │ ├── post_avg.py │ ├── poststreams.py │ └── weatherstreams ├── sensec.sh └── travis-elasticsearch.sh ├── src ├── analyse.erl ├── api_help.erl ├── config.erl ├── datapoints.erl ├── destructure_json.erl ├── engine.app.src ├── engine.erl ├── engine_app.erl ├── engine_sup.erl ├── groups.erl ├── lib_file.erl ├── lib_json.erl ├── openidc.erl ├── parser.erl ├── plus_srv.erl ├── poll_help.erl ├── poller.erl ├── polling_monitor.erl ├── polling_system.erl ├── pubsub │ ├── gen_virtual_stream_process.erl │ ├── resourceProcessMock.erl │ ├── streamProcess.erl │ ├── triggersProcess.erl │ └── virtual_stream_process_supervisor.erl ├── resource.erl ├── resources.erl ├── scoring.erl ├── search.erl ├── static_resource.erl ├── stream_publisher.erl ├── stream_reciever.erl ├── streams.erl ├── suggest.erl ├── triggers.erl ├── triggers_lib.erl ├── users.erl ├── virtual_streams.erl └── vs_func_lib.erl └── test ├── config.spec ├── datapoints_tests.erl ├── gen_virtual_stream_process_tests.erl ├── http.erl ├── lib_json_tests.erl ├── poll_help_tests.erl ├── polling_system_tests.erl ├── resources_tests.erl ├── search_tests.erl ├── streams_tests.erl ├── suggest_tests.erl ├── test.erl ├── triggers_lib_tests.erl ├── triggers_tests.erl ├── users_tests.erl ├── vs_func_lib_tests.erl └── vstreams_tests.erl /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore these 2 | ebin/ 3 | doc/* 4 | lib/ 5 | erl_crash.dump 6 | *~ 7 | TEST*.xml 8 | .project 9 | .settings 10 | *.swp 11 | priv/data 12 | priv/logs 13 | javascripts/node_modules 14 | 15 | # Do not Ignore these 16 | !doc/overview.edoc 17 | /ebin 18 | .temp.log -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: erlang 2 | env: R_HOME=/usr/lib/R 3 | before_install: 4 | - sudo apt-get install -qq xsltproc 5 | - sudo add-apt-repository "deb http://ftp.sunet.se/pub/lang/CRAN/bin/linux/ubuntu precise/" 6 | - sudo apt-get install r-base 7 | services: 8 | - rabbitmq 9 | script: "make install && make test_travis" 10 | before_script: 11 | - "curl https://raw.github.com/projectcs13/sensor-cloud/develop/scripts/travis-elasticsearch.sh | bash" 12 | - "cd scripts/python/" 13 | - "python -m CGIHTTPServer 8001 &" 14 | - "python -m CGIHTTPServer 8002 &" 15 | - "cd ../../" 16 | otp_release: 17 | # - R16B02 18 | # - R16B01 19 | - R16B 20 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | ################################################################################ 2 | ### Makefile for Project CS 2013 ### 3 | ################################################################################ 4 | ################################################################################ 5 | ################################################################################ 6 | ### Variable assignment 7 | ################################################################################ 8 | ERL := erl 9 | REBAR := ./rebar 10 | ERL_CONFIG := -config config/engine.config -config config/sasl.config 11 | ERL_BOOT := -boot start_sasl -s reloader -s engine 12 | ERL_PA_FOLDERS := -pa ebin/ lib/*/ebin/ lib/*/bin/ 13 | TEST_RESULT_FOLDER := test-results 14 | ################################################################################ 15 | ################################################################################ 16 | ################################################################################ 17 | ### Dependency rules 18 | ### Do NOT touch this section! 19 | ### The commands in this sections should not be used in general, but can be used 20 | ### if there is need for it 21 | ################################################################################ 22 | compile: 23 | @$(REBAR) compile skip_deps=true 24 | 25 | ### get_libs will download and install all project libraries 26 | conf: compile_libs 27 | $(ERL) $(ERL_PA_FOLDERS) $(ERL_CONFIG) -s engine -s config 28 | make compile_libs 29 | 30 | get_libs: 31 | @@$(REBAR) get-deps 32 | 33 | compile_libs: 34 | @$(REBAR) compile 35 | $(MAKE) -C lib/rabbitmq-server 36 | $(MAKE) -C lib/rabbitmq-erlang-client 37 | $(MAKE) -C lib/rErlang 38 | 39 | clean_emacs_vsn_files: 40 | rm -rf *~ 41 | rm -rf doc/*~ 42 | rm -rf include/*~ 43 | rm -rf priv/*~ 44 | rm -rf scripts/*~ 45 | rm -rf src/*~ 46 | rm -rf test/*~ 47 | ################################################################################ 48 | ################################################################################ 49 | ################################################################################ 50 | ### Command rules 51 | ### This section contains commands that can be used. 52 | ### This section can be edited if needed 53 | ################################################################################ 54 | 55 | ### Command: make 56 | ### Builds the entire project, excluding the dependencies. 57 | all: compile 58 | 59 | ### Command: make install 60 | ### Downloads all dependencies and builds the entire project 61 | install: get_libs conf 62 | (cd javascripts; npm install socket.io; npm install rabbit.js) 63 | 64 | install_linux_deps: 65 | sudo scripts/install.sh 66 | 67 | ### Command: make run 68 | ### Downloads all depenedencies, bulds entire project and runs the project. 69 | run: compile 70 | curl -XPUT localhost:9200/sensorcloud 71 | $(ERL) $(ERL_PA_FOLDERS) $(ERL_CONFIG) $(ERL_BOOT) -sname engine 72 | 73 | ### Command: make run_all 74 | ### Starts all parts of the system in one command. 75 | run_all: compile 76 | sudo scripts/sensec.sh start 77 | 78 | ### Command: make test_setup 79 | ### Runs all parts of the system except the erlang part. To be used prior to 'make test' for easy test setup 80 | test_setup: compile 81 | sudo scripts/sensec.sh test_setup 82 | 83 | ### Command: make stop_all 84 | ### Stops all parts of the system in one command. 85 | stop_all: 86 | sudo scripts/sensec.sh stop 87 | 88 | ### Command: make run_es 89 | ### Runs elastic search 90 | run_es: 91 | lib/elasticsearch/bin/elasticsearch -f 92 | 93 | ### Command: make run_nodejs 94 | ### Runs NodeJS 95 | run_nodejs: 96 | nodejs javascripts/receive.js 97 | 98 | ### Command: make run_fake_resource 99 | ### Runs the fake resources for polling 100 | run_fake_resource: 101 | (cd scripts/python/ && python -m CGIHTTPServer 8001 &) 102 | (cd scripts/python/ && python -m CGIHTTPServer 8002) 103 | 104 | ### Command: make run_rabbit 105 | ### Runs rabbitMQ server 106 | run_rabbit: 107 | sudo lib/rabbitmq-server/scripts/rabbitmq-server 108 | 109 | ### Command: make test 110 | ### Compile project resources (not libraries) and runs all eunit tests. 111 | test: compile 112 | -@mkdir $(TEST_RESULT_FOLDER) 113 | curl -XDELETE localhost:9200/sensorcloud 114 | curl -XPUT localhost:9200/sensorcloud 115 | $(ERL) $(ERL_PA_FOLDERS) $(ERL_CONFIG) $(ERL_BOOT) -sname engine -s test run 116 | 117 | test_travis: compile 118 | -@mkdir $(TEST_RESULT_FOLDER) 119 | curl -XPUT localhost:9200/sensorcloud 120 | $(ERL) $(ERL_PA_FOLDERS) $(ERL_CONFIG) $(ERL_BOOT) -sname engine -s test run 121 | 122 | test_datapoints: compile 123 | -@mkdir $(TEST_RESULT_FOLDER) 124 | curl -XDELETE localhost:9200/sensorcloud 125 | curl -XPUT localhost:9200/sensorcloud 126 | $(ERL) $(ERL_PA_FOLDERS) $(ERL_CONFIG) $(ERL_BOOT) -sname engine -eval 'test:run(datapoints)' 127 | 128 | test_json: compile 129 | -@mkdir $(TEST_RESULT_FOLDER) 130 | $(ERL) $(ERL_PA_FOLDERS) $(ERL_CONFIG) $(ERL_BOOT) -sname engine -eval 'test:run(lib_json)' 131 | 132 | test_resources: compile 133 | -@mkdir $(TEST_RESULT_FOLDER) 134 | curl -XDELETE localhost:9200/sensorcloud 135 | curl -XPUT localhost:9200/sensorcloud 136 | $(ERL) $(ERL_PA_FOLDERS) $(ERL_CONFIG) $(ERL_BOOT) -sname engine -eval 'test:run(resources)' 137 | 138 | test_streams: compile 139 | -@mkdir $(TEST_RESULT_FOLDER) 140 | curl -XDELETE localhost:9200/sensorcloud 141 | curl -XPUT localhost:9200/sensorcloud 142 | $(ERL) $(ERL_PA_FOLDERS) $(ERL_CONFIG) $(ERL_BOOT) -sname engine -eval 'test:run(streams)' 143 | 144 | test_users: compile 145 | -@mkdir $(TEST_RESULT_FOLDER) 146 | curl -XDELETE localhost:9200/sensorcloud 147 | curl -XPUT localhost:9200/sensorcloud 148 | $(ERL) $(ERL_PA_FOLDERS) $(ERL_CONFIG) $(ERL_BOOT) -sname engine -eval 'test:run(users)' 149 | 150 | test_poll: compile 151 | -@mkdir $(TEST_RESULT_FOLDER) 152 | curl -XDELETE localhost:9200/sensorcloud 153 | curl -XPUT localhost:9200/sensorcloud 154 | $(ERL) $(ERL_PA_FOLDERS) $(ERL_CONFIG) $(ERL_BOOT) -sname engine -eval 'test:run(poll_help)' 155 | 156 | test_poll_system: compile 157 | -@mkdir $(TEST_RESULT_FOLDER) 158 | curl -XDELETE localhost:9200/sensorcloud 159 | curl -XPUT localhost:9200/sensorcloud 160 | $(ERL) $(ERL_PA_FOLDERS) $(ERL_CONFIG) $(ERL_BOOT) -sname engine -eval 'test:run(polling_system)' 161 | 162 | test_search: compile 163 | -@mkdir $(TEST_RESULT_FOLDER) 164 | curl -XDELETE localhost:9200/sensorcloud 165 | curl -XPUT localhost:9200/sensorcloud 166 | $(ERL) $(ERL_PA_FOLDERS) $(ERL_CONFIG) $(ERL_BOOT) -sname engine -eval 'test:run(search)' 167 | 168 | test_triggers: compile 169 | -@mkdir $(TEST_RESULT_FOLDER) 170 | curl -XDELETE localhost:9200/sensorcloud 171 | curl -XPUT localhost:9200/sensorcloud 172 | $(ERL) $(ERL_PA_FOLDERS) $(ERL_CONFIG) $(ERL_BOOT) -sname engine -eval 'test:run(triggers)' 173 | 174 | 175 | test_vstreams: compile 176 | -@mkdir $(TEST_RESULT_FOLDER) 177 | curl -XDELETE localhost:9200/sensorcloud 178 | curl -XPUT localhost:9200/sensorcloud 179 | $(ERL) $(ERL_PA_FOLDERS) $(ERL_CONFIG) $(ERL_BOOT) -sname engine -eval 'test:run(gen_virtual_stream_process)' 180 | 181 | 182 | ### Command: make docs 183 | ### Genereats all of the documentation files 184 | docs: all 185 | ./rebar doc skip_deps=true 186 | 187 | ### Command: make clean 188 | ### Cleans the directory of the following things: 189 | ### * Emacs versioning files. 190 | ### * All erlang .beam files, including 'ebin' folder 191 | clean: clean_emacs_vsn_files 192 | @./rebar clean skip_deps=true 193 | rm -f erl_crash.dump 194 | rm -rf ebin/ 195 | rm -rf test-results/ 196 | 197 | ### Command: make clean_libs 198 | ### Cleans the directory of the following things: 199 | ### * All the downloaded libraries 200 | clean_libs: 201 | @./rebar delete-deps 202 | rm -rf lib/ 203 | 204 | ### Command: make clean_docs 205 | ### Cleans the directory of the following things: 206 | ### * All the documentation files except 'overview.edoc' 207 | clean_docs: 208 | find doc/ -type f -not -name 'overview.edoc' | xargs rm 209 | 210 | ### Command: make help 211 | ### Prints an explanation of the commands in this Makefile 212 | help: 213 | @echo "###################################################################" 214 | @echo "Commands:" 215 | @echo "" 216 | @echo "'make'" 217 | @echo "Compiles all the project sources. Does NOT compile libraries" 218 | @echo "" 219 | @echo "'make install'" 220 | @echo "Downloads and compiles all libraries" 221 | @echo "" 222 | @echo "'make install_linux_deps'" 223 | @echo "Installs all linux dependencies needed. Should only be necessary to do once on a system." 224 | @echo "" 225 | @echo "'make run'" 226 | @echo "Compiles and runs the otp app. Does NOT compile libraries" 227 | @echo "" 228 | @echo "'make run_all'" 229 | @echo "Compiles the system (not libraries) and runs ALL parts of the system." 230 | @echo "" 231 | @echo "'make stop_all'" 232 | @echo "Stops all parts of the system started with 'make run_all'" 233 | @echo "" 234 | @echo "'make run_all'" 235 | @echo "Compiles and runs all parts of the project. Does NOT compile libraries" 236 | @echo "" 237 | @echo "'make test_setup'" 238 | @echo "Easy setup of environment prior to running 'make test''" 239 | @echo "" 240 | @echo "'make stop_all'" 241 | @echo "Stops all parts of the project which was started with 'make run_all'" 242 | @echo "" 243 | @echo "'make run_es'" 244 | @echo "Runs the elastic search server" 245 | @echo "" 246 | @echo "'make run_nodejs'" 247 | @echo "Runs the elastic nodejs" 248 | @echo "" 249 | @echo "make run_rabbit" 250 | @echo "Runs the rabbitMQ server" 251 | @echo "" 252 | @echo "'make docs'" 253 | @echo "Generates documentation for the project" 254 | @echo "" 255 | @echo "'make clean'" 256 | @echo "Cleans all the project, including dependencies" 257 | @echo "" 258 | @echo "'make clean_libs'" 259 | @echo "Cleans all of the libraries" 260 | @echo "" 261 | @echo "'make clean_docs'" 262 | @echo "Cleans all of the documentation files, except for 'overview.edoc'" 263 | @echo "" 264 | @echo "'make help'" 265 | @echo "Prints an explanation of the commands in this Makefile" 266 | @echo "###################################################################" 267 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # IoT-Framework Engine 2 | 3 | Welcome to the "Project CS 2013" project 4 | 5 | ## Demo 6 | 7 | You can check out a demo of the IoT-Framework here: [IoT-Framework demo](https://vimeo.com/98966770). 8 | 9 | ## Installing the project 10 | 11 | 1. Download and compile the linux system dependencies, (only needed once per machine) 12 | 13 | make install_linux_deps 14 | 15 | 2. Download and compile the project dependencies, and compile the project sources 16 | 17 | make install 18 | 19 | ## Running the project 20 | 21 | 1. Run the application by using startup script (one of the commands below) 22 | 23 | make run_all 24 | sudo ./scripts/sensec.sh start 25 | 26 | 2. Alternative run (type each in separate shells) 27 | 28 | make run_rabbit 29 | make run_es 30 | make run_nodejs 31 | % don't forget to export R_HOME for example 32 | export R_HOME="/usr/lib/R" 33 | make run 34 | 35 | 4. To shutdown either close each individual shell or run one of the commands below 36 | 37 | make stop_all 38 | sudo ./scripts/sensec.sh stop 39 | 40 | ## Running tests 41 | 42 | 1. There are two ways of setting up the environment for testing. Either run the startup script by one of the below commands. 43 | 44 | make test_setup 45 | sudo ./scripts/sensec.sh test_setup 46 | 47 | 2. Or run each of the following commands in a separate shell 48 | 49 | make run_rabbit 50 | make run_es 51 | make run_nodejs 52 | make run_fake_resource 53 | 54 | 3. Run the tests 55 | 56 | make test 57 | 58 | ## Code Status 59 | 60 | [![Build Status](https://travis-ci.org/projectcs13/sensor-cloud.png)](https://travis-ci.org/projectcs13/sensor-cloud) 61 | -------------------------------------------------------------------------------- /auth_test.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | ### Test Variables 4 | MY_ID="104295496712959038073" 5 | OTHER_PUBLIC_ID="108895705188930031602" 6 | OTHER_PRIVATE_ID="107908217220817548513" 7 | 8 | MY_STREAM_ID="ZLDDaaZATcK451vMMdHqWw" 9 | RANKED_STREAM="mh5KhW9sTbGbmVRCSItOhg" 10 | MY_ACCESS_TOKEN="ya29.WwDJorUu9ByRvxwAAAADBNA6_9H79kwom8mxZZOdHTvmWkLwwQyj-hV5iR0DPw" 11 | 12 | 13 | 14 | printf "##########################################################\n" 15 | printf "## OpenID Connect Authentication/Authorization Test ###\n" 16 | printf "##########################################################\n\n" 17 | 18 | 19 | printf "#### Rule 1 #### - " 20 | printf "Can MAKE anything with our own data except manipulating datapoints\n" 21 | 22 | printf "\n\nShould be OK -> Update my own user info\n" 23 | curl -XPUT "http://localhost:8000/users/$MY_ID" -H "Access-Token: $MY_ACCESS_TOKEN" -H "Content-Type: application/json" -d '{"description": "testing PUT with my own user"}' 24 | 25 | printf "\n\nShould be OK -> Get my updated user info\n" 26 | curl -XGET "http://localhost:8000/users/$MY_ID" -H "Access-Token: $MY_ACCESS_TOKEN" 27 | 28 | printf "\n\nShould FAIL -> CAN NOT create datapoints\n" 29 | curl -XPOST "http://localhost:8000/streams/$MY_STREAM_ID/data" -H "Access-Token: ya29.WgBV0eqjpIxktSEAAABq5sHdJVSN64Ij3ztnhD49PW6-oEyLJ7M7F3CGAxkKeEvx1Y8Quj-SuVSavRhNmFc" -H "Content-Type: application/json" -d '{"data":[{"timestamp":"2013-12-04T17:43:02.000","value":117.96},{"timestamp":"2013-12-04T17:47:02.000","value":117.96}]}' 30 | 31 | printf "\n\n---------------------------------------------------------------------------\n\n" 32 | 33 | 34 | 35 | printf "#### Rule 2 #### - " 36 | printf "Can NOT MAKE anything to private users\n" 37 | 38 | printf "\n\nShould FAIL -> Get user info of the IoT Framework's registered user\n" 39 | curl -XGET "http://localhost:8000/users/$OTHER_PRIVATE_ID" -H "Access-Token: $MY_ACCESS_TOKEN" 40 | printf "\n\nShould FAIL -> Update user info of the IoT Framework's registered user\n" 41 | curl -XPUT "http://localhost:8000/users/$OTHER_PRIVATE_ID" -H "Access-Token: $MY_ACCESS_TOKEN" -H "Content-Type: application/json" -d '{"description": testing PUT with my own user!}' 42 | 43 | printf "\n\n---------------------------------------------------------------------------\n\n" 44 | 45 | 46 | 47 | printf "#### Rule 3 #### - " 48 | printf "Can ONLY GET User/S/VS from other public users\n" 49 | 50 | printf "\n\nShould be OK -> Fetch public user's data profile\n" 51 | curl -XGET "http://localhost:8000/users/$OTHER_PUBLIC_ID" -H "Access-Token: $MY_ACCESS_TOKEN" 52 | 53 | printf "\n\nShould be OK -> Fetch public user's streams\n" 54 | curl -XGET "http://localhost:8000/users/$OTHER_PUBLIC_ID/streams" -H "Access-Token: $MY_ACCESS_TOKEN" 55 | 56 | printf "\n\nShould be OK -> Fetch public user's vstreams\n" 57 | curl -XGET "http://localhost:8000/users/$OTHER_PUBLIC_ID/vstreams" -H "Access-Token: $MY_ACCESS_TOKEN" 58 | 59 | printf "\n\nShould FAIL -> CAN NOT Fetch public user's triggers\n" 60 | curl -XGET "http://localhost:8000/users/$OTHER_PUBLIC_ID/triggers" -H "Access-Token: $MY_ACCESS_TOKEN" 61 | 62 | printf "\n\nShould FAIL -> CAN NOT Update a public user's stream\n" 63 | curl -XPUT "http://localhost:8000/streams/$MY_STREAM_ID" -H "Access-Token: $MY_ACCESS_TOKEN" -H "Content-Type: application/json" -d '{"name": "Now it is my stream"}' 64 | 65 | printf "\n\n---------------------------------------------------------------------------\n\n" 66 | 67 | 68 | 69 | printf "#### Rule 4 #### - " 70 | printf "Can PUT the ranking of other user's stream\n" 71 | 72 | printf "\n\nShould be OK -> Update the ranking of a stream, either if it is ours or not\n" 73 | curl -XPUT "http://localhost:8000/streams/$RANKED_STREAM/_rank" -H "Access-Token: $MY_ACCESS_TOKEN" -H "Content-Type: application/json" -d '{"stream_id":"$MY_STREAM_ID","value":1.0}' 74 | 75 | printf "\n\n---------------------------------------------------------------------------\n\n" 76 | 77 | 78 | 79 | printf "#### Rule 5 #### - " 80 | printf "Anything else is forbidden\n" 81 | 82 | printf "\n\nShould FAIL -> Remove other user's stream\n" 83 | curl -XDELETE "http://localhost:8000/streams/$MY_STREAM_ID/" -H "Access-Token: $MY_ACCESS_TOKEN" 84 | 85 | printf "\n\n---------------------------------------------------------------------------\n\n" 86 | 87 | 88 | 89 | printf "#### Rule 6 #### - " 90 | printf "Can GET or POST to Users / Streams / VStreams collections\n" 91 | 92 | printf "\n\nShould be OK -> Create a new stream\n" 93 | curl -XPOST "http://localhost:8000/streams" -H "Access-Token: $MY_ACCESS_TOKEN" -H "Content-Type: application/json" -d '{"name": "Stream Test 123", "description": "my new stream", "user_id": "$MY_ID"}' 94 | 95 | printf "\n\n---------------------------------------------------------------------------\n\n" 96 | 97 | printf "All tests done :)\n" 98 | -------------------------------------------------------------------------------- /config/engine.config: -------------------------------------------------------------------------------- 1 | %%-*- mode: erlang -*- 2 | [{engine, [ 3 | %% Specifies the log directory for the main application. 4 | {engine_log_dir, "priv/logs"}, %Relative from the engine application root or absolute path 5 | 6 | %% Specifies the cluster name for elastic search 7 | {es_cluster_name, "engine"}, 8 | 9 | %% Specifies the directory for the elastic search data 10 | {es_db_dir, "priv/data"}, %Relative from engine application root or absolute path 11 | 12 | %% Specifies the directory for the elastic search additional data directories 13 | %%The value for this needs to be specified as a string with each directory passed seperated with a comma 14 | %% Example: "Path/To/Data1,Path/To/Data2" 15 | {es_db_extra_dir, ""}, %List with paths relative from engine application root or absolute paths 16 | 17 | %% Specifies the ip address for elastic search 18 | {es_ip, "localhost"}, 19 | 20 | %% Specifies the log directory for elastic search 21 | {es_log_dir, "priv/logs"}, %Relative from the engine application root or absolute path 22 | 23 | %% Specifies the port number for elastic search 24 | {es_port, 9200}, 25 | 26 | %% Specifies the log directory for nodejs 27 | {nodejs_log_dir, "priv/logs"}, %Relative from the engine application root or absolute path 28 | 29 | %% Specifies the log directory for rabbit_mq 30 | {rabbit_mq_log_dir, "priv/logs"}, %Relative from the engine application root or absolute path 31 | 32 | %% Specifies the log directory for rabbit_mq 33 | {rabbit_mq_ip, "localhost"}, 34 | 35 | %% Specifies the log directory for rabbit_mq 36 | {rabbit_mq_port, 5672}, 37 | 38 | %% Specifies the log directory for webmachine 39 | {webmachine_log_dir, "priv/logs"}, %Relative from the engine application root or absolute path 40 | 41 | %% Specifies the port number for webmachine 42 | {webmachine_port, 8000} 43 | ]} 44 | ]. 45 | -------------------------------------------------------------------------------- /config/sasl.config: -------------------------------------------------------------------------------- 1 | %%-*- mode: erlang -*- 2 | [{sasl, [ 3 | {errlog_type, error} % Values: error | progress | all 4 | ]} 5 | ]. 6 | -------------------------------------------------------------------------------- /doc/overview.edoc: -------------------------------------------------------------------------------- 1 | ** this is the overview.doc file for the application 'Sicth Sense' ** 2 | 3 | @author Project CS Group 4 | @copyright Sicsth Sense 5 | @version 1.0.0 6 | @title Welcome to Sicth Sense 7 | @doc This is the engine module for the Sicsth Sense. -------------------------------------------------------------------------------- /include/common.hrl: -------------------------------------------------------------------------------- 1 | %% @doc 2 | %% Author: Gabriel Tholsgård, Li Hao 3 | %% [www.csproj13.student.it.uu.se] 4 | %% == common settings and names include file == 5 | %% Provides definitions and settings for common things 6 | %% 7 | %% @end 8 | 9 | -ifndef(__COMMON_HRL__). 10 | -define(__COMMON_HRL__, 1). 11 | 12 | 13 | %% IP address to Elastic Search server 14 | -ifndef(ES_IP_ADDR). 15 | -define(ES_IP_ADDR, "localhost"). 16 | -endif. 17 | 18 | 19 | %% Port used by Elastic Search server 20 | -ifndef(ES_PORT). 21 | -define(ES_PORT, "9200"). 22 | -endif. 23 | 24 | 25 | %% Index name of Elastic Search 26 | -ifndef(ES_INDEX). 27 | -define(ES_INDEX, "sensorcloud"). 28 | -endif. 29 | 30 | 31 | %% HTTP URL to Elastic Search server (according to the set macros above) 32 | -ifndef(ES_ADDR). 33 | -define(ES_ADDR, "http://" ++ ?ES_IP_ADDR ++ ":" ++ ?ES_PORT ++ "/" ++ ?ES_INDEX). 34 | -endif. 35 | 36 | 37 | %% User Agent of httpc request 38 | -ifndef(UA). 39 | -define(UA, "sensor-cloud:"). 40 | -endif. 41 | 42 | 43 | %% IP address to RabbitMQ server 44 | -define(RMQ_IPADDR, "localhost"). 45 | 46 | 47 | %% Port used by RabbitMQ server 48 | -define(RMQ_PORT, "5672"). 49 | 50 | 51 | %% HTTP URL to RabbitMQ server 52 | -define(RMQ_ADDR, ?RMQ_ADDR ++ ":" ++ ?RMQ_PORT). 53 | 54 | -endif. 55 | -------------------------------------------------------------------------------- /include/debug.hrl: -------------------------------------------------------------------------------- 1 | %% Author: Tommy Mattsson 2 | %% [www.csproj13.student.it.uu.se] 3 | %% == debug include file == 4 | %% Provides useful macros used for debugging. 5 | %% 6 | %% @end 7 | -define(INFO(X), ?GENERIC("INFO", X)). 8 | 9 | -ifdef(debug). 10 | -define(DEBUG(X), ?GENERIC("DEBUG", X)). 11 | -else. 12 | -define(DEBUG(X), true). 13 | -endif. 14 | 15 | -ifdef(debug). 16 | -define(ERROR(X), ?GENERIC("ERROR", X)). 17 | -else. 18 | -define(ERROR(X), true). 19 | -endif. 20 | 21 | -define(GENERIC(TYPE, X), io:format("*** ~s: {MODULE: ~p}{LINE: ~p}{MSG: ~p} ***~n", [TYPE,?MODULE,?LINE,X])). 22 | 23 | 24 | -------------------------------------------------------------------------------- /include/field_restrictions.hrl: -------------------------------------------------------------------------------- 1 | %% Author: Tomas S�vstr�m , Li Hao 2 | %% [www.csproj13.student.it.uu.se] 3 | %% == api include file == 4 | %% Includes defenitions of what fields are accteded and what fields are restricted, 5 | %% and also what index to use in elastic search 6 | %% 7 | %% @end 8 | 9 | %% Index 10 | -define(INDEX, "sensorcloud"). 11 | 12 | %% Fields for streams 13 | -define(RESTRICTED_STREAMS_UPDATE, ["active","quality","user_ranking","subscribers","last_update","creation_date"]). 14 | -define(RESTRICTED_STREAMS_CREATE, ["active","quality","user_ranking","subscribers","nr_subscribers","last_update","creation_date"]). 15 | -define(ACCEPTED_STREAMS_FIELDS, ["user_id","name","description", "type","tags","private","unit","accuracy","min_val","max_val","polling","uri","polling_freq","location","resource","resource.resource_type","resource.uuid","parser","data_type","location.lon","location.lat"]). 16 | 17 | %% Fields for users 18 | -define(RESTRCITEDUPDATEUSERS, ["username", "subscriptions"]). 19 | -define(ACCEPTEDFIELDSUSERS, ["username", "email", "firstname", "lastname", "description", "password", "private", "access_token", "refresh_token"]). 20 | 21 | %% Fields for resources 22 | -define(RESTRICTED_RESOURCES_UPDATE, []). 23 | -define(RESTRICTED_RESOURCES_CREATE, ["streams_suggest"]). 24 | -define(ACCEPTED_RESOURCES_FIELDS, ["name","description","model","manufacturer"]). 25 | 26 | %% Fields for data-points 27 | -define(ACCEPTED_DATAPOINTS_FIELDS, ["stream_id","timestamp","value"]). 28 | 29 | %% Fields for virtual streams 30 | -define(ACCEPTED_FIELDS_VSTREAMS_UPDATE, ["user_id","name","description","tags","private"]). 31 | -define(ACCEPTED_FIELDS_VSTREAMS_CREATE, ["user_id","name","description","tags","private","function","streams_involved","creation_date","timestampfrom"]). 32 | -------------------------------------------------------------------------------- /include/json.hrl: -------------------------------------------------------------------------------- 1 | %% Author: Tommy Mattsson 2 | %% [www.csproj13.student.it.uu.se] 3 | %% == json include file == 4 | %% Provides definitions for creating JSON string objects 5 | %% 6 | %% @end 7 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 8 | %%% Type definitions 9 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 10 | %% @type attr() = atom() | string() 11 | -type attr() :: atom() | string(). 12 | %% @type field() = json_string() | mochijson() 13 | -type field() :: atom() | string() | [atom()]. 14 | %% @type json() = json_string() | mochijson() 15 | -type json() :: json_string() | mochijson(). 16 | %% @type json_string() = string() 17 | -type json_string() :: string(). 18 | %% @type json_input_value() = atom() | binary() | integer() | string() | json() | [json()] 19 | -type json_input_value() :: atom() | binary() | integer() | json() | [json_input_value()]. 20 | %% @type json_output_value() = integer() | string() | json_string() | [json_output_value()] 21 | -type json_output_value() :: boolean() | binary() | integer() | json_string() | [json_output_value()]. 22 | %% @type mochijson() = tuple() 23 | -type mochijson() :: tuple(). 24 | 25 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 26 | %%% Convenience Macros 27 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 28 | -ifndef(QUOTE). 29 | -define(QUOTE(ARG), "\"" ++ ARG ++ "\""). 30 | -endif. 31 | 32 | -ifndef(LIST). 33 | -define(LIST(ARG), "[" ++ ARG ++ "]"). 34 | -endif. 35 | 36 | -ifndef(TUPLE). 37 | -define(TUPLE(ARG), "{" ++ ARG ++ "}"). 38 | -endif. 39 | 40 | -ifndef(COLON). 41 | -define(COLON, ":"). 42 | -endif. 43 | -------------------------------------------------------------------------------- /include/poller.hrl: -------------------------------------------------------------------------------- 1 | %%here comes the definition of the structure of poller`s information 2 | 3 | -record(pollerInfo, {stream_id, 4 | name, 5 | uri, 6 | frequency, 7 | data_type, 8 | parser, 9 | pid, 10 | timer_ref}). 11 | 12 | 13 | -------------------------------------------------------------------------------- /include/pubsub.hrl: -------------------------------------------------------------------------------- 1 | %% Datapoint erlang representation, that will propagate around in the pub/sub-system. 2 | -record(datapoint, {timestamp, id, value}). 3 | 4 | -ifndef(__PUBSUB_HRL__). 5 | -define(__PUBSUB_HRL__, 1). 6 | 7 | 8 | %% Definition of timeout to wait for new messages, 9 | %% primarily used for virtual streams. 10 | -define(PUB_SUB_TIMEOUT, 1000). 11 | 12 | %% Takes an erlang:localtime() and converts it to the format: 13 | %% YYYY-MM-DDTHH:MM:SS.000 14 | -define(TIME_NOW(TIME), 15 | integer_to_list(element(1, element(1, TIME))) ++ "-" ++ 16 | case element(2, element(1, TIME)) < 10 of 17 | true -> "0" ++ integer_to_list(element(2, element(1, TIME))); 18 | _ -> "" ++ integer_to_list(element(2, element(1, TIME))) 19 | end ++ "-" ++ 20 | case element(3, element(1, TIME)) < 10 of 21 | true -> "0" ++ integer_to_list(element(3, element(1, TIME))); 22 | _ -> "" ++ integer_to_list(element(3, element(1, TIME))) 23 | end ++ 24 | "T" ++ 25 | case element(1, element(2, TIME)) < 10 of 26 | true -> "0" ++ integer_to_list(element(1, element(2, TIME))); 27 | _ -> "" ++ integer_to_list(element(1, element(2, TIME))) 28 | end ++ ":" ++ 29 | case element(2, element(2, TIME)) < 10 of 30 | true -> "0" ++ integer_to_list(element(2, element(2, TIME))); 31 | _ -> "" ++ integer_to_list(element(2, element(2, TIME))) 32 | end ++ ":" ++ 33 | case element(3, element(2, TIME)) < 10 of 34 | true -> "0" ++ integer_to_list(element(3, element(2, TIME))); 35 | _ -> "" ++ integer_to_list(element(3, element(2, TIME))) 36 | end ++ case Num = (element(3, erlang:now()) div 1000) of 37 | X when X < 100 -> 38 | ".0" ++ integer_to_list(Num); 39 | Y when Y < 10 -> 40 | ".00" ++ integer_to_list(Num); 41 | Z -> "." ++ integer_to_list(Z) 42 | end 43 | ). 44 | 45 | 46 | -endif. 47 | -------------------------------------------------------------------------------- /include/rabbit_common/include/gm_specs.hrl: -------------------------------------------------------------------------------- 1 | %% The contents of this file are subject to the Mozilla Public License 2 | %% Version 1.1 (the "License"); you may not use this file except in 3 | %% compliance with the License. You may obtain a copy of the License at 4 | %% http://www.mozilla.org/MPL/ 5 | %% 6 | %% Software distributed under the License is distributed on an "AS IS" 7 | %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the 8 | %% License for the specific language governing rights and limitations 9 | %% under the License. 10 | %% 11 | %% The Original Code is RabbitMQ. 12 | %% 13 | %% The Initial Developer of the Original Code is GoPivotal, Inc. 14 | %% Copyright (c) 2007-2013 GoPivotal, Inc. All rights reserved. 15 | %% 16 | 17 | -ifdef(use_specs). 18 | 19 | -type(callback_result() :: 'ok' | {'stop', any()} | {'become', atom(), args()}). 20 | -type(args() :: any()). 21 | -type(members() :: [pid()]). 22 | 23 | -spec(joined/2 :: (args(), members()) -> callback_result()). 24 | -spec(members_changed/3 :: (args(), members(), members()) -> callback_result()). 25 | -spec(handle_msg/3 :: (args(), pid(), any()) -> callback_result()). 26 | -spec(terminate/2 :: (args(), term()) -> any()). 27 | 28 | -endif. 29 | -------------------------------------------------------------------------------- /include/rabbit_common/include/rabbit.hrl: -------------------------------------------------------------------------------- 1 | %% The contents of this file are subject to the Mozilla Public License 2 | %% Version 1.1 (the "License"); you may not use this file except in 3 | %% compliance with the License. You may obtain a copy of the License 4 | %% at http://www.mozilla.org/MPL/ 5 | %% 6 | %% Software distributed under the License is distributed on an "AS IS" 7 | %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See 8 | %% the License for the specific language governing rights and 9 | %% limitations under the License. 10 | %% 11 | %% The Original Code is RabbitMQ. 12 | %% 13 | %% The Initial Developer of the Original Code is GoPivotal, Inc. 14 | %% Copyright (c) 2007-2013 GoPivotal, Inc. All rights reserved. 15 | %% 16 | 17 | -record(user, {username, 18 | tags, 19 | auth_backend, %% Module this user came from 20 | impl %% Scratch space for that module 21 | }). 22 | 23 | -record(internal_user, {username, password_hash, tags}). 24 | -record(permission, {configure, write, read}). 25 | -record(user_vhost, {username, virtual_host}). 26 | -record(user_permission, {user_vhost, permission}). 27 | 28 | -record(vhost, {virtual_host, dummy}). 29 | 30 | -record(content, 31 | {class_id, 32 | properties, %% either 'none', or a decoded record/tuple 33 | properties_bin, %% either 'none', or an encoded properties binary 34 | %% Note: at most one of properties and properties_bin can be 35 | %% 'none' at once. 36 | protocol, %% The protocol under which properties_bin was encoded 37 | payload_fragments_rev %% list of binaries, in reverse order (!) 38 | }). 39 | 40 | -record(resource, {virtual_host, kind, name}). 41 | 42 | -record(exchange, {name, type, durable, auto_delete, internal, arguments, 43 | scratches, policy, decorators}). 44 | -record(exchange_serial, {name, next}). 45 | 46 | -record(amqqueue, {name, durable, auto_delete, exclusive_owner = none, 47 | arguments, pid, slave_pids, sync_slave_pids, policy, 48 | gm_pids}). 49 | 50 | %% mnesia doesn't like unary records, so we add a dummy 'value' field 51 | -record(route, {binding, value = const}). 52 | -record(reverse_route, {reverse_binding, value = const}). 53 | 54 | -record(binding, {source, key, destination, args = []}). 55 | -record(reverse_binding, {destination, key, source, args = []}). 56 | 57 | -record(topic_trie_node, {trie_node, edge_count, binding_count}). 58 | -record(topic_trie_edge, {trie_edge, node_id}). 59 | -record(topic_trie_binding, {trie_binding, value = const}). 60 | 61 | -record(trie_node, {exchange_name, node_id}). 62 | -record(trie_edge, {exchange_name, node_id, word}). 63 | -record(trie_binding, {exchange_name, node_id, destination}). 64 | 65 | -record(listener, {node, protocol, host, ip_address, port}). 66 | 67 | -record(runtime_parameters, {key, value}). 68 | 69 | -record(basic_message, {exchange_name, routing_keys = [], content, id, 70 | is_persistent}). 71 | 72 | -record(ssl_socket, {tcp, ssl}). 73 | -record(delivery, {mandatory, sender, message, msg_seq_no}). 74 | -record(amqp_error, {name, explanation = "", method = none}). 75 | 76 | -record(event, {type, props, timestamp}). 77 | 78 | -record(message_properties, {expiry, needs_confirming = false}). 79 | 80 | -record(plugin, {name, %% atom() 81 | version, %% string() 82 | description, %% string() 83 | type, %% 'ez' or 'dir' 84 | dependencies, %% [{atom(), string()}] 85 | location}). %% string() 86 | 87 | %%---------------------------------------------------------------------------- 88 | 89 | -define(COPYRIGHT_MESSAGE, "Copyright (C) 2007-2013 GoPivotal, Inc."). 90 | -define(INFORMATION_MESSAGE, "Licensed under the MPL. See http://www.rabbitmq.com/"). 91 | -define(ERTS_MINIMUM, "5.6.3"). 92 | 93 | %% EMPTY_FRAME_SIZE, 8 = 1 + 2 + 4 + 1 94 | %% - 1 byte of frame type 95 | %% - 2 bytes of channel number 96 | %% - 4 bytes of frame payload length 97 | %% - 1 byte of payload trailer FRAME_END byte 98 | %% See rabbit_binary_generator:check_empty_frame_size/0, an assertion 99 | %% called at startup. 100 | -define(EMPTY_FRAME_SIZE, 8). 101 | 102 | -define(MAX_WAIT, 16#ffffffff). 103 | 104 | -define(HIBERNATE_AFTER_MIN, 1000). 105 | -define(DESIRED_HIBERNATE, 10000). 106 | -define(CREDIT_DISC_BOUND, {2000, 500}). 107 | 108 | -define(INVALID_HEADERS_KEY, <<"x-invalid-headers">>). 109 | -define(ROUTING_HEADERS, [<<"CC">>, <<"BCC">>]). 110 | -define(DELETED_HEADER, <<"BCC">>). 111 | -------------------------------------------------------------------------------- /include/rabbit_common/include/rabbit_framing.hrl: -------------------------------------------------------------------------------- 1 | %% Autogenerated code. Do not edit. 2 | %% 3 | %% The contents of this file are subject to the Mozilla Public License 4 | %% Version 1.1 (the "License"); you may not use this file except in 5 | %% compliance with the License. You may obtain a copy of the License 6 | %% at http://www.mozilla.org/MPL/ 7 | %% 8 | %% Software distributed under the License is distributed on an "AS IS" 9 | %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See 10 | %% the License for the specific language governing rights and 11 | %% limitations under the License. 12 | %% 13 | %% The Original Code is RabbitMQ. 14 | %% 15 | %% The Initial Developer of the Original Code is GoPivotal, Inc. 16 | %% Copyright (c) 2007-2013 GoPivotal, Inc. All rights reserved. 17 | %% 18 | -define(PROTOCOL_PORT, 5672). 19 | -define(FRAME_METHOD, 1). 20 | -define(FRAME_HEADER, 2). 21 | -define(FRAME_BODY, 3). 22 | -define(FRAME_HEARTBEAT, 8). 23 | -define(FRAME_MIN_SIZE, 4096). 24 | -define(FRAME_END, 206). 25 | -define(REPLY_SUCCESS, 200). 26 | -define(CONTENT_TOO_LARGE, 311). 27 | -define(NO_ROUTE, 312). 28 | -define(NO_CONSUMERS, 313). 29 | -define(ACCESS_REFUSED, 403). 30 | -define(NOT_FOUND, 404). 31 | -define(RESOURCE_LOCKED, 405). 32 | -define(PRECONDITION_FAILED, 406). 33 | -define(CONNECTION_FORCED, 320). 34 | -define(INVALID_PATH, 402). 35 | -define(FRAME_ERROR, 501). 36 | -define(SYNTAX_ERROR, 502). 37 | -define(COMMAND_INVALID, 503). 38 | -define(CHANNEL_ERROR, 504). 39 | -define(UNEXPECTED_FRAME, 505). 40 | -define(RESOURCE_ERROR, 506). 41 | -define(NOT_ALLOWED, 530). 42 | -define(NOT_IMPLEMENTED, 540). 43 | -define(INTERNAL_ERROR, 541). 44 | -define(FRAME_OOB_METHOD, 4). 45 | -define(FRAME_OOB_HEADER, 5). 46 | -define(FRAME_OOB_BODY, 6). 47 | -define(FRAME_TRACE, 7). 48 | -define(NOT_DELIVERED, 310). 49 | %% Method field records. 50 | -record('connection.start', {version_major = 0, version_minor = 9, server_properties, mechanisms = <<"PLAIN">>, locales = <<"en_US">>}). 51 | -record('connection.start_ok', {client_properties, mechanism = <<"PLAIN">>, response, locale = <<"en_US">>}). 52 | -record('connection.secure', {challenge}). 53 | -record('connection.secure_ok', {response}). 54 | -record('connection.tune', {channel_max = 0, frame_max = 0, heartbeat = 0}). 55 | -record('connection.tune_ok', {channel_max = 0, frame_max = 0, heartbeat = 0}). 56 | -record('connection.open', {virtual_host = <<"/">>, capabilities = <<"">>, insist = false}). 57 | -record('connection.open_ok', {known_hosts = <<"">>}). 58 | -record('connection.close', {reply_code, reply_text = <<"">>, class_id, method_id}). 59 | -record('connection.close_ok', {}). 60 | -record('connection.redirect', {host, known_hosts = <<"">>}). 61 | -record('channel.open', {out_of_band = <<"">>}). 62 | -record('channel.open_ok', {channel_id = <<"">>}). 63 | -record('channel.flow', {active}). 64 | -record('channel.flow_ok', {active}). 65 | -record('channel.close', {reply_code, reply_text = <<"">>, class_id, method_id}). 66 | -record('channel.close_ok', {}). 67 | -record('channel.alert', {reply_code, reply_text = <<"">>, details = []}). 68 | -record('access.request', {realm = <<"/data">>, exclusive = false, passive = true, active = true, write = true, read = true}). 69 | -record('access.request_ok', {ticket = 1}). 70 | -record('exchange.declare', {ticket = 0, exchange, type = <<"direct">>, passive = false, durable = false, auto_delete = false, internal = false, nowait = false, arguments = []}). 71 | -record('exchange.declare_ok', {}). 72 | -record('exchange.delete', {ticket = 0, exchange, if_unused = false, nowait = false}). 73 | -record('exchange.delete_ok', {}). 74 | -record('exchange.bind', {ticket = 0, destination, source, routing_key = <<"">>, nowait = false, arguments = []}). 75 | -record('exchange.bind_ok', {}). 76 | -record('exchange.unbind', {ticket = 0, destination, source, routing_key = <<"">>, nowait = false, arguments = []}). 77 | -record('exchange.unbind_ok', {}). 78 | -record('queue.declare', {ticket = 0, queue = <<"">>, passive = false, durable = false, exclusive = false, auto_delete = false, nowait = false, arguments = []}). 79 | -record('queue.declare_ok', {queue, message_count, consumer_count}). 80 | -record('queue.bind', {ticket = 0, queue = <<"">>, exchange, routing_key = <<"">>, nowait = false, arguments = []}). 81 | -record('queue.bind_ok', {}). 82 | -record('queue.purge', {ticket = 0, queue = <<"">>, nowait = false}). 83 | -record('queue.purge_ok', {message_count}). 84 | -record('queue.delete', {ticket = 0, queue = <<"">>, if_unused = false, if_empty = false, nowait = false}). 85 | -record('queue.delete_ok', {message_count}). 86 | -record('queue.unbind', {ticket = 0, queue = <<"">>, exchange, routing_key = <<"">>, arguments = []}). 87 | -record('queue.unbind_ok', {}). 88 | -record('basic.qos', {prefetch_size = 0, prefetch_count = 0, global = false}). 89 | -record('basic.qos_ok', {}). 90 | -record('basic.consume', {ticket = 0, queue = <<"">>, consumer_tag = <<"">>, no_local = false, no_ack = false, exclusive = false, nowait = false, arguments = []}). 91 | -record('basic.consume_ok', {consumer_tag}). 92 | -record('basic.cancel', {consumer_tag, nowait = false}). 93 | -record('basic.cancel_ok', {consumer_tag}). 94 | -record('basic.publish', {ticket = 0, exchange = <<"">>, routing_key = <<"">>, mandatory = false, immediate = false}). 95 | -record('basic.return', {reply_code, reply_text = <<"">>, exchange, routing_key}). 96 | -record('basic.deliver', {consumer_tag, delivery_tag, redelivered = false, exchange, routing_key}). 97 | -record('basic.get', {ticket = 0, queue = <<"">>, no_ack = false}). 98 | -record('basic.get_ok', {delivery_tag, redelivered = false, exchange, routing_key, message_count}). 99 | -record('basic.get_empty', {cluster_id = <<"">>}). 100 | -record('basic.ack', {delivery_tag = 0, multiple = false}). 101 | -record('basic.reject', {delivery_tag, requeue = true}). 102 | -record('basic.recover_async', {requeue = false}). 103 | -record('basic.recover', {requeue = false}). 104 | -record('basic.recover_ok', {}). 105 | -record('basic.nack', {delivery_tag = 0, multiple = false, requeue = true}). 106 | -record('basic.credit', {consumer_tag = <<"">>, credit, drain}). 107 | -record('basic.credit_ok', {available}). 108 | -record('basic.credit_drained', {consumer_tag = <<"">>, credit_drained}). 109 | -record('tx.select', {}). 110 | -record('tx.select_ok', {}). 111 | -record('tx.commit', {}). 112 | -record('tx.commit_ok', {}). 113 | -record('tx.rollback', {}). 114 | -record('tx.rollback_ok', {}). 115 | -record('confirm.select', {nowait = false}). 116 | -record('confirm.select_ok', {}). 117 | -record('file.qos', {prefetch_size = 0, prefetch_count = 0, global = false}). 118 | -record('file.qos_ok', {}). 119 | -record('file.consume', {ticket = 1, queue = <<"">>, consumer_tag = <<"">>, no_local = false, no_ack = false, exclusive = false, nowait = false}). 120 | -record('file.consume_ok', {consumer_tag}). 121 | -record('file.cancel', {consumer_tag, nowait = false}). 122 | -record('file.cancel_ok', {consumer_tag}). 123 | -record('file.open', {identifier, content_size}). 124 | -record('file.open_ok', {staged_size}). 125 | -record('file.stage', {}). 126 | -record('file.publish', {ticket = 1, exchange = <<"">>, routing_key = <<"">>, mandatory = false, immediate = false, identifier}). 127 | -record('file.return', {reply_code = 200, reply_text = <<"">>, exchange, routing_key}). 128 | -record('file.deliver', {consumer_tag, delivery_tag, redelivered = false, exchange, routing_key, identifier}). 129 | -record('file.ack', {delivery_tag = 0, multiple = false}). 130 | -record('file.reject', {delivery_tag, requeue = true}). 131 | -record('stream.qos', {prefetch_size = 0, prefetch_count = 0, consume_rate = 0, global = false}). 132 | -record('stream.qos_ok', {}). 133 | -record('stream.consume', {ticket = 1, queue = <<"">>, consumer_tag = <<"">>, no_local = false, exclusive = false, nowait = false}). 134 | -record('stream.consume_ok', {consumer_tag}). 135 | -record('stream.cancel', {consumer_tag, nowait = false}). 136 | -record('stream.cancel_ok', {consumer_tag}). 137 | -record('stream.publish', {ticket = 1, exchange = <<"">>, routing_key = <<"">>, mandatory = false, immediate = false}). 138 | -record('stream.return', {reply_code = 200, reply_text = <<"">>, exchange, routing_key}). 139 | -record('stream.deliver', {consumer_tag, delivery_tag, exchange, queue}). 140 | -record('dtx.select', {}). 141 | -record('dtx.select_ok', {}). 142 | -record('dtx.start', {dtx_identifier}). 143 | -record('dtx.start_ok', {}). 144 | -record('tunnel.request', {meta_data}). 145 | -record('test.integer', {integer_1, integer_2, integer_3, integer_4, operation}). 146 | -record('test.integer_ok', {result}). 147 | -record('test.string', {string_1, string_2, operation}). 148 | -record('test.string_ok', {result}). 149 | -record('test.table', {table, integer_op, string_op}). 150 | -record('test.table_ok', {integer_result, string_result}). 151 | -record('test.content', {}). 152 | -record('test.content_ok', {content_checksum}). 153 | %% Class property records. 154 | -record('P_connection', {}). 155 | -record('P_channel', {}). 156 | -record('P_access', {}). 157 | -record('P_exchange', {}). 158 | -record('P_queue', {}). 159 | -record('P_basic', {content_type, content_encoding, headers, delivery_mode, priority, correlation_id, reply_to, expiration, message_id, timestamp, type, user_id, app_id, cluster_id}). 160 | -record('P_tx', {}). 161 | -record('P_confirm', {}). 162 | -record('P_file', {content_type, content_encoding, headers, priority, reply_to, message_id, filename, timestamp, cluster_id}). 163 | -record('P_stream', {content_type, content_encoding, headers, priority, timestamp}). 164 | -record('P_dtx', {}). 165 | -record('P_tunnel', {headers, proxy_name, data_name, durable, broadcast}). 166 | -record('P_test', {}). 167 | -------------------------------------------------------------------------------- /include/rabbit_common/include/rabbit_msg_store.hrl: -------------------------------------------------------------------------------- 1 | %% The contents of this file are subject to the Mozilla Public License 2 | %% Version 1.1 (the "License"); you may not use this file except in 3 | %% compliance with the License. You may obtain a copy of the License 4 | %% at http://www.mozilla.org/MPL/ 5 | %% 6 | %% Software distributed under the License is distributed on an "AS IS" 7 | %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See 8 | %% the License for the specific language governing rights and 9 | %% limitations under the License. 10 | %% 11 | %% The Original Code is RabbitMQ. 12 | %% 13 | %% The Initial Developer of the Original Code is GoPivotal, Inc. 14 | %% Copyright (c) 2007-2013 GoPivotal, Inc. All rights reserved. 15 | %% 16 | 17 | -include("rabbit.hrl"). 18 | 19 | -ifdef(use_specs). 20 | 21 | -type(msg() :: any()). 22 | 23 | -endif. 24 | 25 | -record(msg_location, {msg_id, ref_count, file, offset, total_size}). 26 | -------------------------------------------------------------------------------- /include/state.hrl: -------------------------------------------------------------------------------- 1 | %%here comes the state`s information of the gen_server 2 | 3 | -record(state, {stream_id, 4 | uri, 5 | parser, 6 | data_type, 7 | channel, 8 | exchange, 9 | connection}). -------------------------------------------------------------------------------- /javascripts/package.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "name": "required-deps", 4 | "description":"Installs the necessary dependencies for running receive.js", 5 | "dependencies": { 6 | "socket.io": "*", 7 | "rabbit.js": "*" 8 | } 9 | } -------------------------------------------------------------------------------- /javascripts/receive.js: -------------------------------------------------------------------------------- 1 | 2 | // Create context using rabbit.js (cfr ZMQ), 3 | // io and the subscriber socket. 4 | 5 | var port = 8080, 6 | context = require('rabbit.js').createContext(), 7 | io = require('socket.io').listen(port), 8 | url = require('url'); 9 | var streams = new Object(); 10 | process.setMaxListeners(0); 11 | // Limits debug messages that are printed by socket.io 12 | io.set('log level', 1); 13 | 14 | // A websocket is connected (eg: browser). 15 | io.sockets.on('connection', function(socket) { 16 | var sub = context.socket('SUB'); 17 | sub.setEncoding('utf8'); 18 | var con_url = (url.parse(socket.handshake.url, true).query.ns).split("/"); 19 | var namespace = con_url[con_url.length - 1]; 20 | if(streams[namespace] == undefined) { 21 | streams[namespace] = 1; 22 | sub.connect(namespace); 23 | console.log("Created "+namespace+" with "+streams[namespace]+" users connected."); 24 | }else { 25 | streams[namespace]++; 26 | console.log("Connected to "+namespace+" with "+streams[namespace]+" users connected."); 27 | } 28 | 29 | io.of('/'+namespace).on('connection', function(sock) { 30 | sub.on('data', function(data) { 31 | sock.send(data); 32 | //console.log(io.sockets.clients().length); 33 | }); 34 | }); 35 | socket.on('disconnect', function() { 36 | streams[namespace]--; 37 | 38 | console.log("Disconnected from "+namespace+" with "+streams[namespace]+" users connected."); 39 | if(streams[namespace] == 0) { 40 | console.log("No user connected to "+namespace); 41 | //streams[namespace] = undefined; 42 | //sub.destroy(); 43 | console.log("NameSpace : "+namespace+" is "+streams[namespace]); 44 | } 45 | }); 46 | }); 47 | -------------------------------------------------------------------------------- /priv/dispatch.conf: -------------------------------------------------------------------------------- 1 | %%-*- mode: erlang -*- 2 | 3 | 4 | %%STREAMS 5 | {["users", 'user', "streams", "_search"], streams, []}. 6 | {["users", 'user', "streams", 'stream'], streams, []}. 7 | {["users", 'user', "streams"], streams, []}. 8 | {["streams",'stream',"pollinghistory"], streams, []}. 9 | {["streams", "_search"], streams, []}. 10 | {["streams", 'stream'], streams, []}. 11 | {["streams"], streams, []}. 12 | 13 | %%RESOURCES 14 | {["resources", 'resourceid'], resources, []}. 15 | {["resources", "_search"], resources, []}. 16 | {["resources"], resources, []}. 17 | 18 | %%USERS 19 | {["users", "_auth"], users, []}. 20 | {["users", "_search"], users, []}. 21 | {["users", 'id'], users, []}. 22 | {["users"], users, []}. 23 | 24 | %%VIRTUAL STREAMS 25 | {["vstreams", 'id'], virtual_streams, []}. 26 | {["vstreams", "_search"], virtual_streams, []}. 27 | {["vstreams"], virtual_streams, []}. 28 | {["users", 'user', "vstreams"], virtual_streams, []}. 29 | 30 | %%GROUPS 31 | {["users", 'user',"groups","_search"], groups, []}. 32 | {["users", 'user',"groups",'group'], groups, []}. 33 | 34 | %% list a group or groups if they are public 35 | {["groups", 'group'], groups, []}. 36 | {["groups"], groups, []}. 37 | 38 | %%SUGGESTIONS 39 | {["suggest",'field','term'], suggest, []}. 40 | {["suggest",'term'], suggest, []}. 41 | {["suggest"], suggest, []}. 42 | 43 | %%TRIGGERS 44 | {['users', 'userid', "streams", 'streamid', "triggers"], triggers, []}. 45 | {['users', 'userid', "vstreams", 'vstreamid', "triggers"], triggers, []}. 46 | {["users", 'userid', "triggers",'action'], triggers, []}. 47 | {["users", 'userid', "triggers"], triggers, []}. 48 | 49 | %%DATA-POINTS 50 | {["streams", 'id', "data", "_search"], datapoints, []}. 51 | {["streams", 'id', "data", "_count"], datapoints, []}. 52 | {["streams", 'id', "data"], datapoints, []}. 53 | {["vstreams", 'id', "data", "_search"], datapoints, []}. 54 | {["vstreams", 'id', "data", "_count"], datapoints, []}. 55 | {["vstreams", 'id', "data"], datapoints, []}. 56 | 57 | %%SEARCH 58 | {["_search"], search, []}. 59 | {["_history"], search, []}. 60 | 61 | %%ANALYSE 62 | {["users", 'userid', "streams", 'streamid', "_analyse"], analyse, []}. 63 | {["streams", 'streamid', "_analyse"], analyse, []}. 64 | {["vstreams", 'vstreamid', "_analyse"], analyse, []}. 65 | 66 | %%RANK 67 | {["streams", 'stream', "_rank"], streams, []}. 68 | 69 | %%SUBSCRIPTION 70 | {["users", 'id', "_unsubscribe"], users, []}. 71 | {["users", 'id', "_subscribe"], users, []}. 72 | 73 | %% ?? 74 | {['*'], static_resource, [{root, "priv/www"}]}. 75 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /priv/www/stream.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 11 | 12 | 13 | Awesome Stream Handler 14 | 16 | 71 | 72 | 73 | Create a new stream: 74 |
75 | Stream name: 76 | 77 |
78 |
79 | Fetch streams: 80 |
81 | 82 |
83 |
84 | Delete stream: 85 |
86 | 87 | 88 |
89 |
90 | Update stream: 91 |
92 | Stream id: 93 | Stream description: 94 | 95 |
96 | 97 | 98 | -------------------------------------------------------------------------------- /rebar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/projectcs13/sensor-cloud/0302bd74b2e62fddbd832fb4c7a27b9c62852b90/rebar -------------------------------------------------------------------------------- /rebar.config: -------------------------------------------------------------------------------- 1 | %%-*- mode: erlang -*- 2 | 3 | %% Require a certain version of Erlang 4 | {require_otp_vsn, "R16|R16B01|R16B02"}. 5 | 6 | %% Specify library paths 7 | {lib_dirs, ["lib"]}. 8 | 9 | 10 | %% Edoc options 11 | {edoc_opts, [{packages, false},{private, true}]}. 12 | 13 | %% Eunit options 14 | %% Checks coverage of unit tests 15 | {cover_enabled, true}. 16 | {eunit_opts, [verbose, 17 | {report, {eunit_surefire, [{dir, "."}]}} 18 | ]}. 19 | 20 | %% Erlang compiler options 21 | {erl_opts, [debug_info, 22 | verbose, 23 | %% strong_validation, 24 | return, 25 | warn_export_all, 26 | %% This is needed because there are files in src/ that includes a file from here 27 | %% If header files from other libraries needs to be included, then the path to that include 28 | %% included below (as a separate {i, Dir} entry) 29 | {i, "include/"}, 30 | {i, "lib/erlastic_search/include/"}, 31 | {i, "lib/erlson/include/"}, 32 | {i, "lib/rabbitmq-erlang-client/include/"}, 33 | {i, "lib/webmachine/include/"}, 34 | {src_dirs, ["src","test"]}, 35 | {d, debug} 36 | 37 | ]}. 38 | 39 | {deps_dir, ["lib"]}. 40 | {deps, [ 41 | {webmachine, "1.10.*", {git, "git://github.com/basho/webmachine", "HEAD"}}, 42 | {erlastic_search, ".*", {git, "git://github.com/projectcs13/erlastic_search.git", {branch, "master"}}}, 43 | {elasticsearch, ".*", {git, "git://github.com/projectcs13/elasticsearch.git", {branch, "develop_silver_bullet"}}, [raw]}, 44 | {"rabbitmq-server", ".*", {git, "git://github.com/rabbitmq/rabbitmq-server.git", {tag, "rabbitmq_v3_1_5"}}, [raw]}, 45 | {"rabbitmq-codegen", ".*", {git, "git://github.com/rabbitmq/rabbitmq-codegen.git", {tag, "rabbitmq_v3_1_5"}}, [raw]}, 46 | {"rabbitmq-erlang-client", ".*", {git, "git://github.com/rabbitmq/rabbitmq-erlang-client.git", {tag, "rabbitmq_v3_1_5"}}, [raw]}, 47 | {rErlang, ".*", {git, "git://github.com/projectcs13/rErlang.git", {branch, "master"}}, [raw]}, 48 | {erlson, "", {git, "https://github.com/projectcs13/erlson.git", {branch, "master"}}}, 49 | {mochijson2, "", {git, "https://github.com/bjnortier/mochijson2.git", {branch, "master"}}} 50 | ]}. 51 | 52 | {plugins, [erlson_rebar_plugin]}. % for newer rebar 53 | -------------------------------------------------------------------------------- /scripts/install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | ### Author : Tommy Mattsson 3 | ### Purpose: Easy installation of needed software for our project that is part of the linux system rather than our own project 4 | echo "#################################################################" 5 | echo "Installing misc dependencies" 6 | echo "#################################################################" 7 | sudo apt-get install -yq xsltproc software-properties-common 8 | 9 | echo "#################################################################" 10 | echo "Installing Erlang" 11 | echo "#################################################################" 12 | #grep -q -e 'deb http://packages.erlang-solutions.com/debian '$(lsb_release -sc)' contrib' /etc/apt/sources.list || echo 'deb http://packages.erlang-solutions.com/debian '$(lsb_release -sc)' contrib' >> /etc/apt/sources.list 13 | #sudo wget http://packages.erlang-solutions.com/debian/erlang_solutions.asc 14 | #sudo apt-key add erlang_solutions.asc 15 | #rm -f erlang_solutions.asc 16 | #sudo apt-get update -q 17 | #sudo apt-get install -yq erlang 18 | 19 | echo "#################################################################" 20 | echo "Installing Nodejs together with npm" 21 | echo "#################################################################" 22 | sudo apt-get install python-software-properties python g++ make 23 | sudo add-apt-repository ppa:chris-lea/node.js 24 | sudo apt-get update -q 25 | sudo apt-get install -yq nodejs 26 | 27 | echo "#################################################################" 28 | echo "Installing R" 29 | echo "#################################################################" 30 | sudo add-apt-repository "deb http://ftp.sunet.se/pub/lang/CRAN/bin/linux/ubuntu precise/" 31 | sudo apt-get update -q 32 | sudo apt-get install -yq r-base --force-yes 33 | -------------------------------------------------------------------------------- /scripts/python/README.md: -------------------------------------------------------------------------------- 1 | ## Resource for polling 2 | 3 | This script acts as a dummy resource which you can poll. 4 | You start a small webserver which you can request a 5 | resource JSON-object from. 6 | 7 | To use the script, you start a CGI HTTP server in python. 8 | 9 | From the terminal, in this directory, type: 10 | 11 | python -m CGIHTTPServer 8000 12 | 13 | The port number (8000) can be whatever. 14 | You will now have the resource accessable at 15 | loclahost:8000/cgi-bin/resource.py 16 | 17 | If you get a restriction-error upon GET-request, 18 | Make sure you have the right permissions to resource.py 19 | by typing 20 | 21 | chmod 755 resource.py 22 | 23 | ## Self-posting stream 24 | 25 | The script post_avg.py reads the average load from the file "/proc/loadavg" and sends the value to the specified 26 | stream at the engine url. the script is called using: 27 | 28 | python post_avg.py 29 | 30 | Example use 31 | 32 | python post_avg.py asd21 30 http://localhost:8000 33 | 34 | the above example will post '{"value": }' every 30 seconds to http://localhost:8000/streams/asd21/data 35 | 36 | The script posts using curl and thus requires curl to be installed. 37 | 38 | ## Post streams to localhost:8000 39 | 40 | The script poststreams.py will post streams in a file to localhost:8000/streams, the example files are 41 | weatherstreams, financialstreams and gamestreams. These files contains the JSON objects of a number of streams 42 | and they have the user_id 'andreas'. Either make sure that the user 'andreas' exists or change the user_ids in 43 | the files. 44 | 45 | If you want to make your own streamfiles, make sure that each stream object is contained in ONE line and that 46 | there are no empty lines between objects. Also make sure that the file does not contain trailing endlines or 47 | whitespaces. 48 | 49 | Example usages: 50 | python poststreams.py weatherstreams 51 | 52 | python poststreams.py financialstreams 53 | 54 | python poststreams.py gamestreams 55 | 56 | 57 | -------------------------------------------------------------------------------- /scripts/python/cgi-bin/humidity.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import datetime 3 | import random 4 | timestamp = datetime.datetime.now() 5 | humidity = random.random() 6 | print "Content-Type: application/json" 7 | print 8 | print """\ 9 | {"resource": "polling-resource", 10 | "streams": 11 | { 12 | "humidity": {"value": %f, "timestamp": "%s"} 13 | } 14 | } 15 | """ % (humidity, timestamp.strftime("%Y-%m-%d %H:%M:%S")) 16 | -------------------------------------------------------------------------------- /scripts/python/cgi-bin/temperature.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import datetime 3 | import random 4 | timestamp = datetime.datetime.now() 5 | temperature = random.random() * 100 6 | print "Content-Type: application/json" 7 | print 8 | print """\ 9 | {"resource": "polling-resource", 10 | "streams": 11 | { 12 | "temperature": {"value": %f, "timestamp": "%s"} 13 | } 14 | } 15 | """ % (temperature, timestamp.strftime("%Y-%m-%d %H:%M:%S")) 16 | -------------------------------------------------------------------------------- /scripts/python/financialstreams: -------------------------------------------------------------------------------- 1 | {"name":"EURO vs SEK","description":"This stream displays the value of one EURO in SEK","type":"currency","location":"0,0","tags":"financial, economic, currency, finance, money, sek, euro, eur","private":false,"unit":"SEK","accuracy":0.001,"min_val":0,"max_val":10000,"polling":true,"data_type":"application/json","parser":"rate","uri":"http://rate-exchange.appspot.com/currency?from=EUR&to=SEK","polling_freq":1800,"user_id":"andreas"} 2 | {"name":"USD vs SEK","description":"This stream displays the value of one US dollar in SEK","type":"currency","location":"0,0","tags":"financial, economic, currency, finance, money, sek, USD, dollar","private":false,"unit":"SEK","accuracy":0.001,"min_val":0,"max_val":10000,"polling":true,"data_type":"application/json","parser":"rate","uri":"http://rate-exchange.appspot.com/currency?from=USD&to=SEK","polling_freq":1800,"user_id":"andreas"} 3 | {"name":"GBP vs SEK","description":"This stream displays the value of one Brittish pound in SEK","type":"currency","location":"0,0","tags":"financial, economic, currency, finance, money, sek, GBP, pound","private":false,"unit":"SEK","accuracy":0.001,"min_val":0,"max_val":10000,"polling":true,"data_type":"application/json","parser":"rate","uri":"http://rate-exchange.appspot.com/currency?from=GBP&to=SEK","polling_freq":1800,"user_id":"andreas"} 4 | {"name":"Bitcoin vs SEK","description":"This stream displays the value of one bitcoin in SEK","type":"currency","location":"0,0","tags":"financial, economic, currency, finance, money, sek, bitcoin","private":false,"unit":"SEK","accuracy":0.001,"min_val":0,"max_val":10000000,"polling":true,"data_type":"application/json","parser":"SEK.24h","uri":"http://api.bitcoincharts.com/v1/weighted_prices.json","polling_freq":86400,"user_id":"andreas"} 5 | {"name":"Bitcoin vs USD","description":"This stream displays the value of one bitcoin in USD","type":"currency","location":"0,0","tags":"financial, economic, currency, finance, money, USD, dollar, bitcoin","private":false,"unit":"USD","accuracy":0.001,"min_val":0,"max_val":10000000,"polling":true,"data_type":"application/json","parser":"USD.24h","uri":"http://api.bitcoincharts.com/v1/weighted_prices.json","polling_freq":86400,"user_id":"andreas"} 6 | -------------------------------------------------------------------------------- /scripts/python/gamestreams: -------------------------------------------------------------------------------- 1 | {"name":"Dota 2: Players Online","description":"This stream displays the number of players currently online in Dota 2","type":"players","location":"0,0","tags":"Dota2,players,online,steam,website","private":false,"unit":"Players","accuracy":1,"min_val":0,"max_val":100000000,"polling":true,"data_type":"application/json","parser":"response.player_count","uri":"http://api.steampowered.com/ISteamUserStats/GetNumberOfCurrentPlayers/v1?appid=570","polling_freq":300,"resource":{"resource_type":"Website","uuid":"http://api.steampowered.com"},"user_id":"andreas"} 2 | {"name":"Counterstrike: Players Online","description":"This stream displays the number of players currently online playing Counterstrike","type":"players","location":"0,0","tags":"CS,Counterstrike,players,online,steam,website","private":false,"unit":"Players","accuracy":1,"min_val":0,"max_val":100000000,"polling":true,"data_type":"application/json","parser":"response.player_count","uri":"http://api.steampowered.com/ISteamUserStats/GetNumberOfCurrentPlayers/v1?appid=10","polling_freq":300,"resource":{"resource_type":"Website","uuid":"http://api.steampowered.com"},"user_id":"andreas"} 3 | {"name":"Counterstrike Source : Players Online","description":"This stream displays the number of players currently online playing Counterstrike","type":"player_count","location":"0,0","tags":"CS,Counterstrike,source,players,online,steam,website","private":false,"unit":"Players","accuracy":1,"min_val":0,"max_val":100000000,"polling":true,"data_type":"application/json","parser":"response.player_count","uri":"http://api.steampowered.com/ISteamUserStats/GetNumberOfCurrentPlayers/v1?appid=240","polling_freq":300,"user_id":"andreas"} 4 | -------------------------------------------------------------------------------- /scripts/python/post_avg.py: -------------------------------------------------------------------------------- 1 | import os 2 | import time 3 | import sys 4 | import subprocess 5 | 6 | stream_id = str(sys.argv[1]) 7 | interval = int(sys.argv[2]) 8 | base_url = str(sys.argv[3]) 9 | while 1: 10 | time.sleep(interval) 11 | a = open("/proc/loadavg") 12 | b = a.readline().split(" ") 13 | loadavg = float(b[0])*100 14 | a.close() 15 | value = '{"value": %f}' % loadavg 16 | url = base_url + '/streams/'+ stream_id +'/data' 17 | subprocess.call(['curl', '-XPOST', '-H', 'Content-type: application/json', '-d', value, url]) 18 | 19 | -------------------------------------------------------------------------------- /scripts/python/poststreams.py: -------------------------------------------------------------------------------- 1 | import os 2 | import time 3 | import sys 4 | import urllib 5 | import urllib2 6 | 7 | filename = str(sys.argv[1]) 8 | f = open(filename, 'r') 9 | url = "http://localhost:8000/streams" 10 | 11 | for line in f: 12 | req = urllib2.Request(url) 13 | print line 14 | req.add_data(line) 15 | response = urllib2.urlopen(req) 16 | print response.read() 17 | time.sleep(0.1) 18 | 19 | -------------------------------------------------------------------------------- /scripts/sensec.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | dir=`dirname $0` 3 | #Specify project home directory. 4 | HOME_PATH=`cd $dir;cd ..;pwd` 5 | LOG_DIR=priv/logs 6 | LOG_JS_DIR=priv/logs 7 | ES_PORT=9200 8 | MY_PATH=`pwd` 9 | 10 | if [[ ("$1" = "start") || ("$1" = "test_setup") ]]; then 11 | cd $HOME_PATH 12 | echo "starting rabbit" 13 | make run_rabbit & 14 | echo $! >> $HOME_PATH/.temp.log 15 | echo "starting ES" 16 | make run_es & 17 | echo $! >> $HOME_PATH/.temp.log 18 | echo "Starting node.js with receive.js" 19 | if [ -d "$LOG_JS_DIR" ]; then 20 | make run_nodejs > $LOG_JS_DIR/nodejs_log.log & 21 | echo $! >> $HOME_PATH/.temp.log 22 | else 23 | make run_nodejs & 24 | echo $! >> $HOME_PATH/.temp.log 25 | fi 26 | sleep 7 27 | echo "Starting IoT-Framework" 28 | export R_HOME="/usr/lib/R" 29 | curl -XPUT localhost:$ES_PORT/sensorcloud 30 | sleep 3 31 | if [ "$1" = "start" ]; then 32 | if [ -d "$LOG_DIR" ]; then 33 | erl -noshell -pa $HOME_PATH/ebin/ $HOME_PATH/lib/*/ebin/ $HOME_PATH/lib/*/bin/ -boot start_sasl -s reloader -s engine -sname engine -config $HOME_PATH/config/engine.config > $LOG_DIR/sensor-cloud_log.log & 34 | else 35 | erl -noshell -pa $HOME_PATH/ebin/ $HOME_PATH/lib/*/ebin/ $HOME_PATH/lib/*/bin/ -boot start_sasl -s reloader -s engine -sname engine -config $HOME_PATH/config/engine.config & 36 | fi 37 | fi 38 | echo $! >> $HOME_PATH/.temp.log 39 | cd $MY_PATH 40 | elif [ "$1" = "stop" ]; then 41 | echo "Closing nodejs and Sensor-Cloud" 42 | while read line 43 | do 44 | kill -- -$(ps opgid= $line | tr -d ' ') 45 | done < $HOME_PATH/.temp.log 46 | rm $HOME_PATH/.temp.log 47 | fi 48 | 49 | 50 | -------------------------------------------------------------------------------- /scripts/travis-elasticsearch.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | is_elasticsearch_up(){ 4 | http_code=`echo $(curl -s -o /dev/null -w "%{http_code}" "http://localhost:9200")` 5 | return `test $http_code = "200"` 6 | } 7 | 8 | wait_for_elasticsearch(){ 9 | while ! is_elasticsearch_up; do 10 | sleep 3 11 | done 12 | } 13 | 14 | run() { 15 | echo "Starting elasticsearch ..." 16 | if [ $DEBUG ] 17 | then 18 | ./lib/elasticsearch/bin/elasticsearch -f 19 | else 20 | ./lib/elasticsearch/bin/elasticsearch 2>&1 /dev/null 21 | fi 22 | wait_for_elasticsearch 23 | cd ../../ 24 | echo "Started" 25 | } 26 | 27 | run 28 | -------------------------------------------------------------------------------- /src/api_help.erl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/projectcs13/sensor-cloud/0302bd74b2e62fddbd832fb4c7a27b9c62852b90/src/api_help.erl -------------------------------------------------------------------------------- /src/destructure_json.erl: -------------------------------------------------------------------------------- 1 | %% Copyright (c) 2010,2011 Alessandro Sivieri 2 | %% 3 | %% This file is part of CREST-Erlang. 4 | %% 5 | %% CREST-Erlang is free software: you can redistribute it and/or modify 6 | %% it under the terms of the GNU Lesser General Public License as published by 7 | %% the Free Software Foundation, either version 3 of the License, or 8 | %% (at your option) any later version. 9 | %% 10 | %% CREST-Erlang is distributed in the hope that it will be useful, 11 | %% but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | %% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | %% GNU Lesser General Public License for more details. 14 | %% 15 | %% You should have received a copy of the GNU Lesser General Public License 16 | %% along with CREST-Erlang. If not, see . 17 | %% 18 | -module(destructure_json). 19 | -export([parse/1,file/1]). 20 | -compile(nowarn_unused_vars). 21 | -compile({nowarn_unused_function,[p/4, p/5, p_eof/0, p_optional/1, p_not/1, p_assert/1, p_seq/1, p_and/1, p_choose/1, p_zero_or_more/1, p_one_or_more/1, p_label/2, p_string/1, p_anything/0, p_charclass/1, line/1, column/1]}). 22 | 23 | 24 | 25 | file(Filename) -> {ok, Bin} = file:read_file(Filename), parse(binary_to_list(Bin)). 26 | 27 | parse(Input) -> 28 | setup_memo(), 29 | Result = case 'object'(Input,{{line,1},{column,1}}) of 30 | {AST, [], _Index} -> AST; 31 | Any -> Any 32 | end, 33 | release_memo(), Result. 34 | 35 | 'object'(Input, Index) -> 36 | p(Input, Index, 'object', fun(I,D) -> (p_seq([fun 'var'/2, fun 'path'/2]))(I,D) end, fun(Node, Idx) -> 37 | [_Var, PathFun] = Node, 38 | fun(JSON) -> 39 | PathFun(JSON) 40 | end 41 | end). 42 | 43 | 'path'(Input, Index) -> 44 | p(Input, Index, 'path', fun(I,D) -> (p_choose([p_seq([p_string("."), fun 'var'/2, p_optional(fun 'path'/2)]), p_seq([p_string("["), fun 'int'/2, p_string("]"), p_optional(fun 'path'/2)])]))(I,D) end, fun(Node, Idx) -> 45 | case Node of 46 | [".", Key, []] -> 47 | fun({struct, Obj}) -> 48 | proplists:get_value(Key, Obj) 49 | end; 50 | [".", Key, PathFun] -> 51 | fun({struct, Obj}) -> 52 | V = proplists:get_value(Key, Obj), 53 | PathFun(V) 54 | end; 55 | ["[", I, "]", []] -> 56 | fun(Array) -> 57 | lists:nth(I + 1, Array) 58 | end; 59 | ["[", I, "]", PathFun] -> 60 | fun(Array) -> 61 | V = lists:nth(I + 1, Array), 62 | PathFun(V) 63 | end 64 | end 65 | end). 66 | 67 | 'int'(Input, Index) -> 68 | p(Input, Index, 'int', fun(I,D) -> (p_one_or_more(p_charclass("[0-9]")))(I,D) end, fun(Node, Idx) -> list_to_integer(Node) end). 69 | 70 | 'var'(Input, Index) -> 71 | p(Input, Index, 'var', fun(I,D) -> (p_seq([p_charclass("[_a-zA-Z]"), p_zero_or_more(p_charclass("[_a-zA-Z0-9]"))]))(I,D) end, fun(Node, Idx) -> list_to_binary(Node) end). 72 | 73 | 74 | 75 | 76 | 77 | 78 | p(Inp, Index, Name, ParseFun) -> 79 | p(Inp, Index, Name, ParseFun, fun(N, _Idx) -> N end). 80 | 81 | p(Inp, StartIndex, Name, ParseFun, TransformFun) -> 82 | % Grab the memo table from ets 83 | Memo = get_memo(StartIndex), 84 | % See if the current reduction is memoized 85 | case dict:find(Name, Memo) of 86 | % If it is, return the result 87 | {ok, Result} -> Result; 88 | % If not, attempt to parse 89 | _ -> 90 | case ParseFun(Inp, StartIndex) of 91 | % If it fails, memoize the failure 92 | {fail,_} = Failure -> 93 | memoize(StartIndex, dict:store(Name, Failure, Memo)), 94 | Failure; 95 | % If it passes, transform and memoize the result. 96 | {Result, InpRem, NewIndex} -> 97 | Transformed = TransformFun(Result, StartIndex), 98 | memoize(StartIndex, dict:store(Name, {Transformed, InpRem, NewIndex}, Memo)), 99 | {Transformed, InpRem, NewIndex} 100 | end 101 | end. 102 | 103 | setup_memo() -> 104 | put(parse_memo_table, ets:new(?MODULE, [set])). 105 | 106 | release_memo() -> 107 | ets:delete(memo_table_name()). 108 | 109 | memoize(Position, Struct) -> 110 | ets:insert(memo_table_name(), {Position, Struct}). 111 | 112 | get_memo(Position) -> 113 | case ets:lookup(memo_table_name(), Position) of 114 | [] -> dict:new(); 115 | [{Position, Dict}] -> Dict 116 | end. 117 | 118 | memo_table_name() -> 119 | get(parse_memo_table). 120 | 121 | p_eof() -> 122 | fun([], Index) -> {eof, [], Index}; 123 | (_, Index) -> {fail, {expected, eof, Index}} end. 124 | 125 | p_optional(P) -> 126 | fun(Input, Index) -> 127 | case P(Input, Index) of 128 | {fail,_} -> {[], Input, Index}; 129 | {_, _, _} = Success -> Success 130 | end 131 | end. 132 | 133 | p_not(P) -> 134 | fun(Input, Index)-> 135 | case P(Input,Index) of 136 | {fail,_} -> 137 | {[], Input, Index}; 138 | {Result, _, _} -> {fail, {expected, {no_match, Result},Index}} 139 | end 140 | end. 141 | 142 | p_assert(P) -> 143 | fun(Input,Index) -> 144 | case P(Input,Index) of 145 | {fail,_} = Failure-> Failure; 146 | _ -> {[], Input, Index} 147 | end 148 | end. 149 | 150 | p_and(P) -> 151 | p_seq(P). 152 | 153 | p_seq(P) -> 154 | fun(Input, Index) -> 155 | p_all(P, Input, Index, []) 156 | end. 157 | 158 | p_all([], Inp, Index, Accum ) -> {lists:reverse( Accum ), Inp, Index}; 159 | p_all([P|Parsers], Inp, Index, Accum) -> 160 | case P(Inp, Index) of 161 | {fail, _} = Failure -> Failure; 162 | {Result, InpRem, NewIndex} -> p_all(Parsers, InpRem, NewIndex, [Result|Accum]) 163 | end. 164 | 165 | p_choose(Parsers) -> 166 | fun(Input, Index) -> 167 | p_attempt(Parsers, Input, Index, none) 168 | end. 169 | 170 | p_attempt([], _Input, _Index, Failure) -> Failure; 171 | p_attempt([P|Parsers], Input, Index, FirstFailure)-> 172 | case P(Input, Index) of 173 | {fail, _} = Failure -> 174 | case FirstFailure of 175 | none -> p_attempt(Parsers, Input, Index, Failure); 176 | _ -> p_attempt(Parsers, Input, Index, FirstFailure) 177 | end; 178 | Result -> Result 179 | end. 180 | 181 | p_zero_or_more(P) -> 182 | fun(Input, Index) -> 183 | p_scan(P, Input, Index, []) 184 | end. 185 | 186 | p_one_or_more(P) -> 187 | fun(Input, Index)-> 188 | Result = p_scan(P, Input, Index, []), 189 | case Result of 190 | {[_|_], _, _} -> 191 | Result; 192 | _ -> 193 | {fail, {expected, Failure, _}} = P(Input,Index), 194 | {fail, {expected, {at_least_one, Failure}, Index}} 195 | end 196 | end. 197 | 198 | p_label(Tag, P) -> 199 | fun(Input, Index) -> 200 | case P(Input, Index) of 201 | {fail,_} = Failure -> 202 | Failure; 203 | {Result, InpRem, NewIndex} -> 204 | {{Tag, Result}, InpRem, NewIndex} 205 | end 206 | end. 207 | 208 | p_scan(_, [], Index, Accum) -> {lists:reverse( Accum ), [], Index}; 209 | p_scan(P, Inp, Index, Accum) -> 210 | case P(Inp, Index) of 211 | {fail,_} -> {lists:reverse(Accum), Inp, Index}; 212 | {Result, InpRem, NewIndex} -> p_scan(P, InpRem, NewIndex, [Result | Accum]) 213 | end. 214 | 215 | p_string(S) -> 216 | fun(Input, Index) -> 217 | case lists:prefix(S, Input) of 218 | true -> {S, lists:sublist(Input, length(S)+1, length(Input)), p_advance_index(S,Index)}; 219 | _ -> {fail, {expected, {string, S}, Index}} 220 | end 221 | end. 222 | 223 | p_anything() -> 224 | fun([], Index) -> {fail, {expected, any_character, Index}}; 225 | ([H|T], Index) -> {H, T, p_advance_index(H, Index)} 226 | end. 227 | 228 | p_charclass(Class) -> 229 | fun(Inp, Index) -> 230 | {ok, RE} = re:compile("^"++Class), 231 | case re:run(Inp, RE) of 232 | {match, _} -> 233 | {hd(Inp), tl(Inp), p_advance_index(hd(Inp), Index)}; 234 | _ -> {fail,{expected, {character_class, Class}, Index}} 235 | end 236 | end. 237 | 238 | line({{line,L},_}) -> L; 239 | line(_) -> undefined. 240 | 241 | column({_,{column,C}}) -> C; 242 | column(_) -> undefined. 243 | 244 | p_advance_index(MatchedInput, Index) when is_list(MatchedInput) -> % strings 245 | lists:foldl(fun p_advance_index/2, Index, MatchedInput); 246 | p_advance_index(MatchedInput, Index) when is_integer(MatchedInput) -> % single characters 247 | {{line, Line}, {column, Col}} = Index, 248 | case MatchedInput of 249 | $\n -> {{line, Line+1}, {column, 1}}; 250 | _ -> {{line, Line}, {column, Col+1}} 251 | end. 252 | -------------------------------------------------------------------------------- /src/engine.app.src: -------------------------------------------------------------------------------- 1 | %%-*- mode: erlang -*- 2 | {application, engine, 3 | [ 4 | {description, "engine"}, 5 | {vsn, "1"}, 6 | {modules, []}, 7 | {registered, []}, 8 | {applications, [ 9 | kernel, 10 | stdlib, 11 | inets, 12 | ibrowse, 13 | crypto, 14 | mochiweb, 15 | webmachine 16 | ]}, 17 | {mod, { engine_app, []}}, 18 | {env, []} 19 | ]}. 20 | -------------------------------------------------------------------------------- /src/engine.erl: -------------------------------------------------------------------------------- 1 | %% @author Georgios Koutsoumpakis, Li Hao 2 | %% [www.csproj13.student.it.uu.se] 3 | %% @version 1.0 4 | %% @copyright [Copyright information] 5 | 6 | %% @doc engine startup code 7 | 8 | -module(engine). 9 | -author('author '). 10 | -export([start/0, start_link/0, stop/0]). 11 | 12 | ensure_started(App) -> 13 | case application:start(App) of 14 | ok -> 15 | ok; 16 | {error, {already_started, App}} -> 17 | ok 18 | end. 19 | 20 | %% @spec start_link() -> {ok,Pid::pid()} 21 | %% @doc Starts the app for inclusion in a supervisor tree 22 | start_link() -> 23 | ensure_started(crypto), 24 | ensure_started(as1), 25 | ensure_started(inets), 26 | ensure_started(ibrowse), 27 | ensure_started(public_key), 28 | ensure_started(ssl), 29 | ensure_started(xmerl), 30 | ensure_started(compiler), 31 | ensure_started(syntax_tools), 32 | ensure_started(mochiweb), 33 | application:set_env(webmachine, webmachine_logger_module, 34 | webmachine_logger), 35 | ensure_started(webmachine), 36 | engine_sup:start_link(). 37 | 38 | %% @spec start() -> ok 39 | %% @doc Start the engine server. 40 | start() -> 41 | ensure_started(crypto), 42 | ensure_started(asn1), 43 | ensure_started(inets), 44 | ensure_started(ibrowse), 45 | ensure_started(public_key), 46 | ensure_started(ssl), 47 | ensure_started(xmerl), 48 | ensure_started(compiler), 49 | ensure_started(syntax_tools), 50 | ensure_started(mochiweb), 51 | application:set_env(webmachine, webmachine_logger_module, 52 | webmachine_logger), 53 | ensure_started(webmachine), 54 | % analyse:start(), % possibly temporary solution for ericsson demo 55 | application:start(engine). 56 | 57 | %% @spec stop() -> ok 58 | %% @doc Stop the engine server 59 | stop() -> 60 | Res = application:stop(engine), 61 | application:stop(webmachine), 62 | application:stop(mochiweb), 63 | application:stop(syntax_tools), 64 | application:stop(compiler), 65 | application:stop(xmerl), 66 | application:stop(ssl), 67 | application:stop(asn1), 68 | application:stop(crypto), 69 | application:stop(public_key), 70 | application:stop(ibrowse), 71 | application:stop(inets), 72 | case whereis(polling_monitor) of 73 | undefined -> ok; 74 | P_M_Pid -> 75 | exit(P_M_Pid, "stop") 76 | end, 77 | case whereis(polling_supervisor) of 78 | undefined -> ok; 79 | P_S_Pid -> 80 | exit(P_S_Pid, "stop") 81 | end, 82 | case whereis(vstream_sup) of 83 | undefined -> ok; 84 | V_S_Pid -> 85 | exit(V_S_Pid, "stop") 86 | end, 87 | Res. 88 | -------------------------------------------------------------------------------- /src/engine_app.erl: -------------------------------------------------------------------------------- 1 | %% @author Georgios Koutsoumpakis 2 | %% [www.csproj13.student.it.uu.se] 3 | %% @version 1.0 4 | %% @copyright [Copyright information] 5 | 6 | %% @doc Callbacks for the engine application. 7 | 8 | -module(engine_app). 9 | -author('author '). 10 | 11 | -behaviour(application). 12 | -export([start/2,stop/1]). 13 | 14 | 15 | %% @spec start(_Type, _StartArgs) -> ServerRet 16 | %% @doc application start callback for engine. 17 | start(_Type, _StartArgs) -> 18 | erlastic_search_app:start(), 19 | engine_sup:start_link(). 20 | 21 | %% @spec stop(_State) -> ServerRet 22 | %% @doc application stop callback for engine. 23 | stop(_State) -> 24 | ok. 25 | -------------------------------------------------------------------------------- /src/engine_sup.erl: -------------------------------------------------------------------------------- 1 | %% @author Georgios Koutsoumpakis 2 | %% [www.csproj13.student.it.uu.se] 3 | %% @version 1.0 4 | %% @copyright [Copyright information] 5 | 6 | %% @doc Supervisor for the engine application. 7 | 8 | -module(engine_sup). 9 | -author('author '). 10 | 11 | -behaviour(supervisor). 12 | 13 | %% External exports 14 | -export([start_link/0, upgrade/0]). 15 | 16 | %% supervisor callbacks 17 | -export([init/1]). 18 | 19 | %% @spec start_link() -> ServerRet 20 | %% @doc API for starting the supervisor. 21 | start_link() -> 22 | supervisor:start_link({local, ?MODULE}, ?MODULE, []). 23 | 24 | %% @spec upgrade() -> ok 25 | %% @doc Add processes if necessary. 26 | upgrade() -> 27 | {ok, {_, Specs}} = init([]), 28 | 29 | Old = sets:from_list( 30 | [Name || {Name, _, _, _} <- supervisor:which_children(?MODULE)]), 31 | New = sets:from_list([Name || {Name, _, _, _, _, _} <- Specs]), 32 | Kill = sets:subtract(Old, New), 33 | 34 | sets:fold(fun (Id, ok) -> 35 | supervisor:terminate_child(?MODULE, Id), 36 | supervisor:delete_child(?MODULE, Id), 37 | ok 38 | end, ok, Kill), 39 | 40 | [supervisor:start_child(?MODULE, Spec) || Spec <- Specs], 41 | ok. 42 | 43 | %% @spec init([]) -> SupervisorTree 44 | %% @doc supervisor callback. 45 | init([]) -> 46 | Ip = case os:getenv("WEBMACHINE_IP") of false -> "0.0.0.0"; Any -> Any end, 47 | {ok, App} = application:get_application(?MODULE), 48 | {ok, Dispatch} = file:consult(filename:join([priv_dir(App), 49 | "dispatch.conf"])), 50 | Port = case application:get_env(engine, webmachine_port) of 51 | undefined -> 8000; %Default port for webmachine 52 | {ok, AnyPort} -> AnyPort 53 | end, 54 | LogDir = case application:get_env(engine, webmachine_log_dir) of 55 | undefined -> "priv/log"; 56 | {ok, AnyDir} -> AnyDir 57 | end, 58 | 59 | WebConfig = [ 60 | {ip, Ip}, 61 | {port, Port}, 62 | {log_dir, LogDir}, 63 | {dispatch, Dispatch}], 64 | Web = {webmachine_mochiweb, 65 | {webmachine_mochiweb, start, [WebConfig]}, 66 | permanent, 5000, worker, [mochiweb_socket_server]}, 67 | polling_system:start_link(), 68 | virtual_stream_process_supervisor:start_link(), 69 | virtual_stream_process_supervisor:start_processes(), 70 | triggers:start_all_triggers_in_es(), 71 | Processes = [Web], 72 | {ok, { {one_for_one, 10, 10}, Processes} }. 73 | 74 | %% 75 | %% @doc return the priv dir 76 | priv_dir(Mod) -> 77 | case code:priv_dir(Mod) of 78 | {error, bad_name} -> 79 | Ebin = filename:dirname(code:which(Mod)), 80 | filename:join(filename:dirname(Ebin), "priv"); 81 | PrivDir -> 82 | PrivDir 83 | end. 84 | -------------------------------------------------------------------------------- /src/groups.erl: -------------------------------------------------------------------------------- 1 | 2 | -module(groups). 3 | -export([init/1, 4 | allowed_methods/2, 5 | exists/3, 6 | content_types_accepted/2, 7 | put_handler/2, 8 | put_group/2]). 9 | 10 | -include_lib("webmachine.hrl"). 11 | -include_lib("erlastic_search.hrl"). 12 | 13 | 14 | %% Index and Type definitions 15 | 16 | -define(INDEX, "cloud"). 17 | -define(GROUP, "group"). 18 | -define(USER, "user"). 19 | -define(STREAM, "stream"). 20 | 21 | %% @doc 22 | %% Function: init/1 23 | %% Purpose: init function used to fetch path information from webmachine dispatcher. 24 | %% Returns: {ok, undefined} 25 | %% @end 26 | -spec init([]) -> {ok, undefined}. 27 | init([]) -> 28 | %% start this in the make file somehow 29 | {ok, undefined}. 30 | 31 | %% @doc 32 | %% Function: allowed_methods/2 33 | %% Purpose: init function used to fetch path information from webmachine dispatcher. 34 | %% Returns: {ok, undefined} 35 | %% @end 36 | 37 | allowed_methods(ReqData, State) -> 38 | erlang:display(api_help:parse_path(wrq:path(ReqData))), 39 | case api_help:parse_path(wrq:path(ReqData)) of 40 | [{"users", _UserID}, {"groups", "_search"}] -> 41 | {['GET'], ReqData, State}; 42 | [{"users", _UserID}, {"groups", _GroupID}] -> 43 | {['POST','GET','PUT','DELETE'], ReqData, State}; 44 | [{"users", _UserID}, {"groups"}] -> 45 | {['GET'], ReqData, State}; 46 | [{"groups", _GroupID}] -> 47 | {['GET','PUT'], ReqData, State}; 48 | [{"groups"}] -> 49 | {['GET'], ReqData, State}; 50 | [error] -> 51 | {['POST', 'GET'], ReqData, State} % Probably should give som error message 52 | end. 53 | 54 | %% @doc 55 | %% Function exists/3 56 | %% Purpose: Given a index, type and Id corresponding to elasticsearch, 57 | %% this function checks the existence of a document. 58 | %% Returns: true | false 59 | %% @end 60 | 61 | exists(Index, Type, Id) -> 62 | case erlastic_search:get_doc(Index, Type, Id) of 63 | {ok, _Data} -> true; 64 | {error, _Error} -> false 65 | end. 66 | 67 | %% @doc 68 | %% Function: content_types_accepted/2 69 | %% Purpose: based on the content-type on a 'POST' or 'PUT', we know which kind of data that is 70 | %% allowed to be sent to the server. 71 | %% A code 406 is returned to the client if we don't accept a media type that the client has sent. 72 | %% Returns: {[{Mediatype, Handler}], ReqData, State} 73 | %% @end 74 | content_types_accepted(ReqData, State) -> 75 | {[{"application/json", put_handler}], ReqData, State}. 76 | 77 | %% @doc 78 | %% Function: put_handler/2 79 | %% Purpose: Checks to see if the user id and/or group id exists in elasticsearch 80 | %% before updating the group document. 81 | %% Returns : {true, ReqData, State} | {{error, Reason}, ReqData, State} | {error, ReqData, State} 82 | 83 | 84 | put_handler(ReqData, State) -> 85 | case api_help:parse_path(wrq:path(ReqData)) of 86 | [{"users", UserID}, {"groups", "_search"}] -> 87 | case exists(?INDEX, ?USER, UserID) of 88 | true -> put_group(ReqData, State); 89 | false -> {{error, "User ID does not exist!"}, ReqData, State} 90 | end; 91 | [{"users", UserID}, {"groups", GroupID}] -> 92 | User_cond = exists(?INDEX, ?USER, UserID), 93 | Group_cond = exists(?INDEX, ?GROUP, GroupID), 94 | erlang:display("User : "++User_cond), 95 | erlang:display("Group : "++Group_cond), 96 | case (User_cond =:= true) and (Group_cond =:= true) of 97 | true -> put_group(ReqData, State), 98 | {true, ReqData, State}; 99 | false -> {{error, "User / Group ID does not exist!"}, ReqData, State} 100 | end; 101 | [{"users", UserID}, {"groups"}] -> 102 | case exists(?INDEX, ?USER, UserID) of 103 | true -> put_group(ReqData, State), 104 | {true, ReqData, State}; 105 | false -> {{error, "User ID does not exist!"}, ReqData, State} 106 | end; 107 | [{"groups", GroupID}] -> 108 | case exists(?INDEX, ?GROUP, GroupID) of 109 | true -> put_group(ReqData, State), 110 | {true, ReqData, State}; 111 | false -> {{error, "Group ID does not exist!"}, ReqData, State} 112 | end; 113 | [error] -> {error, ReqData, State} 114 | end. 115 | 116 | %% @doc 117 | %% Function: put_group/2 118 | %% Purpose: updates / replaces fields values in a group document 119 | %% with the data contained in the request body. 120 | %% Side-Effect : The new data will replace the existing data. 121 | 122 | put_group(ReqData, State) -> 123 | GroupId = proplists:get_value(group, wrq:path_info(ReqData)), 124 | {ReqBody,_,_} = api_help:json_handler(ReqData,State), 125 | Update = lib_json:set_attr(doc, ReqBody), 126 | case api_help:update_doc(?INDEX, ?GROUP, GroupId, Update) of 127 | {error,Reason} -> {{error,Reason}, ReqData, State}; 128 | {ok,List} -> {List,ReqData,State} 129 | end. 130 | 131 | 132 | -------------------------------------------------------------------------------- /src/lib_file.erl: -------------------------------------------------------------------------------- 1 | %% @author Tommy Mattsson [www.csproj13.student.it.uu.se] 2 | %% @copyright [Copyright information] 3 | %% @version 1.0 4 | %% @doc == Library for manipulating files and directories == 5 | %% @end 6 | -module(lib_file). 7 | -include("debug.hrl"). 8 | -export([ensure_dir_exists/1, read_file_lines/1, write_file_lines/2]). 9 | 10 | %% @doc 11 | %% Ensures a directory exists. 12 | %% @end 13 | -spec ensure_dir_exists(Dir::string()) -> ok | error. 14 | ensure_dir_exists(AbsoluteDir = "/"++_Dir) -> 15 | User = os:cmd("echo $USER")--"\n", 16 | case User of 17 | "root" -> 18 | os:cmd("mkdir -p "++AbsoluteDir), 19 | ok; 20 | User -> 21 | case re:run("^/home/"++User, AbsoluteDir, [{capture, none}]) of 22 | nomatch -> 23 | case file:list_dir(AbsoluteDir) of 24 | {ok, _FileList} -> 25 | ?DEBUG("Directory '" ++ AbsoluteDir ++ "' exists."), 26 | ok; 27 | {error, _Reason} -> 28 | ?DEBUG("Cannot create directory '"++AbsoluteDir++"' without sudo rights. Rerun with sudo access"), 29 | error 30 | end; 31 | match -> 32 | os:cmd("mkdir -p " ++ AbsoluteDir), 33 | ok 34 | end 35 | end; 36 | ensure_dir_exists(RelativeDir) -> 37 | os:cmd("mkdir -p " ++ RelativeDir), 38 | ok. 39 | 40 | 41 | 42 | 43 | %% @doc 44 | %% Read all lines from a file. 45 | %% @end 46 | -spec read_file_lines(File::string()) -> [string()] | error. 47 | read_file_lines(File) -> 48 | case file:open(File, [read]) of 49 | {ok, Fd} -> 50 | Lines = read_file_lines(Fd, []), 51 | file:close(Fd), 52 | Lines; 53 | _ -> 54 | ?ERROR("Unable to open file: "++File), 55 | error 56 | end. 57 | 58 | %% @doc 59 | %% Read all lines from a file. Used by read_file_lines/1 60 | %% @end 61 | -spec read_file_lines(Fd::pid(), Acc::list()) -> [string()]. 62 | read_file_lines(Fd, Acc) -> 63 | case file:read_line(Fd) of 64 | eof -> 65 | lists:reverse(Acc); 66 | {ok, Line} -> 67 | read_file_lines(Fd, [Line | Acc]) 68 | end. 69 | 70 | %% @doc 71 | %% Writes all lines from a file. If the file exists then all of the old content is removed and Lines will be inserted instead. 72 | %% @end 73 | -spec write_file_lines(Fd::pid(), Lines::[string()]) -> ok | error. 74 | write_file_lines(File, Lines) -> 75 | case file:open(File, [write]) of 76 | {ok, Fd} -> 77 | file:write(Fd, Lines), 78 | file:close(Fd); 79 | _ -> 80 | ?ERROR("Unable to open file: "++File), 81 | error 82 | end. 83 | -------------------------------------------------------------------------------- /src/parser.erl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/projectcs13/sensor-cloud/0302bd74b2e62fddbd832fb4c7a27b9c62852b90/src/parser.erl -------------------------------------------------------------------------------- /src/plus_srv.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% File: plus_srv.erl 3 | %%% @author Ian Barber 4 | %%% @copyright 2012 Google 5 | %%% @doc Example code on interacting with the discovery API 6 | %%% 7 | %%% @end 8 | %%% 9 | %%% @since 2012-11-05 10 | %%%------------------------------------------------------------------- 11 | -module(plus_srv). 12 | -author('Ian Barber'). 13 | -behaviour(gen_server). 14 | 15 | %% API 16 | -export([start_link/4,list_apis/0,set_api/1,call_method/3, 17 | list_methods/0,list_scopes/1,gen_token_url/1,exchange_token/2,get_url/1]). 18 | 19 | %% gen_server callbacks 20 | -export([init/1, handle_call/3,handle_cast/2, handle_info/2, terminate/2, code_change/3]). 21 | 22 | -record(state, {methods, baseurl, discovery, apikey, authtoken, oauth2state, clientid, clientsecret, redirecturl}). 23 | 24 | %%==================================================================== 25 | %% API 26 | %%==================================================================== 27 | 28 | %%-------------------------------------------------------------------- 29 | %% @spec start_link() -> {ok,Pid} | ignore | {error,Error} 30 | %% @doc Starts the server 31 | %% @end 32 | %%-------------------------------------------------------------------- 33 | start_link(APIKey, ClientID, ClientSecret, RedirectURL) -> 34 | gen_server:start_link({local, ?MODULE}, ?MODULE, [APIKey, ClientID, ClientSecret, RedirectURL], []). 35 | 36 | 37 | %%-------------------------------------------------------------------- 38 | %% @spec calL_method() -> {ok,Results} | {error,Error} 39 | %% @doc Call a method and retrieve the resulting JSON document 40 | %% @end 41 | %%-------------------------------------------------------------------- 42 | call_method(Service, Parameters, Body)-> 43 | gen_server:call(?MODULE,{call_method,Service,Parameters, Body}). 44 | 45 | %%-------------------------------------------------------------------- 46 | %% @spec list_apis() -> {ok,Methods} | {error,Error} 47 | %% @doc Returns a list of discoverable APIs 48 | %% @end 49 | %%-------------------------------------------------------------------- 50 | list_apis()-> 51 | gen_server:call(?MODULE,{list_apis}). 52 | 53 | %%-------------------------------------------------------------------- 54 | %% @spec set_api() -> {k | {error,Error} 55 | %% @doc Set the server to operate against a specific API 56 | %% @end 57 | %%-------------------------------------------------------------------- 58 | set_api(ApiRestUrl)-> 59 | gen_server:call(?MODULE,{set_api, ApiRestUrl}). 60 | 61 | %%-------------------------------------------------------------------- 62 | %% @spec list_methods() -> {ok,Methods} | {error,Error} 63 | %% @doc Returns a list of methods strings and aritys 64 | %% @end 65 | %%-------------------------------------------------------------------- 66 | list_methods()-> 67 | gen_server:call(?MODULE,{list_methods}). 68 | 69 | %%-------------------------------------------------------------------- 70 | %% @spec list_scopes() -> {ok,Scopes} | {error,Error} 71 | %% @doc Returns a list of methods scopes. If empty, does not require auth 72 | %% @end 73 | %%-------------------------------------------------------------------- 74 | list_scopes(Method)-> 75 | gen_server:call(?MODULE,{list_scopes,Method}). 76 | 77 | %%-------------------------------------------------------------------- 78 | %% @spec gen_token_url() -> {ok,Url} | {error,Error} 79 | %% @doc Returns a URL to pass to the user to authenticate the app 80 | %% @end 81 | %%-------------------------------------------------------------------- 82 | gen_token_url(Scopes)-> 83 | gen_server:call(?MODULE,{gen_token_url,Scopes}). 84 | 85 | %%-------------------------------------------------------------------- 86 | %% @spec exchange_token() -> ok | {error,Error} 87 | %% @doc Returns a URL to pass to the user to authenticate the app 88 | %% @end 89 | %%-------------------------------------------------------------------- 90 | exchange_token(Code, State)-> 91 | gen_server:call(?MODULE,{exchange_token,Code,State}). 92 | 93 | %%==================================================================== 94 | %% gen_server callbacks 95 | %%==================================================================== 96 | 97 | init([APIKey, ClientID, ClientSecret, RedirectURL]) -> 98 | inets:start(), 99 | ssl:start(), 100 | {ok, #state{apikey=APIKey, clientid=ClientID, clientsecret=ClientSecret, redirecturl=RedirectURL, 101 | methods=[], baseurl=[], discovery=[], authtoken=[]}}. 102 | 103 | %% Directory calls 104 | 105 | handle_call({list_apis}, _From, State) -> 106 | [Apis, Discovery] = get_api_list(State#state.discovery), 107 | {reply, Apis, State#state{discovery=Discovery}}; 108 | 109 | handle_call({set_api, ApiRestUrl}, _From, State) -> 110 | [Methods, BaseURL] = get_api_methods(ApiRestUrl), 111 | {reply, ok, State#state{methods=Methods,baseurl=BaseURL}}; 112 | 113 | %% OAuth Calls 114 | 115 | handle_call({gen_token_url, Scopes}, _From, State) -> 116 | [URL, OAuth2State] = get_authurl(Scopes, State), 117 | {reply, URL, State#state{oauth2state=OAuth2State}}; 118 | 119 | handle_call({exchange_token, Code, _}, _From, State) -> 120 | % handle_call({exchange_token, Code, OAuth2State}, _From, State) when OAuth2State == State#state.oauth2state -> 121 | {ok, Token} = get_authtoken(Code, State), 122 | {reply, Token, State#state{authtoken=Token}}; 123 | 124 | handle_call({exchange_token, _, _}, _From, State) -> 125 | {reply, {error, "Invalid State"}, State}; 126 | 127 | %% Method calls 128 | %% Error case for following calls - requires API to be set. 129 | handle_call(_, _From, State) when length(State#state.methods)==0 -> 130 | {reply, {error, "No API Set"}, State}; 131 | 132 | handle_call({list_methods}, _From, State) -> 133 | {reply, get_method_names(State#state.methods), State}; 134 | 135 | handle_call({list_scopes, Method}, _From, State) -> 136 | {reply, get_method_scopes(Method, State#state.methods), State}; 137 | 138 | handle_call({call_method, Service, Parameters, Body}, _From, State) -> 139 | Reply = call_method(Service, Parameters, Body, State#state.methods, State), 140 | {reply, Reply, State}. 141 | 142 | handle_cast(_Msg, State) -> 143 | {noreply, State}. 144 | 145 | handle_info(_Info, State) -> 146 | {noreply, State}. 147 | 148 | terminate(_Reason, _State) -> 149 | ok. 150 | 151 | code_change(_OldVsn, State, _Extra) -> 152 | {ok, State}. 153 | 154 | %%==================================================================== 155 | %% helper functions 156 | %%==================================================================== 157 | 158 | get_url(Request) -> 159 | case Request of 160 | {ok, {{_Version, 200, _ReasonPhrase}, _Headers, Body}}-> 161 | {struct, Json} = mochijson2:decode(Body), 162 | {ok, Json}; 163 | {ok, {{_Version, Code, _ReasonPhrase}, _Headers, _Body}}-> 164 | {error,Code}; 165 | {error,Error}-> 166 | {error,Error} 167 | end. 168 | 169 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 170 | %% List APIs %% 171 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 172 | 173 | get_api_list([]) -> 174 | case get_url(httpc:request(get,{"https://www.googleapis.com/discovery/v1/apis",[]},[],[])) of 175 | {error, Error} -> {stop, Error}; 176 | {ok, Json} -> 177 | get_api_list(Json) 178 | end; 179 | 180 | get_api_list(Discovery) -> 181 | Apis = proplists:get_value(<<"items">>, Discovery), 182 | [get_apis(Apis), Discovery]. 183 | 184 | get_api_methods(ApiRestUrl) -> 185 | case get_url(httpc:request(get,{ApiRestUrl,[]},[],[])) of 186 | {error, Error} -> {stop, Error}; 187 | {ok, Json} -> 188 | Methods = get_resources(Json), 189 | BaseURL = binary_to_list(proplists:get_value(<<"baseUrl">>, Json)), 190 | [Methods, BaseURL] 191 | end. 192 | 193 | get_apis([]) -> 194 | []; 195 | 196 | get_apis([H|T]) -> 197 | {struct, API} = H, 198 | [{binary_to_list(proplists:get_value(<<"name">>, API)), 199 | binary_to_list(proplists:get_value(<<"discoveryRestUrl">>, API))}|get_apis(T)]. 200 | 201 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 202 | %% Call Methods %% 203 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 204 | 205 | call_method(Service, Parameters, Body, [[Service, Method]|_], State) -> 206 | %% If we have an APIKey and don't need OAuth, include that 207 | BaseString = get_base_params(State#state.apikey, State#state.authtoken), 208 | Headers = get_headers(State), 209 | 210 | %% URL encode args,and paste on to end of string 211 | QS = lists:foldl(fun({Q,V}, QS) -> QS ++ "&" ++ Q ++ "=" ++ V end, 212 | BaseString, Parameters), 213 | %% Join method path on to uri 214 | Path = get_path(binary_to_list(proplists:get_value(<<"path">>, Method)), Parameters), 215 | Url = State#state.baseurl ++ Path ++ "?" ++ QS, 216 | 217 | %% erlang:display(Headers), %%% DEBUG %%% 218 | %% erlang:display(Url), %%% DEBUG %%% 219 | 220 | %% Switch on method 221 | [HttpMethod, Request] = case proplists:get_value(<<"httpMethod">>, Method) of 222 | <<"GET">> -> [get, {Url, Headers}]; 223 | <<"POST">> -> [post, {Url, Headers, "application/json", Body}] 224 | end, 225 | 226 | %% Retrieve response 227 | %%[HttpMethod, Request]; 228 | get_url(httpc:request(HttpMethod, Request, [], [])); 229 | 230 | call_method(Service, Parameters, Body, [_|Methods], State) -> 231 | call_method(Service, Parameters, Body, Methods, State). 232 | 233 | get_headers(#state{authtoken=Token}) when 0 == length(Token) -> 234 | []; 235 | 236 | get_headers(#state{authtoken=Token}) -> 237 | %% TODO: We should check the expiry of our authtoken! 238 | Auth = binary_to_list(proplists:get_value(<<"access_token">>, Token)), 239 | [{"Authorization", "Bearer " ++ Auth}]. 240 | 241 | get_base_params(APIKey, []) when APIKey /= [] -> 242 | "key=" ++ APIKey; 243 | 244 | get_base_params(_, _) -> 245 | "". 246 | 247 | get_path(Path, []) -> 248 | Path; 249 | 250 | get_path(Path, [{Key, Value}|Parameters]) -> 251 | get_path(re:replace(Path, "{" ++ Key ++ "}", Value, [global, {return, list}]), Parameters). 252 | 253 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 254 | %% Retrieve Methods & Resources %% 255 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 256 | 257 | get_method_scopes(Service, [[Service, Method]|_]) -> 258 | case proplists:get_value(<<"scopes">>, Method) of 259 | undefined -> []; 260 | Scopes -> Scopes 261 | end; 262 | 263 | get_method_scopes(Service, [_|Methods]) -> 264 | get_method_scopes(Service, Methods). 265 | 266 | get_method_names([[Method,_]|[]]) -> 267 | [Method]; 268 | 269 | get_method_names([[Method, _]|Methods]) -> 270 | [Method | get_method_names(Methods)]. 271 | 272 | get_resources(_, []) -> 273 | []; 274 | 275 | get_resources(Json, [ResourceName|Resources]) -> 276 | {struct, Resource} = proplists:get_value(ResourceName, Json), 277 | lists:append(get_resources(Resource), get_resources(Json, Resources)). 278 | 279 | get_resources(Json) -> 280 | %% If methods array defined, pull out list 281 | case proplists:get_value(<<"methods">>, Json) of 282 | {struct, Methods} -> 283 | get_methods(Methods, proplists:get_keys(Methods)); 284 | undefined -> 285 | %% If resource array defined, pull out and iterate over children 286 | {struct, ResourceTypes} = proplists:get_value(<<"resources">>, Json), 287 | get_resources(ResourceTypes, proplists:get_keys(ResourceTypes)) 288 | end. 289 | 290 | get_methods(_, []) -> 291 | []; 292 | 293 | get_methods(Json, [MethodName|Methods]) -> 294 | {struct, Method} = proplists:get_value(MethodName, Json), 295 | [[binary_to_list(proplists:get_value(<<"id">>, Method)), Method] | get_methods(Json, Methods)]. 296 | 297 | 298 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 299 | %% OAuth %% 300 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 301 | 302 | get_authurl(Scopes, #state{clientid = ClientID, redirecturl = RedirectURL}) -> 303 | OAuth2State = base64:encode_to_string(crypto:rand_bytes(12)), 304 | ["https://accounts.google.com/o/oauth2/auth?" ++ 305 | "client_id=" ++ ClientID ++ 306 | "&response_type=code" ++ 307 | "&access_type=offline" ++ 308 | % "&approval_prompt=force" ++ 309 | "&redirect_uri=" ++ edoc_lib:escape_uri(RedirectURL) ++ 310 | "&scope=" ++ edoc_lib:escape_uri(Scopes) ++ 311 | "&state=" ++ edoc_lib:escape_uri(OAuth2State), OAuth2State]. 312 | 313 | get_authtoken(Code, #state{clientid = ClientID, clientsecret=ClientSecret, redirecturl = RedirectURL}) -> 314 | %% We check the OAuth2 state variable in the handler for security 315 | URL = "https://accounts.google.com/o/oauth2/token", 316 | Data = "code=" ++ edoc_lib:escape_uri(Code) ++ 317 | "&client_id=" ++ edoc_lib:escape_uri(ClientID) ++ 318 | "&client_secret=" ++ edoc_lib:escape_uri(ClientSecret) ++ 319 | "&redirect_uri=" ++ edoc_lib:escape_uri(RedirectURL) ++ 320 | "&grant_type=authorization_code", 321 | get_url(httpc:request(post, {URL, [], "application/x-www-form-urlencoded", Data}, [], [])). 322 | -------------------------------------------------------------------------------- /src/poll_help.erl: -------------------------------------------------------------------------------- 1 | %% @author Gabriel Tholsg�rd, Li Hao 2 | %% [www.csproj13.student.it.uu.se] 3 | %% @version 1.0 4 | %% @copyright [Copyright information] 5 | %% 6 | %% @doc == poll_help == 7 | %% This module contains helper functions needed for the polling system 8 | %% 9 | %% @end 10 | 11 | -module(poll_help). 12 | 13 | -include("common.hrl"). 14 | -include_lib("erlastic_search.hrl"). 15 | -include("erlson.hrl"). 16 | -include("json.hrl"). 17 | -include("poller.hrl"). 18 | -include("pubsub.hrl"). 19 | -include("field_restrictions.hrl"). 20 | 21 | -export([get_streams_using_polling/0, 22 | json_to_record_streams/1, 23 | json_to_record_stream/1, 24 | create_poller_history/1, 25 | add_success/1, 26 | add_failed/2]). 27 | 28 | 29 | 30 | 31 | %% ==================================================================== 32 | %% API functions 33 | %% ==================================================================== 34 | 35 | %% @doc 36 | %% Function: get_streams_using_polling/0 37 | %% Purpose: Retrieves all streams from Elastic Search that are using polling. 38 | %% Returns: [] | [Stream, ... ] | {error, Reason}. 39 | %% @end 40 | -spec get_streams_using_polling() -> [] | [json_string()] | {error, term()}. 41 | get_streams_using_polling() -> 42 | JsonQuery = "{\"size\":10000, \"query\": {\"term\":{\"polling\":true}}, "++ 43 | "\"filter\": {\"exists\": {\"field\":\"uri\"}}}", 44 | 45 | case erlastic_search:search_json(#erls_params{}, 46 | ?ES_INDEX, 47 | "stream", 48 | JsonQuery) of 49 | {error, Reason} -> {error, Reason}; 50 | {ok, Result} -> 51 | lib_json:get_field(Result, "hits.hits") 52 | end. 53 | 54 | 55 | 56 | 57 | %% @doc 58 | %% Function: json_to_record_streams/1 59 | %% Purpose: Converts a list of stream Jsons to a list of pollerInfo records 60 | %% Returns: [] | [Stream, ...] 61 | %% @end 62 | -spec json_to_record_streams([json_string()]) -> [] | [record()]. 63 | json_to_record_streams([]) -> []; 64 | json_to_record_streams([H|T]) -> 65 | [json_to_record_stream(H) | json_to_record_streams(T)]. 66 | 67 | 68 | 69 | 70 | %% @doc 71 | %% Function: json_to_record_stream/1 72 | %% Purpose: Converts a stream Json to a pollerInfo record 73 | %% Returns: #pollerInfo{} 74 | %% @end 75 | -spec json_to_record_stream(Stream::json_string()) -> record(). 76 | json_to_record_stream(Stream) -> 77 | Name = case lib_json:get_field(Stream, "_source.name") of 78 | undefined -> undefined; 79 | N -> binary_to_list(N) 80 | end, 81 | Uri = case lib_json:get_field(Stream, "_source.uri") of 82 | undefined -> undefined; 83 | U -> binary_to_list(U) 84 | end, 85 | DataType = case lib_json:get_field(Stream, "_source.data_type") of 86 | undefined -> undefined; 87 | D -> binary_to_list(D) 88 | end, 89 | ParserString = case lib_json:get_field(Stream, "_source.parser") of 90 | undefined -> undefined; 91 | P -> binary_to_list(P) 92 | end, 93 | #pollerInfo{stream_id = binary_to_list(lib_json:get_field(Stream, "_id")), 94 | name = Name, 95 | uri = Uri, 96 | frequency = lib_json:get_field(Stream, "_source.polling_freq"), 97 | data_type = DataType, 98 | parser = ParserString 99 | }. 100 | 101 | %% @doc 102 | %% Function: create_poller_history/1 103 | %% Purpose: creates an empty polling history for 104 | %% the given stream 105 | %% Returns: ok or {Code,Body} if there was an error in ES 106 | %% @end 107 | -spec create_poller_history(StreamId::string()) -> ok | {integer(),string()}. 108 | 109 | create_poller_history(StreamId) -> 110 | NewHistory = lib_json:set_attrs([{"history","[]"}]), 111 | case erlastic_search:index_doc_with_id(?INDEX, "pollinghistory", StreamId, NewHistory) of 112 | {error,{Code,Body}} -> 113 | {Code,Body}; 114 | {ok,_List} -> 115 | ok 116 | end. 117 | create_poller_history(StreamId, Message) -> 118 | NewHistory = lib_json:set_attrs([{"history","["++Message++"]"}]), 119 | case erlastic_search:index_doc_with_id(?INDEX, "pollinghistory", StreamId, NewHistory) of 120 | {error,{Code,Body}} -> 121 | {Code,Body}; 122 | {ok,_List} -> 123 | ok 124 | end. 125 | 126 | %% @doc 127 | %% Function: add_failed/1 128 | %% Purpose: Updates the polling history with an error message 129 | %% Returns: ok or {error,{Code,Body}} if there was an error in ES 130 | %% @end 131 | -spec add_failed(StreamId::string(),Error::atom()) -> ok | {atom(),{integer(),string()}}. 132 | 133 | add_failed(StreamId,connection_error) -> 134 | Time = ?TIME_NOW(erlang:localtime()), 135 | UserId = case erlastic_search:get_doc(?INDEX, "stream", StreamId) of 136 | {error, Reason} -> 137 | error; 138 | {ok,List} -> 139 | lib_json:get_field(List, "_source.user_id") 140 | end, 141 | Message = lib_json:set_attrs([{"polling","{}"},{"polling.stream",list_to_binary(StreamId)},{"polling.action",list_to_binary("error")},{"polling.message",list_to_binary("Connection Error")},{"polling.timestamp",list_to_binary(Time)}]), 142 | UpdateJson = "{\"script\":\"ctx._source.notifications += msg\",\"params\":{\"msg\":"++ Message ++"}}", 143 | case api_help:update_doc(?INDEX, "user", UserId, UpdateJson, []) of 144 | {error, {Code, Body}} -> 145 | {error, {Code, Body}}; 146 | {ok, Response} -> 147 | ok 148 | end, 149 | UpdateJson2 = "{\"script\":\"if (ctx._source.history.size() == 100){ctx._source.history.remove((Object) ctx._source.history[0]);ctx._source.history += msg}{ctx._source.history += msg} \",\"params\":{\"msg\":"++ Message ++"}}", 150 | case api_help:update_doc(?INDEX, "pollinghistory", StreamId, UpdateJson2, []) of 151 | {error, {404, Body2}} -> create_poller_history(StreamId, Message); 152 | {error, {Code2, Body2}} -> 153 | erlang:display("Error when updateing pollinghistory for " ++ StreamId), 154 | {error, {Code2, Body2}}; 155 | {ok, Response2} -> 156 | ok 157 | end; 158 | 159 | add_failed(StreamId,elasticsearch_error) -> 160 | Time = ?TIME_NOW(erlang:localtime()), 161 | UserId = case erlastic_search:get_doc(?INDEX, "stream", StreamId) of 162 | {error, Reason} -> 163 | error; 164 | {ok,List} -> 165 | lib_json:get_field(List, "_source.user_id") 166 | end, 167 | Message = lib_json:set_attrs([{"polling","{}"},{"polling.stream",list_to_binary(StreamId)},{"polling.message",list_to_binary("Could not save datapoint")},{"polling.action",list_to_binary("error")},{"polling.timestamp",list_to_binary(Time)}]), 168 | UpdateJson = "{\"script\":\"ctx._source.notifications += msg\",\"params\":{\"msg\":"++ Message ++"}}", 169 | case api_help:update_doc(?INDEX, "user", UserId, UpdateJson, []) of 170 | {error, {Code, Body}} -> 171 | {error, {Code, Body}}; 172 | {ok, Response} -> 173 | ok 174 | end, 175 | UpdateJson2 = "{\"script\":\"if (ctx._source.history.size() == 100){ctx._source.history.remove((Object) ctx._source.history[0]);ctx._source.history += msg}{ctx._source.history += msg} \",\"params\":{\"msg\":"++ Message ++"}}", 176 | case api_help:update_doc(?INDEX, "pollinghistory", StreamId, UpdateJson2, []) of 177 | {error, {404, Body2}} -> create_poller_history(StreamId, Message); 178 | {error, {Code2, Body2}} -> 179 | erlang:display("Error when updateing pollinghistory for " ++ StreamId), 180 | {error, {Code2, Body2}}; 181 | {ok, Response2} -> 182 | ok 183 | end. 184 | 185 | 186 | %% @doc 187 | %% Function: add_success/1 188 | %% Purpose: Updates the polling history with a created datapoint message 189 | %% Returns: ok or {error,{Code,Body}} if there was an error in ES 190 | %% @end 191 | -spec add_success(StreamId::string()) -> ok | {atom(),{integer(),string()}}. 192 | 193 | add_success(StreamId) -> 194 | Time = ?TIME_NOW(erlang:localtime()), 195 | Message = lib_json:set_attrs([{"polling","{}"},{"polling.stream",list_to_binary(StreamId)},{"polling.message",list_to_binary("Created new datapoint")},{"polling.action",list_to_binary("create")},{"polling.timestamp",list_to_binary(Time)}]), 196 | UpdateJson = "{\"script\":\"if (ctx._source.history.size() == 100){ctx._source.history.remove((Object) ctx._source.history[0]);ctx._source.history += msg}{ctx._source.history += msg} \",\"params\":{\"msg\":"++ Message ++"}}", 197 | case api_help:update_doc(?INDEX, "pollinghistory", StreamId, UpdateJson, []) of 198 | {error, {404, Body2}} -> create_poller_history(StreamId, Message); 199 | {error, {Code, Body}} -> 200 | erlang:display("Error when updateing pollinghistory for " ++ StreamId), 201 | {error, {Code, Body}}; 202 | {ok, Response} -> 203 | ok 204 | end. 205 | 206 | %% ==================================================================== 207 | %% Internal functions 208 | %% ==================================================================== 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | -------------------------------------------------------------------------------- /src/poller.erl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/projectcs13/sensor-cloud/0302bd74b2e62fddbd832fb4c7a27b9c62852b90/src/poller.erl -------------------------------------------------------------------------------- /src/polling_monitor.erl: -------------------------------------------------------------------------------- 1 | %% @author Li Hao 2 | 3 | %% [www.csproj13.student.it.uu.se] 4 | %% @version 1.0 5 | %% @copyright [Copyright information] 6 | %% 7 | %% @doc == polling_monitor == 8 | %% this module implements a supervisor of pollers, when one poller crashes for some reason, this monitor could restart it 9 | %% automaticaly. 10 | %% @end 11 | 12 | %% more information about supervisor framework 13 | %% could be seen here: http://learnyousomeerlang.com/supervisors 14 | %% http://www.erlang.org/doc/man/supervisor.html 15 | 16 | -module(polling_monitor). 17 | -include("state.hrl"). 18 | -behaviour(supervisor). 19 | 20 | %% ==================================================================== 21 | %% API functions 22 | %% ==================================================================== 23 | -export([start_link/0, init/1]). 24 | 25 | %% @doc 26 | %% Function: start_link/0 27 | %% Purpose: start function used to generate the polling_monitor process, and will call init/1 function to initialize. 28 | %% Returns: {already_started, pid()} | {shutdown, term()} | {ok, pid()} 29 | %% @end 30 | -spec start_link() -> {ok, pid()} | {already_started, pid()} | {shutdown, term()}. 31 | start_link()-> 32 | supervisor:start_link({local, ?MODULE}, ?MODULE, []). 33 | 34 | %% @doc 35 | %% Function: init/1 36 | %% Purpose: init function used to initialize the polling_monitor, will called by supervisor:start_link() 37 | %% Returns: {ok, {configuration of the supervisor, specifications of the children}} 38 | %% @end 39 | -spec init(term()) -> {ok,{{RestartStrategy,MaxR,MaxT},[ChildSpec]}} | ignore when 40 | RestartStrategy :: term(), 41 | MaxR :: integer(), 42 | MaxT :: integer(), 43 | ChildSpec :: tuple(). 44 | init(_)-> 45 | {ok, {{simple_one_for_one, 5, 60}, 46 | [{poller, {poller, start_link, []}, transient, 1000, worker, [poller]}] 47 | }}. -------------------------------------------------------------------------------- /src/polling_system.erl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/projectcs13/sensor-cloud/0302bd74b2e62fddbd832fb4c7a27b9c62852b90/src/polling_system.erl -------------------------------------------------------------------------------- /src/pubsub/gen_virtual_stream_process.erl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/projectcs13/sensor-cloud/0302bd74b2e62fddbd832fb4c7a27b9c62852b90/src/pubsub/gen_virtual_stream_process.erl -------------------------------------------------------------------------------- /src/pubsub/resourceProcessMock.erl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/projectcs13/sensor-cloud/0302bd74b2e62fddbd832fb4c7a27b9c62852b90/src/pubsub/resourceProcessMock.erl -------------------------------------------------------------------------------- /src/pubsub/streamProcess.erl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/projectcs13/sensor-cloud/0302bd74b2e62fddbd832fb4c7a27b9c62852b90/src/pubsub/streamProcess.erl -------------------------------------------------------------------------------- /src/pubsub/virtual_stream_process_supervisor.erl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/projectcs13/sensor-cloud/0302bd74b2e62fddbd832fb4c7a27b9c62852b90/src/pubsub/virtual_stream_process_supervisor.erl -------------------------------------------------------------------------------- /src/resource.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @author ProjectCS13 <> Andreas, Carl, Tomas 3 | %%% @doc 4 | %%% This module handles the uri requests that are related to resources. 5 | %%% @end 6 | %%% Created : 9 Oct 2013 by ProjectCS13 <> 7 | %%%------------------------------------------------------------------- 8 | -module(resource). 9 | -compile(export_all). 10 | 11 | -include_lib("webmachine.hrl"). 12 | -include_lib("erlastic_search.hrl"). 13 | 14 | -define(INDEX, "sensorcloud"). 15 | 16 | %% @doc 17 | %% Function: init/1 18 | %% Purpose: init function used to fetch path information from webmachine dispatcher. 19 | %% Returns: {ok, undefined} 20 | %% @end 21 | -spec init([]) -> {ok, undefined}. 22 | init([]) -> 23 | %erlastic_search_app:start(), %% start this in the make file somehow 24 | {ok, undefined}. 25 | 26 | %% @doc 27 | %% Function: allowed_methods/2 28 | %% Purpose: Defines which HTTP methods are allowed 29 | %% Returns: {List of allowed HTTP requests, string , string()} 30 | %% @end 31 | -spec allowed_methods(ReqData::tuple(), State::string()) -> {list(), tuple(), string()}. 32 | allowed_methods(ReqData, State) -> 33 | case api_help:parse_path(wrq:path(ReqData)) of 34 | [ {"resources"}] -> 35 | {['POST'], ReqData, State}; 36 | [ {"resources", _ResourceID}] -> 37 | {['GET', 'PUT', 'DELETE'], ReqData, State}; 38 | [{"users", _UserID}, {"resources"}] -> 39 | {['GET','POST'], ReqData, State}; 40 | [{"users", _UserID}, {"resources", "_search" ++ _Query}] -> 41 | {['GET', 'POST'], ReqData, State}; 42 | [{"users", _UserID}, {"resources", _ResourceID}] -> 43 | {['GET', 'PUT', 'DELETE'], ReqData, State}; 44 | [error] -> 45 | {[], ReqData, State} 46 | end. 47 | 48 | 49 | 50 | %% @doc 51 | %% Function: content_types_provided/2 52 | %% Purpose: based on the Accept header on a 'GET' request, we provide different media types to the client. 53 | %% A code 406 is returned to the client if we cannot return the media-type that the user has requested. 54 | %% Returns: {[{Mediatype, Handler}], ReqData, State} 55 | %% @end 56 | -spec content_types_provided(ReqData::tuple(), State::string()) -> {list(), tuple(), string()}. 57 | content_types_provided(ReqData, State) -> 58 | {[{"application/json", get_resource}], ReqData, State}. 59 | 60 | 61 | %% @doc 62 | %% Function: content_types_accepted/2 63 | %% Purpose: based on the content-type on a 'POST' or 'PUT', we know which kind of data that is allowed to be sent to the server. 64 | %% A code 406 is returned to the client if we don't accept a media type that the client has sent. 65 | %% Returns: {[{Mediatype, Handler}], ReqData, State} 66 | %% @end 67 | -spec content_types_accepted(ReqData::tuple(), State::string()) -> {list(), tuple(), string()}. 68 | content_types_accepted(ReqData, State) -> 69 | {[{"application/json", put_resource}], ReqData, State}. 70 | 71 | 72 | %% @doc 73 | %% Function: delete_resource/2 74 | %% Purpose: Deletes a resource and the streams associated with it 75 | %% Returns: ERROR = {{error,Errorcode} ReqData, State} 76 | %% OK = {ok, ReqData, State} 77 | %% @end 78 | -spec delete_resource(ReqData::tuple(), State::string()) -> {string(), tuple(), string()}. 79 | delete_resource(ReqData, State) -> 80 | Id = proplists:get_value('resourceid', wrq:path_info(ReqData)), 81 | erlang:display("DELETE request - check permission here"), 82 | %% TODO Authentication 83 | case delete_streams_with_resource_id(Id) of 84 | {error,Reason} -> {{error,Reason}, wrq:set_resp_body("{\"error\":\""++ lib_json:encode(Reason) ++ "\"}", ReqData), State}; 85 | {ok} -> 86 | case erlastic_search:delete_doc(?INDEX,"resource", Id) of 87 | {error,Reason} -> {{error,Reason}, wrq:set_resp_body("{\"error\":\""++ lib_json:encode(Reason) ++ "\"}", ReqData), State}; 88 | {ok,List} -> {true,wrq:set_resp_body(lib_json:encode(List),ReqData),State} 89 | end 90 | end. 91 | 92 | %% @doc 93 | %% Function: delete_streams_with_resource_id/1 94 | %% Purpose: Deletes the first 500 streams associated with the given resourceid 95 | %% Returns: ERROR = {error,Errorcode} 96 | %% OK = {ok} 97 | %% @end 98 | -spec delete_streams_with_resource_id(Id::string()) -> term(). 99 | 100 | delete_streams_with_resource_id(Id) -> 101 | Query = "resource_id:" ++ Id, 102 | case erlastic_search:search_limit(?INDEX, "stream", Query,500) of 103 | {error,Reason} -> 104 | {error,Reason}; 105 | {ok,List} -> 106 | case get_streams(List) of 107 | [] -> {ok}; 108 | Streams -> 109 | case delete_streams(Streams) of 110 | {error,Reason} -> {error, Reason}; 111 | {ok} -> {ok} 112 | end 113 | end 114 | end. 115 | 116 | %% @doc 117 | %% Function: get_streams/1 118 | %% Purpose: get a list of ids of a list of JSON objects 119 | %% Returns: a list with the ids of the JSON objects given 120 | %% @end 121 | -spec get_streams(JSON::string()) -> list(). 122 | 123 | get_streams(JSON) when is_tuple(JSON)-> 124 | Result = lib_json:get_field(JSON, "hits.hits"), 125 | get_streams(Result); 126 | get_streams(undefined) -> 127 | []; 128 | 129 | get_streams([]) -> 130 | []; 131 | 132 | get_streams([JSON | Tl]) -> 133 | case lib_json:get_field(JSON, "_id") of 134 | undefined -> []; 135 | Id -> [Id] ++ get_streams(Tl) 136 | end. 137 | 138 | 139 | 140 | %% @doc 141 | %% Function: delete_streams/1 142 | %% Purpose: Deletes all streams in the given list, the list elements are streamIds as binaries 143 | %% Returns: ok, or {{error,_Reason}, StreamId, Rest} where StreamId is the binary Id of the stream for which deletion failed 144 | %% @end 145 | 146 | delete_streams([]) -> {ok}; 147 | delete_streams([StreamId|Rest]) -> 148 | case erlastic_search:delete_doc(?INDEX, "stream", StreamId) of 149 | {error,Reason} -> {error,Reason}; 150 | {ok,_List} -> delete_streams(Rest) 151 | end. 152 | 153 | %% @doc 154 | %% Function: process_post/2 155 | %% Purpose: Handle POST request, only working for create and not search - AS OF SPRINT 3 156 | %% Returns: {JSON-object(string), ReqData, State} 157 | %% @end 158 | -spec process_post(ReqData::tuple(), State::string()) -> {atom(), tuple(), string()}. 159 | process_post(ReqData, State) -> 160 | URIList = string:tokens(wrq:path(ReqData), "/"), 161 | IsSearch = (string:sub_string(lists:nth(length(URIList),URIList),1,7) == "_search"), 162 | case IsSearch of 163 | false -> 164 | % Create 165 | {Resource,_,_} = api_help:json_handler(ReqData,State), 166 | case erlastic_search:index_doc(?INDEX,"resource",Resource) of 167 | {error, Reason} -> {{error,Reason}, wrq:set_resp_body("{\"error\":\""++ lib_json:encode(Reason) ++ "\"}", ReqData), State}; 168 | {ok, Json} -> 169 | ResourceId = lib_json:get_field(Json, "_id"), 170 | suggest:add_suggestion(Resource, ResourceId), 171 | {true, wrq:set_resp_body(lib_json:encode(Json), ReqData), State} 172 | end; 173 | true -> 174 | % Search 175 | process_search_post(ReqData,State) 176 | end. 177 | 178 | %% @doc 179 | %% Function: process_search_post/2 180 | %% Purpose: Used to handle search requests that come from POST requests 181 | %% Returns: {Success, ReqData, State}, where Success is true if the search request is 182 | %% successful and false otherwise. 183 | %% @end 184 | -spec process_search_post(ReqData::term(),State::term()) -> {boolean(), term(), term()}. 185 | 186 | process_search_post(ReqData, State) -> 187 | {Json,_,_} = api_help:json_handler(ReqData,State), 188 | case proplists:get_value('userid', wrq:path_info(ReqData)) of 189 | undefined -> 190 | {{halt,405}, ReqData, State}; 191 | UserId -> 192 | UserQuery = "\"user_id\":" ++ UserId, 193 | FilteredJson = filter_json(Json, UserQuery), 194 | case erlastic_search:search_json(#erls_params{},?INDEX, "resource", FilteredJson) of % Maybe wanna take more 195 | {error,Reason} -> {{error,Reason}, wrq:set_resp_body("{\"error\":\""++ lib_json:encode(Reason) ++ "\"}", ReqData), State}; 196 | {ok,List} -> {true,wrq:set_resp_body(lib_json:encode(List),ReqData),State} % May need to convert 197 | end 198 | end. 199 | 200 | 201 | %% @doc 202 | %% Function: put_resource/2 203 | %% Purpose: Updates the resource in the database 204 | %% It is run automatically for POST and PUT requests 205 | %% Returns: {true, ReqData, State} || {{error, Reason}, ReqData, State} 206 | %% @end 207 | -spec put_resource(ReqData::tuple(), State::string()) -> {list(), tuple(), string()}. 208 | put_resource(ReqData, State) -> 209 | %check if doc already exists 210 | Id = id_from_path(ReqData), 211 | case erlastic_search:get_doc(?INDEX, "resource", Id) of 212 | {error, Reason} -> 213 | {{error,Reason}, wrq:set_resp_body("{\"error\":\""++ lib_json:encode(Reason) ++ "\"}", ReqData), State}; 214 | {ok, _} -> 215 | {UserJson,_,_} = api_help:json_handler(ReqData, State), 216 | Update = api_help:create_update(UserJson), 217 | case api_help:update_doc(?INDEX,"resource", Id, Update) of 218 | {error, Reason} -> 219 | {{error,Reason}, wrq:set_resp_body("{\"error\":\""++ lib_json:encode(Reason) ++ "\"}", ReqData), State}; 220 | {ok,List} -> 221 | {true,wrq:set_resp_body(lib_json:encode(List),ReqData),State} 222 | end 223 | end. 224 | 225 | 226 | %% @doc 227 | %% Function: get_resource/2 228 | %% Purpose: Handle GET request 229 | %% Returns: {JSON-object(string), ReqData, State} 230 | %% @end 231 | -spec get_resource(ReqData::tuple(), State::string()) -> {list(), tuple(), string()}. 232 | get_resource(ReqData, State) -> 233 | case api_help:is_search(ReqData) of 234 | false -> 235 | case proplists:get_value('resourceid', wrq:path_info(ReqData)) of 236 | undefined -> 237 | % List resources based on URI 238 | case proplists:get_value('userid', wrq:path_info(ReqData)) of 239 | undefined -> 240 | Query = []; 241 | UserId -> 242 | Query = "user_id:" ++ UserId 243 | end, 244 | case erlastic_search:search_limit(?INDEX, "resource", Query, 100) of % Maybe wanna take more 245 | {error,Reason} -> {{error,Reason}, wrq:set_resp_body("{\"error\":\""++ lib_json:encode(Reason) ++ "\"}", ReqData), State}; 246 | {ok,JsonStruct} -> 247 | FinalJson = lib_json:get_list_and_add_id(JsonStruct), 248 | {FinalJson, ReqData, State} 249 | end; 250 | ResourceId -> 251 | % Get specific resource 252 | case erlastic_search:get_doc(?INDEX, "resource", ResourceId) of 253 | {error,Reason} -> 254 | {{error,Reason}, wrq:set_resp_body("{\"error\":\""++ lib_json:encode(Reason) ++ "\"}", ReqData), State}; 255 | {ok,JsonStruct} -> 256 | FinalJson = lib_json:get_and_add_id(JsonStruct), 257 | {FinalJson, ReqData, State} 258 | end 259 | end; 260 | true -> 261 | process_search(ReqData,State, get) 262 | end. 263 | 264 | 265 | %% @doc 266 | %% Function: process_search/3 267 | %% Purpose: Does search for Users for either search done with POST or GET 268 | %% Returns: {true, ReqData, State} || {{error, Reason}, ReqData, State} 269 | %% @end 270 | -spec process_search(ReqData::tuple(), State::string(), term()) -> 271 | {list(), tuple(), string()}. 272 | process_search(ReqData, State, post) -> 273 | {Json,_,_} = api_help:json_handler(ReqData,State), 274 | {struct, JsonData} = mochijson2:decode(Json), 275 | Query = api_help:transform(JsonData), 276 | case erlastic_search:search_limit(?INDEX, "resource", Query, 10) of 277 | 278 | {error,Reason} -> {{error,Reason}, wrq:set_resp_body("{\"error\":\""++ lib_json:encode(Reason) ++ "\"}", ReqData), State}; 279 | {ok,List} -> {true, wrq:set_resp_body(lib_json:encode(List),ReqData),State} 280 | end; 281 | process_search(ReqData, State, get) -> 282 | TempQuery = wrq:req_qs(ReqData), 283 | TransformedQuery = api_help:transform(TempQuery), 284 | case erlastic_search:search_limit(?INDEX, "resource", TransformedQuery, 10) of 285 | {error,Reason} -> {{error,Reason}, wrq:set_resp_body("{\"error\":\""++ lib_json:encode(Reason) ++ "\"}", ReqData), State}; 286 | {ok,List} -> {lib_json:encode(List),ReqData,State} % May need to convert 287 | end. 288 | 289 | %% @doc 290 | %% Function: id_from_path/1 291 | %% Purpose: Retrieves the id from the path. 292 | %% Returns: Id 293 | %% @end 294 | -spec id_from_path(tuple()) -> string(). 295 | id_from_path(RD) -> 296 | case wrq:path_info(resourceid, RD) of 297 | undefined-> 298 | ["resource", Id] = string:tokens(wrq:disp_path(RD), "/"), 299 | Id; 300 | Id -> Id 301 | end. 302 | 303 | %% @doc 304 | %% Function: filter_json/2 305 | %% Purpose: Used to add private and resource filters to the json query 306 | %% Returns: JSON string that is updated with filter 307 | %% @end 308 | filter_json(Json,UserQuery) -> 309 | NewJson = string:sub_string(Json,1,string:len(Json)-1), 310 | "{\"query\":{\"filtered\":"++NewJson++",\"filter\":{\"bool\":{\"must\":[{\"term\":{"++UserQuery++"}}]}}}}}". 311 | 312 | -------------------------------------------------------------------------------- /src/scoring.erl: -------------------------------------------------------------------------------- 1 | %% @author Tommy Mattsson, Georgios Koutsoumpakis 2 | %% [www.csproj13.student.it.uu.se] 3 | %% @version 1.0 4 | %% @headerfile "json.hrl" 5 | %% @copyright [Copyright information] 6 | %% @doc == Library for calculation scores (for suggestions) == 7 | %% @end 8 | 9 | -module(scoring). 10 | -include("json.hrl"). 11 | 12 | -export([calc/1, 13 | calc/2 14 | ]). 15 | 16 | %% ==================================================================== 17 | %% API functions 18 | %% ==================================================================== 19 | 20 | % @doc 21 | % Calculates the number of non "undefined" in the input list. It is used as a scoring 22 | % mechanism for suggestions 23 | % @end 24 | -spec calc(List::list()) -> integer(). 25 | calc(List) when is_list(List)-> 26 | Fun = fun(undefined, Acc) -> Acc; 27 | ("", Acc) -> Acc; 28 | (<<>>, Acc) -> Acc; 29 | (_, Acc) -> Acc+1 30 | end, 31 | lists:foldr(Fun, 0, List). 32 | 33 | 34 | % @doc 35 | % Calculates the score for a given resource. It is used as a scoring 36 | % mechanism for suggestions 37 | % @end 38 | -spec calc(Resource::json(), atom()) -> integer(). 39 | calc(Resource, resource) -> 40 | Manufacturer = lib_json:get_field(Resource, "manufacturer"), 41 | Tags = lib_json:get_field(Resource, "tags"), 42 | Polling_freq = lib_json:get_field(Resource, "polling_freq"), 43 | List = [Manufacturer, Tags, Polling_freq], 44 | calc(List); 45 | calc(Stream, stream) -> 46 | Name = lib_json:get_field(Stream, "name"), 47 | Description = lib_json:get_field(Stream, "description"), 48 | Min_val = lib_json:get_field(Stream, "min_val"), 49 | Max_val = lib_json:get_field(Stream, "max_val"), 50 | Tags = lib_json:get_field(Stream, "tags"), 51 | Type = lib_json:get_field(Stream, "type"), 52 | Accuracy = lib_json:get_field(Stream, "accuracy"), 53 | calc([Name, Description, Min_val, Max_val, Tags, Type, Accuracy]). 54 | 55 | 56 | 57 | 58 | %% ==================================================================== 59 | %% Internal functions 60 | %% ==================================================================== 61 | 62 | 63 | -------------------------------------------------------------------------------- /src/search.erl: -------------------------------------------------------------------------------- 1 | %% @author Andreas, Jose 2 | %% [www.csproj13.student.it.uu.se] 3 | %% @version 1.0 4 | %% @copyright [Copyright information] 5 | 6 | %% @doc Webmachine_resource for /users 7 | 8 | -module(search). 9 | -export([init/1, 10 | allowed_methods/2, 11 | content_types_accepted/2, 12 | content_types_provided/2, 13 | process_post/2, 14 | get_search/2 15 | ]). 16 | 17 | -include("webmachine.hrl"). 18 | -include_lib("erlastic_search.hrl"). 19 | 20 | 21 | -define(INDEX, "sensorcloud"). 22 | 23 | 24 | %% @doc 25 | %% Function: init/1 26 | %% Purpose: init function used to fetch path information from webmachine dispatcher. 27 | %% Returns: {ok, undefined} 28 | %% @end 29 | -spec init([]) -> {ok, undefined}. 30 | init([]) -> 31 | {ok, undefined}. 32 | 33 | 34 | 35 | %% @doc 36 | %% Function: allowed_methods/2 37 | %% Purpose: init function used to fetch path information from webmachine dispatcher. 38 | %% Returns: {ok, undefined} 39 | %% @end 40 | -spec allowed_methods(ReqData::tuple(), State::string()) -> {list(), tuple(), string()}. 41 | allowed_methods(ReqData, State) -> 42 | case api_help:parse_path(wrq:path(ReqData)) of 43 | [{"_search"}] -> 44 | {['POST','GET'], ReqData, State}; 45 | [{"_history"}] -> 46 | {['POST','GET'], ReqData, State}; 47 | [error] -> 48 | {['POST','GET'], ReqData, State} 49 | end. 50 | 51 | 52 | %% @doc 53 | %% Function: content_types_provided/2 54 | %% Purpose: based on the Accept header on a 'GET' request, we provide different media types to the client. 55 | %% A code 406 is returned to the client if we cannot return the media-type that the user has requested. 56 | %% Returns: {[{Mediatype, Handler}], ReqData, State} 57 | %% @end 58 | -spec content_types_provided(ReqData::tuple(), State::string()) -> {list(), tuple(), string()}. 59 | content_types_provided(ReqData, State) -> 60 | {[{"application/json", get_search}], ReqData, State}. 61 | 62 | 63 | %% @doc 64 | %% Function: content_types_accepted/2 65 | %% Purpose: based on the content-type on a 'POST' or 'PUT', we know which kind of data that is allowed to be sent to the server. 66 | %% A code 406 is returned to the client if we don't accept a media type that the client has sent. 67 | %% Returns: {[{Mediatype, Handler}], ReqData, State} 68 | %% @end 69 | -spec content_types_accepted(ReqData::tuple(), State::string()) -> {list(), tuple(), string()}. 70 | content_types_accepted(ReqData, State) -> 71 | {[{"application/json", process_post}], ReqData, State}. 72 | 73 | %% @doc 74 | %% Function: get_search/2 75 | %% Purpose: Handles _history requests. With an input list of stream_ids seperated by ',' 76 | %% it will return a proper json object that contains a list of jsonobjects that contain 77 | %% a stream_id and the last 'size' number of datapoints for that specific stream. 78 | %% 79 | %% It is run automatically for GET requests 80 | %% 81 | %% Returns: {Body, ReqData, State} || {{error, Reason}, ReqData, State} 82 | %% 83 | %% Side effects: Inserts a new User in the database (when for insertion) 84 | %% @end 85 | -spec get_search(ReqData::tuple(), State::string()) -> {string(), tuple(), string()}. 86 | get_search(ReqData, State) -> 87 | case api_help:is_search(ReqData) of 88 | false -> 89 | case wrq:get_qs_value("size",ReqData) of 90 | undefined -> 91 | NrValues = 25; 92 | Values -> 93 | {NrValues, _} = string:to_integer(Values) 94 | end, 95 | case wrq:get_qs_value("stream_id",ReqData) of 96 | undefined -> 97 | ErrorString = api_help:generate_error(<<"Invalid stream_id">>, 405), 98 | {{halt, 405}, wrq:set_resp_body(ErrorString, ReqData), State}; 99 | StreamIds -> 100 | IdList = string:tokens(StreamIds, ","), 101 | {get_history(IdList, NrValues, "{\"history\":[]}"), ReqData, State} 102 | end; 103 | true -> 104 | {{halt, 501}, wrq:set_resp_body("Please use POST search instead.", ReqData), State} 105 | end. 106 | 107 | %% @doc 108 | %% Function: process_post/2 109 | %% Purpose: decodes a JSON object and either adds the new User in the DB or 110 | %% performs search in the User database. 111 | %% It is run automatically for POST requests 112 | %% Returns: {true, ReqData, State} || {{error, Reason}, ReqData, State} 113 | %% 114 | %% Side effects: Inserts a new User in the database (when for insertion) 115 | %% @end 116 | -spec process_post(ReqData::tuple(), State::string()) -> {true, tuple(), string()}. 117 | process_post(ReqData, State) -> 118 | process_search_post(ReqData,State). 119 | 120 | %% @doc 121 | %% Function: process_search_post/2 122 | %% Purpose: Used to handle search requests that come from POST requests 123 | %% Returns: {Success, ReqData, State}, where Success is true if the search request is 124 | %% successful and false otherwise. 125 | %% @end 126 | -spec process_search_post(ReqData::term(),State::term()) -> {boolean(), term(), term()}. 127 | process_search_post(ReqData, State) -> 128 | erlang:display("search with json request"), 129 | case wrq:get_qs_value("size",ReqData) of 130 | undefined -> 131 | Size = "10"; 132 | SizeParam -> 133 | Size = SizeParam 134 | end, 135 | case wrq:get_qs_value("sort",ReqData) of 136 | undefined -> 137 | Sort = "user_ranking.average"; 138 | SortParam -> 139 | Sort = SortParam 140 | end, 141 | case wrq:get_qs_value("from",ReqData) of 142 | undefined -> 143 | From = "0"; 144 | FromParam -> 145 | From = FromParam 146 | end, 147 | case wrq:get_qs_value("location",ReqData) of 148 | undefined -> 149 | LocationParam = false; 150 | "true" -> 151 | LocationParam = true; 152 | _ -> 153 | LocationParam = false 154 | end, 155 | {Json,_,_} = api_help:json_handler(ReqData,State), 156 | FilteredJson = filter_json(Json, From, Size, Sort, LocationParam), 157 | case erlastic_search:search_json(#erls_params{},?INDEX, "stream", FilteredJson) of % Maybe wanna take more 158 | {error, Reason1} -> 159 | StreamSearch = {error, Reason1}; 160 | {ok,List1} -> 161 | case lib_json:get_field(FilteredJson, "query.filtered.query.query_string.query") of 162 | undefined -> 163 | erlang:display("No query string text, cannot add a suggestion."); 164 | QueryString -> 165 | add_query_suggestion(QueryString) 166 | end, 167 | StreamSearch = lib_json:encode(List1) % May need to convert 168 | end, 169 | FilteredJson2 = filter_json(Json, From, Size, Sort, false), 170 | case erlastic_search:search_json(#erls_params{},?INDEX, "virtual_stream", lib_json:rm_field(FilteredJson2, "sort")) of % Maybe wanna take more 171 | {error, Reason2} -> 172 | VStreamSearch = {error, Reason2}; 173 | {ok,List2} -> 174 | VStreamSearch = lib_json:encode(List2) % May need to convert 175 | end, 176 | case erlastic_search:search_json(#erls_params{},?INDEX, "user", lib_json:rm_field(FilteredJson2, "sort")) of % Maybe wanna take more 177 | {error, Reason3} -> 178 | UserSearch = {error, Reason3}; 179 | {ok,List3} -> 180 | UserSearch = lib_json:encode(List3) % May need to convert 181 | end, 182 | % check search-results for error 183 | case {StreamSearch, VStreamSearch, UserSearch} of 184 | {{error, {Body, Code}},_,_} -> 185 | ErrorString = api_help:generate_error(Body, Code), 186 | {{halt, Code}, wrq:set_resp_body(ErrorString, ReqData), State}; 187 | {_,{error, {Body2, Code2}},_} -> 188 | ErrorString = api_help:generate_error(Body2, Code2), 189 | {{halt, Code2}, wrq:set_resp_body(ErrorString, ReqData), State}; 190 | {_,_,{error, {Body3, Code3}}} -> 191 | ErrorString = api_help:generate_error(Body3, Code3), 192 | {{halt, Code3}, wrq:set_resp_body(ErrorString, ReqData), State}; 193 | _ -> 194 | SearchResults = "{\"streams\":"++ StreamSearch ++",\"vstreams\":"++ VStreamSearch ++",\"users\":"++ UserSearch ++"}", 195 | {true,wrq:set_resp_body(SearchResults,ReqData),State} 196 | end. 197 | 198 | %% GROUPS ARE NOT IMPLEMENTED 199 | %% case erlastic_search:search_json(#erls_params{},?INDEX, "group", FilteredJson) of % Maybe wanna take more 200 | %% {error,Reason} -> {{halt,Reason}, ReqData, State}; 201 | %% {ok,List} -> {true,wrq:set_resp_body(json_encode(List),ReqData),State} % May need to convert 202 | %% end. 203 | 204 | 205 | 206 | 207 | %% @doc 208 | %% Function: get_history/2 209 | %% Purpose: Gets the NrValues latest datapoints for each streamid that exists in list IdList 210 | %% Returns: JSON string that contains the data and streamid for each streamid in IdList 211 | %% @end 212 | get_history([], _NrValues, Acc) -> 213 | Acc; 214 | get_history([Head|Rest], NrValues, Acc) -> 215 | case erlastic_search:search_limit(?INDEX, "datapoint","stream_id:" ++ Head ++ "&sort=timestamp:desc", NrValues) of 216 | {error,{Code, Body}} -> 217 | ErrorString = api_help:generate_error(Body, Code), 218 | IdAndDataJson = "{\"stream_id\":\""++Head++"\",\"data\":{\"error\": \""++ErrorString++"\"}"; 219 | {ok,JsonStruct} -> 220 | IdAndDataJson = parse_datapoints(lib_json:get_field(JsonStruct, "hits.hits"),"{\"stream_id\":\""++Head++"\",\"data\":[]}") 221 | end, 222 | get_history(Rest, NrValues, lib_json:add_value(Acc,"history",IdAndDataJson)). 223 | 224 | 225 | %% @doc 226 | %% Function: parse_datapoints/2 227 | %% Purpose: Gets the NrValues latest datapoints for each streamid that exists in list IdList 228 | %% Returns: JSON string that contains the data and streamid for each streamid in IdList 229 | %% @end 230 | parse_datapoints([], FinalJson) -> 231 | FinalJson; 232 | parse_datapoints([Head|Rest], FinalJson) -> 233 | Datapoint = lib_json:rm_field(lib_json:get_field(Head, "_source"), "stream_id"), 234 | parse_datapoints(Rest, lib_json:add_value(FinalJson,"data",Datapoint)). 235 | 236 | %% @doc 237 | %% Function: add_query_suggestion/1 238 | %% Purpose: Adds the suggestion to ES if it does not exist, or adds weight to an existing 239 | %% suggestion. 240 | %% Returns: an atom, error, created or updated depending on if the search suggestion was 241 | %% saved, updated or gave an error 242 | %% @end 243 | add_query_suggestion(QueryString) -> 244 | case erlastic_search:search_json(#erls_params{},?INDEX,"search_query","{\"query\":{\"term\":{\"search_suggest\":\""++ lib_json:to_string(QueryString) ++"\"}}}") of 245 | {error,_} -> 246 | error; 247 | {ok,JsonStruct} -> 248 | case lib_json:get_field(JsonStruct, "hits.hits[0]._id") of 249 | undefined -> 250 | case erlastic_search:index_doc(?INDEX,"search_query","{\"search_suggest\":{\"input\":[\""++ lib_json:to_string(QueryString) ++"\"],\"weight\":1}}") of 251 | {error,_} -> 252 | error; 253 | {ok,_} -> 254 | created 255 | end; 256 | Id -> 257 | case api_help:update_doc(?INDEX,"search_query", binary_to_list(Id), "{\"script\" : \"ctx._source.search_suggest.weight += 1\"}",[]) of 258 | {error,_} -> 259 | error; 260 | {ok,_} -> 261 | updated 262 | end 263 | end 264 | end. 265 | 266 | 267 | 268 | 269 | %% @doc 270 | %% Function: filter_json/1 271 | %% Purpose: Used to add private filters to the json query 272 | %% Returns: JSON string that is updated with filter 273 | %% @end 274 | filter_json(Json) -> 275 | NewJson = string:sub_string(Json,1,string:len(Json)-1), 276 | "{\"query\":{\"filtered\":"++NewJson++",\"filter\":{\"bool\":{\"must_not\":{\"term\":{\"private\":\"true\"}}}}}}}". 277 | 278 | 279 | %% @doc 280 | %% Function: filter_json/4 281 | %% Purpose: Used to add private filters to the json query with pagination 282 | %% Returns: JSON string that is updated with filter and the from size parameters 283 | %% @end 284 | filter_json(Json, From, Size, Sort, false) -> 285 | case lib_json:get_field(Json, "sort") of 286 | undefined -> 287 | UseSort = "\"" ++ Sort ++ "\"", 288 | SortJson = Json; 289 | SortValue when is_binary(SortValue) -> 290 | UseSort = "\"" ++ binary_to_list(SortValue) ++ "\"", 291 | SortJson = lib_json:rm_field(Json, "sort"); 292 | SortValue -> 293 | UseSort = SortValue, 294 | SortJson = lib_json:rm_field(Json, "sort") 295 | end, 296 | NewJson = string:sub_string(SortJson,1,string:len(SortJson)-1), 297 | "{\"from\" : "++From++ 298 | ",\"size\" : "++Size++ 299 | ",\"sort\" : " ++UseSort++ 300 | ",\"query\" : {\"filtered\" : "++NewJson++ 301 | ",\"filter\" : {\"bool\" : {\"must_not\" : {\"term\" : {\"private\" : \"true\"}}}}}}}"; 302 | filter_json(Json, From, Size, Sort, true) -> 303 | case lib_json:get_field(Json, "sort") of 304 | undefined -> 305 | UseSort = "\"" ++ Sort ++ "\"", 306 | SortJson = Json; 307 | SortValue when is_binary(SortValue) -> 308 | UseSort = "\"" ++ binary_to_list(SortValue) ++ "\"", 309 | SortJson = lib_json:rm_field(Json, "sort"); 310 | SortValue -> 311 | UseSort = SortValue, 312 | SortJson = lib_json:rm_field(Json, "sort") 313 | end, 314 | NewJson = string:sub_string(SortJson,1,string:len(SortJson)-1), 315 | "{\"script_fields\":{\"location\":{\"script\":\"doc['location'].value\"}},\"fields\":\"_source\","++ 316 | "\"from\" : "++From++ 317 | ",\"size\" : "++Size++ 318 | ",\"sort\" : " ++UseSort++ 319 | ",\"query\" : {\"filtered\" : "++NewJson++ 320 | ",\"filter\" : {\"bool\" : {\"must_not\" : {\"term\" : {\"private\" : \"true\"}}}}}}}". 321 | -------------------------------------------------------------------------------- /src/static_resource.erl: -------------------------------------------------------------------------------- 1 | %% @author Bryan Fink 2 | %% @author Andy Gross 3 | %% @author Justin Sheehy 4 | %% @copyright 2008-2009 Basho Technologies, Inc. 5 | 6 | -module(static_resource). 7 | -export([init/1]). 8 | -export([allowed_methods/2, 9 | resource_exists/2, 10 | last_modified/2, 11 | content_types_provided/2, 12 | content_types_accepted/2, 13 | delete_resource/2, 14 | post_is_create/2, 15 | create_path/2, 16 | provide_content/2, 17 | accept_content/2, 18 | generate_etag/2]). 19 | 20 | -record(context, {root,response_body=undefined,metadata=[]}). 21 | 22 | -include("webmachine.hrl"). 23 | -include_lib("kernel/include/file.hrl"). 24 | 25 | 26 | init(ConfigProps) -> 27 | {root, Root} = proplists:lookup(root, ConfigProps), 28 | {ok, #context{root=Root}}. 29 | 30 | allowed_methods(ReqData, Context) -> 31 | {['HEAD', 'GET', 'PUT', 'DELETE', 'POST'], ReqData, Context}. 32 | 33 | file_path(_Context, []) -> 34 | false; 35 | file_path(Context, Name) -> 36 | RelName = case hd(Name) of 37 | "/" -> tl(Name); 38 | _ -> Name 39 | end, 40 | filename:join([Context#context.root, RelName]). 41 | 42 | file_exists(Context, Name) -> 43 | NamePath = file_path(Context, Name), 44 | case filelib:is_regular(NamePath) of 45 | true -> 46 | {true, NamePath}; 47 | false -> 48 | false 49 | end. 50 | 51 | resource_exists(ReqData, Context) -> 52 | Path = wrq:disp_path(ReqData), 53 | case file_exists(Context, Path) of 54 | {true, _} -> 55 | {true, ReqData, Context}; 56 | _ -> 57 | case Path of 58 | "p" -> {true, ReqData, Context}; 59 | _ -> {false, ReqData, Context} 60 | end 61 | end. 62 | 63 | maybe_fetch_object(Context, Path) -> 64 | % if returns {true, NewContext} then NewContext has response_body 65 | case Context#context.response_body of 66 | undefined -> 67 | case file_exists(Context, Path) of 68 | {true, FullPath} -> 69 | {ok, Value} = file:read_file(FullPath), 70 | {true, Context#context{response_body=Value}}; 71 | false -> 72 | {false, Context} 73 | end; 74 | _Body -> 75 | {true, Context} 76 | end. 77 | 78 | content_types_provided(ReqData, Context) -> 79 | CT = webmachine_util:guess_mime(wrq:disp_path(ReqData)), 80 | {[{CT, provide_content}], ReqData, 81 | Context#context{metadata=[{'content-type', CT}|Context#context.metadata]}}. 82 | 83 | content_types_accepted(ReqData, Context) -> 84 | CT = case wrq:get_req_header("content-type", ReqData) of 85 | undefined -> "application/octet-stream"; 86 | X -> X 87 | end, 88 | {MT, _Params} = webmachine_util:media_type_to_detail(CT), 89 | {[{MT, accept_content}], ReqData, 90 | Context#context{metadata=[{'content-type', MT}|Context#context.metadata]}}. 91 | 92 | accept_content(ReqData, Context) -> 93 | Path = wrq:disp_path(ReqData), 94 | FP = file_path(Context, Path), 95 | ok = filelib:ensure_dir(FP), 96 | ReqData1 = case file_exists(Context, Path) of 97 | {true, _} -> 98 | ReqData; 99 | _ -> 100 | LOC = "http://" ++ 101 | wrq:get_req_header("host", ReqData) ++ 102 | "/fs/" ++ Path, 103 | wrq:set_resp_header("Location", LOC, ReqData) 104 | end, 105 | Value = wrq:req_body(ReqData1), 106 | case file:write_file(FP, Value) of 107 | ok -> 108 | {true, wrq:set_resp_body(Value, ReqData1), Context}; 109 | Err -> 110 | {{error, Err}, ReqData1, Context} 111 | end. 112 | 113 | post_is_create(ReqData, Context) -> 114 | {true, ReqData, Context}. 115 | 116 | create_path(ReqData, Context) -> 117 | case wrq:get_req_header("slug", ReqData) of 118 | undefined -> {undefined, ReqData, Context}; 119 | Slug -> 120 | case file_exists(Context, Slug) of 121 | {true, _} -> {undefined, ReqData, Context}; 122 | _ -> {Slug, ReqData, Context} 123 | end 124 | end. 125 | 126 | delete_resource(ReqData, Context) -> 127 | case file:delete(file_path( 128 | Context, wrq:disp_path(ReqData))) of 129 | ok -> {true, ReqData, Context}; 130 | _ -> {false, ReqData, Context} 131 | end. 132 | 133 | provide_content(ReqData, Context) -> 134 | case maybe_fetch_object(Context, wrq:disp_path(ReqData)) of 135 | {true, NewContext} -> 136 | Body = NewContext#context.response_body, 137 | {Body, ReqData, Context}; 138 | {false, NewContext} -> 139 | {error, ReqData, NewContext} 140 | end. 141 | 142 | last_modified(ReqData, Context) -> 143 | {true, FullPath} = file_exists(Context, 144 | wrq:disp_path(ReqData)), 145 | LMod = filelib:last_modified(FullPath), 146 | {LMod, ReqData, Context#context{metadata=[{'last-modified', 147 | httpd_util:rfc1123_date(LMod)}|Context#context.metadata]}}. 148 | 149 | hash_body(Body) -> mochihex:to_hex(binary_to_list(crypto:sha(Body))). 150 | 151 | generate_etag(ReqData, Context) -> 152 | case maybe_fetch_object(Context, wrq:disp_path(ReqData)) of 153 | {true, BodyContext} -> 154 | ETag = hash_body(BodyContext#context.response_body), 155 | {ETag, ReqData, 156 | BodyContext#context{metadata=[{etag,ETag}| 157 | BodyContext#context.metadata]}}; 158 | _ -> 159 | {undefined, ReqData, Context} 160 | end. 161 | -------------------------------------------------------------------------------- /src/stream_publisher.erl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/projectcs13/sensor-cloud/0302bd74b2e62fddbd832fb4c7a27b9c62852b90/src/stream_publisher.erl -------------------------------------------------------------------------------- /src/stream_reciever.erl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/projectcs13/sensor-cloud/0302bd74b2e62fddbd832fb4c7a27b9c62852b90/src/stream_reciever.erl -------------------------------------------------------------------------------- /src/triggers.erl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/projectcs13/sensor-cloud/0302bd74b2e62fddbd832fb4c7a27b9c62852b90/src/triggers.erl -------------------------------------------------------------------------------- /src/triggers_lib.erl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/projectcs13/sensor-cloud/0302bd74b2e62fddbd832fb4c7a27b9c62852b90/src/triggers_lib.erl -------------------------------------------------------------------------------- /src/vs_func_lib.erl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/projectcs13/sensor-cloud/0302bd74b2e62fddbd832fb4c7a27b9c62852b90/src/vs_func_lib.erl -------------------------------------------------------------------------------- /test/config.spec: -------------------------------------------------------------------------------- 1 | {include, ["../include/", "../lib/erlastic_search/include/", "../lib/erlson/include/", "../lib/rabbitmq-erlang-client/include/", "../lib/webmachine/include/"]}. 2 | {suites, "../test/", virtualStreamProcess_tests_SUITE}. 3 | {logdir, "../test-results/"}. 4 | 5 | -------------------------------------------------------------------------------- /test/datapoints_tests.erl: -------------------------------------------------------------------------------- 1 | %% @author Iakovos Koutsoumpakis 2 | %% [www.csproj13.student.it.uu.se] 3 | %% @version 1.0 4 | %% @copyright [Copyright information] 5 | %% 6 | %% @doc == datapoints_tests == 7 | %% This module contains several tests to test the functionallity 8 | %% in the restful API in users. 9 | %% 10 | %% @end 11 | 12 | -module(datapoints_tests). 13 | -include_lib("eunit/include/eunit.hrl"). 14 | -include_lib("stdlib/include/qlc.hrl"). 15 | -include("debug.hrl"). 16 | 17 | 18 | %% ==================================================================== 19 | %% API functions 20 | %% ==================================================================== 21 | -export([]). 22 | 23 | %% ==================================================================== 24 | %% Internal functions 25 | %% ==================================================================== 26 | 27 | -define(WEBMACHINE_URL, api_help:get_webmachine_url()). 28 | -define(DATAPOINTS_URL, ?WEBMACHINE_URL++"/streams/4/data/"). 29 | -define(TEST_VALUE, "3"). 30 | -define(TEST_TIMESTAMP, "2"). 31 | -define(INDEX, "sensorcloud"). 32 | 33 | %% @doc 34 | %% Function: post_test/0 35 | %% Purpose: Test a post request 36 | %% Returns: ok | {error, term()} 37 | %% 38 | %% @end 39 | -spec post_test() -> ok | {error, term()}. 40 | post_test() -> 41 | erlastic_search:index_doc_with_id(?INDEX,"stream","4","{\"tags\" : \"data_points\"}"), 42 | api_help:refresh(), 43 | Response1 = post_request(?DATAPOINTS_URL, "application/json", 44 | "{\"value\":\"" ++ ?TEST_VALUE ++ "\", \"timestamp\": \"" ++ ?TEST_TIMESTAMP ++ "\"}"), 45 | check_returned_code(Response1, 200), 46 | api_help:refresh(), 47 | erlastic_search:delete_doc(?INDEX,"stream","4"), 48 | ?assertNotMatch({error, "no match"}, get_index_id(?TEST_VALUE, ?TEST_TIMESTAMP)). 49 | 50 | 51 | %% @doc 52 | %% Function: get_existing_datapoint_test/0 53 | %% Purpose: Test a get request for a datapoint that exists, using its Id 54 | %% Returns: ok | {error, term()} 55 | %% 56 | %% @end 57 | -spec get_existing_datapoint_test() -> ok | {error, term()}. 58 | get_existing_datapoint_test() -> 59 | Id = get_index_id(?TEST_VALUE, ?TEST_TIMESTAMP), 60 | ?assertNotMatch({error, "\"no match\""}, Id), 61 | Response1 = get_request(?DATAPOINTS_URL ++ "_search?_id=" ++ Id), 62 | check_returned_code(Response1, 200). 63 | 64 | 65 | %% @doc 66 | %% Function: no_timestamp_test/0 67 | %% Purpose: Test a post request without a timestamp 68 | %% Returns: ok | {error, term()} 69 | %% 70 | %% @end 71 | -spec no_timestamp_test() -> ok | {error, term()}. 72 | no_timestamp_test() -> 73 | erlastic_search:index_doc_with_id(?INDEX,"stream","5","{\"tags\" : \"data_points\"}"), 74 | api_help:refresh(), 75 | Response1 = post_request(?WEBMACHINE_URL++"/streams/5/data/", "application/json", 76 | "{\"value\":\"55\"}"), 77 | check_returned_code(Response1, 200), 78 | api_help:refresh(), 79 | {ok,{_,_,Body}} = httpc:request(get, {?WEBMACHINE_URL++"/streams/5/data/", []}, [], []), 80 | ObjectList = lib_json:get_field(Body,"data"), 81 | ?assertEqual(true, lib_json:get_field(lists:nth(1,ObjectList),"timestamp") =/= undefined). 82 | 83 | %% @doc 84 | %% Function: update_stream_fields_test/0 85 | %% Purpose: Test adding a datapoint and see that the 86 | %% stream is updated 87 | %% Returns: ok | {error, term()} 88 | %% 89 | %% @end 90 | -spec update_stream_fields_test() -> ok | {error, term()}. 91 | update_stream_fields_test() -> 92 | {ok, {{_Version, 200, _ReasonPhrase}, _Headers, Body}} = httpc:request(post, {?WEBMACHINE_URL++ "/users", [],"application/json", "{\"username\" : \"update_stream_user\"}"}, [], []), 93 | UserId = lib_json:get_field(Body,"_id"), 94 | api_help:refresh(), 95 | {ok, {{_Version1, 200, _ReasonPhrase1}, _Headers1, Body1}} = httpc:request(post, {?WEBMACHINE_URL++"/streams", [],"application/json", "{\"name\" : \"search\",\"user_id\" : \"update_stream_user\", \"private\" : \"false\"}"}, [], []), 96 | StreamId = lib_json:get_field(Body1,"_id"), 97 | api_help:refresh(), 98 | {ok, {{_Version2, 200, _ReasonPhrase2}, _Headers2, Body2}} = httpc:request(post, {?WEBMACHINE_URL++"/streams/" ++ lib_json:to_string(StreamId) ++ "/data", [],"application/json", "{\"value\":5.0}"}, [], []), 99 | api_help:refresh(), 100 | {ok, {{_Version3, 200, _ReasonPhrase3}, _Headers3, Body3}} = httpc:request(get, {?WEBMACHINE_URL++"/streams/" ++ lib_json:to_string(StreamId), []}, [], []), 101 | {ok, {{_Version4, 200, _ReasonPhrase4}, _Headers4, Body4}} = httpc:request(get, {?WEBMACHINE_URL++"/streams/" ++ lib_json:to_string(StreamId) ++ "/data", []}, [], []), 102 | {ok, {{_Version5, 200, _ReasonPhrase5}, _Headers5, _Body5}} = httpc:request(delete, {?WEBMACHINE_URL++"/users/update_stream_user", []}, [], []), 103 | ?assertEqual(lib_json:get_field(Body3,"last_updated"), lib_json:get_field(Body4,"data[0].timestamp")). 104 | 105 | %% @doc 106 | %% Function: get_index_id/0 107 | %% Purpose: Searches the ES and returns the _id of a datapoint 108 | %% Returns: string() | {error, string()} 109 | %% 110 | %% @end 111 | -spec get_index_id(string(), string()) -> string() | {error, string()}. 112 | get_index_id(Uvalue, Uvalue2) -> 113 | Response1 = get_request(?DATAPOINTS_URL ++ "_search?value=" ++ Uvalue ++ "×tamp=" ++ Uvalue2), 114 | check_returned_code(Response1, 200), 115 | {ok, {_,_,A}} = Response1, 116 | case re:run(A, "id\":\"[^\"]*", [{capture, first, list}]) of 117 | {match, ["id\":\"" ++ Id]} -> Id; 118 | nomatch -> {error, "no match"} 119 | end. 120 | 121 | 122 | %% @doc 123 | %% Function: check_returned_code/0 124 | %% Purpose: Checks if the Response has the correct http return code 125 | %% 126 | %% @end 127 | -spec check_returned_code(string(), integer()) -> ok. 128 | check_returned_code(Response, Code) -> 129 | {ok, Rest} = Response, 130 | {Header,_,_} = Rest, 131 | ?assertMatch({_, Code, _}, Header). 132 | 133 | 134 | %% @doc 135 | %% Function: get_non_existent_user_datapoint/0 136 | %% Purpose: Tests a get request for a datapoint that doesn't exist 137 | %% Returns: ok | {error, term()} 138 | %% 139 | %% @end 140 | -spec get_non_existent_datapoint_test() -> ok | {error, term()}. 141 | get_non_existent_datapoint_test() -> 142 | Response1 = get_request(?DATAPOINTS_URL ++ "_search?_id=" ++ "nonexistent"), 143 | {ok, Rest} = Response1, 144 | {_,_,Result} = Rest, 145 | ?assertNotEqual(0, string:str(Result, "data\":[]")). 146 | 147 | 148 | %% @doc 149 | %% Function: add_unsupported_field_test/0 150 | %% Purpose: Test that unsuported fields are not allowed to be added 151 | %% on create 152 | %% Returns: ok | {error, term()} 153 | %% @end 154 | -spec add_unsupported_field_test() -> ok | {error, term()}. 155 | add_unsupported_field_test() -> 156 | {ok, {{_Version1, 403, _ReasonPhrase1}, _Headers1, Body1}} = httpc:request(post, {?WEBMACHINE_URL++"/streams/5/data", [],"application/json", "{\"test\":\"asdas\",\"value\" : 5.0}"}, [], []), 157 | erlastic_search:delete_doc(?INDEX,"stream","5"). 158 | 159 | post_request(URL, ContentType, Body) -> request(post, {URL, [], ContentType, Body}). 160 | get_request(URL) -> request(get, {URL, []}). 161 | request(Method, Request) -> 162 | httpc:request(Method, Request, [], []). 163 | 164 | 165 | -------------------------------------------------------------------------------- /test/gen_virtual_stream_process_tests.erl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/projectcs13/sensor-cloud/0302bd74b2e62fddbd832fb4c7a27b9c62852b90/test/gen_virtual_stream_process_tests.erl -------------------------------------------------------------------------------- /test/http.erl: -------------------------------------------------------------------------------- 1 | %% @author Tommy Mattsson 2 | %% [www.csproj13.student.it.uu.se] 3 | %% @version 1.0 4 | %% @copyright [Copyright information] 5 | %% 6 | %% @doc == http == 7 | %% This module contains several functions for making http request with webmachine 8 | %% in the module triggers which is done by calling the webbmachine. 9 | %% 10 | %% @end 11 | -module(http). 12 | -export([delete/1, get/1, post/2, put/2]). 13 | 14 | %% ==================================================================== 15 | %% API functions 16 | %% ==================================================================== 17 | %% @doc 18 | %% Makes an http delete request to an url. 19 | %% @end 20 | -spec delete(Url::string()) -> {integer(), string()}. 21 | delete(Url) -> 22 | {ok,{{_Vsn,Status,_Reason},_Hdrs,Body}} = httpc:request(delete,{Url,[]},[],[]), 23 | {Status, Body}. 24 | 25 | %% @doc 26 | %% Makes an http get request to an url. 27 | %% @end 28 | -spec get(Url::string()) -> {integer(), string()}. 29 | get(Url) -> 30 | {ok,{{_Vsn,Status,_Reason},_Hdrs,Body}} = httpc:request(get,{Url, []}, [], []), 31 | {Status, Body}. 32 | 33 | %% @doc 34 | %% Makes an http post request to an url with specific data. 35 | %% @end 36 | -spec post(Url::string(), Request::string()) -> {integer(), string()}. 37 | post(Url, Request) -> 38 | {ok,{{_Vsn,Status,_Reason},_Hdrs,Body}} = httpc:request(post,{Url,[],"application/json",Request},[],[]), 39 | {Status, Body}. 40 | 41 | %% @doc 42 | %% Makes an http put request to an url with specific data. 43 | %% @end 44 | -spec put(Url::string(), Request::string()) -> {integer(), string()}. 45 | put(Url, Request) -> 46 | {ok,{{_Vsn,Status,_Reason},_Hdrs,Body}} = httpc:request(put,{Url,[],"application/json",Request},[],[]), 47 | {Status, Body}. 48 | -------------------------------------------------------------------------------- /test/lib_json_tests.erl: -------------------------------------------------------------------------------- 1 | %% @author Tommy Mattsson 2 | %% [www.csproj13.student.it.uu.se] 3 | %% @version 1.0 4 | %% @copyright [Copyright information] 5 | %% 6 | %% @doc == json library_tests == 7 | %% This module contains several tests to test the functionallity 8 | %% in the module lib_json for decoding json objects. 9 | %% 10 | %% @end 11 | -module(lib_json_tests). 12 | -include_lib("eunit/include/eunit.hrl"). 13 | -include("json.hrl"). 14 | -export([]). 15 | 16 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 17 | %%% = = Test input = = 18 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 19 | -define(JSON1, 20 | "{" 21 | "\"friend\":[" 22 | "{\"name\":\"FriendName1\",\"nickname\":\"NickName1\"}," 23 | "{\"name\":\"FriendName2\",\"nickname\":[\"NickName2\",\"NickName3\"]}," 24 | "{\"name\":\"FriendName3\",\"nickname\":[\"NickName4\",\"NickName5\"]}" 25 | "]," 26 | "\"name\":\"Name1\"" 27 | "}"). 28 | 29 | -define(JSON2, 30 | "{" 31 | "\"name\":\"Name1\"," 32 | "\"friend\":{\"name\":\"FriendName2\",\"nickname\":[\"NickName2\",\"NickName3\"]}" 33 | "}"). 34 | 35 | -define(JSON3, 36 | "{\"took\":1,\"timed_out\":false,\"_shards\":{\"total\":5,\"successful\":5,\"failed\":0},\"hits\":{\"total\":0,\"max_score\":null,\"hits\":[]}}"). 37 | 38 | 39 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 40 | %%% = = Test desired input = = 41 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 42 | -define(JSON_RESULT1, 43 | "{" 44 | "\"friend\":[" 45 | "{\"name\":\"FriendName0\",\"nickname\":\"NickName0\"}," 46 | "{\"name\":\"FriendName1\",\"nickname\":\"NickName1\"}," 47 | "{\"name\":\"FriendName2\",\"nickname\":[\"NickName2\",\"NickName3\"]}," 48 | "{\"name\":\"FriendName3\",\"nickname\":[\"NickName4\",\"NickName5\"]}" 49 | "]," 50 | "\"name\":\"Name1\"" 51 | "}"). 52 | 53 | -define(JSON_RESULT2, 54 | "{" 55 | "\"friend\":[" 56 | "{\"name\":\"FriendName1\",\"nickname\":\"NickName1\"}," 57 | "{\"name\":\"FriendName2\",\"nickname\":[\"NickName2\",\"NickName3\",\"NickName6\"]}," 58 | "{\"name\":\"FriendName3\",\"nickname\":[\"NickName4\",\"NickName5\"]}" 59 | "]," 60 | "\"name\":\"Name1\"" 61 | "}"). 62 | 63 | -define(JSON_RESULT3, 64 | "{" 65 | "\"friend\":[" 66 | "{\"height\":180,\"name\":\"FriendName1\",\"nickname\":\"NickName1\"}," 67 | "{\"name\":\"FriendName2\",\"nickname\":[\"NickName2\",\"NickName3\"]}," 68 | "{\"name\":\"FriendName3\",\"nickname\":[\"NickName4\",\"NickName5\"]}" 69 | "]," 70 | "\"name\":\"Name1\"" 71 | "}"). 72 | -define(JSON_RESULT4, 73 | "{" 74 | "\"friend\":[" 75 | "{\"height\":[180,182],\"name\":\"FriendName1\",\"nickname\":\"NickName1\"}," 76 | "{\"name\":\"FriendName2\",\"nickname\":[\"NickName2\",\"NickName3\"]}," 77 | "{\"name\":\"FriendName3\",\"nickname\":[\"NickName4\",\"NickName5\"]}" 78 | "]," 79 | "\"name\":\"Name1\"" 80 | "}"). 81 | 82 | 83 | -define(JSON_RESULT5, 84 | ["{\"name\":\"FriendName1\",\"nickname\":\"NickName1\"}", 85 | "{\"name\":\"FriendName2\",\"nickname\":[\"NickName2\",\"NickName3\"]}", 86 | "{\"name\":\"FriendName3\",\"nickname\":[\"NickName4\",\"NickName5\"]}" 87 | ]). 88 | 89 | -define(JSON_RESULT6, 90 | "{\"name\":\"FriendName1\",\"nickname\":\"NickName1\"}" 91 | ). 92 | 93 | -define(JSON_RESULT7, 94 | "{\"name\":\"poff\",\"nickname\":\"NickName1\"}" 95 | ). 96 | 97 | -define(ENCODE_RESULT1, 98 | [$\{,[$\",<<"friend">>,$\"],$:,[$[, 99 | [${,[$\",<<"name">>,$\"],$:,[$\",<<"FriendName1">>,$\"],$,,[$\",<<"nickname">>,$\"],$:,[$\",<<"NickName1">>,$\"],$}],$,, 100 | [${,[$\",<<"name">>,$\"],$:,[$\",<<"FriendName2">>,$\"],$,,[$\",<<"nickname">>,$\"],$:,[$[,[$\",<<"NickName2">>,$\"],$,,[$\",<<"NickName3">>,$\"],$]],$}],$,, 101 | [${,[$\",<<"name">>,$\"],$:,[$\",<<"FriendName3">>,$\"],$,,[$\",<<"nickname">>,$\"],$:,[$[,[$\",<<"NickName4">>,$\"],$,,[$\",<<"NickName5">>,$\"],$]],$}] 102 | ,$]], 103 | $,,[$\", <<"name">>,$\"],$:,[$\",<<"Name1">>,$\"], 104 | $}]). 105 | 106 | -define(DECODE_RESULT1, 107 | {struct,[{<<"friend">>, 108 | [{struct,[{<<"name">>,<<"FriendName1">>}, 109 | {<<"nickname">>,<<"NickName1">>}]}, 110 | {struct,[{<<"name">>,<<"FriendName2">>}, 111 | {<<"nickname">>,[<<"NickName2">>,<<"NickName3">>]}]}, 112 | {struct,[{<<"name">>,<<"FriendName3">>}, 113 | {<<"nickname">>,[<<"NickName4">>,<<"NickName5">>]}]}]}, 114 | {<<"name">>,<<"Name1">>}]}). 115 | 116 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 117 | %%% = = Test functions = = 118 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 119 | %% @doc 120 | %% Purpose: Tests lib_json:add_value3 121 | %% @doc 122 | add_value_test() -> 123 | ?assertEqual("{\"attr1\":\"value1\"}", lib_json:add_value("{}", attr1, <<"value1">>)), 124 | 125 | %% For a regular string value the function does not recognize it, so 126 | %% it needs to be defined as a binary like above 127 | ?assertEqual("{\"attr1\":\"value1\"}", lib_json:add_value("{}", attr1, <<"value1">>)), 128 | ?assertEqual(?JSON_RESULT1, lib_json:add_value(?JSON1, friend, "{\"name\":\"FriendName0\", \"nickname\":\"NickName0\"}")), 129 | ?assertEqual(?JSON_RESULT2, lib_json:add_value(?JSON1, "friend[1].nickname", <<"NickName6">>)), 130 | ?assertEqual(?JSON_RESULT3, lib_json:add_value(?JSON1, "friend[0].height", 180)), 131 | ?assertEqual(?JSON_RESULT4, lib_json:add_value(?JSON1, "friend[0].height", "[180,182]")), 132 | ?assertEqual(?JSON_RESULT4, lib_json:add_value(?JSON1, "friend[0].height", [180, 182])), 133 | 134 | %% If the field already exist and is not a list then no action is taken 135 | ?assertEqual(?JSON1, lib_json:add_value(?JSON1, name, <<"poff">>)), 136 | 137 | ?assertEqual(?JSON1, lib_json:add_value(?JSON1, "name.poff", <<"poff">>)). 138 | 139 | 140 | add_values_test() -> 141 | true. 142 | 143 | %% @doc 144 | %% Purpose: Test the json_lib:decode/1 145 | %% @end 146 | decode_test() -> 147 | ?assertEqual(?DECODE_RESULT1, lib_json:decode(?JSON1)). 148 | 149 | %% @doc 150 | %% Purpose: Test the json_lib:encode/1 151 | %% @end 152 | encode_test() -> 153 | ?assertEqual(?ENCODE_RESULT1, lib_json:encode(?JSON1)). 154 | 155 | %% @doc 156 | %% Purpose: Test the json_lib:encode/1 and lib_json:decode/1 in combination 157 | %% @end 158 | encode_decode_test() -> 159 | ?assertEqual(?DECODE_RESULT1, lib_json:decode(lib_json:encode(?JSON1))), 160 | ?assertEqual(?ENCODE_RESULT1, lib_json:encode(lib_json:decode(lib_json:encode(?JSON1)))). 161 | 162 | %% @doc 163 | %% Purpose: Test the json_lib:field_value_exists/3 164 | %% @end 165 | field_value_exists_test() -> 166 | ?assertEqual(true, lib_json:field_value_exists(?JSON1, "friend[1].name", <<"FriendName2">>)), 167 | ?assertEqual(false, lib_json:field_value_exists(?JSON1, "friend[0].name", <<"FriendName2">>)), 168 | ?assertEqual(true, lib_json:field_value_exists(?JSON1, "friend[*].nickname", <<"NickName1">>)), 169 | ?assertEqual(true, lib_json:field_value_exists(?JSON1, "friend[*].nickname", <<"NickName3">>)), 170 | ?assertEqual(false, lib_json:field_value_exists(?JSON1, "friend[*].name", <<"NickName3">>)), 171 | ?assertEqual(false, lib_json:field_value_exists(?JSON2, "friend[*].name", <<"NickName3">>)), 172 | ?assertEqual(true, lib_json:field_value_exists(?JSON2, "friend.nickname", <<"NickName3">>)), 173 | ?assertEqual(true, lib_json:field_value_exists(?JSON1, "friend[0]", ?JSON_RESULT6)), 174 | ?assertEqual(true, lib_json:field_value_exists(?JSON1, "friend", ?JSON_RESULT5)). 175 | 176 | %% @doc 177 | %% Purpose: Tests lib_json:get_field/2 178 | %% @end 179 | get_field_test() -> 180 | ?assertEqual(<<"Name1">>, lib_json:get_field(?JSON1, "name")), 181 | ?assertEqual(?JSON_RESULT5, lib_json:get_field(?JSON1, "friend")), 182 | ?assertEqual(?JSON_RESULT6, lib_json:get_field(?JSON1, "friend[0]")), 183 | ?assertEqual(<<"FriendName1">>, lib_json:get_field(?JSON1, "friend[0].name")), 184 | ?assertEqual(<<"NickName1">>, lib_json:get_field(?JSON1, "friend[0].nickname")), 185 | ?assertEqual([<<"NickName2">>, <<"NickName3">>], lib_json:get_field(?JSON1, "friend[1].nickname")), 186 | ?assertEqual(<<"NickName2">>, lib_json:get_field(?JSON1, "friend[1].nickname[0]")), 187 | ?assertEqual(undefined, lib_json:get_field(?JSON1, "friend[0].nick")), 188 | ?assertEqual("{\"name\":\"FriendName2\",\"nickname\":[\"NickName2\",\"NickName3\"]}", 189 | lib_json:get_field(?JSON2, "friend")), 190 | 191 | AddedField1 = lib_json:add_value(?JSON1, "friend[0].height", [1,2]), 192 | ?assertEqual([1,2], lib_json:get_field(AddedField1, "friend[0].height")), 193 | AddedField2 = lib_json:add_value(?JSON1, "friend[0].height", ["value1","value2"]), 194 | ?assertEqual([<<"value1">>,<<"value2">>], lib_json:get_field(AddedField2, "friend[0].height")). 195 | 196 | %% @doc 197 | %% Purpose: Tests json_lib:get_field_value/2 198 | %% @end 199 | get_field_value_test() -> 200 | ?assertEqual(<<"FriendName2">>, lib_json:get_field_value(?JSON1, "friend[1].name", <<"FriendName2">>)), 201 | ?assertEqual(undefined, lib_json:get_field_value(?JSON1, "friend[0].name", <<"FriendName2">>)), 202 | ?assertEqual(<<"NickName1">>, lib_json:get_field_value(?JSON1, "friend[*].nickname", <<"NickName1">>)), 203 | ?assertEqual(<<"NickName3">>, lib_json:get_field_value(?JSON1, "friend[*].nickname", <<"NickName3">>)), 204 | ?assertEqual(undefined, lib_json:get_field_value(?JSON1, "friend[*].name", <<"NickName3">>)), 205 | ?assertEqual(undefined, lib_json:get_field_value(?JSON2, "friend[*].name", <<"NickName3">>)), 206 | ?assertEqual(<<"NickName3">>, lib_json:get_field_value(?JSON2, "friend.nickname", <<"NickName3">>)), 207 | ?assertEqual(?JSON_RESULT6, lib_json:get_field_value(?JSON1, "friend[0]", ?JSON_RESULT6)), 208 | ?assertEqual(?JSON_RESULT5, lib_json:get_field_value(?JSON1, "friend", ?JSON_RESULT5)), 209 | ?assertEqual(null, lib_json:get_field_value(?JSON3, "hits.max_score", null)), 210 | 211 | %% This call will produce an error. Added here as an example of how 212 | %% lib_json:get_field_value/3 NOT should be used. The second argument is not 213 | %% allowed to end with [*] 214 | %% See lib_json:get_field_value/3 for details of how to use the function 215 | Try = try lib_json:get_field_value(?JSON1, "friend[*]", "") of 216 | _ -> will_not_happen 217 | catch 218 | _:_ -> error 219 | end, 220 | ?assertEqual(error, Try). 221 | 222 | replace_field_test() -> 223 | true. 224 | 225 | rm_field_test() -> 226 | true. 227 | 228 | set_attr_test() -> 229 | true. 230 | 231 | set_attrs_test() -> 232 | true. 233 | 234 | to_string_test() -> 235 | true. 236 | -------------------------------------------------------------------------------- /test/poll_help_tests.erl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/projectcs13/sensor-cloud/0302bd74b2e62fddbd832fb4c7a27b9c62852b90/test/poll_help_tests.erl -------------------------------------------------------------------------------- /test/resources_tests.erl: -------------------------------------------------------------------------------- 1 | %% @author Andreas Moreg�rd 2 | %% [www.csproj13.student.it.uu.se] 3 | %% @version 1.0 4 | %% 5 | %% @doc Tests for the resource module 6 | %% This module contains tests for the resource module 7 | %% 8 | %% @end 9 | 10 | -module(resources_tests). 11 | -include_lib("eunit/include/eunit.hrl"). 12 | 13 | -define(WEBMACHINE_URL, api_help:get_webmachine_url()). 14 | 15 | %% @doc 16 | %% Function: inti_test/0 17 | %% Purpose: Used to start the inets to be able to do HTTP requests 18 | %% Returns: ok | {error, term()} 19 | %% 20 | %% Side effects: Start inets 21 | %% @end 22 | -spec init_test() -> ok | {error, term()}. 23 | init_test() -> 24 | inets:start(). 25 | 26 | 27 | %% @doc 28 | %% Function: process_search_post_test/0 29 | %% Purpose: Test the process_post_test function by doing some HTTP requests 30 | %% Returns: ok | {error, term()} 31 | %% 32 | %% Side effects: creates documents in elasticsearch 33 | %% @end 34 | process_search_post_test() -> 35 | {ok, {{_Version1, 200, _ReasonPhrase1}, _Headers1, Body1}} = httpc:request(post, {?WEBMACHINE_URL++"/resources", [],"application/json", "{\"name\" : \"post\"}"}, [], []), 36 | DocId1 = lib_json:get_field(Body1,"_id"), 37 | ?assertEqual(true,lib_json:get_field(Body1,"ok")), 38 | api_help:refresh(), 39 | {ok, {{_Version3, 200, _ReasonPhrase3}, _Headers3, Body3}} = httpc:request(post, {?WEBMACHINE_URL++"/resources/_search", [],"application/json", "{\"query\":{\"match_all\":{}}}"}, [], []), 40 | {ok, {{_Version3, 200, _ReasonPhrase4}, _Headers4, Body4}} = httpc:request(post, {?WEBMACHINE_URL++"/resources/_search", [],"application/json", "{\"query\":{\"match_all\":{}}}"}, [], []), 41 | {ok, {{_Version8, 200, _ReasonPhrase5}, _Headers5, _Body5}} = httpc:request(delete, {?WEBMACHINE_URL++"/resources/" ++ lib_json:to_string(DocId1), []}, [], []), 42 | ?assertEqual(true,lib_json:get_field(Body3,"hits.total") >= 1), 43 | ?assertEqual(true,lib_json:get_field(Body4,"hits.total") >= 0). 44 | 45 | %% @doc 46 | %% Function: process_post_test/0 47 | %% Purpose: Test the process_post_test function by doing some HTTP requests 48 | %% Returns: ok | {error, term()} 49 | %% 50 | %% Side effects: creates documents in elasticsearch 51 | %% @end 52 | process_post_test() -> 53 | {ok, {{_Version1, 200, _ReasonPhrase1}, _Headers1, Body1}} = httpc:request(post, {?WEBMACHINE_URL++"/resources", [],"application/json", "{\"name\" : \"post\"}"}, [], []), 54 | api_help:refresh(), 55 | ?assertEqual(true,lib_json:get_field(Body1,"ok")), 56 | %Clean up after the test 57 | httpc:request(delete, {?WEBMACHINE_URL++"/resources/" ++ lib_json:to_string(lib_json:get_field(Body1,"_id")), []}, [], []). 58 | 59 | %% @doc 60 | %% Function: delete_resource_test/0 61 | %% Purpose: Test the delete_resource_test function by doing some HTTP requests 62 | %% Returns: ok | {error, term()} 63 | %% 64 | %% Side effects: creates and deletes documents in elasticsearch 65 | %% @end 66 | delete_resource_test() -> 67 | {ok, {{_Version2, 200, _ReasonPhrase2}, _Headers2, Body2}} = httpc:request(post, {?WEBMACHINE_URL++"/resources", [],"application/json", "{\"name\" : \"delete\"}"}, [], []), 68 | DocId = lib_json:get_field(Body2,"_id"), 69 | api_help:refresh(), 70 | {ok, {{_Version3, 200, _ReasonPhrase3}, _Headers3, Body3}} = httpc:request(delete, {?WEBMACHINE_URL++"/resources/" ++ lib_json:to_string(DocId), []}, [], []), 71 | api_help:refresh(), 72 | % Delete a resource that doesn't exist 73 | {ok, {{_Version5, 404, _ReasonPhrase5}, _Headers5, _Body5}} = httpc:request(delete, {?WEBMACHINE_URL++"/resources/1", []}, [], []), 74 | ?assertEqual(true, lib_json:get_field(Body2,"ok")), 75 | ?assertEqual(true, lib_json:get_field(Body3,"ok")). 76 | 77 | %% @doc 78 | %% Function: put_resource_test/0 79 | %% Purpose: Test the put_resource_test function by doing some HTTP requests 80 | %% Returns: ok | {error, term()} 81 | %% 82 | %% Side effects: creates and updatess a document in elasticsearch 83 | %% @end 84 | put_resource_test() -> 85 | {ok, {{_Version1, 200, _ReasonPhrase1}, _Headers1, Body1}} = httpc:request(post, {?WEBMACHINE_URL++"/resources/", [],"application/json", "{\"name\" : \"put1\"}"}, [], []), 86 | api_help:refresh(), 87 | DocId = lib_json:get_field(Body1,"_id"), 88 | {ok, {{_Version2, 200, _ReasonPhrase2}, _Headers2, Body2}} = httpc:request(put, {?WEBMACHINE_URL++"/resources/" ++ lib_json:to_string(DocId) , [],"application/json", "{\"name\" : \"put2\"}"}, [], []), 89 | {ok, {{_Version3, 200, _ReasonPhrase3}, _Headers3, Body3}} = httpc:request(get, {?WEBMACHINE_URL++"/resources/" ++ lib_json:to_string(DocId), []}, [], []), 90 | %Try to put to a resource that doesn't exist 91 | {ok, {{_Version4, 404, _ReasonPhrase4}, _Headers4, _Body4}} = httpc:request(put, {?WEBMACHINE_URL++"/resources/" ++ "non-existing-index", [],"application/json", "{\"name\" : \"put2\"}"}, [], []), 92 | ?assertEqual(true,lib_json:get_field(Body1,"ok")), 93 | ?assertEqual(true,lib_json:get_field(Body2,"ok")), 94 | ?assertEqual(<<"put2">>,lib_json:get_field(Body3,"name")), 95 | %Clean up 96 | httpc:request(delete, {?WEBMACHINE_URL++"/resources/" ++ lib_json:to_string(DocId), []}, [], []). 97 | 98 | 99 | %% @doc 100 | %% Function: get_resource_test/0 101 | %% Purpose: Test the get_resource_test function by doing some HTTP requests 102 | %% Returns: ok | {error, term()} 103 | %% 104 | %% Side effects: creates and returns documents in elasticsearch 105 | %% @end 106 | get_resource_test() -> 107 | {ok, {{_Version1, 200, _ReasonPhrase1}, _Headers1, Body1}} = httpc:request(post, {?WEBMACHINE_URL++"/resources/", [],"application/json", "{\"name\" : \"get\"}"}, [], []), 108 | api_help:refresh(), 109 | DocId = lib_json:get_field(Body1,"_id"), 110 | {ok, {{_Version2, 200, _ReasonPhrase2}, _Headers2, Body2}} = httpc:request(get, {?WEBMACHINE_URL++"/resources/" ++ lib_json:to_string(DocId), []}, [], []), 111 | {ok, {{_Version4, 200, _ReasonPhrase4}, _Headers4, Body4}} = httpc:request(get, {?WEBMACHINE_URL++"/resources/_search?name=get", []}, [], []), 112 | %Get resource that doesn't exist 113 | {ok, {{_Version5, 404, _ReasonPhrase5}, _Headers5, _Body5}} = httpc:request(get, {?WEBMACHINE_URL++"/resources/1", []}, [], []), 114 | % Tests to make sure the correct creation date is added 115 | ?assertEqual(<<"get">>,lib_json:get_field(Body2,"name")), 116 | ?assertEqual(true , lib_json:get_field(Body4,"hits.total") >= 1), 117 | %Clean up 118 | httpc:request(delete, {?WEBMACHINE_URL++"/resources/" ++ lib_json:to_string(DocId), []}, [], []). 119 | 120 | 121 | %% @doc 122 | %% Function: get_resource_test/0 123 | %% Purpose: Test the get_resource_test function by doing some HTTP requests 124 | %% Returns: ok | {error, term()} 125 | %% 126 | %% Side effects: creates and returns documents in elasticsearch 127 | %% @end 128 | get_streams_suggested_test() -> 129 | {ok, {{_Version1, 200, _ReasonPhrase1}, _Headers1, Body1}} = httpc:request(post, {"http://localhost:8000/resources/", [],"application/json", "{\"name\" : \"sugg\" , \"model\" : \"model1\", \"manufacturer\" : \"man1\"}"}, [], []), 130 | {ok, {{_VersionUser, 200, _ReasonPhraseUser}, _HeadersUser, _BodyUser}} = httpc:request(post, {?WEBMACHINE_URL++"/users", [], "application/json", "{\"username\":\"1\"}"}, [], []), 131 | api_help:refresh(), 132 | DocId = lib_json:get_field(Body1,"_id"), 133 | 134 | {ok, {{_Version3, 200, _ReasonPhrase3}, _Headers3, Body3}} = httpc:request(post, {"http://localhost:8000/streams", [],"application/json", "{\"name\" : \"suggStream\",\"type\": \"Temperature\",\"user_id\" : \"1\", \"resource\" : {\"resource_type\" :\""++ lib_json:to_string(DocId) ++ "\", \"uuid\" : \"1\" }}"}, [], []), 135 | %Get the created resource in order to check the new new suggested stream 136 | 137 | {ok, {{_Version2, 200, _ReasonPhrase2}, _Headers2, Body2}} = httpc:request(get, {"http://localhost:8000/resources/" ++ lib_json:to_string(DocId), []}, [], []), 138 | 139 | % Tests to make sure the correct creation date is added 140 | ?assertEqual(<<"suggStream">>,lib_json:get_field(Body2,"streams_suggest[0].name")), 141 | 142 | {ok, {{_Version4, 200, _ReasonPhrase4}, _Headers4, Body4}} = httpc:request(post, {"http://localhost:8000/streams", [],"application/json", "{\"name\" : \"suggStr\",\"type\": \"Temperature\",\"user_id\" : \"1\", \"resource\" : {\"resource_type\" :\""++ lib_json:to_string(DocId) ++ "\", \"uuid\" : \"1\" }}"}, [], []), 143 | {ok, {{_Version5, 200, _ReasonPhrase5}, _Headers5, Body5}} = httpc:request(get, {"http://localhost:8000/resources/" ++ lib_json:to_string(DocId), []}, [], []), 144 | 145 | ?assertEqual(undefined,lib_json:get_field(Body5,"streams_suggest[1].name")), 146 | 147 | 148 | %Clean up 149 | httpc:request(delete, {"http://localhost:8000/resources/" ++ lib_json:to_string(DocId), []}, [], []). 150 | 151 | %% @doc 152 | %% Function: add_unsupported_field_test/0 153 | %% Purpose: Test that unsuported fields are not allowed to be added 154 | %% on create or update 155 | %% Returns: ok | {error, term()} 156 | %% @end 157 | -spec add_unsupported_field_test() -> ok | {error, term()}. 158 | add_unsupported_field_test() -> 159 | {ok, {{_Version, 200, _ReasonPhrase}, _Headers, Body}} = httpc:request(post, {?WEBMACHINE_URL++"/resources", [], "application/json", "{\"name\" : \"test\"}"}, [], []), 160 | DocId = lib_json:get_field(Body,"_id"), 161 | api_help:refresh(), 162 | {ok, {{_Version1, 403, _ReasonPhrase1}, _Headers1, _Body1}} = httpc:request(post, {?WEBMACHINE_URL++"/resources", [],"application/json", "{\"test\":\"asdas\",\"name\" : \"test\"}"}, [], []), 163 | {ok, {{_Version2, 403, _ReasonPhrase2}, _Headers2, _Body2}} = httpc:request(put, {?WEBMACHINE_URL++"/resources/" ++ lib_json:to_string(DocId), [],"application/json", "{\"test\":\"asdas\",\"name\" : \"test\"}"}, [], []), 164 | {ok, {{_Version3, 200, _ReasonPhrase3}, _Headers3, _Body3}} = httpc:request(delete, {?WEBMACHINE_URL++"/resources/" ++ lib_json:to_string(DocId), []}, [], []). 165 | 166 | 167 | %% @doc 168 | %% Function: generate_date/2 169 | %% Purpose: Used to create a date valid in ES 170 | %% from the input which should be the list 171 | %% [Year,Mounth,Day] 172 | %% Returns: The generated timestamp 173 | %% 174 | %% @end 175 | -spec generate_date(DateList::list()) -> string(). 176 | 177 | generate_date([First]) -> 178 | case First < 10 of 179 | true -> "0" ++ integer_to_list(First); 180 | false -> "" ++ integer_to_list(First) 181 | end; 182 | generate_date([First|Rest]) -> 183 | case First < 10 of 184 | true -> "0" ++ integer_to_list(First) ++ "-" ++ generate_date(Rest); 185 | false -> "" ++ integer_to_list(First) ++ "-" ++ generate_date(Rest) 186 | end. 187 | -------------------------------------------------------------------------------- /test/search_tests.erl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/projectcs13/sensor-cloud/0302bd74b2e62fddbd832fb4c7a27b9c62852b90/test/search_tests.erl -------------------------------------------------------------------------------- /test/suggest_tests.erl: -------------------------------------------------------------------------------- 1 | %% @author Georgios Koutsoumpakis 2 | %% [www.csproj13.student.it.uu.se] 3 | %% @version 1.0 4 | %% @copyright [Copyright information] 5 | %% 6 | %% @doc == suggest_tests == 7 | %% This module contains several tests to test the functionallity 8 | %% in the restful API for the autocomplete feature. 9 | %% 10 | %% @end 11 | 12 | -module(suggest_tests). 13 | -include_lib("eunit/include/eunit.hrl"). 14 | 15 | %% ==================================================================== 16 | %% API functions 17 | %% ==================================================================== 18 | -export([]). 19 | 20 | %% ==================================================================== 21 | %% Internal functions 22 | %% ==================================================================== 23 | 24 | -define(URL, api_help:get_webmachine_url()). 25 | -define(STREAMS_URL, ?URL ++ "/streams"). 26 | -define(SUGGEST_URL, ?URL ++ "/suggest"). 27 | -define(RESOURCES_URL, ?URL ++ "/resources"). 28 | 29 | %% @doc 30 | %% Test a post request 31 | %% @end 32 | %-spec post_test() -> ok | {error, term()}. 33 | %post_test() -> 34 | %Response1 = post_request(?RESOURCES_URL, "application/json", 35 | % "{ 36 | % \"model\" : \"testsmartphone2\", 37 | % \"tags\" : \"testtag\" 38 | % }"), 39 | %check_returned_code(Response1, 200), 40 | %api_help:refresh(), 41 | %Response2 = get_request(?SUGGEST_URL++"/testsmartphone2"), 42 | %check_returned_code(Response2, 200), 43 | %{ok, {_, _ ,Body}} = Response2, 44 | %?assertEqual(<<"testtag">>,lib_json:get_field(Body, "suggestions[0].payload.tags")). 45 | 46 | 47 | %% @doc 48 | %% Test creating a suggestion which contains both resource and streams 49 | %% @end 50 | %-spec post_and_stream_test() -> ok | {error, term()}. 51 | %post_and_stream_test() -> 52 | %Response1 = post_request(?RESOURCES_URL, "application/json", 53 | % "{ 54 | % \"model\" : \"testwithstream\", 55 | % \"tags\" : \"testtag\" 56 | % }"), 57 | %check_returned_code(Response1, 200), 58 | %{ok, {{_Version, 200, _ReasonPhrase}, _Headers, Body}} = Response1, 59 | %Id = lib_json:get_field(Body, "_id"), 60 | %api_help:refresh(), 61 | %{ok, {{_Version1, 200, _ReasonPhrase1}, _Headers1, _Body1}} = httpc:request(post, {?STREAMS_URL, [],"application/json", "{\"name\" : \"search\",\"resource_id\" : \""++lib_json:to_string(Id)++"\", \"private\" : \"false\", \"tags\":\"test_tag\"}"}, [], []), 62 | %api_help:refresh(), 63 | %{ok, {{_Version11, 200, _ReasonPhrase11}, _Headers11, _Body11}} = httpc:request(post, {?STREAMS_URL, [],"application/json", "{\"name\" : \"search2\",\"resource_id\" : \""++lib_json:to_string(Id)++"\", \"private\" : \"false\", \"tags\":\"test2\"}"}, [], []), 64 | %api_help:refresh(), 65 | %Response2 = get_request(?SUGGEST_URL++"/testwithstream"), 66 | %check_returned_code(Response2, 200), 67 | %{ok, {_, _ ,Body2}} = Response2, 68 | %?assertEqual(<<"testtag">>,lib_json:get_field(Body2, "suggestions[0].payload.tags")), 69 | %?assertEqual(true, lib_json:field_value_exists(Body2, "suggestions[0].payload.streams[*].tags",<<"test_tag">>)), 70 | %?assertEqual(<<"test2">>, lib_json:get_field_value(Body2, "suggestions[0].payload.streams[*].tags",<<"test2">>)). 71 | 72 | 73 | %% @doc 74 | %% Test a get request for a suggestion 75 | %% @end 76 | %-spec get_suggestion_test() -> ok | {error, term()}. 77 | %get_suggestion_test() -> 78 | %Response1 = post_request(?RESOURCES_URL, "application/json", 79 | % "{ 80 | % \"model\" : \"testgetsuggestion\", 81 | % \"manufacturer\" : \"ericsson\" 82 | % }"), 83 | %check_returned_code(Response1, 200), 84 | %api_help:refresh(), 85 | %Response2 = get_request(?SUGGEST_URL ++ "/testgetsuggestion"), 86 | %check_returned_code(Response2, 200), 87 | %{ok, {_, _ ,Body}} = Response2, 88 | %?assertEqual(<<"ericsson">>,lib_json:get_field(Body, "suggestions[0].payload.manufacturer")). 89 | 90 | 91 | %% @doc 92 | %% Test a get request for a model that doesn't exist 93 | %% @end 94 | %-spec get_non_existing_term_test() -> ok | {error, term()}. 95 | %get_non_existing_term_test() -> 96 | %Response1 = get_request(?SUGGEST_URL ++ "/non-existing-term"), 97 | %check_returned_code(Response1, 404). 98 | 99 | 100 | 101 | %% @doc 102 | %% Updates a resource and makes sure that the suggestion was updated properly 103 | %% @end 104 | %-spec update_resource_test() -> ok | {error, term()}. 105 | %update_resource_test() -> 106 | %Response1 = post_request(?RESOURCES_URL, "application/json", 107 | % "{ 108 | % \"model\" : \"testupdate\", 109 | % \"tags\" : \"testtag\", 110 | % \"manufacturer\" : \"testmanu\" 111 | % }"), 112 | %check_returned_code(Response1, 200), 113 | %{ok, {{_Version, 200, _ReasonPhrase}, _Headers, Body}} = Response1, 114 | %Id = lib_json:get_field(Body, "_id"), 115 | %api_help:refresh(), 116 | %{ok, {{_Version1, 200, _ReasonPhrase1}, _Headers1, _Body1}} = httpc:request(post, {?STREAMS_URL, [],"application/json", "{\"name\" : \"search\",\"resource_id\" : \""++lib_json:to_string(Id)++"\", \"private\" : \"false\", \"tags\":\"test_tag\"}"}, [], []), 117 | %api_help:refresh(), 118 | %{ok, {{_Version11, 200, _ReasonPhrase11}, _Headers11, _Body11}} = httpc:request(post, {?STREAMS_URL, [],"application/json", "{\"name\" : \"search2\",\"resource_id\" : \""++lib_json:to_string(Id)++"\", \"private\" : \"false\", \"tags\":\"test2\"}"}, [], []), 119 | %api_help:refresh(), 120 | %Response2 = get_request(?SUGGEST_URL++"/testupdate"), 121 | %check_returned_code(Response2, 200), 122 | %{ok, {_, _ ,Body2}} = Response2, 123 | %?assertEqual(<<"testtag">>,lib_json:get_field(Body2, "suggestions[0].payload.tags")), 124 | %?assertEqual(true, lib_json:field_value_exists(Body2, "suggestions[0].payload.streams[*].tags",<<"test_tag">>)), 125 | %?assertEqual(<<"test2">>, lib_json:get_field_value(Body2, "suggestions[0].payload.streams[*].tags",<<"test2">>)), 126 | %{ok, {{_Version21, 200, _ReasonPhrase21}, _Headers21, _Body21}} = httpc:request(put, {?RESOURCES_URL++"/"++lib_json:to_string(Id), [],"application/json", "{\"model\" : \"testupdate\",\"tags\" : \"newtag\"}"}, [], []), 127 | %api_help:refresh(), 128 | %{ok, {_, _ ,Body3}} = get_request(?SUGGEST_URL++"/testupdate"), 129 | %?assertEqual(<<"newtag">>,lib_json:get_field(Body3, "suggestions[0].payload.tags")), 130 | %?assertEqual(true, lib_json:field_value_exists(Body3, "suggestions[0].payload.streams[*].tags",<<"test_tag">>)), 131 | %?assertEqual(<<"test2">>, lib_json:get_field_value(Body3, "suggestions[0].payload.streams[*].tags",<<"test2">>)). 132 | 133 | 134 | 135 | %% @doc 136 | %% Updates a stream and makes sure that the suggestion was updated properly 137 | %% @end 138 | %-spec update_stream_test() -> ok | {error, term()}. 139 | %update_stream_test() -> 140 | %Response1 = post_request(?RESOURCES_URL, "application/json", 141 | % "{ 142 | % \"model\" : \"teststreamupdate\", 143 | % \"tags\" : \"testtag\", 144 | % \"manufacturer\" : \"testmanu\" 145 | % }"), 146 | %check_returned_code(Response1, 200), 147 | %{ok, {{_Version, 200, _ReasonPhrase}, _Headers, Body}} = Response1, 148 | %Id = lib_json:get_field(Body, "_id"), 149 | %api_help:refresh(), 150 | %{ok, {{_Version1, 200, _ReasonPhrase1}, _Headers1, _Body1}} = httpc:request(post, {?STREAMS_URL, [],"application/json", "{\"name\" : \"search\",\"resource_id\" : \""++lib_json:to_string(Id)++"\", \"private\" : \"false\", \"tags\":\"test_tag\"}"}, [], []), 151 | %api_help:refresh(), 152 | %{ok, {{_Version11, 200, _ReasonPhrase11}, _Headers11, Body11}} = httpc:request(post, {?STREAMS_URL, [],"application/json", "{\"name\" : \"search2\",\"resource_id\" : \""++lib_json:to_string(Id)++"\", \"private\" : \"false\", \"tags\":\"test2\"}"}, [], []), 153 | %StreamId = lib_json:get_field(Body11, "_id"), 154 | %api_help:refresh(), 155 | %Response2 = get_request(?SUGGEST_URL++"/teststreamupdate"), 156 | %check_returned_code(Response2, 200), 157 | %{ok, {_, _ ,Body2}} = Response2, 158 | %?assertEqual(<<"testtag">>,lib_json:get_field(Body2, "suggestions[0].payload.tags")), 159 | %?assertEqual(true, lib_json:field_value_exists(Body2, "suggestions[0].payload.streams[*].tags",<<"test_tag">>)), 160 | %?assertEqual(<<"test2">>, lib_json:get_field_value(Body2, "suggestions[0].payload.streams[*].tags",<<"test2">>)), 161 | %{ok, {{_Version21, 200, _ReasonPhrase21}, _Headers21, _Body21}} = httpc:request(put, {?STREAMS_URL++"/"++lib_json:to_string(StreamId), [],"application/json", "{\"name\" : \"search2\", \"private\" : \"false\", \"tags\":\"newtest2\"}"}, [], []), 162 | %api_help:refresh(), 163 | %{ok, {_, _ ,Body3}} = get_request(?SUGGEST_URL++"/teststreamupdate"), 164 | %?assertEqual(<<"testtag">>,lib_json:get_field(Body3, "suggestions[0].payload.tags")), 165 | %?assertEqual(true, lib_json:field_value_exists(Body3, "suggestions[0].payload.streams[*].tags",<<"test_tag">>)), 166 | %?assertEqual(<<"newtest2">>, lib_json:get_field_value(Body3, "suggestions[0].payload.streams[*].tags",<<"newtest2">>)). 167 | 168 | 169 | %% @doc 170 | %% Checks if API for text autocompletion works properly 171 | %% @end 172 | %-spec text_autocompletion_test() -> ok | {error, term()}. 173 | %text_autocompletion_test() -> 174 | %Response1 = post_request(?RESOURCES_URL, "application/json", 175 | % "{ 176 | % \"model\" : \"testauto\", 177 | % \"tags\" : \"testautotag\" 178 | % }"), 179 | %check_returned_code(Response1, 200), 180 | %{ok, {{_Version, 200, _ReasonPhrase}, _Headers, Body}} = Response1, 181 | %Id = lib_json:get_field(Body, "_id"), 182 | %api_help:refresh(), 183 | %{ok, {{_Version1, 200, _ReasonPhrase1}, _Headers1, Body1}} = httpc:request(post, {?STREAMS_URL, [],"application/json", "{\"name\" : \"search\",\"resource_id\" : \""++lib_json:to_string(Id)++"\", \"private\" : \"false\", \"tags\":\"test_auto_tag\"}"}, [], []), 184 | %api_help:refresh(), 185 | %Response2 = get_request(?SUGGEST_URL++"/model/"++"testauto"), 186 | %Response3 = get_request(?SUGGEST_URL++"/tags/"++"testautotag"), 187 | %Response4 = get_request(?SUGGEST_URL++"/tags/"++"test_auto_tag"), 188 | %check_returned_code(Response2, 200), 189 | %check_returned_code(Response3, 200), 190 | %check_returned_code(Response4, 200), 191 | %{ok, {_, _ ,Body2}} = Response2, 192 | %{ok, {_, _ ,Body3}} = Response3, 193 | %{ok, {_, _ ,Body4}} = Response4, 194 | %?assertEqual(<<"testauto">>,lib_json:get_field(Body2, "suggestions[0].text")), 195 | %?assertEqual(<<"testautotag">>, lib_json:get_field(Body3, "suggestions[0].text")), 196 | %?assertEqual(<<"test_auto_tag">>, lib_json:get_field(Body4, "suggestions[0].text")), 197 | %make changes to resource/stream and check if it got updated 198 | %StreamId = lib_json:get_field(Body1, "_id"), 199 | %{ok, {{_Version21, 200, _ReasonPhrase21}, _Headers21, _Body21}} = httpc:request(put, {?STREAMS_URL++"/"++lib_json:to_string(StreamId), [],"application/json", "{\"name\" : \"newsearch\", \"private\" : \"false\", \"tags\":\"newtag\"}"}, [], []), 200 | %api_help:refresh(), 201 | %Response5 = get_request(?SUGGEST_URL++"/name/"++"newsearch"), 202 | %check_returned_code(Response5, 200), 203 | %{ok, {_, _ ,Body5}} = Response5, 204 | %?assertEqual(<<"newsearch">>, lib_json:get_field(Body5, "suggestions[0].text")). 205 | 206 | %% @doc 207 | %% Checks if API for search autocompletion works 208 | %% @end 209 | -spec search_autocompletion_test() -> ok | {error, term()}. 210 | search_autocompletion_test() -> 211 | {ok, {{_Version1, 200, _ReasonPhrase1}, _Headers1, _Body1}} = httpc:request(post, {"http://localhost:8000/_search", [],"application/json", "{\"sort\":\"nr_subscribers\",\"query\":{\"query_string\":{\"query\":\"test\"}}}"}, [], []), 212 | api_help:refresh(), 213 | {ok, {{_Version2, 200, _ReasonPhrase2}, _Headers2, Body2}} = httpc:request(get, {"http://localhost:8000/suggest/_search?query=test", []}, [], []), 214 | {ok, {{_Version3, 200, _ReasonPhrase3}, _Headers3, _Body3}} = httpc:request(get, {"http://localhost:8000/suggest/_search?query=öäå", []}, [], []), 215 | {ok, {{_Version4, 200, _ReasonPhrase4}, _Headers4, _Body4}} = httpc:request(get, {"http://localhost:8000/suggest/_search?query=test%20or", []}, [], []), 216 | ?assertNotEqual(0,string:str(lib_json:to_string(lib_json:get_field(Body2, "suggestions[0].text")), "test")). 217 | 218 | 219 | %% @doc 220 | %% Checks if the Response has the correct http return code 221 | %% @end 222 | -spec check_returned_code(string(), integer()) -> ok. 223 | check_returned_code(Response, Code) -> 224 | {ok, Rest} = Response, 225 | {Header,_,_} = Rest, 226 | ?assertMatch({_, Code, _}, Header). 227 | 228 | 229 | post_request(URL, ContentType, Body) -> request(post, {URL, [], ContentType, Body}). 230 | 231 | put_request(URL, ContentType, Body) -> request(put, {URL, [], ContentType, Body}). 232 | get_request(URL) -> request(get, {URL, []}). 233 | 234 | request(Method, Request) -> 235 | httpc:request(Method, Request, [], []). 236 | 237 | -------------------------------------------------------------------------------- /test/test.erl: -------------------------------------------------------------------------------- 1 | %% @author Tommy Mattsson 2 | %% [www.csproj13.student.it.uu.se] 3 | %% @version 1.0 4 | %% @copyright [Copyright information] 5 | 6 | %% @doc Test wrapper module 7 | -module(test). 8 | -author('Tommy Mattsson'). 9 | -export([run/0, run/1]). 10 | 11 | -define(WEBMACHINE_URL, api_help:get_webmachine_url()). 12 | -define(RESOURCE_URL, ?WEBMACHINE_URL++"/resources/"). 13 | %% @doc 14 | %% Function: run/0 15 | %% Purpose: Wrapper function for testing in order to be able to return a 16 | %% non-zero exit code on failure of one or more test cases fails. 17 | %% This is for getting tests to work with Travis CI. 18 | %% Returns: ok | no_return() 19 | %% @end 20 | run() -> 21 | run("ebin"). 22 | 23 | 24 | post_request(URL, ContentType, Body) -> request(post, {URL, [], ContentType, Body}). 25 | request(Method, Request) -> 26 | httpc:request(Method, Request, [], []). 27 | 28 | run(Suite) -> 29 | Result = eunit:test(Suite, 30 | [verbose, 31 | {cover_enabled, true}, 32 | {report, {eunit_surefire, [{dir, "test-results"}]}} 33 | ]), 34 | case Result of 35 | ok -> 36 | init:stop(); 37 | error -> 38 | halt(1) 39 | end. 40 | -------------------------------------------------------------------------------- /test/triggers_lib_tests.erl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/projectcs13/sensor-cloud/0302bd74b2e62fddbd832fb4c7a27b9c62852b90/test/triggers_lib_tests.erl -------------------------------------------------------------------------------- /test/vs_func_lib_tests.erl: -------------------------------------------------------------------------------- 1 | %% @author Tomas S�vstr�m 2 | %% [www.csproj13.student.it.uu.se] 3 | %% @version 1.0 4 | %% @copyright [Copyright information] 5 | %% 6 | %% @doc == vs_func_lib_tests == 7 | %% This module contains several tests to test the functionallity 8 | %% in the module vs_func_lib to test the data functions 9 | %% 10 | %% @end 11 | -module(vs_func_lib_tests). 12 | 13 | %% ==================================================================== 14 | %% API functions 15 | %% ==================================================================== 16 | -export([]). 17 | -include_lib("eunit/include/eunit.hrl"). 18 | 19 | 20 | %% ==================================================================== 21 | %% Internal functions 22 | %% ==================================================================== 23 | 24 | 25 | %% @doc 26 | %% Function: min_test/0 27 | %% Purpose: Used test the min function 28 | %% Returns: ok | {error, term()} 29 | %% 30 | %% @end 31 | -spec min_test() -> ok | {error, term()}. 32 | 33 | min_test() -> 34 | Data = [["{\"timestamp\":\"2014-11-21T10:15:56.000\",\"value\":5.0}", 35 | "{\"timestamp\":\"2014-11-21T10:16:56.000\",\"value\":4.0}", 36 | "{\"timestamp\":\"2014-11-21T11:15:56.000\",\"value\":3.0}", 37 | "{\"timestamp\":\"2014-12-21T10:15:56.000\",\"value\":2.0}", 38 | "{\"timestamp\":\"2014-11-24T10:15:56.000\",\"value\":1.0}"], 39 | ["{\"timestamp\":\"2015-11-21T10:15:56.000\",\"value\":-5.0}", 40 | "{\"timestamp\":\"2014-12-21T10:15:56.000\",\"value\":-4.0}", 41 | "{\"timestamp\":\"2014-11-25T10:15:56.000\",\"value\":-3.0}", 42 | "{\"timestamp\":\"2014-11-21T10:16:56.000\",\"value\":-2.0}", 43 | "{\"timestamp\":\"2014-11-21T10:15:59.000\",\"value\":-1.0}"], 44 | ["{\"timestamp\":\"2014-11-21T10:15:56.000\",\"value\":0.0}"], 45 | ["{\"timestamp\":\"2014-11-21T10:15:56.000\",\"value\":5.0}", 46 | "{\"timestamp\":\"2015-11-21T10:15:56.000\",\"value\":-5.0}"]], 47 | Result = vs_func_lib:min(Data,<<"adjkkvcj--sdffs">>), 48 | ?assertEqual(["{\"stream_id\":\"adjkkvcj--sdffs\",\"timestamp\":\"2015-11-21T10:15:56.000\",\"value\":-5.0}", 49 | "{\"stream_id\":\"adjkkvcj--sdffs\",\"timestamp\":\"2014-11-21T10:15:56.000\",\"value\":0.0}", 50 | "{\"stream_id\":\"adjkkvcj--sdffs\",\"timestamp\":\"2015-11-21T10:15:56.000\",\"value\":-5.0}", 51 | "{\"stream_id\":\"adjkkvcj--sdffs\",\"timestamp\":\"2014-12-21T10:15:56.000\",\"value\":1.0}"],Result). 52 | 53 | %% @doc 54 | %% Function: max_test/0 55 | %% Purpose: Used test the max function 56 | %% Returns: ok | {error, term()} 57 | %% 58 | %% @end 59 | -spec max_test() -> ok | {error, term()}. 60 | 61 | max_test() -> 62 | Data = [["{\"timestamp\":\"2014-11-21T10:15:56.000\",\"value\":5.0}", 63 | "{\"timestamp\":\"2014-11-21T10:16:56.000\",\"value\":4.0}", 64 | "{\"timestamp\":\"2014-11-21T11:15:56.000\",\"value\":3.0}", 65 | "{\"timestamp\":\"2014-12-21T10:15:56.000\",\"value\":2.0}", 66 | "{\"timestamp\":\"2014-11-24T10:15:56.000\",\"value\":1.0}"], 67 | ["{\"timestamp\":\"2015-11-21T10:15:56.000\",\"value\":-5.0}", 68 | "{\"timestamp\":\"2014-12-21T10:15:56.000\",\"value\":-4.0}", 69 | "{\"timestamp\":\"2014-11-25T10:15:56.000\",\"value\":-3.0}", 70 | "{\"timestamp\":\"2014-11-21T10:16:56.000\",\"value\":-2.0}", 71 | "{\"timestamp\":\"2014-11-21T10:15:59.000\",\"value\":-1.0}"], 72 | ["{\"timestamp\":\"2014-11-21T10:15:56.000\",\"value\":0.0}"], 73 | ["{\"timestamp\":\"2014-11-21T10:15:56.000\",\"value\":5.0}", 74 | "{\"timestamp\":\"2015-11-21T10:15:56.000\",\"value\":-5.0}"]], 75 | Result = vs_func_lib:max(Data,<<"adjkkvcj--sdffs">>), 76 | ?assertEqual(["{\"stream_id\":\"adjkkvcj--sdffs\",\"timestamp\":\"2015-11-21T10:15:56.000\",\"value\":5.0}", 77 | "{\"stream_id\":\"adjkkvcj--sdffs\",\"timestamp\":\"2014-11-21T10:15:56.000\",\"value\":0.0}", 78 | "{\"stream_id\":\"adjkkvcj--sdffs\",\"timestamp\":\"2015-11-21T10:15:56.000\",\"value\":-1.0}", 79 | "{\"stream_id\":\"adjkkvcj--sdffs\",\"timestamp\":\"2014-12-21T10:15:56.000\",\"value\":5.0}"],Result). 80 | 81 | %% @doc 82 | %% Function: avg_test/0 83 | %% Purpose: Used test the avg function 84 | %% Returns: ok | {error, term()} 85 | %% 86 | %% @end 87 | -spec avg_test() -> ok | {error, term()}. 88 | 89 | avg_test() -> 90 | Data = [["{\"timestamp\":\"2014-11-21T10:15:56.000\",\"value\":5.0}", 91 | "{\"timestamp\":\"2014-11-21T10:16:56.000\",\"value\":4.0}", 92 | "{\"timestamp\":\"2014-11-21T11:15:56.000\",\"value\":3.0}", 93 | "{\"timestamp\":\"2014-12-21T10:15:56.000\",\"value\":2.0}", 94 | "{\"timestamp\":\"2014-11-24T10:15:56.000\",\"value\":1.0}"], 95 | ["{\"timestamp\":\"2015-11-21T10:15:56.000\",\"value\":-5.0}", 96 | "{\"timestamp\":\"2014-12-21T10:15:56.000\",\"value\":-4.0}", 97 | "{\"timestamp\":\"2014-11-25T10:15:56.000\",\"value\":-3.0}", 98 | "{\"timestamp\":\"2014-11-21T10:16:56.000\",\"value\":-2.0}", 99 | "{\"timestamp\":\"2014-11-21T10:15:59.000\",\"value\":-1.0}"], 100 | ["{\"timestamp\":\"2014-11-21T10:15:56.000\",\"value\":0.0}"], 101 | ["{\"timestamp\":\"2014-11-21T10:15:56.000\",\"value\":5.0}", 102 | "{\"timestamp\":\"2015-11-21T10:15:56.000\",\"value\":-5.0}"]], 103 | Result = vs_func_lib:mean(Data,<<"adjkkvcj--sdffs">>), 104 | ?assertEqual(["{\"stream_id\":\"adjkkvcj--sdffs\",\"timestamp\":\"2015-11-21T10:15:56.000\",\"value\":0.0}", 105 | "{\"stream_id\":\"adjkkvcj--sdffs\",\"timestamp\":\"2014-11-21T10:15:56.000\",\"value\":0.0}", 106 | "{\"stream_id\":\"adjkkvcj--sdffs\",\"timestamp\":\"2015-11-21T10:15:56.000\",\"value\":-3.0}", 107 | "{\"stream_id\":\"adjkkvcj--sdffs\",\"timestamp\":\"2014-12-21T10:15:56.000\",\"value\":3.0}"],Result). 108 | 109 | %% @doc 110 | %% Function: sum_test/0 111 | %% Purpose: Used test the total(sum) function 112 | %% Returns: ok | {error, term()} 113 | %% 114 | %% @end 115 | -spec sum_test() -> ok | {error, term()}. 116 | 117 | sum_test() -> 118 | Data = [["{\"timestamp\":\"2014-11-21T10:15:56.000\",\"value\":5.0}", 119 | "{\"timestamp\":\"2014-11-21T10:16:56.000\",\"value\":4.0}", 120 | "{\"timestamp\":\"2014-11-21T11:15:56.000\",\"value\":3.0}", 121 | "{\"timestamp\":\"2014-12-21T10:15:56.000\",\"value\":2.0}", 122 | "{\"timestamp\":\"2014-11-24T10:15:56.000\",\"value\":1.0}"], 123 | ["{\"timestamp\":\"2015-11-21T10:15:56.000\",\"value\":-5.0}", 124 | "{\"timestamp\":\"2014-12-21T10:15:56.000\",\"value\":-4.0}", 125 | "{\"timestamp\":\"2014-11-25T10:15:56.000\",\"value\":-3.0}", 126 | "{\"timestamp\":\"2014-11-21T10:16:56.000\",\"value\":-2.0}", 127 | "{\"timestamp\":\"2014-11-21T10:15:59.000\",\"value\":-1.0}"], 128 | ["{\"timestamp\":\"2014-11-21T10:15:56.000\",\"value\":0.0}"], 129 | ["{\"timestamp\":\"2014-11-21T10:15:56.000\",\"value\":5.0}", 130 | "{\"timestamp\":\"2015-11-21T10:15:56.000\",\"value\":-5.0}"]], 131 | Result = vs_func_lib:total(Data,<<"adjkkvcj--sdffs">>), 132 | ?assertEqual(["{\"stream_id\":\"adjkkvcj--sdffs\",\"timestamp\":\"2015-11-21T10:15:56.000\",\"value\":0.0}", 133 | "{\"stream_id\":\"adjkkvcj--sdffs\",\"timestamp\":\"2014-11-21T10:15:56.000\",\"value\":0.0}", 134 | "{\"stream_id\":\"adjkkvcj--sdffs\",\"timestamp\":\"2015-11-21T10:15:56.000\",\"value\":-15.0}", 135 | "{\"stream_id\":\"adjkkvcj--sdffs\",\"timestamp\":\"2014-12-21T10:15:56.000\",\"value\":15.0}"],Result). -------------------------------------------------------------------------------- /test/vstreams_tests.erl: -------------------------------------------------------------------------------- 1 | %% @author Iakovos Koutsoumpakis 2 | %% [www.csproj13.student.it.uu.se] 3 | %% @version 1.0 4 | %% @copyright [Copyright information] 5 | %% 6 | %% @doc == datapoints_tests == 7 | %% This module contains several tests to test 8 | %% the virtual streams functionality. 9 | %% 10 | %% @end 11 | 12 | -module(vstreams_tests). 13 | -include_lib("eunit/include/eunit.hrl"). 14 | 15 | -define(WEBMACHINE_URL, api_help:get_webmachine_url()). 16 | -define(STREAMS_URL, ?WEBMACHINE_URL ++ "/streams/"). 17 | -define(VSTREAMS_URL, ?WEBMACHINE_URL ++ "/vstreams/"). 18 | -define(VSDATAPOINTS_URL, ?WEBMACHINE_URL ++ "/data/"). 19 | -define(TEST_VALUE, "1"). 20 | -define(INDEX, "sensorcloud"). 21 | 22 | 23 | %% @doc 24 | %% Function: init_test/0 25 | %% Purpose: Used to start the inets to be able to do HTTP requests 26 | %% Returns: ok | {error, term()} 27 | %% 28 | %% Side effects: Start inets 29 | %% @end 30 | -spec init_test() -> ok | {error, term()}. 31 | 32 | init_test() -> 33 | inets:start(). 34 | 35 | 36 | %% @doc 37 | %% Function: post_test/0 38 | %% Purpose: Test a post request 39 | %% Returns: ok | {error, term()} 40 | %% 41 | %% @end 42 | -spec post_test() -> ok | {error, term()}. 43 | post_test() -> 44 | {ok, {{_Version, 200, _ReasonPhrase}, _Headers, Body1}} = httpc:request(post, {?WEBMACHINE_URL++"/users", [],"application/json", "{\"username\" : \"vstreamuser\"}"}, [], []), 45 | UserId = lib_json:get_field(Body1,"_id"), 46 | api_help:refresh(), 47 | {ok, {{_Version2, 200, _ReasonPhrase2}, _Headers2, Body2}} = post_request(?STREAMS_URL, "application/json", "{ \"name\" : \"teststream1\", \"user_id\" : \"" ++ lib_json:to_string(UserId) ++ "\" }"), 48 | Streamid1 = lib_json:get_field(Body2, "_id"), 49 | api_help:refresh(), 50 | {ok, {{_Version3, 200, _ReasonPhrase3}, _Headers3, Body3}} = post_request(?STREAMS_URL, "application/json", "{ \"name\" : \"teststream2\", \"user_id\" : \"" ++ lib_json:to_string(UserId) ++ "\" }"), 51 | Streamid2 = lib_json:get_field(Body3, "_id"), 52 | api_help:refresh(), 53 | post_request(?STREAMS_URL ++ lib_json:to_string(Streamid1) ++ "/data", "application/json", "{ \"value\":1}"), 54 | post_request(?STREAMS_URL ++ lib_json:to_string(Streamid1) ++ "/data", "application/json", "{ \"value\":1}"), 55 | post_request(?STREAMS_URL ++ lib_json:to_string(Streamid1) ++ "/data", "application/json", "{ \"value\":1}"), 56 | post_request(?STREAMS_URL ++ lib_json:to_string(Streamid1) ++ "/data", "application/json", "{ \"value\":1}"), 57 | post_request(?STREAMS_URL ++ lib_json:to_string(Streamid1) ++ "/data", "application/json", "{ \"value\":1}"), 58 | 59 | post_request(?STREAMS_URL ++ lib_json:to_string(Streamid2) ++ "/data", "application/json", "{ \"value\":2}"), 60 | post_request(?STREAMS_URL ++ lib_json:to_string(Streamid2) ++ "/data", "application/json", "{ \"value\":2}"), 61 | post_request(?STREAMS_URL ++ lib_json:to_string(Streamid2) ++ "/data", "application/json", "{ \"value\":2}"), 62 | post_request(?STREAMS_URL ++ lib_json:to_string(Streamid2) ++ "/data", "application/json", "{ \"value\":2}"), 63 | post_request(?STREAMS_URL ++ lib_json:to_string(Streamid2) ++ "/data", "application/json", "{ \"value\":2}"), 64 | api_help:refresh(), 65 | Response3 = post_request(?VSTREAMS_URL, "application/json", "{\"user_id\" : \"" ++ lib_json:to_string(UserId) ++ "\", \"name\" : \"post_testvstream1\", \"description\" : \"test\", 66 | \"streams_involved\" : [\"" ++ lib_json:to_string(Streamid1) ++ "\", \"" ++ lib_json:to_string(Streamid2) ++ "\"], 67 | \"timestampfrom\" : \"now-1h\", \"function\" : [\"mean\", \"1s\"]}"), 68 | check_returned_code(Response3, 200), 69 | api_help:refresh(). 70 | 71 | 72 | 73 | %% @doc 74 | %% Function: get_vstream_test/0 75 | %% Purpose: Test the get_stream function by doing some HTTP requests 76 | %% Returns: ok | {error, term()} 77 | %% 78 | %% Side effects: creates a document in elasticsearch 79 | %% @end 80 | -spec get_vstream_test() -> ok | {error, term()}. 81 | 82 | get_vstream_test() -> 83 | {ok, {{_Version1, 200, _ReasonPhrase1}, _Headers1, Body1}} = httpc:request(post, {?WEBMACHINE_URL++"/vstreams/_search",[],"application/json", "{\"query\":{\"term\" : { \"name\" : \"post_testvstream1\"}}}"}, [], []), 84 | StreamId = lib_json:get_field(Body1,"hits[0].id"), 85 | {ok, {{_Version2, 200, _ReasonPhrase2}, _Headers2, Body2}} = httpc:request(get, {?WEBMACHINE_URL++"/vstreams/" ++ lib_json:to_string(StreamId), []}, [], []) 86 | . 87 | 88 | 89 | %% @doc 90 | %% Function: put_vstream_test/0 91 | %% Purpose: Test the put_vstream function by doing some HTTP requests 92 | %% Returns: ok | {error, term()} 93 | %% 94 | %% Side effects: creates 2 document in elasticsearch and updates them 95 | %% @end 96 | -spec put_vstream_test() -> ok | {error, term()}. 97 | put_vstream_test() -> 98 | {ok, {{_Version1, 200, _ReasonPhrase1}, _Headers1, Body1}} = httpc:request(post, {?WEBMACHINE_URL++"/vstreams/_search",[],"application/json", "{\"query\":{\"term\" : { \"name\" : \"post_testvstream1\"}}}"}, [], []), 99 | StreamId = lib_json:get_field(Body1,"hits[0].id"), 100 | {ok, {{_Version2, 200, _ReasonPhrase2}, _Headers2, Body2}} = httpc:request(put, {?WEBMACHINE_URL++"/vstreams/" ++ lib_json:to_string(StreamId), [], "application/json", "{\n\"name\" : \"updated_testvstream1\"}"}, [], []), 101 | api_help:refresh(), 102 | {ok, {{_Version3, 200, _ReasonPhrase3}, _Headers3, Body3}} = httpc:request(post, {?WEBMACHINE_URL++"/vstreams/_search",[],"application/json", "{\"query\":{\"term\" : { \"name\" : \"post_testvstream1\"}}}"}, [], []), 103 | ?assertEqual(length(lib_json:get_field(lib_json:to_string(Body3), "hits")), 0), 104 | {ok, {{_Version4, 200, _ReasonPhrase4}, _Headers4, Body4}} = httpc:request(post, {?WEBMACHINE_URL++"/vstreams/_search",[],"application/json", "{\"query\":{\"term\" : { \"name\" : \"updated_testvstream1\"}}}"}, [], []), 105 | ?assertEqual(length(lib_json:get_field(lib_json:to_string(Body4), "hits")), 1) 106 | . 107 | 108 | 109 | %% @doc 110 | %% Function: delete_vstream_test/0 111 | %% Purpose: Test the delete_vstream function by doing some HTTP requests 112 | %% Returns: ok | {error, term()} 113 | %% 114 | %% Side effects: creates 2 document in elasticsearch and deletes them 115 | %% @end 116 | -spec delete_vstream_test() -> ok | {error, term()}. 117 | 118 | delete_vstream_test() -> 119 | %delete vstream, make sure there are no vsdatapoints left 120 | {ok, {{_Version1, 200, _ReasonPhrase1}, _Headers1, Body1}} = httpc:request(post, {?WEBMACHINE_URL++"/vstreams/_search",[],"application/json", "{\"query\":{\"term\" : { \"name\" : \"updated_testvstream1\"}}}"}, [], []), 121 | StreamId = lib_json:get_field(Body1,"hits[0].id"), 122 | {ok, {{_Version2, 200, _ReasonPhrase2}, _Headers2, Body2}} = httpc:request(delete, {?WEBMACHINE_URL++"/vstreams/" ++ lib_json:to_string(StreamId), []}, [], []), 123 | api_help:refresh(), 124 | {ok, {{_Version3, 200, _ReasonPhrase3}, _Headers3, Body3}} = httpc:request(post, {?WEBMACHINE_URL++"/vstreams/_search",[],"application/json", "{\"query\":{\"term\" : { \"name\" : \"updated_testvstream1\"}}}"}, [], []), 125 | ?assertEqual(length(lib_json:get_field(lib_json:to_string(Body3), "hits")), 0), 126 | {ok, {{_Version3, 200, _ReasonPhrase4}, _Headers4, Body4}} = httpc:request(get, {?WEBMACHINE_URL++"/vstreams/" ++ lib_json:to_string(StreamId) ++ "/data/_search", []}, [], []), 127 | ?assertEqual(length(lib_json:get_field(lib_json:to_string(Body4), "data")), 0), 128 | api_help:refresh(), 129 | {ok, {{_Version6, 200, _ReasonPhrase6}, _Headers6, Body6}} = httpc:request(delete, {?WEBMACHINE_URL++"/users/vstreamuser", []}, [], []) 130 | . 131 | 132 | 133 | %% @doc 134 | %% Checks if the Response has the correct http return code 135 | %% @end 136 | -spec check_returned_code(string(), integer()) -> ok. 137 | check_returned_code(Response, Code) -> 138 | {ok, Rest} = Response, 139 | {Header,_,_} = Rest, 140 | ?assertMatch({_, Code, _}, Header). 141 | 142 | 143 | post_request(URL, ContentType, Body) -> request(post, {URL, [], ContentType, Body}). 144 | get_request(URL) -> request(get, {URL, []}). 145 | request(Method, Request) -> 146 | httpc:request(Method, Request, [], []). --------------------------------------------------------------------------------