diff --git a/.gitignore b/.gitignore index 03f837719..73dabe242 100644 --- a/.gitignore +++ b/.gitignore @@ -10,12 +10,8 @@ hs_err_pid* # JavaScript main/src/main/webapp/bower_components main/src/main/webapp/node_modules -main/src/main/webapp/app/app.js -main/src/main/webapp/app/templates.js - -# CSS -main/src/main/webapp/app/style.min.css -main/src/main/webapp/app/stylesheets/style.css +main/src/main/webapp/dist +main/src/main/webapp/tests/coverage # Auto Generated Swagger Doc main/src/main/webapp/restdocs/docs diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 000000000..db03e88fe --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,191 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +Copyright 2016 TU Dortmund + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. \ No newline at end of file diff --git a/README.md b/README.md index a1ee9591e..b3e436fce 100644 --- a/README.md +++ b/README.md @@ -1,72 +1,126 @@ -Automata Learning Experience (ALEX) -=================================== +

+ +

-Getting Started ---------------- -To build ALEX [Java][], [Maven][], [Node.JS][nodejs] and the [Grunt CLI][grunt] must be installed. -If all this tools are available, the fastest and easiest way to get started is to run the following commands: +# ALEX - # the next line must only be done once! - mvn install +Automata Learning Experience (ALEX) is a web application that allows you run automated tests on web applications and JSON-based REST services using Active Automata Learning. - # start the server - cd main - mvn jetty:run +## How it works -These commands will download all required dependencies and then build ALEX. -If the build process was successful and the server has started, ALEX is available -at [localhost](http://localhost:8080/). +Let us take a look at the example of an authentication system. +For an integration test you would probably write tests using Selenium or something else that check the following behavior: +* register > login > logout _should work_ +* logout _should not work_ +* register > logout _should not work_ +* ... -Building a Release ------------------- -To create a release archive please make sure that all requirements are fulfilled and that you can successfully build -ALEX. If this is the case just run: +With increasing complexity and feature set of an application, integration tests might be hard to maintain if all possible and impossible combinations of features should be tested. +With ALEX, we follow a different, more automated approach that - mvn clean package site - mvn -P release assembly:single +1. does not require any knowledge about the implementation of an application, +2. does not require any experience in programming and +3. separates the language specific tests from the application to test. -Afterwards the *ALEX-xxx.tar.gz* and the *ALEX-xxx.tar.zip archive are created in the `target` directory. +Assume the authentication system mentioned above has the following features: _register_, _login_ and _logout_. +Then, we model each feature independent of each other using e.g. [Selenium](http://www.seleniumhq.org/). +In Active Automata Learning, this set of testable features is called **alphabet** where the elements of the alphabet are called **symbols**. +A sequence of symbols is called a **word**. +We then pass the learning alphabet to a **learner** that uses specific algorithms to create a model of the underlying application and how it behaves based on the given symbols. +Therefore, words are executed on the real system under learning and its reactions are observed. +Finally, the learner creates a **hypothesis** as an output that is modeled as a [Mealy machine](https://en.wikipedia.org/wiki/Mealy_machine). +In this example, it would hopefully look like this: -Docker ------- -Another way to test and / or release ALEX involves the tool [Docker](https://www.docker.com). -Please make sure that Docker is installed on your machine and that you have the right permissions to use it. -If this is the case just run: +![Hypothesis](assets/images/hypothesis.jpg) - # the next line must only be done once! - mvn install - - cd main - mvn docker:build +As it can be seen, the learner has learned the application and all features work as intended. +From here on, more symbols can be added and learned. -Afterwards a new Docker image is created and you use it like any other Docker image, e.g. - - # lets take a look at all images - docker images - - # run ALEX at http://localhost:8000 - docker run -d -p 8000:8080 alex:1.0-SNAPSHOT +It can be that the learner has not learned the application correctly, i.e. there is a word in the model that has a different output than the actual application. +In fact, one can never be a 100% sure if the model actually represents the system under learning. +How to check for and handle these mistakes is described in the user manual. -More Documentation ------------------- -Further documentation is provided through the *site* feature of maven. -To Create the documentation simply run: +If you want to know more about Active Automata Learning, you can find some resources at [Google Scholar](https://scholar.google.de/scholar?hl=de&q=active+automata+learning) or the homepage of the [LearnLib](http://learnlib.de/). - mvn site +## Installation and first login -Afterwards the documentation is in the `target/site` folders of every module. +We developed and tested ALEX using either Windows 8.1 and higher and Linux Ubuntu 14.10 and higher. +As the application runs on JAVA, any other system with an installed JVM should do fine. +We also advise to use a modern web browser like Google Chrome >= v46, Mozilla Firefox >= v42 or Microsoft Edge with JavaScript enabled in order to run the front-end properly. +After the first start, you can login as admin backend using the account below. -Maintainers ------------ +Email: *admin@alex.example*
+Password: *admin* -* [Alexander Bainczyk](mailto:alexander.bainczyk@tu-dortmund.de) -* [Alexander Schieweck](mailto:alexander.schieweck@tu-dortmund.de) +#### Using the packaged version +Make sure you have Java 8 installed on your system. + +1. Download the latest version from here. +2. Open a terminal and start the war archive using `java -jar ALEX.war [--port=XXXX]` +3. Open *http://localhost:8000* in a web browser + +#### From source + +For the Installation from the source files make sure your system matches the following system requirements: + +* Java JDK 8 +* Maven 3 +* Node.js v4.2.* and the NPM +* Bower `npm install -g bower` +* Grunt and Grund-Cli `npm install -g grunt grunt-cli` + +To install and run ALEX, execute the following commands in a directory of your choice: + +1. `git clone https://github.com/LearnLib/alex.git` +2. `cd alex` +3. `mvn install [-DskipTests]` +4. `cd main` +5. `mvn spring-boot:run [-Dport=XXXX]` +6. open *http://localhost:8000* in a web browser + +## Further reading + +* Frontend user manual +* REST API documentation +* Screencasts +* Developer docs + +## FAQs + +**Is ALEX ready for production use?**
+ALEX has already been used by a class at the TU Dortmund University as a tool to test a student project. +As far as we are concerned, we used the application to learn applications like Bugzilla and parts of the famous Wordpress. + +**Could I potentially learn any available application that is accessible over the internet?**
+Yes, you could, but really should not do that, since testing usually takes a lot of traffic, and your test targets may not like it. +But we do not programmatically prohibit it either. +The primary use case is to use ALEX for testing applications that are installed locally or in the same network. + +**Do I really not have to have any programming experience?**
+Yes and no, it depends on how you define programming experience. +We made the best efforts to abstract all necessary steps to test a web application with ease. +While modelling Selenium tests, it may eventually be helpful to have basic understanding of HTML and CSS. +The same goes for modelling REST tests where it is needed that you can write JSON documents. +But to our understanding neither JSON, HTML nor CSS are programming languages. + +**How potent does my system have to be to run tests?**
+Learning is a CPU and memory intense process, but we cannot really tell you an lower bound for your system specs. +From our own observations, it is advised to have at least 2Gb of RAM and a relatively modern Dual Core CPU to run tests at a satisfiable performance. +The more power your system has, the faster your tests should execute. +But feel free to test it on your Raspberry PI. + +**I want to learn a specific feature of my application but ALEX does not provide a way to model it. What can I do?**
+Initially, we did not develop ALEX as a solution to all situations that may occur in a web application, but to the most common ones. +If you think a key feature is missing, feel free to submit an issue and we will see what we can do. + +*Note: Surprisingly, neither of the above questions have been asked at any point in time. But it it still a neat way to package information that do not fit anywhere else in the documentation.* [java]: https://java.com [maven]: https://maven.apache.org [nodejs]: https://nodejs.org [grunt]: http://gruntjs.com +[bower]: http://bower.io diff --git a/api/pom.xml b/api/pom.xml index 45557f477..0652392d9 100644 --- a/api/pom.xml +++ b/api/pom.xml @@ -7,7 +7,7 @@ de.learnlib.alex alex-parent - 1.0-SNAPSHOT + 1.0 ../pom.xml @@ -26,22 +26,4 @@ - - - - - org.apache.maven.plugins - maven-project-info-reports-plugin - ${reports-plugin.version} - - - - index - - - - - - - \ No newline at end of file diff --git a/api/src/main/java/de/learnlib/alex/algorithms/LearnAlgorithmFactory.java b/api/src/main/java/de/learnlib/alex/algorithms/LearnAlgorithmFactory.java index d0ec3f09e..ff09796a1 100644 --- a/api/src/main/java/de/learnlib/alex/algorithms/LearnAlgorithmFactory.java +++ b/api/src/main/java/de/learnlib/alex/algorithms/LearnAlgorithmFactory.java @@ -1,3 +1,19 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package de.learnlib.alex.algorithms; import de.learnlib.api.LearningAlgorithm; diff --git a/api/src/main/java/de/learnlib/alex/algorithms/package-info.java b/api/src/main/java/de/learnlib/alex/algorithms/package-info.java index 54d85449f..59b5a4865 100644 --- a/api/src/main/java/de/learnlib/alex/algorithms/package-info.java +++ b/api/src/main/java/de/learnlib/alex/algorithms/package-info.java @@ -1,3 +1,19 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + /** * Package that holds the API around algorithms. */ diff --git a/api/src/main/java/de/learnlib/alex/annotations/LearnAlgorithm.java b/api/src/main/java/de/learnlib/alex/annotations/LearnAlgorithm.java index 2690b72f7..8f6c64e10 100644 --- a/api/src/main/java/de/learnlib/alex/annotations/LearnAlgorithm.java +++ b/api/src/main/java/de/learnlib/alex/annotations/LearnAlgorithm.java @@ -1,3 +1,19 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package de.learnlib.alex.annotations; /** diff --git a/api/src/main/java/de/learnlib/alex/annotations/package-info.java b/api/src/main/java/de/learnlib/alex/annotations/package-info.java index db3ee438b..e9af40ba5 100644 --- a/api/src/main/java/de/learnlib/alex/annotations/package-info.java +++ b/api/src/main/java/de/learnlib/alex/annotations/package-info.java @@ -1,3 +1,19 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + /** * Package for all the annotations. */ diff --git a/api/src/main/java/de/learnlib/alex/exceptions/LearnerException.java b/api/src/main/java/de/learnlib/alex/exceptions/LearnerException.java index ab24914a3..aca98c338 100644 --- a/api/src/main/java/de/learnlib/alex/exceptions/LearnerException.java +++ b/api/src/main/java/de/learnlib/alex/exceptions/LearnerException.java @@ -1,3 +1,19 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package de.learnlib.alex.exceptions; /** diff --git a/api/src/main/java/de/learnlib/alex/exceptions/NotFoundException.java b/api/src/main/java/de/learnlib/alex/exceptions/NotFoundException.java index 99fc06b0e..14148c4e4 100644 --- a/api/src/main/java/de/learnlib/alex/exceptions/NotFoundException.java +++ b/api/src/main/java/de/learnlib/alex/exceptions/NotFoundException.java @@ -1,3 +1,19 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package de.learnlib.alex.exceptions; public class NotFoundException extends Exception { diff --git a/api/src/site/resources/css/bootstrap-responsive.min.css b/api/src/site/resources/css/bootstrap-responsive.min.css deleted file mode 120000 index a4d199547..000000000 --- a/api/src/site/resources/css/bootstrap-responsive.min.css +++ /dev/null @@ -1 +0,0 @@ -../../../../../src/site/resources/css/bootstrap-responsive.min.css \ No newline at end of file diff --git a/api/src/site/resources/css/bootstrap.min.css b/api/src/site/resources/css/bootstrap.min.css deleted file mode 120000 index 5e3fc7732..000000000 --- a/api/src/site/resources/css/bootstrap.min.css +++ /dev/null @@ -1 +0,0 @@ -../../../../../src/site/resources/css/bootstrap.min.css \ No newline at end of file diff --git a/api/src/site/resources/js/bootstrap.min.js b/api/src/site/resources/js/bootstrap.min.js deleted file mode 120000 index 59bc73727..000000000 --- a/api/src/site/resources/js/bootstrap.min.js +++ /dev/null @@ -1 +0,0 @@ -../../../../../src/site/resources/js/bootstrap.min.js \ No newline at end of file diff --git a/api/src/site/site.xml b/api/src/site/site.xml deleted file mode 100644 index 8e0f9fd4b..000000000 --- a/api/src/site/site.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/assets/images/hypothesis.jpg b/assets/images/hypothesis.jpg new file mode 100644 index 000000000..a9ca4c14f Binary files /dev/null and b/assets/images/hypothesis.jpg differ diff --git a/main/src/main/webapp/app/images/logo.png b/assets/images/logo.png similarity index 100% rename from main/src/main/webapp/app/images/logo.png rename to assets/images/logo.png diff --git a/main/pom.xml b/main/pom.xml index ef628498b..2aaca4c4d 100644 --- a/main/pom.xml +++ b/main/pom.xml @@ -7,7 +7,7 @@ de.learnlib.alex alex-parent - 1.0-SNAPSHOT + 1.0 ../pom.xml @@ -19,6 +19,12 @@ The actual ALEX. + + 1.3.2.RELEASE + 2.4.1 + de.learnlib.alex.App + + @@ -35,18 +41,33 @@ compile - - org.glassfish.jersey.containers - jersey-container-jetty-servlet - ${jersey.version} + org.springframework.boot + spring-boot-starter-jersey + ${spring-boot.version} + + + org.springframework.boot + spring-boot-starter-web + ${spring-boot.version} + + + org.springframework.boot + spring-boot-starter-tomcat + ${spring-boot.version} + provided + + + org.springframework + spring-context + 4.2.4.RELEASE org.hibernate hibernate-core - 4.3.10.Final + 5.0.3.Final org.hibernate @@ -74,16 +95,24 @@ 2.3.3 - org.glassfish.jersey.media - jersey-media-json-jackson + jersey-media-multipart ${jersey.version} + + - org.glassfish.jersey.media - jersey-media-multipart - ${jersey.version} + org.bitbucket.b_c + jose4j + ${jose4j.version} + + + + + org.apache.shiro + shiro-core + ${shiro.version} @@ -151,12 +180,29 @@ org.apache.logging.log4j log4j-api - 2.3 + ${log4j.version} org.apache.logging.log4j log4j-core - 2.3 + ${log4j.version} + + + org.apache.logging.log4j + log4j-jul + ${log4j.version} + + + org.apache.logging.log4j + log4j-jcl + ${log4j.version} + + + + + org.springframework.boot + spring-boot-starter-test + ${spring-boot.version} @@ -191,143 +237,79 @@ src/main/resources true + + src/main/webapp + + **/node_modules/** + **/tests/** + **/src/** + package.json + gruntfile.js + + + **/node_modules/font-awesome/fonts/** + + - - org.apache.maven.plugins - maven-compiler-plugin - ${compiler-plugin.version} - - - - - - - - org.apache.maven.plugins maven-war-plugin ${war-plugin.version} + false + + + src/main/webapp + + **/node_modules/** + **/tests/** + **/src/** + package.json + gruntfile.js + + + **/node_modules/font-awesome/fonts/** + + + - **/node_modules/**, + **/node_modules/** + **/tests/** + **/src/** + package.json + gruntfile.js - - app/**, - index.html, - img/**, - restdocs/**, - WEB-INF/**, - + + **/node_modules/font-awesome/fonts/** + - org.codehaus.mojo - exec-maven-plugin - 1.4.0 - - - npm-install - generate-sources - - exec - - - npm - src/main/webapp - - install - - - - - grunt - generate-sources - - exec - - - grunt - src/main/webapp - - - - grunt-css - generate-sources - - exec - - - grunt - - build-css - - src/main/webapp - - - - - - - org.apache.maven.plugins - maven-javadoc-plugin - ${javadoc-plugin.version} + org.springframework.boot + spring-boot-maven-plugin + ${spring-boot.version} + + de.learnlib.alex.App + false + - generate-service-docs - generate-resources - javadoc-no-fork + repackage - - com.carma.swagger.doclet.ServiceDoclet - - com.carma - swagger-doclet - 1.1.0 - - ${basedir}/src/main/webapp - false - -apiVersion 1 -docBasePath /restdocs/docs -apiBasePath /rest -disableCopySwaggerUi - restdocs/docs - - - - - - org.eclipse.jetty - jetty-maven-plugin - ${jetty.version} - - - org.eclipse.jetty - jetty-servlets - ${jetty.version} - - - - 60 - 8082 - stop - - - start-jetty + server-start pre-integration-test start - - true - 0 - - stop-jetty + server-stop post-integration-test stop @@ -344,7 +326,11 @@ alex.dbpath - mem:integration-test-db + mem:test-db + + + java.util.logging.manager + org.apache.logging.log4j.jul.LogManager @@ -360,6 +346,10 @@ alex.dbpath mem:integration-test-db + + java.util.logging.manager + org.apache.logging.log4j.jul.LogManager + @@ -397,77 +387,48 @@ - - - - - - org.apache.maven.plugins - maven-project-info-reports-plugin - ${reports-plugin.version} - - - - index - - - - - - - - default + createRestDoc true - - + - org.apache.maven.plugins - maven-surefire-report-plugin - ${surefire-report.version} - - - - - org.codehaus.mojo - cobertura-maven-plugin - ${cobertura.version} - - - - cobertura - - - - - - html - - - 60 - 60 - - + maven-javadoc-plugin + ${javadoc-plugin.version} + + + generate-service-docs + generate-resources + + javadoc-no-fork + + + com.carma.swagger.doclet.ServiceDoclet + + com.carma + swagger-doclet + 1.1.1 + + ${basedir}/src/main/webapp + false + -apiVersion 1 -docBasePath /restdocs/docs -apiBasePath /rest -disableCopySwaggerUi + restdocs/docs + + + - - - - - release + - bootstrap-npm + createFrontend - - src/main/webapp/node_modules - + true @@ -477,15 +438,17 @@ 1.4.0 - npm - initialize + grunt + generate-sources exec - npm + grunt - install + build-html + build-css + build-js src/main/webapp @@ -495,11 +458,12 @@ + - bootstrap-bower + bootstrap - src/main/webapp/bower_components + src/main/webapp/node_modules @@ -510,23 +474,35 @@ 1.4.0 - grunt-bower + npm initialize exec - grunt + npm - bower:install + install src/main/webapp + + grunt + generate-sources + + exec + + + grunt + src/main/webapp + + + diff --git a/main/src/docker/Dockerfile b/main/src/docker/Dockerfile index badb1754a..8b9eb608f 100644 --- a/main/src/docker/Dockerfile +++ b/main/src/docker/Dockerfile @@ -1,5 +1,9 @@ -FROM jetty:9.3 +FROM java:8 -ADD ALEX-*.war /var/lib/jetty/webapps/ROOT.war +ADD ALEX-*.war /usr/src/alex/ALEX.war +WORKDIR /usr/src/alex + +EXPOSE 8080 + +ENTRYPOINT ["java", "-jar", "ALEX.war"] -CMD ["jetty.sh", "run"] diff --git a/main/src/main/java/de/learnlib/alex/ALEXApplication.java b/main/src/main/java/de/learnlib/alex/ALEXApplication.java index a07f87936..a181acb57 100644 --- a/main/src/main/java/de/learnlib/alex/ALEXApplication.java +++ b/main/src/main/java/de/learnlib/alex/ALEXApplication.java @@ -1,53 +1,94 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package de.learnlib.alex; -import de.learnlib.alex.core.dao.CounterDAO; -import de.learnlib.alex.core.dao.CounterDAOImpl; -import de.learnlib.alex.core.dao.FileDAO; -import de.learnlib.alex.core.dao.FileDAOImpl; -import de.learnlib.alex.core.dao.LearnerResultDAO; -import de.learnlib.alex.core.dao.LearnerResultDAOImpl; -import de.learnlib.alex.core.dao.ProjectDAO; -import de.learnlib.alex.core.dao.ProjectDAOImpl; -import de.learnlib.alex.core.dao.SymbolDAO; -import de.learnlib.alex.core.dao.SymbolDAOImpl; -import de.learnlib.alex.core.dao.SymbolGroupDAO; -import de.learnlib.alex.core.dao.SymbolGroupDAOImpl; -import de.learnlib.alex.core.learner.Learner; -import de.learnlib.alex.core.learner.LearnerThreadFactory; -import org.glassfish.hk2.utilities.binding.AbstractBinder; +import de.learnlib.alex.core.dao.UserDAO; +import de.learnlib.alex.core.entities.User; +import de.learnlib.alex.core.entities.UserRole; +import de.learnlib.alex.rest.CounterResource; +import de.learnlib.alex.rest.FileResource; +import de.learnlib.alex.rest.IFrameProxyResource; +import de.learnlib.alex.rest.LearnerResource; +import de.learnlib.alex.rest.LearnerResultResource; +import de.learnlib.alex.rest.ProjectResource; +import de.learnlib.alex.rest.SymbolGroupResource; +import de.learnlib.alex.rest.SymbolResource; +import de.learnlib.alex.rest.UserResource; +import de.learnlib.alex.security.AuthenticationFilter; import org.glassfish.jersey.media.multipart.MultiPartFeature; import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.server.filter.RolesAllowedDynamicFeature; +import org.springframework.stereotype.Component; + +import javax.annotation.PostConstruct; +import javax.inject.Inject; +import javax.ws.rs.ApplicationPath; /** * Main class of the REST API. Implements the Jersey {@link ResourceConfig} and does some configuration and stuff. */ +@Component +@ApplicationPath("rest") public class ALEXApplication extends ResourceConfig { + /** The E-Mail for the default admin, i.e. the admin that will be auto created if no other admin exists. */ + public static final String DEFAULT_ADMIN_EMAIL = "admin@alex.example"; + + /** The Password for the default admin, i.e. the admin that will be auto created if no other admin exists. */ + public static final String DEFAULT_ADMIN_PASSWORD = "admin"; + + /** The UserDOA to create an admin if needed. */ + @Inject + private UserDAO userDAO; + /** * Constructor where the magic happens. */ public ALEXApplication() { - // packages with REST resources classes - packages(true, "de.learnlib.alex"); + // register REST resources classes + register(CounterResource.class); + register(FileResource.class); + register(IFrameProxyResource.class); + register(LearnerResource.class); + register(LearnerResultResource.class); + register(ProjectResource.class); + register(SymbolGroupResource.class); + register(SymbolResource.class); + register(UserResource.class); register(MultiPartFeature.class); + register(AuthenticationFilter.class); + register(RolesAllowedDynamicFeature.class); // allow protecting routes with user roles + } + + /** + * Create an admin at the start of th ALEX if no admin is currently in the DB. + */ + @PostConstruct + public void createAdminIfNeeded() { + // create an admin if none exists + if (userDAO.getAllByRole(UserRole.ADMIN).size() == 0) { + User admin = new User(); + admin.setEmail(DEFAULT_ADMIN_EMAIL); + admin.setRole(UserRole.ADMIN); + admin.setEncryptedPassword(DEFAULT_ADMIN_PASSWORD); - // register some classes/ objects for IoC. - register(new AbstractBinder() { - @Override - protected void configure() { - LearnerResultDAOImpl learnerResultDAO = new LearnerResultDAOImpl(); - LearnerThreadFactory threadFactory = new LearnerThreadFactory(learnerResultDAO); - - bind(ProjectDAOImpl.class).to(ProjectDAO.class); - bind(CounterDAOImpl.class).to(CounterDAO.class); - bind(SymbolGroupDAOImpl.class).to(SymbolGroupDAO.class); - bind(SymbolDAOImpl.class).to(SymbolDAO.class); - bind(LearnerResultDAOImpl.class).to(LearnerResultDAO.class); - bind(new Learner(threadFactory)).to(Learner.class); - bind(FileDAOImpl.class).to(FileDAO.class); - } - }); + userDAO.create(admin); + } } } diff --git a/main/src/main/java/de/learnlib/alex/App.java b/main/src/main/java/de/learnlib/alex/App.java new file mode 100644 index 000000000..1a96baa41 --- /dev/null +++ b/main/src/main/java/de/learnlib/alex/App.java @@ -0,0 +1,29 @@ +package de.learnlib.alex; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.boot.context.web.SpringBootServletInitializer; + +/** + * The entry point to ALEX. + */ +@SpringBootApplication +public class App extends SpringBootServletInitializer { + + @Override + protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { + return application.sources(App.class); + } + + /** + * Starts the standalone modus of ALEX. + * + * @param args + * Additional commandline parameters. + */ + public static void main(String[] args) { + SpringApplication.run(App.class, args); + } +} + diff --git a/main/src/main/java/de/learnlib/alex/actions/ExecuteSymbolAction.java b/main/src/main/java/de/learnlib/alex/actions/ExecuteSymbolAction.java index a8b27cc33..b0914c05e 100644 --- a/main/src/main/java/de/learnlib/alex/actions/ExecuteSymbolAction.java +++ b/main/src/main/java/de/learnlib/alex/actions/ExecuteSymbolAction.java @@ -1,13 +1,32 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package de.learnlib.alex.actions; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonTypeName; +import de.learnlib.alex.core.dao.SymbolDAO; +import de.learnlib.alex.core.dao.SymbolDAOImpl; import de.learnlib.alex.core.entities.ExecuteResult; import de.learnlib.alex.core.entities.IdRevisionPair; import de.learnlib.alex.core.entities.Symbol; import de.learnlib.alex.core.entities.SymbolAction; import de.learnlib.alex.core.learner.connectors.ConnectorManager; +import de.learnlib.alex.exceptions.NotFoundException; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -25,14 +44,19 @@ @JsonTypeName("executeSymbol") public class ExecuteSymbolAction extends SymbolAction { - /** to be serializable. */ + /** + * to be serializable. + */ private static final long serialVersionUID = 3143716533295082498L; - /** Use the logger for the server part. */ - private static final Logger LOGGER = LogManager.getLogger("server"); + /** + * Use the learner logger. + */ + private static final Logger LOGGER = LogManager.getLogger("learner"); /** * Reference to the Symbol that will be executed. + * * @requiredField */ @Transient @@ -47,6 +71,11 @@ public class ExecuteSymbolAction extends SymbolAction { @JsonIgnore private Symbol symbolToExecute; + /** + * Indicates if the latest revision of the symbol should be used. + */ + private boolean useLatestRevision; + /** * Get the reference to the Symbol which will be executed. * @@ -63,8 +92,7 @@ public IdRevisionPair getSymbolToExecuteAsIdRevisionPair() { * Set a new reference to the Symbol to execute. * This does not update the actual Symbol! * - * @param symbolToExecuteAsIdRevisionPair - * The new IdRevisionPair of the Symbol to execute. + * @param symbolToExecuteAsIdRevisionPair The new IdRevisionPair of the Symbol to execute. */ public void setSymbolToExecuteAsIdRevisionPair(IdRevisionPair symbolToExecuteAsIdRevisionPair) { this.symbolToExecuteAsIdRevisionPair = symbolToExecuteAsIdRevisionPair; @@ -104,14 +132,36 @@ public String getSymbolToExecuteName() { return symbolToExecute.getName(); } + /** + * Checks if the latest revision of the symbols should be used. + * + * @return if the latest revision should be used. + */ + public boolean isUseLatestRevision() { + return useLatestRevision; + } + + /** + * Sets the flag that indicates if the latest revision of the symbol should be used. + * + * @param useLatestRevision the flag + */ + public void setUseLatestRevision(boolean useLatestRevision) { + this.useLatestRevision = useLatestRevision; + } + @Override public ExecuteResult execute(ConnectorManager connector) { if (symbolToExecute == null) { - LOGGER.info("ExecuteSymbolAction.execute: Symbol not found!"); + LOGGER.info("No other Symbol to execute was set " + + "(ignoreFailure : " + ignoreFailure + ", negated: " + negated + ")."); return getFailedOutput(); } ExecuteResult symbolResult = symbolToExecute.execute(connector); + LOGGER.info("Executed other Symbol <" + symbolToExecute.getId() + ":" + symbolToExecute.getRevision() + "> " + + " with the result of '" + symbolResult + "' " + + "(ignoreFailure : " + ignoreFailure + ", negated: " + negated + ")."); if (symbolResult == ExecuteResult.OK) { return getSuccessOutput(); diff --git a/main/src/main/java/de/learnlib/alex/actions/RESTSymbolActions/CallAction.java b/main/src/main/java/de/learnlib/alex/actions/RESTSymbolActions/CallAction.java index 18141dc90..87eb6d152 100644 --- a/main/src/main/java/de/learnlib/alex/actions/RESTSymbolActions/CallAction.java +++ b/main/src/main/java/de/learnlib/alex/actions/RESTSymbolActions/CallAction.java @@ -1,3 +1,19 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package de.learnlib.alex.actions.RESTSymbolActions; import com.fasterxml.jackson.annotation.JsonTypeName; @@ -7,6 +23,7 @@ import org.apache.logging.log4j.Logger; import org.hibernate.validator.constraints.NotBlank; +import javax.persistence.Column; import javax.persistence.DiscriminatorValue; import javax.persistence.Entity; import javax.persistence.Lob; @@ -28,8 +45,8 @@ public class CallAction extends RESTSymbolAction { /** to be serializable. */ private static final long serialVersionUID = 7971257988991996022L; - /** Use the logger for the server part. */ - private static final Logger LOGGER = LogManager.getLogger("server"); + /** Use the learner logger. */ + private static final Logger LOGGER = LogManager.getLogger("learner"); /** * Enumeration to specify the HTTP method. @@ -71,8 +88,12 @@ public enum Method { private HashMap cookies; // OM NOM NOM NOM!!! /** Optional data to sent with a POST or PUT request. */ + @Column(columnDefinition = "CLOB") private String data; + /** + * Default constructor that just initializes the internal data structures. + */ public CallAction() { this.headers = new HashMap<>(); this.cookies = new HashMap<>(); @@ -221,6 +242,9 @@ public void setData(String data) { @Override public ExecuteResult execute(WebServiceConnector target) { try { + LOGGER.info("Do REST request '" + method + " " + url + "' " + + "(ignoreFailure : " + ignoreFailure + ", negated: " + negated + ")."); + doRequest(target); return getSuccessOutput(); } catch (Exception e) { @@ -248,7 +272,7 @@ private void doRequest(WebServiceConnector target) { getCookiesWithVariableValues()); break; default: - LOGGER.info("tried to make a call to a REST API with an unknown method '" + method.name() + "'."); + LOGGER.warn("tried to make a call to a REST API with an unknown method '" + method.name() + "'."); } } diff --git a/main/src/main/java/de/learnlib/alex/actions/RESTSymbolActions/CheckAttributeExistsAction.java b/main/src/main/java/de/learnlib/alex/actions/RESTSymbolActions/CheckAttributeExistsAction.java index c5763d649..47fcc4b27 100644 --- a/main/src/main/java/de/learnlib/alex/actions/RESTSymbolActions/CheckAttributeExistsAction.java +++ b/main/src/main/java/de/learnlib/alex/actions/RESTSymbolActions/CheckAttributeExistsAction.java @@ -1,3 +1,19 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package de.learnlib.alex.actions.RESTSymbolActions; import com.fasterxml.jackson.annotation.JsonIgnore; diff --git a/main/src/main/java/de/learnlib/alex/actions/RESTSymbolActions/CheckAttributeTypeAction.java b/main/src/main/java/de/learnlib/alex/actions/RESTSymbolActions/CheckAttributeTypeAction.java index 7a712a14e..c5514d4d3 100644 --- a/main/src/main/java/de/learnlib/alex/actions/RESTSymbolActions/CheckAttributeTypeAction.java +++ b/main/src/main/java/de/learnlib/alex/actions/RESTSymbolActions/CheckAttributeTypeAction.java @@ -1,3 +1,19 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package de.learnlib.alex.actions.RESTSymbolActions; import com.fasterxml.jackson.annotation.JsonIgnore; diff --git a/main/src/main/java/de/learnlib/alex/actions/RESTSymbolActions/CheckAttributeValueAction.java b/main/src/main/java/de/learnlib/alex/actions/RESTSymbolActions/CheckAttributeValueAction.java index 482b30375..af5803161 100644 --- a/main/src/main/java/de/learnlib/alex/actions/RESTSymbolActions/CheckAttributeValueAction.java +++ b/main/src/main/java/de/learnlib/alex/actions/RESTSymbolActions/CheckAttributeValueAction.java @@ -1,3 +1,19 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package de.learnlib.alex.actions.RESTSymbolActions; import com.fasterxml.jackson.annotation.JsonIgnore; diff --git a/main/src/main/java/de/learnlib/alex/actions/RESTSymbolActions/CheckHeaderFieldAction.java b/main/src/main/java/de/learnlib/alex/actions/RESTSymbolActions/CheckHeaderFieldAction.java index 7429d8b6c..40277f4a5 100644 --- a/main/src/main/java/de/learnlib/alex/actions/RESTSymbolActions/CheckHeaderFieldAction.java +++ b/main/src/main/java/de/learnlib/alex/actions/RESTSymbolActions/CheckHeaderFieldAction.java @@ -1,3 +1,19 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package de.learnlib.alex.actions.RESTSymbolActions; import com.fasterxml.jackson.annotation.JsonIgnore; diff --git a/main/src/main/java/de/learnlib/alex/actions/RESTSymbolActions/CheckStatusAction.java b/main/src/main/java/de/learnlib/alex/actions/RESTSymbolActions/CheckStatusAction.java index 2e2957327..7bad87813 100644 --- a/main/src/main/java/de/learnlib/alex/actions/RESTSymbolActions/CheckStatusAction.java +++ b/main/src/main/java/de/learnlib/alex/actions/RESTSymbolActions/CheckStatusAction.java @@ -1,8 +1,26 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package de.learnlib.alex.actions.RESTSymbolActions; import com.fasterxml.jackson.annotation.JsonTypeName; import de.learnlib.alex.core.entities.ExecuteResult; import de.learnlib.alex.core.learner.connectors.WebServiceConnector; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import javax.persistence.DiscriminatorValue; import javax.persistence.Entity; @@ -18,6 +36,9 @@ public class CheckStatusAction extends RESTSymbolAction { /** to be serializable. */ private static final long serialVersionUID = -4444604521120530087L; + /** Use the learner logger. */ + private static final Logger LOGGER = LogManager.getLogger("learner"); + /** The status code to check. */ private int status; @@ -42,7 +63,11 @@ public void setStatus(int status) { @Override public ExecuteResult execute(WebServiceConnector target) { - if (status == target.getStatus()) { + int returnedStatus = target.getStatus(); + LOGGER.info("check if the returned status code '" + returnedStatus + " is equal to '" + status + "' " + + "(ignoreFailure : " + ignoreFailure + ", negated: " + negated + ")."); + + if (this.status == returnedStatus) { return getSuccessOutput(); } else { return getFailedOutput(); diff --git a/main/src/main/java/de/learnlib/alex/actions/RESTSymbolActions/CheckTextRestAction.java b/main/src/main/java/de/learnlib/alex/actions/RESTSymbolActions/CheckTextRestAction.java index ee1f585ff..9646ffa6c 100644 --- a/main/src/main/java/de/learnlib/alex/actions/RESTSymbolActions/CheckTextRestAction.java +++ b/main/src/main/java/de/learnlib/alex/actions/RESTSymbolActions/CheckTextRestAction.java @@ -1,3 +1,19 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package de.learnlib.alex.actions.RESTSymbolActions; import com.fasterxml.jackson.annotation.JsonIgnore; diff --git a/main/src/main/java/de/learnlib/alex/actions/RESTSymbolActions/RESTSymbolAction.java b/main/src/main/java/de/learnlib/alex/actions/RESTSymbolActions/RESTSymbolAction.java index 71a10c421..bce97aa06 100644 --- a/main/src/main/java/de/learnlib/alex/actions/RESTSymbolActions/RESTSymbolAction.java +++ b/main/src/main/java/de/learnlib/alex/actions/RESTSymbolActions/RESTSymbolAction.java @@ -1,3 +1,19 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package de.learnlib.alex.actions.RESTSymbolActions; import com.fasterxml.jackson.annotation.JsonTypeName; diff --git a/main/src/main/java/de/learnlib/alex/actions/RESTSymbolActions/package-info.java b/main/src/main/java/de/learnlib/alex/actions/RESTSymbolActions/package-info.java index e416f47b8..a80cddf14 100644 --- a/main/src/main/java/de/learnlib/alex/actions/RESTSymbolActions/package-info.java +++ b/main/src/main/java/de/learnlib/alex/actions/RESTSymbolActions/package-info.java @@ -1,3 +1,19 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + /** * This package contains all concrete implementations around REST Symbols. */ diff --git a/main/src/main/java/de/learnlib/alex/actions/StoreSymbolActions/AssertCounterAction.java b/main/src/main/java/de/learnlib/alex/actions/StoreSymbolActions/AssertCounterAction.java index 69111dd46..662c16c36 100644 --- a/main/src/main/java/de/learnlib/alex/actions/StoreSymbolActions/AssertCounterAction.java +++ b/main/src/main/java/de/learnlib/alex/actions/StoreSymbolActions/AssertCounterAction.java @@ -1,5 +1,22 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package de.learnlib.alex.actions.StoreSymbolActions; +import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonTypeName; import de.learnlib.alex.core.entities.ExecuteResult; import de.learnlib.alex.core.entities.SymbolAction; @@ -12,18 +29,56 @@ import javax.validation.constraints.NotNull; /** - * Action to assert the value of a counter + * Action to assert the value of a counter. */ @Entity @DiscriminatorValue("assertCounter") @JsonTypeName("assertCounter") public class AssertCounterAction extends SymbolAction { - // assert types to mimic <, <=, ==, >=, > in this order - private enum Operator { - LESS_THAN, LESS_OR_EQUAL, EQUAL, GREATER_OR_EQUAL, GREATER_THAN + /** + * Assert types to mimic the different operators. + */ + public enum Operator { + /** '<'. */ + LESS_THAN, + + /** '<='. */ + LESS_OR_EQUAL, + + /** '=='. */ + EQUAL, + + /** '>='. */ + GREATER_OR_EQUAL, + + /** '>'. */ + GREATER_THAN; + + /** + * Parser function to handle the enum names case insensitive. + * + * @param name + * The enum name. + * @return The corresponding CookieType. + * @throws IllegalArgumentException + * If the name could not be parsed. + */ + @JsonCreator + public static Operator fromString(String name) throws IllegalArgumentException { + return Operator.valueOf(name.toUpperCase()); + } + + @Override + public String toString() { + return name().toLowerCase(); + } + } + /** to be serializable. */ + private static final long serialVersionUID = -8210218030257177422L; + /** * The name of the variable to assert. */ @@ -37,15 +92,57 @@ private enum Operator { private Integer value; /** - * The method to compare the counterVariable value with the one given + * The method to compare the counterVariable value with the one given. */ @NotNull private Operator operator; + /** + * @return The name of the counter that will be compared. + */ + public String getName() { + return name; + } + + /** + * @param name The new name of the counter to compare. + */ + public void setName(String name) { + this.name = name; + } + + /** + * @return The value that the counter will be compared with. + */ + public Integer getValue() { + return value; + } + + /** + * @param value The new value to compare the counter with. + */ + public void setValue(Integer value) { + this.value = value; + } + + /** + * @return The current operator to compare the counter with the given value. + */ + public Operator getOperator() { + return operator; + } + + /** + * @param assertMethod Set the new operator to use to compare the counter with the given value. + */ + public void setOperator(Operator assertMethod) { + this.operator = assertMethod; + } + @Override protected ExecuteResult execute(ConnectorManager connector) { CounterStoreConnector storeConnector = connector.getConnector(CounterStoreConnector.class); - Integer counterValue = storeConnector.get(project.getId(), name); + Integer counterValue = storeConnector.get(getUser().getId(), project.getId(), name); boolean result; switch (operator) { @@ -69,32 +166,11 @@ protected ExecuteResult execute(ConnectorManager connector) { break; } - return result ? getSuccessOutput() : getFailedOutput(); - } - - // auto generated getter & setter - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public Integer getValue() { - return value; - } - - public void setValue(Integer value) { - this.value = value; - } - - public Operator getOperator() { - return operator; + if (result) { + return getSuccessOutput(); + } else { + return getFailedOutput(); + } } - public void setOperator(Operator assertMethod) { - this.operator = assertMethod; - } } diff --git a/main/src/main/java/de/learnlib/alex/actions/StoreSymbolActions/AssertVariableAction.java b/main/src/main/java/de/learnlib/alex/actions/StoreSymbolActions/AssertVariableAction.java index a8b42b994..36c150581 100644 --- a/main/src/main/java/de/learnlib/alex/actions/StoreSymbolActions/AssertVariableAction.java +++ b/main/src/main/java/de/learnlib/alex/actions/StoreSymbolActions/AssertVariableAction.java @@ -1,3 +1,19 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package de.learnlib.alex.actions.StoreSymbolActions; import com.fasterxml.jackson.annotation.JsonTypeName; @@ -12,13 +28,16 @@ import javax.validation.constraints.NotNull; /** - * Action to assert the equality of the content of a variable with a given string + * Action to assert the equality of the content of a variable with a given string. */ @Entity @DiscriminatorValue("assertVariable") @JsonTypeName("assertVariable") public class AssertVariableAction extends SymbolAction { + /** to be serializable. */ + private static final long serialVersionUID = 6363724455992504221L; + /** * The name of the variable to assert. */ @@ -32,45 +51,70 @@ public class AssertVariableAction extends SymbolAction { private String value; /** - * Whether the value of the variable is matched against a regular expression + * Whether the value of the variable is matched against a regular expression. */ private boolean regexp; - @Override - protected ExecuteResult execute(ConnectorManager connector) { - VariableStoreConnector storeConnector = connector.getConnector(VariableStoreConnector.class); - String variableValue = storeConnector.get(name); - - if (regexp) { - return variableValue.matches(value) ? getSuccessOutput() : getFailedOutput(); - } else { - return variableValue.equals(value) ? getSuccessOutput() : getFailedOutput(); - } - } - - // auto generated getter & setter - + /** + * @return The name of the variable to assert. + */ public String getName() { return name; } + /** + * @param name The new name of the variable to assert. + */ public void setName(String name) { this.name = name; } + /** + * @return The value to check the variable against. + */ public String getValue() { return value; } + /** + * @param value The new vlue to check the variable against. + */ public void setValue(String value) { this.value = value; } + /** + * @return Treat the value as regexp? + */ public boolean isRegexp() { return regexp; } + /** + * @param regexp True, if the value is a regular expression; false otherwise. + */ public void setRegexp(boolean regexp) { this.regexp = regexp; } + + @Override + protected ExecuteResult execute(ConnectorManager connector) { + VariableStoreConnector storeConnector = connector.getConnector(VariableStoreConnector.class); + String variableValue = storeConnector.get(name); + + if (regexp) { + if (variableValue.matches(value)) { + return getSuccessOutput(); + } else { + return getFailedOutput(); + } + } else { + if (variableValue.equals(value)) { + return getSuccessOutput(); + } else { + return getFailedOutput(); + } + } + } + } diff --git a/main/src/main/java/de/learnlib/alex/actions/StoreSymbolActions/IncrementCounterAction.java b/main/src/main/java/de/learnlib/alex/actions/StoreSymbolActions/IncrementCounterAction.java index 4a0bab43b..07ad11327 100644 --- a/main/src/main/java/de/learnlib/alex/actions/StoreSymbolActions/IncrementCounterAction.java +++ b/main/src/main/java/de/learnlib/alex/actions/StoreSymbolActions/IncrementCounterAction.java @@ -1,3 +1,19 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package de.learnlib.alex.actions.StoreSymbolActions; import com.fasterxml.jackson.annotation.JsonTypeName; @@ -44,12 +60,7 @@ public void setName(String name) { @Override public ExecuteResult execute(ConnectorManager connector) { CounterStoreConnector storeConnector = connector.getConnector(CounterStoreConnector.class); - try { - storeConnector.increment(project.getId(), name); - return getSuccessOutput(); - } catch (IllegalStateException e) { - return getFailedOutput(); - } - + storeConnector.increment(user.getId(), project.getId(), name); + return getSuccessOutput(); } } diff --git a/main/src/main/java/de/learnlib/alex/actions/StoreSymbolActions/SetCounterAction.java b/main/src/main/java/de/learnlib/alex/actions/StoreSymbolActions/SetCounterAction.java index 9430aeed9..758e850a7 100644 --- a/main/src/main/java/de/learnlib/alex/actions/StoreSymbolActions/SetCounterAction.java +++ b/main/src/main/java/de/learnlib/alex/actions/StoreSymbolActions/SetCounterAction.java @@ -1,3 +1,19 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package de.learnlib.alex.actions.StoreSymbolActions; import com.fasterxml.jackson.annotation.JsonTypeName; @@ -19,6 +35,9 @@ @JsonTypeName("setCounter") public class SetCounterAction extends SymbolAction { + /** to be serializable. */ + private static final long serialVersionUID = -6023597222318880440L; + /** The name of the counter to set a new value to. */ @NotBlank private String name; @@ -27,18 +46,30 @@ public class SetCounterAction extends SymbolAction { @NotNull private Integer counterValue; + /** + * @return The name of the counter to set. + */ public String getName() { return name; } + /** + * @param name The new name of the counter to set. + */ public void setName(String name) { this.name = name; } + /** + * @return The value to set the counter to. + */ public Integer getValue() { return counterValue; } + /** + * @param value The new value to set the counter to. + */ public void setValue(Integer value) { this.counterValue = value; } @@ -46,11 +77,7 @@ public void setValue(Integer value) { @Override public ExecuteResult execute(ConnectorManager connector) { CounterStoreConnector storeConnector = connector.getConnector(CounterStoreConnector.class); - try { - storeConnector.set(project.getId(), name, counterValue); - return getSuccessOutput(); - } catch (IllegalStateException e) { - return getFailedOutput(); - } + storeConnector.set(getUser().getId(), project.getId(), name, counterValue); + return getSuccessOutput(); } } diff --git a/main/src/main/java/de/learnlib/alex/actions/StoreSymbolActions/SetVariableAction.java b/main/src/main/java/de/learnlib/alex/actions/StoreSymbolActions/SetVariableAction.java index ab5eca598..952d6ffb3 100644 --- a/main/src/main/java/de/learnlib/alex/actions/StoreSymbolActions/SetVariableAction.java +++ b/main/src/main/java/de/learnlib/alex/actions/StoreSymbolActions/SetVariableAction.java @@ -1,3 +1,19 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package de.learnlib.alex.actions.StoreSymbolActions; import com.fasterxml.jackson.annotation.JsonTypeName; @@ -19,6 +35,9 @@ @JsonTypeName("setVariable") public class SetVariableAction extends SymbolAction { + /** to be serializable. */ + private static final long serialVersionUID = 1935478771410953466L; + /** The name of the variable to set a new value to. */ @NotBlank protected String name; @@ -27,18 +46,30 @@ public class SetVariableAction extends SymbolAction { @NotNull protected String value; + /** + * @return The name of the variable to set. + */ public String getName() { return name; } + /** + * @param name The new name of the variable to set. + */ public void setName(String name) { this.name = name; } + /** + * @return The value to set the variable to. + */ public String getValue() { return value; } + /** + * @param value The new value to set the variable to. + */ public void setValue(String value) { this.value = value; } @@ -46,11 +77,7 @@ public void setValue(String value) { @Override public ExecuteResult execute(ConnectorManager connector) { VariableStoreConnector storeConnector = connector.getConnector(VariableStoreConnector.class); - try { - storeConnector.set(name, value); - return getSuccessOutput(); - } catch (IllegalStateException e) { - return getFailedOutput(); - } + storeConnector.set(name, value); + return getSuccessOutput(); } } diff --git a/main/src/main/java/de/learnlib/alex/actions/StoreSymbolActions/SetVariableByCookieAction.java b/main/src/main/java/de/learnlib/alex/actions/StoreSymbolActions/SetVariableByCookieAction.java index 778649bd7..94cd7804a 100644 --- a/main/src/main/java/de/learnlib/alex/actions/StoreSymbolActions/SetVariableByCookieAction.java +++ b/main/src/main/java/de/learnlib/alex/actions/StoreSymbolActions/SetVariableByCookieAction.java @@ -1,5 +1,22 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package de.learnlib.alex.actions.StoreSymbolActions; +import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonTypeName; import de.learnlib.alex.core.entities.ExecuteResult; import de.learnlib.alex.core.learner.connectors.ConnectorManager; @@ -8,34 +25,75 @@ import de.learnlib.alex.core.learner.connectors.WebSiteConnector; import org.openqa.selenium.Cookie; import org.openqa.selenium.NoSuchElementException; +import org.openqa.selenium.WebDriver; import javax.persistence.DiscriminatorValue; import javax.persistence.Entity; import javax.ws.rs.core.NewCookie; import java.util.Map; +/** + * Action to set the value of a variable based on a response cookie. + */ @Entity @DiscriminatorValue("setVariableByCookie") @JsonTypeName("setVariableByCookie") public class SetVariableByCookieAction extends SetVariableAction { - private enum CookieType { - WEB, REST + /** + * Enum to differentiate web & REST cookies. + */ + public enum CookieType { + + /** Cookies from or to a web interface. */ + WEB, + + /** Cookies from or to an REST interface. */ + REST; + + /** + * Parser function to handle the enum names case insensitive. + * + * @param name + * The enum name. + * @return The corresponding CookieType. + * @throws IllegalArgumentException + * If the name could not be parsed. + */ + @JsonCreator + public static CookieType fromString(String name) throws IllegalArgumentException { + return CookieType.valueOf(name.toUpperCase()); + } + + @Override + public String toString() { + return name().toLowerCase(); + } + } + /** to be serializable. */ + private static final long serialVersionUID = 5093001294341313128L; + /** - * The type of the cookie. Either by selenium cookie or from a http request + * The type of the cookie. Either by selenium cookie or from a http request. * * @requiredField */ private CookieType cookieType; + /** + * @return The type of the cookie. + */ public CookieType getCookieType() { return cookieType; } - public void setCookieType(CookieType t) { - cookieType = t; + /** + * @param type The new type of the cookie. + */ + public void setCookieType(CookieType type) { + cookieType = type; } @Override @@ -45,25 +103,27 @@ public ExecuteResult execute(ConnectorManager connector) { WebSiteConnector webSiteConnector = connector.getConnector(WebSiteConnector.class); try { - String value = null; + String cookieValue = null; if (cookieType == CookieType.WEB) { - Cookie cookie = webSiteConnector.getDriver().manage().getCookieNamed(value); + WebDriver driver = webSiteConnector.getDriver(); + WebDriver.Options manage = driver.manage(); + Cookie cookie = manage.getCookieNamed(value); if (cookie != null) { - value = cookie.getValue(); + cookieValue = cookie.getValue(); } } else if (cookieType == CookieType.REST) { Map cookies = webServiceConnector.getCookies(); javax.ws.rs.core.Cookie cookie = cookies.get(value); if (cookie != null) { - value = cookies.get(value).getValue(); + cookieValue = cookie.getValue(); } } else { return getFailedOutput(); } - if (value != null) { - storeConnector.set(name, value); + if (cookieValue != null) { + storeConnector.set(name, cookieValue); return getSuccessOutput(); } else { return getFailedOutput(); diff --git a/main/src/main/java/de/learnlib/alex/actions/StoreSymbolActions/SetVariableByHTMLElementAction.java b/main/src/main/java/de/learnlib/alex/actions/StoreSymbolActions/SetVariableByHTMLElementAction.java index c2760b692..b17fedc9d 100644 --- a/main/src/main/java/de/learnlib/alex/actions/StoreSymbolActions/SetVariableByHTMLElementAction.java +++ b/main/src/main/java/de/learnlib/alex/actions/StoreSymbolActions/SetVariableByHTMLElementAction.java @@ -1,3 +1,19 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package de.learnlib.alex.actions.StoreSymbolActions; import com.fasterxml.jackson.annotation.JsonTypeName; @@ -5,6 +21,8 @@ import de.learnlib.alex.core.learner.connectors.ConnectorManager; import de.learnlib.alex.core.learner.connectors.VariableStoreConnector; import de.learnlib.alex.core.learner.connectors.WebSiteConnector; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.openqa.selenium.NoSuchElementException; import javax.persistence.DiscriminatorValue; @@ -18,6 +36,12 @@ @JsonTypeName("setVariableByHTML") public class SetVariableByHTMLElementAction extends SetVariableAction { + /** to be serializable. */ + private static final long serialVersionUID = -7654754471208209824L; + + /** Use the learner logger. */ + private static final Logger LOGGER = LogManager.getLogger("learner"); + @Override public ExecuteResult execute(ConnectorManager connector) { VariableStoreConnector storeConnector = connector.getConnector(VariableStoreConnector.class); @@ -27,7 +51,8 @@ public ExecuteResult execute(ConnectorManager connector) { String valueInTheNode = webSiteConnector.getElement(value).getText(); storeConnector.set(name, valueInTheNode); return getSuccessOutput(); - } catch (IllegalStateException | NoSuchElementException e) { + } catch (NoSuchElementException e) { + LOGGER.info("Could not set the variable '" + name + "' to the value of the HTML node '" + value + "'."); return getFailedOutput(); } } diff --git a/main/src/main/java/de/learnlib/alex/actions/StoreSymbolActions/SetVariableByJSONAttributeAction.java b/main/src/main/java/de/learnlib/alex/actions/StoreSymbolActions/SetVariableByJSONAttributeAction.java index 5adcdae41..382fe17b3 100644 --- a/main/src/main/java/de/learnlib/alex/actions/StoreSymbolActions/SetVariableByJSONAttributeAction.java +++ b/main/src/main/java/de/learnlib/alex/actions/StoreSymbolActions/SetVariableByJSONAttributeAction.java @@ -1,14 +1,34 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package de.learnlib.alex.actions.StoreSymbolActions; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonTypeName; import de.learnlib.alex.core.entities.ExecuteResult; import de.learnlib.alex.core.learner.connectors.ConnectorManager; import de.learnlib.alex.core.learner.connectors.VariableStoreConnector; import de.learnlib.alex.core.learner.connectors.WebServiceConnector; import de.learnlib.alex.utils.JSONHelpers; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import javax.persistence.DiscriminatorValue; import javax.persistence.Entity; +import javax.persistence.Transient; /** * Action to set a variable to a value received from an element of the current (JSON) body. @@ -18,6 +38,11 @@ @JsonTypeName("setVariableByJSON") public class SetVariableByJSONAttributeAction extends SetVariableAction { + /** Use the learner logger. */ + @Transient + @JsonIgnore + private static final Logger LOGGER = LogManager.getLogger("learner"); + @Override public ExecuteResult execute(ConnectorManager connector) { VariableStoreConnector storeConnector = connector.getConnector(VariableStoreConnector.class); @@ -25,7 +50,11 @@ public ExecuteResult execute(ConnectorManager connector) { String body = webServiceConnector.getBody(); String valueInTheBody = JSONHelpers.getAttributeValue(body, value); + if (valueInTheBody == null) { + LOGGER.info("Could not set the variable '" + name + "' to the value of the " + + "JSON attribute '" + value + "' in the body '" + body + "' " + + "(ignoreFailure : " + ignoreFailure + ", negated: " + negated + ")."); return getFailedOutput(); } diff --git a/main/src/main/java/de/learnlib/alex/actions/StoreSymbolActions/SetVariableByNodeAttributeAction.java b/main/src/main/java/de/learnlib/alex/actions/StoreSymbolActions/SetVariableByNodeAttributeAction.java new file mode 100644 index 000000000..861a6e46c --- /dev/null +++ b/main/src/main/java/de/learnlib/alex/actions/StoreSymbolActions/SetVariableByNodeAttributeAction.java @@ -0,0 +1,125 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package de.learnlib.alex.actions.StoreSymbolActions; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonTypeName; +import de.learnlib.alex.core.entities.ExecuteResult; +import de.learnlib.alex.core.entities.SymbolAction; +import de.learnlib.alex.core.learner.connectors.ConnectorManager; +import de.learnlib.alex.core.learner.connectors.VariableStoreConnector; +import de.learnlib.alex.core.learner.connectors.WebSiteConnector; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.hibernate.validator.constraints.NotBlank; +import org.openqa.selenium.NoSuchElementException; + +import javax.persistence.Column; +import javax.persistence.DiscriminatorValue; +import javax.persistence.Entity; +import javax.persistence.Transient; +import javax.validation.constraints.NotNull; + +/** + * Action to set a variable to the value of an attribute of an HTML node. + */ +@Entity +@DiscriminatorValue("setVariableByNodeAttribute") +@JsonTypeName("setVariableByNodeAttribute") +public class SetVariableByNodeAttributeAction extends SymbolAction { + + /** to be serializable. */ + private static final long serialVersionUID = 8998187003156355834L; + + /** Use the learner logger. */ + @Transient + @JsonIgnore + private static final Logger LOGGER = LogManager.getLogger("learner"); + + /** The name of the variable. */ + @NotBlank + protected String name; + + /** The node to look for. */ + @NotNull + @Column(columnDefinition = "CLOB") + protected String node; + + /** The attribute name of the node to look for. */ + @NotNull + protected String attribute; + + /** + * @return The name of the variable to set. + */ + public String getName() { + return name; + } + + /** + * @param name The name of the new variable to set. + */ + public void setName(String name) { + this.name = name; + } + + /** + * @return The node to get the attribute from. + */ + public String getNode() { + return node; + } + + /** + * @param node The new identifier for the node to get the attribute from. + */ + public void setNode(String node) { + this.node = node; + } + + /** + * @return The identifier of the attribute to get the value from. + */ + public String getAttribute() { + return attribute; + } + + /** + * @param attribute The identifier of the new attribute to get the value from. + */ + public void setAttribute(String attribute) { + this.attribute = attribute; + } + + @Override + protected ExecuteResult execute(ConnectorManager connector) { + VariableStoreConnector storeConnector = connector.getConnector(VariableStoreConnector.class); + WebSiteConnector webSiteConnector = connector.getConnector(WebSiteConnector.class); + + try { + String value = webSiteConnector.getElement(node).getAttribute(attribute); + storeConnector.set(name, value); + + return getSuccessOutput(); + } catch (NoSuchElementException e) { + LOGGER.info("Could not set the variable '" + name + "' to the value of the attribute " + + "of the HTML node '" + node + "'."); + return getFailedOutput(); + } + } + +} diff --git a/main/src/main/java/de/learnlib/alex/actions/StoreSymbolActions/package-info.java b/main/src/main/java/de/learnlib/alex/actions/StoreSymbolActions/package-info.java index 8bc6056b0..a191573f8 100644 --- a/main/src/main/java/de/learnlib/alex/actions/StoreSymbolActions/package-info.java +++ b/main/src/main/java/de/learnlib/alex/actions/StoreSymbolActions/package-info.java @@ -1,3 +1,19 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + /** * This package contains all implementations of actions that store data (counters & variables). */ diff --git a/main/src/main/java/de/learnlib/alex/actions/WaitAction.java b/main/src/main/java/de/learnlib/alex/actions/WaitAction.java index af053b8bd..dce72ea9d 100644 --- a/main/src/main/java/de/learnlib/alex/actions/WaitAction.java +++ b/main/src/main/java/de/learnlib/alex/actions/WaitAction.java @@ -1,3 +1,19 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package de.learnlib.alex.actions; import com.fasterxml.jackson.annotation.JsonTypeName; diff --git a/main/src/main/java/de/learnlib/alex/actions/WebSymbolActions/CheckNodeAction.java b/main/src/main/java/de/learnlib/alex/actions/WebSymbolActions/CheckNodeAction.java index 4a9a19ffc..502b104ca 100644 --- a/main/src/main/java/de/learnlib/alex/actions/WebSymbolActions/CheckNodeAction.java +++ b/main/src/main/java/de/learnlib/alex/actions/WebSymbolActions/CheckNodeAction.java @@ -1,3 +1,19 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package de.learnlib.alex.actions.WebSymbolActions; import com.fasterxml.jackson.annotation.JsonIgnore; diff --git a/main/src/main/java/de/learnlib/alex/actions/WebSymbolActions/CheckPageTitleAction.java b/main/src/main/java/de/learnlib/alex/actions/WebSymbolActions/CheckPageTitleAction.java index 472e10a3f..dad7144b9 100644 --- a/main/src/main/java/de/learnlib/alex/actions/WebSymbolActions/CheckPageTitleAction.java +++ b/main/src/main/java/de/learnlib/alex/actions/WebSymbolActions/CheckPageTitleAction.java @@ -1,3 +1,19 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package de.learnlib.alex.actions.WebSymbolActions; import com.fasterxml.jackson.annotation.JsonIgnore; @@ -12,16 +28,14 @@ import javax.persistence.Entity; /** - * Action to check the page title + * Action to check the page title. */ @Entity @DiscriminatorValue("web_checkPageTitle") @JsonTypeName("web_checkPageTitle") public class CheckPageTitleAction extends WebSymbolAction { - /** - * The title of the web page - */ + /** The title of the web page. */ @NotBlank private String title; @@ -31,14 +45,11 @@ public class CheckPageTitleAction extends WebSymbolAction { */ private boolean regexp; - @Override - public ExecuteResult execute(WebSiteConnector connector) { - WebDriver driver = connector.getDriver(); - if (SearchHelper.search(getTitleWithVariableValues(), driver.getTitle(), regexp)) { - return getSuccessOutput(); - } else { - return getFailedOutput(); - } + /** + * @return The title to search for (without replaced counters nor variables). + */ + public String getTitle() { + return title; } /** @@ -52,19 +63,35 @@ public String getTitleWithVariableValues() { return insertVariableValues(title); } - public String getTitle() { - return title; - } - + /** + * @param title The new title to search for. + */ public void setTitle(String title) { this.title = title; } + /** + * @return Is the title to search for a regexp? + */ public boolean isRegexp() { return regexp; } + /** + * @param regexp True if the title is a regexp; False otherwise. + */ public void setRegexp(boolean regexp) { this.regexp = regexp; } + + @Override + public ExecuteResult execute(WebSiteConnector connector) { + WebDriver driver = connector.getDriver(); + if (SearchHelper.search(getTitleWithVariableValues(), driver.getTitle(), regexp)) { + return getSuccessOutput(); + } else { + return getFailedOutput(); + } + } + } diff --git a/main/src/main/java/de/learnlib/alex/actions/WebSymbolActions/CheckTextWebAction.java b/main/src/main/java/de/learnlib/alex/actions/WebSymbolActions/CheckTextWebAction.java index 08f123246..b07db2f62 100644 --- a/main/src/main/java/de/learnlib/alex/actions/WebSymbolActions/CheckTextWebAction.java +++ b/main/src/main/java/de/learnlib/alex/actions/WebSymbolActions/CheckTextWebAction.java @@ -1,3 +1,19 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package de.learnlib.alex.actions.WebSymbolActions; import com.fasterxml.jackson.annotation.JsonIgnore; diff --git a/main/src/main/java/de/learnlib/alex/actions/WebSymbolActions/ClearAction.java b/main/src/main/java/de/learnlib/alex/actions/WebSymbolActions/ClearAction.java index 962c05898..660bef564 100644 --- a/main/src/main/java/de/learnlib/alex/actions/WebSymbolActions/ClearAction.java +++ b/main/src/main/java/de/learnlib/alex/actions/WebSymbolActions/ClearAction.java @@ -1,3 +1,19 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package de.learnlib.alex.actions.WebSymbolActions; import com.fasterxml.jackson.annotation.JsonIgnore; diff --git a/main/src/main/java/de/learnlib/alex/actions/WebSymbolActions/ClickAction.java b/main/src/main/java/de/learnlib/alex/actions/WebSymbolActions/ClickAction.java index 161f70dd7..5d0255166 100644 --- a/main/src/main/java/de/learnlib/alex/actions/WebSymbolActions/ClickAction.java +++ b/main/src/main/java/de/learnlib/alex/actions/WebSymbolActions/ClickAction.java @@ -1,3 +1,19 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package de.learnlib.alex.actions.WebSymbolActions; import com.fasterxml.jackson.annotation.JsonIgnore; diff --git a/main/src/main/java/de/learnlib/alex/actions/WebSymbolActions/ClickLinkAction.java b/main/src/main/java/de/learnlib/alex/actions/WebSymbolActions/ClickLinkAction.java index 9c068f126..e8cecc676 100644 --- a/main/src/main/java/de/learnlib/alex/actions/WebSymbolActions/ClickLinkAction.java +++ b/main/src/main/java/de/learnlib/alex/actions/WebSymbolActions/ClickLinkAction.java @@ -1,3 +1,19 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package de.learnlib.alex.actions.WebSymbolActions; import com.fasterxml.jackson.annotation.JsonIgnore; diff --git a/main/src/main/java/de/learnlib/alex/actions/WebSymbolActions/FillAction.java b/main/src/main/java/de/learnlib/alex/actions/WebSymbolActions/FillAction.java index 4f837c580..8615a64b6 100644 --- a/main/src/main/java/de/learnlib/alex/actions/WebSymbolActions/FillAction.java +++ b/main/src/main/java/de/learnlib/alex/actions/WebSymbolActions/FillAction.java @@ -1,3 +1,19 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package de.learnlib.alex.actions.WebSymbolActions; import com.fasterxml.jackson.annotation.JsonIgnore; diff --git a/main/src/main/java/de/learnlib/alex/actions/WebSymbolActions/GotoAction.java b/main/src/main/java/de/learnlib/alex/actions/WebSymbolActions/GotoAction.java index 904d4e62b..35c24d963 100644 --- a/main/src/main/java/de/learnlib/alex/actions/WebSymbolActions/GotoAction.java +++ b/main/src/main/java/de/learnlib/alex/actions/WebSymbolActions/GotoAction.java @@ -1,3 +1,19 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package de.learnlib.alex.actions.WebSymbolActions; import com.fasterxml.jackson.annotation.JsonIgnore; @@ -21,8 +37,8 @@ public class GotoAction extends WebSymbolAction { /** to be serializable. */ private static final long serialVersionUID = -9158530821188611940L; - /** Use the logger for the server part. */ - private static final Logger LOGGER = LogManager.getLogger("server"); + /** Use the learner logger. */ + private static final Logger LOGGER = LogManager.getLogger("learner"); /** The URL of the site. */ private String url; @@ -61,9 +77,10 @@ public void setUrl(String url) { public ExecuteResult execute(WebSiteConnector connector) { try { connector.get(getURLWithVariableValues()); + LOGGER.info("Could goto '" + url + "'."); return getSuccessOutput(); } catch (Exception e) { - LOGGER.info("Could not goto " + url, e); + LOGGER.info("Could not goto '" + url + "'.", e); return getFailedOutput(); } } diff --git a/main/src/main/java/de/learnlib/alex/actions/WebSymbolActions/SelectAction.java b/main/src/main/java/de/learnlib/alex/actions/WebSymbolActions/SelectAction.java index 08f358624..77621f153 100644 --- a/main/src/main/java/de/learnlib/alex/actions/WebSymbolActions/SelectAction.java +++ b/main/src/main/java/de/learnlib/alex/actions/WebSymbolActions/SelectAction.java @@ -1,5 +1,22 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package de.learnlib.alex.actions.WebSymbolActions; +import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonTypeName; import de.learnlib.alex.core.entities.ExecuteResult; import de.learnlib.alex.core.learner.connectors.WebSiteConnector; @@ -20,8 +37,38 @@ @JsonTypeName("web_select") public class SelectAction extends FillAction { - private enum SelectByType { - VALUE, TEXT, INDEX; + /** + * Enum to choose how to interact with the selection input. + */ + public enum SelectByType { + /** Select by the value attribute. */ + VALUE, + + /** Select by the option text. */ + TEXT, + + /** Select simply by using the index starting at 0. */ + INDEX; + + /** + * Parser function to handle the enum names case insensitive. + * + * @param name + * The enum name. + * @return The corresponding SelectByType. + * @throws IllegalArgumentException + * If the name could not be parsed. + */ + @JsonCreator + public static SelectByType fromString(String name) throws IllegalArgumentException { + return SelectByType.valueOf(name.toUpperCase()); + } + + @Override + public String toString() { + return name().toLowerCase(); + } + } /** @@ -31,6 +78,20 @@ private enum SelectByType { */ private SelectByType selectBy; + /** + * @return How to do the selection of the node. + */ + public SelectByType getSelectBy() { + return selectBy; + } + + /** + * @param selectBy The new method to select the value in the node. + */ + public void setSelectBy(SelectByType selectBy) { + this.selectBy = selectBy; + } + @Override public ExecuteResult execute(WebSiteConnector connector) { try { @@ -51,16 +112,9 @@ public ExecuteResult execute(WebSiteConnector connector) { break; } return getSuccessOutput(); - } catch (NoSuchElementException | UnexpectedTagNameException e) { + } catch (NoSuchElementException | NumberFormatException | UnexpectedTagNameException e) { return getFailedOutput(); } } - public SelectByType getSelectBy() { - return selectBy; - } - - public void setSelectBy(SelectByType selectBy) { - this.selectBy = selectBy; - } } diff --git a/main/src/main/java/de/learnlib/alex/actions/WebSymbolActions/SubmitAction.java b/main/src/main/java/de/learnlib/alex/actions/WebSymbolActions/SubmitAction.java index 6365c0a3a..4e84908ac 100644 --- a/main/src/main/java/de/learnlib/alex/actions/WebSymbolActions/SubmitAction.java +++ b/main/src/main/java/de/learnlib/alex/actions/WebSymbolActions/SubmitAction.java @@ -1,3 +1,19 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package de.learnlib.alex.actions.WebSymbolActions; import com.fasterxml.jackson.annotation.JsonIgnore; diff --git a/main/src/main/java/de/learnlib/alex/actions/WebSymbolActions/WaitForNodeAction.java b/main/src/main/java/de/learnlib/alex/actions/WebSymbolActions/WaitForNodeAction.java new file mode 100644 index 000000000..97c3a5279 --- /dev/null +++ b/main/src/main/java/de/learnlib/alex/actions/WebSymbolActions/WaitForNodeAction.java @@ -0,0 +1,190 @@ +package de.learnlib.alex.actions.WebSymbolActions; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonTypeName; +import de.learnlib.alex.core.entities.ExecuteResult; +import de.learnlib.alex.core.learner.connectors.WebSiteConnector; +import de.learnlib.alex.utils.CSSUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.hibernate.validator.constraints.NotBlank; +import org.openqa.selenium.By; +import org.openqa.selenium.TimeoutException; +import org.openqa.selenium.support.ui.ExpectedConditions; +import org.openqa.selenium.support.ui.WebDriverWait; + +import javax.persistence.Column; +import javax.persistence.DiscriminatorValue; +import javax.persistence.Entity; +import javax.validation.constraints.NotNull; + +/** + * Action to wait for the state of an element to change. + */ +@Entity +@DiscriminatorValue("web_waitForNode") +@JsonTypeName("web_waitForNode") +public class WaitForNodeAction extends WebSymbolAction { + + /** + * to be serializable. + */ + private static final long serialVersionUID = 4029222122474954117L; + + /** Use the learner logger. */ + private static final Logger LOGGER = LogManager.getLogger("learner"); + + /** + * Enumeration to specify the wait criterion. + */ + public enum WaitCriterion { + + /** + * If an element is attached to the DOM and visible. + */ + VISIBLE, + + /** + * If an element is attached to the DOM but not visible. + */ + INVISIBLE, + + /** + * If an element is added to the DOM tree. + */ + ADDED, + + /** + * If an element is removed from the DOM tree. + */ + REMOVED; + + /** + * Parser function to handle the enum names case insensitive. + * + * @param name + * The enum name. + * @return The corresponding WaitCriterion. + * @throws IllegalArgumentException + * If the name could not be parsed. + */ + @JsonCreator + public static WaitCriterion fromString(String name) throws IllegalArgumentException { + return WaitCriterion.valueOf(name.toUpperCase()); + } + + @Override + public String toString() { + return name().toLowerCase(); + } + } + + /** + * The css selector of the element. + */ + @NotBlank + @Column(columnDefinition = "CLOB") + private String node; + + /** + * Which criterion is used to wait for the title. + */ + @NotNull + private WaitCriterion waitCriterion; + + /** + * How many seconds should be waited before the action fails. + */ + @NotNull + private long maxWaitTime; + + /** + * Get the selector of the element. + * + * @return The selector of the element + */ + public String getNode() { + return node; + } + + /** + * Set the selector of the element. + * + * @param node The selector of the element + */ + public void setNode(String node) { + this.node = node; + } + + /** + * Get the wait criterion. + * + * @return The wait criterion + */ + public WaitCriterion getWaitCriterion() { + return waitCriterion; + } + + /** + * Set the wait criterion. + * + * @param waitCriterion The wait criterion + */ + public void setWaitCriterion(WaitCriterion waitCriterion) { + this.waitCriterion = waitCriterion; + } + + /** + * Get the max amount of time in seconds to wait before the action fails. + * + * @return The max amount of time + */ + public long getMaxWaitTime() { + return maxWaitTime; + } + + /** + * Set the max amount of time in seconds to wait before the action fails. + * + * @param maxWaitTime The max amount of time in seconds + */ + public void setMaxWaitTime(long maxWaitTime) { + this.maxWaitTime = maxWaitTime; + } + + @Override + protected ExecuteResult execute(WebSiteConnector connector) { + if (maxWaitTime < 0) { + return getFailedOutput(); + } + + WebDriverWait wait = new WebDriverWait(connector.getDriver(), maxWaitTime); + String selector = CSSUtils.escapeSelector(insertVariableValues(node)); + + try { + switch (waitCriterion) { + case VISIBLE: + wait.until(ExpectedConditions.visibilityOf(connector.getElement(selector))); + break; + case INVISIBLE: + wait.until(ExpectedConditions.invisibilityOfElementLocated(By.cssSelector(selector))); + break; + case ADDED: + wait.until(ExpectedConditions.presenceOfElementLocated(By.cssSelector(selector))); + break; + case REMOVED: + wait.until(ExpectedConditions.stalenessOf(connector.getElement(selector))); + break; + default: + return getFailedOutput(); + } + return getSuccessOutput(); + } catch (TimeoutException e) { + LOGGER.info("Waiting on the node '" + selector + "' (criterion: '" + waitCriterion + "') timed out."); + return getFailedOutput(); + } + + } + + +} diff --git a/main/src/main/java/de/learnlib/alex/actions/WebSymbolActions/WaitForTitleAction.java b/main/src/main/java/de/learnlib/alex/actions/WebSymbolActions/WaitForTitleAction.java new file mode 100644 index 000000000..e7384708b --- /dev/null +++ b/main/src/main/java/de/learnlib/alex/actions/WebSymbolActions/WaitForTitleAction.java @@ -0,0 +1,169 @@ +package de.learnlib.alex.actions.WebSymbolActions; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonTypeName; +import de.learnlib.alex.core.entities.ExecuteResult; +import de.learnlib.alex.core.learner.connectors.WebSiteConnector; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.hibernate.validator.constraints.NotBlank; +import org.openqa.selenium.TimeoutException; +import org.openqa.selenium.support.ui.ExpectedConditions; +import org.openqa.selenium.support.ui.WebDriverWait; + +import javax.persistence.DiscriminatorValue; +import javax.persistence.Entity; +import javax.validation.constraints.NotNull; + +/** + * Action to wait for the title of a page to change. + */ +@Entity +@DiscriminatorValue("web_waitForTitle") +@JsonTypeName("web_waitForTitle") +public class WaitForTitleAction extends WebSymbolAction { + + /** + * to be serializable. + */ + private static final long serialVersionUID = -7416267361597106520L; + + /** Use the learner logger. */ + private static final Logger LOGGER = LogManager.getLogger("learner"); + + /** + * Enumeration to specify the wait criterion. + */ + public enum WaitCriterion { + /** + * If the title should be the value. + */ + IS, + + /** + * If the title should contain the value. + */ + CONTAINS; + + + /** + * Parser function to handle the enum names case insensitive. + * + * @param name + * The enum name. + * @return The corresponding WaitCriterion. + * @throws IllegalArgumentException + * If the name could not be parsed. + */ + @JsonCreator + public static WaitCriterion fromString(String name) throws IllegalArgumentException { + return WaitCriterion.valueOf(name.toUpperCase()); + } + + @Override + public String toString() { + return name().toLowerCase(); + } + } + + /** + * The value the title should match / contain. + */ + @NotBlank + private String value; + + /** + * Which criterion is used to wait for the title. + */ + @NotNull + private WaitCriterion waitCriterion; + + /** + * How many seconds should be waited before the action fails. + */ + @NotNull + private long maxWaitTime; + + /** + * Get the value for the title. + * + * @return The value for the title + */ + public String getValue() { + return value; + } + + /** + * Set the expected value of the title. + * + * @param value The expected value of the title + */ + public void setValue(String value) { + this.value = value; + } + + /** + * Get the wait criterion. + * + * @return The wait criterion + */ + public WaitCriterion getWaitCriterion() { + return waitCriterion; + } + + /** + * Set the wait criterion. + * + * @param waitCriterion The wait criterion + */ + public void setWaitCriterion(WaitCriterion waitCriterion) { + this.waitCriterion = waitCriterion; + } + + /** + * Get the max amount of time in seconds to wait before the action fails. + * + * @return The max amount of time + */ + public long getMaxWaitTime() { + return maxWaitTime; + } + + /** + * Set the max amount of time in seconds to wait before the action fails. + * + * @param maxWaitTime The max amount of time in seconds + */ + public void setMaxWaitTime(long maxWaitTime) { + this.maxWaitTime = maxWaitTime; + } + + @Override + protected ExecuteResult execute(WebSiteConnector connector) { + if (maxWaitTime < 0) { + return getFailedOutput(); + } + + WebDriverWait wait = new WebDriverWait(connector.getDriver(), maxWaitTime); + + try { + switch (waitCriterion) { + case IS: + wait.until(ExpectedConditions.titleIs(value)); + break; + case CONTAINS: + wait.until(ExpectedConditions.titleContains(value)); + break; + default: + return getFailedOutput(); + } + + return getSuccessOutput(); + } catch (TimeoutException e) { + LOGGER.info("Waiting on the title '" + value + "' (criterion: '" + waitCriterion + "') timed out. " + + "Last known title was '" + connector.getDriver().getTitle() + "'."); + return getFailedOutput(); + } + } + +} diff --git a/main/src/main/java/de/learnlib/alex/actions/WebSymbolActions/WebSymbolAction.java b/main/src/main/java/de/learnlib/alex/actions/WebSymbolActions/WebSymbolAction.java index a102a4c15..b5849e89f 100644 --- a/main/src/main/java/de/learnlib/alex/actions/WebSymbolActions/WebSymbolAction.java +++ b/main/src/main/java/de/learnlib/alex/actions/WebSymbolActions/WebSymbolAction.java @@ -1,3 +1,19 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package de.learnlib.alex.actions.WebSymbolActions; import com.fasterxml.jackson.annotation.JsonTypeName; diff --git a/main/src/main/java/de/learnlib/alex/actions/WebSymbolActions/package-info.java b/main/src/main/java/de/learnlib/alex/actions/WebSymbolActions/package-info.java index 67590e85d..8b7268833 100644 --- a/main/src/main/java/de/learnlib/alex/actions/WebSymbolActions/package-info.java +++ b/main/src/main/java/de/learnlib/alex/actions/WebSymbolActions/package-info.java @@ -1,3 +1,19 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + /** * This package contains all concrete implementations around REST Symbols. */ diff --git a/main/src/main/java/de/learnlib/alex/actions/package-info.java b/main/src/main/java/de/learnlib/alex/actions/package-info.java index 611e97030..54edd53f0 100644 --- a/main/src/main/java/de/learnlib/alex/actions/package-info.java +++ b/main/src/main/java/de/learnlib/alex/actions/package-info.java @@ -1,3 +1,19 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + /** * All the actions!! */ diff --git a/main/src/main/java/de/learnlib/alex/algorithms/DHC.java b/main/src/main/java/de/learnlib/alex/algorithms/DHC.java index cd0d530d7..312f4efda 100644 --- a/main/src/main/java/de/learnlib/alex/algorithms/DHC.java +++ b/main/src/main/java/de/learnlib/alex/algorithms/DHC.java @@ -1,3 +1,19 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package de.learnlib.alex.algorithms; import de.learnlib.alex.annotations.LearnAlgorithm; diff --git a/main/src/main/java/de/learnlib/alex/algorithms/DiscriminationTree.java b/main/src/main/java/de/learnlib/alex/algorithms/DiscriminationTree.java index 24405b227..2fe1a667b 100644 --- a/main/src/main/java/de/learnlib/alex/algorithms/DiscriminationTree.java +++ b/main/src/main/java/de/learnlib/alex/algorithms/DiscriminationTree.java @@ -1,3 +1,19 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package de.learnlib.alex.algorithms; import de.learnlib.alex.annotations.LearnAlgorithm; diff --git a/main/src/main/java/de/learnlib/alex/algorithms/LStar.java b/main/src/main/java/de/learnlib/alex/algorithms/LStar.java index 3a475d5f6..55de264c5 100644 --- a/main/src/main/java/de/learnlib/alex/algorithms/LStar.java +++ b/main/src/main/java/de/learnlib/alex/algorithms/LStar.java @@ -1,3 +1,19 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package de.learnlib.alex.algorithms; import de.learnlib.alex.annotations.LearnAlgorithm; diff --git a/main/src/main/java/de/learnlib/alex/algorithms/TTT.java b/main/src/main/java/de/learnlib/alex/algorithms/TTT.java index 20dbdb609..2b8369932 100644 --- a/main/src/main/java/de/learnlib/alex/algorithms/TTT.java +++ b/main/src/main/java/de/learnlib/alex/algorithms/TTT.java @@ -1,3 +1,19 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package de.learnlib.alex.algorithms; import de.learnlib.alex.annotations.LearnAlgorithm; diff --git a/main/src/main/java/de/learnlib/alex/algorithms/package-info.java b/main/src/main/java/de/learnlib/alex/algorithms/package-info.java index afcc11396..cc80ff6ce 100644 --- a/main/src/main/java/de/learnlib/alex/algorithms/package-info.java +++ b/main/src/main/java/de/learnlib/alex/algorithms/package-info.java @@ -1,3 +1,19 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + /** * This package contains the algorithms that ALEX will be able to use. */ diff --git a/main/src/main/java/de/learnlib/alex/core/dao/CounterDAO.java b/main/src/main/java/de/learnlib/alex/core/dao/CounterDAO.java index 4a7771bd3..832deafe6 100644 --- a/main/src/main/java/de/learnlib/alex/core/dao/CounterDAO.java +++ b/main/src/main/java/de/learnlib/alex/core/dao/CounterDAO.java @@ -1,3 +1,19 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package de.learnlib.alex.core.dao; import de.learnlib.alex.core.entities.Counter; @@ -27,17 +43,21 @@ public interface CounterDAO { /** * Get all counter of a project. * + * @param userId + * The user that owns the counters * @param projectId * The project of the counters. * @return A list of counters within the given project. * @throws NotFoundException * If no project with the given ID exists. */ - List getAll(Long projectId) throws NotFoundException; + List getAll(Long userId, Long projectId) throws NotFoundException; /** * Get a specific counter. * + * @param userId + * The user that owns the counter. * @param projectId * The project of the counter. * @param name @@ -46,7 +66,7 @@ public interface CounterDAO { * @throws NotFoundException * If the counter was not found. */ - Counter get(Long projectId, String name) throws NotFoundException; + Counter get(Long userId, Long projectId, String name) throws NotFoundException; /** * Update a counter. @@ -63,6 +83,8 @@ public interface CounterDAO { /** * Deletes counters. * + * @param userId + * The user that owns the counters * @param projectId * The project of the counter. * @param names @@ -70,6 +92,6 @@ public interface CounterDAO { * @throws NotFoundException * If the project or counter was not found. */ - void delete(Long projectId, String... names) throws NotFoundException; + void delete(Long userId, Long projectId, String... names) throws NotFoundException; } diff --git a/main/src/main/java/de/learnlib/alex/core/dao/CounterDAOImpl.java b/main/src/main/java/de/learnlib/alex/core/dao/CounterDAOImpl.java index 1122d9aec..187593114 100644 --- a/main/src/main/java/de/learnlib/alex/core/dao/CounterDAOImpl.java +++ b/main/src/main/java/de/learnlib/alex/core/dao/CounterDAOImpl.java @@ -1,3 +1,19 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package de.learnlib.alex.core.dao; import de.learnlib.alex.core.entities.Counter; @@ -7,14 +23,15 @@ import org.hibernate.HibernateException; import org.hibernate.Session; import org.hibernate.criterion.Restrictions; +import org.springframework.stereotype.Repository; import javax.validation.ValidationException; -import java.util.LinkedList; import java.util.List; /** * Implementation of a CounterDAO using Hibernate. */ +@Repository public class CounterDAOImpl implements CounterDAO { @Override @@ -22,8 +39,7 @@ public void create(Counter counter) throws ValidationException { HibernateUtil.beginTransaction(); Session session = HibernateUtil.getSession(); try { - Project project = (Project) session.load(Project.class, counter.getProjectId()); - project.getId(); + Project project = session.load(Project.class, counter.getProjectId()); counter.setProject(project); session.save(counter); @@ -37,29 +53,30 @@ public void create(Counter counter) throws ValidationException { } @Override - public List getAll(Long projectId) throws NotFoundException { + public List getAll(Long userId, Long projectId) throws NotFoundException { HibernateUtil.beginTransaction(); Session session = HibernateUtil.getSession(); - if (ProjectDAOImpl.isProjectIdInvalid(projectId)) { + if (session.get(Project.class, projectId) == null) { throw new NotFoundException("The project with the id " + projectId + " was not found."); } @SuppressWarnings("Should always return a list of Counters.") List result = session.createCriteria(Counter.class) - .add(Restrictions.eq("project.id", projectId)) - .list(); + .add(Restrictions.eq("project.id", projectId)) + .add(Restrictions.eq("user.id", userId)) + .list(); HibernateUtil.commitTransaction(); return result; } @Override - public Counter get(Long projectId, String name) throws NotFoundException { + public Counter get(Long userId, Long projectId, String name) throws NotFoundException { HibernateUtil.beginTransaction(); Session session = HibernateUtil.getSession(); try { - Counter result = get(session, projectId, name); + Counter result = get(session, userId, projectId, name); HibernateUtil.commitTransaction(); return result; @@ -69,20 +86,21 @@ public Counter get(Long projectId, String name) throws NotFoundException { } } - private Counter get(Session session, Long projectId, String name) throws NotFoundException { - if (ProjectDAOImpl.isProjectIdInvalid(projectId)) { + private Counter get(Session session, Long userId, Long projectId, String name) throws NotFoundException { + if (session.get(Project.class, projectId) == null) { throw new NotFoundException("The project with the id " + projectId + " was not found."); } @SuppressWarnings("Should always return a list of Counters.") Counter result = (Counter) session.createCriteria(Counter.class) - .add(Restrictions.eq("project.id", projectId)) - .add(Restrictions.eq("name", name)) - .uniqueResult(); + .add(Restrictions.eq("project.id", projectId)) + .add(Restrictions.eq("user.id", userId)) + .add(Restrictions.eq("name", name)) + .uniqueResult(); if (result == null) { throw new NotFoundException("Could not find the counter with the name '" + name - + "' in the project " + projectId + "!"); + + "' in the project " + projectId + "!"); } return result; @@ -94,7 +112,7 @@ public void update(Counter counter) throws NotFoundException, ValidationExceptio Session session = HibernateUtil.getSession(); try { - get(session, counter.getProjectId(), counter.getName()); // check if the counter exists + get(session, counter.getUserId(), counter.getProjectId(), counter.getName()); // check if the counter exists session.merge(counter); HibernateUtil.commitTransaction(); @@ -108,21 +126,23 @@ public void update(Counter counter) throws NotFoundException, ValidationExceptio } @Override - public void delete(Long projectId, String... names) throws NotFoundException { + public void delete(Long userId, Long projectId, String... names) throws NotFoundException { HibernateUtil.beginTransaction(); Session session = HibernateUtil.getSession(); - try { - List counters = new LinkedList<>(); - for (String name : names) { - counters.add(get(session, projectId, name)); - } + @SuppressWarnings("Should always return a list of Counters.") + List counters = session.createCriteria(Counter.class) + .add(Restrictions.in("name", names)) + .add(Restrictions.eq("project.id", projectId)) + .add(Restrictions.eq("user.id", userId)) + .list(); + if (names.length == counters.size()) { // all counters found -> delete them & success counters.forEach(session::delete); HibernateUtil.commitTransaction(); - } catch (NotFoundException e) { + } else { HibernateUtil.rollbackTransaction(); - throw e; + throw new NotFoundException("Could not delete the counter(s), becauser at least one does not exists!"); } } } diff --git a/main/src/main/java/de/learnlib/alex/core/dao/FileDAO.java b/main/src/main/java/de/learnlib/alex/core/dao/FileDAO.java index 925287d44..1a2e0b76a 100644 --- a/main/src/main/java/de/learnlib/alex/core/dao/FileDAO.java +++ b/main/src/main/java/de/learnlib/alex/core/dao/FileDAO.java @@ -1,3 +1,19 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package de.learnlib.alex.core.dao; import de.learnlib.alex.core.entities.UploadableFile; @@ -8,15 +24,70 @@ import java.io.InputStream; import java.util.List; +/** + * Interface to describe how Files are handled. + */ public interface FileDAO { - void create(Long projectId, InputStream uploadedInputStream, FormDataContentDisposition fileDetail) - throws IllegalArgumentException, IOException, IllegalStateException; + /** + * Put a new file into the file system. + * + * @param userId + * The user the file belongs to. + * @param projectId + * The id of the project that the file belongs to. + * @param uploadedInputStream + * The file as input stream. + * @param fileDetail + * Other file (meta) data. + * @throws IOException + * If something during the actual writing went wrong. + * @throws IllegalStateException + * If the file already exists or the destination directory is not a directory or otherwise blocked. + */ + void create(Long userId, Long projectId, InputStream uploadedInputStream, FormDataContentDisposition fileDetail) + throws IOException, IllegalStateException; - List getAll(Long projectId) throws NotFoundException; + /** + * Get a list of all fiels of a user within a project. + * + * @param userId + * The user to show the files of. + * @param projectId + * The project the files belong to. + * @return A List of Files. Can be empty. + * @throws NotFoundException + * If no files can be found. + */ + List getAll(Long userId, Long projectId) throws NotFoundException; - String getAbsoulteFilePath(Long projectId, String fileName) throws NotFoundException; + /** + * Get the absolute path to a file on the machine. + * + * @param userId + * The user the file belongs to. + * @param projectId + * The id of the project that the file belongs to. + * @param fileName + * The name of the file. + * @return The absolute path to the file on the actual machine. + * @throws NotFoundException + * If the file could not be found. + */ + String getAbsoluteFilePath(Long userId, Long projectId, String fileName) throws NotFoundException; - void delete(Long projectId, String fileName) throws NotFoundException; + /** + * Deletes a file. + * + * @param userId + * The user the file belongs to. + * @param projectId + * The id of the project that the file belongs to. + * @param fileName + * The name of the file to delete. + * @throws NotFoundException + * If the file could not be found. + */ + void delete(Long userId, Long projectId, String fileName) throws NotFoundException; } diff --git a/main/src/main/java/de/learnlib/alex/core/dao/FileDAOImpl.java b/main/src/main/java/de/learnlib/alex/core/dao/FileDAOImpl.java index 63748e758..9e2615c3a 100644 --- a/main/src/main/java/de/learnlib/alex/core/dao/FileDAOImpl.java +++ b/main/src/main/java/de/learnlib/alex/core/dao/FileDAOImpl.java @@ -1,8 +1,25 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package de.learnlib.alex.core.dao; import de.learnlib.alex.core.entities.UploadableFile; import de.learnlib.alex.exceptions.NotFoundException; import org.glassfish.jersey.media.multipart.FormDataContentDisposition; +import org.springframework.stereotype.Repository; import java.io.File; import java.io.FileOutputStream; @@ -13,10 +30,21 @@ import java.util.LinkedList; import java.util.List; +/** + * Simple implementation of a FileDAO. + */ +@Repository public class FileDAOImpl implements FileDAO { + /** The size of the output write buffer in bytes. */ + public static final int WRITE_BUFFER_SIZE = 1024; + + /** Path to the root of the upload directory. */ private java.nio.file.Path uploadedDirectoryBaseLocation; + /** + * Default consturtor that jsut initialises the internal data structures. + */ public FileDAOImpl() { this.uploadedDirectoryBaseLocation = Paths.get(System.getProperty("user.dir"), "uploads"); @@ -27,9 +55,11 @@ public FileDAOImpl() { } @Override - public void create(Long projectId, InputStream uploadedInputStream, FormDataContentDisposition fileDetail) - throws IllegalArgumentException, IOException, IllegalStateException { + public void create(Long userId, Long projectId, InputStream uploadedInputStream, + FormDataContentDisposition fileDetail) + throws IOException, IllegalStateException { java.nio.file.Path uploadedDirectoryLocation = Paths.get(uploadedDirectoryBaseLocation.toString(), + String.valueOf(userId), String.valueOf(projectId)); File uploadDirectory = uploadedDirectoryLocation.toFile(); @@ -38,7 +68,7 @@ public void create(Long projectId, InputStream uploadedInputStream, FormDataCont } if (!uploadDirectory.isDirectory()) { - throw new IllegalArgumentException("Could not find the right directory to upload the file."); + throw new IllegalStateException("Could not find the right directory to upload the file."); } java.nio.file.Path uploadedFileLocation = Paths.get(uploadedDirectoryLocation.toString(), @@ -53,8 +83,8 @@ public void create(Long projectId, InputStream uploadedInputStream, FormDataCont } @Override - public List getAll(Long projectId) throws NotFoundException { - File uploadDirectory = getUploadDirectory(projectId); + public List getAll(Long userId, Long projectId) throws NotFoundException { + File uploadDirectory = getUploadDirectory(userId, projectId); List files = new LinkedList<>(); for (File f : uploadDirectory.listFiles()) { @@ -65,12 +95,17 @@ public List getAll(Long projectId) throws NotFoundException { files.add(uploadableFile); } + if (files.isEmpty()) { + throw new NotFoundException("No files found for the User <" + userId + "> and " + + "the project <" + projectId + ">."); + } + return files; } @Override - public String getAbsoulteFilePath(Long projectId, String fileName) throws NotFoundException { - File uploadDirectory = getUploadDirectory(projectId); + public String getAbsoluteFilePath(Long userId, Long projectId, String fileName) throws NotFoundException { + File uploadDirectory = getUploadDirectory(userId, projectId); java.nio.file.Path uploadedFileLocation = Paths.get(uploadDirectory.getPath(), fileName); File file = uploadedFileLocation.toFile(); @@ -83,8 +118,8 @@ public String getAbsoulteFilePath(Long projectId, String fileName) throws NotFou } @Override - public void delete(Long projectId, String fileName) throws NotFoundException { - File uploadDirectory = getUploadDirectory(projectId); + public void delete(Long userId, Long projectId, String fileName) throws NotFoundException { + File uploadDirectory = getUploadDirectory(userId, projectId); java.nio.file.Path uploadedFileLocation = Paths.get(uploadDirectory.getPath(), fileName); File file = uploadedFileLocation.toFile(); @@ -96,14 +131,15 @@ public void delete(Long projectId, String fileName) throws NotFoundException { file.delete(); } - private File getUploadDirectory(Long projectId) throws NotFoundException { + private File getUploadDirectory(Long userId, Long projectId) throws NotFoundException { java.nio.file.Path uploadedDirectoryLocation = Paths.get(uploadedDirectoryBaseLocation.toString(), + String.valueOf(userId), String.valueOf(projectId)); File uploadDirectory = uploadedDirectoryLocation.toFile(); if (!uploadDirectory.exists() || !uploadDirectory.isDirectory()) { try { - uploadDirectory.mkdir(); + uploadDirectory.mkdirs(); } catch (SecurityException e) { throw new NotFoundException("Could not find the project directory you are looking for."); } @@ -115,16 +151,15 @@ private File getUploadDirectory(Long projectId) throws NotFoundException { // save uploaded file to new location private void writeToFile(InputStream uploadedInputStream, String uploadedFileLocation) throws IOException { - OutputStream out; - int read; - byte[] bytes = new byte[1024]; + try (OutputStream out = new FileOutputStream(new File(uploadedFileLocation))) { + int read; + byte[] bytes = new byte[WRITE_BUFFER_SIZE]; - out = new FileOutputStream(new File(uploadedFileLocation)); - while ((read = uploadedInputStream.read(bytes)) != -1) { - out.write(bytes, 0, read); + while ((read = uploadedInputStream.read(bytes)) != -1) { + out.write(bytes, 0, read); + } + out.flush(); } - out.flush(); - out.close(); } diff --git a/main/src/main/java/de/learnlib/alex/core/dao/LearnerResultDAO.java b/main/src/main/java/de/learnlib/alex/core/dao/LearnerResultDAO.java index 25a8a3a10..9f7caaace 100644 --- a/main/src/main/java/de/learnlib/alex/core/dao/LearnerResultDAO.java +++ b/main/src/main/java/de/learnlib/alex/core/dao/LearnerResultDAO.java @@ -1,6 +1,25 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package de.learnlib.alex.core.dao; import de.learnlib.alex.core.entities.LearnerResult; +import de.learnlib.alex.core.entities.LearnerResultStep; +import de.learnlib.alex.core.entities.LearnerResumeConfiguration; +import de.learnlib.alex.core.entities.User; import de.learnlib.alex.exceptions.NotFoundException; import javax.validation.ValidationException; @@ -22,101 +41,98 @@ public interface LearnerResultDAO { void create(LearnerResult learnerResult) throws ValidationException; /** - * Get a list of JSON data containing all the LearnerResults, that are the latest of any test run for a given - * Project. This LearnerResult are most likely the final results of each test run. + * Get a list of all the LearnerResults for a given + * Project. * + * @param userId + * The user of the LearnerResult * @param projectId * The project id of the test run. - * @return A list of LearnerResults as JSON data. + * @param includeSteps + * Should all steps be included? + * @return A list of LearnerResults. * @throws NotFoundException * If the project id was invalid. */ - List getAllAsJSON(Long projectId) throws NotFoundException; - - /** - * Get a list of JSON data containing all the steps of a given TestRun for a given Project. - * - * @param projectId - * The project id if the test run. - * @param testNo - * The test no. of the test run. - * @return A list of LearnerResults as JSON data. - * @throws NotFoundException - * If the project id or test no. was invalid. - */ - List getAllAsJSON(Long projectId, Long testNo) throws NotFoundException; + List getAll(Long userId, Long projectId, boolean includeSteps) throws NotFoundException; /** - * Get a list of lists of JSON data containing all the steps of a given TestRun for a given Project. + * Get a list of LearnResults with given testNos for a given Project. * + * @param userId + * The user of the LearnerResult * @param projectId * The project id if the test run. * @param testNos - * The list of test nos. of the test runs. - * @return A list of list containing LearnerResults as JSON data. + * The list of test nos. of the LearnResults. + * @param includeSteps + * Should all steps be included? + * @return A list of LearnerResults. * @throws NotFoundException * If the project id or test no. was invalid. */ - List> getAllAsJson(Long projectId, List testNos) throws NotFoundException; + List getAll(Long userId, Long projectId, Long[] testNos, boolean includeSteps) + throws NotFoundException; /** - * Get a the last / final LearnerResult of one test run. + * Get a single LearnResult. * + * @param userId + * The user of the LearnerResult * @param projectId * The project id if the test run. - * @param testRunNo - * The test no. of the test run. - * @return The LearnerResult you are looking for, if it exists. + * @param testNos + * The list of test nos. of the LearnResults. + * @param includeSteps + * Should all steps be included? + * @return The LearnResult you are looking for. * @throws NotFoundException - * If the project id or test no. was invalid. + * If the given LearnerResult was invalid. */ - LearnerResult get(Long projectId, Long testRunNo) throws NotFoundException; + LearnerResult get(Long userId, Long projectId, Long testNos, boolean includeSteps) throws NotFoundException; /** - * Get the latest LearnerResult of a given test run as JSON data, e.g. the final result. + * Create a new step for a LearnResult based on the latest step within the result. * - * @param projectId - * The project id of the test run. - * @param testNo - * The test no. of the test run. - * @return The latest LearnerResult, i.e. the one with the highest step no., for the given test run. - * @throws NotFoundException - * If the project id or test no. was invalid. + * @param result + * The result that the new step will be added to. + * @return A new step. + * @throws ValidationException + * If the requested result could not be found. */ - String getAsJSON(Long projectId, Long testNo) throws NotFoundException; + LearnerResultStep createStep(LearnerResult result) throws ValidationException; /** - * Get a specific LearnerResult as JSON data. + * Create a new step for a LearnResult based on the given configuration. * - * @param projectId - * The project id of the test run / LearnerResult. - * @param testNo - * The test non. of the test run / LearnerResult. - * @param stepNo - * The step no. of the test run / LearnerResult. - * @return The LearnerResult as JSON data. - * @throws NotFoundException - * If the project id, test no. or step no. was invalid. + * @param result + * The result that the new step will be added to. + * @param configuration + * The configuration to set the step up. + * @return A new step. + * @throws ValidationException + * If the given LearnerResult was invalid. */ - String getAsJSON(Long projectId, Long testNo, Long stepNo) throws NotFoundException; + LearnerResultStep createStep(LearnerResult result, LearnerResumeConfiguration configuration) + throws ValidationException; /** - * Update a given LearnResult. Update means here, to save a new LearnerResult with an increased step no. - * The previous steps of one test run should not change. - * This method must also verify that the given result is valid. + * Save / Update a step. * - * @param learnerResult - * The LearnerResult to update. + * @param result + * The result that the step is part of. + * @param step + * The step the should be saved / updated. * @throws ValidationException * If the given LearnerResult was invalid. - * @throws NotFoundException - * If the project id or test no. was invalid. */ - void update(LearnerResult learnerResult) throws NotFoundException, ValidationException; + void saveStep(LearnerResult result, LearnerResultStep step) throws ValidationException; /** * Remove a complete test run of a project. * + * @param user + * The user of the LearnerResult * @param projectId * The project id. * @param testNo @@ -124,5 +140,5 @@ public interface LearnerResultDAO { * @throws NotFoundException * If the project id or test no. was invalid. */ - void delete(Long projectId, Long... testNo) throws NotFoundException; + void delete(User user, Long projectId, Long... testNo) throws NotFoundException; } diff --git a/main/src/main/java/de/learnlib/alex/core/dao/LearnerResultDAOImpl.java b/main/src/main/java/de/learnlib/alex/core/dao/LearnerResultDAOImpl.java index 5d90ddb63..abb0bc8b3 100644 --- a/main/src/main/java/de/learnlib/alex/core/dao/LearnerResultDAOImpl.java +++ b/main/src/main/java/de/learnlib/alex/core/dao/LearnerResultDAOImpl.java @@ -1,28 +1,50 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package de.learnlib.alex.core.dao; import de.learnlib.alex.core.entities.LearnerResult; +import de.learnlib.alex.core.entities.LearnerResultStep; +import de.learnlib.alex.core.entities.LearnerResumeConfiguration; import de.learnlib.alex.core.entities.LearnerStatus; +import de.learnlib.alex.core.entities.Statistics; +import de.learnlib.alex.core.entities.User; +import de.learnlib.alex.core.learner.Learner; import de.learnlib.alex.exceptions.NotFoundException; import de.learnlib.alex.utils.HibernateUtil; -import de.learnlib.alex.core.learner.Learner; +import org.hibernate.Hibernate; import org.hibernate.Session; import org.hibernate.criterion.Order; import org.hibernate.criterion.Projections; import org.hibernate.criterion.Restrictions; +import org.springframework.stereotype.Repository; +import javax.inject.Inject; import javax.validation.ValidationException; import java.util.Arrays; import java.util.Collection; -import java.util.LinkedList; import java.util.List; import java.util.NoSuchElementException; import java.util.Set; import java.util.TreeSet; -import javax.inject.Inject; /** * Implementation of a LearnerResultDAO using Hibernate. */ +@Repository public class LearnerResultDAOImpl implements LearnerResultDAO { /** The {@link Learner learner} to use. */ @@ -31,7 +53,7 @@ public class LearnerResultDAOImpl implements LearnerResultDAO { /** * Set the learner instance to use. - * Only package visible, because normlly this instance will be injected, but for testing a manual setter is needed. + * Only package visible, because normally this instance will be injected, but for testing a manual setter is needed. * * @param learner The learner to use. */ @@ -42,11 +64,11 @@ void setLearner(Learner learner) { @Override public void create(LearnerResult learnerResult) throws ValidationException { // new LearnerResults should have a project, not a test number not a step number - if (learnerResult.getProject() == null - || learnerResult.getTestNo() != null - || learnerResult.getStepNo() != null) { + if (learnerResult.getUser() == null + || learnerResult.getProject() == null + || learnerResult.getTestNo() != null) { throw new ValidationException( - "To create a LearnResult it must have a Project but must not have a test no. nor step no."); + "To create a LearnResult it must have a User and Project but must not have a test no."); } // start session @@ -55,6 +77,7 @@ public void create(LearnerResult learnerResult) throws ValidationException { // get the current highest test no in the project and add 1 for the next id Long maxTestNo = (Long) session.createCriteria(LearnerResult.class) + .add(Restrictions.eq("user", learnerResult.getUser())) .add(Restrictions.eq("project", learnerResult.getProject())) .setProjection(Projections.max("testNo")) .uniqueResult(); @@ -65,44 +88,45 @@ public void create(LearnerResult learnerResult) throws ValidationException { learnerResult.setId(0L); learnerResult.setTestNo(nextTestNo); - learnerResult.setStepNo(0L); session.save(learnerResult); HibernateUtil.commitTransaction(); } @Override - public List getAllAsJSON(Long projectId) throws NotFoundException { + public List getAll(Long userId, Long projectId, boolean includeSteps) throws NotFoundException { // start session Session session = HibernateUtil.getSession(); HibernateUtil.beginTransaction(); - if (ProjectDAOImpl.isProjectIdInvalid(projectId)) { - throw new NotFoundException("The project with the id " + projectId + " was not found."); - } - // fetch the LearnerResults of the project with the the highest step no. @SuppressWarnings("unchecked") // should return a list of LearnerResults - List resultsAsJSON = session.createCriteria(LearnerResult.class) - .add(Restrictions.eq("project.id", projectId)) - .add(Restrictions.eq("stepNo", 0L)) - .setProjection(Projections.property("JSON")) - .addOrder(Order.asc("testNo")) - .list(); + List results = session.createCriteria(LearnerResult.class) + .add(Restrictions.eq("user.id", userId)) + .add(Restrictions.eq("project.id", projectId)) + .addOrder(Order.asc("testNo")) + .list(); + + if (results.isEmpty()) { + HibernateUtil.rollbackTransaction(); + throw new NotFoundException("The project with the id " + projectId + " was not found."); + } + initializeLazyRelations(session, results, includeSteps); // done HibernateUtil.commitTransaction(); - return resultsAsJSON; + return results; } @Override - public List getAllAsJSON(Long projectId, Long testNo) throws NotFoundException { + public List getAll(Long userId, Long projectId, Long[] testNos, boolean includeSteps) + throws NotFoundException { // start session Session session = HibernateUtil.getSession(); HibernateUtil.beginTransaction(); try { - List result = getAllAsJSON(session, projectId, testNo); + List result = getAll(session, userId, projectId, testNos, includeSteps); // done HibernateUtil.commitTransaction(); @@ -114,167 +138,149 @@ public List getAllAsJSON(Long projectId, Long testNo) throws NotFoundExc } @Override - public List> getAllAsJson(Long projectId, List testNos) throws NotFoundException { + public LearnerResult get(Long userId, Long projectId, Long testNo, boolean includeSteps) throws NotFoundException { // start session Session session = HibernateUtil.getSession(); HibernateUtil.beginTransaction(); - List> result = new LinkedList<>(); - try { - for (Long testNo : testNos) { - List stepsOfOneTestRun = getAllAsJSON(session, projectId, testNo); - result.add(stepsOfOneTestRun); - } + List result = getAll(session, userId, projectId, new Long[] {testNo}, includeSteps); + // done HibernateUtil.commitTransaction(); - return result; - } catch (NotFoundException e) { + return result.get(0); + } catch (NoSuchElementException e) { HibernateUtil.rollbackTransaction(); throw e; } } - private List getAllAsJSON(Session session, Long projectId, Long testNo) throws NotFoundException { - if (ProjectDAOImpl.isProjectIdInvalid(projectId)) { - throw new NotFoundException("The project with the id " + projectId + " was not found."); - } - - // fetch the LearnerResults of the project with the the highest step no. + private List getAll(Session session, Long userId, Long projectId, + Long[] testNos, boolean includeSteps) + throws NotFoundException { @SuppressWarnings("unchecked") // should return a list of LearnerResults - List result = session.createCriteria(LearnerResult.class) - .add(Restrictions.eq("project.id", projectId)) - .add(Restrictions.eq("testNo", testNo)) - .setProjection(Projections.property("JSON")) - .addOrder(Order.asc("stepNo")) - .list(); - - if (result.isEmpty()) { - throw new NotFoundException("No result with the test no. " + testNo + " was found."); - } - - // done - return result; - } - - @Override - public LearnerResult get(Long projectId, Long testNo) throws NotFoundException { - // start session - Session session = HibernateUtil.getSession(); - HibernateUtil.beginTransaction(); + List results = session.createCriteria(LearnerResult.class) + .add(Restrictions.eq("user.id", userId)) + .add(Restrictions.eq("project.id", projectId)) + .add(Restrictions.in("testNo", testNos)) + .list(); - LearnerResult result = get(session, projectId, testNo); + if (results.size() != testNos.length) { + throw new NotFoundException("Not all results with the test nos. " + Arrays.toString(testNos) + + " for the user " + userId + "were found."); + } + initializeLazyRelations(session, results, includeSteps); // done - HibernateUtil.commitTransaction(); - return result; + return results; } - private LearnerResult get(Session session, Long projectId, Long testNo) throws NotFoundException { - if (ProjectDAOImpl.isProjectIdInvalid(projectId)) { - throw new NotFoundException("The project with the id " + projectId + " was not found."); - } - - LearnerResult result = (LearnerResult) session.createCriteria(LearnerResult.class) - .add(Restrictions.eq("project.id", projectId)) - .add(Restrictions.eq("testNo", testNo)) - .add(Restrictions.eq("stepNo", 0L)) - .uniqueResult(); - - if (result == null) { - HibernateUtil.rollbackTransaction(); - throw new NotFoundException("The results with the test no. " + testNo + " in the project " + projectId - + " was not found."); + private void initializeLazyRelations(Session session, List results, boolean includeSteps) { + results.forEach(r -> Hibernate.initialize(r.getResetSymbol())); + results.forEach(r -> Hibernate.initialize(r.getSymbols())); + if (includeSteps) { + results.forEach(r -> Hibernate.initialize(r.getSteps())); + } else { + results.forEach(r -> { + session.evict(r); + r.setSteps(null); + }); } - return result; } @Override - public String getAsJSON(Long projectId, Long testNo) throws NotFoundException { + public LearnerResultStep createStep(LearnerResult result) + throws ValidationException { // start session Session session = HibernateUtil.getSession(); HibernateUtil.beginTransaction(); - if (ProjectDAOImpl.isProjectIdInvalid(projectId)) { - throw new NotFoundException("The project with the id " + projectId + " was not found."); - } - - String result = getAsJSON(session, projectId, testNo, 0L); - - if (result == null) { + LearnerResultStep latestStep = result.getSteps().get(result.getSteps().size() - 1); + + LearnerResultStep newStep = new LearnerResultStep(); + newStep.setUser(result.getUser()); + newStep.setProject(result.getProject()); + newStep.setResult(result); + newStep.setStepNo(latestStep.getStepNo() + 1); + newStep.setEqOracle(latestStep.getEqOracle()); + if (latestStep.getStepsToLearn() > 0) { + newStep.setStepsToLearn(latestStep.getStepsToLearn() - 1); + } else if (latestStep.getStepsToLearn() == -1) { + newStep.setStepsToLearn(-1); + } else { HibernateUtil.rollbackTransaction(); - throw new NotFoundException("The results with the test no. " + testNo + " in the project " + projectId - + " was not found."); + throw new IllegalStateException("The previous step has a step to learn of 0 " + + "-> no new step can be crated!"); } - // done + result.getSteps().add(newStep); + session.save(newStep); + session.update(result); + HibernateUtil.commitTransaction(); - return result; + return newStep; } @Override - public String getAsJSON(Long projectId, Long testNo, Long stepNo) throws NotFoundException { + public LearnerResultStep createStep(LearnerResult result, LearnerResumeConfiguration configuration) + throws ValidationException { // start session Session session = HibernateUtil.getSession(); HibernateUtil.beginTransaction(); - if (ProjectDAOImpl.isProjectIdInvalid(projectId)) { - throw new NotFoundException("The project with the id " + projectId + " was not found."); - } - - String result = getAsJSON(session, projectId, testNo, stepNo); + // create the new step + LearnerResultStep newStep = new LearnerResultStep(); + newStep.setUser(result.getUser()); + newStep.setProject(result.getProject()); + newStep.setResult(result); + newStep.setStepNo((long) result.getSteps().size()); + newStep.setEqOracle(configuration.getEqOracle()); + newStep.setStepsToLearn(configuration.getMaxAmountOfStepsToLearn()); - if (result == null) { - throw new NotFoundException("The result with the test no. " + testNo - + " and the step no. " + stepNo + " was not found."); - } + result.getSteps().add(newStep); + session.save(newStep); + session.update(result); - // done HibernateUtil.commitTransaction(); - return result; + return newStep; } @Override - public void update(LearnerResult learnerResult) throws NotFoundException, ValidationException { + public void saveStep(LearnerResult result, LearnerResultStep step) throws ValidationException { // start session Session session = HibernateUtil.getSession(); HibernateUtil.beginTransaction(); - learnerResult.setId(0L); - learnerResult.setStepNo(learnerResult.getStepNo() + 1); - session.save(learnerResult); + session.update(step); - updateSummary(session, learnerResult); + updateSummary(result, step); + session.update(result); - // done HibernateUtil.commitTransaction(); } - private void updateSummary(Session session, LearnerResult result) throws NotFoundException { - LearnerResult summaryResult = get(session, result.getProjectId(), result.getTestNo()); - summaryResult.setErrorText(result.getErrorText()); - summaryResult.setHypothesis(result.getHypothesis()); + private void updateSummary(LearnerResult result, LearnerResultStep step) { + result.setHypothesis(step.getHypothesis()); + result.setErrorText(step.getErrorText()); - LearnerResult.Statistics summaryStatistics = summaryResult.getStatistics(); - LearnerResult.Statistics newStatistics = result.getStatistics(); + Statistics summaryStatistics = result.getStatistics(); + Statistics newStatistics = step.getStatistics(); summaryStatistics.setDuration(summaryStatistics.getDuration() + newStatistics.getDuration()); summaryStatistics.setMqsUsed(summaryStatistics.getMqsUsed() + newStatistics.getMqsUsed()); summaryStatistics.setSymbolsUsed(summaryStatistics.getSymbolsUsed() + newStatistics.getSymbolsUsed()); summaryStatistics.setEqsUsed(summaryStatistics.getEqsUsed() + newStatistics.getEqsUsed()); - - session.update(summaryResult); } @Override - public void delete(Long projectId, Long... testNo) throws NotFoundException, ValidationException { - checkIfResultsCanBeDeleted(projectId, testNo); // check before the session is opened + public void delete(User user, Long projectId, Long... testNo) throws NotFoundException, ValidationException { + checkIfResultsCanBeDeleted(user, projectId, testNo); // check before the session is opened // start session Session session = HibernateUtil.getSession(); HibernateUtil.beginTransaction(); - List validTestNumbers = getTestNumbersInDB(session, projectId, testNo); + List validTestNumbers = getTestNumbersInDB(session, user.getId(), projectId, testNo); Set diffSet = setDifference(Arrays.asList(testNo), validTestNumbers); if (diffSet.size() > 0) { throw new NotFoundException("The result with the number " + diffSet + " was not found, thus nothing could" @@ -283,18 +289,26 @@ public void delete(Long projectId, Long... testNo) throws NotFoundException, Val @SuppressWarnings("unchecked") // should always return a list of LernerResults List results = session.createCriteria(LearnerResult.class) + .add(Restrictions.eq("user", user)) .add(Restrictions.eq("project.id", projectId)) .add(Restrictions.in("testNo", testNo)) .list(); + results.forEach(session::delete); // done HibernateUtil.commitTransaction(); } - private void checkIfResultsCanBeDeleted(Long projectId, Long... testNo) throws ValidationException { + private void checkIfResultsCanBeDeleted(User user, Long projectId, Long... testNo) throws ValidationException { // don't delete the learnResult of the active learning process - LearnerStatus status = new LearnerStatus(learner); + LearnerStatus status = learner.getStatus(user); + + // user has no active thread -> no conflict possible + if (!status.isActive()) { + return; + } + Long activeTestNo = status.getTestNo(); Long activeProjectId = status.getProjectId(); @@ -313,21 +327,13 @@ private void checkIfResultsCanBeDeleted(Long projectId, Long... testNo) throws V } } - private String getAsJSON(Session session, long projectId, long testNo, long stepNo) { - return (String) session.createCriteria(LearnerResult.class) - .add(Restrictions.eq("project.id", projectId)) - .add(Restrictions.eq("testNo", testNo)) - .add(Restrictions.eq("stepNo", stepNo)) - .setProjection(Projections.property("JSON")) - .uniqueResult(); - } - - private List getTestNumbersInDB(Session session, Long projectId, Long... testNo) { + private List getTestNumbersInDB(Session session, Long userId, Long projectId, Long... testNo) { return session.createCriteria(LearnerResult.class) - .add(Restrictions.eq("project.id", projectId)) - .add(Restrictions.in("testNo", testNo)) - .setProjection(Projections.distinct(Projections.property("testNo"))) - .list(); + .add(Restrictions.eq("user.id", userId)) + .add(Restrictions.eq("project.id", projectId)) + .add(Restrictions.in("testNo", testNo)) + .setProjection(Projections.distinct(Projections.property("testNo"))) + .list(); } private Set setDifference(Collection collectionA, Collection collectionB) { diff --git a/main/src/main/java/de/learnlib/alex/core/dao/ProjectDAO.java b/main/src/main/java/de/learnlib/alex/core/dao/ProjectDAO.java index 23b4498e2..727128393 100644 --- a/main/src/main/java/de/learnlib/alex/core/dao/ProjectDAO.java +++ b/main/src/main/java/de/learnlib/alex/core/dao/ProjectDAO.java @@ -1,6 +1,23 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package de.learnlib.alex.core.dao; import de.learnlib.alex.core.entities.Project; +import de.learnlib.alex.core.entities.User; import de.learnlib.alex.exceptions.NotFoundException; import javax.validation.ValidationException; @@ -28,6 +45,9 @@ enum EmbeddableFields { /** Flag to embed test results. */ TEST_RESULTS, + /** FLag to embed counters. */ + COUNTERS, + /** Flag to embed everything. */ ALL; @@ -53,7 +73,7 @@ public String toString() { /** * Save the given project. - * + * * @param project * The project to be saved. * @throws ValidationException @@ -63,17 +83,20 @@ public String toString() { /** * Get a list of all the projects. - * + * @param user + * The user of the project. * @param embedFields * The fields to include in returned project. By default no additional data will be fetched from the DB. * @return All projects in a list. */ - List getAll(EmbeddableFields... embedFields); + List getAll(User user, EmbeddableFields... embedFields); /** * Get a specific project by its ID. * - * @param id + * @param userId + * The ID of the user. + * @param projectId * The ID of the project to find. * @param embedFields * The fields to include in returned project. By default no additional data will be fetched from the DB. @@ -81,11 +104,21 @@ public String toString() { * @throws NotFoundException * If the project could not be found. */ - Project getByID(long id, EmbeddableFields... embedFields) throws NotFoundException; + Project getByID(Long userId, Long projectId, EmbeddableFields... embedFields) throws NotFoundException; + + /** + * Get a specific project by its ID. + * @param userId + * The ID of the user. + * @param projectName + * The name of the project. + * @return The project with the name. + */ + Project getByName(Long userId, String projectName); /** * Update a project. - * + * * @param project * The project to update. * @throws NotFoundException @@ -97,12 +130,14 @@ public String toString() { /** * Delete a project. - * - * @param id - * The id of the project to delete. + * + * @param userId + * The id of the user. + * @param projectId + * The id of the project to delete. * @throws NotFoundException - * When the Project id was not found. + * When the Project id was not found. */ - void delete(long id) throws NotFoundException; + void delete(Long userId, Long projectId) throws NotFoundException; } diff --git a/main/src/main/java/de/learnlib/alex/core/dao/ProjectDAOImpl.java b/main/src/main/java/de/learnlib/alex/core/dao/ProjectDAOImpl.java index ccfe0cce6..f35c4b290 100644 --- a/main/src/main/java/de/learnlib/alex/core/dao/ProjectDAOImpl.java +++ b/main/src/main/java/de/learnlib/alex/core/dao/ProjectDAOImpl.java @@ -1,17 +1,37 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package de.learnlib.alex.core.dao; import de.learnlib.alex.core.entities.Project; import de.learnlib.alex.core.entities.Symbol; import de.learnlib.alex.core.entities.SymbolGroup; +import de.learnlib.alex.core.entities.User; import de.learnlib.alex.exceptions.NotFoundException; import de.learnlib.alex.utils.HibernateUtil; +import de.learnlib.alex.utils.ValidationExceptionHelper; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.hibernate.Hibernate; import org.hibernate.ObjectNotFoundException; import org.hibernate.Session; -import org.hibernate.criterion.Projections; import org.hibernate.criterion.Restrictions; +import org.springframework.stereotype.Repository; +import javax.inject.Inject; import javax.validation.ValidationException; import java.util.Arrays; import java.util.Collections; @@ -22,11 +42,26 @@ /** * Implementation of a ProjectDAO using Hibernate. */ +@Repository public class ProjectDAOImpl implements ProjectDAO { /** Use the logger for the server part. */ private static final Logger LOGGER = LogManager.getLogger("server"); + /** The SymbolDAO to use. */ + private SymbolDAOImpl symbolDAO; + + /** + * The constructor. + * + * @param symbolDAO + * The SymbolDAOImpl to use. + */ + @Inject + public ProjectDAOImpl(SymbolDAOImpl symbolDAO) { + this.symbolDAO = symbolDAO; + } + @Override public void create(Project project) throws ValidationException { // start session @@ -35,6 +70,7 @@ public void create(Project project) throws ValidationException { try { + // TODO: fix this branch with multi user if (project.getGroups().size() > 0) { // create new project from json with existing groups Integer i = 0; for (SymbolGroup group: project.getGroups()) { @@ -64,6 +100,7 @@ public void create(Project project) throws ValidationException { SymbolGroup defaultGroup = new SymbolGroup(); defaultGroup.setName("Default Group"); defaultGroup.setProject(project); + defaultGroup.setUser(project.getUser()); project.addGroup(defaultGroup); project.setDefaultGroup(defaultGroup); @@ -72,6 +109,7 @@ public void create(Project project) throws ValidationException { symbol.setId(nextSymbolId); symbol.setRevision(0L); symbol.setProject(project); + symbol.setUser(project.getUser()); if (symbol.getGroup() == null) { symbol.setGroup(defaultGroup); } @@ -83,27 +121,33 @@ public void create(Project project) throws ValidationException { HibernateUtil.commitTransaction(); // error handling - } catch (javax.validation.ConstraintViolationException - | org.hibernate.exception.ConstraintViolationException e) { + } catch (javax.validation.ConstraintViolationException e) { + HibernateUtil.rollbackTransaction(); + LOGGER.info("Project creation failed:", e); + throw ValidationExceptionHelper.createValidationException("Project was not created:", e); + } catch (org.hibernate.exception.ConstraintViolationException e) { HibernateUtil.rollbackTransaction(); + LOGGER.info("Project creation failed:", e); throw new javax.validation.ValidationException( - "The Project was not created because it did not pass the validation!", e); + "The Project was not created: " + e.getMessage(), e); } } @Override - public List getAll(EmbeddableFields... embedFields) { + public List getAll(User user, EmbeddableFields... embedFields) { // start session Session session = HibernateUtil.getSession(); HibernateUtil.beginTransaction(); // get the Projects @SuppressWarnings("unchecked") // it should be a list of Projects - List result = session.createCriteria(Project.class).list(); + List result = session.createCriteria(Project.class) + .add(Restrictions.eq("user", user)) + .list(); // load lazy relations for (Project p : result) { - initLazyRelations(p, embedFields); + initLazyRelations(p, session, embedFields); } // done @@ -112,28 +156,44 @@ public List getAll(EmbeddableFields... embedFields) { } @Override - public Project getByID(long id, EmbeddableFields... embedFields) throws NotFoundException { + public Project getByID(Long userId, Long projectId, EmbeddableFields... embedFields) throws NotFoundException { // start session Session session = HibernateUtil.getSession(); HibernateUtil.beginTransaction(); // get the Project - Project result = (Project) session.get(Project.class, id); + Project result = session.get(Project.class, projectId); // load lazy relations if (result != null) { - initLazyRelations(result, embedFields); + initLazyRelations(result, session, embedFields); } // done HibernateUtil.commitTransaction(); if (result == null) { - throw new NotFoundException("Could not find the project with the id " + id + "."); + throw new NotFoundException("Could not find the project with the id " + projectId + "."); } return result; } + @Override + public Project getByName(Long userId, String projectName) { + // start session + Session session = HibernateUtil.getSession(); + HibernateUtil.beginTransaction(); + + Project result = (Project) session.createCriteria(Project.class) + .add(Restrictions.eq("user.id", userId)) + .add(Restrictions.eq("name", projectName)) + .uniqueResult(); + + HibernateUtil.commitTransaction(); + + return result; + } + @Override public void update(Project project) throws NotFoundException, ValidationException { // start session @@ -141,7 +201,7 @@ public void update(Project project) throws NotFoundException, ValidationExceptio HibernateUtil.beginTransaction(); try { - Project projectInDB = (Project) session.load(Project.class, project.getId()); + Project projectInDB = session.load(Project.class, project.getId()); // apply changes projectInDB.setName(project.getName()); @@ -153,21 +213,24 @@ public void update(Project project) throws NotFoundException, ValidationExceptio // error handling } catch (ObjectNotFoundException e) { - LOGGER.info("Project Update Failed:", e); + LOGGER.info("Project update failed:", e); HibernateUtil.rollbackTransaction(); throw new NotFoundException("Could not find the project with the id " + project.getId() + ".", e); - } catch (javax.validation.ConstraintViolationException - | org.hibernate.exception.ConstraintViolationException e) { - LOGGER.info("Project Update Failed:", e); + } catch (javax.validation.ConstraintViolationException e) { + HibernateUtil.rollbackTransaction(); + LOGGER.info("Project update failed:", e); + throw ValidationExceptionHelper.createValidationException("Project was not updated:", e); + } catch (org.hibernate.exception.ConstraintViolationException e) { HibernateUtil.rollbackTransaction(); + LOGGER.info("Project update failed:", e); throw new javax.validation.ValidationException( - "The Project was not updated because it did not pass the validation!", e); + "The Project was not updated: " + e.getMessage(), e); } } @Override - public void delete(long id) throws NotFoundException { - Project project = getByID(id, EmbeddableFields.ALL); + public void delete(Long userId, Long projectId) throws NotFoundException { + Project project = getByID(userId, projectId, EmbeddableFields.ALL); // start session Session session = HibernateUtil.getSession(); @@ -183,46 +246,55 @@ public void delete(long id) throws NotFoundException { /** * Load objects that are connected with a project over a 'lazy' relation ship. - * * @param project * The project which needs the 'lazy' objects. + * @param session + * */ - private void initLazyRelations(Project project, EmbeddableFields... embedFields) { + private void initLazyRelations(Project project, Session session, EmbeddableFields... embedFields) { if (embedFields != null && embedFields.length > 0) { Set fieldsToLoad = fieldsArrayToHashSet(embedFields); if (fieldsToLoad.contains(EmbeddableFields.GROUPS)) { - project.getGroups().forEach(group -> group.getSymbols().forEach(SymbolDAOImpl::loadLazyRelations)); + project.getGroups().forEach(group -> group.getSymbols() + .forEach(s -> symbolDAO.loadLazyRelations(session, s))); } else { project.setGroups(null); } if (fieldsToLoad.contains(EmbeddableFields.DEFAULT_GROUP)) { - project.getDefaultGroup(); - if (project.getDefaultGroup() != null) { - project.getDefaultGroup().getSymbols().forEach(SymbolDAOImpl::loadLazyRelations); - } + project.getDefaultGroup().getSymbols().forEach(s -> symbolDAO.loadLazyRelations(session, s)); } else { project.setDefaultGroup(null); } if (fieldsToLoad.contains(EmbeddableFields.SYMBOLS)) { - project.getSymbols().forEach(SymbolDAOImpl::loadLazyRelations); + project.getSymbols().forEach(s -> symbolDAO.loadLazyRelations(session, s)); } else { project.setSymbols(null); } if (fieldsToLoad.contains(EmbeddableFields.TEST_RESULTS)) { - project.getTestResults(); + Hibernate.initialize(project.getTestResults()); } else { project.setTestResults(null); } + + if (fieldsToLoad.contains(EmbeddableFields.COUNTERS)) { + Hibernate.initialize(project.getCounters()); + } else { + project.setCounters(null); + } } else { project.setGroups(null); project.setDefaultGroup(null); project.setSymbols(null); project.setTestResults(null); + project.setCounters(null); } + + // make sure that the "changes" of the project are never actually send to the db. + session.evict(project); } private Set fieldsArrayToHashSet(EmbeddableFields[] embedFields) { @@ -232,28 +304,11 @@ private Set fieldsArrayToHashSet(EmbeddableFields[] embedField fieldsToLoad.add(EmbeddableFields.DEFAULT_GROUP); fieldsToLoad.add(EmbeddableFields.SYMBOLS); fieldsToLoad.add(EmbeddableFields.TEST_RESULTS); + fieldsToLoad.add(EmbeddableFields.COUNTERS); } else { Collections.addAll(fieldsToLoad, embedFields); } return fieldsToLoad; } - /** - * Checks if a project with the given project id exists. - * - * @param projectId - * The project id to test. - * @return true if the a project exits, false otherwise. - */ - public static boolean isProjectIdInvalid(long projectId) { - Session session = HibernateUtil.getSession(); - - Long projectCount = (Long) session.createCriteria(Project.class) - .add(Restrictions.eq("id", projectId)) - .setProjection(Projections.rowCount()) - .uniqueResult(); - - return projectCount == 0; - } - } diff --git a/main/src/main/java/de/learnlib/alex/core/dao/SymbolDAO.java b/main/src/main/java/de/learnlib/alex/core/dao/SymbolDAO.java index 71b43f4d7..bb5c9ee0b 100644 --- a/main/src/main/java/de/learnlib/alex/core/dao/SymbolDAO.java +++ b/main/src/main/java/de/learnlib/alex/core/dao/SymbolDAO.java @@ -1,8 +1,25 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package de.learnlib.alex.core.dao; import de.learnlib.alex.core.entities.IdRevisionPair; import de.learnlib.alex.core.entities.Symbol; import de.learnlib.alex.core.entities.SymbolVisibilityLevel; +import de.learnlib.alex.core.entities.User; import de.learnlib.alex.exceptions.NotFoundException; import javax.validation.ValidationException; @@ -15,11 +32,11 @@ public interface SymbolDAO { /** * Save the given symbol. - * + * * @param symbol - * The symbol to save. + * The symbol to save. * @throws ValidationException - * When the symbol was not valid. + * When the symbol was not valid. */ void create(Symbol symbol) throws ValidationException; @@ -27,16 +44,18 @@ public interface SymbolDAO { * Save the given symbols. * * @param symbols - * The symbols to save. + * The symbols to save. * @throws ValidationException - * When one the symbols was not valid. - * In this case all symbols are reverted and not saved. + * When one the symbols was not valid. + * In this case all symbols are reverted and not saved. */ void create(List symbols) throws ValidationException; /** * Get a list of specific symbols of a project. * + * @param user + * The owner of the symbols * @param projectId * The project the symbols should belong to. * @param idRevPairs @@ -45,27 +64,79 @@ public interface SymbolDAO { * @throws NotFoundException * If the project or one of the symbols could not be found. */ - List getAll(Long projectId, List idRevPairs) throws NotFoundException; + List getAll(User user, Long projectId, List idRevPairs) throws NotFoundException; + + /** + * Get all symbols of a Project. + * + * @param user + * The user of the Symbols. + * @param projectID + * The project the Symbols should belong to. + * @param visibilityLevel + * Include symbols that are currently marked as hidden? + * @return A list of symbols belonging to the Project. Can be empty. + * @throws NotFoundException + * If the User or Project could not be found. + */ + List getAllWithLatestRevision(User user, Long projectID, SymbolVisibilityLevel visibilityLevel) + throws NotFoundException; - List getAllWithLatestRevision(Long projectId, Long groupId) throws NotFoundException; + /** + * Get a List of Symbols that are within a specific Group within a Project. + * + * @param user + * The user of the Symbols. + * @param projectId + * The Project of the Symbols. + * @param groupId + * The Group of the Symbols. + * @return A List of Symbols belonging to the Group. Can be empty. + * @throws NotFoundException + * If the User, Project or Group could not be found. + */ + List getAllWithLatestRevision(User user, Long projectId, Long groupId) + throws NotFoundException; - List getAllWithLatestRevision(Long projectId, Long groupId, SymbolVisibilityLevel visibilityLevel) + /** + * Get a List of Symbols that are withing a specific Group within a Project and have a specific visibility level. + * + * @param user + * The user of the Symbols. + * @param projectId + * The Project the Symbols. + * @param groupId + * The Group of the Symbols. + * @param visibilityLevel + * Only look for Symbols with the given visibility level. + * @return A List of Symbols belonging to the Group with the given VisibilityLevel. Can be empty. + * @throws NotFoundException + * If the User, Project or Group could not be found. + */ + List getAllWithLatestRevision(User user, Long projectId, + Long groupId, SymbolVisibilityLevel visibilityLevel) throws NotFoundException; /** * Get a list of symbols by their ids. Fetch only the latest revision of each. * + * @param user + * The owner of the symbol * @param projectId * The project the symbols should belong to. * @param ids * The ids of the symbols you want to get. * @return A list of symbols. Can be empty. + * @throws NotFoundException + * If no Symbol was found. */ - List getByIdsWithLatestRevision(Long projectId, Long... ids) throws NotFoundException; + List getByIdsWithLatestRevision(User user, Long projectId, Long... ids) throws NotFoundException; /** * Get a list of symbols by their ids. Fetch only the latest revision of each. * + * @param user + * The owner of the symbol * @param projectId * The project the symbols should belong to. * @param visibilityLevel @@ -73,50 +144,75 @@ List getAllWithLatestRevision(Long projectId, Long groupId, SymbolVisibi * @param ids * The ids of the symbols you want to get. * @return A list of symbols. Can be empty. + * @throws NotFoundException + * If no Symbol was found. */ - List getByIdsWithLatestRevision(Long projectId, SymbolVisibilityLevel visibilityLevel, Long... ids) + List getByIdsWithLatestRevision(User user, Long projectId, + SymbolVisibilityLevel visibilityLevel, Long... ids) throws NotFoundException; /** - * Get all symbols of a project. + * Get a Symbol by the user, project and a Pair of an ID and a revision. * - * @param projectID - * The project the symbols should belong to. - * @param visibilityLevel - * Include symbols that are currently marked as hidden? - * @return A list of symbols belonging to the project. + * @param user + * The owner of the Symbol. + * @param projectId + * The ID of the project the symbol belongs to. + * @param idRevisionPair + * The ID and the Revision of the Symbol in the project. + * @return The Symbol. + * @throws NotFoundException + * If the Symbol could not be found. */ - List getAllWithLatestRevision(Long projectID, SymbolVisibilityLevel visibilityLevel) - throws NotFoundException; - - Symbol get(Long projectId, IdRevisionPair idRevisionPair) throws NotFoundException; + Symbol get(User user, Long projectId, IdRevisionPair idRevisionPair) throws NotFoundException; /** * Get a specific symbol by its identifying parameters. - * + * + * @param user + * The owner of the Symbol. * @param projectId - * The ID of the project the symbol belongs to. + * The ID of the project the symbol belongs to. * @param id - * The ID of the symbol itself in the project. + * The ID of the symbol itself in the project. * @param revision - * The wanted revision of the symbol. + * The wanted revision of the symbol. * @return The Symbol or null. + * @throws NotFoundException + * If the Symbol was not found. */ - Symbol get(Long projectId, Long id, Long revision) throws NotFoundException; + Symbol get(User user, Long projectId, Long id, Long revision) throws NotFoundException; /** * Get a specific symbol by its identifying parameters and the last * revision. * + * @param user + * The owner of the Symbol. * @param projectId - * The ID of the project the symbol belongs to. + * The ID of the project the symbol belongs to. * @param id - * The ID of the symbol itself in the project. + * The ID of the symbol itself in the project. * @return The Symbol or null. + * @throws NotFoundException + * If teh Symbols was not found. */ - Symbol getWithLatestRevision(Long projectId, Long id) throws NotFoundException; + Symbol getWithLatestRevision(User user, Long projectId, Long id) throws NotFoundException; - List getWithAllRevisions(Long projectId, Long id) throws NotFoundException; + /** + * Get all Revisions of one Symbol. + * + * @param user + * The owner of the Symbol. + * @param projectId + * The Project the Symbol belongs to. + * @param id + * The ID of the Symbol within the Project. + * @return A List of all Revision. Sorted with the earliest in the beginning. + * @throws NotFoundException + * If the Symbol could not be found. + */ + List getWithAllRevisions(User user, Long projectId, Long id) throws NotFoundException; /** * Update a symbol. @@ -146,32 +242,59 @@ List getAllWithLatestRevision(Long projectID, SymbolVisibilityLevel visi */ void update(List symbols) throws IllegalArgumentException, NotFoundException, ValidationException; + /** + * Move a Symbol to a new Group. + * This does not increase the revision of the Symbol. + * + * @param symbol + * The Symbol to move. + * @param newGroupId + * The new Group. + * @throws NotFoundException + * If the Symbol or the Group could not be found. + */ void move(Symbol symbol, Long newGroupId) throws NotFoundException; + /** + * Moves a List of Symbols ot a new Group. + * This does not increase the revision of any Symbol. + * If one Symbol failed to be move, no Symbol will be moved. + * + * @param symbols + * The Symbol to move. + * @param newGroupId + * The new Group. + * @throws NotFoundException + * If at least one of the Symbols or if the Group could not be found. + */ void move(List symbols, Long newGroupId) throws NotFoundException; /** * Mark a symbol as hidden. - * + * + * @param userId + * The ID of the user the symbols belongs to. * @param projectId - * The ID of the project the symbol belongs to. + * The ID of the project the symbol belongs to. * @param ids - * The IDs of the symbols to hide. + * The IDs of the symbols to hide. * @throws NotFoundException - * When the Symbol was not found. + * When the Symbol was not found. */ - void hide(Long projectId, Long... ids) throws NotFoundException; + void hide(Long userId, Long projectId, Long... ids) throws NotFoundException; /** * Revive a symbol from the hidden state. * + * @param userId + * The ID of the user the symbols belongs to. * @param projectId - * The ID of the project the symbol belongs to. + * The ID of the project the symbol belongs to. * @param ids - * The ID of the symbols to show. + * The ID of the symbols to show. * @throws NotFoundException - * When the Symbol was not found. + * When the Symbol was not found. */ - void show(Long projectId, Long... ids) throws NotFoundException; + void show(Long userId, Long projectId, Long... ids) throws NotFoundException; } diff --git a/main/src/main/java/de/learnlib/alex/core/dao/SymbolDAOImpl.java b/main/src/main/java/de/learnlib/alex/core/dao/SymbolDAOImpl.java index 3701e0b88..70d70217e 100644 --- a/main/src/main/java/de/learnlib/alex/core/dao/SymbolDAOImpl.java +++ b/main/src/main/java/de/learnlib/alex/core/dao/SymbolDAOImpl.java @@ -1,3 +1,19 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package de.learnlib.alex.core.dao; import de.learnlib.alex.actions.ExecuteSymbolAction; @@ -7,17 +23,21 @@ import de.learnlib.alex.core.entities.SymbolAction; import de.learnlib.alex.core.entities.SymbolGroup; import de.learnlib.alex.core.entities.SymbolVisibilityLevel; +import de.learnlib.alex.core.entities.User; import de.learnlib.alex.exceptions.NotFoundException; import de.learnlib.alex.utils.HibernateUtil; +import de.learnlib.alex.utils.ValidationExceptionHelper; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.hibernate.Hibernate; import org.hibernate.Session; import org.hibernate.criterion.DetachedCriteria; import org.hibernate.criterion.Disjunction; -import org.hibernate.criterion.Junction; import org.hibernate.criterion.Order; import org.hibernate.criterion.Projections; import org.hibernate.criterion.Restrictions; import org.hibernate.criterion.Subqueries; +import org.springframework.stereotype.Repository; import javax.validation.ValidationException; import java.util.Arrays; @@ -30,8 +50,12 @@ /** * Implementation of a SymbolDAO using Hibernate. */ +@Repository public class SymbolDAOImpl implements SymbolDAO { + /** Use the logger for the server part. */ + private static final Logger LOGGER = LogManager.getLogger("server"); + @Override public void create(Symbol symbol) throws ValidationException { // start session @@ -44,9 +68,17 @@ public void create(Symbol symbol) throws ValidationException { HibernateUtil.commitTransaction(); // error handling - } catch (javax.validation.ConstraintViolationException - | org.hibernate.exception.ConstraintViolationException - | IllegalStateException e) { + } catch (javax.validation.ConstraintViolationException e) { + HibernateUtil.rollbackTransaction(); + symbol.setId(null); + symbol.setRevision(null); + throw ValidationExceptionHelper.createValidationException("Symbol was not created:", e); + } catch (org.hibernate.exception.ConstraintViolationException e) { + HibernateUtil.rollbackTransaction(); + symbol.setId(null); + symbol.setRevision(null); + throw new ValidationException("Symbol was not created: " + e.getMessage(), e); + } catch (IllegalStateException e) { HibernateUtil.rollbackTransaction(); symbol.setId(null); symbol.setRevision(null); @@ -86,9 +118,21 @@ public void create(List symbols) throws ValidationException { }); throw new ValidationException("Could not create a symbol because it has a reference to another" + " unknown symbol.", e); - } catch (javax.validation.ConstraintViolationException - | org.hibernate.exception.ConstraintViolationException - | IllegalStateException e) { + } catch (javax.validation.ConstraintViolationException e) { + HibernateUtil.rollbackTransaction(); + symbols.forEach(s -> { + s.setId(null); + s.setRevision(null); + }); + throw ValidationExceptionHelper.createValidationException("Symbols were not created:", e); + } catch (org.hibernate.exception.ConstraintViolationException e) { + HibernateUtil.rollbackTransaction(); + symbols.forEach(s -> { + s.setId(null); + s.setRevision(null); + }); + throw new ValidationException("Symbols were not created: " + e.getMessage(), e); + } catch (IllegalStateException e) { HibernateUtil.rollbackTransaction(); symbols.forEach(s -> { s.setId(null); @@ -105,10 +149,7 @@ private void create(Session session, Symbol symbol) { "To create a symbols it must have a Project but not haven an ID or and revision"); } - Project project = (Project) session.load(Project.class, symbol.getProjectId()); - - // test for unique constrains - checkUniqueConstrains(session, symbol); // will throw exception if the symbol is invalid + Project project = session.load(Project.class, symbol.getProjectId()); // get the current highest symbol id in the project and add 1 for the next id long id = project.getNextSymbolId(); @@ -120,10 +161,11 @@ private void create(Session session, Symbol symbol) { symbol.setRevision(1L); project.addSymbol(symbol); - SymbolGroup group = (SymbolGroup) session.byNaturalId(SymbolGroup.class) - .using("project", project) - .using("id", symbol.getGroupId()) - .load(); + SymbolGroup group = session.byNaturalId(SymbolGroup.class) + .using("user", symbol.getUser()) + .using("project", project) + .using("id", symbol.getGroupId()) + .load(); if (group == null) { throw new ValidationException("The specified group was not found and thus the Symbol was not created."); } @@ -135,19 +177,29 @@ private void create(Session session, Symbol symbol) { } @Override - public List getAll(Long projectId, List idRevPairs) throws NotFoundException { + public List getAll(User user, Long projectId, List idRevPairs) throws NotFoundException { // start session Session session = HibernateUtil.getSession(); HibernateUtil.beginTransaction(); - List result = getAll(session, projectId, idRevPairs); + List result = getAll(session, user, projectId, idRevPairs); // done HibernateUtil.commitTransaction(); return result; } - List getAll(Session session, Long projectId, List idRevPairs) throws NotFoundException { + /** + * Like {@link #getAll(User, Long, List)}, but use a given Hibernate session. + * @param session The session to use. + * @param user The owner of the Symbols. + * @param projectId The project the symbols should belong to. + * @param idRevPairs A list of pairs of an ID and revisions to specify the expected symbols. + * @return A list of symbols matching the project and list of IDs and revisions. + * @throws NotFoundException If the project or one of the symbols could not be found. + */ + List getAll(Session session, User user, Long projectId, List idRevPairs) + throws NotFoundException { // no DB interaction if no symbols are requested if (idRevPairs.isEmpty()) { return new LinkedList<>(); @@ -159,6 +211,7 @@ List getAll(Session session, Long projectId, List idRevP symbolIdRestrictions.add(Restrictions.eq("idRevisionPair", pair)); } DetachedCriteria symbolIds = DetachedCriteria.forClass(Symbol.class) + .add(Restrictions.eq("user.id", user.getId())) .add(Restrictions.eq("project.id", projectId)) .add(symbolIdRestrictions) .setProjection(Projections.property("symbolId")); @@ -176,48 +229,69 @@ List getAll(Session session, Long projectId, List idRevP } // load the lazy relations - result.forEach(SymbolDAOImpl::loadLazyRelations); + result.forEach(s -> loadLazyRelations(session, s)); return result; } @Override - public List getAllWithLatestRevision(Long projectId, Long groupId) throws NotFoundException { - return getAllWithLatestRevision(projectId, groupId, SymbolVisibilityLevel.VISIBLE); + public List getAllWithLatestRevision(User user, Long projectId, Long groupId) throws NotFoundException { + return getAllWithLatestRevision(user, projectId, groupId, SymbolVisibilityLevel.VISIBLE); } @Override - public List getAllWithLatestRevision(Long projectId, Long groupId, SymbolVisibilityLevel visibilityLevel) + public List getAllWithLatestRevision(User user, Long projectId, + Long groupId, SymbolVisibilityLevel visibilityLevel) throws NotFoundException { // start session Session session = HibernateUtil.getSession(); HibernateUtil.beginTransaction(); - List idRevPairs = getIdRevisionPairs(session, projectId, groupId, visibilityLevel); + List idRevPairs = getIdRevisionPairs(session, user.getId(), projectId, + groupId, visibilityLevel); HibernateUtil.commitTransaction(); if (idRevPairs.isEmpty()) { return new LinkedList<>(); } - return getAll(projectId, idRevPairs); + return getAll(user, projectId, idRevPairs); } - List getIdRevisionPairs(Session session, - Long projectId, - Long groupId, - SymbolVisibilityLevel visibilityLevel) throws NotFoundException { + /** + * Get a List of IdRevisionPairs that describes all Symbols within a specific group, + * using the given Hibernate session. + * + * @param session + * The Session to use. + * @param userId + * The owner of the Symbols. + * @param projectId + * The Project that the Symbols are part of. + * @param groupId + * The Group in which the Symbols are in. + * @param visibilityLevel + * The visibility level of the Symbols. + * @return A List of IdRevisionPairs to represent the Symbols within a group. + * @throws NotFoundException + * If the group could not be found. + */ + List getIdRevisionPairs(Session session, Long userId, Long projectId, + Long groupId, SymbolVisibilityLevel visibilityLevel) + throws NotFoundException { SymbolGroup group = (SymbolGroup) session.createCriteria(SymbolGroup.class) + .add(Restrictions.eq("user.id", userId)) .add(Restrictions.eq("project.id", projectId)) .add(Restrictions.eq("id", groupId)) .uniqueResult(); if (group == null) { - throw new NotFoundException("Could not find the group the id " + groupId + throw new NotFoundException("Could not find the group with the id " + groupId + " in the project " + projectId + ". "); } // get latest revision List idRevList = session.createCriteria(Symbol.class) + .add(Restrictions.eq("user.id", userId)) .add(Restrictions.eq("project.id", projectId)) .add(Restrictions.eq("group", group)) .add(visibilityLevel.getCriterion()) @@ -235,30 +309,49 @@ List getIdRevisionPairs(Session session, @Override - public List getByIdsWithLatestRevision(Long projectId, Long... ids) throws NotFoundException { - return getByIdsWithLatestRevision(projectId, SymbolVisibilityLevel.ALL, ids); + public List getByIdsWithLatestRevision(User user, Long projectId, Long... ids) throws NotFoundException { + return getByIdsWithLatestRevision(user, projectId, SymbolVisibilityLevel.ALL, ids); } @Override - public List getByIdsWithLatestRevision(Long projectId, SymbolVisibilityLevel visibilityLevel, + public List getByIdsWithLatestRevision(User user, Long projectId, SymbolVisibilityLevel visibilityLevel, Long... ids) throws NotFoundException { try { // start session Session session = HibernateUtil.getSession(); HibernateUtil.beginTransaction(); - return getByIdsWithLatestRevision(session, projectId, visibilityLevel, ids); + return getByIdsWithLatestRevision(session, user, projectId, visibilityLevel, ids); } finally { HibernateUtil.commitTransaction(); } } - public List getByIdsWithLatestRevision(Session session, Long projectId, + /** + * Get a list of Symbols, like {@link #getByIdsWithLatestRevision(User, Long, SymbolVisibilityLevel, Long...)}, + * but use a given Hibernate session. + * + * @param session + * The session to use. + * @param user + * The owner of the Symbols. + * @param projectId + * The Project the Symbols are part of. + * @param visibilityLevel + * The visibility level to check. + * @param ids + * The IDs of the Symbols to fetch. + * @return The List of Symbols fulfilling all the above criteria. + * @throws NotFoundException + * If no symbol was found. + */ + public List getByIdsWithLatestRevision(Session session, User user, Long projectId, SymbolVisibilityLevel visibilityLevel, Long... ids) throws NotFoundException { // get latest revision @SuppressWarnings("unchecked") // should return a list of objects arrays which contain 2 Long values. List idRevList = session.createCriteria(Symbol.class) + .add(Restrictions.eq("user", user)) .add(Restrictions.eq("project.id", projectId)) .add(Restrictions.in("idRevisionPair.id", ids)) .add(visibilityLevel.getCriterion()) @@ -267,24 +360,23 @@ public List getByIdsWithLatestRevision(Session session, Long projectId, .add(Projections.max("idRevisionPair.revision")) ).list(); - List idRevPairs = createIdRevisionPairList(idRevList); if (idRevPairs.isEmpty()) { throw new NotFoundException("Could not find symbols in the project " + projectId + " with the ids " + Arrays.toString(ids) + "."); } - return getAll(session, projectId, idRevPairs); + return getAll(session, user, projectId, idRevPairs); } @Override - public List getAllWithLatestRevision(Long projectId, SymbolVisibilityLevel visibilityLevel) + public List getAllWithLatestRevision(User user, Long projectId, SymbolVisibilityLevel visibilityLevel) throws NotFoundException { // start session Session session = HibernateUtil.getSession(); HibernateUtil.beginTransaction(); - Project project = (Project) session.get(Project.class, projectId); + Project project = session.get(Project.class, projectId); if (project == null) { throw new NotFoundException("Could not find the project with the id " + projectId + "."); } @@ -292,6 +384,7 @@ public List getAllWithLatestRevision(Long projectId, SymbolVisibilityLev @SuppressWarnings("unchecked") // should return a list of Symbols List ids = session.createCriteria(Symbol.class) .add(Restrictions.eq("project", project)) + .add(Restrictions.eq("user", user)) .setProjection(Projections.property("idRevisionPair.id")) .list(); @@ -300,14 +393,14 @@ public List getAllWithLatestRevision(Long projectId, SymbolVisibilityLev if (ids.isEmpty()) { return new LinkedList<>(); } - return getByIdsWithLatestRevision(projectId, visibilityLevel, ids.toArray(new Long[ids.size()])); + return getByIdsWithLatestRevision(user, projectId, visibilityLevel, ids.toArray(new Long[ids.size()])); } @Override - public Symbol get(Long projectId, IdRevisionPair idRevisionPair) throws NotFoundException { + public Symbol get(User user, Long projectId, IdRevisionPair idRevisionPair) throws NotFoundException { List idRevisionList = new LinkedList<>(); idRevisionList.add(idRevisionPair); - List resultList = getAll(projectId, idRevisionList); + List resultList = getAll(user, projectId, idRevisionList); if (resultList.isEmpty()) { throw new NotFoundException("Could not find a symbol in the project " + projectId @@ -317,24 +410,24 @@ public Symbol get(Long projectId, IdRevisionPair idRevisionPair) throws NotFound } @Override - public Symbol get(Long projectId, Long id, Long revision) throws NotFoundException { + public Symbol get(User user, Long projectId, Long id, Long revision) throws NotFoundException { IdRevisionPair idRevisionPair = new IdRevisionPair(id, revision); - return get(projectId, idRevisionPair); + return get(user, projectId, idRevisionPair); } @Override - public Symbol getWithLatestRevision(Long projectId, Long id) throws NotFoundException { + public Symbol getWithLatestRevision(User user, Long projectId, Long id) throws NotFoundException { HibernateUtil.beginTransaction(); Session session = HibernateUtil.getSession(); - Symbol result = getWithLatestRevision(session, projectId, id); + Symbol result = getWithLatestRevision(session, user, projectId, id); HibernateUtil.commitTransaction(); return result; } - private Symbol getWithLatestRevision(Session session, Long projectId, Long id) throws NotFoundException { - List resultList = getByIdsWithLatestRevision(session, projectId, SymbolVisibilityLevel.ALL, id); + private Symbol getWithLatestRevision(Session session, User user, Long projectId, Long id) throws NotFoundException { + List resultList = getByIdsWithLatestRevision(session, user, projectId, SymbolVisibilityLevel.ALL, id); if (resultList.isEmpty()) { throw new NotFoundException("Could not find symbols in the project " + projectId @@ -344,7 +437,7 @@ private Symbol getWithLatestRevision(Session session, Long projectId, Long id) t } @Override - public List getWithAllRevisions(Long projectId, Long id) throws NotFoundException { + public List getWithAllRevisions(User user, Long projectId, Long id) throws NotFoundException { // start session Session session = HibernateUtil.getSession(); HibernateUtil.beginTransaction(); @@ -352,6 +445,7 @@ public List getWithAllRevisions(Long projectId, Long id) throws NotFound @SuppressWarnings("unchecked") // should return a list of revisions (type: Long) List revisions = session.createCriteria(Symbol.class) .add(Restrictions.eq("project.id", projectId)) + .add(Restrictions.eq("user", user)) .add(Restrictions.eq("idRevisionPair.id", id)) .setProjection(Projections.property("idRevisionPair.revision")) .addOrder(Order.asc("idRevisionPair.revision")) @@ -367,7 +461,7 @@ public List getWithAllRevisions(Long projectId, Long id) throws NotFound List idRevisionList = new LinkedList<>(); revisions.forEach(revision -> idRevisionList.add(new IdRevisionPair(id, revision))); - return getAll(projectId, idRevisionList); + return getAll(user, projectId, idRevisionList); } @Override @@ -384,9 +478,13 @@ public void update(Symbol symbol) throws IllegalArgumentException, NotFoundExcep HibernateUtil.commitTransaction(); // error handling - } catch (javax.validation.ConstraintViolationException - | org.hibernate.exception.ConstraintViolationException - | IllegalStateException e) { + } catch (javax.validation.ConstraintViolationException e) { + HibernateUtil.rollbackTransaction(); + throw ValidationExceptionHelper.createValidationException("Symbol was not updated:", e); + } catch (org.hibernate.exception.ConstraintViolationException e) { + HibernateUtil.rollbackTransaction(); + throw new ValidationException("Symbol was not updated: " + e.getMessage(), e); + } catch (IllegalStateException e) { HibernateUtil.rollbackTransaction(); throw new ValidationException("Could not update the Symbol because it is not valid.", e); } catch (IllegalArgumentException e) { @@ -419,9 +517,21 @@ public void update(List symbols) throws IllegalArgumentException, NotFou HibernateUtil.commitTransaction(); // error handling - } catch (javax.validation.ConstraintViolationException - | org.hibernate.exception.ConstraintViolationException - | IllegalStateException e) { + } catch (javax.validation.ConstraintViolationException e) { + HibernateUtil.rollbackTransaction(); + symbols.forEach(s -> { + s.setId(null); + s.setRevision(null); + }); + throw ValidationExceptionHelper.createValidationException("Symbols were not updated:", e); + } catch (org.hibernate.exception.ConstraintViolationException e) { + HibernateUtil.rollbackTransaction(); + symbols.forEach(s -> { + s.setId(null); + s.setRevision(null); + }); + throw new ValidationException("Symbols were not updated: " + e.getMessage(), e); + } catch (IllegalStateException e) { HibernateUtil.rollbackTransaction(); symbols.forEach(s -> { s.setId(null); @@ -454,7 +564,7 @@ private void update(Session session, Symbol symbol) throws IllegalArgumentExcept Symbol symbolInDB; try { - symbolInDB = getWithLatestRevision(session, symbol.getProjectId(), symbol.getId()); + symbolInDB = getWithLatestRevision(session, symbol.getUser(), symbol.getProjectId(), symbol.getId()); } catch (NotFoundException e) { throw new NotFoundException("Update failed: Could not find the symbols with the id " + symbol.getId() + " in the project " + symbol.getProjectId() + "."); @@ -467,17 +577,16 @@ private void update(Session session, Symbol symbol) throws IllegalArgumentExcept + symbol.getProjectId() + "."); } - // test for unique constrains - checkUniqueConstrains(session, symbol); // will throw exception if the symbol is invalid - - SymbolGroup oldGroup = (SymbolGroup) session.byNaturalId(SymbolGroup.class) - .using("project", symbol.getProject()) - .using("id", symbolInDB.getGroupId()) - .load(); - SymbolGroup newGroup = (SymbolGroup) session.byNaturalId(SymbolGroup.class) - .using("project", symbol.getProject()) - .using("id", symbol.getGroupId()) - .load(); + SymbolGroup oldGroup = session.byNaturalId(SymbolGroup.class) + .using("user", symbol.getUser()) + .using("project", symbol.getProject()) + .using("id", symbolInDB.getGroupId()) + .load(); + SymbolGroup newGroup = session.byNaturalId(SymbolGroup.class) + .using("user", symbol.getUser()) + .using("project", symbol.getProject()) + .using("id", symbol.getGroupId()) + .load(); // count revision up symbol.setSymbolId(0L); @@ -491,6 +600,7 @@ private void update(Session session, Symbol symbol) throws IllegalArgumentExcept // update group if (!newGroup.equals(oldGroup)) { List symbols = session.createCriteria(Symbol.class) + .add(Restrictions.eq("user", symbol.getUser())) .add(Restrictions.eq("project", symbol.getProject())) .add(Restrictions.eq("idRevisionPair.id", symbol.getId())) .list(); @@ -532,14 +642,16 @@ public void move(List symbols, Long newGroupId) throws NotFoundException } private void move(Session session, Symbol symbol, Long newGroupId) throws NotFoundException { - SymbolGroup oldGroup = (SymbolGroup) session.byNaturalId(SymbolGroup.class) - .using("project", symbol.getProject()) - .using("id", symbol.getGroupId()) - .load(); - SymbolGroup newGroup = (SymbolGroup) session.byNaturalId(SymbolGroup.class) - .using("project", symbol.getProject()) - .using("id", newGroupId) - .load(); + SymbolGroup oldGroup = session.byNaturalId(SymbolGroup.class) + .using("user", symbol.getUser()) + .using("project", symbol.getProject()) + .using("id", symbol.getGroupId()) + .load(); + SymbolGroup newGroup = session.byNaturalId(SymbolGroup.class) + .using("user", symbol.getUser()) + .using("project", symbol.getProject()) + .using("id", newGroupId) + .load(); if (newGroup == null) { throw new NotFoundException("The group with the id " + newGroupId + " does not exist!"); @@ -547,6 +659,7 @@ private void move(Session session, Symbol symbol, Long newGroupId) throws NotFou if (!newGroup.equals(oldGroup)) { List symbols = session.createCriteria(Symbol.class) + .add(Restrictions.eq("user", symbol.getUser())) .add(Restrictions.eq("project", symbol.getProject())) .add(Restrictions.eq("idRevisionPair.id", symbol.getId())) .list(); @@ -555,7 +668,7 @@ private void move(Session session, Symbol symbol, Long newGroupId) throws NotFou } @Override - public void hide(Long projectId, Long... ids) throws NotFoundException { + public void hide(Long userId, Long projectId, Long... ids) throws NotFoundException { // start session Session session = HibernateUtil.getSession(); HibernateUtil.beginTransaction(); @@ -563,7 +676,7 @@ public void hide(Long projectId, Long... ids) throws NotFoundException { // update try { for (Long id : ids) { - List symbols = getSymbols(session, projectId, id); + List symbols = getSymbols(session, userId, projectId, id); hideSymbols(session, symbols); } @@ -578,7 +691,7 @@ public void hide(Long projectId, Long... ids) throws NotFoundException { private void hideSymbols(Session session, List symbols) { for (Symbol symbol : symbols) { - loadLazyRelations(symbol); + loadLazyRelations(session, symbol); symbol.setHidden(true); session.update(symbol); @@ -586,7 +699,7 @@ private void hideSymbols(Session session, List symbols) { } @Override - public void show(Long projectId, Long... ids) throws NotFoundException { + public void show(Long userId, Long projectId, Long... ids) throws NotFoundException { // start session Session session = HibernateUtil.getSession(); HibernateUtil.beginTransaction(); @@ -594,7 +707,7 @@ public void show(Long projectId, Long... ids) throws NotFoundException { // update try { for (Long id : ids) { - List symbols = getSymbols(session, projectId, id); + List symbols = getSymbols(session, userId, projectId, id); showSymbols(session, symbols); } } catch (IllegalArgumentException e) { @@ -625,9 +738,11 @@ private List createIdRevisionPairList(List idRevisions return idRevPairs; } - private List getSymbols(Session session, Long projectId, Long symbolId) throws NotFoundException { + private List getSymbols(Session session, Long userId, Long projectId, Long symbolId) + throws NotFoundException { @SuppressWarnings("should return a list of Symbols") List symbols = session.createCriteria(Symbol.class) + .add(Restrictions.eq("user.id", userId)) .add(Restrictions.eq("project.id", projectId)) .add(Restrictions.eq("idRevisionPair.id", symbolId)) .list(); @@ -639,46 +754,11 @@ private List getSymbols(Session session, Long projectId, Long symbolId) return symbols; } - /** - * Check the unique constrains of a symbol, e.g. a unique id, name or abbreviation per Project. - * If the symbol is valid, nothing will happen, otherwise an exception will be thrown. - * - * @param session - * The current session. - * @param symbol - * The Symbol to check. - * @throws IllegalArgumentException - * If the symbol failed the check. - */ - private void checkUniqueConstrains(Session session, Symbol symbol) throws IllegalArgumentException { - // put constrains into a query - Junction restrictions = Restrictions.conjunction() - .add(Restrictions.eq("project", symbol.getProject())) - .add(Restrictions.disjunction() - .add(Restrictions.eq("name", symbol.getName())) - .add(Restrictions.eq("abbreviation", symbol.getAbbreviation()))); - if (symbol.getId() != null) { - restrictions = restrictions.add(Restrictions.ne("idRevisionPair.id", symbol.getId())); - } - - @SuppressWarnings("unchecked") // should return list of symbols - List testList = session.createCriteria(symbol.getClass()) - .add(restrictions) - .list(); - - // if the query result is not empty, the constrains are violated. - if (testList.size() > 0) { - HibernateUtil.rollbackTransaction(); - throw new ValidationException("The name '" + symbol.getName() + "' or the abbreviation '" - + symbol.getAbbreviation() + "' of the symbol is already used" - + " in the project."); - } - } - private void beforeSymbolSave(Symbol symbol) { for (int i = 0; i < symbol.getActions().size(); i++) { SymbolAction action = symbol.getActions().get(i); action.setId(null); + action.setUser(symbol.getUser()); action.setProject(symbol.getProject()); action.setSymbol(symbol); action.setNumber(i); @@ -702,6 +782,7 @@ private void setExecuteToSymbols(Session session, Symbol symbol, Map a instanceof ExecuteSymbolAction).forEach(a -> { ExecuteSymbolAction action = (ExecuteSymbolAction) a; + + if (action.isUseLatestRevision()) { + try { + Symbol symbolToExecute = getWithLatestRevision(session, symbol.getUser(), symbol.getProjectId(), + action.getSymbolToExecuteAsIdRevisionPair().getId()); + action.setSymbolToExecute(symbolToExecute); + } catch (NotFoundException e) { + LOGGER.warn("While loading the lazy relation for the symbol '" + symbol + "': Could not find the" + + "latest revision of the symbol to execute '" + action.getSymbolToExecute() + "'."); + action.setSymbolToExecute(null); + } + } + Symbol symbolToExecute = action.getSymbolToExecute(); if (symbolToExecute != null @@ -730,7 +833,7 @@ public static void loadLazyRelations(Symbol symbol) { || !Hibernate.isInitialized(symbolToExecute.getActions()))) { Hibernate.initialize(symbolToExecute); Hibernate.initialize(symbolToExecute.getActions()); - loadLazyRelations(symbolToExecute); + loadLazyRelations(session, symbolToExecute); } }); } diff --git a/main/src/main/java/de/learnlib/alex/core/dao/SymbolGroupDAO.java b/main/src/main/java/de/learnlib/alex/core/dao/SymbolGroupDAO.java index aee1ffba8..c38efac61 100644 --- a/main/src/main/java/de/learnlib/alex/core/dao/SymbolGroupDAO.java +++ b/main/src/main/java/de/learnlib/alex/core/dao/SymbolGroupDAO.java @@ -1,6 +1,23 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package de.learnlib.alex.core.dao; import de.learnlib.alex.core.entities.SymbolGroup; +import de.learnlib.alex.core.entities.User; import de.learnlib.alex.exceptions.NotFoundException; import javax.validation.ValidationException; @@ -11,11 +28,17 @@ */ public interface SymbolGroupDAO { + /** + * Enum to selecct which fields should be not only referenced but directly be included, i.e. loaded from the DB. + */ enum EmbeddableFields { + /** Fetch all fields. */ ALL, + /** Fetch all the symbols with all actions. */ COMPLETE_SYMBOLS, + /** Fetch the symbols. */ SYMBOLS; /** @@ -51,6 +74,8 @@ public String toString() { /** * Get a list of all groups withing one project. * + * @param userId + * The id of the owner of the group * @param projectId * The project the groups should belong to. * @param embedFields @@ -59,11 +84,13 @@ public String toString() { * @throws NotFoundException * If no project with the given id was found. */ - List getAll(long projectId, EmbeddableFields... embedFields) throws NotFoundException; + // TODO: most of the others methods use a User object. So, should this method be changed to use objects as well? + List getAll(long userId, long projectId, EmbeddableFields... embedFields) throws NotFoundException; /** * Get one group. * + * @param user The owner of the group * @param projectId * The project the group belongs to. * @param groupId @@ -74,7 +101,7 @@ public String toString() { * @throws NotFoundException * If the Project or the Group could not be found. */ - SymbolGroup get(long projectId, Long groupId, EmbeddableFields... embedFields) throws NotFoundException; + SymbolGroup get(User user, long projectId, Long groupId, EmbeddableFields... embedFields) throws NotFoundException; /** * Update a group. @@ -91,6 +118,7 @@ public String toString() { /** * Delete a group. * + * @param user The owner of the group * @param projectId * The project the group belongs to. * @param groupId @@ -100,6 +128,6 @@ public String toString() { * @throws NotFoundException * If The project or group could not be found. */ - void delete(long projectId, Long groupId) throws IllegalArgumentException, NotFoundException; + void delete(User user, long projectId, Long groupId) throws IllegalArgumentException, NotFoundException; } diff --git a/main/src/main/java/de/learnlib/alex/core/dao/SymbolGroupDAOImpl.java b/main/src/main/java/de/learnlib/alex/core/dao/SymbolGroupDAOImpl.java index c07675896..11e336c66 100644 --- a/main/src/main/java/de/learnlib/alex/core/dao/SymbolGroupDAOImpl.java +++ b/main/src/main/java/de/learnlib/alex/core/dao/SymbolGroupDAOImpl.java @@ -1,3 +1,19 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package de.learnlib.alex.core.dao; import de.learnlib.alex.core.entities.IdRevisionPair; @@ -5,11 +21,18 @@ import de.learnlib.alex.core.entities.Symbol; import de.learnlib.alex.core.entities.SymbolGroup; import de.learnlib.alex.core.entities.SymbolVisibilityLevel; +import de.learnlib.alex.core.entities.User; import de.learnlib.alex.exceptions.NotFoundException; import de.learnlib.alex.utils.HibernateUtil; +import de.learnlib.alex.utils.ValidationExceptionHelper; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.hibernate.Session; import org.hibernate.criterion.Restrictions; +import org.springframework.stereotype.Repository; +import javax.inject.Inject; +import javax.validation.ConstraintViolationException; import javax.validation.ValidationException; import java.util.Arrays; import java.util.Collections; @@ -20,17 +43,24 @@ /** * Implementation of a SymbolGroupDAO using Hibernate. */ +@Repository public class SymbolGroupDAOImpl implements SymbolGroupDAO { + /** Use the logger for the server part. */ + private static final Logger LOGGER = LogManager.getLogger("server"); + /** The SymbolDAO to use. */ - private final SymbolDAOImpl symbolDAO; + private SymbolDAOImpl symbolDAO; /** - * Constructor. - * Creates a new SymbolDAOImpl for internal use. + * The constructor. + * + * @param symbolDAO + * The SymbolDAOImpl to use. */ - public SymbolGroupDAOImpl() { - this.symbolDAO = new SymbolDAOImpl(); + @Inject + public SymbolGroupDAOImpl(SymbolDAOImpl symbolDAO) { + this.symbolDAO = symbolDAO; } @Override @@ -45,29 +75,36 @@ public void create(SymbolGroup group) throws ValidationException { Session session = HibernateUtil.getSession(); HibernateUtil.beginTransaction(); - Project project = (Project) session.load(Project.class, group.getProjectId()); + try { + Project project = session.load(Project.class, group.getProjectId()); - checkConstrains(session, group); // will throw an ValidationException, if something is wrong + // get the current highest group id in the project and add 1 for the next id + long id = project.getNextGroupId(); + project.setNextGroupId(id + 1); + session.update(project); - // get the current highest group id in the project and add 1 for the next id - long id = project.getNextGroupId(); - project.setNextGroupId(id + 1); - session.update(project); + group.setId(id); + project.addGroup(group); - group.setId(id); - project.addGroup(group); - - session.save(group); - HibernateUtil.commitTransaction(); + session.save(group); + HibernateUtil.commitTransaction(); + // error handling + } catch (ConstraintViolationException e) { + HibernateUtil.rollbackTransaction(); + LOGGER.info("SymbolGroup creation failed:", e); + throw ValidationExceptionHelper.createValidationException("SymbolGroup was not created:", e); + } } @Override - public List getAll(long projectId, EmbeddableFields... embedFields) throws NotFoundException { + public List getAll(long userId, long projectId, EmbeddableFields... embedFields) + throws NotFoundException { // start session Session session = HibernateUtil.getSession(); HibernateUtil.beginTransaction(); - Project project = (Project) session.load(Project.class, projectId); + Project project = session.load(Project.class, projectId); + User user = session.load(User.class, userId); if (project == null) { HibernateUtil.rollbackTransaction(); @@ -75,11 +112,12 @@ public List getAll(long projectId, EmbeddableFields... embedFields) } List resultList = session.createCriteria(SymbolGroup.class) + .add(Restrictions.eq("user", user)) .add(Restrictions.eq("project", project)) .list(); for (SymbolGroup group : resultList) { - initLazyRelations(session, group, embedFields); + initLazyRelations(session, user, group, embedFields); } HibernateUtil.commitTransaction(); @@ -87,14 +125,15 @@ public List getAll(long projectId, EmbeddableFields... embedFields) } @Override - public SymbolGroup get(long projectId, Long groupId, EmbeddableFields... embedFields) + public SymbolGroup get(User user, long projectId, Long groupId, EmbeddableFields... embedFields) throws NotFoundException { // start session Session session = HibernateUtil.getSession(); HibernateUtil.beginTransaction(); - Project project = (Project) session.load(Project.class, projectId); - SymbolGroup result = (SymbolGroup) session.byNaturalId(SymbolGroup.class) + Project project = session.load(Project.class, projectId); + SymbolGroup result = session.byNaturalId(SymbolGroup.class) + .using("user", user) .using("project", project) .using("id", groupId) .load(); @@ -105,7 +144,7 @@ public SymbolGroup get(long projectId, Long groupId, EmbeddableFields... embedFi + " in the project " + projectId + "."); } - initLazyRelations(session, result, embedFields); + initLazyRelations(session, user, result, embedFields); HibernateUtil.commitTransaction(); return result; @@ -117,9 +156,8 @@ public void update(SymbolGroup group) throws NotFoundException, ValidationExcept Session session = HibernateUtil.getSession(); HibernateUtil.beginTransaction(); - checkConstrains(session, group); // will throw an ValidationException, if something is wrong - - SymbolGroup groupInDB = (SymbolGroup) session.byNaturalId(SymbolGroup.class) + SymbolGroup groupInDB = session.byNaturalId(SymbolGroup.class) + .using("user", group.getUser()) .using("project", group.getProject()) .using("id", group.getId()) .load(); @@ -128,22 +166,29 @@ public void update(SymbolGroup group) throws NotFoundException, ValidationExcept throw new NotFoundException("You can only update existing groups!"); } - // apply changes - groupInDB.setName(group.getName()); - session.update(groupInDB); + try { + // apply changes + groupInDB.setName(group.getName()); + session.update(groupInDB); - HibernateUtil.commitTransaction(); + HibernateUtil.commitTransaction(); + // error handling + } catch (ConstraintViolationException e) { + HibernateUtil.rollbackTransaction(); + LOGGER.info("SymbolGroup update failed:", e); + throw ValidationExceptionHelper.createValidationException("SymbolGroup was not updated:", e); + } } @Override - public void delete(long projectId, Long groupId) throws IllegalArgumentException, NotFoundException { - SymbolGroup group = get(projectId, groupId, EmbeddableFields.ALL); + public void delete(User user, long projectId, Long groupId) throws IllegalArgumentException, NotFoundException { + SymbolGroup group = get(user, projectId, groupId, EmbeddableFields.ALL); // start session Session session = HibernateUtil.getSession(); HibernateUtil.beginTransaction(); - Project project = (Project) session.load(Project.class, projectId); + Project project = session.load(Project.class, projectId); if (group.equals(project.getDefaultGroup())) { HibernateUtil.rollbackTransaction(); @@ -156,35 +201,24 @@ public void delete(long projectId, Long groupId) throws IllegalArgumentException session.update(symbol); } + group.setSymbols(null); session.delete(group); HibernateUtil.commitTransaction(); } - private void checkConstrains(Session session, SymbolGroup group) throws ValidationException { - List constrainsTestList = session.createCriteria(SymbolGroup.class) - .add(Restrictions.eq("project", group.getProject())) - .add(Restrictions.eq("name", group.getName())) - .list(); - - if (!constrainsTestList.isEmpty()) { - HibernateUtil.rollbackTransaction(); - throw new ValidationException("The group name must be unique per project."); - } - } - - private void initLazyRelations(Session session, SymbolGroup group, EmbeddableFields... embedFields) { + private void initLazyRelations(Session session, User user, SymbolGroup group, EmbeddableFields... embedFields) { Set fieldsToLoad = fieldsArrayToHashSet(embedFields); if (fieldsToLoad.contains(EmbeddableFields.COMPLETE_SYMBOLS)) { - group.getSymbols(); - group.getSymbols().forEach(SymbolDAOImpl::loadLazyRelations); + group.getSymbols().forEach(s -> symbolDAO.loadLazyRelations(session, s)); } else if (fieldsToLoad.contains(EmbeddableFields.SYMBOLS)) { try { List idRevisionPairs = symbolDAO.getIdRevisionPairs(session, + group.getUserId(), group.getProjectId(), group.getId(), SymbolVisibilityLevel.ALL); - List symbols = symbolDAO.getAll(session, group.getProjectId(), idRevisionPairs); + List symbols = symbolDAO.getAll(session, user, group.getProjectId(), idRevisionPairs); group.setSymbols(new HashSet<>(symbols)); } catch (NotFoundException e) { group.setSymbols(null); diff --git a/main/src/main/java/de/learnlib/alex/core/dao/UserDAO.java b/main/src/main/java/de/learnlib/alex/core/dao/UserDAO.java new file mode 100644 index 000000000..5ceab6909 --- /dev/null +++ b/main/src/main/java/de/learnlib/alex/core/dao/UserDAO.java @@ -0,0 +1,100 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package de.learnlib.alex.core.dao; + +import de.learnlib.alex.core.entities.User; +import de.learnlib.alex.core.entities.UserRole; +import de.learnlib.alex.exceptions.NotFoundException; + +import javax.validation.ValidationException; +import java.util.List; + +/** + * Interface for database operations on users. + */ +public interface UserDAO { + + /** + * Creates a new user + * . + * + * @param user + * The user to create + * @throws ValidationException + * If the user could not be created because the object has invalid properties. + */ + void create(User user) throws ValidationException; + + /** + * Gets a list of all registered users. + * + * @return The list of all users. Can be empty. + */ + List getAll(); + + /** + * Gets a list of registered users with a specific role. + * + * @param role + * The role of the user. + * @return A list of all users with the given role. Can be empty. + */ + List getAllByRole(UserRole role); + + /** + * Gets a user by its email. + * + * @param email + * The users email + * @return The user with the given email. + * @throws NotFoundException + * If ne User was found. + */ + User getByEmail(String email) throws NotFoundException; + + /** + * Gets a user by its id. + * + * @param id + * The id of the user + * @return The user with the given id. + * @throws NotFoundException + * If ne User was found. + */ + User getById(Long id) throws NotFoundException; + + /** + * Updates a user. + * + * @param user + * The user to update + * @throws ValidationException + * If the user could not be updated because the object has invalid properties. + */ + void update(User user) throws ValidationException; + + /** + * Deletes a user from the database. Admins can only be deleted if there is more than one available. + * + * @param id + * The id of the user to delete + * @throws NotFoundException + * If the user to delete was not found (and thus not deleted). + */ + void delete(Long id) throws NotFoundException; + +} diff --git a/main/src/main/java/de/learnlib/alex/core/dao/UserDAOImpl.java b/main/src/main/java/de/learnlib/alex/core/dao/UserDAOImpl.java new file mode 100644 index 000000000..de1eded30 --- /dev/null +++ b/main/src/main/java/de/learnlib/alex/core/dao/UserDAOImpl.java @@ -0,0 +1,170 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package de.learnlib.alex.core.dao; + +import de.learnlib.alex.core.entities.User; +import de.learnlib.alex.core.entities.UserRole; +import de.learnlib.alex.exceptions.NotFoundException; +import de.learnlib.alex.utils.HibernateUtil; +import de.learnlib.alex.utils.ValidationExceptionHelper; +import org.hibernate.Session; +import org.hibernate.criterion.Restrictions; +import org.hibernate.exception.GenericJDBCException; +import org.springframework.stereotype.Repository; + +import javax.validation.ConstraintViolationException; +import javax.validation.ValidationException; +import java.util.List; + +/** + * Implementation of a UserDAO using Hibernate. + */ +@Repository +public class UserDAOImpl implements UserDAO { + + @Override + public void create(User user) throws ValidationException { + Session session = HibernateUtil.getSession(); + HibernateUtil.beginTransaction(); + + try { + session.save(user); + HibernateUtil.commitTransaction(); + } catch (ConstraintViolationException e) { + HibernateUtil.rollbackTransaction(); + throw ValidationExceptionHelper.createValidationException("User was not created:", e); + } catch (org.hibernate.exception.ConstraintViolationException e) { + HibernateUtil.rollbackTransaction(); + throw new ValidationException("User was not created: " + e.getMessage(), e); + } catch (GenericJDBCException e) { + throw new javax.validation.ValidationException( + "The User was not created because it did not pass the validation!", e); + } + } + + @Override + public List getAll() { + Session session = HibernateUtil.getSession(); + HibernateUtil.beginTransaction(); + + @SuppressWarnings("unchecked") + List users = session.createCriteria(User.class).list(); + + HibernateUtil.commitTransaction(); + return users; + } + + @Override + public List getAllByRole(UserRole role) { + Session session = HibernateUtil.getSession(); + HibernateUtil.beginTransaction(); + + @SuppressWarnings("unchecked") + List users = session.createCriteria(User.class) + .add(Restrictions.eq("role", role)) + .list(); + + HibernateUtil.commitTransaction(); + return users; + } + + @Override + public User getById(Long id) throws NotFoundException { + Session session = HibernateUtil.getSession(); + HibernateUtil.beginTransaction(); + + User user; + try { + user = get(session, id); + } catch (NotFoundException e) { + HibernateUtil.rollbackTransaction(); + throw e; + } + + HibernateUtil.commitTransaction(); + return user; + } + + @Override + public User getByEmail(String email) throws NotFoundException { + Session session = HibernateUtil.getSession(); + HibernateUtil.beginTransaction(); + + User user = (User) session.createCriteria(User.class) + .add(Restrictions.eq("email", email)) + .uniqueResult(); + + if (user == null) { + HibernateUtil.rollbackTransaction(); + throw new NotFoundException("Could not find the user with the email '" + email + "'!"); + } + + HibernateUtil.commitTransaction(); + return user; + } + + @Override + public void update(User user) throws ValidationException { + Session session = HibernateUtil.getSession(); + HibernateUtil.beginTransaction(); + + session.update(user); + + HibernateUtil.commitTransaction(); + } + + @Override + public void delete(Long id) throws NotFoundException { + Session session = HibernateUtil.getSession(); + HibernateUtil.beginTransaction(); + + User user; + try { + user = get(session, id); + } catch (NotFoundException e) { + HibernateUtil.rollbackTransaction(); + throw e; + } + + // make sure there is at least one registered admin + if (user.getRole().equals(UserRole.ADMIN)) { + + @SuppressWarnings("unchecked") + List admins = session.createCriteria(User.class) + .add(Restrictions.eq("role", UserRole.ADMIN)) + .list(); + + if (admins.size() == 1) { + throw new NotFoundException("There has to be at least one admin left"); + } + } + + session.delete(user); + HibernateUtil.commitTransaction(); + } + + private User get(Session session, Long id) throws NotFoundException { + User user = session.get(User.class, id); + + if (user == null) { + throw new NotFoundException("Could not find the user with the ID '" + id + "'!"); + } + + return user; + } + +} diff --git a/main/src/main/java/de/learnlib/alex/core/dao/package-info.java b/main/src/main/java/de/learnlib/alex/core/dao/package-info.java index 6a068c22c..66ec1857f 100644 --- a/main/src/main/java/de/learnlib/alex/core/dao/package-info.java +++ b/main/src/main/java/de/learnlib/alex/core/dao/package-info.java @@ -1,3 +1,19 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + /** * This package contains the actual bridge between the ALEX and the DB. */ diff --git a/main/src/main/java/de/learnlib/alex/core/entities/Counter.java b/main/src/main/java/de/learnlib/alex/core/entities/Counter.java index c0b9e0fe2..e840c12b5 100644 --- a/main/src/main/java/de/learnlib/alex/core/entities/Counter.java +++ b/main/src/main/java/de/learnlib/alex/core/entities/Counter.java @@ -1,11 +1,29 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package de.learnlib.alex.core.entities; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; import org.hibernate.annotations.NaturalId; import org.hibernate.validator.constraints.NotBlank; import javax.persistence.Entity; +import javax.persistence.FetchType; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.ManyToOne; @@ -17,6 +35,7 @@ * A simple counter class. */ @Entity +@JsonPropertyOrder(alphabetic = true) public class Counter implements Serializable { /** to be serializable. */ @@ -28,6 +47,11 @@ public class Counter implements Serializable { @JsonIgnore private Long counterId; + /** The user the counter belongs to. */ + @ManyToOne(fetch = FetchType.EAGER, optional = false) + @JsonIgnore + private User user; + /** The project the counter belongs to. */ @ManyToOne @NaturalId @@ -44,6 +68,34 @@ public class Counter implements Serializable { @NotNull private Integer value; + /** + * @return The current user that owns the counter. + */ + @JsonIgnore + public User getUser() { + return user; + } + + /** + * @param user The new user to own the counter. + */ + @JsonIgnore + public void setUser(User user) { + this.user = user; + } + + /** + * @return The ID of the user the counter belongs to. + */ + @JsonProperty("user") + public Long getUserId() { + if (user == null) { + return 0L; + } + + return user.getId(); + } + /** * Get the project in which the counter is used in.. * @return The related project. diff --git a/main/src/main/java/de/learnlib/alex/core/entities/ExecuteResult.java b/main/src/main/java/de/learnlib/alex/core/entities/ExecuteResult.java index 66822fb17..e47f6940c 100644 --- a/main/src/main/java/de/learnlib/alex/core/entities/ExecuteResult.java +++ b/main/src/main/java/de/learnlib/alex/core/entities/ExecuteResult.java @@ -1,3 +1,19 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package de.learnlib.alex.core.entities; /** @@ -33,16 +49,16 @@ public Integer getFailedActionNumber() { * @param failedActionNumber * Number to indicate the failed action . Must be null when OK. */ - public void setFailedActionNumber(Integer failedActionNumber) { + void setFailedActionNumber(Integer failedActionNumber) { this.failedActionNumber = failedActionNumber; } @Override public String toString() { - if (this == FAILED) { - return this.name() + "(" + (failedActionNumber + 1) + ")"; + if (failedActionNumber == null) { + return this.name(); // most likely OK } else { - return this.name(); + return this.name() + "(" + (failedActionNumber + 1) + ")"; } } } diff --git a/main/src/main/java/de/learnlib/alex/core/entities/IdRevisionPair.java b/main/src/main/java/de/learnlib/alex/core/entities/IdRevisionPair.java index 95725512c..7b6ffe073 100644 --- a/main/src/main/java/de/learnlib/alex/core/entities/IdRevisionPair.java +++ b/main/src/main/java/de/learnlib/alex/core/entities/IdRevisionPair.java @@ -1,3 +1,19 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package de.learnlib.alex.core.entities; import javax.persistence.Embeddable; @@ -112,6 +128,6 @@ public int hashCode() { @Override public String toString() { - return "(" + id + "/" + revision + ")"; + return "<" + id + ":" + revision + ">"; } } diff --git a/main/src/main/java/de/learnlib/alex/core/entities/LearnerConfiguration.java b/main/src/main/java/de/learnlib/alex/core/entities/LearnerConfiguration.java index 0fc768d73..b603b3e07 100644 --- a/main/src/main/java/de/learnlib/alex/core/entities/LearnerConfiguration.java +++ b/main/src/main/java/de/learnlib/alex/core/entities/LearnerConfiguration.java @@ -1,14 +1,29 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package de.learnlib.alex.core.entities; -import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import de.learnlib.alex.core.learner.connectors.WebBrowser; -import javax.persistence.Transient; import javax.validation.constraints.Size; import java.io.Serializable; -import java.util.LinkedList; -import java.util.List; +import java.util.HashSet; +import java.util.Set; /** * Entity to hold the information and parameters to configure a learn process. @@ -19,54 +34,38 @@ public class LearnerConfiguration extends LearnerResumeConfiguration implements /** to be serializable. */ private static final long serialVersionUID = -5130245647384793948L; + /** The maximum length of a comment. */ + private static final int MAX_COMMENT_LENGTH = 255; + /** * Link to the Symbols that are used during the learning. * @requiredField */ - @Transient - @JsonProperty("symbols") - private List symbolsAsIdRevisionPairs; - - /** - * The actual list of Symbols used during the learning. - * Only used internally. - */ - @Transient - @JsonIgnore - private List symbols; + private Set symbolsAsIdRevisionPairs; /** * Link to the Symbols that should be used as a reset Symbol. * @requiredField */ - @Transient - @JsonProperty("resetSymbol") private IdRevisionPair resetSymbolAsIdRevisionPair; - /** - * The actual Symbols that should be used as a reset Symbol. - * Only used internally. - */ - @Transient - @JsonIgnore - private Symbol resetSymbol; - /** * The algorithm to be used during the learning. * @requiredField */ private LearnAlgorithms algorithm; + /** The browser to use during the learn process. */ + private WebBrowser browser; + /** A shot comment to describe the learn set up. */ - @Size(max = 255) private String comment; /** * Default constructor. */ public LearnerConfiguration() { - super(); - this.symbolsAsIdRevisionPairs = new LinkedList<>(); + this.symbolsAsIdRevisionPairs = new HashSet<>(); this.algorithm = LearnAlgorithms.TTT; this.comment = ""; } @@ -76,7 +75,12 @@ public LearnerConfiguration() { * * @return A List of IdRevisionPair referring to symbols that must be used during the learning. */ - public List getSymbolsAsIdRevisionPairs() { + @JsonProperty("symbols") + public Set getSymbolsAsIdRevisionPairs() { + if (symbolsAsIdRevisionPairs == null || symbolsAsIdRevisionPairs.isEmpty()) { + symbolsAsIdRevisionPairs = new HashSet<>(); + } + return symbolsAsIdRevisionPairs; } @@ -86,34 +90,17 @@ public List getSymbolsAsIdRevisionPairs() { * @param symbolsAsIdRevisionPairs * The List of IdRevisionPairs to refer to symbols that must be used during the learning. */ - public void setSymbolsAsIdRevisionPairs(List symbolsAsIdRevisionPairs) { + @JsonProperty("symbols") + public void setSymbolsAsIdRevisionPairs(Set symbolsAsIdRevisionPairs) { this.symbolsAsIdRevisionPairs = symbolsAsIdRevisionPairs; } - /** - * Get the list of Symbols that must be used for the learning process. - * - * @return The list of Symbols. - */ - public List getSymbols() { - return symbols; - } - - /** - * Set a list of symbols to be used for the learning process. - * - * @param symbols - * The new list of Symbols. - */ - public void setSymbols(List symbols) { - this.symbols = symbols; - } - /** * Get the IdRevisionPair of the reset symbol. * * @return The link to the reset symbol. */ + @JsonProperty("resetSymbol") public IdRevisionPair getResetSymbolAsIdRevisionPair() { return resetSymbolAsIdRevisionPair; } @@ -128,29 +115,11 @@ public void setResetSymbolAsIdRevisionPair(IdRevisionPair resetSymbolAsIdRevisio this.resetSymbolAsIdRevisionPair = resetSymbolAsIdRevisionPair; } - /** - * Get the actual reset symbol. - * - * @return The reset symbol. - */ - public Symbol getResetSymbol() { - return resetSymbol; - } - - /** - * Set the reset symbol. This updates not the IdRevisionPair of the reset symbol. - * @param resetSymbol The new reset symbol. - */ - public void setResetSymbol(Symbol resetSymbol) { - this.resetSymbol = resetSymbol; - } - /** * Get the LearnerAlgorithm that should be used for the learning process. * * @return The selected LearnerAlgorithm. */ - @Transient public LearnAlgorithms getAlgorithm() { return algorithm; } @@ -165,11 +134,30 @@ public void setAlgorithm(LearnAlgorithms algorithm) { this.algorithm = algorithm; } + /** + * @return The browser to use for the learning. + */ + public WebBrowser getBrowser() { + if (browser == null) { + return WebBrowser.HTMLUNITDRIVER; + } else { + return browser; + } + } + + /** + * @param browser The new browser to use for the learning process. + */ + public void setBrowser(WebBrowser browser) { + this.browser = browser; + } + /** * Get the current comment for the learn setup. * * @return The current comment. */ + @Size(max = MAX_COMMENT_LENGTH) public String getComment() { return comment; } @@ -185,14 +173,4 @@ public void setComment(String comment) { this.comment = comment; } - /** - * Update the configuration based on the different parameters in the ResumeConfiguration. - * - * @param configuration - * Resume Configuration that specifies the new parameters for this Configuration - */ - public void updateConfiguration(LearnerResumeConfiguration configuration) { - this.maxAmountOfStepsToLearn = configuration.maxAmountOfStepsToLearn; - this.eqOracle = configuration.eqOracle; - } } diff --git a/main/src/main/java/de/learnlib/alex/core/entities/LearnerResult.java b/main/src/main/java/de/learnlib/alex/core/entities/LearnerResult.java index cece3f461..7a682c5ee 100644 --- a/main/src/main/java/de/learnlib/alex/core/entities/LearnerResult.java +++ b/main/src/main/java/de/learnlib/alex/core/entities/LearnerResult.java @@ -1,35 +1,50 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package de.learnlib.alex.core.entities; -import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyOrder; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; +import de.learnlib.alex.core.entities.learnlibproxies.AlphabetProxy; import de.learnlib.alex.core.entities.learnlibproxies.CompactMealyMachineProxy; -import de.learnlib.oracles.DefaultQuery; +import de.learnlib.alex.core.learner.connectors.WebBrowser; import net.automatalib.automata.transout.MealyMachine; -import net.automatalib.words.Alphabet; -import net.automatalib.words.Word; -import net.automatalib.words.impl.SimpleAlphabet; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.hibernate.annotations.Cascade; +import org.hibernate.annotations.CascadeType; import org.hibernate.annotations.NaturalId; import javax.persistence.Column; -import javax.persistence.Embeddable; import javax.persistence.Embedded; import javax.persistence.Entity; +import javax.persistence.Enumerated; import javax.persistence.GeneratedValue; import javax.persistence.Id; +import javax.persistence.ManyToMany; import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; +import javax.persistence.OrderBy; import javax.persistence.Transient; -import java.io.IOException; import java.io.Serializable; -import java.util.Date; +import java.util.HashSet; +import java.util.LinkedList; import java.util.List; import java.util.Objects; +import java.util.Set; /** * Entity class to store the result of a test run, i.e. the outcome of a learn iteration and must not be the final @@ -45,177 +60,45 @@ public class LearnerResult implements Serializable { /** to be serializable. */ private static final long serialVersionUID = 4619722174562257862L; - /** Use the logger for the server part. */ - private static final Logger LOGGER = LogManager.getLogger("server"); - - /** - * Embeddable statistics object to hold all the statistics together. - */ - @Embeddable - @JsonPropertyOrder(alphabetic = true) - public static class Statistics implements Serializable { - - /** to be serializable. */ - private static final long serialVersionUID = -5221139436025380739L; - - /** - * Date and Time when the learning step was started. - * The format is conform with the ISO 8601 (JavaScript-Style). - */ - @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS+00:00", timezone = "UTC") - private Date startTime; - - /** The duration of the learn step. */ - private long duration; - - /** The amount of equivalence queries.. */ - private long eqsUsed; - - /** The amount of membership queries/ SUL resets. */ - private long mqsUsed; - - /** The amount of actual symbols called during the learning process. */ - private long symbolsUsed; - - /** - * Default constructor. - */ - public Statistics() { - this.startTime = new Date(0); - } - - /** - * Get the start time of the learn step. - * - * @return The start time. - */ - public Date getStartTime() { - return startTime; - } - - /** - * Set the start time of the learn step. - * - * @param startTime - * The new start time. - */ - public void setStartTime(Date startTime) { - this.startTime = startTime; - } - - /** - * Get the duration the learn step took. - * - * @return The duration of the learn step. - */ - public long getDuration() { - return duration; - } - - /** - * Set how long the learn step took. - * - * @param duration - * The new duration. - */ - public void setDuration(long duration) { - this.duration = duration; - } - - /** - * Get the amount of equivalence oracles used during the learning. - * - * @return The amount of eq oracles. - */ - public long getEqsUsed() { - return eqsUsed; - } - - /** - * Set the amount of equivalence oracles used during the learning. - * - * @param eqsUsed - * The new amount of eq oracles. - */ - public void setEqsUsed(long eqsUsed) { - this.eqsUsed = eqsUsed; - } - - /** - * Get the amount of resets done while learning. - * - * @return The amount of resets during the learn step. - */ - public long getMqsUsed() { - return mqsUsed; - } - - /** - * Set the amount of resets done while learning. - * - * @param mqsUsed - * The amount of resets during the learn step. - */ - public void setMqsUsed(long mqsUsed) { - this.mqsUsed = mqsUsed; - } - - /** - * Get the total amount of symbols executed during the learning. - * - * @return The total amount of symbols used. - */ - public long getSymbolsUsed() { - return symbolsUsed; - } - - /** - * Set the total amount of symbols executed during the learning. - * - * @param symbolsUsed - * The new amount of symbols used during the learning. - */ - public void setSymbolsUsed(long symbolsUsed) { - this.symbolsUsed = symbolsUsed; - } - } - /** The id of the LearnerResult in the DB. */ private Long id; + /** The user of the LearnerResult. */ + private User user; + /** The reference to the Project the test run belongs to. */ private Project project; /** The test no. within a Project which lead to the result. */ private Long testNo; - /** The step no. within a test run which lead to the result. */ - private Long stepNo; + /** The steps of the LearnerResult. */ + private List steps; - /** Buffer for the JSON which represents the result. */ - private String json; + /** The reset symbol to use during the learning. */ + private Symbol resetSymbol; - /** Internal helper field to determine if the json needs to be recalculated. */ - @JsonIgnore - private boolean jsonChanged; + /** The symbols to use during the learning. */ + private Set symbols; - /** The LearnerConfiguration which was used to create the result. */ - private LearnerConfiguration configuration; + /** The Alphabet used while learning. */ + private AlphabetProxy sigma; - /** The statistics of the result. */ - private Statistics statistics; + /** The algorithm to use during the learning. */ + private LearnAlgorithms algorithm; - /** The Alphabet used while learning. */ - private Alphabet sigma; + /** The browser to use during the learning. */ + private WebBrowser browser; + + /** A comment to describe the intention / setting of the learn process. + * This field is optional. */ + private String comment; /** The hypothesis of the result. */ private CompactMealyMachineProxy hypothesis; - /** The last found counterexample. */ - private DefaultQuery> counterExample; - - /** This is an optional property and can contain things like the internal data structure. */ - private String algorithmInformation; + /** The statistics of the result. */ + private Statistics statistics; /** * If this field is set some sort of error occurred during the learning. @@ -228,8 +111,10 @@ public void setSymbolsUsed(long symbolsUsed) { * Default constructor. */ public LearnerResult() { - this.configuration = new LearnerConfiguration(); - this.jsonChanged = true; + this.symbols = new HashSet<>(); + this.steps = new LinkedList<>(); + this.browser = WebBrowser.HTMLUNITDRIVER; + this.comment = ""; this.statistics = new Statistics(); } @@ -253,7 +138,36 @@ public Long getId() { */ public void setId(Long id) { this.id = id; - this.jsonChanged = true; + } + + /** + * @return Get the user of the result. + */ + @NaturalId + @ManyToOne + @JsonIgnore + public User getUser() { + return user; + } + + /** + * @param user Set a new user for the result. + */ + public void setUser(User user) { + this.user = user; + } + + /** + * @return Get the ID of the user related to the result. + */ + @Transient + @JsonProperty("user") + public Long getUserId() { + if (user == null) { + return 0L; + } + + return user.getId(); } /** @@ -263,7 +177,6 @@ public void setId(Long id) { */ @NaturalId @ManyToOne - //@JoinColumn(name = "projectId") @JsonIgnore public Project getProject() { return project; @@ -277,7 +190,6 @@ public Project getProject() { */ public void setProject(Project project) { this.project = project; - this.jsonChanged = true; } /** @@ -295,18 +207,6 @@ public Long getProjectId() { } } - /** - * Set the related Project new by a project id. - * - * @param projectId - * The id of the new Project. - */ - @JsonProperty("project") - public void setProjectId(Long projectId) { - this.project = new Project(projectId); - this.jsonChanged = true; - } - /** * Get the no. of the test run the result took place in. * @@ -326,73 +226,83 @@ public Long getTestNo() { */ public void setTestNo(Long testNo) { this.testNo = testNo; - this.jsonChanged = true; } /** - * Get the step number of the result, i.e. the number of the result within the test run. - * - * @return The step no. of the result within the test run. + * @return Get the steps of the result. */ - @NaturalId - @Column(nullable = false) - public Long getStepNo() { - return stepNo; + @OneToMany(mappedBy = "result") + @Cascade({CascadeType.SAVE_UPDATE, CascadeType.REMOVE}) + @OrderBy("stepNo ASC") + public List getSteps() { + return steps; } /** - * Set a new step number of the result, i.e. the number of the result within the test run. - * - * @param stepNo - * The new step no. of the result within the test run. + * @param steps The new list of steps for the result. + */ + public void setSteps(List steps) { + this.steps = steps; + } + + /** + * @return The reset symbol used during the learning. */ - public void setStepNo(Long stepNo) { - this.stepNo = stepNo; - this.jsonChanged = true; + @ManyToOne + @JsonIgnore + public Symbol getResetSymbol() { + return resetSymbol; } /** - * Get the LearnerConfiguration used to create the result. + * @param resetSymbol The new reset symbol to use during the learning. + */ + public void setResetSymbol(Symbol resetSymbol) { + this.resetSymbol = resetSymbol; + } + + /** + * Return the reset symbol as ID and revision pair for the JSON output. * - * @return The LearnerConfiguration used during the learning which lead to the result. + * @return The ID and revision pair of the reset symbol. */ - @JsonProperty("configuration") @Transient - public LearnerConfiguration getConfiguration() { - return configuration; + @JsonProperty("resetSymbol") + public IdRevisionPair getResetSymbolAsIdRevisionPair() { + if (resetSymbol == null) { + return new IdRevisionPair(); + } else { + return resetSymbol.getIdRevisionPair(); + } } /** - * Set the LearnerConfiguration used while learning which lead to the result. - * - * @param configuration - * The new LearnerConfiguration. + * @return Get the symbols used during the learning. */ - @JsonProperty("configuration") - public void setConfiguration(LearnerConfiguration configuration) { - this.configuration = configuration; - this.jsonChanged = true; + @ManyToMany + @JsonIgnore + public Set getSymbols() { + return symbols; } /** - * Get the statistic of this learn step. - * - * @return The learning statistics. + * @param symbols The new set of symbols used during the learning. */ - @Embedded - public Statistics getStatistics() { - return statistics; + public void setSymbols(Set symbols) { + this.symbols = symbols; } /** - * Set a new statistics object for the learning result. + * Return the set of symbol as List of ID and revision pairs for the JSON output. * - * @param statistics - * The new statistics. + * @return The ID and revision pairs of the symbols. */ - public void setStatistics(Statistics statistics) { - this.statistics = statistics; - this.jsonChanged = true; + @Transient + @JsonProperty("symbols") + public List getSymbolAsIdRevisionPair() { + List pairs = new LinkedList<>(); + symbols.stream().map(Symbol::getIdRevisionPair).forEach(pairs::add); + return pairs; } /** @@ -400,9 +310,9 @@ public void setStatistics(Statistics statistics) { * * @return The Alphabet of the learning process & the hypothesis. */ - @Transient @JsonProperty("sigma") - public Alphabet getSigma() { + @Column(name = "sigma", columnDefinition = "BLOB") + public AlphabetProxy getSigma() { return sigma; } @@ -413,24 +323,52 @@ public Alphabet getSigma() { * The new Alphabet. */ @JsonIgnore - public void setSigma(Alphabet sigma) { + public void setSigma(AlphabetProxy sigma) { this.sigma = sigma; - this.jsonChanged = true; } /** - * Create and set the Alphabet used during learning and used in the hypotheses through a List of String. - * - * @param sigmaAsList - * The Alphabet encoded as List of String. + * @return The algorithm to use during the learning. */ - @JsonProperty("sigma") - public void createSigmaFrom(List sigmaAsList) { - this.sigma = new SimpleAlphabet<>(); + @Enumerated + public LearnAlgorithms getAlgorithm() { + return algorithm; + } - this.sigma.addAll(sigmaAsList); + /** + * @param algorithm The new algorithm to use during the learning. + */ + public void setAlgorithm(LearnAlgorithms algorithm) { + this.algorithm = algorithm; + } - this.jsonChanged = true; + /** + * @return The browser to use during the learning. + */ + @Enumerated + public WebBrowser getBrowser() { + return browser; + } + + /** + * @param browser The new browser to use during the learning. + */ + public void setBrowser(WebBrowser browser) { + this.browser = browser; + } + + /** + * @return The comment to describe the result. Can be empty. + */ + public String getComment() { + return comment; + } + + /** + * @param comment The new comment to describe the result. Can be empty. + */ + public void setComment(String comment) { + this.comment = comment; } /** @@ -438,7 +376,7 @@ public void createSigmaFrom(List sigmaAsList) { * * @return The hypothesis (as proxy) of the result. */ - @Transient + @Embedded @JsonProperty("hypothesis") public CompactMealyMachineProxy getHypothesis() { return hypothesis; @@ -453,7 +391,6 @@ public CompactMealyMachineProxy getHypothesis() { @JsonProperty("hypothesis") public void setHypothesis(CompactMealyMachineProxy hypothesis) { this.hypothesis = hypothesis; - this.jsonChanged = true; } /** @@ -462,67 +399,30 @@ public void setHypothesis(CompactMealyMachineProxy hypothesis) { * @param mealyMachine * The new hypothesis as MealyMachine from the LearnLib. */ - public void createHypothesisFrom(MealyMachine mealyMachine) { - this.hypothesis = CompactMealyMachineProxy.createFrom(mealyMachine, sigma); - this.jsonChanged = true; - } - - /** - * Get the latest counterexample that was found.. - * - * @return The latest counterexample or null. - */ - @JsonIgnore @Transient - public DefaultQuery> getCounterExample() { - return counterExample; - } - - /** - * Get the latest counterexample as string. - * - * @return The last counterexample or an empty string. - */ - @JsonProperty("counterExample") - @Transient - public String getCounterExampleAsString() { - if (counterExample == null) { - return ""; - } else { - return counterExample.toString(); - } - } - - /** - * Set the latest counterexample new. - * - * @param counterExample - * The new counterexample. - */ - public void setCounterExample(DefaultQuery> counterExample) { - this.counterExample = counterExample; - this.jsonChanged = true; + @JsonIgnore + public void createHypothesisFrom(MealyMachine mealyMachine) { + this.hypothesis = CompactMealyMachineProxy.createFrom(mealyMachine, sigma.createAlphabet()); } /** - * Get more (internal) information about the algorithm used during the learning. + * Get the statistic of this learn step. * - * @return More (internal) information of the algorithm as string. + * @return The learning statistics. */ - @Column(columnDefinition = "CLOB") - public String getAlgorithmInformation() { - return algorithmInformation; + @Embedded + public Statistics getStatistics() { + return statistics; } /** - * Set the internal or other information about the algorithm. + * Set a new statistics object for the learning result. * - * @param algorithmInformation - * The new information about the algorithm. + * @param statistics + * The new statistics. */ - public void setAlgorithmInformation(String algorithmInformation) { - this.algorithmInformation = algorithmInformation; - this.jsonChanged = true; + public void setStatistics(Statistics statistics) { + this.statistics = statistics; } /** @@ -530,37 +430,12 @@ public void setAlgorithmInformation(String algorithmInformation) { * * @return The current error text (can be null). */ + @Column @JsonProperty("errorText") - @Column(columnDefinition = "CLOB") public String getErrorText() { return errorText; } - /** - * Did some kind of error occurred during the learning, i.e. the error text property is set. - * - * @return true if the result represents a failed learning process; null otherwise. - */ - @JsonProperty("error") - @Transient - public Boolean isError() { - if (errorText == null) { - return null; // null instead of false, so that it will not appear in the JSON - } else { - return Boolean.TRUE; - } - } - - /** - * Does nothing but prevents {@link #setJSON(String)} from throwing an error when the learner has an error that - * results in the reset symbol and the symbols not being fetched. - * - * @param error - dummy - */ - public void setError(Boolean error){ - // do nothing :) - } - /** * Set an error text as part of the learning result. * If a error text is set, it also implies that something during the learning went wrong and @@ -571,99 +446,46 @@ public void setError(Boolean error){ */ public void setErrorText(String errorText) { this.errorText = errorText; - this.jsonChanged = true; } - /** - * Get the result as JSON. - * This method buffers the generated JSON. - * - * @return The result encoded as JSON data. - */ - @Column(columnDefinition = "CLOB") - @JsonIgnore - public String getJSON() { - if (jsonChanged) { - this.json = generateJSON(); - this.jsonChanged = false; - } - - return json; - } - /** - * Generate the JSON of this object. - * - * @return This Object encoded as JSON. - */ - private String generateJSON() { - try { - ObjectMapper objectMapper = new ObjectMapper(); - json = objectMapper.writeValueAsString(this); - return json; - } catch (JsonProcessingException e) { - LOGGER.warn("could not generate the JSON for the result '" + this.toString() + "'.", e); - return "{}"; - } - } /** - * Set the result via JSON. This method will not only remember the JSON as String but will also parse the JSON and - * set all other field according to the JSON data! + * Did some kind of error occurred during the learning, i.e. the error text property is set. * - * @param newJSON - * The new result encoded in JSON data. + * @return true if the result represents a failed learning process; null otherwise. */ - @JsonIgnore - public void setJSON(String newJSON) { - try { - ObjectMapper objectMapper = new ObjectMapper(); - LearnerResult newResult = objectMapper.readValue(newJSON, LearnerResult.class); - setProject(newResult.getProject()); - setTestNo(newResult.getTestNo()); - setStepNo(newResult.getStepNo()); - setStatistics(newResult.statistics); - setConfiguration(newResult.getConfiguration()); - setSigma(newResult.getSigma()); - setHypothesis(newResult.getHypothesis()); - setCounterExample(newResult.getCounterExample()); - setAlgorithmInformation(newResult.getAlgorithmInformation()); - - this.json = newJSON; - this.jsonChanged = false; - } catch (IOException e) { - LOGGER.info("could not read the JSON '" + newJSON + "' for a LearnerResult.", e); + @Transient + @JsonProperty("error") + public Boolean isError() { + if (errorText == null) { + return null; // null instead of false, so that it will not appear in the JSON + } else { + return Boolean.TRUE; } } - //CHECKSTYLE.OFF: AvoidInlineConditionals|MagicNumber|NeedBraces - auto generated by IntelliJ IDEA + //CHECKSTYLE.OFF: NeedBraces|OperatorWrap - auto generated by IntelliJ IDEA @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; - - LearnerResult result = (LearnerResult) o; - - if (!Objects.equals(stepNo, result.stepNo)) return false; - if (!Objects.equals(testNo, result.testNo)) return false; - if (project != null ? !project.equals(result.project) : result.project != null) return false; - - return true; + LearnerResult that = (LearnerResult) o; + return Objects.equals(user, that.user) && + Objects.equals(project, that.project) && + Objects.equals(testNo, that.testNo); } @Override public int hashCode() { - int result = project != null ? project.hashCode() : 0; - result = 31 * result + (int) (testNo ^ (testNo >>> 32)); - result = 31 * result + (int) (stepNo ^ (stepNo >>> 32)); - return result; + return Objects.hash(user, project, testNo); } - //CHECKSTYLE.ON: AvoidInlineConditionals|MagicNumber|NeedBraces + //CHECKSTYLE.ON: NeedBraces|OperatorWrap @Override public String toString() { - return "[LearnerResult " + id + "] " + getProjectId() + " / " + testNo + " / " + stepNo + ": " - + sigma + ", " + hypothesis; + return "[LearnerResult " + id + "] " + getUserId() + " / " + getProjectId() + " / " + testNo + " / " + + ": " + sigma + ", " + hypothesis; } } diff --git a/main/src/main/java/de/learnlib/alex/core/entities/LearnerResultStep.java b/main/src/main/java/de/learnlib/alex/core/entities/LearnerResultStep.java new file mode 100644 index 000000000..b859e5cd0 --- /dev/null +++ b/main/src/main/java/de/learnlib/alex/core/entities/LearnerResultStep.java @@ -0,0 +1,425 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package de.learnlib.alex.core.entities; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import de.learnlib.alex.core.entities.learnlibproxies.CompactMealyMachineProxy; +import de.learnlib.alex.core.entities.learnlibproxies.DefaultQueryProxy; +import de.learnlib.alex.core.entities.learnlibproxies.eqproxies.AbstractEquivalenceOracleProxy; +import net.automatalib.automata.transout.MealyMachine; +import org.hibernate.annotations.NaturalId; + +import javax.persistence.Column; +import javax.persistence.Embedded; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.ManyToOne; +import javax.persistence.Transient; +import javax.validation.constraints.Min; +import java.io.Serializable; +import java.util.Objects; + +/** + * Entity class to store the result of a test run, i.e. the outcome of a learn iteration and must not be the final + * result. + * The result contains the configuration of the learning process, the resulting hypothesis and some meta data + * (duration, #EQ, ...). + */ +@Entity +@JsonPropertyOrder(alphabetic = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +public class LearnerResultStep implements Serializable { + + /** to be serializable. */ + private static final long serialVersionUID = -6932946318109366918L; + + /** The user of the LearnerResult. */ + private User user; + + /** The id of the LearnerResult in the DB. */ + private Long id; + + /** The reference to the Project the test run belongs to. */ + private Project project; + + /** The result the step is part of. */ + private LearnerResult result; + + /** The step no. within a test run which lead to the result. */ + private Long stepNo; + + /** The type of EQ oracle to find a counter example. */ + private AbstractEquivalenceOracleProxy eqOracle; + + /** The amount of steps to learn without user interaction. */ + private int stepsToLearn; + + /** The hypothesis of the result. */ + private CompactMealyMachineProxy hypothesis; + + /** The statistics of the result. */ + private Statistics statistics; + + /** The last found counterexample. */ + private DefaultQueryProxy counterExample; + + /** This is an optional property and can contain things like the internal data structure. */ + private String algorithmInformation; + + /** + * If this field is set some sort of error occurred during the learning. + * In this case this field stores more information about the error. + * All other field can still have data, that are valid to some extend and should only be used carefully. + */ + private String errorText; + + /** + * Default constructor. + */ + public LearnerResultStep() { + this.statistics = new Statistics(); + } + + /** + * Get the ID of the result used in the DB. + * + * @return The ID of teh result. + */ + @Id + @GeneratedValue + @JsonIgnore + public Long getId() { + return id; + } + + /** + * Set a new ID for the result in the DB. + * + * @param id + * The new ID for the result. + */ + public void setId(Long id) { + this.id = id; + } + + /** + * @return The current user of the step. + */ + @NaturalId + @ManyToOne + @JsonIgnore + public User getUser() { + return user; + } + + /** + * @param user The new user for the step. + */ + public void setUser(User user) { + this.user = user; + } + + /** + * @param userId The ID of the user related to the step. + */ + @JsonProperty("user") + public void setUserId(Long userId) { + user = new User(userId); + } + + /** + * Get the project the result belongs to. + * + * @return The connected Project. + */ + @NaturalId + @ManyToOne + @JsonIgnore + public Project getProject() { + return project; + } + + /** + * Set a new reference to a Project the result belongs to. + * + * @param project + * The new Project the result is connected with. + */ + public void setProject(Project project) { + this.project = project; + } + + /** + * @return The current LearnResult of the step. + */ + @NaturalId + @ManyToOne + @JsonIgnore + public LearnerResult getResult() { + return result; + } + + /** + * @param result The new LearnResult for the step. + */ + public void setResult(LearnerResult result) { + this.result = result; + } + + /** + * Get the step number of the result, i.e. the number of the result within the test run. + * + * @return The step no. of the result within the test run. + */ + @NaturalId + @Column(nullable = false) + public Long getStepNo() { + return stepNo; + } + + /** + * Set a new step number of the result, i.e. the number of the result within the test run. + * + * @param stepNo + * The new step no. of the result within the test run. + */ + public void setStepNo(Long stepNo) { + this.stepNo = stepNo; + } + + /** + * @return The current eq oracle strategy of the step. + */ + @Column(columnDefinition = "BLOB") + public AbstractEquivalenceOracleProxy getEqOracle() { + return eqOracle; + } + + /** + * @param eqOracle The new eq oracle strategy for the step. + */ + public void setEqOracle(AbstractEquivalenceOracleProxy eqOracle) { + this.eqOracle = eqOracle; + } + + /** + * @return The max amount of steps to learn without user interaction. + */ + @Min(-1) + public int getStepsToLearn() { + return stepsToLearn; + } + + /** + * @param stepsToLearn The new max amount of steps to learn without user interaction. + */ + public void setStepsToLearn(int stepsToLearn) { + this.stepsToLearn = stepsToLearn; + } + + /** + * The hypothesis (as proxy) which is one of the core information of the result. + * + * @return The hypothesis (as proxy) of the result. + */ + @Embedded + @JsonProperty("hypothesis") + public CompactMealyMachineProxy getHypothesis() { + return hypothesis; + } + + /** + * Set a new hypothesis (as proxy) for the result. + * + * @param hypothesis + * The new hypothesis (as proxy). + */ + @JsonProperty("hypothesis") + public void setHypothesis(CompactMealyMachineProxy hypothesis) { + this.hypothesis = hypothesis; + } + + /** + * Set a new hypothesis (as proxy) for the result based on a MealyMachine from the LearnLib. + * + * @param mealyMachine + * The new hypothesis as MealyMachine from the LearnLib. + */ + @Transient + @JsonIgnore + public void createHypothesisFrom(MealyMachine mealyMachine) { + this.hypothesis = CompactMealyMachineProxy.createFrom(mealyMachine, result.getSigma().createAlphabet()); + } + + /** + * Get the statistic of this learn step. + * + * @return The learning statistics. + */ + @Embedded + public Statistics getStatistics() { + return statistics; + } + + /** + * Set a new statistics object for the learning result. + * + * @param statistics + * The new statistics. + */ + public void setStatistics(Statistics statistics) { + this.statistics = statistics; + } + + /** + * Get the latest counterexample that was found.. + * + * @return The latest counterexample or null. + */ + @Embedded + @JsonIgnore + public DefaultQueryProxy getCounterExample() { + return counterExample; + } + + /** + * Set the latest counterexample new. + * + * @param counterExample + * The new counterexample. + */ + public void setCounterExample(DefaultQueryProxy counterExample) { + this.counterExample = counterExample; + } + + /** + * Get the latest counterexample as string. + * + * @return The last counterexample or an empty string. + */ + @Transient + @JsonProperty("counterExample") + public String getCounterExampleAsString() { + if (counterExample == null) { + return ""; + } else { + return counterExample.createDefaultProxy().toString(); + } + } + + /** + * Get more (internal) information about the algorithm used during the learning. + * + * @return More (internal) information of the algorithm as string. + */ + @Column(columnDefinition = "CLOB") + public String getAlgorithmInformation() { + return algorithmInformation; + } + + /** + * Set the internal or other information about the algorithm. + * + * @param algorithmInformation + * The new information about the algorithm. + */ + public void setAlgorithmInformation(String algorithmInformation) { + this.algorithmInformation = algorithmInformation; + } + + /** + * Get the current error text of the learning process. + * + * @return The current error text (can be null). + */ + @Column + @JsonProperty("errorText") + public String getErrorText() { + return errorText; + } + + /** + * Did some kind of error occurred during the learning, i.e. the error text property is set. + * + * @return true if the result represents a failed learning process; null otherwise. + */ + @Transient + @JsonProperty("error") + public Boolean isError() { + if (errorText == null) { + return null; // null instead of false, so that it will not appear in the JSON + } else { + return Boolean.TRUE; + } + } + + /** + * Set an error text as part of the learning result. + * If a error text is set, it also implies that something during the learning went wrong and + * {@link #isError()} will return True. + * + * @param errorText + * The new error text. + */ + public void setErrorText(String errorText) { + this.errorText = errorText; + } + + //CHECKSTYLE.OFF: NeedBraces|OperatorWrap - auto generated by IntelliJ IDEA + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + LearnerResultStep that = (LearnerResultStep) o; + return Objects.equals(user, that.user) && + Objects.equals(project, that.project) && + Objects.equals(result, that.result) && + Objects.equals(stepNo, that.stepNo); + } + + @Override + public int hashCode() { + return Objects.hash(user, project, result, stepNo); + } + //CHECKSTYLE.ON: NeedBraces|OperatorWrap + + @Override + public String toString() { + Long userId = 0L; + if (user != null) { + userId = user.getId(); + } + + Long projectId = 0L; + if (project != null) { + projectId = project.getId(); + } + + Long testNo = 0L; + if (result != null) { + testNo = result.getTestNo(); + } + + return "[LearnerResultStep " + this.id + "] " + userId + " / " + projectId + " / " + testNo + " / " + + stepNo + ": " + hypothesis; + } + +} diff --git a/main/src/main/java/de/learnlib/alex/core/entities/LearnerResumeConfiguration.java b/main/src/main/java/de/learnlib/alex/core/entities/LearnerResumeConfiguration.java index 4a864b5f0..cb3f77d6a 100644 --- a/main/src/main/java/de/learnlib/alex/core/entities/LearnerResumeConfiguration.java +++ b/main/src/main/java/de/learnlib/alex/core/entities/LearnerResumeConfiguration.java @@ -1,5 +1,22 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package de.learnlib.alex.core.entities; +import com.fasterxml.jackson.annotation.JsonProperty; import de.learnlib.alex.core.entities.learnlibproxies.eqproxies.AbstractEquivalenceOracleProxy; import de.learnlib.alex.core.entities.learnlibproxies.eqproxies.MealyRandomWordsEQOracleProxy; @@ -10,15 +27,51 @@ */ public class LearnerResumeConfiguration { + /** The ID of the user related to the configuration. */ + private Long userId; + + /** The ID of the project related to the configuration. */ + private Long projectId; + /** * The type of EQ oracle to find a counter example. * @requiredField */ protected AbstractEquivalenceOracleProxy eqOracle; + /** + * @return The ID of the user related to the configuration. + */ + @JsonProperty("user") + public Long getUserId() { + return userId; + } + + /** + * @param userId The new ID of the user related to the configuration. + */ + public void setUserId(Long userId) { + this.userId = userId; + } + + /** + * @return The ID of the project related to the configuration. + */ + @JsonProperty("project") + public Long getProjectId() { + return projectId; + } + + /** + * @param projectId The new ID of the project related to the configuration. + */ + public void setProjectId(Long projectId) { + this.projectId = projectId; + } + /** How many steps should the learner take before stopping the process. - * Must be greater or equal to 0. - * 0 := Do not stop until no counter example is found. */ + * Must be greater or equal to -1, but not 0. + * -1 := Do not stop until no counter example is found. */ protected int maxAmountOfStepsToLearn; /** @@ -26,7 +79,7 @@ public class LearnerResumeConfiguration { */ public LearnerResumeConfiguration() { this.eqOracle = new MealyRandomWordsEQOracleProxy(); - this.maxAmountOfStepsToLearn = 0; // infinity + this.maxAmountOfStepsToLearn = -1; // infinity } /** @@ -73,6 +126,12 @@ public void setMaxAmountOfStepsToLearn(int maxAmountOfStepsToLearn) { * If the configuration is invalid. */ public void checkConfiguration() throws IllegalArgumentException { + if (maxAmountOfStepsToLearn < -1) { + throw new IllegalArgumentException("The MaxAmountOfStep property must not be less than -1."); + } else if (maxAmountOfStepsToLearn == 0) { + throw new IllegalArgumentException("The MaxAmountOfStep property must not be equal to 0."); + } + if (eqOracle == null) { throw new IllegalArgumentException("Could not find an EQ oracle."); } diff --git a/main/src/main/java/de/learnlib/alex/core/entities/LearnerStatus.java b/main/src/main/java/de/learnlib/alex/core/entities/LearnerStatus.java index 415f3a7a8..15347e1f0 100644 --- a/main/src/main/java/de/learnlib/alex/core/entities/LearnerStatus.java +++ b/main/src/main/java/de/learnlib/alex/core/entities/LearnerStatus.java @@ -1,12 +1,27 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package de.learnlib.alex.core.entities; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyOrder; -import de.learnlib.alex.core.learner.Learner; -import java.util.Date; +import java.time.ZonedDateTime; /** * Class to provide information about the current learn process. @@ -15,53 +30,86 @@ @JsonInclude(JsonInclude.Include.NON_NULL) public class LearnerStatus { - /** - * The learn process to observe. - */ - @JsonIgnore - private final Learner learner; + /** Is the Learner active? */ + private final boolean active; + + /** The ID of the Project this test status belongs to. */ + private final Long projectId; + + /** The current test no. */ + private final Long testNo; + + /** The statistics of the learner. */ + private final LearnerStatusStatistics statistics; /** - * Statistics Class for the learner status + * Statistics Class for the learner status. */ @JsonPropertyOrder(alphabetic = true) - private class LearnerStatusStatistics { + public static class LearnerStatusStatistics { - private Long startTime; + /** When the learning started. */ + private final ZonedDateTime startDate; - private Long mqsUsed; + /** How many MQ where issued. */ + private final Long mqsUsed; - public LearnerStatusStatistics(Long startTime, Long mqsUsed) { - this.startTime = startTime; + /** + * @param startDate When the learning was started. + * @param mqsUsed How many MQ where issued during the learning. + */ + public LearnerStatusStatistics(ZonedDateTime startDate, Long mqsUsed) { + this.startDate = startDate; this.mqsUsed = mqsUsed; } - public Long getStartTime() { - return startTime; + /** + * @return When the learning was started. + */ + @JsonIgnore + public ZonedDateTime getStartDate() { + return startDate; + } + + /** + * @return When the learning was started as nice ISO 8160 string, including milliseconds and zone. + */ + @JsonProperty("startDate") + public String getStartDateAsString() { + return startDate.format(Statistics.DATE_TIME_FORMATTER); } + /** + * @return How many MQ where issued during the learning. + */ public Long getMqsUsed() { return mqsUsed; } } /** - * Constructor. - * - * @param learner The learner to get the information from. + * Constructor for a status of an incative thread. */ - public LearnerStatus(Learner learner) { - this.learner = learner; + public LearnerStatus() { + this.active = false; + this.projectId = null; + this.testNo = null; + this.statistics = null; } /** - * Get the learner which is observed. + * Constructor for a status of an active thread. * - * @return The current learner. + * @param learnerResult + * The result that contain the interesting statistics and information for the status.. */ - @JsonIgnore - public Learner getLearner() { - return learner; + public LearnerStatus(LearnerResult learnerResult) { + this.active = true; + + this.projectId = learnerResult.getProjectId(); + this.testNo = learnerResult.getTestNo(); + this.statistics = new LearnerStatusStatistics(learnerResult.getStatistics().getStartDate(), + learnerResult.getStatistics().getMqsUsed()); } /** @@ -69,9 +117,8 @@ public Learner getLearner() { * * @return true if the learn process is active; false otherwise */ - @JsonProperty public boolean isActive() { - return learner.isActive(); + return active; } /** @@ -82,15 +129,7 @@ public boolean isActive() { */ @JsonProperty("project") public Long getProjectId() { - if (isActive()) { - LearnerResult result = learner.getResult(); - if (result == null) { - return 0L; - } - return result.getProjectId(); - } else { - return null; - } + return projectId; } /** @@ -99,25 +138,17 @@ public Long getProjectId() { * * @return The active test no in the project. */ - @JsonProperty public Long getTestNo() { - if (isActive()) { - LearnerResult result = learner.getResult(); - if (result == null) { - return 0L; - } - return result.getTestNo(); - } else { - return null; - } + return testNo; } - @JsonProperty + /** + * Additional Statistics of the learn process. + * Only included if the learner is active. + * + * @return Additional statistics, e.g. the start date. + */ public LearnerStatusStatistics getStatistics() { - if (!learner.isActive()) { - return null; - } else { - return new LearnerStatusStatistics(learner.getStartTime(), learner.getMQsUsed()); - } + return statistics; } } diff --git a/main/src/main/java/de/learnlib/alex/core/entities/Project.java b/main/src/main/java/de/learnlib/alex/core/entities/Project.java index 7676e0470..234195ed9 100644 --- a/main/src/main/java/de/learnlib/alex/core/entities/Project.java +++ b/main/src/main/java/de/learnlib/alex/core/entities/Project.java @@ -1,19 +1,34 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package de.learnlib.alex.core.entities; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import de.learnlib.alex.core.entities.validators.UniqueProjectName; import org.hibernate.annotations.Cascade; import org.hibernate.annotations.CascadeType; import org.hibernate.validator.constraints.NotBlank; -import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.GeneratedValue; import javax.persistence.Id; +import javax.persistence.ManyToOne; import javax.persistence.OneToMany; import javax.persistence.OneToOne; import java.io.Serializable; @@ -26,72 +41,95 @@ */ @Entity @JsonInclude(JsonInclude.Include.NON_NULL) +@UniqueProjectName public class Project implements Serializable { - /** to be serializable. */ + /** + * to be serializable. + */ private static final long serialVersionUID = -6760395646972200067L; - /** Use the logger for the server part. */ - private static final Logger LOGGER = LogManager.getLogger("server"); - - /** The project ID. */ + /** + * The project ID. + */ @Id @GeneratedValue private Long id; + /** The user that owns this project. */ + @ManyToOne(fetch = FetchType.EAGER, optional = false) + @JsonIgnore + private User user; + /** * The name of the project. This property is required & must be unique. + * * @requiredField */ @NotBlank - @Column(unique = true) private String name; /** * The root URL of the project. + * * @requiredField */ @NotBlank private String baseUrl; - /** A text to describe the Project. */ + /** + * A text to describe the Project. + */ private String description; - /** The list of groups in the project. */ + /** + * The list of groups in the project. + */ @OneToMany(mappedBy = "project", fetch = FetchType.LAZY) - @Cascade({ CascadeType.SAVE_UPDATE, CascadeType.REMOVE }) + @Cascade({CascadeType.SAVE_UPDATE, CascadeType.REMOVE}) @JsonProperty("groups") private Set groups; - /** The default group of the project. */ + /** + * The default group of the project. + */ @OneToOne - @Cascade({ CascadeType.SAVE_UPDATE, CascadeType.REMOVE }) - @JsonIgnore + @Cascade({CascadeType.SAVE_UPDATE, CascadeType.REMOVE}) private SymbolGroup defaultGroup; - /** The next id for a group in the project. */ + /** + * The next id for a group in the project. + */ @JsonIgnore private Long nextGroupId; - /** The symbols used to test. */ + /** + * The symbols used to test. + */ @OneToMany(mappedBy = "project", fetch = FetchType.LAZY) - @Cascade({ CascadeType.SAVE_UPDATE, CascadeType.REMOVE }) + @Cascade({CascadeType.SAVE_UPDATE, CascadeType.REMOVE}) @JsonProperty("symbols") private Set symbols; - /** The next id for a symbol in this project. */ + /** + * The next id for a symbol in this project. + */ @JsonIgnore private Long nextSymbolId; - /** The results of the test for the project. */ + /** + * The results of the test for the project. + */ @OneToMany(mappedBy = "project", fetch = FetchType.LAZY) - @Cascade({ CascadeType.SAVE_UPDATE, CascadeType.REMOVE }) + @Cascade({CascadeType.SAVE_UPDATE, CascadeType.REMOVE}) @JsonIgnore private Set testResults; - /** The counters of the project. */ + /** + * The counters of the project. + */ @OneToMany(mappedBy = "project", fetch = FetchType.LAZY) - @Cascade({ CascadeType.SAVE_UPDATE, CascadeType.REMOVE }) + @Cascade({CascadeType.SAVE_UPDATE, CascadeType.REMOVE}) @JsonIgnore private Set counters; @@ -104,9 +142,8 @@ public Project() { /** * Constructor which set the ID. - * - * @param projectId - * The ID. + * + * @param projectId The ID. */ public Project(Long projectId) { this.id = projectId; @@ -118,7 +155,7 @@ public Project(Long projectId) { /** * Get the ID of the project. - * + * * @return The ID. */ public Long getId() { @@ -127,17 +164,52 @@ public Long getId() { /** * Set the ID of this project. - * - * @param id - * The new ID. + * + * @param id The new ID. */ public void setId(Long id) { this.id = id; } + /** + * @return The user that owns the project. + */ + @JsonIgnore + public User getUser() { + return user; + } + + /** + * @param user The new user that owns the project. + */ + @JsonIgnore + public void setUser(User user) { + this.user = user; + } + + /** + * @return The ID of the user, which is needed for the JSON. + */ + @JsonProperty("user") + public Long getUserId() { + if (user == null) { + return 0L; + } else { + return user.getId(); + } + } + + /** + * @param userId The new ID of the user, which is needed for the JSON. + */ + @JsonProperty("user") + public void setUserId(Long userId) { + user = new User(userId); + } + /** * Get the name of this project. - * + * * @return The name. */ public String getName() { @@ -146,9 +218,8 @@ public String getName() { /** * Set a new name for the project. The name must be there and be unique. - * - * @param name - * The new name. + * + * @param name The new name. */ public void setName(String name) { this.name = name; @@ -166,8 +237,7 @@ public String getBaseUrl() { /** * Set the base URL of the Project. * - * @param baseUrl - * The new base URL. + * @param baseUrl The new base URL. */ public void setBaseUrl(String baseUrl) { this.baseUrl = baseUrl; @@ -185,8 +255,7 @@ public String getDescription() { /** * Set the description of this project. * - * @param description - * The new description. + * @param description The new description. */ public void setDescription(String description) { this.description = description; @@ -205,8 +274,7 @@ public Set getGroups() { /** * Set a new set of groups that are used in the project. * - * @param groups - * The new set of groups. + * @param groups The new set of groups. */ public void setGroups(Set groups) { this.groups = groups; @@ -215,8 +283,7 @@ public void setGroups(Set groups) { /** * Add one group to the project. * - * @param group - * The group to add. + * @param group The group to add. */ public void addGroup(SymbolGroup group) { this.groups.add(group); @@ -235,16 +302,11 @@ public SymbolGroup getDefaultGroup() { /** * Set a new default group for the project.. * - * @param defaultGroup - * The new default group. + * @param defaultGroup The new default group. */ public void setDefaultGroup(SymbolGroup defaultGroup) { - if (defaultGroup == null) { - return; - } - if (groups != null && !groups.contains(defaultGroup)) { - groups.add(defaultGroup); + addGroup(defaultGroup); } this.defaultGroup = defaultGroup; } @@ -262,8 +324,7 @@ public Long getNextGroupId() { /** * Set a new ID that a group in the project should have. * - * @param nextGroupId - * The new next group id. + * @param nextGroupId The new next group id. */ public void setNextGroupId(Long nextGroupId) { this.nextGroupId = nextGroupId; @@ -271,7 +332,7 @@ public void setNextGroupId(Long nextGroupId) { /** * Get the set of symbols in the project. - * + * * @return The Set of Symbols. */ @JsonIgnore @@ -280,8 +341,7 @@ public Collection getSymbols() { } /** - * @param symbols - * the symbols to set + * @param symbols the symbols to set */ public void setSymbols(Set symbols) { this.symbols = symbols; @@ -291,7 +351,7 @@ public void setSymbols(Set symbols) { * Add a Symbol to the Project and set the Project in the Symbol. * This only establishes the bidirectional relation does nothing else, * e.g. it does not take care of the right id. - * + * * @param symbol The Symbol to add. */ public void addSymbol(Symbol symbol) { @@ -301,7 +361,7 @@ public void addSymbol(Symbol symbol) { /** * Get the amount of Symbols related to this project. - * + * * @return The current count of Symbols. If the project has no symbols (== null) 0 is returned. */ @JsonProperty("symbolAmount") @@ -325,8 +385,7 @@ public Long getNextSymbolId() { /** * Set the ID the next symbol in this project should have. * - * @param nextSymbolId - * The next free id for a symbol. + * @param nextSymbolId The next free id for a symbol. */ public void setNextSymbolId(Long nextSymbolId) { this.nextSymbolId = nextSymbolId; @@ -337,7 +396,7 @@ public void setNextSymbolId(Long nextSymbolId) { * * @return The test results of the project. */ - @JsonIgnore + @JsonProperty("testResults") public Set getTestResults() { return testResults; } @@ -345,14 +404,29 @@ public Set getTestResults() { /** * Set the related test results for this project. * - * @param testResults - * The test result of the project. + * @param testResults The test result of the project. */ @JsonIgnore public void setTestResults(Set testResults) { this.testResults = testResults; } + /** + * @return All the counters of the Project. + */ + @JsonProperty("counters") + public Set getCounters() { + return counters; + } + + /** + * @param counters The new set of counters for the project. + */ + @JsonIgnore + public void setCounters(Set counters) { + this.counters = counters; + } + //CHECKSTYLE.OFF: AvoidInlineConditionals|MagicNumber|NeedBraces - auto generated by IntelliJ IDEA @Override public boolean equals(Object o) { @@ -374,7 +448,7 @@ public int hashCode() { @Override public String toString() { - return "[Project " + id + "] " + name + "(" + baseUrl + ")"; + return "[Project " + id + "]: " + user + ", " + name + "(" + baseUrl + ")"; } } diff --git a/main/src/main/java/de/learnlib/alex/core/entities/Statistics.java b/main/src/main/java/de/learnlib/alex/core/entities/Statistics.java new file mode 100644 index 000000000..f447f9983 --- /dev/null +++ b/main/src/main/java/de/learnlib/alex/core/entities/Statistics.java @@ -0,0 +1,198 @@ +package de.learnlib.alex.core.entities; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; + +import javax.persistence.Embeddable; +import javax.persistence.Transient; +import java.io.Serializable; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; + +/** + * Embeddable statistics object to hold all the statistics together. + */ +@Embeddable +@JsonPropertyOrder(alphabetic = true) +public class Statistics implements Serializable { + + /** to be serializable. */ + private static final long serialVersionUID = -5221139436025380739L; + + /** Standard DateTimeFormatter that will create a nice ISO 8160 string with milliseconds and a time zone. */ + public static final DateTimeFormatter DATE_TIME_FORMATTER + = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSxxx"); + + /** A ZonedDateTime object based at the unix time 0. */ + public static final ZonedDateTime UNIX_TIME_START = ZonedDateTime.parse("1970-01-01T00:00:00.000+00:00"); + + /** + * Date and Time when the learning step was started. + * The format is conform with the ISO 8601 (JavaScript-Style). + */ + @JsonIgnore + private ZonedDateTime startDate; + + /** + * The 'time' the test started in nanoseconds. + * This is just internally user to calculate the duration and the Java value for the used 'nanoTime' has no + * real world meaning. + */ + @JsonIgnore + private long startTime; + + /** The duration of the learn step. */ + private long duration; + + /** The amount of equivalence queries. */ + private long eqsUsed; + + /** The amount of membership queries/ SUL resets. */ + private long mqsUsed; + + /** The amount of actual symbols called during the learning process. */ + private long symbolsUsed; + + /** + * Default constructor. + */ + public Statistics() { + this.startDate = UNIX_TIME_START; + this.startTime = 0; + this.duration = 0; + } + + /** + * Get the start time of the learn step. + * + * @return The start time. + */ + public long getStartTime() { + return startTime; + } + + /** + * Set the start time. + * + * @param startTime + * The time in ns + */ + public void setStartTime(long startTime) { + this.startTime = startTime; + } + + /** + * Get the date of when the test run started. + * + * @return The date + */ + @JsonIgnore + public ZonedDateTime getStartDate() { + return startDate; + } + + /** + * Set the date when the test started. + * + * @param startDate + * The date object + */ + @JsonIgnore + public void setStartDate(ZonedDateTime startDate) { + this.startDate = startDate; + } + + /** + * @return When the learning was started as nice ISO 8160 string, including milliseconds and zone. + */ + @Transient + @JsonProperty("startDate") + public String getStartDateAsString() { + return startDate.format(DATE_TIME_FORMATTER); + } + + /** + * @param dateAsString The point in time when the learning was started. + */ + @JsonProperty("startDate") + public void setStartDateByString(String dateAsString) { + this.startDate = ZonedDateTime.parse(dateAsString); + } + + /** + * Get the duration the learn step took. + * + * @return The duration of the learn step. + */ + public long getDuration() { + return duration; + } + + /** + * Set how long the learn step took. + * + * @param duration + * The new duration. + */ + public void setDuration(long duration) { + this.duration = duration; + } + + /** + * Get the amount of equivalence oracles used during the learning. + * + * @return The amount of eq oracles. + */ + public long getEqsUsed() { + return eqsUsed; + } + + /** + * Set the amount of equivalence oracles used during the learning. + * + * @param eqsUsed + * The new amount of eq oracles. + */ + public void setEqsUsed(long eqsUsed) { + this.eqsUsed = eqsUsed; + } + + /** + * Get the amount of resets done while learning. + * + * @return The amount of resets during the learn step. + */ + public long getMqsUsed() { + return mqsUsed; + } + + /** + * Set the amount of resets done while learning. + * + * @param mqsUsed + * The amount of resets during the learn step. + */ + public void setMqsUsed(long mqsUsed) { + this.mqsUsed = mqsUsed; + } + + /** + * Get the total amount of symbols executed during the learning. + * + * @return The total amount of symbols used. + */ + public long getSymbolsUsed() { + return symbolsUsed; + } + + /** + * Set the total amount of symbols executed during the learning. + * + * @param symbolsUsed + * The new amount of symbols used during the learning. + */ + public void setSymbolsUsed(long symbolsUsed) { + this.symbolsUsed = symbolsUsed; + } +} diff --git a/main/src/main/java/de/learnlib/alex/core/entities/Symbol.java b/main/src/main/java/de/learnlib/alex/core/entities/Symbol.java index d3922bdea..cf386531d 100644 --- a/main/src/main/java/de/learnlib/alex/core/entities/Symbol.java +++ b/main/src/main/java/de/learnlib/alex/core/entities/Symbol.java @@ -1,8 +1,26 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package de.learnlib.alex.core.entities; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import de.learnlib.alex.core.entities.validators.UniqueSymbolAbbreviation; +import de.learnlib.alex.core.entities.validators.UniqueSymbolName; import de.learnlib.alex.core.learner.connectors.ConnectorManager; import de.learnlib.api.SULException; import de.learnlib.mapper.api.ContextExecutableInput; @@ -33,13 +51,18 @@ */ @Entity @JsonPropertyOrder(alphabetic = true) +@UniqueSymbolName +@UniqueSymbolAbbreviation public class Symbol implements ContextExecutableInput, Serializable { /** to be serializable. */ private static final long serialVersionUID = 7987585761829495962L; - /** Use the logger for the server part. */ - private static final Logger LOGGER = LogManager.getLogger("server"); + /** The maximum lenght of the abbreviation. */ + public static final int MAX_ABBREVIATION_LENGTH = 15; + + /** Use the learner logger. */ + private static final Logger LOGGER = LogManager.getLogger("learner"); /** The ID of the Symbol in the DB. */ @Id @@ -47,10 +70,15 @@ public class Symbol implements ContextExecutableInput symbols; /** @@ -70,6 +95,30 @@ public SymbolGroup() { this.symbols = new HashSet<>(); } + @JsonIgnore + public void setUser(User user) { + this.user = user; + } + + @JsonIgnore + public User getUser() { + return user; + } + + @JsonProperty("user") + public Long getUserId() { + if (user == null) { + return 0L; + } else { + return user.getId(); + } + } + + @JsonProperty("user") + public void setUserId(Long userId) { + user = new User(userId); + } + /** * Get the related/ 'parent' project of the group. * @@ -225,6 +274,6 @@ public int hashCode() { @Override public String toString() { - return "SymbolGroup[" + groupId + "] (" + project + ", " + id + "): " + name; + return "SymbolGroup[" + groupId + "]: " + user + ", " + project + ", " + id + ", " + name; } } diff --git a/main/src/main/java/de/learnlib/alex/core/entities/SymbolSet.java b/main/src/main/java/de/learnlib/alex/core/entities/SymbolSet.java index 5a70a93f8..3036822da 100644 --- a/main/src/main/java/de/learnlib/alex/core/entities/SymbolSet.java +++ b/main/src/main/java/de/learnlib/alex/core/entities/SymbolSet.java @@ -1,3 +1,19 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package de.learnlib.alex.core.entities; import com.fasterxml.jackson.annotation.JsonIgnore; diff --git a/main/src/main/java/de/learnlib/alex/core/entities/SymbolVisibilityLevel.java b/main/src/main/java/de/learnlib/alex/core/entities/SymbolVisibilityLevel.java index c004583c7..a406d3ded 100644 --- a/main/src/main/java/de/learnlib/alex/core/entities/SymbolVisibilityLevel.java +++ b/main/src/main/java/de/learnlib/alex/core/entities/SymbolVisibilityLevel.java @@ -1,3 +1,19 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package de.learnlib.alex.core.entities; import org.hibernate.criterion.Criterion; diff --git a/main/src/main/java/de/learnlib/alex/core/entities/UploadableFile.java b/main/src/main/java/de/learnlib/alex/core/entities/UploadableFile.java index a7edb21f6..cac40972b 100644 --- a/main/src/main/java/de/learnlib/alex/core/entities/UploadableFile.java +++ b/main/src/main/java/de/learnlib/alex/core/entities/UploadableFile.java @@ -1,27 +1,73 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package de.learnlib.alex.core.entities; import com.fasterxml.jackson.annotation.JsonProperty; import org.hibernate.validator.constraints.NotBlank; +/** + * Entity to describe an file which was uploaded. + */ public class UploadableFile { + + /** + * The ID of the project the File belongs to. + */ @JsonProperty("project") private Long projectId; + /** + * The name of the file. + */ @NotBlank private String name; + /** + * Get the id of the project of the file. + * + * @return The id of the project. + */ public Long getProjectId() { return projectId; } + /** + * Set the project id of the file. + * + * @param projectId The id of the project. + */ public void setProjectId(Long projectId) { this.projectId = projectId; } + /** + * Get the name of the file. + * + * @return The name of the file. + */ public String getName() { return name; } + /** + * Set the name of the file. + * + * @param name The name of the file. + */ public void setName(String name) { this.name = name; } diff --git a/main/src/main/java/de/learnlib/alex/core/entities/User.java b/main/src/main/java/de/learnlib/alex/core/entities/User.java new file mode 100644 index 000000000..921456f97 --- /dev/null +++ b/main/src/main/java/de/learnlib/alex/core/entities/User.java @@ -0,0 +1,321 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package de.learnlib.alex.core.entities; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.apache.shiro.crypto.SecureRandomNumberGenerator; +import org.apache.shiro.crypto.hash.Sha512Hash; +import org.hibernate.annotations.Cascade; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.OneToMany; +import javax.validation.constraints.NotNull; +import java.io.Serializable; +import java.util.Objects; +import java.util.Set; + +/** + * The model for a user. + */ +@Entity +public class User implements Serializable { + + /** Auto generated id for saving it into the db. */ + private static final long serialVersionUID = -3567360676364330143L; + + /** the number of iterations to perform to create a secure hash. */ + private static final int HASH_ITERATIONS = 2048; + + /** The unique id of the user. */ + @Id + @GeneratedValue + private Long id; + + /** The email address of the user he uses to login. */ + @NotNull + @Column(unique = true) + private String email; + + /** * The hash of the users password. */ + @NotNull + private String password; + + /** The salt that is used to hash the password. */ + private String salt; + + /** The role of the user. */ + private UserRole role; + + /** The projects of the user. */ + @JsonIgnore + @OneToMany(mappedBy = "user", fetch = FetchType.LAZY) + @Cascade({org.hibernate.annotations.CascadeType.SAVE_UPDATE, org.hibernate.annotations.CascadeType.REMOVE}) + private Set projects; + + /** The SymbolGroups in all Projects of the user. */ + @JsonIgnore + @OneToMany(mappedBy = "user", fetch = FetchType.LAZY) + @Cascade({org.hibernate.annotations.CascadeType.SAVE_UPDATE, org.hibernate.annotations.CascadeType.REMOVE}) + private Set symbolGroups; + + /** All Symbols in all Projects / Groups of the user. */ + @JsonIgnore + @OneToMany(mappedBy = "user", fetch = FetchType.LAZY) + @Cascade({org.hibernate.annotations.CascadeType.SAVE_UPDATE, org.hibernate.annotations.CascadeType.REMOVE}) + private Set symbols; + + /** All Actions in all Symbol of the user. */ + @JsonIgnore + @OneToMany(mappedBy = "user", fetch = FetchType.LAZY) + @Cascade({org.hibernate.annotations.CascadeType.SAVE_UPDATE, org.hibernate.annotations.CascadeType.REMOVE}) + private Set actions; + + /** All Counters in all Projects of the user. */ + @JsonIgnore + @OneToMany(mappedBy = "user", fetch = FetchType.LAZY) + @Cascade({org.hibernate.annotations.CascadeType.SAVE_UPDATE, org.hibernate.annotations.CascadeType.REMOVE}) + private Set counters; + + /** All LearnResults in all Projects of the user. */ + @JsonIgnore + @OneToMany(mappedBy = "user", fetch = FetchType.LAZY) + @Cascade({org.hibernate.annotations.CascadeType.SAVE_UPDATE, org.hibernate.annotations.CascadeType.REMOVE}) + private Set learnerResults; + + /** + * Default constructor that gives the user the role of "registered". + */ + public User() { + role = UserRole.REGISTERED; + } + + /** + * Constructor that sets a specific ID and gives the user the role of "registered". + * + * @param id + * The ID of the User. + */ + public User(Long id) { + role = UserRole.REGISTERED; + this.id = id; + } + + /** + * @return The ID of the User in the DB. + */ + public Long getId() { + return id; + } + + /** + * @param id The new ID of the User in the DB. + */ + public void setId(Long id) { + this.id = id; + } + + /** + * @return The current EMail of the User. + */ + public String getEmail() { + return email; + } + + /** + * @param email The new EMail of the User. + */ + public void setEmail(String email) { + this.email = email; + } + + /** + * @return The current Role of the User. + */ + public UserRole getRole() { + return role; + } + + /** + * @param role The new Role of the User. + */ + public void setRole(UserRole role) { + this.role = role; + } + + /** + * @return The hashed password of the User. + */ + @JsonIgnore + @JsonProperty("password") + public String getPassword() { + return password; + } + + /** + * @param password The new (already hashed) password of the User. + */ + public void setPassword(String password) { + this.password = password; + } + + /** + * @param plainPassword The new password of the User as plain text. + */ + @JsonIgnore + public void setEncryptedPassword(String plainPassword) { + this.salt = new SecureRandomNumberGenerator().nextBytes().toBase64(); + this.password = new Sha512Hash(plainPassword, this.salt, HASH_ITERATIONS).toBase64(); + } + + /** + * Checks if the given password equals the password of the user. + * + * @param plainPasswordToCheck + * The password to check. + * @return True, if both passwords matched, false otherwise. + */ + @JsonIgnore + public boolean isValidPassword(String plainPasswordToCheck) { + String hashedPassword = new Sha512Hash(plainPasswordToCheck, this.salt, HASH_ITERATIONS).toBase64(); + return hashedPassword.equals(this.password); + } + + /** + * @return The current salt to ahs the password of the user. + */ + @JsonIgnore + @JsonProperty("salt") + public String getSalt() { + return salt; + } + + /** + * @param salt The new Salt to hash the password of the user. + */ + public void setSalt(String salt) { + this.salt = salt; + } + + /** + * @return All Projects owned by the User. + */ + public Set getProjects() { + return projects; + } + + /** + * @param projects The new set of the Projects owned by the User. + */ + public void setProjects(Set projects) { + this.projects = projects; + } + + /** + * @return All Groups owned by the User. + */ + public Set getSymbolGroups() { + return symbolGroups; + } + + /** + * @param symbolGroups The new set of the Groups owned by the User. + */ + public void setSymbolGroups(Set symbolGroups) { + this.symbolGroups = symbolGroups; + } + + /** + * @return All Symbols owned by the User. + */ + public Set getSymbols() { + return symbols; + } + + /** + * @param symbols The new set of the Symbols owned by the User. + */ + public void setSymbols(Set symbols) { + this.symbols = symbols; + } + + /** + * @return All Actions owned by the User. + */ + public Set getActions() { + return actions; + } + + /** + * @param actions The new set of Actions owned by the User. + */ + public void setActions(Set actions) { + this.actions = actions; + } + + /** + * @return All Counters owned by the User. + */ + public Set getCounters() { + return counters; + } + + /** + * @param counters The new set of Counters owned by the user. + */ + public void setCounters(Set counters) { + this.counters = counters; + } + + /** + * @return All LearnerResults owned by the user. + */ + public Set getLearnerResults() { + return learnerResults; + } + + /** + * @param learnerResults The new set of LearnerResults owned by the User. + */ + public void setLearnerResults(Set learnerResults) { + this.learnerResults = learnerResults; + } + + //CHECKSTYLE.OFF: AvoidInlineConditionals|MagicNumber|NeedBraces - auto generated by IntelliJ IDEA + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + User user = (User) o; + return Objects.equals(id, user.id); + } + + @Override + public int hashCode() { + return Objects.hash(id); + } + //CHECKSTYLE.ON: AvoidInlineConditionals|MagicNumber|NeedBraces + + @Override + public String toString() { + return "<" + id + "> '" + email + "'"; + } +} diff --git a/main/src/main/java/de/learnlib/alex/core/entities/UserRole.java b/main/src/main/java/de/learnlib/alex/core/entities/UserRole.java new file mode 100644 index 000000000..cbd393947 --- /dev/null +++ b/main/src/main/java/de/learnlib/alex/core/entities/UserRole.java @@ -0,0 +1,54 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package de.learnlib.alex.core.entities; + +/** + * Enumeration for User roles. + */ +public enum UserRole { + + /** + * User is default registered user. + */ + REGISTERED, + + /** + * User is administrator with higher privileges. + */ + ADMIN +// +// /** +// * Parse a string into an entry of this enum. +// * +// * @param name The name to parse into an entry. +// * @return The fitting entry of this enum. +// * @throws IllegalArgumentException If the name could not be parsed +// */ +// public static UserRole fromString(String name) throws IllegalArgumentException { +// return UserRole.valueOf(name.toUpperCase()); +// } +// +// /** +// * Transform uppercase enum entry to lowercase +// * +// * @return An enum entry in lowercase +// */ +// @Override +// public String toString() { +// return name().toLowerCase(); +// } +} diff --git a/main/src/main/java/de/learnlib/alex/core/entities/learnlibproxies/AlphabetProxy.java b/main/src/main/java/de/learnlib/alex/core/entities/learnlibproxies/AlphabetProxy.java new file mode 100644 index 000000000..312f8f31a --- /dev/null +++ b/main/src/main/java/de/learnlib/alex/core/entities/learnlibproxies/AlphabetProxy.java @@ -0,0 +1,72 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package de.learnlib.alex.core.entities.learnlibproxies; + +import net.automatalib.words.Alphabet; +import net.automatalib.words.impl.SimpleAlphabet; + +import java.util.Collection; +import java.util.LinkedList; + +/** + * Proxy around an Alphabet. + * The Proxy is needed to make it easier to (de-)serialize the Transition into/ from JSON. + * + * @see net.automatalib.words.Alphabet + */ +public class AlphabetProxy extends LinkedList { + + /** to be serializable. */ + private static final long serialVersionUID = 3404889779588414036L; + + /** + * Default constructor. + * Would be hidden by the other constructor(s) if not explicit written here. + */ + public AlphabetProxy() { + } + + /** + * Constructor that creates a proxy based on an String collection (could be an Alphabet). + * + * @param collection + * The base collection of the new Proxy. + */ + public AlphabetProxy(Collection collection) { + super(collection); + } + + /** + * Create a new Proxy based on the provided Alphabet. + * + * @param input + * The base Alphabet of the new proxy. Can be null. + * @return A new proxy based on the given Alphabet. + */ + public static AlphabetProxy createFrom(Alphabet input) { + return new AlphabetProxy(input); + } + + /** + * Create an Alphabet based on this proxy. + * + * @return A new Alphabet. + */ + public Alphabet createAlphabet() { + return new SimpleAlphabet<>(this); + } +} diff --git a/main/src/main/java/de/learnlib/alex/core/entities/learnlibproxies/CompactMealyMachineProxy.java b/main/src/main/java/de/learnlib/alex/core/entities/learnlibproxies/CompactMealyMachineProxy.java index b8c44682d..8f903f7eb 100644 --- a/main/src/main/java/de/learnlib/alex/core/entities/learnlibproxies/CompactMealyMachineProxy.java +++ b/main/src/main/java/de/learnlib/alex/core/entities/learnlibproxies/CompactMealyMachineProxy.java @@ -1,9 +1,33 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package de.learnlib.alex.core.entities.learnlibproxies; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.type.CollectionType; import net.automatalib.automata.transout.MealyMachine; import net.automatalib.automata.transout.impl.compact.CompactMealy; import net.automatalib.words.Alphabet; +import javax.persistence.Column; +import javax.persistence.Embeddable; +import javax.persistence.Transient; +import java.io.IOException; import java.io.Serializable; import java.util.ArrayList; import java.util.HashMap; @@ -17,11 +41,15 @@ * * @see net.automatalib.automata.transout.impl.compact.CompactMealy */ +@Embeddable public class CompactMealyMachineProxy implements Serializable { /** to be serializable. */ private static final long serialVersionUID = -5155147869595906457L; + /** Create one static ObjectMapper to (de-)serialize the Proxy for the DB. */ + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + /** The states of the machine. */ private List nodes; @@ -91,6 +119,7 @@ private static List readTransitionsFrom(MealyMachin * * @return The stats of the mealy machine. */ + @Transient public List getNodes() { return nodes; } @@ -105,6 +134,39 @@ public void setNodes(List nodes) { this.nodes = nodes; } + /** + * Getter method to interact with the DB, because the Java standard serialization doesn't work. + * + * @return The Nodes of the machine as JSON string. + */ + @Column(name = "nodes") + @JsonIgnore + public String getNodesDB() { + try { + return OBJECT_MAPPER.writeValueAsString(nodes); + } catch (JsonProcessingException e) { + e.printStackTrace(); + return ""; + } + } + + /** + * Setter method to interact with the DB, because the Java standard deserialization doesn't work. + * + * @param nodesAsString + * The Nodes of the machine as JSON string. + */ + @JsonIgnore + public void setNodesDB(String nodesAsString) { + try { + CollectionType valueType = OBJECT_MAPPER.getTypeFactory() + .constructCollectionType(List.class, Integer.class); + this.nodes = OBJECT_MAPPER.readValue(nodesAsString, valueType); + } catch (IOException e) { + e.printStackTrace(); + } + } + /** * Get the initial state of the mealy machine. * @@ -129,6 +191,7 @@ public void setInitNode(Integer initNode) { * * @return The transition of the mealy machine as List of MealyTransitionProxies. */ + @Transient public List getEdges() { return edges; } @@ -143,6 +206,40 @@ public void setEdges(List edges) { this.edges = edges; } + /** + * Getter method to interact with the DB, because the Java standard serialization doesn't work. + * + * @return The Edges of the machine as JSON string. + */ + @Column(name = "edges", columnDefinition = "CLOB") + @JsonIgnore + public String getEdgesDB() { + try { + return OBJECT_MAPPER.writeValueAsString(edges); + } catch (JsonProcessingException e) { + e.printStackTrace(); + return ""; + } + } + + /** + * Setter method to interact with the DB, because the Java standard deserialization doesn't work. + * + * @param edgesAsString + * The Edges of the machine as JSON string. + */ + @JsonIgnore + public void setEdgesDB(String edgesAsString) { + try { + CollectionType valueType = OBJECT_MAPPER.getTypeFactory() + .constructCollectionType(List.class, + CompactMealyTransitionProxy.class); + this.edges = OBJECT_MAPPER.readValue(edgesAsString, valueType); + } catch (IOException e) { + e.printStackTrace(); + } + } + /** * Create a CompactMealy based on the Proxy. * diff --git a/main/src/main/java/de/learnlib/alex/core/entities/learnlibproxies/CompactMealyTransitionProxy.java b/main/src/main/java/de/learnlib/alex/core/entities/learnlibproxies/CompactMealyTransitionProxy.java index 42c0b0035..3549dc9f5 100644 --- a/main/src/main/java/de/learnlib/alex/core/entities/learnlibproxies/CompactMealyTransitionProxy.java +++ b/main/src/main/java/de/learnlib/alex/core/entities/learnlibproxies/CompactMealyTransitionProxy.java @@ -1,3 +1,19 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package de.learnlib.alex.core.entities.learnlibproxies; import java.io.Serializable; diff --git a/main/src/main/java/de/learnlib/alex/core/entities/learnlibproxies/DefaultQueryProxy.java b/main/src/main/java/de/learnlib/alex/core/entities/learnlibproxies/DefaultQueryProxy.java new file mode 100644 index 000000000..43f3d4f90 --- /dev/null +++ b/main/src/main/java/de/learnlib/alex/core/entities/learnlibproxies/DefaultQueryProxy.java @@ -0,0 +1,199 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package de.learnlib.alex.core.entities.learnlibproxies; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import de.learnlib.oracles.DefaultQuery; +import net.automatalib.words.Word; + +import javax.persistence.Column; +import javax.persistence.Embeddable; +import javax.persistence.Transient; +import java.io.IOException; +import java.io.Serializable; +import java.util.LinkedList; +import java.util.List; +import java.util.Objects; + +/** + * Proxy around a DefaultQuery. + * The Proxy is needed to make it easier to (de-)serialize the Transition into/ from JSON. + * + * @see de.learnlib.oracles.DefaultQuery + */ +@Embeddable +public class DefaultQueryProxy implements Serializable { + + /** to be serializable. */ + private static final long serialVersionUID = 4759682392322006213L; + + /** Create one static ObjectMapper to (de-)serialize the Proxy for the DB. */ + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + + /** The Prefix of the query as Strings. */ + private List prefix; + + /** The Suffix of the query as Strings. */ + private List suffix; + + /** The Output of the query as Strings. */ + private List output; + + /** + * Default constructor that creates a new LinkedList for the prefix, the suffix and the output. + */ + public DefaultQueryProxy() { + this.prefix = new LinkedList<>(); + this.suffix = new LinkedList<>(); + this.output = new LinkedList<>(); + } + + /** + * Create a new Proxy based on the provided DefaultQuery. + * + * @param query + * The base Query of the new proxy. Can be null. + * @return The new Proxy around the DefaultQuery.. + */ + public static DefaultQueryProxy createFrom(DefaultQuery> query) { + DefaultQueryProxy newProxy = new DefaultQueryProxy(); + if (query != null) { + newProxy.prefix = query.getPrefix().asList(); + newProxy.suffix = query.getSuffix().asList(); + if (query.getOutput() != null) { + newProxy.output = query.getOutput().asList(); + } + } + + return newProxy; + } + + /** + * @return The Prefix of the Proxy. + */ + @Transient + @JsonProperty + public List getPrefix() { + return prefix; + } + + /** + * @param prefix The new prefix of the Proxy. + */ + public void setPrefix(List prefix) { + this.prefix = prefix; + } + + /** + * @return The Suffix of the Proxy. + */ + @Transient + @JsonProperty + public List getSuffix() { + return suffix; + } + + /** + * @param suffix The new suffix of the Proxy. + */ + public void setSuffix(List suffix) { + this.suffix = suffix; + } + + /** + * @return The Output of the Proxy. + */ + @Transient + @JsonProperty + public List getOutput() { + return output; + } + + /** + * @param output The new output of the Proxy. + */ + public void setOutput(List output) { + this.output = output; + } + + /** + * Getter method to interact with the DB, because the Java standard serialization doesn't work. + * + * @return The Proxy as JSON string. + */ + @Column(name = "counterExample", columnDefinition = "CLOB") + @JsonIgnore + public String getProxyDB() { + try { + return OBJECT_MAPPER.writeValueAsString(this); + } catch (JsonProcessingException e) { + e.printStackTrace(); + return ""; + } + } + + /** + * Setter method to interact with the DB, because the Java standard deserialization doesn't work. + * + * @param proxyAsString + * The Proxy as JSON string. + */ + @JsonIgnore + public void setProxyDB(String proxyAsString) { + try { + DefaultQueryProxy that = OBJECT_MAPPER.readValue(proxyAsString, DefaultQueryProxy.class); + this.prefix = that.prefix; + this.suffix = that.suffix; + this.output = that.output; + } catch (IOException e) { + e.printStackTrace(); + } + } + + /** + * Create a DefaultQuery based on this proxy. + * + * @return The new DefaultQuery. + */ + public DefaultQuery> createDefaultProxy() { + Word prefixAsWord = Word.fromList(prefix); + Word suffixAsWord = Word.fromList(suffix); + Word outputAsWord = Word.fromList(output); + return new DefaultQuery<>(prefixAsWord, suffixAsWord, outputAsWord); + } + + //CHECKSTYLE.OFF: AvoidInlineConditionals|NeedBraces|OperatorWrap - auto generated by IntelliJ IDEA + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + DefaultQueryProxy that = (DefaultQueryProxy) o; + return Objects.equals(prefix, that.prefix) && + Objects.equals(suffix, that.suffix) && + Objects.equals(output, that.output); + } + + @Override + public int hashCode() { + return Objects.hash(prefix, suffix, output); + } + // CHECKSTYLE.OFF: AvoidInlineConditionals|NeedBraces|OperatorWrap + +} diff --git a/main/src/main/java/de/learnlib/alex/core/entities/learnlibproxies/eqproxies/AbstractEquivalenceOracleProxy.java b/main/src/main/java/de/learnlib/alex/core/entities/learnlibproxies/eqproxies/AbstractEquivalenceOracleProxy.java index a837bf1a4..957a9829a 100644 --- a/main/src/main/java/de/learnlib/alex/core/entities/learnlibproxies/eqproxies/AbstractEquivalenceOracleProxy.java +++ b/main/src/main/java/de/learnlib/alex/core/entities/learnlibproxies/eqproxies/AbstractEquivalenceOracleProxy.java @@ -1,3 +1,19 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package de.learnlib.alex.core.entities.learnlibproxies.eqproxies; @@ -8,6 +24,8 @@ import net.automatalib.automata.transout.MealyMachine; import net.automatalib.words.Word; +import java.io.Serializable; + /** * Base class for Proxies around a the different EquivalenceOracles from the LearnLib. * The Proxy is needed to make it easier to (de-)serialize the EQ oracles into/ from JSON. @@ -18,9 +36,13 @@ @JsonSubTypes({ @JsonSubTypes.Type(name = "random_word", value = MealyRandomWordsEQOracleProxy.class), @JsonSubTypes.Type(name = "complete", value = CompleteExplorationEQOracleProxy.class), - @JsonSubTypes.Type(name = "sample", value = SampleEQOracleProxy.class) + @JsonSubTypes.Type(name = "sample", value = SampleEQOracleProxy.class), + @JsonSubTypes.Type(name = "wmethod", value = WMethodEQOracleProxy.class) }) -public abstract class AbstractEquivalenceOracleProxy { +public abstract class AbstractEquivalenceOracleProxy implements Serializable { + + /** to be serializable. */ + private static final long serialVersionUID = 6270462192160289890L; /** * Check if the parameter of the proxy are valid, i.e. it is possible to create a functional EQ oracle out of the diff --git a/main/src/main/java/de/learnlib/alex/core/entities/learnlibproxies/eqproxies/CompleteExplorationEQOracleProxy.java b/main/src/main/java/de/learnlib/alex/core/entities/learnlibproxies/eqproxies/CompleteExplorationEQOracleProxy.java index be31dd458..cc5efbb87 100644 --- a/main/src/main/java/de/learnlib/alex/core/entities/learnlibproxies/eqproxies/CompleteExplorationEQOracleProxy.java +++ b/main/src/main/java/de/learnlib/alex/core/entities/learnlibproxies/eqproxies/CompleteExplorationEQOracleProxy.java @@ -1,3 +1,19 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package de.learnlib.alex.core.entities.learnlibproxies.eqproxies; import com.fasterxml.jackson.annotation.JsonTypeName; @@ -7,6 +23,8 @@ import net.automatalib.automata.transout.MealyMachine; import net.automatalib.words.Word; +import java.io.Serializable; + /** * Proxy around a CompleteExplorationEQOracle. * The Proxy is needed to make it easier to (de-)serialize the Transition into/ from JSON. @@ -14,7 +32,10 @@ * @see de.learnlib.eqtests.basic.CompleteExplorationEQOracle */ @JsonTypeName("complete") -public class CompleteExplorationEQOracleProxy extends AbstractEquivalenceOracleProxy { +public class CompleteExplorationEQOracleProxy extends AbstractEquivalenceOracleProxy implements Serializable { + + /** to be serializable. */ + private static final long serialVersionUID = 8363769818889990904L; /** The minimal depth to explore, i.e. minimal length of words to test. */ private int minDepth; diff --git a/main/src/main/java/de/learnlib/alex/core/entities/learnlibproxies/eqproxies/MealyRandomWordsEQOracleProxy.java b/main/src/main/java/de/learnlib/alex/core/entities/learnlibproxies/eqproxies/MealyRandomWordsEQOracleProxy.java index b4d5b7d83..c3d2e5b75 100644 --- a/main/src/main/java/de/learnlib/alex/core/entities/learnlibproxies/eqproxies/MealyRandomWordsEQOracleProxy.java +++ b/main/src/main/java/de/learnlib/alex/core/entities/learnlibproxies/eqproxies/MealyRandomWordsEQOracleProxy.java @@ -1,3 +1,19 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package de.learnlib.alex.core.entities.learnlibproxies.eqproxies; import com.fasterxml.jackson.annotation.JsonTypeName; @@ -7,6 +23,7 @@ import net.automatalib.automata.transout.MealyMachine; import net.automatalib.words.Word; +import java.io.Serializable; import java.util.Random; /** @@ -16,7 +33,7 @@ * @see de.learnlib.eqtests.basic.RandomWordsEQOracle.MealyRandomWordsEQOracle */ @JsonTypeName("random_word") -public class MealyRandomWordsEQOracleProxy extends AbstractEquivalenceOracleProxy { +public class MealyRandomWordsEQOracleProxy extends AbstractEquivalenceOracleProxy implements Serializable { /** The seed to use for the RNG. */ public static final int RANDOM_SEED = 42; diff --git a/main/src/main/java/de/learnlib/alex/core/entities/learnlibproxies/eqproxies/SampleEQOracleProxy.java b/main/src/main/java/de/learnlib/alex/core/entities/learnlibproxies/eqproxies/SampleEQOracleProxy.java index 5f53026ca..77a374ffc 100644 --- a/main/src/main/java/de/learnlib/alex/core/entities/learnlibproxies/eqproxies/SampleEQOracleProxy.java +++ b/main/src/main/java/de/learnlib/alex/core/entities/learnlibproxies/eqproxies/SampleEQOracleProxy.java @@ -1,3 +1,19 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package de.learnlib.alex.core.entities.learnlibproxies.eqproxies; import com.fasterxml.jackson.annotation.JsonTypeName; @@ -8,6 +24,7 @@ import net.automatalib.words.Word; import org.hibernate.validator.constraints.NotBlank; +import java.io.Serializable; import java.util.LinkedList; import java.util.List; import java.util.stream.Stream; @@ -19,12 +36,18 @@ * @see de.learnlib.eqtests.basic.SampleSetEQOracle */ @JsonTypeName("sample") -public class SampleEQOracleProxy extends AbstractEquivalenceOracleProxy { +public class SampleEQOracleProxy extends AbstractEquivalenceOracleProxy implements Serializable { + + /** to be serializable. */ + private static final long serialVersionUID = -110995671060498443L; /** * Construct to hold a pair of an input and output string. */ - public static class InputOutputPair { + public static class InputOutputPair implements Serializable { + + /** to be serializable. */ + private static final long serialVersionUID = 2200629936714510637L; /** The input. */ @NotBlank @@ -40,23 +63,44 @@ public static class InputOutputPair { public InputOutputPair() { } + /** + * Constructor. + * @param input The input symbol. + * @param output The output symbol. + */ public InputOutputPair(String input, String output) { this.input = input; this.output = output; } + /** + * Get the input. + * @return The input. + */ public String getInput() { return input; } + /** + * Set the input. + * @param input The input. + */ public void setInput(String input) { this.input = input; } + /** + * Get the output. + * @return The output. + */ public String getOutput() { return output; } + /** + * Set the output. + * @param output The output. + */ public void setOutput(String output) { this.output = output; } diff --git a/main/src/main/java/de/learnlib/alex/core/entities/learnlibproxies/eqproxies/WMethodEQOracleProxy.java b/main/src/main/java/de/learnlib/alex/core/entities/learnlibproxies/eqproxies/WMethodEQOracleProxy.java new file mode 100644 index 000000000..194981187 --- /dev/null +++ b/main/src/main/java/de/learnlib/alex/core/entities/learnlibproxies/eqproxies/WMethodEQOracleProxy.java @@ -0,0 +1,82 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package de.learnlib.alex.core.entities.learnlibproxies.eqproxies; + +import com.fasterxml.jackson.annotation.JsonTypeName; +import de.learnlib.api.EquivalenceOracle; +import de.learnlib.eqtests.basic.WMethodEQOracle; +import de.learnlib.oracles.SULOracle; +import net.automatalib.automata.transout.MealyMachine; +import net.automatalib.words.Word; + +import java.io.Serializable; + +/** + * Proxy around a WMethodEQOracle. + * The Proxy is needed to make it easier to (de-)serialize the Transition into/ from JSON. + * + * @see de.learnlib.eqtests.basic.WMethodEQOracle + */ +@JsonTypeName("wmethod") +public class WMethodEQOracleProxy extends AbstractEquivalenceOracleProxy implements Serializable { + + /** to be serializable. */ + private static final long serialVersionUID = 2016142289217760178L; + + /** The maximal depth to explore, i.e. minimal length of words to test. */ + private int maxDepth; + + /** + * Default constructor. + */ + public WMethodEQOracleProxy() { + this.maxDepth = 1; + } + + /** + * Get the maximal depth to explore, i.e. the maximal length of the words to test. + * + * @return The maximal depth to explore. + */ + public int getMaxDepth() { + return maxDepth; + } + + /** + * Set a new maximum for the exploration level. + * + * @param maxDepth + * The new maximal depth to explore. + */ + public void setMaxDepth(int maxDepth) { + this.maxDepth = maxDepth; + } + + + @Override + public void checkParameters() throws IllegalArgumentException { + if (maxDepth < 0) { + throw new IllegalArgumentException("W Method EQ Oracle: max depth must not be negative."); + } + } + + @Override + public EquivalenceOracle, String, Word> createEqOracle( + SULOracle membershipOracle) { + return new WMethodEQOracle.MealyWMethodEQOracle<>(this.maxDepth, membershipOracle); + } +} diff --git a/main/src/main/java/de/learnlib/alex/core/entities/learnlibproxies/eqproxies/package-info.java b/main/src/main/java/de/learnlib/alex/core/entities/learnlibproxies/eqproxies/package-info.java index 14fb7c918..fdade9a95 100644 --- a/main/src/main/java/de/learnlib/alex/core/entities/learnlibproxies/eqproxies/package-info.java +++ b/main/src/main/java/de/learnlib/alex/core/entities/learnlibproxies/eqproxies/package-info.java @@ -1,3 +1,19 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + /** * This package contains a proxy layer for the Equivalence Query classes form the LearnLib to make them easy to use in * our JSON data. diff --git a/main/src/main/java/de/learnlib/alex/core/entities/learnlibproxies/package-info.java b/main/src/main/java/de/learnlib/alex/core/entities/learnlibproxies/package-info.java index d489861c8..868bacc2c 100644 --- a/main/src/main/java/de/learnlib/alex/core/entities/learnlibproxies/package-info.java +++ b/main/src/main/java/de/learnlib/alex/core/entities/learnlibproxies/package-info.java @@ -1,3 +1,19 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + /** * This package contains a proxy layer for the LearnLib classes that are not easy to serialize into JSON or are messing * around with Hibernate. diff --git a/main/src/main/java/de/learnlib/alex/core/entities/package-info.java b/main/src/main/java/de/learnlib/alex/core/entities/package-info.java index 461f80016..a371408f8 100644 --- a/main/src/main/java/de/learnlib/alex/core/entities/package-info.java +++ b/main/src/main/java/de/learnlib/alex/core/entities/package-info.java @@ -1,3 +1,19 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + /** * THis package contains a bunch of Beans, which are used in the REST API and which are persisted in the DB. */ diff --git a/main/src/main/java/de/learnlib/alex/core/entities/validators/UniqueProjectName.java b/main/src/main/java/de/learnlib/alex/core/entities/validators/UniqueProjectName.java new file mode 100644 index 000000000..46352c28e --- /dev/null +++ b/main/src/main/java/de/learnlib/alex/core/entities/validators/UniqueProjectName.java @@ -0,0 +1,47 @@ +package de.learnlib.alex.core.entities.validators; + +import javax.validation.Constraint; +import javax.validation.Payload; +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.ElementType.ANNOTATION_TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * Annotation for the the Project class that checks, that every Project name is unique per User. + * See http://docs.jboss.org/hibernate/validator/5.2/reference/en-US/html/ch06.html for more details. + */ +@Target({ TYPE, ANNOTATION_TYPE }) +@Retention(RUNTIME) +@Constraint(validatedBy = { UniqueProjectNameValidator.class }) +@Documented +public @interface UniqueProjectName { + + /** + * The error message if the constraint is violated. + * A default error message is provided. + * + * @return The error message. + */ + String message() default "{de.learnlib.alex.core.entities.validators.UniqueProjectName.message}"; + + /** + * Attribute to group constrains. + * This group is empty by default. + * + * @return The groups the constraint belongs to + */ + Class[] groups() default { }; + + /** + * Additional payload of the annotation. + * This payload is empty by default. + * + * @return The current payload. + */ + Class[] payload() default { }; + +} diff --git a/main/src/main/java/de/learnlib/alex/core/entities/validators/UniqueProjectNameValidator.java b/main/src/main/java/de/learnlib/alex/core/entities/validators/UniqueProjectNameValidator.java new file mode 100644 index 000000000..05c6bf5af --- /dev/null +++ b/main/src/main/java/de/learnlib/alex/core/entities/validators/UniqueProjectNameValidator.java @@ -0,0 +1,48 @@ +package de.learnlib.alex.core.entities.validators; + +import de.learnlib.alex.core.entities.Project; +import de.learnlib.alex.core.entities.User; +import de.learnlib.alex.utils.HibernateUtil; +import org.hibernate.Session; +import org.hibernate.criterion.Restrictions; + +import javax.validation.ConstraintValidator; +import javax.validation.ConstraintValidatorContext; +import java.util.List; + +/** + * The Validator related to the UniqueProjectName annotation. + */ +public class UniqueProjectNameValidator implements ConstraintValidator { + + @Override + public void initialize(UniqueProjectName validateUniqueNamePerUser) { + } + + @Override + public boolean isValid(Project project, ConstraintValidatorContext constraintValidatorContext) { + String name = project.getName(); + if (name == null || name.isEmpty()) { + return false; + } + + User user = project.getUser(); + if (user == null) { + return false; + } + + Session session = HibernateUtil.getSession(); + List projects = session.createCriteria(Project.class) + .add(Restrictions.eq("name", name)) + .add(Restrictions.eq("user", user)) + .list(); + + for (Project p : projects) { + if (!p.equals(project)) { + return false; + } + } + + return true; + } +} diff --git a/main/src/main/java/de/learnlib/alex/core/entities/validators/UniqueSymbolAbbreviation.java b/main/src/main/java/de/learnlib/alex/core/entities/validators/UniqueSymbolAbbreviation.java new file mode 100644 index 000000000..bd42ed9a9 --- /dev/null +++ b/main/src/main/java/de/learnlib/alex/core/entities/validators/UniqueSymbolAbbreviation.java @@ -0,0 +1,47 @@ +package de.learnlib.alex.core.entities.validators; + +import javax.validation.Constraint; +import javax.validation.Payload; +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.ANNOTATION_TYPE; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * Annotation for the the Symbol class that checks, that every Symbol abbreviation is unique per User and Project. + * See http://docs.jboss.org/hibernate/validator/5.2/reference/en-US/html/ch06.html for more details. + */ +@Target({ TYPE, ANNOTATION_TYPE }) +@Retention(RUNTIME) +@Constraint(validatedBy = { UniqueSymbolAbbreviationValidator.class }) +@Documented +public @interface UniqueSymbolAbbreviation { + + /** + * The error message if the constraint is violated. + * A default error message is provided. + * + * @return The error message. + */ + String message() default "{de.learnlib.alex.core.entities.validators.UniqueSymbolAbbreviation.message}"; + + /** + * Attribute to group constrains. + * This group is empty by default. + * + * @return The groups the constraint belongs to + */ + Class[] groups() default { }; + + /** + * Additional payload of the annotation. + * This payload is empty by default. + * + * @return The current payload. + */ + Class[] payload() default { }; + +} diff --git a/main/src/main/java/de/learnlib/alex/core/entities/validators/UniqueSymbolAbbreviationValidator.java b/main/src/main/java/de/learnlib/alex/core/entities/validators/UniqueSymbolAbbreviationValidator.java new file mode 100644 index 000000000..8a24c5e78 --- /dev/null +++ b/main/src/main/java/de/learnlib/alex/core/entities/validators/UniqueSymbolAbbreviationValidator.java @@ -0,0 +1,58 @@ +package de.learnlib.alex.core.entities.validators; + +import de.learnlib.alex.core.entities.Project; +import de.learnlib.alex.core.entities.Symbol; +import de.learnlib.alex.core.entities.User; +import de.learnlib.alex.utils.HibernateUtil; +import org.hibernate.Session; +import org.hibernate.criterion.Junction; +import org.hibernate.criterion.Restrictions; + +import javax.validation.ConstraintValidator; +import javax.validation.ConstraintValidatorContext; +import java.util.List; + +/** + * The Validator related to the UniqueSymbolAbbreviation annotation. + */ +public class UniqueSymbolAbbreviationValidator implements ConstraintValidator { + + @Override + public void initialize(UniqueSymbolAbbreviation validateUniqueAbbreviationPerUser) { + } + + @Override + public boolean isValid(Symbol symbol, ConstraintValidatorContext constraintValidatorContext) { + String abbreviation = symbol.getAbbreviation(); + User user = symbol.getUser(); + Project project = symbol.getProject(); + + if (abbreviation == null || abbreviation.isEmpty() + || user == null + || project == null) { + return false; + } + + Junction restrictions = Restrictions.conjunction() + .add(Restrictions.eq("abbreviation", abbreviation)) + .add(Restrictions.eq("user", user)) + .add(Restrictions.eq("project", project)); + if (symbol.getId() != null) { + restrictions = restrictions.add(Restrictions.ne("idRevisionPair.id", symbol.getId())); + } + + + Session session = HibernateUtil.getSession(); + List symbols = session.createCriteria(Symbol.class) + .add(restrictions) + .list(); + + for (Symbol s : symbols) { + if (!s.equals(symbol)) { + return false; + } + } + + return true; + } +} diff --git a/main/src/main/java/de/learnlib/alex/core/entities/validators/UniqueSymbolGroupName.java b/main/src/main/java/de/learnlib/alex/core/entities/validators/UniqueSymbolGroupName.java new file mode 100644 index 000000000..966c139d3 --- /dev/null +++ b/main/src/main/java/de/learnlib/alex/core/entities/validators/UniqueSymbolGroupName.java @@ -0,0 +1,47 @@ +package de.learnlib.alex.core.entities.validators; + +import javax.validation.Constraint; +import javax.validation.Payload; +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.ANNOTATION_TYPE; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * Annotation for the the SymbolGroup class that checks, that every SymbolGroup name is unique per User and Project. + * See http://docs.jboss.org/hibernate/validator/5.2/reference/en-US/html/ch06.html for more details. + */ +@Target({ TYPE, ANNOTATION_TYPE }) +@Retention(RUNTIME) +@Constraint(validatedBy = { UniqueSymbolGroupNameValidator.class }) +@Documented +public @interface UniqueSymbolGroupName { + + /** + * The error message if the constraint is violated. + * A default error message is provided. + * + * @return The error message. + */ + String message() default "{de.learnlib.alex.core.entities.validators.UniqueSymbolGroupName.message}"; + + /** + * Attribute to group constrains. + * This group is empty by default. + * + * @return The groups the constraint belongs to + */ + Class[] groups() default { }; + + /** + * Additional payload of the annotation. + * This payload is empty by default. + * + * @return The current payload. + */ + Class[] payload() default { }; + +} diff --git a/main/src/main/java/de/learnlib/alex/core/entities/validators/UniqueSymbolGroupNameValidator.java b/main/src/main/java/de/learnlib/alex/core/entities/validators/UniqueSymbolGroupNameValidator.java new file mode 100644 index 000000000..fd073e26f --- /dev/null +++ b/main/src/main/java/de/learnlib/alex/core/entities/validators/UniqueSymbolGroupNameValidator.java @@ -0,0 +1,50 @@ +package de.learnlib.alex.core.entities.validators; + +import de.learnlib.alex.core.entities.Project; +import de.learnlib.alex.core.entities.SymbolGroup; +import de.learnlib.alex.core.entities.User; +import de.learnlib.alex.utils.HibernateUtil; +import org.hibernate.Session; +import org.hibernate.criterion.Restrictions; + +import javax.validation.ConstraintValidator; +import javax.validation.ConstraintValidatorContext; +import java.util.List; + +/** + * The Validator related to the UniqueSymbolGroupName annotation. + */ +public class UniqueSymbolGroupNameValidator implements ConstraintValidator { + + @Override + public void initialize(UniqueSymbolGroupName validateUniqueNamePerUser) { + } + + @Override + public boolean isValid(SymbolGroup group, ConstraintValidatorContext constraintValidatorContext) { + String name = group.getName(); + User user = group.getUser(); + Project project = group.getProject(); + + if (name == null || name.isEmpty() + || user == null + || project == null) { + return false; + } + + Session session = HibernateUtil.getSession(); + List groups = session.createCriteria(SymbolGroup.class) + .add(Restrictions.eq("name", name)) + .add(Restrictions.eq("user", user)) + .add(Restrictions.eq("project", project)) + .list(); + + for (SymbolGroup g : groups) { + if (!g.equals(group)) { + return false; + } + } + + return true; + } +} diff --git a/main/src/main/java/de/learnlib/alex/core/entities/validators/UniqueSymbolName.java b/main/src/main/java/de/learnlib/alex/core/entities/validators/UniqueSymbolName.java new file mode 100644 index 000000000..9fa1495f8 --- /dev/null +++ b/main/src/main/java/de/learnlib/alex/core/entities/validators/UniqueSymbolName.java @@ -0,0 +1,47 @@ +package de.learnlib.alex.core.entities.validators; + +import javax.validation.Constraint; +import javax.validation.Payload; +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.ANNOTATION_TYPE; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * Annotation for the the Symbol class that checks, that every Symbol name is unique per User and Project. + * See http://docs.jboss.org/hibernate/validator/5.2/reference/en-US/html/ch06.html for more details. + */ +@Target({ TYPE, ANNOTATION_TYPE }) +@Retention(RUNTIME) +@Constraint(validatedBy = { UniqueSymbolNameValidator.class }) +@Documented +public @interface UniqueSymbolName { + + /** + * The error message if the constraint is violated. + * A default error message is provided. + * + * @return The error message. + */ + String message() default "{de.learnlib.alex.core.entities.validators.UniqueSymbolName.message}"; + + /** + * Attribute to group constrains. + * This group is empty by default. + * + * @return The groups the constraint belongs to + */ + Class[] groups() default { }; + + /** + * Additional payload of the annotation. + * This payload is empty by default. + * + * @return The current payload. + */ + Class[] payload() default { }; + +} diff --git a/main/src/main/java/de/learnlib/alex/core/entities/validators/UniqueSymbolNameValidator.java b/main/src/main/java/de/learnlib/alex/core/entities/validators/UniqueSymbolNameValidator.java new file mode 100644 index 000000000..515d119f8 --- /dev/null +++ b/main/src/main/java/de/learnlib/alex/core/entities/validators/UniqueSymbolNameValidator.java @@ -0,0 +1,57 @@ +package de.learnlib.alex.core.entities.validators; + +import de.learnlib.alex.core.entities.Project; +import de.learnlib.alex.core.entities.Symbol; +import de.learnlib.alex.core.entities.User; +import de.learnlib.alex.utils.HibernateUtil; +import org.hibernate.Session; +import org.hibernate.criterion.Junction; +import org.hibernate.criterion.Restrictions; + +import javax.validation.ConstraintValidator; +import javax.validation.ConstraintValidatorContext; +import java.util.List; + +/** + * The Validator related to the UniqueSymbolName annotation. + */ +public class UniqueSymbolNameValidator implements ConstraintValidator { + + @Override + public void initialize(UniqueSymbolName validateUniqueNamePerUser) { + } + + @Override + public boolean isValid(Symbol symbol, ConstraintValidatorContext constraintValidatorContext) { + String name = symbol.getName(); + User user = symbol.getUser(); + Project project = symbol.getProject(); + + if (name == null || name.isEmpty() + || user == null + || project == null) { + return false; + } + + Junction restrictions = Restrictions.conjunction() + .add(Restrictions.eq("name", name)) + .add(Restrictions.eq("user", user)) + .add(Restrictions.eq("project", project)); + if (symbol.getId() != null) { + restrictions = restrictions.add(Restrictions.ne("idRevisionPair.id", symbol.getId())); + } + + Session session = HibernateUtil.getSession(); + List symbols = session.createCriteria(Symbol.class) + .add(restrictions) + .list(); + + for (Symbol s : symbols) { + if (!s.equals(symbol)) { + return false; + } + } + + return true; + } +} diff --git a/main/src/main/java/de/learnlib/alex/core/entities/validators/package-info.java b/main/src/main/java/de/learnlib/alex/core/entities/validators/package-info.java new file mode 100644 index 000000000..923b2b11a --- /dev/null +++ b/main/src/main/java/de/learnlib/alex/core/entities/validators/package-info.java @@ -0,0 +1,5 @@ +/** + * This package contains additional Hibernate constraint validators to check non default and more complex constrains. + * See http://docs.jboss.org/hibernate/validator/5.2/reference/en-US/html/ch06.html for more details. + */ +package de.learnlib.alex.core.entities.validators; diff --git a/main/src/main/java/de/learnlib/alex/core/learner/BaseUrlManager.java b/main/src/main/java/de/learnlib/alex/core/learner/BaseUrlManager.java index 8344a6531..700882d05 100644 --- a/main/src/main/java/de/learnlib/alex/core/learner/BaseUrlManager.java +++ b/main/src/main/java/de/learnlib/alex/core/learner/BaseUrlManager.java @@ -1,3 +1,19 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package de.learnlib.alex.core.learner; /** diff --git a/main/src/main/java/de/learnlib/alex/core/learner/Learner.java b/main/src/main/java/de/learnlib/alex/core/learner/Learner.java index 2f68a91a1..fed3f101a 100644 --- a/main/src/main/java/de/learnlib/alex/core/learner/Learner.java +++ b/main/src/main/java/de/learnlib/alex/core/learner/Learner.java @@ -1,18 +1,53 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package de.learnlib.alex.core.learner; +import de.learnlib.alex.core.dao.LearnerResultDAO; +import de.learnlib.alex.core.dao.SymbolDAO; import de.learnlib.alex.core.entities.LearnerConfiguration; import de.learnlib.alex.core.entities.LearnerResult; import de.learnlib.alex.core.entities.LearnerResumeConfiguration; +import de.learnlib.alex.core.entities.LearnerStatus; import de.learnlib.alex.core.entities.Project; import de.learnlib.alex.core.entities.Symbol; +import de.learnlib.alex.core.entities.User; import de.learnlib.alex.core.entities.learnlibproxies.eqproxies.SampleEQOracleProxy; import de.learnlib.alex.core.learner.connectors.ConnectorContextHandler; import de.learnlib.alex.core.learner.connectors.ConnectorContextHandlerFactory; import de.learnlib.alex.core.learner.connectors.ConnectorManager; +import de.learnlib.alex.core.learner.connectors.WebBrowser; import de.learnlib.alex.exceptions.LearnerException; +import de.learnlib.alex.exceptions.NotFoundException; import de.learnlib.oracles.ResetCounterSUL; - -import java.util.*; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Service; + +import javax.annotation.PreDestroy; +import javax.inject.Inject; +import java.time.ZonedDateTime; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.stream.Collectors; @@ -20,11 +55,25 @@ * Basic class to control and monitor a learn process. * This class is a high level abstraction of the LearnLib. */ +@Service +@Scope("singleton") public class Learner { + /** How many concurrent threads the system can handle. */ + private static final int MAX_CONCURRENT_THREADS = 2; + + /** The SymbolDAO to use. */ + @Inject + private SymbolDAO symbolDAO; + + /** The LearnerResultDAO to use. */ + @Inject + private LearnerResultDAO learnerResultDAO; + /** * Factory to create a new ContextHandler. */ + @Inject private ConnectorContextHandlerFactory contextHandlerFactory; /** @@ -32,58 +81,92 @@ public class Learner { */ private ConnectorContextHandler contextHandler; - /** - * Factory to create the {@link LearnerThread LearnerThreads}. - */ - private LearnerThreadFactory learnThreadFactory; + /** The factory to create the learner threads. */ + @Inject + private LearnerThreadFactory learnerThreadFactory; /** - * The current learning thread. Could be null. + * The last thread of an user, if one exists. */ - private LearnerThread learnThread; + private final Map userThreads; + + /** The executer service will take care of creating and scheduling the actual OS threads. */ + private ExecutorService executorService; /** - * The SUL of the current learning thread. + * This constructor creates a new Learner + * The SymbolDAO and LearnerResultDAO must be externally injected. */ - private ResetCounterSUL resetCounterSUL; - - private Long startTime; + public Learner() { + this.userThreads = new HashMap<>(); + this.executorService = Executors.newFixedThreadPool(MAX_CONCURRENT_THREADS); + } /** - * Constructor which only set the thread factory. - * This constructor creates a new ConnectorContextHandlerFactory for internal use. + * Constructor that sets all fields by the given parameter. * - * @param threadFactory The thread factory to use. + * @param symbolDAO + * The SymbolDAO to use. + * @param learnerResultDAO + * The LearnerResultDAO to use. + * @param contextHandlerFactory + * The factory that will be used to create new context handler. + * @param learnerThreadFactory + * The factory to create the learner threads. */ - public Learner(LearnerThreadFactory threadFactory) { - this(threadFactory, new ConnectorContextHandlerFactory()); + Learner(SymbolDAO symbolDAO, LearnerResultDAO learnerResultDAO, + ConnectorContextHandlerFactory contextHandlerFactory, LearnerThreadFactory learnerThreadFactory) { + this(); + this.symbolDAO = symbolDAO; + this.learnerResultDAO = learnerResultDAO; + this.contextHandlerFactory = contextHandlerFactory; + this.learnerThreadFactory = learnerThreadFactory; } /** - * Constructor that initialises only the LearnerThreadFactory. - * - * @param learnThreadFactory The thread factory to use. - * @param contextHandlerFactory The factory that will be used to create new context handler. + * Method should be called before the Learner is 'destroyed'. + * It will shutdown the executor service gracefully. */ - public Learner(LearnerThreadFactory learnThreadFactory, ConnectorContextHandlerFactory contextHandlerFactory) { - this.learnThreadFactory = learnThreadFactory; - this.contextHandlerFactory = contextHandlerFactory; + @PreDestroy + public void destroy() { + executorService.shutdown(); } /** * Start a learning process by activating a LearningThread. * - * @param project The project the learning process runs in. - * @param configuration The configuration to use for the learning process. - * @throws IllegalArgumentException If the configuration was invalid. - * @throws IllegalStateException If a learning process is already active. - */ - public void start(Project project, LearnerConfiguration configuration) - throws IllegalArgumentException, IllegalStateException { - if (isActive()) { - throw new IllegalStateException("You can only start the learning if no other learning is active"); - } + * @param user + * The user that wants to start the learning process. + * @param project + * The project the learning process runs in. + * @param configuration + * The configuration to use for the learning process. + * @throws IllegalArgumentException + * If the configuration was invalid or the user tried to start a second active learning thread. + * @throws IllegalStateException + * If a learning process is already active. + * @throws NotFoundException + * If the symbols specified in the configuration could not be found. + */ + public void start(User user, Project project, LearnerConfiguration configuration) + throws IllegalArgumentException, IllegalStateException, NotFoundException { + preStartCheck(user); + + LearnerResult learnerResult = createLearnerResult(user, project, configuration); + + WebBrowser browser = configuration.getBrowser(); + contextHandler = contextHandlerFactory.createContext(project, browser); + contextHandler.setResetSymbol(learnerResult.getResetSymbol()); + LearnerThread learnThread = learnerThreadFactory.createThread(learnerResult, contextHandler); + startThread(user, learnThread); + // get the sul here once so that the timer doesn't get '0' for .getStatisticalData.getCount() after continuing + // a learning process + learnThread.getResetCounterSUL(); + } + + private LearnerResult createLearnerResult(User user, Project project, LearnerConfiguration configuration) + throws NotFoundException { // TODO: make it nicer than this instanceof stuff, because you have to somehow tell the eq oracle that the // learner started and not resumed if (configuration.getEqOracle() instanceof SampleEQOracleProxy) { @@ -95,48 +178,114 @@ public void start(Project project, LearnerConfiguration configuration) configuration.checkConfiguration(); // throws IllegalArgumentException if something is wrong } - contextHandler = contextHandlerFactory.createContext(project); - learnThread = learnThreadFactory.createThread(contextHandler, project, configuration); - Thread thread = Executors.defaultThreadFactory().newThread(learnThread); - thread.start(); + LearnerResult learnerResult = new LearnerResult(); + learnerResult.setUser(user); + learnerResult.setProject(project); - // get the sul here once so that the timer doesn't get '0' for .getStatisticalData.getCount() after continuing - // a learning process - resetCounterSUL = learnThread.getResetCounterSUL(); - startTime = new Date().getTime(); + try { + Symbol resetSymbol = symbolDAO.get(user, project.getId(), configuration.getResetSymbolAsIdRevisionPair()); + learnerResult.setResetSymbol(resetSymbol); + } catch (NotFoundException e) { // Extra exception to emphasize that this is the reset symbol. + throw new NotFoundException("Could not find the reset symbol!", e); + } + + // TODO: remove new HashMap -> getAll should return a Set + List symbolsAsList = symbolDAO.getAll(user, project.getId(), + new LinkedList<>(configuration.getSymbolsAsIdRevisionPairs())); + Set symbols = new HashSet<>(symbolsAsList); + learnerResult.setSymbols(symbols); + + learnerResult.setBrowser(configuration.getBrowser()); + learnerResult.setAlgorithm(configuration.getAlgorithm()); + learnerResult.setComment(configuration.getComment()); + learnerResultDAO.create(learnerResult); + + learnerResultDAO.createStep(learnerResult, configuration); + + return learnerResult; } /** * Resuming a learning process by activating a LearningThread. * - * @param newConfiguration The configuration to use for the next learning steps. - * @throws IllegalArgumentException If the new configuration has errors. - * @throws IllegalStateException If a learning process is already active. - */ - public void resume(LearnerResumeConfiguration newConfiguration) - throws IllegalArgumentException, IllegalStateException { - if (isActive()) { - throw new IllegalStateException("You can only restart the learning if no other learning is active"); - } + * @param user + * The user that wants to restart his latest thread. + * @param newConfiguration + * The configuration to use for the next learning steps. + * @throws IllegalArgumentException + * If the new configuration has errors. + * @throws IllegalStateException + * If a learning process is already active or if now process to resume was found. + * @throws NotFoundException + * If the symbols specified in the configuration could not be found. + */ + public void resume(User user, LearnerResumeConfiguration newConfiguration) + throws IllegalArgumentException, IllegalStateException, NotFoundException { + preStartCheck(user); newConfiguration.checkConfiguration(); // throws IllegalArgumentException if something is wrong - validateCounterExample(newConfiguration); + validateCounterExample(user, newConfiguration); + + // get the previousThread and the LearnResult + LearnerThread previousThread = userThreads.get(user); + if (previousThread == null) { + throw new IllegalStateException("No previous learn process to resume was found!"); + } + + LearnerResult learnerResult = previousThread.getResult(); + + // create the new step + learnerResultDAO.createStep(learnerResult, newConfiguration); - learnThread = learnThreadFactory.updateThread(learnThread, newConfiguration); - Thread thread = Executors.defaultThreadFactory().newThread(learnThread); - thread.start(); + LearnerThread learnThread = learnerThreadFactory.createThread(previousThread, learnerResult); + startThread(user, learnThread); + } + + /** + * Check if a thread for the user can possibly started. + * This means that the user has no other active learning thread + * and the overall learning thread capacity is not reached. + * + * @param user + * The user to check for. + * @throws IllegalStateException + * If a new thread could not start. + */ + private void preStartCheck(User user) { + if (isActive(user)) { + throw new IllegalStateException("Only one active learning is allowed per user, " + + "even for user" + user + "!"); + } + } + + /** + * Starts the thread and updates the thread maps. + * + * @param user + * The user that starts the thread. + * @param learnThread + * The thread to start. + */ + private void startThread(User user, LearnerThread learnThread) { + executorService.submit(learnThread); + userThreads.put(user, learnThread); } /** * If the new configuration is base on manual counterexamples, these samples must be checked. * - * @param newConfiguration The new configuration. - * @throws IllegalArgumentException If the new configuration is based on manual counterexamples and at least one of them is wrong. + * @param user + * The user to validate the counterexample for. + * @param newConfiguration + * The new configuration. + * @throws IllegalArgumentException + * If the new configuration is based on manual counterexamples and at least one of them is wrong. */ - private void validateCounterExample(LearnerResumeConfiguration newConfiguration) throws IllegalArgumentException { + private void validateCounterExample(User user, LearnerResumeConfiguration newConfiguration) + throws IllegalArgumentException { if (newConfiguration.getEqOracle() instanceof SampleEQOracleProxy) { SampleEQOracleProxy oracle = (SampleEQOracleProxy) newConfiguration.getEqOracle(); - LearnerResult lastResult = getResult(); + LearnerResult lastResult = getResult(user); for (List counterexample : oracle.getCounterExamples()) { List symbolsFromCounterexample = new ArrayList<>(); @@ -145,9 +294,9 @@ private void validateCounterExample(LearnerResumeConfiguration newConfiguration) // search symbols in configuration where symbol.abbreviation == counterexample.input for (SampleEQOracleProxy.InputOutputPair io : counterexample) { - Optional symbol = lastResult.getConfiguration().getSymbols().stream() - .filter(s -> s.getAbbreviation().equals(io.getInput())) - .findFirst(); + Optional symbol = lastResult.getSymbols().stream() + .filter(s -> s.getAbbreviation().equals(io.getInput())) + .findFirst(); // collect all outputs in order to compare it with the result of learner.readOutputs() if (symbol.isPresent()) { @@ -160,9 +309,10 @@ private void validateCounterExample(LearnerResumeConfiguration newConfiguration) } // finally check if the given sample matches the behavior of the SUL - List results = readOutputs(lastResult.getProject(), - lastResult.getConfiguration().getResetSymbol(), - symbolsFromCounterexample); + List results = readOutputs(lastResult.getUser(), + lastResult.getProject(), + lastResult.getResetSymbol(), + symbolsFromCounterexample); if (!results.equals(outputs)) { throw new IllegalArgumentException("At least one of the given samples for counterexamples" + " is not matching the behavior of the SUL."); @@ -173,63 +323,129 @@ private void validateCounterExample(LearnerResumeConfiguration newConfiguration) /** * Ends the learning process after the current step. + * + * @param user + * The user that wants to stop his active thread. */ - public void stop() { - learnThread.interrupt(); + public void stop(User user) { + LearnerThread learnerThread = userThreads.get(user); + + if (learnerThread != null) { + learnerThread.interrupt(); + } } /** * Method to check if a learning process is still active or if it has finished. * + * @param user + * The user to check for active threads. * @return true if the learning process is active, false otherwise. */ - public boolean isActive() { - return learnThread != null && learnThread.isActive(); + public boolean isActive(User user) { + LearnerThread learnerThread = userThreads.get(user); + + // if no thread for the user exists -> return false + if (learnerThread == null) { + return false; + } + + return !learnerThread.isFinished(); + } + + /** + * Get the status of the Learner as immutable object. + * + * @param user + * The user that wants a LearnerStatus object for his (active) thread. + * @return A snapshot of the Learner status. + */ + public LearnerStatus getStatus(User user) { + LearnerStatus status; + + boolean active = isActive(user); + if (!active) { + status = new LearnerStatus(); // not active + } else { + status = new LearnerStatus(getResult(user)); // active + } + + return status; } /** * Get the current result of the learning process. * This must not be a valid step of a test run! * + * @param user + * The user that wants to see his result. * @return The current result of the LearnerThread. */ - public LearnerResult getResult() { - if (learnThread != null) { - return learnThread.getResult(); + public LearnerResult getResult(User user) { + LearnerThread learnerThread = userThreads.get(user); + + if (learnerThread != null) { + return learnerThread.getResult(); } else { return null; } } /** - * Get the number of executed MQs in the current learn process + * Get the number of executed MQs in the current learn process. * + * @param user + * The User that wants to his MQs used. * @return null if the learnThread has not been started or the number of executed MQs in the current learn process */ - public Long getMQsUsed() { - return learnThread == null ? null : resetCounterSUL.getStatisticalData().getCount(); + public Long getMQsUsed(User user) { + LearnerThread learnerThread = userThreads.get(user); + + if (learnerThread == null) { + return null; + } else { + ResetCounterSUL resetCounterSUL = learnerThread.getResetCounterSUL(); + return resetCounterSUL.getStatisticalData().getCount(); + } } /** - * Get the time the learner started learning + * Get the date and time when the learner started learning. * - * @return The time the learner started learning + * @param user + * The user that wants to see his latest start date. + * @return The date and time when the learner started learning. */ - public Long getStartTime() { - return startTime; + public ZonedDateTime getStartDate(User user) { + LearnerThread learnerThread = userThreads.get(user); + + if (learnerThread == null) { + return null; + } else { + return getResult(user).getStatistics().getStartDate(); + } } /** * Determine the output of the SUL by testing a sequence of input symbols. * - * @param project The project in which context the test should happen. - * @param resetSymbol The reset symbol to use. - * @param symbols The symbol sequence to execute in order to generate the output sequence. + * @param user + * The user in which context the test should happen. + * @param project + * The project in which context the test should happen. + * @param resetSymbol + * The reset symbol to use. + * @param symbols + * The symbol sequence to execute in order to generate the output sequence. * @return The following output sequence. + * @throws LearnerException + * If something went wrong while testing the symbols. */ - public List readOutputs(Project project, Symbol resetSymbol, List symbols) throws LearnerException { + public List readOutputs(User user, Project project, Symbol resetSymbol, List symbols) + throws LearnerException { if (contextHandler == null) { - contextHandler = contextHandlerFactory.createContext(project); + // todo: remove hardcoded browser + contextHandler = contextHandlerFactory.createContext(project, WebBrowser.HTMLUNITDRIVER); } contextHandler.setResetSymbol(resetSymbol); diff --git a/main/src/main/java/de/learnlib/alex/core/learner/LearnerThread.java b/main/src/main/java/de/learnlib/alex/core/learner/LearnerThread.java index f236942cb..a7e1b3c72 100644 --- a/main/src/main/java/de/learnlib/alex/core/learner/LearnerThread.java +++ b/main/src/main/java/de/learnlib/alex/core/learner/LearnerThread.java @@ -1,10 +1,30 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package de.learnlib.alex.core.learner; import de.learnlib.alex.core.dao.LearnerResultDAO; import de.learnlib.alex.core.entities.ExecuteResult; import de.learnlib.alex.core.entities.LearnAlgorithms; import de.learnlib.alex.core.entities.LearnerResult; +import de.learnlib.alex.core.entities.LearnerResultStep; +import de.learnlib.alex.core.entities.Statistics; import de.learnlib.alex.core.entities.Symbol; +import de.learnlib.alex.core.entities.learnlibproxies.AlphabetProxy; +import de.learnlib.alex.core.entities.learnlibproxies.DefaultQueryProxy; import de.learnlib.alex.core.learner.connectors.ConnectorContextHandler; import de.learnlib.alex.core.learner.connectors.ConnectorManager; import de.learnlib.alex.exceptions.NotFoundException; @@ -23,12 +43,12 @@ import net.automatalib.automata.transout.MealyMachine; import net.automatalib.words.Alphabet; import net.automatalib.words.Word; -import org.apache.logging.log4j.Level; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import java.util.Date; +import java.time.ZonedDateTime; import java.util.List; +import java.util.Set; /** * Thread to run a learning process. It needs to be a Thread so that the server can still deal with other requests. @@ -37,14 +57,14 @@ public class LearnerThread extends Thread { /** - * Use the logger for the server part. + * Use the learner logger. */ - private static final Logger LOGGER = LogManager.getLogger("server"); + private static final Logger LOGGER = LogManager.getLogger("learner"); /** * Is the thread still running? */ - private boolean active; + private boolean finished; /** * Mapper to match the Alphabet to the right symbols. @@ -81,6 +101,11 @@ public class LearnerThread extends Thread { */ private final LearnerResult result; + /** + * The current step of the learn result. + */ + private LearnerResultStep currentStep; + /** * The learner to use during the learning. */ @@ -99,23 +124,28 @@ public class LearnerThread extends Thread { /** * Constructor to set the LearnerThread up. * - * @param learnerResultDAO The DAO to persists the results. - * @param result The result to update, including the proper configuration. - * @param context The context of the SUL. If this context is a counter, the 'amountOfResets' field will be set correctly. + * @param learnerResultDAO + * The DAO to persists the results. + * @param result + * The result to update, including the proper configuration. + * @param context + * The context of the SUL. If this context is a counter, the 'amountOfResets' field will be set correctly. */ - public LearnerThread(LearnerResultDAO learnerResultDAO, LearnerResult result, - ConnectorContextHandler context) { - this.active = false; + public LearnerThread(LearnerResultDAO learnerResultDAO, LearnerResult result, ConnectorContextHandler context) { + this.finished = false; this.learnerResultDAO = learnerResultDAO; this.result = result; + this.currentStep = result.getSteps().get(result.getSteps().size() - 1); // get the latest step Symbol[] symbolsArray = readSymbolArray(); // use the symbols in the result to create the symbol array. this.symbolMapper = new SymbolMapper(symbolsArray); this.sigma = symbolMapper.getAlphabet(); - this.result.setSigma(sigma); + this.result.setSigma(AlphabetProxy.createFrom(sigma)); - ContextExecutableInputSUL, ExecuteResult, ConnectorManager> ceiSUL; - ceiSUL = new ContextExecutableInputSUL<>(context); + ContextExecutableInputSUL, + ExecuteResult, + ConnectorManager> + ceiSUL = new ContextExecutableInputSUL<>(context); SUL mappedSUL = Mappers.apply(symbolMapper, ceiSUL); this.cachedSUL = SULCaches.createCache(this.sigma, mappedSUL); resetCounterSUL = new ResetCounterSUL<>("reset counter", this.cachedSUL); @@ -124,7 +154,7 @@ public LearnerThread(LearnerResultDAO learnerResultDAO, LearnerResult result, this.mqOracle = new SULOracle<>(sul); - LearnAlgorithms algorithm = result.getConfiguration().getAlgorithm(); + LearnAlgorithms algorithm = result.getAlgorithm(); this.learner = algorithm.createLearner(sigma, mqOracle); } @@ -140,13 +170,14 @@ public LearnerThread(LearnerResultDAO learnerResultDAO, LearnerResult result, */ public LearnerThread(LearnerResultDAO learnerResultDAO, LearnerResult result, SULCache existingSUL, MealyLearner learner, Symbol... symbols) { - this.active = false; + this.finished = false; this.learnerResultDAO = learnerResultDAO; this.result = result; + this.currentStep = result.getSteps().get(result.getSteps().size() - 1); // get the latest step this.symbolMapper = new SymbolMapper(symbols); this.sigma = symbolMapper.getAlphabet(); - result.setSigma(sigma); + result.setSigma(AlphabetProxy.createFrom(sigma)); this.cachedSUL = existingSUL; resetCounterSUL = new ResetCounterSUL<>("reset counter", this.cachedSUL); @@ -159,7 +190,7 @@ public LearnerThread(LearnerResultDAO learnerResultDAO, LearnerResult result, SU } private Symbol[] readSymbolArray() { - List symbols = result.getConfiguration().getSymbols(); + Set symbols = result.getSymbols(); return symbols.toArray(new Symbol[symbols.size()]); } @@ -168,8 +199,8 @@ private Symbol[] readSymbolArray() { * * @return true, if the Thread is still active; false otherwise. */ - public boolean isActive() { - return active; + public boolean isFinished() { + return finished; } /** @@ -210,24 +241,22 @@ public List getSymbols() { @Override public void run() { - active = true; + LOGGER.trace("LearnThread.run() - enter"); + try { learn(); } catch (Exception e) { LOGGER.warn("Something in the LearnerThread went wrong:", e); - result.setErrorText(e.getMessage()); - try { - learnerResultDAO.update(result); - } catch (NotFoundException nfe) { - LOGGER.log(Level.FATAL, "Something in the LearnerThread went wrong and the result could not be saved!", - e); - } + currentStep.setErrorText(e.getMessage()); + learnerResultDAO.saveStep(result, currentStep); } - active = false; + + LOGGER.trace("LearnThread.run() - exit"); + finished = true; } /** - * Get the ResetCounterSUL + * Get the ResetCounterSUL. * * @return The active ResetCounterSUL */ @@ -236,34 +265,21 @@ public ResetCounterSUL getResetCounterSUL() { } private void learn() throws NotFoundException { - long maxStepNo = calculateCurrentStepNo() + result.getConfiguration().getMaxAmountOfStepsToLearn(); - do { learnOneStep(); - } while (continueLearning(maxStepNo)); - } - - private long calculateCurrentStepNo() { - if (result.getStepNo() == null) { - return 0L; - } else { - return result.getStepNo(); - } + } while (continueLearning()); } - private boolean continueLearning(long maxStepNo) { - int maxAmountOfStepsToLearn = result.getConfiguration().getMaxAmountOfStepsToLearn(); - return result.getCounterExample() != null - && (maxAmountOfStepsToLearn == 0 || result.getStepNo() < maxStepNo) + private boolean continueLearning() { + return currentStep.getCounterExample() != null + && (currentStep.getStepsToLearn() > 0 || currentStep.getStepsToLearn() == -1) && !Thread.interrupted(); } private void learnOneStep() throws NotFoundException { - LearnerResult.Statistics statistics = result.getStatistics(); - statistics.setStartTime(new Date()); - statistics.setEqsUsed(0L); + LOGGER.trace("LearnerThread.learnOneStep()"); - if (result.getStepNo() == null || result.getStepNo().equals(0L)) { + if (result.getStatistics().getDuration() == 0L) { learnFirstStep(); } else { learnSuccessiveStep(); @@ -273,22 +289,41 @@ private void learnOneStep() throws NotFoundException { } private void learnFirstStep() { - learnerResultDAO.create(result); + LOGGER.trace("LearnerThread.learnFirstStep()"); + Statistics statistics = currentStep.getStatistics(); + statistics.setStartDate(ZonedDateTime.now()); + statistics.setStartTime(System.nanoTime()); + statistics.setDuration(0L); + statistics.setEqsUsed(0L); + result.getStatistics().setStartDate(statistics.getStartDate()); learner.startLearning(); result.createHypothesisFrom(learner.getHypothesisModel()); findAndRememberCounterExample(); } - private void learnSuccessiveStep() { + private void learnSuccessiveStep() throws NotFoundException { + // If the learning is resumed, a new step with the new configuration, will already exists. + // Otherwise we have to create a new step based on the previous one. + if (currentStep.getStatistics().getDuration() > 0) { + currentStep = learnerResultDAO.createStep(result); + } + + // set the start date and the start time: + // - The start date acts as timestamp and is public. + // - The start time is more accurate (nanoseconds) and will be only used internally to calculate the duration. + Statistics statistics = currentStep.getStatistics(); + statistics.setStartDate(ZonedDateTime.now()); + statistics.setStartTime(System.nanoTime()); + // if the previous step didn't yield any counter example, try again // (maybe more luck this time or configuration has changed) - if (result.getCounterExample() == null) { + if (currentStep.getCounterExample() == null) { findAndRememberCounterExample(); } // if a there is a counter example refine the hypothesis - if (result.getCounterExample() != null) { - refineHypothesis(); + if (currentStep.getCounterExample() != null) { + learner.refineHypothesis(currentStep.getCounterExample().createDefaultProxy()); findAndRememberCounterExample(); } } @@ -296,42 +331,49 @@ private void learnSuccessiveStep() { private void findAndRememberCounterExample() { // find EquivalenceOracle, String, Word> eqOracle; - eqOracle = result.getConfiguration().getEqOracle().createEqOracle(mqOracle); + eqOracle = currentStep.getEqOracle().createEqOracle(mqOracle); DefaultQuery> newCounterExample; newCounterExample = eqOracle.findCounterExample(learner.getHypothesisModel(), sigma); // remember - result.getStatistics().setEqsUsed(result.getStatistics().getEqsUsed() + 1); - result.setCounterExample(newCounterExample); - } - - private void refineHypothesis() { - learner.refineHypothesis(result.getCounterExample()); - result.createHypothesisFrom(learner.getHypothesisModel()); + currentStep.getStatistics().setEqsUsed(currentStep.getStatistics().getEqsUsed() + 1); + if (newCounterExample == null) { + currentStep.setCounterExample(null); + } else { + currentStep.setCounterExample(DefaultQueryProxy.createFrom(newCounterExample)); + } + LOGGER.info("The new counter example is '" + newCounterExample + "'."); } private void rememberMetaData() throws NotFoundException { // statistics - LearnerResult.Statistics statistics = result.getStatistics(); + Statistics statistics = currentStep.getStatistics(); + + long startTime = statistics.getStartTime(); + long currentTime = System.nanoTime(); - long startTime = statistics.getStartTime().getTime(); - long currentTime = new Date().getTime(); + currentStep.createHypothesisFrom(learner.getHypothesisModel()); statistics.setDuration(currentTime - startTime); - statistics.setMqsUsed(Math.abs(resetCounterSUL.getStatisticalData().getCount() - statistics.getMqsUsed())); - statistics.setSymbolsUsed(Math.abs(symbolCounterSUL.getStatisticalData().getCount() - statistics.getSymbolsUsed())); + LOGGER.debug("Duration of the learning: " + statistics.getDuration() + " " + + "(start: " + startTime + ", end: " + currentTime + ")."); + + long mqUsedDiff = resetCounterSUL.getStatisticalData().getCount() - statistics.getMqsUsed(); + statistics.setMqsUsed(Math.abs(mqUsedDiff)); + long symbolUsedDiff = symbolCounterSUL.getStatisticalData().getCount() - statistics.getSymbolsUsed(); + statistics.setSymbolsUsed(Math.abs(symbolUsedDiff)); // algorithm information - LearnAlgorithms algorithm = result.getConfiguration().getAlgorithm(); + LearnAlgorithms algorithm = result.getAlgorithm(); String algorithmInformation; try { algorithmInformation = algorithm.getInternalData(learner); } catch (IllegalStateException e) { // algorithm has no internal data to show algorithmInformation = ""; } - result.setAlgorithmInformation(algorithmInformation); + currentStep.setAlgorithmInformation(algorithmInformation); // done - learnerResultDAO.update(result); + learnerResultDAO.saveStep(result, currentStep); } } diff --git a/main/src/main/java/de/learnlib/alex/core/learner/LearnerThreadFactory.java b/main/src/main/java/de/learnlib/alex/core/learner/LearnerThreadFactory.java index 433d022b5..9f34608f1 100644 --- a/main/src/main/java/de/learnlib/alex/core/learner/LearnerThreadFactory.java +++ b/main/src/main/java/de/learnlib/alex/core/learner/LearnerThreadFactory.java @@ -1,84 +1,70 @@ package de.learnlib.alex.core.learner; import de.learnlib.alex.core.dao.LearnerResultDAO; -import de.learnlib.alex.core.entities.LearnerConfiguration; +import de.learnlib.alex.core.dao.LearnerResultDAOImpl; import de.learnlib.alex.core.entities.LearnerResult; -import de.learnlib.alex.core.entities.LearnerResumeConfiguration; -import de.learnlib.alex.core.entities.Project; import de.learnlib.alex.core.entities.Symbol; import de.learnlib.alex.core.learner.connectors.ConnectorContextHandler; import de.learnlib.api.LearningAlgorithm; +import org.springframework.stereotype.Service; import java.util.List; /** - * Factory to create a {@link LearnerThread} for the given Symbols. + * Class to create a learner thread. + * This is a helper class to make the testing of the Learner class more easily. */ +@Service public class LearnerThreadFactory { - /** The DAO to receive and save the results. */ + /** The LearnerResultDAO to use. */ private LearnerResultDAO learnerResultDAO; /** - * Constructor to set the factory up. + * Default constructor. This will create a new LearnerResultDAO for internal use. + */ + public LearnerThreadFactory() { + this(new LearnerResultDAOImpl()); + } + + /** + * Constructor that sets the LearnerResultDAO. * * @param learnerResultDAO - * The DAO to access and store the results. + * The LearnerResultDAO to use. */ public LearnerThreadFactory(LearnerResultDAO learnerResultDAO) { this.learnerResultDAO = learnerResultDAO; } /** - * Create a LearnerThread suitable for the given parameter. + * Create a brand new LearnThread. * + * @param learnerResult + * The newly created LearnResult with the configuration. * @param contextHandler - * The current ContextHandler to use. - * @param project - * The Project of the test run. - * @param configuration - * The LearnerConfiguration to use for the learning. - * @return A new thread ready to use for learning. + * The Connectors to use. + * @return A brand new learn thread. You have to start it by calling the .run() method on it. */ - public LearnerThread createThread(ConnectorContextHandler contextHandler, Project project, - LearnerConfiguration configuration) { - if (configuration.getSymbols().isEmpty()) { - throw new IllegalArgumentException("No Symbols found."); - } - - LearnerResult learnerResult = createLearnerResult(project, configuration); - contextHandler.setResetSymbol(configuration.getResetSymbol()); - + public LearnerThread createThread(LearnerResult learnerResult, ConnectorContextHandler contextHandler) { return new LearnerThread(learnerResultDAO, learnerResult, contextHandler); } /** - * Create a LearnerThread suitable for the given parameter. + * Create a new LearnThread based on a previous thread, i.e. if you want to resume a learn process. * - * @param thread - * The previous LearnerThread to copy information from. - * @param newConfiguration - * The resume configuration to use for the next learning steps. - * @return A new thread ready to use for learning. + * @param previousThread + * The previous thread which provides the actual algorithm and other properties. + * @param learnerResult + * The learner result with the new configuration in the current last step. + * @return The new LearnerThread. You have to start it by calling the .run() method on it. */ - public LearnerThread updateThread(LearnerThread thread, LearnerResumeConfiguration newConfiguration) { - LearnerResult learnerResult = thread.getResult(); - - learnerResult.getConfiguration().updateConfiguration(newConfiguration); - List symbolsList = thread.getSymbols(); - - LearningAlgorithm.MealyLearner learner = thread.getLearner(); + public LearnerThread createThread(LearnerThread previousThread, LearnerResult learnerResult) { + List symbolsList = previousThread.getSymbols(); Symbol[] symbols = symbolsList.toArray(new Symbol[symbolsList.size()]); + LearningAlgorithm.MealyLearner learner = previousThread.getLearner(); - return new LearnerThread(learnerResultDAO, learnerResult, thread.getCachedSUL(), learner, symbols); - } - - private LearnerResult createLearnerResult(Project project, LearnerConfiguration configuration) { - LearnerResult learnerResult = new LearnerResult(); - learnerResult.setConfiguration(configuration); - learnerResult.setProject(project); - - return learnerResult; + return new LearnerThread(learnerResultDAO, learnerResult, previousThread.getCachedSUL(), learner, symbols); } } diff --git a/main/src/main/java/de/learnlib/alex/core/learner/SymbolMapper.java b/main/src/main/java/de/learnlib/alex/core/learner/SymbolMapper.java index 30baaa0f4..a42fd34fc 100644 --- a/main/src/main/java/de/learnlib/alex/core/learner/SymbolMapper.java +++ b/main/src/main/java/de/learnlib/alex/core/learner/SymbolMapper.java @@ -1,3 +1,19 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package de.learnlib.alex.core.learner; import de.learnlib.alex.core.entities.ExecuteResult; @@ -19,7 +35,8 @@ /** * Class to map the Symbols and their result to the values used in the learning process. */ -public class SymbolMapper implements Mapper, ExecuteResult> { +public class SymbolMapper + implements Mapper, ExecuteResult> { /** Use the logger for the server part. */ private static final Logger LOGGER = LogManager.getLogger("learner"); @@ -27,6 +44,12 @@ public class SymbolMapper implements Mapper symbols; + /** + * Constructor. + * Initialize the map abbreviation -> symbol. + * + * @param symbols - The symbols for the learning process. + */ public SymbolMapper(Symbol... symbols) { this.symbols = new HashMap<>(); @@ -67,6 +90,11 @@ public void pre() { // nothing to do } + /** + * Get the alphabet for the learning process as required by the LearnLib. + * + * @return The alphabet. + */ public Alphabet getAlphabet() { Alphabet sigma = new SimpleAlphabet<>(); @@ -75,6 +103,11 @@ public Alphabet getAlphabet() { return sigma; } + /** + * Get the list of symbols. + * + * @return The list of symbols. + */ public List getSymbols() { List list = new LinkedList<>(); list.addAll(symbols.values()); diff --git a/main/src/main/java/de/learnlib/alex/core/learner/connectors/Connector.java b/main/src/main/java/de/learnlib/alex/core/learner/connectors/Connector.java index ad33f3b55..20bdd27b7 100644 --- a/main/src/main/java/de/learnlib/alex/core/learner/connectors/Connector.java +++ b/main/src/main/java/de/learnlib/alex/core/learner/connectors/Connector.java @@ -1,3 +1,19 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package de.learnlib.alex.core.learner.connectors; /** @@ -11,5 +27,11 @@ public interface Connector { */ void reset(); + /** + * Dispose the connector. + * This method will be called after the learning and allow to do necessary clean ups. + * After this method is called, the connector should not work anymore. + */ void dispose(); + } diff --git a/main/src/main/java/de/learnlib/alex/core/learner/connectors/ConnectorContextHandler.java b/main/src/main/java/de/learnlib/alex/core/learner/connectors/ConnectorContextHandler.java index 14d63c473..f01810a5d 100644 --- a/main/src/main/java/de/learnlib/alex/core/learner/connectors/ConnectorContextHandler.java +++ b/main/src/main/java/de/learnlib/alex/core/learner/connectors/ConnectorContextHandler.java @@ -1,3 +1,19 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package de.learnlib.alex.core.learner.connectors; import de.learnlib.alex.core.entities.ExecuteResult; @@ -61,7 +77,8 @@ private void executeResetSymbol() throws LearnerException { } if (resetResult.equals(ExecuteResult.FAILED)) { - throw new LearnerException("The execution of the reset symbol failed."); + throw new LearnerException("The execution of the reset symbol failed on step " + + resetResult.getFailedActionNumber() + "."); } } diff --git a/main/src/main/java/de/learnlib/alex/core/learner/connectors/ConnectorContextHandlerFactory.java b/main/src/main/java/de/learnlib/alex/core/learner/connectors/ConnectorContextHandlerFactory.java index 27c2ab5cb..7d751b51d 100644 --- a/main/src/main/java/de/learnlib/alex/core/learner/connectors/ConnectorContextHandlerFactory.java +++ b/main/src/main/java/de/learnlib/alex/core/learner/connectors/ConnectorContextHandlerFactory.java @@ -1,10 +1,28 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package de.learnlib.alex.core.learner.connectors; import de.learnlib.alex.core.entities.Project; +import org.springframework.stereotype.Service; /** * Factor to create a ContextHandler which knows all available connectors. */ +@Service public class ConnectorContextHandlerFactory { /** @@ -12,13 +30,15 @@ public class ConnectorContextHandlerFactory { * * @param project * The current project in which the context should be. + * @param browser + * The browser to use for the frontend learning. * @return A ContextHandler for the project with all the connectors. */ - public ConnectorContextHandler createContext(Project project) { + public ConnectorContextHandler createContext(Project project, WebBrowser browser) { ConnectorContextHandler context = new ConnectorContextHandler(); String baseUrl = project.getBaseUrl(); - context.addConnector(new WebSiteConnector(baseUrl)); + context.addConnector(new WebSiteConnector(baseUrl, browser)); context.addConnector(new WebServiceConnector(baseUrl)); context.addConnector(new CounterStoreConnector()); context.addConnector(new VariableStoreConnector()); diff --git a/main/src/main/java/de/learnlib/alex/core/learner/connectors/ConnectorManager.java b/main/src/main/java/de/learnlib/alex/core/learner/connectors/ConnectorManager.java index 5db2fa9b7..76507700e 100644 --- a/main/src/main/java/de/learnlib/alex/core/learner/connectors/ConnectorManager.java +++ b/main/src/main/java/de/learnlib/alex/core/learner/connectors/ConnectorManager.java @@ -1,3 +1,19 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package de.learnlib.alex.core.learner.connectors; import java.util.HashMap; @@ -9,7 +25,9 @@ */ public class ConnectorManager implements Iterable { - /** Map of all the connectors by their type. */ + /** + * Map of all the connectors by their type. + */ private Map, Connector> connectors; /** @@ -19,14 +37,30 @@ public ConnectorManager() { this.connectors = new HashMap<>(); } + /** + * Removes all connectors. + */ public void reset() { this.connectors.clear(); } + /** + * Adds a new connector to the manager. + * + * @param type The class of the connector to add. + * @param connector The instance of the connector to add. + */ public void addConnector(Class type, Connector connector) { this.connectors.put(type, connector); } + /** + * Get the connector specified by a connector class. + * + * @param type The class of the connector. + * @param The type of the connector. + * @return The connector that matches the specified class. + */ public T getConnector(Class type) { return (T) this.connectors.get(type); } diff --git a/main/src/main/java/de/learnlib/alex/core/learner/connectors/CounterStoreConnector.java b/main/src/main/java/de/learnlib/alex/core/learner/connectors/CounterStoreConnector.java index 0f044dd87..0d3c87292 100644 --- a/main/src/main/java/de/learnlib/alex/core/learner/connectors/CounterStoreConnector.java +++ b/main/src/main/java/de/learnlib/alex/core/learner/connectors/CounterStoreConnector.java @@ -1,17 +1,41 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package de.learnlib.alex.core.learner.connectors; import de.learnlib.alex.core.dao.CounterDAO; import de.learnlib.alex.core.dao.CounterDAOImpl; import de.learnlib.alex.core.entities.Counter; import de.learnlib.alex.core.entities.Project; +import de.learnlib.alex.core.entities.User; import de.learnlib.alex.exceptions.NotFoundException; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; /** * Connector to store and manage counters. */ public class CounterStoreConnector implements Connector { - /** The DAO to persist the counters to and fetch the counters from. */ + /** Use the learner logger. */ + private static final Logger LOGGER = LogManager.getLogger("learner"); + + /** + * The DAO to persist the counters to and fetch the counters from. + */ private CounterDAO counterDAO; /** @@ -22,6 +46,10 @@ public CounterStoreConnector() { this(new CounterDAOImpl()); } + /** + * Constructor. + * @param counterDAO An instance of a counter dao. + */ public CounterStoreConnector(CounterDAO counterDAO) { this.counterDAO = counterDAO; } @@ -36,48 +64,85 @@ public void dispose() { // nothing to do here } - public void set(Long projectId, String name, Integer value) { - Counter counter; + /** + * Set the value of an existing counter. + * Creates a new counter implicitly with the specified name and value if it does not exist yet. + * + * @param userId The id of the user. + * @param projectId The id of the project. + * @param name The name of the counter. + * @param value The value of the counter. + */ + public void set(Long userId, Long projectId, String name, Integer value) { try { - counter = counterDAO.get(projectId, name); + Counter counter = counterDAO.get(userId, projectId, name); counter.setValue(value); counterDAO.update(counter); } catch (NotFoundException e) { - createCounter(projectId, name, value); + createCounter(userId, projectId, name, value); } + LOGGER.debug("Set the counter '" + name + "' in the project <" + projectId + "> " + + "of user <" + userId + "> to '" + value + "'."); } - public void reset(Long projectId, String name) { - set(projectId, name, 0); - } - - public void increment(Long projectId, String name) { + /** + * Increment the value of an existing counter. + * Creates a new counter implicitly with the specified name if it does not exist yet. + * The value of the new counter will be 1. + * + * @param userId The id of the user. + * @param projectId The id of the project. + * @param name The name of the counter to increment. + */ + public void increment(Long userId, Long projectId, String name) { Counter counter; try { - counter = counterDAO.get(projectId, name); + counter = counterDAO.get(userId, projectId, name); counter.setValue(counter.getValue() + 1); counterDAO.update(counter); } catch (NotFoundException e) { - createCounter(projectId, name, 1); + counter = createCounter(userId, projectId, name, 1); } + LOGGER.debug("Incremented the counter '" + name + "' in the project <" + projectId + "> " + + "of user <" + userId + "> to '" + counter.getValue() + "'."); } - public Integer get(Long projectId, String name) throws IllegalStateException { + /** + * Get the value of an existing counter. + * + * @param userId The id of the user. + * @param projectId The id of the project. + * @param name The name of the counter. + * @return The positive value of the counter. + * @throws IllegalStateException + */ + public Integer get(Long userId, Long projectId, String name) throws IllegalStateException { try { Counter counter; - counter = counterDAO.get(projectId, name); + counter = counterDAO.get(userId, projectId, name); return counter.getValue(); } catch (NotFoundException e) { throw new IllegalStateException("The counter '" + name + "' was not set and has no value!"); } } - void createCounter(Long projectId, String name, Integer value) { + /** + * Create a new counter with a name and an initial, non negative value. + * The name of the counter should not exist in the database. + * + * @param userId The id of the user. + * @param projectId The id of the project. + * @param name The name of the counter. + * @param value The initial value of the counter. + * @return The created counter. + */ + Counter createCounter(Long userId, Long projectId, String name, Integer value) { Counter counter = new Counter(); + counter.setUser(new User(userId)); counter.setProject(new Project(projectId)); counter.setName(name); counter.setValue(value); counterDAO.create(counter); + return counter; } - } diff --git a/main/src/main/java/de/learnlib/alex/core/learner/connectors/FileStoreConnector.java b/main/src/main/java/de/learnlib/alex/core/learner/connectors/FileStoreConnector.java index 5a3e4ede5..dbe396796 100644 --- a/main/src/main/java/de/learnlib/alex/core/learner/connectors/FileStoreConnector.java +++ b/main/src/main/java/de/learnlib/alex/core/learner/connectors/FileStoreConnector.java @@ -1,17 +1,42 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package de.learnlib.alex.core.learner.connectors; import de.learnlib.alex.core.dao.FileDAO; import de.learnlib.alex.core.dao.FileDAOImpl; import de.learnlib.alex.exceptions.NotFoundException; +/** + * Connector to store and manage files. + */ public class FileStoreConnector implements Connector { + /** The FileDAO to use. */ private FileDAO fileDAO; + /** Constructor. */ public FileStoreConnector() { this.fileDAO = new FileDAOImpl(); } + /** + * Constructor. + * @param fileDAO An instance of the file dao. + */ public FileStoreConnector(FileDAO fileDAO) { this.fileDAO = fileDAO; } @@ -26,9 +51,18 @@ public void dispose() { // nothing to do here } - public String getAbsoluteFileLocation(Long projectId, String fileName) throws IllegalStateException { + /** + * Get the absolute path of a file in the uploads directory. + * + * @param userId The id of the user. + * @param projectId The id of the project. + * @param fileName The name of the file. + * @return The absolute path to the file. + * @throws IllegalStateException + */ + public String getAbsoluteFileLocation(Long userId, Long projectId, String fileName) throws IllegalStateException { try { - return fileDAO.getAbsoulteFilePath(projectId, fileName); + return fileDAO.getAbsoluteFilePath(userId, projectId, fileName); } catch (NotFoundException e) { throw new IllegalStateException("No file with the name '" + fileName + "' was uploaded into the project " + projectId + "."); diff --git a/main/src/main/java/de/learnlib/alex/core/learner/connectors/VariableStoreConnector.java b/main/src/main/java/de/learnlib/alex/core/learner/connectors/VariableStoreConnector.java index 6bffe8fd3..3fcc6db6e 100644 --- a/main/src/main/java/de/learnlib/alex/core/learner/connectors/VariableStoreConnector.java +++ b/main/src/main/java/de/learnlib/alex/core/learner/connectors/VariableStoreConnector.java @@ -1,5 +1,24 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package de.learnlib.alex.core.learner.connectors; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + import java.util.HashMap; import java.util.Map; @@ -8,6 +27,9 @@ */ public class VariableStoreConnector implements Connector { + /** Use the learner logger. */ + private static final Logger LOGGER = LogManager.getLogger("learner"); + /** The variable store. */ private Map store; @@ -38,6 +60,7 @@ public void dispose() { */ public void set(String name, String value) { store.put(name, value); + LOGGER.debug("Set the variable '" + name + "' to the value '" + value + "'."); } /** diff --git a/main/src/main/java/de/learnlib/alex/core/learner/connectors/WebBrowser.java b/main/src/main/java/de/learnlib/alex/core/learner/connectors/WebBrowser.java new file mode 100644 index 000000000..77123fb4c --- /dev/null +++ b/main/src/main/java/de/learnlib/alex/core/learner/connectors/WebBrowser.java @@ -0,0 +1,119 @@ +package de.learnlib.alex.core.learner.connectors; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; +import com.gargoylesoftware.htmlunit.BrowserVersion; +import com.gargoylesoftware.htmlunit.WebClient; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.openqa.selenium.WebDriver; +import org.openqa.selenium.chrome.ChromeDriver; +import org.openqa.selenium.firefox.FirefoxDriver; +import org.openqa.selenium.htmlunit.HtmlUnitDriver; +import org.openqa.selenium.ie.InternetExplorerDriver; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; + +/** + * Enum to select the browser used by Selenium. + */ +public enum WebBrowser { + + /** Use Mozilla Firefox. */ + FIREFOX(FirefoxDriver.class), + + /** Use Google Chrome. */ + CHROME(ChromeDriver.class), + + /** Use the Internet Explorer by Microsoft. */ + IE(InternetExplorerDriver.class), + + /** Simple & headless browser. This is the default driver. */ + HTMLUNITDRIVER(HtmlUnitDriver.class); + + + /** Use the learner logger. */ + private static final Logger LOGGER = LogManager.getLogger("leaner"); + + /** The connected WebDriver class. */ + private Class webDriverClass; + + /** + * Constructor. + * + * @param webDriverClass The class of the webDriver to instantiate. + */ + WebBrowser(Class webDriverClass) { + this.webDriverClass = webDriverClass; + } + + /** + * Get the enum type of a web browser from a string. + * + * @param name The name of the web browser. + * @return The corresponding WebBrowser enum type. + * @throws IllegalArgumentException + */ + @JsonCreator + public static WebBrowser fromString(String name) throws IllegalArgumentException { + return WebBrowser.valueOf(name.toUpperCase()); + } + + @Override + @JsonValue + public String toString() { + return name().toLowerCase(); + } + + /** + * Create an instance of a web driver given by the given class. + * + * @return An instance of a web driver. + */ + public WebDriver getWebDriver() { + try { + if (this == HTMLUNITDRIVER) { + HtmlUnitDriver driver = (HtmlUnitDriver) webDriverClass.getConstructor(BrowserVersion.class) + .newInstance(BrowserVersion.FIREFOX_38); + enableJavaScript(driver); + return driver; + } else { + return (WebDriver) webDriverClass.getConstructor().newInstance(); + } + // todo: logging and error handling + } catch (InstantiationException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } catch (InvocationTargetException e) { + e.printStackTrace(); + } catch (NoSuchMethodException e) { + e.printStackTrace(); + } + + return null; + } + + /** + * Solves issues with "wrong" or non strict JavaScript that is executed on the page (e.g. jQuery). + * Uses reflections to get the private 'webClient' property from the UnitDriver and set the flag + * to ignore js errors. + */ + private void enableJavaScript(HtmlUnitDriver htmlUnitDriver) { + try { + LOGGER.debug("Try enabling JavaScript for the HTMLUnitDriver"); + htmlUnitDriver.setJavascriptEnabled(true); + Field f = htmlUnitDriver.getClass().getDeclaredField("webClient"); + f.setAccessible(true); + WebClient client = (WebClient) f.get(htmlUnitDriver); + client.getOptions().setThrowExceptionOnScriptError(false); + } catch (NoSuchFieldException e) { + LOGGER.warn("Enabling JavaScript for the HTMLUnitDriver failed. " + + "Private Property 'webClient' does not exist", e); + } catch (IllegalAccessException e) { + LOGGER.warn("Enabling JavaScript for the HTMLUnitDriver failed. " + + "Problem accessing private 'webClient'", e); + } + } +} diff --git a/main/src/main/java/de/learnlib/alex/core/learner/connectors/WebServiceConnector.java b/main/src/main/java/de/learnlib/alex/core/learner/connectors/WebServiceConnector.java index 5caa9791b..fbf52e99e 100644 --- a/main/src/main/java/de/learnlib/alex/core/learner/connectors/WebServiceConnector.java +++ b/main/src/main/java/de/learnlib/alex/core/learner/connectors/WebServiceConnector.java @@ -1,3 +1,19 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package de.learnlib.alex.core.learner.connectors; import de.learnlib.alex.core.learner.BaseUrlManager; @@ -36,7 +52,7 @@ public class WebServiceConnector implements Connector { /** The response body of the last call done by the connection. */ private String body; - /** The cookies from th last call done by the connection */ + /** The cookies from th last call done by the connection. */ private Map cookies; /** diff --git a/main/src/main/java/de/learnlib/alex/core/learner/connectors/WebSiteConnector.java b/main/src/main/java/de/learnlib/alex/core/learner/connectors/WebSiteConnector.java index 390a1a413..491727ad4 100644 --- a/main/src/main/java/de/learnlib/alex/core/learner/connectors/WebSiteConnector.java +++ b/main/src/main/java/de/learnlib/alex/core/learner/connectors/WebSiteConnector.java @@ -1,7 +1,21 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package de.learnlib.alex.core.learner.connectors; -import com.gargoylesoftware.htmlunit.BrowserVersion; -import com.gargoylesoftware.htmlunit.WebClient; import de.learnlib.alex.core.learner.BaseUrlManager; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -10,12 +24,7 @@ import org.openqa.selenium.NoSuchElementException; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; -import org.openqa.selenium.chrome.ChromeDriver; -import org.openqa.selenium.firefox.FirefoxDriver; -import org.openqa.selenium.htmlunit.HtmlUnitDriver; -import org.openqa.selenium.ie.InternetExplorerDriver; -import java.lang.reflect.Field; import java.util.concurrent.TimeUnit; /** @@ -33,23 +42,29 @@ public class WebSiteConnector implements Connector { /** Max. time to wait for JavaScript to load before aborting */ private static final int JAVASCRIPT_LOADING_THRESHOLD = 5000; // 5 seconds - /** Use the logger for the server part. */ - private static final Logger LOGGER = LogManager.getLogger("server"); + /** Use the learner logger. */ + private static final Logger LOGGER = LogManager.getLogger("leaner"); - /** The driver used to send and receive data to a WebSite. */ - private WebDriver driver; + /** The browser to use. */ + private WebBrowser browser; /** A managed base url to use. */ private BaseUrlManager baseUrl; + /** The driver used to send and receive data to a WebSite. */ + private WebDriver driver; + /** * Constructor. * * @param baseUrl * The new base url to use for further request. All request will be based on this! + * @param browser + * The browser to use for further request. */ - public WebSiteConnector(String baseUrl) { + public WebSiteConnector(String baseUrl, WebBrowser browser) { this.baseUrl = new BaseUrlManager(baseUrl); + this.browser = browser; } /** @@ -57,46 +72,11 @@ public WebSiteConnector(String baseUrl) { */ @Override public void reset() { - String driverName = System.getProperty("alex.driver", "HTMLUnitDriver"); - switch (driverName.toLowerCase()) { - case "firefox": - this.driver = new FirefoxDriver(); - break; - case "chrome": - this.driver = new ChromeDriver(); - break; - case "ie": - this.driver = new InternetExplorerDriver(); - break; - default: - this.driver = new HtmlUnitDriver(BrowserVersion.FIREFOX_38); - enableJavaScript((HtmlUnitDriver) this.driver); - } - + this.driver = browser.getWebDriver(); this.driver.manage().timeouts().implicitlyWait(IMPLICITLY_WAIT_TIME, TimeUnit.SECONDS); this.driver.manage().timeouts().pageLoadTimeout(PAGE_LOAD_TIMEOUT_TIME, TimeUnit.SECONDS); } - /** - * Solves issues with "wrong" or non strict JavaScript that is executed on the page (e.g. jQuery). - * Uses reflections to get the private 'webClient' property from the UnitDriver and set the flag - * to ignore js errors. - */ - private void enableJavaScript(HtmlUnitDriver htmlUnitDriver) { - try { - LOGGER.debug("Try enabling JavaScript"); - htmlUnitDriver.setJavascriptEnabled(true); - Field f = htmlUnitDriver.getClass().getDeclaredField("webClient"); - f.setAccessible(true); - WebClient client = (WebClient) f.get(htmlUnitDriver); - client.getOptions().setThrowExceptionOnScriptError(false); - } catch (NoSuchFieldException e) { - LOGGER.warn("Enabling JavaScript failed. Private Property 'webClient' does not exist", e); - } catch (IllegalAccessException e) { - LOGGER.warn("Enabling JavaScript failed. Problem accessing private 'webClient'", e); - } - } - @Override public void dispose() { this.driver.close(); diff --git a/main/src/main/java/de/learnlib/alex/core/learner/connectors/package-info.java b/main/src/main/java/de/learnlib/alex/core/learner/connectors/package-info.java index d29966a18..11fb7797e 100644 --- a/main/src/main/java/de/learnlib/alex/core/learner/connectors/package-info.java +++ b/main/src/main/java/de/learnlib/alex/core/learner/connectors/package-info.java @@ -1,3 +1,19 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + /** * This package contains all connectors, which are used by the actions to execute their tasks, * as well as the related meta/ manager classes. diff --git a/main/src/main/java/de/learnlib/alex/core/learner/package-info.java b/main/src/main/java/de/learnlib/alex/core/learner/package-info.java index 3dc12ebdc..bc182cb07 100644 --- a/main/src/main/java/de/learnlib/alex/core/learner/package-info.java +++ b/main/src/main/java/de/learnlib/alex/core/learner/package-info.java @@ -1,3 +1,19 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + /** * This package contains the bridge between the ALEX and the LearnLib. */ diff --git a/main/src/main/java/de/learnlib/alex/package-info.java b/main/src/main/java/de/learnlib/alex/package-info.java index cb6d9538b..7add43d25 100644 --- a/main/src/main/java/de/learnlib/alex/package-info.java +++ b/main/src/main/java/de/learnlib/alex/package-info.java @@ -1,3 +1,19 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + /** * Top package for the ALEX. */ diff --git a/main/src/main/java/de/learnlib/alex/rest/CounterResource.java b/main/src/main/java/de/learnlib/alex/rest/CounterResource.java index 6a9ea9727..84956a0bd 100644 --- a/main/src/main/java/de/learnlib/alex/rest/CounterResource.java +++ b/main/src/main/java/de/learnlib/alex/rest/CounterResource.java @@ -1,11 +1,31 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package de.learnlib.alex.rest; import de.learnlib.alex.core.dao.CounterDAO; import de.learnlib.alex.core.entities.Counter; +import de.learnlib.alex.core.entities.User; import de.learnlib.alex.exceptions.NotFoundException; +import de.learnlib.alex.security.UserPrincipal; import de.learnlib.alex.utils.ResourceErrorHandler; import de.learnlib.alex.utils.ResponseHelper; import de.learnlib.alex.utils.StringList; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import javax.inject.Inject; import javax.ws.rs.DELETE; @@ -13,8 +33,10 @@ import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; +import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; +import javax.ws.rs.core.SecurityContext; import java.util.List; /** @@ -25,10 +47,17 @@ @Path("/projects/{project_id}/counters") public class CounterResource { + /** Use the logger for the server part. */ + private static final Logger LOGGER = LogManager.getLogger("server"); + /** The CounterDAO to use. */ @Inject private CounterDAO counterDAO; + /** The security context containing the user of the request. */ + @Context + private SecurityContext securityContext; + /** * Get all counters of a project. * @@ -41,8 +70,11 @@ public class CounterResource { @GET @Produces(MediaType.APPLICATION_JSON) public Response getAllCounters(@PathParam("project_id") Long projectId) { + User user = ((UserPrincipal) securityContext.getUserPrincipal()).getUser(); + LOGGER.trace("CounterResource.getAllCounters(" + projectId + ") for user " + user.toString() + "."); + try { - List counters = counterDAO.getAll(projectId); + List counters = counterDAO.getAll(user.getId(), projectId); return ResponseHelper.renderList(counters, Response.Status.OK); } catch (NotFoundException e) { return ResourceErrorHandler.createRESTErrorMessage("CounterResource.getAll", Response.Status.NOT_FOUND, e); @@ -63,15 +95,17 @@ public Response getAllCounters(@PathParam("project_id") Long projectId) { @Path("/{counter_name}") @Produces(MediaType.APPLICATION_JSON) public Response deleteCounter(@PathParam("project_id") Long projectId, @PathParam("counter_name") String name) { + User user = ((UserPrincipal) securityContext.getUserPrincipal()).getUser(); + LOGGER.trace("CounterResource.deleteCounter(" + projectId + ", " + name + ") for user " + user + "."); + try { - counterDAO.delete(projectId, name); + counterDAO.delete(user.getId(), projectId, name); return Response.status(Response.Status.NO_CONTENT).build(); } catch (NotFoundException e) { return ResourceErrorHandler.createRESTErrorMessage("CounterResource.deleteCounter", Response.Status.NOT_FOUND, e); } - } /** @@ -89,8 +123,11 @@ public Response deleteCounter(@PathParam("project_id") Long projectId, @PathPara @Produces(MediaType.APPLICATION_JSON) public Response deleteCounter(@PathParam("project_id") Long projectId, @PathParam("counter_names") StringList names) { + User user = ((UserPrincipal) securityContext.getUserPrincipal()).getUser(); + LOGGER.trace("CounterResource.deleteCounter(" + projectId + ", " + names + ") for user " + user + "."); + try { - counterDAO.delete(projectId, names.toArray(new String[names.size()])); + counterDAO.delete(user.getId(), projectId, names.toArray(new String[names.size()])); return Response.status(Response.Status.NO_CONTENT).build(); } catch (NotFoundException e) { return ResourceErrorHandler.createRESTErrorMessage("CounterResource.deleteCounter", diff --git a/main/src/main/java/de/learnlib/alex/rest/FileResource.java b/main/src/main/java/de/learnlib/alex/rest/FileResource.java index 9d0809eaf..6b594d2fc 100644 --- a/main/src/main/java/de/learnlib/alex/rest/FileResource.java +++ b/main/src/main/java/de/learnlib/alex/rest/FileResource.java @@ -1,12 +1,33 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package de.learnlib.alex.rest; import de.learnlib.alex.core.dao.FileDAO; import de.learnlib.alex.core.entities.UploadableFile; +import de.learnlib.alex.core.entities.User; import de.learnlib.alex.exceptions.NotFoundException; +import de.learnlib.alex.security.UserPrincipal; import de.learnlib.alex.utils.ResourceErrorHandler; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.glassfish.jersey.media.multipart.FormDataContentDisposition; import org.glassfish.jersey.media.multipart.FormDataParam; +import javax.annotation.security.RolesAllowed; import javax.inject.Inject; import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; @@ -15,27 +36,54 @@ import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; +import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; +import javax.ws.rs.core.SecurityContext; import java.io.IOException; import java.io.InputStream; import java.util.List; +/** + * REST API to manage files. + * @resourcePath files + * @resourceDescription Operations about files + */ @Path("/projects/{project_id}/files") +@RolesAllowed({"REGISTERED"}) public class FileResource { + /** Use the logger for the server part. */ + private static final Logger LOGGER = LogManager.getLogger("server"); + + /** The security context containing the user of the request. */ + @Context + private SecurityContext securityContext; + + /** The FileDAO to use. */ @Inject private FileDAO fileDAO; + /** + * Uploads a new file to the corresponding upload directory uploads/{userId}/{projectId}/{filename}. + * + * @param projectId The id of the project the file belongs to. + * @param uploadedInputStream The input stream for the file. + * @param fileDetail The form data of the file. + * @return The HTTP response with the file object on success. + */ @POST - @Path("/") @Consumes(MediaType.MULTIPART_FORM_DATA) @Produces(MediaType.APPLICATION_JSON) public Response uploadFile(@PathParam("project_id") Long projectId, @FormDataParam("file") InputStream uploadedInputStream, @FormDataParam("file") FormDataContentDisposition fileDetail) { + User user = ((UserPrincipal) securityContext.getUserPrincipal()).getUser(); + LOGGER.trace("FileResource.uploadFile(" + projectId + ", " + uploadedInputStream + ", " + fileDetail + ") " + + "for user " + user + "."); + try { - fileDAO.create(projectId, uploadedInputStream, fileDetail); + fileDAO.create(user.getId(), projectId, uploadedInputStream, fileDetail); UploadableFile result = new UploadableFile(); result.setName(fileDetail.getFileName()); @@ -53,12 +101,20 @@ public Response uploadFile(@PathParam("project_id") Long projectId, } } + /** + * Get all available files of a project. + * + * @param projectId The id of the project. + * @return The list of all files of the project. + */ @GET - @Path("/") @Produces(MediaType.APPLICATION_JSON) public Response getAllFiles(@PathParam("project_id") Long projectId) { + User user = ((UserPrincipal) securityContext.getUserPrincipal()).getUser(); + LOGGER.trace("FileResource.getAllFiles(" + projectId + ") for user " + user + "."); + try { - List allFiles = fileDAO.getAll(projectId); + List allFiles = fileDAO.getAll(user.getId(), projectId); return Response.ok(allFiles).build(); } catch (NotFoundException e) { return ResourceErrorHandler.createRESTErrorMessage("FileResource.getAllFiles", @@ -66,17 +122,25 @@ public Response getAllFiles(@PathParam("project_id") Long projectId) { } } + /** + * Delete a single file from the project directory. + * + * @param projectId The id of the project. + * @param fileName The name of the file. + * @return Status 204 No Content on success. + */ @DELETE @Path("/{file_name}") @Produces(MediaType.APPLICATION_JSON) public Response deleteOneFile(@PathParam("project_id") Long projectId, @PathParam("file_name") String fileName) { + User user = ((UserPrincipal) securityContext.getUserPrincipal()).getUser(); + LOGGER.trace("FileResource.deleteOneFile(" + projectId + ", " + fileName + ") for user " + user + "."); + try { - fileDAO.delete(projectId, fileName); + fileDAO.delete(user.getId(), projectId, fileName); return Response.status(Response.Status.NO_CONTENT).build(); } catch (NotFoundException e) { return ResourceErrorHandler.createRESTErrorMessage("FileResource.uploadFile", Response.Status.NOT_FOUND, e); } } - - } diff --git a/main/src/main/java/de/learnlib/alex/rest/IFrameProxyResource.java b/main/src/main/java/de/learnlib/alex/rest/IFrameProxyResource.java index de2091e0a..83ff7c1b6 100644 --- a/main/src/main/java/de/learnlib/alex/rest/IFrameProxyResource.java +++ b/main/src/main/java/de/learnlib/alex/rest/IFrameProxyResource.java @@ -1,3 +1,19 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package de.learnlib.alex.rest; import org.apache.logging.log4j.LogManager; @@ -55,6 +71,8 @@ public class IFrameProxyResource { @GET @Produces(MediaType.TEXT_HTML) public Response doGetProxy(@QueryParam("url") String url, @HeaderParam("Cookie") String cookies) { + LOGGER.trace("IFrameProxyResource.doGetProxy(" + url + ", " + cookies + ")."); + try { Connection connection = Jsoup.connect(url); connection = parseAndProcessCookies(connection, cookies); @@ -86,8 +104,11 @@ public Response doGetProxy(@QueryParam("url") String url, @HeaderParam("Cookie") @POST @Consumes(MediaType.APPLICATION_FORM_URLENCODED) @Produces(MediaType.TEXT_HTML) - public Response doPostProxy(@QueryParam("url") String url, @HeaderParam("Cookie") String cookies, + public Response doPostProxy(@QueryParam("url") String url, + @HeaderParam("Cookie") String cookies, MultivaluedMap body) { + LOGGER.trace("IFrameProxyResource.doPostProxy(" + url + ", " + cookies + ", " + body + ")."); + try { Connection connection = Jsoup.connect(url); connection = parseAndProcessCookies(connection, cookies); diff --git a/main/src/main/java/de/learnlib/alex/rest/LearnerResource.java b/main/src/main/java/de/learnlib/alex/rest/LearnerResource.java index ae853dba2..ef07c3ec4 100644 --- a/main/src/main/java/de/learnlib/alex/rest/LearnerResource.java +++ b/main/src/main/java/de/learnlib/alex/rest/LearnerResource.java @@ -1,3 +1,19 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package de.learnlib.alex.rest; import de.learnlib.alex.core.dao.LearnerResultDAO; @@ -11,14 +27,17 @@ import de.learnlib.alex.core.entities.Project; import de.learnlib.alex.core.entities.Symbol; import de.learnlib.alex.core.entities.SymbolSet; +import de.learnlib.alex.core.entities.User; import de.learnlib.alex.core.learner.Learner; import de.learnlib.alex.exceptions.LearnerException; import de.learnlib.alex.exceptions.NotFoundException; +import de.learnlib.alex.security.UserPrincipal; import de.learnlib.alex.utils.ResourceErrorHandler; import de.learnlib.alex.utils.ResponseHelper; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import javax.annotation.security.RolesAllowed; import javax.inject.Inject; import javax.ws.rs.Consumes; import javax.ws.rs.GET; @@ -26,9 +45,11 @@ import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; +import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; +import javax.ws.rs.core.SecurityContext; import java.util.LinkedList; import java.util.List; @@ -38,6 +59,7 @@ * @resourceDescription Operations about the learning */ @Path("/learner/") +@RolesAllowed({"REGISTERED"}) public class LearnerResource { /** Use the logger for the server part. */ @@ -51,6 +73,7 @@ public class LearnerResource { @Inject private SymbolDAO symbolDAO; + /** The {@link LearnerResultDAO} to use. */ @Inject private LearnerResultDAO learnerResultDAO; @@ -58,6 +81,10 @@ public class LearnerResource { @Inject private Learner learner; + /** The security context containing the user of the request. */ + @Context + private SecurityContext securityContext; + /** * Start the learning. * @@ -68,33 +95,34 @@ public class LearnerResource { * @return The status of the current learn process. * @successResponse 200 OK * @responseType de.learnlib.alex.core.entities.LearnerStatus - * @errorResponse 400 bad request `de.learnlib.alex.utils.ResourceErrorHandler.RESTError - * @errorResponse 404 not found `de.learnlib.alex.utils.ResourceErrorHandler.RESTError + * @errorResponse 302 not modified `de.learnlib.alex.utils.ResourceErrorHandler.RESTError + * @errorResponse 400 bad request `de.learnlib.alex.utils.ResourceErrorHandler.RESTError + * @errorResponse 404 not found `de.learnlib.alex.utils.ResourceErrorHandler.RESTError */ @POST @Path("/start/{project_id}") @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) public Response start(@PathParam("project_id") long projectId, LearnerConfiguration configuration) { - LearnerStatus status = new LearnerStatus(learner); - try { - Project project = projectDAO.getByID(projectId, ProjectDAO.EmbeddableFields.ALL); + User user = ((UserPrincipal) securityContext.getUserPrincipal()).getUser(); + LOGGER.trace("LearnerResource.start(" + projectId + ", " + configuration + ") for user " + user + "."); - try { - Symbol resetSymbol = symbolDAO.get(projectId, configuration.getResetSymbolAsIdRevisionPair()); - configuration.setResetSymbol(resetSymbol); - } catch (NotFoundException e) { // Extra exception to emphasize that this is the reset symbol. - throw new NotFoundException("Could not find the reset symbol!", e); + try { + if ( + (configuration.getUserId() != null && !user.getId().equals(configuration.getUserId())) + || (configuration.getProjectId() != null && !configuration.getProjectId().equals(projectId)) + ) { + throw new IllegalArgumentException("If an user or a project is provided in the configuration, " + + "they must match the parameters in the path!"); } - List symbols = symbolDAO.getAll(projectId, configuration.getSymbolsAsIdRevisionPairs()); - configuration.setSymbols(symbols); + Project project = projectDAO.getByID(user.getId(), projectId, ProjectDAO.EmbeddableFields.ALL); - learner.start(project, configuration); + learner.start(user, project, configuration); + LearnerStatus status = learner.getStatus(user); return Response.ok(status).build(); } catch (IllegalStateException e) { - LOGGER.info("tried to start the learning again."); - return Response.status(Status.NOT_MODIFIED).entity(status).build(); + return ResourceErrorHandler.createRESTErrorMessage("LearnerResource.start", Status.NOT_MODIFIED, e); } catch (IllegalArgumentException e) { return ResourceErrorHandler.createRESTErrorMessage("LearnerResource.start", Status.BAD_REQUEST, e); } catch (NotFoundException e) { @@ -116,6 +144,9 @@ public Response start(@PathParam("project_id") long projectId, LearnerConfigurat * @return The status of the current learn process. * @successResponse 200 OK * @responseType de.learnlib.alex.core.entities.LearnerStatus + * @errorResponse 302 not modified `de.learnlib.alex.utils.ResourceErrorHandler.RESTError + * @errorResponse 400 bad request `de.learnlib.alex.utils.ResourceErrorHandler.RESTError + * @errorResponse 404 not found `de.learnlib.alex.utils.ResourceErrorHandler.RESTError */ @POST @Path("/resume/{project_id}/{test_run}") @@ -123,20 +154,30 @@ public Response start(@PathParam("project_id") long projectId, LearnerConfigurat @Produces(MediaType.APPLICATION_JSON) public Response resume(@PathParam("project_id") long projectId, @PathParam("test_run") long testRunNo, LearnerResumeConfiguration configuration) { - LearnerStatus status = new LearnerStatus(learner); + User user = ((UserPrincipal) securityContext.getUserPrincipal()).getUser(); + LOGGER.trace("LearnerResource.resume(" + projectId + ", " + testRunNo + ", " + configuration + ") " + + "for user " + user + "."); + try { - Project project = projectDAO.getByID(projectId); // check if project exists + projectDAO.getByID(user.getId(), projectId); // check if project exists + + LearnerResult lastResult = learner.getResult(user); + if (lastResult == null) { + return Response.status(Status.NOT_FOUND).build(); + } - LearnerResult lastResult = learner.getResult(); if (lastResult.getProjectId() != projectId || lastResult.getTestNo() != testRunNo) { LOGGER.info("could not resume the learner of another project or with an wrong test run."); - return Response.status(Status.NOT_MODIFIED).entity(status).build(); + throw new IllegalArgumentException("The given project id or test no does not match " + + "with the latest learn result!"); } - learner.resume(configuration); + learner.resume(user, configuration); + LearnerStatus status = learner.getStatus(user); return Response.ok(status).build(); } catch (IllegalStateException e) { LOGGER.info("tried to restart the learning while the learner is running."); + LearnerStatus status = learner.getStatus(user); return Response.status(Status.NOT_MODIFIED).entity(status).build(); } catch (IllegalArgumentException e) { return ResourceErrorHandler.createRESTErrorMessage("LearnerResource.resume", Status.BAD_REQUEST, e); @@ -159,12 +200,15 @@ public Response resume(@PathParam("project_id") long projectId, @PathParam("test @Path("/stop") @Produces(MediaType.APPLICATION_JSON) public Response stop() { - LearnerStatus status = new LearnerStatus(learner); - if (learner.isActive()) { - learner.stop(); // Hammer Time + User user = ((UserPrincipal) securityContext.getUserPrincipal()).getUser(); + LOGGER.trace("LearnerResource.stop() for user " + user + "."); + + if (learner.isActive(user)) { + learner.stop(user); // Hammer Time } else { LOGGER.info("tried to stop the learning again."); } + LearnerStatus status = learner.getStatus(user); return Response.ok(status).build(); } @@ -179,7 +223,10 @@ public Response stop() { @Path("/active") @Produces(MediaType.APPLICATION_JSON) public Response isActive() { - LearnerStatus status = new LearnerStatus(learner); + User user = ((UserPrincipal) securityContext.getUserPrincipal()).getUser(); + LOGGER.trace("LearnerResource.isActive() for user " + user + "."); + + LearnerStatus status = learner.getStatus(user); return Response.ok(status).build(); } @@ -195,14 +242,18 @@ public Response isActive() { @Path("/status") @Produces(MediaType.APPLICATION_JSON) public Response getResult() { - LearnerResult resultInThread = learner.getResult(); + User user = ((UserPrincipal) securityContext.getUserPrincipal()).getUser(); + LOGGER.trace("LearnerResource.getResult() for user " + user + "."); + + LearnerResult resultInThread = learner.getResult(user); if (resultInThread == null) { IllegalStateException e = new IllegalStateException("No result was learned in this instance of ALEX."); return ResourceErrorHandler.createRESTErrorMessage("LearnerResource.status", Status.NOT_FOUND, e); } try { - learnerResultDAO.get(resultInThread.getProjectId(), resultInThread.getTestNo()); + learnerResultDAO.get(resultInThread.getUserId(), resultInThread.getProjectId(), + resultInThread.getTestNo(), false); } catch (NotFoundException nfe) { IllegalArgumentException e = new IllegalArgumentException("The last learned result was deleted."); return ResourceErrorHandler.createRESTErrorMessage("LearnerResource.status", Status.NOT_FOUND, e); @@ -230,35 +281,39 @@ public Response getResult() { @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) public Response readOutput(@PathParam("project_id") Long projectId, SymbolSet symbolSet) { + User user = ((UserPrincipal) securityContext.getUserPrincipal()).getUser(); + LOGGER.trace("LearnerResource.readOutput(" + projectId + ", " + symbolSet + ") for user " + user + "."); + try { - Project project = projectDAO.getByID(projectId); + Project project = projectDAO.getByID(user.getId(), projectId); IdRevisionPair resetSymbolAsIdRevisionPair = symbolSet.getResetSymbolAsIdRevisionPair(); if (resetSymbolAsIdRevisionPair == null) { throw new NotFoundException("No reset symbol specified!"); } - Symbol resetSymbol = symbolDAO.get(projectId, resetSymbolAsIdRevisionPair); + Symbol resetSymbol = symbolDAO.get(user, projectId, resetSymbolAsIdRevisionPair); symbolSet.setResetSymbol(resetSymbol); - List symbols = loadSymbols(projectId, symbolSet.getSymbolsAsIdRevisionPairs()); + List symbols = loadSymbols(user, projectId, symbolSet.getSymbolsAsIdRevisionPairs()); symbolSet.setSymbols(symbols); - List results = learner.readOutputs(project, resetSymbol, symbols); + List results = learner.readOutputs(user, project, resetSymbol, symbols); return ResponseHelper.renderList(results, Status.OK); } catch (NotFoundException e) { return ResourceErrorHandler.createRESTErrorMessage("LearnerResource.readOutput", Status.NOT_FOUND, e); } catch (LearnerException e) { - return ResourceErrorHandler.createRESTErrorMessage("LearnerResource.readOutput", Status.BAD_REQUEST , e); + return ResourceErrorHandler.createRESTErrorMessage("LearnerResource.readOutput", Status.BAD_REQUEST, e); } } // load all from SymbolDAO always orders the Symbols by ID - private List loadSymbols(Long projectId, List idRevisionPairs) throws NotFoundException { + private List loadSymbols(User user, Long projectId, List idRevisionPairs) + throws NotFoundException { List symbols = new LinkedList<>(); for (IdRevisionPair pair : idRevisionPairs) { - Symbol symbol = symbolDAO.get(projectId, pair); + Symbol symbol = symbolDAO.get(user, projectId, pair); symbols.add(symbol); } diff --git a/main/src/main/java/de/learnlib/alex/rest/LearnerResultResource.java b/main/src/main/java/de/learnlib/alex/rest/LearnerResultResource.java index 01d5bc78a..5f49cfb1d 100644 --- a/main/src/main/java/de/learnlib/alex/rest/LearnerResultResource.java +++ b/main/src/main/java/de/learnlib/alex/rest/LearnerResultResource.java @@ -1,20 +1,44 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package de.learnlib.alex.rest; import de.learnlib.alex.core.dao.LearnerResultDAO; +import de.learnlib.alex.core.entities.LearnerResult; +import de.learnlib.alex.core.entities.User; import de.learnlib.alex.exceptions.NotFoundException; +import de.learnlib.alex.security.UserPrincipal; import de.learnlib.alex.utils.IdsList; import de.learnlib.alex.utils.ResourceErrorHandler; import de.learnlib.alex.utils.ResponseHelper; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import javax.inject.Inject; +import javax.validation.ValidationException; import javax.ws.rs.DELETE; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; -import javax.validation.ValidationException; +import javax.ws.rs.core.SecurityContext; import java.util.List; /** @@ -25,117 +49,142 @@ @Path("/projects/{project_id}/results") public class LearnerResultResource { + /** Use the logger for the server part. */ + private static final Logger LOGGER = LogManager.getLogger("server"); + /** The {@link de.learnlib.alex.core.dao.LearnerResultDAO} to use. */ @Inject private LearnerResultDAO learnerResultDAO; + /** The security context containing the user of the request. */ + @Context + private SecurityContext securityContext; + /** - * Get all final / last results of each test run within one project. + * Get all learn results one project. * * @param projectId - * The project of the test results. - * @return A List of all final / lasts test results within one project. + * The project of the learn results. + * @param embed + * By default no steps are included in the response. However you can ask to include them with + * this parameter set to 'steps'. + * @return A List of all learn results within one project. * @successResponse 200 OK * @responseType java.util.List - * @errorResponse 404 not found `de.learnlib.alex.utils.ResourceErrorHandler.RESTError + * @errorResponse 400 bad request `de.learnlib.alex.utils.ResourceErrorHandler.RESTError + * @errorResponse 404 not found `de.learnlib.alex.utils.ResourceErrorHandler.RESTError */ @GET @Produces(MediaType.APPLICATION_JSON) - public Response getAllFinalResults(@PathParam("project_id") long projectId) { + public Response getAll(@PathParam("project_id") long projectId, @QueryParam("embed") String embed) { + User user = ((UserPrincipal) securityContext.getUserPrincipal()).getUser(); + LOGGER.trace("LearnerResultResource.getAllFinalResults(" + projectId + ") for user " + user + "."); + try { - List resultsAsJSON = learnerResultDAO.getAllAsJSON(projectId); - return ResponseHelper.renderStringList(resultsAsJSON, Response.Status.OK); + boolean includeSteps = parseEmbeddableFields(embed); + + List results = learnerResultDAO.getAll(user.getId(), projectId, includeSteps); + return ResponseHelper.renderList(results, Response.Status.OK); + } catch (IllegalArgumentException e) { + return ResourceErrorHandler.createRESTErrorMessage("LearnerResultResource.getAllSteps", + Response.Status.BAD_REQUEST, e); } catch (NotFoundException e) { - return ResourceErrorHandler.createRESTErrorMessage("HypothesesResource.getAllFinalResults", + return ResourceErrorHandler.createRESTErrorMessage("LearnerResultResource.getAllFinalResults", Response.Status.NOT_FOUND, e); } } /** - * Get all steps of test runs, i.e. all results that were generated during the run. + * Get one / a list of learn result(s). * * @param projectId - * The project of the test runs. + * The project of the learn result(s). * @param testNos - * The number(s) of the test run(s). + * The number(s) of the learn result(s). + * @param embed + * By default no steps are included in the response. However you can ask to include them with + * this parameter set to 'steps'. * @return A List of all step of possible multiple test runs. * @successResponse 200 OK * @responseType java.util.List + * @errorResponse 400 bad request `de.learnlib.alex.utils.ResourceErrorHandler.RESTError * @errorResponse 404 not found `de.learnlib.alex.utils.ResourceErrorHandler.RESTError */ @GET - @Path("{test_nos}/complete") + @Path("{test_nos}") @Produces(MediaType.APPLICATION_JSON) - public Response getAllStep(@PathParam("project_id") Long projectId, - @PathParam("test_nos") IdsList testNos) { + public Response getAll(@PathParam("project_id") Long projectId, + @PathParam("test_nos") IdsList testNos, + @QueryParam("embed") String embed) { + User user = ((UserPrincipal) securityContext.getUserPrincipal()).getUser(); + LOGGER.trace("LearnerResultResource.getAllSteps(" + projectId + ", " + testNos + ") for user " + user + "."); + try { + boolean includeSteps = parseEmbeddableFields(embed); + if (testNos.size() == 1) { - List result = learnerResultDAO.getAllAsJSON(projectId, testNos.get(0)); - return ResponseHelper.renderStringList(result, Response.Status.OK); + LearnerResult result = learnerResultDAO.get(user.getId(), projectId, testNos.get(0), includeSteps); + return Response.ok(result).build(); } else { - List> result = learnerResultDAO.getAllAsJson(projectId, testNos); - return ResponseHelper.renderStringList(result, Response.Status.OK); + List result = learnerResultDAO.getAll(user.getId(), + projectId, + testNos.toArray(new Long[testNos.size()]), + includeSteps); + return ResponseHelper.renderList(result, Response.Status.OK); } + } catch (IllegalArgumentException e) { + return ResourceErrorHandler.createRESTErrorMessage("LearnerResultResource.getAllSteps", + Response.Status.BAD_REQUEST, e); } catch (NotFoundException e) { - return ResourceErrorHandler.createRESTErrorMessage("HypothesesResource.getAllStep", - Response.Status.NOT_FOUND, e); - } - } - - /** - * Get the final / latest result of one test run. - * - * @param projectId - * The project of the test run. - * @param testNo - * The number of the test run. - * @return The final / latest result of one test run. - * @successResponse 200 OK - * @responseType de.learnlib.alex.core.entities.LearnerResult - * @errorResponse 404 not found `de.learnlib.alex.utils.ResourceErrorHandler.RESTError - */ - @GET - @Path("{test_no}") - @Produces(MediaType.APPLICATION_JSON) - public Response getOneFinalResult(@PathParam("project_id") long projectId, @PathParam("test_no") long testNo) { - try { - String json = learnerResultDAO.getAsJSON(projectId, testNo); - return Response.ok(json).build(); - } catch (NotFoundException e) { - return ResourceErrorHandler.createRESTErrorMessage("HypothesesResource.getOneFinalResult", + return ResourceErrorHandler.createRESTErrorMessage("LearnerResultResource.getAllSteps", Response.Status.NOT_FOUND, e); } } /** - * Delete all results of one test run. + * Delete one or more learn result(s). * * @param projectId - * The project of the test run. + * The project of the learn results. * @param testNumbers - * The numbers of the results to delete as a comma (',') separated list. + * The test numbers of the results to delete as a comma (',') separated list. E.g. 1,2,3 * @return On success no content will be returned; an error message on failure. * @successResponse 204 OK & no content - * @errorResponse 404 not found `de.learnlib.alex.utils.ResourceErrorHandler.RESTError - * 403 forbidden `de.learnlib.alex.utils.ResourceErrorHandler.RESTError + * @errorResponse 400 bad request `de.learnlib.alex.utils.ResourceErrorHandler.RESTError + * @errorResponse 404 not found `de.learnlib.alex.utils.ResourceErrorHandler.RESTError */ @DELETE @Path("{test_numbers}") @Produces(MediaType.APPLICATION_JSON) public Response deleteResultSet(@PathParam("project_id") Long projectId, @PathParam("test_numbers") IdsList testNumbers) { + User user = ((UserPrincipal) securityContext.getUserPrincipal()).getUser(); + LOGGER.trace("LearnerResultResource.deleteResultSet(" + projectId + ", " + testNumbers + ") " + + "for user " + user + "."); + try { Long[] numbersLongArray = testNumbers.toArray(new Long[testNumbers.size()]); - learnerResultDAO.delete(projectId, numbersLongArray); + learnerResultDAO.delete(user, projectId, numbersLongArray); return Response.status(Response.Status.NO_CONTENT).build(); } catch (NotFoundException e) { - return ResourceErrorHandler.createRESTErrorMessage("HypothesesResource.deleteResultSet", + return ResourceErrorHandler.createRESTErrorMessage("LearnerResultResource.deleteResultSet", Response.Status.NOT_FOUND, e); } catch (ValidationException e) { - return ResourceErrorHandler.createRESTErrorMessage("HypothesesResource.deleteResultSet", + return ResourceErrorHandler.createRESTErrorMessage("LearnerResultResource.deleteResultSet", Response.Status.BAD_REQUEST, e); } } + private boolean parseEmbeddableFields(String embed) throws IllegalArgumentException { + if (embed == null + || embed.isEmpty()) { + return false; + } else if (embed.toLowerCase().equals("steps")) { + return true; + } else { + throw new IllegalArgumentException("Could not parse the embed value '" + embed + "'."); + } + } + } diff --git a/main/src/main/java/de/learnlib/alex/rest/ProjectResource.java b/main/src/main/java/de/learnlib/alex/rest/ProjectResource.java index 20d4c73bb..aab365400 100644 --- a/main/src/main/java/de/learnlib/alex/rest/ProjectResource.java +++ b/main/src/main/java/de/learnlib/alex/rest/ProjectResource.java @@ -1,11 +1,33 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package de.learnlib.alex.rest; import de.learnlib.alex.core.dao.ProjectDAO; import de.learnlib.alex.core.entities.Project; +import de.learnlib.alex.core.entities.User; import de.learnlib.alex.exceptions.NotFoundException; +import de.learnlib.alex.security.UserPrincipal; import de.learnlib.alex.utils.ResourceErrorHandler; import de.learnlib.alex.utils.ResponseHelper; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.shiro.authz.UnauthorizedException; +import javax.annotation.security.RolesAllowed; import javax.inject.Inject; import javax.validation.ValidationException; import javax.ws.rs.Consumes; @@ -21,6 +43,7 @@ import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; +import javax.ws.rs.core.SecurityContext; import javax.ws.rs.core.UriInfo; import java.util.List; @@ -29,8 +52,12 @@ * @resourceDescription Operations about projects */ @Path("/projects") +@RolesAllowed({"REGISTERED"}) public class ProjectResource { + /** Use the logger for the server part. */ + private static final Logger LOGGER = LogManager.getLogger("server"); + /** Context information about the URI. */ @Context private UriInfo uri; @@ -39,9 +66,13 @@ public class ProjectResource { @Inject private ProjectDAO projectDAO; + /** The security context containing the user of the request. */ + @Context + private SecurityContext securityContext; + /** * Create a new Project. - * + * * @param project * The project to create. * @return On success the added project (enhanced with information from the DB); an error message on failure. @@ -53,6 +84,16 @@ public class ProjectResource { @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) public Response create(Project project) { + User user = ((UserPrincipal) securityContext.getUserPrincipal()).getUser(); + LOGGER.trace("ProjectResource.create(" + project + ") for user " + user + "."); + + // make sure that if an user for a project is given, it the correct user id. + if (project.getUser() != null && !user.equals(project.getUser())) { + throw new ValidationException("The given user id does not belong to the current user!"); + } + + project.setUser(user); + try { projectDAO.create(project); String projectURL = uri.getBaseUri() + "projects/" + project.getId(); @@ -63,11 +104,11 @@ public Response create(Project project) { } /** - * Get a list of all the projects. + * Get a list of all the projects owned by the user of the request. * * @param embed * By default no related objects are included in the projects. However you can ask to include them with - * this parameter. Valid values are: 'symbols', 'groups', 'default_group' & 'test_results'. + * this parameter. Valid values are: 'symbols', 'groups', 'default_group', 'test_results' & 'counters'. * You can request multiple by just put a ',' between them. * @return All projects in a list. * @responseType java.util.List @@ -76,6 +117,9 @@ public Response create(Project project) { @GET @Produces(MediaType.APPLICATION_JSON) public Response getAll(@QueryParam("embed") String embed) { + User user = ((UserPrincipal) securityContext.getUserPrincipal()).getUser(); + LOGGER.trace("ProjectResource.getAll(" + embed + ") for user " + user + "."); + ProjectDAO.EmbeddableFields[] embeddableFields; try { embeddableFields = parseEmbeddableFields(embed); @@ -83,18 +127,18 @@ public Response getAll(@QueryParam("embed") String embed) { return ResourceErrorHandler.createRESTErrorMessage("ProjectResource.get", Status.BAD_REQUEST, e); } - List projects = projectDAO.getAll(embeddableFields); + List projects = projectDAO.getAll(user, embeddableFields); return ResponseHelper.renderList(projects, Status.OK); } /** * Get a specific project. - * - * @param id + * + * @param projectId * The ID of the project. * @param embed * By default no related objects are included in the project. However you can ask to include them with - * this parameter. Valid values are: 'symbols', 'groups', 'default_group' & 'test_results'. + * this parameter. Valid values are: 'symbols', 'groups', 'default_group', 'test_results' & 'counters'. * You can request multiple by just put a ',' between them. * @return The project or an error message. * @responseType de.learnlib.alex.core.entities.Project @@ -104,22 +148,32 @@ public Response getAll(@QueryParam("embed") String embed) { @GET @Path("/{id}") @Produces(MediaType.APPLICATION_JSON) - public Response get(@PathParam("id") long id, @QueryParam("embed") String embed) { + public Response get(@PathParam("id") long projectId, @QueryParam("embed") String embed) { + User user = ((UserPrincipal) securityContext.getUserPrincipal()).getUser(); + LOGGER.trace("ProjectResource.get(" + projectId + ", " + embed + ") for user " + user + "."); + ProjectDAO.EmbeddableFields[] embeddableFields; try { embeddableFields = parseEmbeddableFields(embed); - Project project = projectDAO.getByID(id, embeddableFields); - return Response.ok(project).build(); + Project project = projectDAO.getByID(user.getId(), projectId, embeddableFields); + + if (project.getUser().equals(user)) { + return Response.ok(project).build(); + } else { + throw new UnauthorizedException("You are not allowed to view this project"); + } } catch (IllegalArgumentException e) { return ResourceErrorHandler.createRESTErrorMessage("ProjectResource.get", Status.BAD_REQUEST, e); } catch (NotFoundException e) { return ResourceErrorHandler.createRESTErrorMessage("ProjectResource.get", Status.NOT_FOUND, null); + } catch (UnauthorizedException e) { + return ResourceErrorHandler.createRESTErrorMessage("ProjectResource.get", Status.UNAUTHORIZED, e); } } /** * Update a specific project. - * + * * @param id * The ID of the project. * @param project @@ -135,24 +189,33 @@ public Response get(@PathParam("id") long id, @QueryParam("embed") String embed) @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) public Response update(@PathParam("id") long id, Project project) { + User user = ((UserPrincipal) securityContext.getUserPrincipal()).getUser(); + LOGGER.trace("ProjectResource.update(" + id + ", " + project + ") for user " + user + "."); + if (id != project.getId()) { return Response.status(Status.BAD_REQUEST).build(); } else { try { - projectDAO.update(project); - return Response.ok(project).build(); + if (project.getUser() == null || user.equals(project.getUser())) { + projectDAO.update(project); + return Response.ok(project).build(); + } else { + throw new UnauthorizedException("You are not allowed to update this project"); + } } catch (NotFoundException e) { return ResourceErrorHandler.createRESTErrorMessage("ProjectResource.update", Status.NOT_FOUND, e); } catch (ValidationException e) { return ResourceErrorHandler.createRESTErrorMessage("ProjectResource.update", Status.BAD_REQUEST, e); + } catch (UnauthorizedException e) { + return ResourceErrorHandler.createRESTErrorMessage("ProjectResource.update", Status.UNAUTHORIZED, e); } } } /** * Delete a specific project. - * - * @param id + * + * @param projectId * The ID of the project. * @return On success no content will be returned; an error message on failure. * @successResponse 204 OK & no content @@ -161,12 +224,22 @@ public Response update(@PathParam("id") long id, Project project) { @DELETE @Path("/{id}") @Produces(MediaType.APPLICATION_JSON) - public Response delete(@PathParam("id") long id) { + public Response delete(@PathParam("id") long projectId) { + User user = ((UserPrincipal) securityContext.getUserPrincipal()).getUser(); + LOGGER.trace("ProjectResource.delete(" + projectId + ") for user " + user + "."); + try { - projectDAO.delete(id); - return Response.status(Status.NO_CONTENT).build(); + Project project = projectDAO.getByID(user.getId(), projectId); + if (project.getUser().equals(user)) { + projectDAO.delete(user.getId(), projectId); + return Response.status(Status.NO_CONTENT).build(); + } else { + throw new UnauthorizedException("You are not allowed to delete this project"); + } } catch (NotFoundException e) { return ResourceErrorHandler.createRESTErrorMessage("ProjectResource.delete", Status.NOT_FOUND, e); + } catch (UnauthorizedException e) { + return ResourceErrorHandler.createRESTErrorMessage("ProjectResource.delete", Status.UNAUTHORIZED, e); } } @@ -184,5 +257,4 @@ private ProjectDAO.EmbeddableFields[] parseEmbeddableFields(String embed) throws return embedFields; } - } diff --git a/main/src/main/java/de/learnlib/alex/rest/SymbolGroupResource.java b/main/src/main/java/de/learnlib/alex/rest/SymbolGroupResource.java index 2c5c16596..e90388e6b 100644 --- a/main/src/main/java/de/learnlib/alex/rest/SymbolGroupResource.java +++ b/main/src/main/java/de/learnlib/alex/rest/SymbolGroupResource.java @@ -1,13 +1,37 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package de.learnlib.alex.rest; +import de.learnlib.alex.core.dao.ProjectDAO; import de.learnlib.alex.core.dao.SymbolDAO; import de.learnlib.alex.core.dao.SymbolGroupDAO; +import de.learnlib.alex.core.entities.Project; import de.learnlib.alex.core.entities.Symbol; import de.learnlib.alex.core.entities.SymbolGroup; +import de.learnlib.alex.core.entities.User; import de.learnlib.alex.exceptions.NotFoundException; +import de.learnlib.alex.security.UserPrincipal; import de.learnlib.alex.utils.ResourceErrorHandler; import de.learnlib.alex.utils.ResponseHelper; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.shiro.authz.UnauthorizedException; +import javax.annotation.security.RolesAllowed; import javax.inject.Inject; import javax.validation.ValidationException; import javax.ws.rs.Consumes; @@ -22,6 +46,7 @@ import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; +import javax.ws.rs.core.SecurityContext; import javax.ws.rs.core.UriInfo; import java.util.List; @@ -32,8 +57,12 @@ * @resourceDescription Operations for groups */ @Path("/projects/{project_id}/groups") +@RolesAllowed("REGISTERED") public class SymbolGroupResource { + /** Use the logger for the server part. */ + private static final Logger LOGGER = LogManager.getLogger("server"); + /** Context information about the URI. */ @Context private UriInfo uri; @@ -46,6 +75,14 @@ public class SymbolGroupResource { @Inject private SymbolDAO symbolDAO; + /** The ProjectDAO to use. */ + @Inject + private ProjectDAO projectDAO; + + /** The security context containing the user of the request. */ + @Context + private SecurityContext securityContext; + /** * Create a new group. * @@ -62,13 +99,22 @@ public class SymbolGroupResource { @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) public Response createGroup(@PathParam("project_id") long projectId, SymbolGroup group) { + User user = ((UserPrincipal) securityContext.getUserPrincipal()).getUser(); + LOGGER.trace("SymbolGroupResource.createGroup(" + projectId + ", " + group + ") for user " + user + "."); + try { + Project project = projectDAO.getByID(user.getId(), projectId); + if (!project.getUser().equals(user)) { + throw new ValidationException("You are not the owner of the project"); + } + group.setProjectId(projectId); + group.setUser(user); symbolGroupDAO.create(group); String groupURL = uri.getBaseUri() + "projects/" + group.getProjectId() + "/groups/" + group.getId(); return Response.status(Response.Status.CREATED).header("Location", groupURL).entity(group).build(); - } catch (ValidationException e) { + } catch (ValidationException | NotFoundException e) { return ResourceErrorHandler.createRESTErrorMessage("SymbolGroupResource.create", Response.Status.BAD_REQUEST, e); } @@ -90,9 +136,12 @@ public Response createGroup(@PathParam("project_id") long projectId, SymbolGroup @GET @Produces(MediaType.APPLICATION_JSON) public Response getAll(@PathParam("project_id") long projectId, @QueryParam("embed") String embed) { + User user = ((UserPrincipal) securityContext.getUserPrincipal()).getUser(); + LOGGER.trace("SymbolGroupResource.getAll(" + projectId + ", " + embed + ") for user " + user + "."); + try { SymbolGroupDAO.EmbeddableFields[] embeddableFields = parseEmbeddableFields(embed); - List groups = symbolGroupDAO.getAll(projectId, embeddableFields); + List groups = symbolGroupDAO.getAll(user.getId(), projectId, embeddableFields); return ResponseHelper.renderList(groups, Response.Status.OK); } catch (IllegalArgumentException e) { return ResourceErrorHandler.createRESTErrorMessage("SymbolGroupResource.getAll", @@ -124,9 +173,12 @@ public Response getAll(@PathParam("project_id") long projectId, @QueryParam("emb public Response get(@PathParam("project_id") long projectId, @PathParam("id") Long id, @QueryParam("embed") String embed) { + User user = ((UserPrincipal) securityContext.getUserPrincipal()).getUser(); + LOGGER.trace("SymbolGroupResource.get(" + projectId + ", " + id + ", " + embed + ") for user " + user + "."); + try { SymbolGroupDAO.EmbeddableFields[] embeddableFields = parseEmbeddableFields(embed); - SymbolGroup group = symbolGroupDAO.get(projectId, id, embeddableFields); + SymbolGroup group = symbolGroupDAO.get(user, projectId, id, embeddableFields); return Response.ok(group).build(); } catch (IllegalArgumentException e) { return ResourceErrorHandler.createRESTErrorMessage("SymbolGroupResource.get", @@ -153,8 +205,11 @@ public Response get(@PathParam("project_id") long projectId, @Path("/{id}/symbols") @Produces(MediaType.APPLICATION_JSON) public Response getSymbols(@PathParam("project_id") long projectId, @PathParam("id") Long id) { + User user = ((UserPrincipal) securityContext.getUserPrincipal()).getUser(); + LOGGER.trace("SymbolGroupResource.getSymbols(" + projectId + ", " + id + ") for user " + user + "."); + try { - List symbols = symbolDAO.getAllWithLatestRevision(projectId, id); + List symbols = symbolDAO.getAllWithLatestRevision(user, projectId, id); return ResponseHelper.renderList(symbols, Response.Status.OK); } catch (NotFoundException e) { return ResourceErrorHandler.createRESTErrorMessage("SymbolGroupResource.getSymbols", @@ -182,15 +237,25 @@ public Response getSymbols(@PathParam("project_id") long projectId, @PathParam(" @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) public Response update(@PathParam("project_id") long projectId, @PathParam("id") Long id, SymbolGroup group) { + User user = ((UserPrincipal) securityContext.getUserPrincipal()).getUser(); + LOGGER.trace("SymbolGroupResource.update(" + projectId + ", " + id + ", " + group + ") for user " + user + "."); + try { - symbolGroupDAO.update(group); - return Response.ok(group).build(); + if (group.getUserId().equals(user.getId())) { + symbolGroupDAO.update(group); + return Response.ok(group).build(); + } else { + throw new UnauthorizedException("You are not allowed to edit this group"); + } } catch (NotFoundException e) { return ResourceErrorHandler.createRESTErrorMessage("SymbolGroupResource.update", Response.Status.NOT_FOUND, e); } catch (ValidationException e) { return ResourceErrorHandler.createRESTErrorMessage("SymbolGroupResource.update", Response.Status.BAD_REQUEST, e); + } catch (UnauthorizedException e) { + return ResourceErrorHandler.createRESTErrorMessage("SymbolGroupResource.update", + Response.Status.UNAUTHORIZED, e); } } @@ -209,15 +274,27 @@ public Response update(@PathParam("project_id") long projectId, @PathParam("id") @Path("/{id}") @Produces(MediaType.APPLICATION_JSON) public Response deleteAResultSet(@PathParam("project_id") long projectId, @PathParam("id") Long id) { + User user = ((UserPrincipal) securityContext.getUserPrincipal()).getUser(); + LOGGER.trace("SymbolGroupResource.deleteAResultSet(" + projectId + ", " + id + ") for user " + user + "."); + try { - symbolGroupDAO.delete(projectId, id); - return Response.noContent().build(); + SymbolGroup group = symbolGroupDAO.get(user, projectId, id); + + if (group.getUserId().equals(user.getId())) { + symbolGroupDAO.delete(user, projectId, id); + return Response.noContent().build(); + } else { + throw new UnauthorizedException("You are not allowed to delete the group"); + } } catch (IllegalArgumentException e) { return ResourceErrorHandler.createRESTErrorMessage("SymbolGroupResource.update", Response.Status.BAD_REQUEST, e); } catch (NotFoundException e) { return ResourceErrorHandler.createRESTErrorMessage("SymbolGroupResource.update", Response.Status.NOT_FOUND, e); + } catch (UnauthorizedException e) { + return ResourceErrorHandler.createRESTErrorMessage("SymbolGroupResource.update", + Response.Status.UNAUTHORIZED, e); } } diff --git a/main/src/main/java/de/learnlib/alex/rest/SymbolResource.java b/main/src/main/java/de/learnlib/alex/rest/SymbolResource.java index 6cf60f834..b50e963dc 100644 --- a/main/src/main/java/de/learnlib/alex/rest/SymbolResource.java +++ b/main/src/main/java/de/learnlib/alex/rest/SymbolResource.java @@ -1,16 +1,38 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package de.learnlib.alex.rest; +import de.learnlib.alex.core.dao.ProjectDAO; import de.learnlib.alex.core.dao.SymbolDAO; +import de.learnlib.alex.core.entities.Project; import de.learnlib.alex.core.entities.Symbol; import de.learnlib.alex.core.entities.SymbolVisibilityLevel; +import de.learnlib.alex.core.entities.User; import de.learnlib.alex.exceptions.NotFoundException; -import de.learnlib.alex.utils.IdsList; +import de.learnlib.alex.security.UserPrincipal; import de.learnlib.alex.utils.IdRevisionPairList; +import de.learnlib.alex.utils.IdsList; import de.learnlib.alex.utils.ResourceErrorHandler; import de.learnlib.alex.utils.ResponseHelper; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.apache.shiro.authz.UnauthorizedException; +import javax.annotation.security.RolesAllowed; import javax.inject.Inject; import javax.validation.ValidationException; import javax.ws.rs.Consumes; @@ -26,6 +48,7 @@ import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; +import javax.ws.rs.core.SecurityContext; import javax.ws.rs.core.UriInfo; import java.util.HashSet; import java.util.LinkedList; @@ -39,6 +62,7 @@ * @resourceDescription Operations about symbols */ @Path("/projects/{project_id}/symbols") +@RolesAllowed({"REGISTERED"}) public class SymbolResource { /** Use the logger for the server part. */ @@ -52,6 +76,14 @@ public class SymbolResource { @Inject private SymbolDAO symbolDAO; + /** The security context containing the user of the request. */ + @Context + private SecurityContext securityContext; + + /** The {@link ProjectDAO} to use. */ + @Inject + private ProjectDAO projectDAO; + /** * Create a new Symbol. * @@ -68,18 +100,31 @@ public class SymbolResource { @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) public Response createSymbol(@PathParam("project_id") Long projectId, Symbol symbol) { + User user = ((UserPrincipal) securityContext.getUserPrincipal()).getUser(); + LOGGER.trace("SymbolResource.createSymbol(" + projectId + ", " + symbol + ") for user " + user + "."); try { checkSymbolBeforeCreation(projectId, symbol); // can throw an IllegalArgumentException - symbolDAO.create(symbol); - String symbolURL = uri.getBaseUri() + "projects/" + symbol.getProjectId() + "/symbols/" + symbol.getId(); - return Response.status(Status.CREATED).header("Location", symbolURL).entity(symbol).build(); + Project project = projectDAO.getByID(user.getId(), projectId); + if (project.getUser().equals(user)) { + symbol.setUser(user); + symbolDAO.create(symbol); + String symbolURL = uri.getBaseUri() + "projects/" + symbol.getProjectId() + + "/symbols/" + symbol.getId(); + return Response.status(Status.CREATED).header("Location", symbolURL).entity(symbol).build(); + } else { + throw new UnauthorizedException("The user may not create a symbol in this project"); + } } catch (IllegalArgumentException e) { return ResourceErrorHandler.createRESTErrorMessage("SymbolResource.createSymbol", Status.BAD_REQUEST, e); } catch (ValidationException e) { return ResourceErrorHandler.createRESTErrorMessage("SymbolResource.createSymbol", Status.BAD_REQUEST, e); + } catch (NotFoundException e) { + return ResourceErrorHandler.createRESTErrorMessage("SymbolResource.createSymbol", Status.NOT_FOUND, e); + } catch (UnauthorizedException e) { + return ResourceErrorHandler.createRESTErrorMessage("SymbolResource.createSymbol", Status.UNAUTHORIZED, e); } } @@ -100,20 +145,32 @@ public Response createSymbol(@PathParam("project_id") Long projectId, Symbol sym @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) public Response batchCreateSymbols(@PathParam("project_id") Long projectId, List symbols) { + User user = ((UserPrincipal) securityContext.getUserPrincipal()).getUser(); + LOGGER.trace("SymbolResource.batchCreateSymbols(" + projectId + ", " + symbols + ") for user " + user + "."); + try { - for (Symbol symbol : symbols) { - checkSymbolBeforeCreation(projectId, symbol); // can throw an IllegalArgumentException + Project project = projectDAO.getByID(user.getId(), projectId); + if (project.getUser().equals(user)) { + for (Symbol symbol : symbols) { + checkSymbolBeforeCreation(projectId, symbol); // can throw an IllegalArgumentException + symbol.setUser(user); + } + symbolDAO.create(symbols); + + return ResponseHelper.renderList(symbols, Status.CREATED); + } else { + throw new UnauthorizedException("The user may not create the symbols in this project"); } - symbolDAO.create(symbols); - - return ResponseHelper.renderList(symbols, Status.CREATED); - } catch (IllegalArgumentException e) { return ResourceErrorHandler.createRESTErrorMessage("SymbolResource.batchCreateSymbols", Status.BAD_REQUEST, e); } catch (ValidationException e) { return ResourceErrorHandler.createRESTErrorMessage("SymbolResource.batchCreateSymbols", Status.BAD_REQUEST, e); + } catch (NotFoundException e) { + return ResourceErrorHandler.createRESTErrorMessage("SymbolResource.createSymbol", Status.NOT_FOUND, e); + } catch (UnauthorizedException e) { + return ResourceErrorHandler.createRESTErrorMessage("SymbolResource.createSymbol", Status.UNAUTHORIZED, e); } } @@ -143,9 +200,12 @@ private void checkSymbolBeforeCreation(Long projectId, Symbol symbol) { @Produces(MediaType.APPLICATION_JSON) public Response getAll(@PathParam("project_id") Long projectId, @QueryParam("visibility") @DefaultValue("VISIBLE") SymbolVisibilityLevel visibilityLevel) { + User user = ((UserPrincipal) securityContext.getUserPrincipal()).getUser(); + LOGGER.trace("SymbolResource.getAll(" + projectId + ", " + visibilityLevel + ") for user " + user + "."); + List symbols; try { - symbols = symbolDAO.getAllWithLatestRevision(projectId, visibilityLevel); + symbols = symbolDAO.getAllWithLatestRevision(user, projectId, visibilityLevel); } catch (NotFoundException e) { symbols = new LinkedList<>(); } @@ -154,7 +214,7 @@ public Response getAll(@PathParam("project_id") Long projectId, } /** - * Get Symbols by a list of id/revision pairs + * Get Symbols by a list of id/revision pairs. * * @param projectId * The ID of the project @@ -171,8 +231,12 @@ public Response getAll(@PathParam("project_id") Long projectId, @Produces(MediaType.APPLICATION_JSON) public Response getByIdRevisionPairs(@PathParam("project_id") Long projectId, @PathParam("idRevisionPairs") IdRevisionPairList idRevisionPairs) { + User user = ((UserPrincipal) securityContext.getUserPrincipal()).getUser(); + LOGGER.trace("SymbolResource.getByIdRevisionPairs(" + projectId + ", " + idRevisionPairs + ") " + + "for user " + user + "."); + try { - List symbols = symbolDAO.getAll(projectId, idRevisionPairs); + List symbols = symbolDAO.getAll(user, projectId, idRevisionPairs); return ResponseHelper.renderList(symbols, Status.OK); } catch (NotFoundException e) { return ResourceErrorHandler.createRESTErrorMessage("SymbolResource.getByIdRevisionPairs", @@ -199,8 +263,11 @@ public Response getByIdRevisionPairs(@PathParam("project_id") Long projectId, @Path("/{id}") @Produces(MediaType.APPLICATION_JSON) public Response get(@PathParam("project_id") Long projectId, @PathParam("id") Long id) { + User user = ((UserPrincipal) securityContext.getUserPrincipal()).getUser(); + LOGGER.trace("SymbolResource.get(" + projectId + ", " + id + ") for user " + user + "."); + try { - Symbol symbol = symbolDAO.getWithLatestRevision(projectId, id); + Symbol symbol = symbolDAO.getWithLatestRevision(user, projectId, id); return Response.ok(symbol).build(); } catch (NotFoundException e) { return ResourceErrorHandler.createRESTErrorMessage("SymbolResource.get", Status.NOT_FOUND, null); @@ -224,8 +291,11 @@ public Response get(@PathParam("project_id") Long projectId, @PathParam("id") Lo @Path("/{id}/complete") @Produces(MediaType.APPLICATION_JSON) public Response getComplete(@PathParam("project_id") Long projectId, @PathParam("id") Long id) { + User user = ((UserPrincipal) securityContext.getUserPrincipal()).getUser(); + LOGGER.trace("SymbolResource.getComplete(" + projectId + ", " + id + ") for user " + user + "."); + try { - List symbols = symbolDAO.getWithAllRevisions(projectId, id); + List symbols = symbolDAO.getWithAllRevisions(user, projectId, id); return ResponseHelper.renderList(symbols, Status.OK); } catch (NotFoundException e) { return ResourceErrorHandler.createRESTErrorMessage("SymbolResource.getComplete", Status.NOT_FOUND, null); @@ -249,10 +319,15 @@ public Response getComplete(@PathParam("project_id") Long projectId, @PathParam( @GET @Path("/{id}:{revision}") @Produces(MediaType.APPLICATION_JSON) - public Response getWithRevision(@PathParam("project_id") Long projectId, @PathParam("id") Long id, - @PathParam("revision") long revision) { + public Response getWithRevision(@PathParam("project_id") Long projectId, + @PathParam("id") Long id, + @PathParam("revision") long revision) { + User user = ((UserPrincipal) securityContext.getUserPrincipal()).getUser(); + LOGGER.trace("SymbolResource.getWithRevision(" + projectId + ", " + id + ", " + revision + ") " + + "for user " + user + "."); + try { - Symbol symbol = symbolDAO.get(projectId, id, revision); + Symbol symbol = symbolDAO.get(user, projectId, id, revision); return Response.ok(symbol).build(); } catch (NotFoundException e) { return ResourceErrorHandler.createRESTErrorMessage("SymbolResource.getWithRevision", @@ -281,9 +356,15 @@ public Response getWithRevision(@PathParam("project_id") Long projectId, @PathPa @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) public Response update(@PathParam("project_id") Long projectId, @PathParam("id") Long id, Symbol symbol) { - if (!Objects.equals(id, symbol.getId()) || !Objects.equals(projectId, symbol.getProjectId())) { + User user = ((UserPrincipal) securityContext.getUserPrincipal()).getUser(); + LOGGER.trace("SymbolResource.update(" + projectId + ", " + id + ", " + symbol + ") for user " + user + "."); + + if (!Objects.equals(id, symbol.getId()) + || !Objects.equals(projectId, symbol.getProjectId()) + || (symbol.getUserId() != 0L && !user.equals(symbol.getUser()))) { return Response.status(Status.BAD_REQUEST).build(); } + symbol.setUser(user); try { symbolDAO.update(symbol); @@ -317,9 +398,15 @@ public Response update(@PathParam("project_id") Long projectId, @PathParam("id") public Response batchUpdate(@PathParam("project_id") Long projectId, @PathParam("ids") IdsList ids, List symbols) { + User user = ((UserPrincipal) securityContext.getUserPrincipal()).getUser(); + LOGGER.trace("SymbolResource.batchUpdate(" + projectId + ", " + ids + ", " + symbols + ") " + + "for user " + user + "."); + Set idsSet = new HashSet<>(ids); for (Symbol symbol : symbols) { - if (!idsSet.contains(symbol.getId()) || !Objects.equals(projectId, symbol.getProjectId())) { + if (!idsSet.contains(symbol.getId()) + || !Objects.equals(projectId, symbol.getProjectId()) + || !symbol.getUserId().equals(user.getId())) { return Response.status(Status.BAD_REQUEST).build(); } } @@ -350,8 +437,12 @@ public Response batchUpdate(@PathParam("project_id") Long projectId, public Response moveSymbolToAnotherGroup(@PathParam("project_id") Long projectId, @PathParam("symbol_id") Long symbolId, @PathParam("group_id") Long groupId) { + User user = ((UserPrincipal) securityContext.getUserPrincipal()).getUser(); + LOGGER.trace("SymbolResource.moveSymbolToAnotherGroup(" + projectId + ", " + symbolId + ", " + groupId + ") " + + "for user " + user + "."); + try { - Symbol symbol = symbolDAO.getWithLatestRevision(projectId, symbolId); + Symbol symbol = symbolDAO.getWithLatestRevision(user, projectId, symbolId); symbolDAO.move(symbol, groupId); return Response.ok(symbol).build(); } catch (NotFoundException e) { @@ -377,8 +468,12 @@ public Response moveSymbolToAnotherGroup(@PathParam("project_id") Long projectId public Response moveSymbolToAnotherGroup(@PathParam("project_id") Long projectId, @PathParam("symbol_ids") IdsList symbolIds, @PathParam("group_id") Long groupId) { + User user = ((UserPrincipal) securityContext.getUserPrincipal()).getUser(); + LOGGER.trace("SymbolResource.moveSymbolToAnotherGroup(" + projectId + ", " + symbolIds + ", " + groupId + ") " + + "for user " + user + "."); + try { - List symbols = symbolDAO.getByIdsWithLatestRevision(projectId, + List symbols = symbolDAO.getByIdsWithLatestRevision(user, projectId, symbolIds.toArray(new Long[symbolIds.size()])); symbolDAO.move(symbols, groupId); @@ -404,13 +499,22 @@ public Response moveSymbolToAnotherGroup(@PathParam("project_id") Long projectId @Path("/{id}/hide") @Produces(MediaType.APPLICATION_JSON) public Response hide(@PathParam("project_id") Long projectId, @PathParam("id") Long id) { - try { - symbolDAO.hide(projectId, id); - Symbol symbol = symbolDAO.getWithLatestRevision(projectId, id); - return Response.ok(symbol).build(); + User user = ((UserPrincipal) securityContext.getUserPrincipal()).getUser(); + LOGGER.trace("SymbolResource.hide(" + projectId + ", " + id + ") for user " + user + "."); + try { + Symbol s = symbolDAO.getWithLatestRevision(user, projectId, id); + if (s.getUser().equals(user)) { + symbolDAO.hide(user.getId(), projectId, id); + Symbol symbol = symbolDAO.getWithLatestRevision(user, projectId, id); + return Response.ok(symbol).build(); + } else { + throw new UnauthorizedException("The symbol does not belong to the user"); + } } catch (NotFoundException e) { return ResourceErrorHandler.createRESTErrorMessage("SymbolResource.hide", Status.NOT_FOUND, e); + } catch (UnauthorizedException e) { + return ResourceErrorHandler.createRESTErrorMessage("SymbolResource.hide", Status.UNAUTHORIZED, e); } } @@ -429,11 +533,13 @@ public Response hide(@PathParam("project_id") Long projectId, @PathParam("id") L @Path("/batch/{ids}/hide") @Produces(MediaType.APPLICATION_JSON) public Response hide(@PathParam("project_id") long projectId, @PathParam("ids") IdsList ids) { + User user = ((UserPrincipal) securityContext.getUserPrincipal()).getUser(); + LOGGER.trace("SymbolResource.hide(" + projectId + ", " + ids + ") for user " + user + "."); + try { Long[] idsArray = ids.toArray(new Long[ids.size()]); - symbolDAO.hide(projectId, idsArray); - List symbols = symbolDAO.getByIdsWithLatestRevision(projectId, idsArray); - + List symbols = symbolDAO.getByIdsWithLatestRevision(user, projectId, idsArray); + symbolDAO.hide(user.getId(), projectId, idsArray); return ResponseHelper.renderList(symbols, Status.OK); } catch (NotFoundException e) { return ResourceErrorHandler.createRESTErrorMessage("SymbolResource.hide", Status.NOT_FOUND, e); @@ -455,9 +561,12 @@ public Response hide(@PathParam("project_id") long projectId, @PathParam("ids") @Path("/{id}/show") @Produces(MediaType.APPLICATION_JSON) public Response show(@PathParam("project_id") long projectId, @PathParam("id") Long id) { + User user = ((UserPrincipal) securityContext.getUserPrincipal()).getUser(); + LOGGER.trace("SymbolResource.show(" + projectId + ", " + id + ") for user " + user + "."); + try { - symbolDAO.show(projectId, id); - Symbol symbol = symbolDAO.getWithLatestRevision(projectId, id); + symbolDAO.show(user.getId(), projectId, id); + Symbol symbol = symbolDAO.getWithLatestRevision(user, projectId, id); return Response.ok(symbol).build(); } catch (NotFoundException e) { return ResourceErrorHandler.createRESTErrorMessage("SymbolResource.show", Status.NOT_FOUND, e); @@ -479,10 +588,13 @@ public Response show(@PathParam("project_id") long projectId, @PathParam("id") L @Path("/batch/{ids}/show") @Produces(MediaType.APPLICATION_JSON) public Response show(@PathParam("project_id") long projectId, @PathParam("ids") IdsList ids) { + User user = ((UserPrincipal) securityContext.getUserPrincipal()).getUser(); + LOGGER.trace("SymbolResource.show(" + projectId + ", " + ids + ") for user " + user + "."); + try { Long[] idsArray = ids.toArray(new Long[ids.size()]); - symbolDAO.show(projectId, idsArray); - List symbols = symbolDAO.getByIdsWithLatestRevision(projectId, idsArray); + symbolDAO.show(user.getId(), projectId, idsArray); + List symbols = symbolDAO.getByIdsWithLatestRevision(user, projectId, idsArray); return ResponseHelper.renderList(symbols, Status.OK); } catch (NotFoundException e) { diff --git a/main/src/main/java/de/learnlib/alex/rest/UserResource.java b/main/src/main/java/de/learnlib/alex/rest/UserResource.java new file mode 100644 index 000000000..5f4212608 --- /dev/null +++ b/main/src/main/java/de/learnlib/alex/rest/UserResource.java @@ -0,0 +1,409 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package de.learnlib.alex.rest; + +import de.learnlib.alex.core.dao.UserDAO; +import de.learnlib.alex.core.entities.User; +import de.learnlib.alex.core.entities.UserRole; +import de.learnlib.alex.exceptions.NotFoundException; +import de.learnlib.alex.security.JWTHelper; +import de.learnlib.alex.security.UserPrincipal; +import de.learnlib.alex.utils.ResourceErrorHandler; +import de.learnlib.alex.utils.ResponseHelper; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.shiro.authz.UnauthorizedException; +import org.hibernate.validator.internal.constraintvalidators.hv.EmailValidator; +import org.jose4j.json.internal.json_simple.JSONObject; +import org.jose4j.lang.JoseException; + +import javax.annotation.security.RolesAllowed; +import javax.inject.Inject; +import javax.ws.rs.BadRequestException; +import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.Response.Status; +import javax.ws.rs.core.SecurityContext; +import javax.xml.bind.ValidationException; +import java.util.List; + +/** + * REST resource to handle users. + * @resourcePath users + * @resourceDescription Operations around users. + */ +@Path("/users") +public class UserResource { + + /** Use the logger for the server part. */ + private static final Logger LOGGER = LogManager.getLogger("server"); + + /** The UserDAO to user. */ + @Inject + private UserDAO userDAO; + + /** The security context containing the user of the request. */ + @Context + private SecurityContext securityContext; + + /** + * Creates a new user. + * + * @param user + * The user to create + * @return The created user (enhanced with information form the DB); an error message on failure. + * @responseType de.learnlib.alex.core.entities.User + * @successResponse 201 created + * @errorResponse 400 bad request `de.learnlib.alex.utils.ResourceErrorHandler.RESTError + */ + @POST + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public Response create(User user) { + LOGGER.trace("UserResource.create(" + user + ")."); + try { + // validate email address + if (!new EmailValidator().isValid(user.getEmail(), null)) { + throw new ValidationException("The email is not valid"); + } + + user.setEncryptedPassword(user.getPassword()); + + // create user + userDAO.create(user); + return Response.status(Status.CREATED).entity(user).build(); + } catch (ValidationException e) { + return ResourceErrorHandler.createRESTErrorMessage("UserResource.create", Status.BAD_REQUEST, e); + } + } + + /** + * Get the account information about one user. + * This only works for your own account or if you are an administrator. + * + * @param userId + * The ID of the user. + * @return Detailed information about the user. + * @responseType de.learnlib.alex.core.entities.User + * @successResponse 200 Ok + * @errorResponse 400 bad request `de.learnlib.alex.utils.ResourceErrorHandler.RESTError + * @errorResponse 403 forbidden `de.learnlib.alex.utils.ResourceErrorHandler.RESTError + */ + @GET + @Path("/{id}") + @Produces(MediaType.APPLICATION_JSON) + @RolesAllowed({"REGISTERED"}) + public Response get(@PathParam("id") Long userId) { + User user = ((UserPrincipal) securityContext.getUserPrincipal()).getUser(); + LOGGER.trace("UserResource.get(" + userId + ") for user " + user + "."); + + // only the user itself or an admin should be allow to view this + if (!user.getRole().equals(UserRole.ADMIN) && !user.getId().equals(userId)) { + return ResourceErrorHandler.createRESTErrorMessage("UserResource.get", Status.FORBIDDEN, null); + } + + try { + return Response.ok(userDAO.getById(userId)).build(); + } catch (NotFoundException e) { + return ResourceErrorHandler.createRESTErrorMessage("UserResource.get", Status.NOT_FOUND, e); + } + } + + /** + * Get all users. + * This is only allowed for admins. + * + * @return A list of all users. + * @responseType java.util.List + * @successResponse 200 Ok + */ + @GET + @Produces(MediaType.APPLICATION_JSON) + @RolesAllowed({"ADMIN"}) + public Response getAll() { + List users = userDAO.getAll(); + return ResponseHelper.renderList(users, Status.OK); + } + + /** + * Changes the password of the user. + * + * @param userId + * The id of the user + * @param json + * The pair of oldPasswordn and newPassword as json + * @return The updated user. + * + * @responseType de.learnlib.alex.core.entities.User + * @successResponse 200 Ok + * @errorResponse 400 bad request `de.learnlib.alex.utils.ResourceErrorHandler.RESTError + * @errorResponse 403 forbidden `de.learnlib.alex.utils.ResourceErrorHandler.RESTError + * @errorResponse 404 not found `de.learnlib.alex.utils.ResourceErrorHandler.RESTError + */ + @PUT + @Path("/{id}/password") + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + @RolesAllowed({"REGISTERED"}) + public Response changePassword(@PathParam("id") Long userId, JSONObject json) { + User user = ((UserPrincipal) securityContext.getUserPrincipal()).getUser(); + LOGGER.trace("UserResource.changePassword(" + userId + ", " + json + ") for user " + user + "."); + + // only the user is allowed to change his own password + if (!user.getId().equals(userId)) { + return ResourceErrorHandler.createRESTErrorMessage("UserResource.changePassword", Status.FORBIDDEN, null); + } + + String oldPassword = (String) json.get("oldPassword"); + String newPassword = (String) json.get("newPassword"); + + try { + User realUser = userDAO.getById(userId); + + // make sure that the password is valid + if (!realUser.isValidPassword(oldPassword)) { + throw new IllegalArgumentException("Please provide your old password!"); + } + + realUser.setEncryptedPassword(newPassword); + userDAO.update(realUser); + return Response.ok(user).build(); + } catch (IllegalArgumentException e) { + return ResourceErrorHandler.createRESTErrorMessage("UserResource.changePassword", Status.FORBIDDEN, e); + } catch (NotFoundException e) { + // This should never happen! + return ResourceErrorHandler.createRESTErrorMessage("UserResource.changePassword", Status.NOT_FOUND, e); + } + } + + /** + * Changes the email of the user. + * This can only be invoked for your own account or if you are an administrator. + * Please also note: Your new email must not be your current one and no other user should already have this email. + * + * @param userId + * The id of the user + * @param json + * the json with a property 'email' + * @return The updated user. + * + * @responseType de.learnlib.alex.core.entities.User + * @successResponse 200 Ok + * @errorResponse 400 bad request `de.learnlib.alex.utils.ResourceErrorHandler.RESTError + * @errorResponse 403 forbidden `de.learnlib.alex.utils.ResourceErrorHandler.RESTError + * @errorResponse 404 not found `de.learnlib.alex.utils.ResourceErrorHandler.RESTError + */ + @PUT + @Path("/{id}/email") + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + @RolesAllowed({"REGISTERED"}) + public Response changeEmail(@PathParam("id") Long userId, JSONObject json) { + User user = ((UserPrincipal) securityContext.getUserPrincipal()).getUser(); + LOGGER.trace("UserResource.changeEmail(" + userId + ", " + json + ") for user " + user + "."); + + // only the user or an admin is allowed to change the email + if (!user.getId().equals(userId) && !user.getRole().equals(UserRole.ADMIN)) { + return ResourceErrorHandler.createRESTErrorMessage("UserResource.changePassword", Status.FORBIDDEN, null); + } + + String email = (String) json.get("email"); + try { + User realUser = userDAO.getById(userId); + + if (!new EmailValidator().isValid(email, null)) { + throw new ValidationException("The email is not valid!"); + } + if (email.equals(user.getEmail())) { + throw new ValidationException("The email is the same as the current one!"); + } + + // check if the new email is already taken + try { + userDAO.getByEmail(email); + throw new ValidationException("The email is already taken!"); + } catch (NotFoundException e) { + // email is free, let's move on + } + + realUser.setEmail(email); + userDAO.update(realUser); + + return Response.ok(realUser).build(); + } catch (ValidationException e) { + return ResourceErrorHandler.createRESTErrorMessage("UserResource.changeEmail", Status.BAD_REQUEST, e); + } catch (NotFoundException e) { + return ResourceErrorHandler.createRESTErrorMessage("UserResource.changeEmail", Status.NOT_FOUND, e); + } + } + + /** + * Promotes an user to be an administrator. + * This can only be done by administrators. + * + * @param userId + * The ID of the user to promote. + * @return The account information of the new administrator. + * + * @responseType de.learnlib.alex.core.entities.User + * @successResponse 200 Ok + * @errorResponse 404 not found `de.learnlib.alex.utils.ResourceErrorHandler.RESTError + */ + @PUT + @Path("/{id}/promote") + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + @RolesAllowed({"ADMIN"}) + public Response promoteUser(@PathParam("id") Long userId) { + User user = ((UserPrincipal) securityContext.getUserPrincipal()).getUser(); + LOGGER.trace("UserResource.promoteUser(" + userId + ") for user " + user + "."); + + try { + User userToPromote = userDAO.getById(userId); + userToPromote.setRole(UserRole.ADMIN); + userDAO.update(userToPromote); + System.out.println("user promoted"); + return Response.ok(userToPromote).build(); + } catch (NotFoundException e) { + return ResourceErrorHandler.createRESTErrorMessage("UserResource.promoteUser", Status.NOT_FOUND, e); + } + } + + /** + * Demotes an user to be an simple use (and no administrator anymore). + * This can only be done by administrators. + * Please also note: At least one administrator has to remain in the system. + * + * @param userId + * The ID of the user to demote. + * @return The account information of the formally administrator. + * @responseType de.learnlib.alex.core.entities.User + * @successResponse 200 Ok + * @errorResponse 400 bad request `de.learnlib.alex.utils.ResourceErrorHandler.RESTError + * @errorResponse 404 not found `de.learnlib.alex.utils.ResourceErrorHandler.RESTError + */ + @PUT + @Path("/{id}/demote") + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + @RolesAllowed({"ADMIN"}) + public Response demoteUser(@PathParam("id") Long userId) { + User user = ((UserPrincipal) securityContext.getUserPrincipal()).getUser(); + LOGGER.trace("UserResource.demoteUser(" + userId + ") for user " + user + "."); + + try { + // if the admin wants to revoke his own rights + // -> take care that always one admin is in the system + if (user.getId().equals(userId)) { + List admins = userDAO.getAllByRole(UserRole.ADMIN); + if (admins.size() == 1) { + throw new BadRequestException("The only admin left cannot take away his own admin rights!"); + } + } + + User userToDemote = userDAO.getById(userId); + userToDemote.setRole(UserRole.REGISTERED); + userDAO.update(userToDemote); + return Response.ok(userToDemote).build(); + } catch (BadRequestException e) { + return ResourceErrorHandler.createRESTErrorMessage("UserResource.demoteUser", Status.BAD_REQUEST, e); + } catch (NotFoundException e) { + return ResourceErrorHandler.createRESTErrorMessage("UserResource.demoteUser", Status.NOT_FOUND, e); + } + } + + /** + * Delete an user. + * This is only allowed for your own account or if you are an administrator. + * + * @param userId + * The ID of the user to delete. + * @return Nothing if the user was deleted. + * + * @successResponse 204 No Contetnt + * @errorResponse 400 bad request `de.learnlib.alex.utils.ResourceErrorHandler.RESTError + * @errorResponse 404 not found `de.learnlib.alex.utils.ResourceErrorHandler.RESTError + */ + @DELETE + @Path("/{id}") + @Produces(MediaType.APPLICATION_JSON) + @RolesAllowed({"REGISTERED"}) + public Response delete(@PathParam("id") long userId) { + User user = ((UserPrincipal) securityContext.getUserPrincipal()).getUser(); + LOGGER.trace("UserResource.delete(" + userId + ") for user " + user + "."); + + if (!user.getId().equals(userId) && !user.getRole().equals(UserRole.ADMIN)) { + UnauthorizedException e = new UnauthorizedException("You are not allowed to delete this user"); + return ResourceErrorHandler.createRESTErrorMessage("UserResource.delete", Status.FORBIDDEN, e); + } + + try { + userDAO.delete(userId); + return Response.status(Status.NO_CONTENT).build(); + } catch (NotFoundException e) { + return ResourceErrorHandler.createRESTErrorMessage("UserResource.delete", Status.NOT_FOUND, e); + } + } + + /** + * Logs in a user by generating a unique JWT for him that needs to be send in every request. + * + * @param user + * The user to login + * @return If the user was successfully logged in: a JSON Object with the authentication token as only field; + * An error otherwise. + * + * @successResponse 200 Ok + * @errorResponse 401 unauthorized `de.learnlib.alex.utils.ResourceErrorHandler.RESTError + * @errorResponse 404 not found `de.learnlib.alex.utils.ResourceErrorHandler.RESTError + */ + @POST + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + @Path("/login") + public Response login(User user) { + LOGGER.trace("UserResource.login(" + user + ")."); + + try { + User realUser = userDAO.getByEmail(user.getEmail()); + + // make sure that the password is valid + if (!realUser.isValidPassword(user.getPassword())) { + throw new IllegalArgumentException("Please provide your correct password!"); + } + + String json = "{\"token\": \"" + JWTHelper.generateJWT(realUser) + "\"}"; + return Response.ok(json).build(); + } catch (IllegalArgumentException e) { + return ResourceErrorHandler.createRESTErrorMessage("UserResource.delete", Status.UNAUTHORIZED, e); + } catch (NotFoundException e) { + return ResourceErrorHandler.createRESTErrorMessage("UserResource.delete", Status.NOT_FOUND, e); + } catch (JoseException e) { + return ResourceErrorHandler.createRESTErrorMessage("UserResource.delete", Status.INTERNAL_SERVER_ERROR, e); + } + } + +} diff --git a/main/src/main/java/de/learnlib/alex/rest/package-info.java b/main/src/main/java/de/learnlib/alex/rest/package-info.java index 5932c7d16..146787c54 100644 --- a/main/src/main/java/de/learnlib/alex/rest/package-info.java +++ b/main/src/main/java/de/learnlib/alex/rest/package-info.java @@ -1,3 +1,19 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + /** * This package contains the public REST API of ALEX. */ diff --git a/main/src/main/java/de/learnlib/alex/security/AuthenticationFilter.java b/main/src/main/java/de/learnlib/alex/security/AuthenticationFilter.java new file mode 100644 index 000000000..ffc3c53f9 --- /dev/null +++ b/main/src/main/java/de/learnlib/alex/security/AuthenticationFilter.java @@ -0,0 +1,151 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package de.learnlib.alex.security; + +import de.learnlib.alex.core.dao.UserDAO; +import de.learnlib.alex.core.entities.User; +import de.learnlib.alex.core.entities.UserRole; +import de.learnlib.alex.exceptions.NotFoundException; +import org.jose4j.jwt.JwtClaims; +import org.jose4j.jwt.consumer.InvalidJwtException; +import org.jose4j.jwt.consumer.JwtConsumer; +import org.jose4j.jwt.consumer.JwtConsumerBuilder; + +import javax.annotation.Priority; +import javax.inject.Inject; +import javax.ws.rs.Priorities; +import javax.ws.rs.container.ContainerRequestContext; +import javax.ws.rs.container.ContainerRequestFilter; +import javax.ws.rs.core.SecurityContext; +import javax.ws.rs.ext.Provider; +import java.io.IOException; +import java.security.Principal; +import java.util.regex.Pattern; + +/** + * Custom Request filter that is executed for each incoming request. + */ +@Provider +@Priority(Priorities.AUTHENTICATION) +public class AuthenticationFilter implements ContainerRequestFilter { + + /** The RegExp to describe a proper formatted 'Authorization' header field. */ + private static final Pattern PATTERN = Pattern.compile("bearer [a-z0-9-_]+\\.[a-z0-9-_]+\\.[a-z0-9-_]+", + Pattern.CASE_INSENSITIVE); + + /** The UserDAO to use. */ + @Inject + private UserDAO userDAO; + + /** + * checks for the availability of a JWT and puts the corresponding user into a SecurityContext that can be injected + * into Resources. Uses dummy user without role if no JWT is available. + * + * @param requestContext The context of the request + * @throws IOException + */ + @Override + public void filter(ContainerRequestContext requestContext) throws IOException { + try { + User user; + + // get the jwt from Authorization Header and split at 'Bearer [token]' + String jwt = requestContext.getHeaderString("Authorization"); + if (jwt != null && PATTERN.matcher(jwt).matches()) { + jwt = jwt.split(" ")[1]; + + // check if the jwt is valid + JwtConsumer jwtConsumer = new JwtConsumerBuilder() + .setExpectedIssuer("ALEX") + .setVerificationKey(JWTHelper.getKey().getPublicKey()) + .build(); + + // get payload and get user id + // if no exception was throws up to here you can be sure that the jwt has not been modified + // and that the user that send the jwt is the one he seems to be + JwtClaims claims = jwtConsumer.processToClaims(jwt); + Long id = (Long) claims.getClaimsMap().get("userId"); + + // get user from the db + try { + user = userDAO.getById(id); + } catch (NotFoundException e) { + user = null; + } + } else { + + // create dummy guest user + user = new User(); + user.setRole(null); + } + + // create injectable security context with user here + requestContext.setSecurityContext(new AuthContext(user)); + + } catch (InvalidJwtException e) { + e.printStackTrace(); + } + } + + /** + * Custom Security context that allows to save a user instance in the context. + */ + private static class AuthContext implements SecurityContext { + + /** The authenticated user or a new dummy one. */ + private User user; + + /** + * @param user The user that should be available in the context + */ + AuthContext(User user) { + this.user = user; + } + + /** + * @return A Principal Object that contains the user + */ + @Override + public Principal getUserPrincipal() { + return new UserPrincipal(user); + } + + /** + * Checks for the role of the user. + * Allow an admin to do everything a registered one can also do + * + * @param role + * - The role to check + * @return true, if the user is in the role; false otherwise. + */ + @Override + public boolean isUserInRole(String role) { + return UserRole.valueOf(role) == user.getRole() || user.getRole() == UserRole.ADMIN; + } + + // set this to true when ssl enabled + @Override + public boolean isSecure() { + return false; + } + + @Override + public String getAuthenticationScheme() { + return null; + } + } +} diff --git a/main/src/main/java/de/learnlib/alex/security/JWTHelper.java b/main/src/main/java/de/learnlib/alex/security/JWTHelper.java new file mode 100644 index 000000000..9ec222579 --- /dev/null +++ b/main/src/main/java/de/learnlib/alex/security/JWTHelper.java @@ -0,0 +1,95 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package de.learnlib.alex.security; + +import de.learnlib.alex.core.entities.User; +import org.jose4j.jwk.RsaJsonWebKey; +import org.jose4j.jwk.RsaJwkGenerator; +import org.jose4j.jws.AlgorithmIdentifiers; +import org.jose4j.jws.JsonWebSignature; +import org.jose4j.jwt.JwtClaims; +import org.jose4j.lang.JoseException; + +/** + * Helper class around the JWTs, including the security aspect. + */ +public final class JWTHelper { + + /** The number of bits used by the JWK lib. */ + public static final int JWK_STRENGTH_IN_BITS = 2048; + + /** + * The RSA public/private key pair. + */ + private static RsaJsonWebKey rsaJsonWebKey; + + + /** + * Default constructor disabled, because this is just a helper class. + */ + private JWTHelper() { + } + + /** + * Get the RSA key pair. + * The method will create a pair, if non exists, i.e. the first call of this method. + * If no pair can be created, it will shutdown the VM because this would mean a hugh security risk. + * + * @return The RSA key pair + */ + public static RsaJsonWebKey getKey() { + if (rsaJsonWebKey == null) { + try { + rsaJsonWebKey = RsaJwkGenerator.generateJwk(JWK_STRENGTH_IN_BITS); + } catch (JoseException e) { + e.printStackTrace(); + System.exit(0); + } + } + + return rsaJsonWebKey; + } + + /** + * Generates a JWT as String representation. + * Encodes the id and the role of the user as "userId" and "userRole" in the claims of the jwt + * + * @param user + * The user to generate the JWT from. + * @return The string representation of the jwt. + * @throws JoseException + * If the Jose library failed to create a JWT token. + */ + public static String generateJWT(User user) throws JoseException { + // generate claims with user data + JwtClaims claims = new JwtClaims(); + claims.setIssuer("ALEX"); + claims.setGeneratedJwtId(); + claims.setClaim("userId", user.getId()); + claims.setClaim("userRole", user.getRole()); + + // create signature + JsonWebSignature jws = new JsonWebSignature(); + jws.setPayload(claims.toJson()); + jws.setKey(getKey().getPrivateKey()); + jws.setAlgorithmHeaderValue(AlgorithmIdentifiers.RSA_USING_SHA256); + + // return signed jwt + return jws.getCompactSerialization(); + } + +} diff --git a/main/src/main/java/de/learnlib/alex/security/UserPrincipal.java b/main/src/main/java/de/learnlib/alex/security/UserPrincipal.java new file mode 100644 index 000000000..4c6833a6e --- /dev/null +++ b/main/src/main/java/de/learnlib/alex/security/UserPrincipal.java @@ -0,0 +1,54 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package de.learnlib.alex.security; + +import de.learnlib.alex.core.entities.User; + +import java.security.Principal; + +/** + * The security principal class that allows access to a user object from a security context. + */ +public class UserPrincipal implements Principal { + + /** + * The user of the context. + */ + private User user; + + /** + * @param user The user that should be used for the context + */ + public UserPrincipal(User user) { + this.user = user; + } + + /** + * @return The email the user is identified by + */ + @Override + public String getName() { + return user.getEmail(); + } + + /** + * @return The user of the context + */ + public User getUser() { + return user; + } +} diff --git a/main/src/main/java/de/learnlib/alex/security/package-info.java b/main/src/main/java/de/learnlib/alex/security/package-info.java new file mode 100644 index 000000000..1d72b2c42 --- /dev/null +++ b/main/src/main/java/de/learnlib/alex/security/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * This package contains all the stuff needed to make ALEX secure. + */ +package de.learnlib.alex.security; diff --git a/main/src/main/java/de/learnlib/alex/utils/CSSUtils.java b/main/src/main/java/de/learnlib/alex/utils/CSSUtils.java index f49f9a1b6..de25d85fe 100644 --- a/main/src/main/java/de/learnlib/alex/utils/CSSUtils.java +++ b/main/src/main/java/de/learnlib/alex/utils/CSSUtils.java @@ -1,12 +1,40 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package de.learnlib.alex.utils; /** - * Utility class for CSS stuff + * Utility class for CSS stuff. */ -public class CSSUtils { +public final class CSSUtils { + + /** ASCII Code for the unit separator character, which is not really printable. */ + public static final int UNIT_SEPARATOR_CHARACTER = 31; + + /** ASCII Code for the delete character, which is not really printable. */ + public static final int DELETE_CHARACTER = 127; + + /** + * Deactivate the constructor because this is a utility class. + */ + private CSSUtils() { + } /** - * Escape special characters in css id selectors + * Escape special characters in css id selectors. * * Port of https://github.com/mathiasbynens/CSS.escape/blob/master/css.escape.js * @@ -14,46 +42,45 @@ public class CSSUtils { * @return The escaped id */ private static String escapeIdentifier(String id) { - String result = ""; - Integer firstUnit = (int) id.charAt(0); + StringBuilder result = new StringBuilder(); + char firstUnit = id.charAt(0); - for (Integer i = 0; i < id.length(); i++) { - char c = id.charAt(i); - Integer unit = (int) c; + for (int i = 0; i < id.length(); i++) { + char unit = id.charAt(i); - if (unit.equals(0)) { + if (unit == 0) { // null character return null; } if ( - (unit >= 1 && unit <= 31) || unit.equals(127) || - (i.equals(0) && unit >= 48 && unit <= 57) || - (i.equals(1) && unit >= 48 && unit <= 57 && firstUnit.equals(45)) + unit <= UNIT_SEPARATOR_CHARACTER || unit == DELETE_CHARACTER // the non printable ASCII characters + || (i == 0 && unit >= '0' && unit <= '9') + || (i == 1 && unit >= '0' && unit <= '9' && firstUnit == '-') ) { - result += "\\" + Integer.toHexString(unit) + " "; + result.append("\\").append(Integer.toHexString(unit)).append(" "); continue; } if ( - unit >= 128 || - unit.equals(45) || - unit.equals(95) || - unit >= 48 && unit <= 57 || - unit >= 65 && unit <= 90 || - unit >= 97 && unit <= 122 + unit > DELETE_CHARACTER // "second half" of the (extended) ASCII table + || unit == '-' + || unit == '_' + || unit >= '0' && unit <= '9' + || unit >= 'A' && unit <= 'Z' + || unit >= 'a' && unit <= 'z' ) { - result += c; + result.append(unit); continue; } - result += "\\" + c; + result.append("\\").append(unit); } - return result; + return result.toString(); } /** - * Escapes special characters in CSS selectors in case it contains ids + * Escapes special characters in CSS selectors in case it contains ids. * * @param css The css string to escape * @return The escaped css string @@ -63,18 +90,18 @@ public static String escapeSelector(String css) { if (!css.contains(" ") && css.startsWith("#")) { return "#" + escapeIdentifier(css.substring(1, css.length())); } else { - String result = ""; + StringBuilder result = new StringBuilder(); String[] pieces = css.split(" "); for (String p : pieces) { if (p.startsWith("#")) { - result += "#" + escapeIdentifier(p.substring(1, p.length())) + " "; + result.append("#").append(escapeIdentifier(p.substring(1, p.length()))).append(" "); } else { - result += p + " "; + result.append(p).append(" "); } } - return result; + return result.toString(); } } } diff --git a/main/src/main/java/de/learnlib/alex/utils/IdRevisionPairList.java b/main/src/main/java/de/learnlib/alex/utils/IdRevisionPairList.java index ab9b91adc..d7b605658 100644 --- a/main/src/main/java/de/learnlib/alex/utils/IdRevisionPairList.java +++ b/main/src/main/java/de/learnlib/alex/utils/IdRevisionPairList.java @@ -1,3 +1,19 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package de.learnlib.alex.utils; import de.learnlib.alex.core.entities.IdRevisionPair; @@ -5,7 +21,7 @@ import java.util.LinkedList; /** - * Helper class to allow batch get of symbols by id/revision pairs + * Helper class to allow batch get of symbols by id/revision pairs. */ public class IdRevisionPairList extends LinkedList { @@ -29,8 +45,8 @@ public IdRevisionPairList(String value) { throw new IllegalArgumentException("Wrong format used for id/revision pairs"); } add(new IdRevisionPair( - Long.valueOf(idRevision[0]), - Long.valueOf(idRevision[1]))); + Long.parseLong(idRevision[0]), + Long.parseLong(idRevision[1]))); } } } diff --git a/main/src/main/java/de/learnlib/alex/utils/IdsList.java b/main/src/main/java/de/learnlib/alex/utils/IdsList.java index f007526e2..e10e4cac2 100644 --- a/main/src/main/java/de/learnlib/alex/utils/IdsList.java +++ b/main/src/main/java/de/learnlib/alex/utils/IdsList.java @@ -1,3 +1,19 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package de.learnlib.alex.utils; import java.util.LinkedList; diff --git a/main/src/main/java/de/learnlib/alex/utils/JSONHelpers.java b/main/src/main/java/de/learnlib/alex/utils/JSONHelpers.java index 2eb60fa6c..c534e85e2 100644 --- a/main/src/main/java/de/learnlib/alex/utils/JSONHelpers.java +++ b/main/src/main/java/de/learnlib/alex/utils/JSONHelpers.java @@ -1,3 +1,19 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package de.learnlib.alex.utils; import com.fasterxml.jackson.databind.JsonNode; @@ -13,8 +29,8 @@ */ public final class JSONHelpers { - /** Use the logger for the server part. */ - private static final Logger LOGGER = LogManager.getLogger("server"); + /** Use the learner logger. */ + private static final Logger LOGGER = LogManager.getLogger("learner"); /** * Disabled default constructor, this is only a utility class with static methods. @@ -35,9 +51,14 @@ public static String getAttributeValue(String json, String attribute) { try { JsonNode node = getNodeByAttribute(json, attribute); if (node == null) { + LOGGER.info("Could not extract the value of the attribute '" + attribute + "' " + + "in the body '" + json + "'."); return null; } else { - return node.asText(); + String value = node.asText(); + LOGGER.info("The attribute '" + attribute + "' has the value '" + value + "' " + + " in the body '" + json + "'."); + return value; } } catch (IOException e) { LOGGER.info("Could not pares the JSON to get the value of an attribute.", e); @@ -58,9 +79,14 @@ public static JsonNodeType getAttributeType(String json, String attribute) { try { JsonNode node = getNodeByAttribute(json, attribute); if (node == null) { + LOGGER.info("Could not extract the type of the attribute '" + attribute + "' " + + "in the body '" + json + "'."); return null; } else { - return node.getNodeType(); + JsonNodeType nodeType = node.getNodeType(); + LOGGER.info("The attribute '" + attribute + "' has the type '" + nodeType + "' " + + " in the body '" + json + "'."); + return nodeType; } } catch (IOException e) { LOGGER.info("Could not pares the JSON to get the type of an attribute.", e); @@ -69,16 +95,30 @@ public static JsonNodeType getAttributeType(String json, String attribute) { } private static JsonNode getNodeByAttribute(String json, String attribute) throws IOException { + if (!isValidJSONPath(attribute)) { + throw new IllegalArgumentException("The attribute '" + attribute + "' is not a valid JSON path!"); + } + ObjectMapper mapper = new ObjectMapper(); JsonNode current = mapper.readTree(json); - String[] attributes = attribute.split("\\."); // "\\." is required because it uses regex + // split on field separators ('.') and array indexes ('[') + String[] attributes = attribute.split("\\.|\\["); // "\\" is required because it uses regex for (int i = 0; i < attributes.length && current != null; i++) { - current = current.get(attributes[i]); + if (attributes[i].endsWith("]")) { // array index + int arrayIndex = Integer.parseInt(attributes[i].substring(0, attributes[i].length() - 1)); + current = current.get(arrayIndex); + } else { // simple attribute name + current = current.get(attributes[i]); + } } return current; } + private static boolean isValidJSONPath(String attribute) { + return attribute.matches("^[a-zA-Z0-9-_]+(\\[[0-9]+\\])*(\\.[a-zA-Z0-9-_]+(\\[[0-9]+\\])*)*$"); + } + } diff --git a/main/src/main/java/de/learnlib/alex/utils/ResourceErrorHandler.java b/main/src/main/java/de/learnlib/alex/utils/ResourceErrorHandler.java index f3635c694..512670844 100644 --- a/main/src/main/java/de/learnlib/alex/utils/ResourceErrorHandler.java +++ b/main/src/main/java/de/learnlib/alex/utils/ResourceErrorHandler.java @@ -1,3 +1,19 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package de.learnlib.alex.utils; import com.fasterxml.jackson.annotation.JsonGetter; diff --git a/main/src/main/java/de/learnlib/alex/utils/ResponseHelper.java b/main/src/main/java/de/learnlib/alex/utils/ResponseHelper.java index 35596562c..52003a01b 100644 --- a/main/src/main/java/de/learnlib/alex/utils/ResponseHelper.java +++ b/main/src/main/java/de/learnlib/alex/utils/ResponseHelper.java @@ -1,13 +1,46 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package de.learnlib.alex.utils; import javax.ws.rs.core.Response; import java.util.List; +/** + * A utility class to render List to a response with additional information in the header. + */ public final class ResponseHelper { + /** + * Deactivated the default constructor because this is only a utility class. + */ private ResponseHelper() { } + /** + * Create a Response for a List that includes the "X-Total-Count" header. + * This method will treat the list elements as JSON ready objects. + * + * @param list + * The list to include in the Response. + * The Elements of the list will be "rendered" using the standard JSON processor. + * @param status + * The status of the Response. + * @return The corresponding Response. + */ public static Response renderList(List list, Response.Status status) { return Response.status(status) .header("X-Total-Count", list.size()) @@ -15,11 +48,4 @@ public static Response renderList(List list, Response.Status status) { .build(); } - public static Response renderStringList(List list, Response.Status status) { - return Response.status(status) - .header("X-Total-Count", list.size()) - .entity(list.toString()) - .build(); - } - } diff --git a/main/src/main/java/de/learnlib/alex/utils/SearchHelper.java b/main/src/main/java/de/learnlib/alex/utils/SearchHelper.java index b0b4ef07d..dac31fee9 100644 --- a/main/src/main/java/de/learnlib/alex/utils/SearchHelper.java +++ b/main/src/main/java/de/learnlib/alex/utils/SearchHelper.java @@ -1,3 +1,19 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package de.learnlib.alex.utils; import de.learnlib.alex.core.learner.connectors.ConnectorManager; @@ -64,6 +80,8 @@ public static boolean searchWithRegex(String regex, String text) { * * @param connector * The connectors to connect to the counter and variable stores. + * @param userId + * The user id for the context. * @param projectId * The project as context. * @param text @@ -72,7 +90,7 @@ public static boolean searchWithRegex(String regex, String text) { * @throws IllegalStateException * If a variable value should be inserted, but the variable does not exists or was never set. */ - public static String insertVariableValues(ConnectorManager connector, Long projectId, String text) + public static String insertVariableValues(ConnectorManager connector, Long userId, Long projectId, String text) throws IllegalStateException { StringBuilder result = new StringBuilder(); int variableStartPos = text.indexOf("{{"); @@ -88,7 +106,8 @@ public static String insertVariableValues(ConnectorManager connector, Long proje String variableValue; switch (text.charAt(variableStartPos + 2)) { case '#': // counter - variableValue = String.valueOf(connector.getConnector(CounterStoreConnector.class).get(projectId, variableName)); + variableValue = String.valueOf(connector.getConnector(CounterStoreConnector.class) + .get(userId, projectId, variableName)); result.append(variableValue); break; case '$': // variable: @@ -97,7 +116,7 @@ public static String insertVariableValues(ConnectorManager connector, Long proje break; case '/': // file name variableValue = connector.getConnector(FileStoreConnector.class) - .getAbsoluteFileLocation(projectId, variableName); + .getAbsoluteFileLocation(userId, projectId, variableName); result.append(variableValue); break; default: // bullshit diff --git a/main/src/main/java/de/learnlib/alex/utils/StringList.java b/main/src/main/java/de/learnlib/alex/utils/StringList.java index 819a69a0d..f51e4e82f 100644 --- a/main/src/main/java/de/learnlib/alex/utils/StringList.java +++ b/main/src/main/java/de/learnlib/alex/utils/StringList.java @@ -1,3 +1,19 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package de.learnlib.alex.utils; import java.util.LinkedList; diff --git a/main/src/main/java/de/learnlib/alex/utils/ValidationExceptionHelper.java b/main/src/main/java/de/learnlib/alex/utils/ValidationExceptionHelper.java new file mode 100644 index 000000000..65f05a172 --- /dev/null +++ b/main/src/main/java/de/learnlib/alex/utils/ValidationExceptionHelper.java @@ -0,0 +1,37 @@ +package de.learnlib.alex.utils; + +import javax.validation.ConstraintViolation; +import javax.validation.ConstraintViolationException; +import javax.validation.ValidationException; + +/** + * Helper class to convert a ConstraintViolationException from Hibernate into a ValidationException. + */ +public final class ValidationExceptionHelper { + + /** + * Default constructor disabled because this is a static helper class. + */ + private ValidationExceptionHelper() { + } + + /** + * Converts a ConstraintViolationException into a ValidationException, which has a nice message with all the + * Constraint Violations in it. + * + * @param baseMessage + * The prefix of the error message. + * @param e + * The ConstraintViolationException to convert. + * @return A new ValidationException base on the ConstraintViolationException. + */ + public static ValidationException createValidationException(String baseMessage, ConstraintViolationException e) { + StringBuilder errorMessage = new StringBuilder(); + errorMessage.append(baseMessage); + for (ConstraintViolation constraintViolation : e.getConstraintViolations()) { + errorMessage.append(' '); + errorMessage.append(constraintViolation.getMessage()); + } + return new ValidationException(errorMessage.toString(), e); + } +} diff --git a/main/src/main/java/de/learnlib/alex/utils/package-info.java b/main/src/main/java/de/learnlib/alex/utils/package-info.java index 646d7c772..73cc6ad23 100644 --- a/main/src/main/java/de/learnlib/alex/utils/package-info.java +++ b/main/src/main/java/de/learnlib/alex/utils/package-info.java @@ -1,3 +1,19 @@ +/* + * Copyright 2016 TU Dortmund + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + /** * This package contains different Helper classes for the ALEX. */ diff --git a/main/src/main/resources/ValidationMessages.properties b/main/src/main/resources/ValidationMessages.properties new file mode 100644 index 000000000..b621b5adb --- /dev/null +++ b/main/src/main/resources/ValidationMessages.properties @@ -0,0 +1,4 @@ +de.learnlib.alex.core.entities.validators.UniqueProjectName.message=The name of a Project must be unique! +de.learnlib.alex.core.entities.validators.UniqueSymbolGroupName.message=The name of a SymbolGroup must be unique per Project! +de.learnlib.alex.core.entities.validators.UniqueSymbolName.message=The name of a Symbol must be unique per Project! +de.learnlib.alex.core.entities.validators.UniqueSymbolAbbreviation.message=The abbreviation of a Symbol must be unique per Project! diff --git a/main/src/main/resources/application.properties b/main/src/main/resources/application.properties new file mode 100644 index 000000000..3801ba15f --- /dev/null +++ b/main/src/main/resources/application.properties @@ -0,0 +1 @@ +server.port=${port:8000} diff --git a/main/src/main/resources/commons-logging.properties b/main/src/main/resources/commons-logging.properties new file mode 100644 index 000000000..292925620 --- /dev/null +++ b/main/src/main/resources/commons-logging.properties @@ -0,0 +1 @@ +org.apache.commons.logging.Log=org.apache.commons.logging.impl.Log4JLogger diff --git a/main/src/main/resources/hibernate.cfg.xml b/main/src/main/resources/hibernate.cfg.xml index 4c76410ef..707781e96 100644 --- a/main/src/main/resources/hibernate.cfg.xml +++ b/main/src/main/resources/hibernate.cfg.xml @@ -25,15 +25,9 @@ org.hibernate.cache.internal.NoCacheProvider - - true - update - - - \ No newline at end of file diff --git a/main/src/main/resources/log4j2.xml b/main/src/main/resources/log4j2.xml index 77b78209a..a63d74a38 100644 --- a/main/src/main/resources/log4j2.xml +++ b/main/src/main/resources/log4j2.xml @@ -6,6 +6,21 @@ + + + + + + + + + + + + + + diff --git a/main/src/main/webapp/WEB-INF/web.xml b/main/src/main/webapp/WEB-INF/web.xml deleted file mode 100644 index e34263cdc..000000000 --- a/main/src/main/webapp/WEB-INF/web.xml +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - Jersey Web Application - org.glassfish.jersey.servlet.ServletContainer - - javax.ws.rs.Application - de.learnlib.alex.ALEXApplication - - 1 - - - - Jersey Web Application - /rest/* - - - - - - default - - useFileMappedBuffer - false - - - - diff --git a/main/src/main/webapp/app/images/favicon-learning.gif b/main/src/main/webapp/app/images/favicon-learning.gif deleted file mode 100644 index 3fad404a9..000000000 Binary files a/main/src/main/webapp/app/images/favicon-learning.gif and /dev/null differ diff --git a/main/src/main/webapp/app/modules/actions/constants.js b/main/src/main/webapp/app/modules/actions/constants.js deleted file mode 100644 index b40c9d885..000000000 --- a/main/src/main/webapp/app/modules/actions/constants.js +++ /dev/null @@ -1,50 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.actions') - - // init dictionaries - .constant('actionGroupTypes', { - WEB: 'web', - REST: 'rest', - GENERAL: 'general' - }) - - // get the type of an action by actionTypes[actionGroupTypes.WEB|REST|GENERAL] - .constant('actionTypes', { - web: { - CLICK: 'web_click', - CLICK_LINK_BY_TEXT: 'web_clickLinkByText', - CLEAR: 'web_clear', - FILL: 'web_fill', - CHECK_FOR_TEXT: 'web_checkForText', - CHECK_FOR_NODE: 'web_checkForNode', - CHECK_PAGE_TITLE: 'web_checkPageTitle', - SUBMIT: 'web_submit', - GO_TO: 'web_goto', - SELECT: 'web_select' - }, - rest: { - CALL_URL: 'rest_call', - CHECK_STATUS: 'rest_checkStatus', - CHECK_HEADER_FIELD: 'rest_checkHeaderField', - CHECK_HTTP_BODY_TEXT: 'rest_checkForText', - CHECK_ATTRIBUTE_EXISTS: 'rest_checkAttributeExists', - CHECK_ATTRIBUTE_VALUE: 'rest_checkAttributeValue', - CHECK_ATTRIBUTE_TYPE: 'rest_checkAttributeType' - }, - general: { - ASSERT_COUNTER: 'assertCounter', - ASSERT_VARIABLE: 'assertVariable', - EXECUTE_SYMBOL: 'executeSymbol', - INCREMENT_COUNTER: 'incrementCounter', - SET_COUNTER: 'setCounter', - SET_VARIABLE: 'setVariable', - SET_VARIABLE_BY_COOKIE: 'setVariableByCookie', - SET_VARIABLE_BY_JSON_ATTRIBUTE: 'setVariableByJSON', - SET_VARIABLE_BY_NODE: 'setVariableByHTML', - WAIT: 'wait' - } - }); -}()); \ No newline at end of file diff --git a/main/src/main/webapp/app/modules/actions/directives/actionFormFields.js b/main/src/main/webapp/app/modules/actions/directives/actionFormFields.js deleted file mode 100644 index a6cee1001..000000000 --- a/main/src/main/webapp/app/modules/actions/directives/actionFormFields.js +++ /dev/null @@ -1,45 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.actions') - .directive('actionFormFields', actionFormFields); - - var template = '
' + - '

' + - '

' + - ' Advanced Options' + - '

' + - '
' + - '
' + - '
' + - '
' + - '
'; - - /** - * The directive that loads an action template by its type. E.g. type: 'web_click' -> load 'web_click.html' - * - * Attribute 'action' should contain the action object - * Attribute 'symbols' should contain the list of symbols so that they are available by the action - * - * Use: - * @returns {{scope: {action: string}, template: string, link: link}} - */ - function actionFormFields() { - return { - scope: { - action: '=', - symbols: '=', - map: '=' - }, - template: template, - link: link - }; - - function link(scope) { - scope.getActionTemplate = function () { - return 'app/modules/actions/views/' + scope.action.type + '.html'; - } - } - } -}()); \ No newline at end of file diff --git a/main/src/main/webapp/app/modules/actions/init.js b/main/src/main/webapp/app/modules/actions/init.js deleted file mode 100644 index 3ca179115..000000000 --- a/main/src/main/webapp/app/modules/actions/init.js +++ /dev/null @@ -1,48 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.actions', []) - .run(run); - - run.$inject = ['$injector', 'ActionService', 'actionGroupTypes', 'actionTypes']; - - function run($injector, ActionService, groups, actions) { - - // register all actions so that they can be accessed in the action editor - ActionService - - // register web actions in module - .register(groups.WEB, actions[groups.WEB].CLEAR, $injector.get('ClearWebAction')) - .register(groups.WEB, actions[groups.WEB].CLICK, $injector.get('ClickWebAction')) - .register(groups.WEB, actions[groups.WEB].CHECK_FOR_TEXT, $injector.get('CheckForTextWebAction')) - .register(groups.WEB, actions[groups.WEB].CHECK_FOR_NODE, $injector.get('CheckForNodeWebAction')) - .register(groups.WEB, actions[groups.WEB].CHECK_PAGE_TITLE, $injector.get('CheckPageTitleAction')) - .register(groups.WEB, actions[groups.WEB].CLICK_LINK_BY_TEXT, $injector.get('ClickLinkByTextWebAction')) - .register(groups.WEB, actions[groups.WEB].FILL, $injector.get('FillWebAction')) - .register(groups.WEB, actions[groups.WEB].GO_TO, $injector.get('GoToWebAction')) - .register(groups.WEB, actions[groups.WEB].SELECT, $injector.get('SelectWebAction')) - .register(groups.WEB, actions[groups.WEB].SUBMIT, $injector.get('SubmitWebAction')) - - // register rest actions in module - .register(groups.REST, actions[groups.REST].CALL_URL, $injector.get('CallRestAction')) - .register(groups.REST, actions[groups.REST].CHECK_ATTRIBUTE_EXISTS, $injector.get('CheckAttributeExistsRestAction')) - .register(groups.REST, actions[groups.REST].CHECK_ATTRIBUTE_TYPE, $injector.get('CheckAttributeTypeRestAction')) - .register(groups.REST, actions[groups.REST].CHECK_ATTRIBUTE_VALUE, $injector.get('CheckAttributeValueRestAction')) - .register(groups.REST, actions[groups.REST].CHECK_HEADER_FIELD, $injector.get('CheckHeaderFieldRestAction')) - .register(groups.REST, actions[groups.REST].CHECK_HTTP_BODY_TEXT, $injector.get('CheckHTTPBodyTextRestAction')) - .register(groups.REST, actions[groups.REST].CHECK_STATUS, $injector.get('CheckStatusRestAction')) - - // register general actions in module - .register(groups.GENERAL, actions[groups.GENERAL].ASSERT_COUNTER, $injector.get('AssertCounterAction')) - .register(groups.GENERAL, actions[groups.GENERAL].ASSERT_VARIABLE, $injector.get('AssertVariableAction')) - .register(groups.GENERAL, actions[groups.GENERAL].EXECUTE_SYMBOL, $injector.get('ExecuteSymbolGeneralAction')) - .register(groups.GENERAL, actions[groups.GENERAL].INCREMENT_COUNTER, $injector.get('IncrementCounterGeneralAction')) - .register(groups.GENERAL, actions[groups.GENERAL].SET_COUNTER, $injector.get('SetCounterGeneralAction')) - .register(groups.GENERAL, actions[groups.GENERAL].SET_VARIABLE, $injector.get('SetVariableGeneralAction')) - .register(groups.GENERAL, actions[groups.GENERAL].SET_VARIABLE_BY_COOKIE, $injector.get('SetVariableByCookieAction')) - .register(groups.GENERAL, actions[groups.GENERAL].SET_VARIABLE_BY_JSON_ATTRIBUTE, $injector.get('SetVariableByJsonAttributeGeneralAction')) - .register(groups.GENERAL, actions[groups.GENERAL].SET_VARIABLE_BY_NODE, $injector.get('SetVariableByNodeGeneralAction')) - .register(groups.GENERAL, actions[groups.GENERAL].WAIT, $injector.get('WaitGeneralAction')); - } -}()); \ No newline at end of file diff --git a/main/src/main/webapp/app/modules/actions/models/AbstractAction.js b/main/src/main/webapp/app/modules/actions/models/AbstractAction.js deleted file mode 100644 index 809e3cd70..000000000 --- a/main/src/main/webapp/app/modules/actions/models/AbstractAction.js +++ /dev/null @@ -1,42 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.actions') - .factory('AbstractAction', AbstractActionFactory); - - /** - * The factory that contains the model for an abstract action - * - * @returns {AbstractAction} - * @constructor - */ - function AbstractActionFactory() { - - /** - * The action model all other actions should extend from - * - * @param {string} type - The unique action type - * @param {boolean} negated - Whether the outcome is negated - * @param {boolean} ignoreFailure - Whether the learner continues despite failure - * @param {boolean} disabled - Whether the execution of the action should be skipped - * @constructor - */ - function AbstractAction(type, negated, ignoreFailure, disabled) { - this.type = type; - this.negated = negated || false; - this.ignoreFailure = ignoreFailure || false; - this.disabled = disabled || false; - } - - AbstractAction.prototype.set = function (key, value) { - this[key] = value; - }; - - AbstractAction.prototype.toString = function () { - return this.type + ': No string representation available' - }; - - return AbstractAction; - } -}()); \ No newline at end of file diff --git a/main/src/main/webapp/app/modules/actions/models/generalActions/AssertCounterAction.js b/main/src/main/webapp/app/modules/actions/models/generalActions/AssertCounterAction.js deleted file mode 100644 index 7f47751c3..000000000 --- a/main/src/main/webapp/app/modules/actions/models/generalActions/AssertCounterAction.js +++ /dev/null @@ -1,58 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.actions') - .factory('AssertCounterAction', AssertCounterActionFactory); - - AssertCounterActionFactory.$inject = ['AbstractAction', 'actionGroupTypes', 'actionTypes']; - - function AssertCounterActionFactory(AbstractAction, actionGroupTypes, actionTypes) { - - /** - * Action to compare the value of a counter to another integer value - * - * @param {string} name - The name of the counter - * @param {string} value - The value to compare the content with - * @param {string} operator - The operator to compare two integer values - * @constructor - */ - function AssertCounterAction(name, value, operator) { - AbstractAction.call(this, actionTypes[actionGroupTypes.GENERAL].ASSERT_COUNTER); - this.name = name || null; - this.value = value || null; - this.operator = operator || null; - } - - AssertCounterAction.prototype = Object.create(AbstractAction.prototype); - - AssertCounterAction.prototype.toString = function () { - var s; - - switch (this.operator) { - case 'LESS_THAN': - s = 'less than'; - break; - case 'LESS_OR_EQUAL': - s = 'less or equal to'; - break; - case 'EQUAL': - s = 'equal to'; - break; - case 'GREATER_OR_EQUAL': - s = 'greater or equal to'; - break; - case 'GREATER_THAN': - s = 'greater than'; - break; - default: - s = 'undefined'; - break; - } - - return 'Check if counter "' + this.name + '" is ' + s + ' ' + this.value; - }; - - return AssertCounterAction; - } -}()); \ No newline at end of file diff --git a/main/src/main/webapp/app/modules/actions/models/generalActions/AssertVariableAction.js b/main/src/main/webapp/app/modules/actions/models/generalActions/AssertVariableAction.js deleted file mode 100644 index 06ff3b1a2..000000000 --- a/main/src/main/webapp/app/modules/actions/models/generalActions/AssertVariableAction.js +++ /dev/null @@ -1,39 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.actions') - .factory('AssertVariableAction', AssertVariableActionFactory); - - AssertVariableActionFactory.$inject = ['AbstractAction', 'actionGroupTypes', 'actionTypes']; - - function AssertVariableActionFactory(AbstractAction, actionGroupTypes, actionTypes) { - - /** - * Action to check if the value of a variable equals or matches a specific string value - * - * @param {string} name - The name of the variable - * @param {string} value - The value to assert against - * @param {boolean} regexp - If value is a regular expression - * @constructor - */ - function AssertVariableAction(name, value, regexp) { - AbstractAction.call(this, actionTypes[actionGroupTypes.GENERAL].ASSERT_VARIABLE); - this.name = name || null; - this.value = value || null; - this.regexp = regexp || false; - } - - AssertVariableAction.prototype = Object.create(AbstractAction.prototype); - - AssertVariableAction.prototype.toString = function () { - if (this.regexp) { - return 'Assert variable "' + this.name + '" to match "' + this.value + '"'; - } else { - return 'Assert variable "' + this.name + '" to equal "' + this.value + '"'; - } - }; - - return AssertVariableAction; - } -}()); \ No newline at end of file diff --git a/main/src/main/webapp/app/modules/actions/models/generalActions/ExecuteSymbolGeneralAction.js b/main/src/main/webapp/app/modules/actions/models/generalActions/ExecuteSymbolGeneralAction.js deleted file mode 100644 index fb08858f3..000000000 --- a/main/src/main/webapp/app/modules/actions/models/generalActions/ExecuteSymbolGeneralAction.js +++ /dev/null @@ -1,85 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.actions') - .factory('ExecuteSymbolGeneralAction', ExecuteSymbolGeneralActionFactory); - - ExecuteSymbolGeneralActionFactory.$inject = ['AbstractAction', 'actionGroupTypes', 'actionTypes']; - - /** - * The factory for ExecuteSymbolGeneralAction - * - * @param AbstractAction - * @param actionGroupTypes - * @param actionTypes - * @returns {ExecuteSymbolGeneralAction} - * @constructor - */ - function ExecuteSymbolGeneralActionFactory(AbstractAction, actionGroupTypes, actionTypes) { - - /** - * Executes another symbol before continuing with other actions - * - * @param {string} symbolName - The name of the symbol - * @param {{id:number,revision:number}} idRevisionPair - The id/revision pair - * @constructor - */ - function ExecuteSymbolGeneralAction(symbolName, idRevisionPair) { - AbstractAction.call(this, actionTypes[actionGroupTypes.GENERAL].EXECUTE_SYMBOL); - - var _symbol = { - name: symbolName || null, - revision: null - }; - - this.symbolToExecute = idRevisionPair || {id: null, revision: null}; - - // some magic that works - this.setSymbol = function (symbols) { - var symbol; - - for (var i = 0; i < symbols.length; i++) { - if (symbols[i].name === _symbol.name) { - symbol = symbols[i]; - } - } - - if (angular.isDefined(symbol)) { - this.symbolToExecute = { - id: symbol.id, - revision: symbol.revision - }; - _symbol.name = symbol.name; - _symbol.revision = symbol.revision; - } - }; - - this.getSymbol = function () { - return _symbol; - } - } - - ExecuteSymbolGeneralAction.prototype = Object.create(AbstractAction.prototype); - - /** - * @returns {string} - */ - ExecuteSymbolGeneralAction.prototype.toString = function () { - return 'Execute symbol "' + this.getSymbol().name + '", rev. ' + this.symbolToExecute.revision; - }; - - ExecuteSymbolGeneralAction.prototype.set = function (key, value) { - if (key === 'symbolToExecuteName') { - this.getSymbol().name = value; - } else { - if (key === 'symbolToExecute') { - this.getSymbol().revision = value.revision; - } - this[key] = value; - } - }; - - return ExecuteSymbolGeneralAction; - } -}()); diff --git a/main/src/main/webapp/app/modules/actions/models/generalActions/IncrementCounterGeneralAction.js b/main/src/main/webapp/app/modules/actions/models/generalActions/IncrementCounterGeneralAction.js deleted file mode 100644 index 13cea897a..000000000 --- a/main/src/main/webapp/app/modules/actions/models/generalActions/IncrementCounterGeneralAction.js +++ /dev/null @@ -1,43 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.actions') - .factory('IncrementCounterGeneralAction', IncrementCounterGeneralActionFactory); - - IncrementCounterGeneralActionFactory.$inject = ['AbstractAction', 'actionGroupTypes', 'actionTypes']; - - /** - * The factory for IncrementCounterGeneralAction - * - * @param AbstractAction - * @param actionGroupTypes - * @param actionTypes - * @returns {IncrementCounterGeneralAction} - * @constructor - */ - function IncrementCounterGeneralActionFactory(AbstractAction, actionGroupTypes, actionTypes) { - - /** - * Increments a counter by one - * - * @param {string} name - The name of the counter - * @constructor - */ - function IncrementCounterGeneralAction(name) { - AbstractAction.call(this, actionTypes[actionGroupTypes.GENERAL].INCREMENT_COUNTER); - this.name = name || null; - } - - IncrementCounterGeneralAction.prototype = Object.create(AbstractAction.prototype); - - /** - * @returns {string} - */ - IncrementCounterGeneralAction.prototype.toString = function () { - return 'Increment counter "' + this.name + '"'; - }; - - return IncrementCounterGeneralAction; - } -}()); diff --git a/main/src/main/webapp/app/modules/actions/models/generalActions/SetCounterGeneralAction.js b/main/src/main/webapp/app/modules/actions/models/generalActions/SetCounterGeneralAction.js deleted file mode 100644 index 2c4993656..000000000 --- a/main/src/main/webapp/app/modules/actions/models/generalActions/SetCounterGeneralAction.js +++ /dev/null @@ -1,46 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.actions') - .factory('SetCounterGeneralAction', SetCounterGeneralActionFactory); - - SetCounterGeneralActionFactory.$inject = ['AbstractAction', 'actionGroupTypes', 'actionTypes']; - - /** - * The factory for SetCounterGeneralAction - * - * @param AbstractAction - * @param actionGroupTypes - * @param actionTypes - * @returns {SetCounterGeneralAction} - * @constructor - */ - function SetCounterGeneralActionFactory(AbstractAction, actionGroupTypes, actionTypes) { - - /** - * Sets a counter to an integer value and creates it implicitly if the counter has not been initialized or used - * before - * - * @param {string} name - The name of the counter - * @param {number} value - The value of the counter - * @constructor - */ - function SetCounterGeneralAction(name, value) { - AbstractAction.call(this, actionTypes[actionGroupTypes.GENERAL].SET_COUNTER); - this.name = name || null; - this.value = value || null; - } - - SetCounterGeneralAction.prototype = Object.create(AbstractAction.prototype); - - /** - * @returns {string} - */ - SetCounterGeneralAction.prototype.toString = function () { - return 'Set counter "' + this.name + '" to "' + this.value + '"'; - }; - - return SetCounterGeneralAction; - } -}()); diff --git a/main/src/main/webapp/app/modules/actions/models/generalActions/SetVariableByCookieAction.js b/main/src/main/webapp/app/modules/actions/models/generalActions/SetVariableByCookieAction.js deleted file mode 100644 index 2d5ab027f..000000000 --- a/main/src/main/webapp/app/modules/actions/models/generalActions/SetVariableByCookieAction.js +++ /dev/null @@ -1,47 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.actions') - .factory('SetVariableByCookieAction', SetVariableByCookieAction); - - SetVariableByCookieAction.$inject = ['AbstractAction', 'actionGroupTypes', 'actionTypes']; - - /** - * The factory for SetVariableGeneralAction - * - * @param AbstractAction - * @param actionGroupTypes - * @param actionTypes - * @returns {SetVariableGeneralAction} - * @constructor - */ - function SetVariableByCookieAction(AbstractAction, actionGroupTypes, actionTypes) { - - /** - * Sets a variable to a specific value and implicitly initializes it if it has not been created before - * - * @param {string} name - The name of the variable - * @param {string} value - The value of the variable - * @param {string} cookieType - The type of the cookie (REST|WEB) - * @constructor - */ - function SetVariableByCookieAction(name, value, cookieType) { - AbstractAction.call(this, actionTypes[actionGroupTypes.GENERAL].SET_VARIABLE_BY_COOKIE); - this.name = name || null; - this.value = value || null; - this.cookieType = cookieType || 'WEB'; - } - - SetVariableByCookieAction.prototype = Object.create(AbstractAction.prototype); - - /** - * @returns {string} - */ - SetVariableByCookieAction.prototype.toString = function () { - return 'Set variable "' + this.name + '" to the value of the cookie: "' + this.value + '"'; - }; - - return SetVariableByCookieAction; - } -}()); diff --git a/main/src/main/webapp/app/modules/actions/models/generalActions/SetVariableByJsonAttributeGeneralAction.js b/main/src/main/webapp/app/modules/actions/models/generalActions/SetVariableByJsonAttributeGeneralAction.js deleted file mode 100644 index aa6eb1348..000000000 --- a/main/src/main/webapp/app/modules/actions/models/generalActions/SetVariableByJsonAttributeGeneralAction.js +++ /dev/null @@ -1,45 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.actions') - .factory('SetVariableByJsonAttributeGeneralAction', SetVariableByJsonAttributeGeneralActionFactory); - - SetVariableByJsonAttributeGeneralActionFactory.$inject = ['AbstractAction', 'actionGroupTypes', 'actionTypes']; - - /** - * The factory for SetVariableByJsonAttributeGeneralAction - * - * @param AbstractAction - * @param actionGroupTypes - * @param actionTypes - * @returns {SetVariableByJsonAttributeGeneralAction} - * @constructor - */ - function SetVariableByJsonAttributeGeneralActionFactory(AbstractAction, actionGroupTypes, actionTypes) { - - /** - * Extracts the value of a JSON object from a HTTP response and saves it into a variable - * - * @param {string} name - The name of the variable - * @param {string} jsonAttribute - The JSON property - * @constructor - */ - function SetVariableByJsonAttributeGeneralAction(name, jsonAttribute) { - AbstractAction.call(this, actionTypes[actionGroupTypes.GENERAL].SET_VARIABLE_BY_JSON_ATTRIBUTE); - this.name = name || null; - this.value = jsonAttribute || null; - } - - SetVariableByJsonAttributeGeneralAction.prototype = Object.create(AbstractAction.prototype); - - /** - * @returns {string} - */ - SetVariableByJsonAttributeGeneralAction.prototype.toString = function () { - return 'Set variable "' + this.name + '" to the value of the JSON attribute "' + this.value + '"'; - }; - - return SetVariableByJsonAttributeGeneralAction; - } -}()); diff --git a/main/src/main/webapp/app/modules/actions/models/generalActions/SetVariableByNodeGeneralAction.js b/main/src/main/webapp/app/modules/actions/models/generalActions/SetVariableByNodeGeneralAction.js deleted file mode 100644 index aa7018cd6..000000000 --- a/main/src/main/webapp/app/modules/actions/models/generalActions/SetVariableByNodeGeneralAction.js +++ /dev/null @@ -1,45 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.actions') - .factory('SetVariableByNodeGeneralAction', SetVariableByNodeGeneralActionFactory); - - SetVariableByNodeGeneralActionFactory.$inject = ['AbstractAction', 'actionGroupTypes', 'actionTypes']; - - /** - * The factory for SetVariableByNodeGeneralAction - * - * @param AbstractAction - * @param actionGroupTypes - * @param actionTypes - * @returns {SetVariableByNodeGeneralAction} - * @constructor - */ - function SetVariableByNodeGeneralActionFactory(AbstractAction, actionGroupTypes, actionTypes) { - - /** - * Extracts the text content value of an element and saves it value in a variable - * - * @param {string} name - The name of the variable - * @param {string} selector - The CSS selector of an element - * @constructor - */ - function SetVariableByNodeGeneralAction(name, selector) { - AbstractAction.call(this, actionTypes[actionGroupTypes.GENERAL].SET_VARIABLE_BY_NODE); - this.name = name || null; - this.value = selector || null; - } - - SetVariableByNodeGeneralAction.prototype = Object.create(AbstractAction.prototype); - - /** - * @returns {string} - */ - SetVariableByNodeGeneralAction.prototype.toString = function () { - return 'Set variable "' + this.name + '" to the value of the element "' + this.value + '"'; - }; - - return SetVariableByNodeGeneralAction; - } -}()); diff --git a/main/src/main/webapp/app/modules/actions/models/generalActions/SetVariableGeneralAction.js b/main/src/main/webapp/app/modules/actions/models/generalActions/SetVariableGeneralAction.js deleted file mode 100644 index 5523415c4..000000000 --- a/main/src/main/webapp/app/modules/actions/models/generalActions/SetVariableGeneralAction.js +++ /dev/null @@ -1,45 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.actions') - .factory('SetVariableGeneralAction', SetVariableGeneralActionFactory); - - SetVariableGeneralActionFactory.$inject = ['AbstractAction', 'actionGroupTypes', 'actionTypes']; - - /** - * The factory for SetVariableGeneralAction - * - * @param AbstractAction - * @param actionGroupTypes - * @param actionTypes - * @returns {SetVariableGeneralAction} - * @constructor - */ - function SetVariableGeneralActionFactory(AbstractAction, actionGroupTypes, actionTypes) { - - /** - * Sets a variable to a specific value and implicitly initializes it if it has not been created before - * - * @param {string} name - The name of the variable - * @param {string} value - The value of the variable - * @constructor - */ - function SetVariableGeneralAction(name, value) { - AbstractAction.call(this, actionTypes[actionGroupTypes.GENERAL].SET_VARIABLE); - this.name = name || null; - this.value = value || null; - } - - SetVariableGeneralAction.prototype = Object.create(AbstractAction.prototype); - - /** - * @returns {string} - */ - SetVariableGeneralAction.prototype.toString = function () { - return 'Set variable "' + this.name + '" to "' + this.value + '"'; - }; - - return SetVariableGeneralAction; - } -}()); diff --git a/main/src/main/webapp/app/modules/actions/models/generalActions/WaitGeneralAction.js b/main/src/main/webapp/app/modules/actions/models/generalActions/WaitGeneralAction.js deleted file mode 100644 index 39e03f0b0..000000000 --- a/main/src/main/webapp/app/modules/actions/models/generalActions/WaitGeneralAction.js +++ /dev/null @@ -1,43 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.actions') - .factory('WaitGeneralAction', WaitGeneralActionFactory); - - WaitGeneralActionFactory.$inject = ['AbstractAction', 'actionGroupTypes', 'actionTypes']; - - /** - * The factory for WaitGeneralAction - * - * @param AbstractAction - * @param actionGroupTypes - * @param actionTypes - * @returns {WaitGeneralAction} - * @constructor - */ - function WaitGeneralActionFactory(AbstractAction, actionGroupTypes, actionTypes) { - - /** - * Wait for a certain amount of time before executing the next action+ - * - * @param {number} duration - The time to wait in milliseconds - * @constructor - */ - function WaitGeneralAction(duration) { - AbstractAction.call(this, actionTypes[actionGroupTypes.GENERAL].WAIT); - this.duration = duration || 0; - } - - WaitGeneralAction.prototype = Object.create(AbstractAction.prototype); - - /** - * @returns {string} - */ - WaitGeneralAction.prototype.toString = function () { - return 'Wait for ' + this.duration + 'ms' - }; - - return WaitGeneralAction; - } -}()); diff --git a/main/src/main/webapp/app/modules/actions/models/restActions/CallRestAction.js b/main/src/main/webapp/app/modules/actions/models/restActions/CallRestAction.js deleted file mode 100644 index f774b4d30..000000000 --- a/main/src/main/webapp/app/modules/actions/models/restActions/CallRestAction.js +++ /dev/null @@ -1,91 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.actions') - .factory('CallRestAction', CallRestActionFactory); - - CallRestActionFactory.$inject = ['AbstractAction', 'actionGroupTypes', 'actionTypes']; - - /** - * The factory for CallRestAction - * - * @param AbstractAction - * @param actionGroupTypes - * @param actionTypes - * @returns {CallRestAction} - * @constructor - */ - function CallRestActionFactory(AbstractAction, actionGroupTypes, actionTypes) { - - /** - * Makes an HTTP request - * - * @param {string} method - The HTTP method in {GET,POST,PUT,DELETE} - * @param {string} url - The URL the request is send to - * @param {string} data - The body data for POST and PUT requests - * @constructor - */ - function CallRestAction(method, url, data) { - AbstractAction.call(this, actionTypes[actionGroupTypes.REST].CALL_URL); - this.method = method || null; - this.url = url || null; - this.data = data || null; - this.cookies = {}; - this.headers = {}; - } - - CallRestAction.prototype = Object.create(AbstractAction.prototype); - - /** - * Adds a cookie to the action - * - * @param {string} key - The cookie key - * @param {string} value - The cookie value - */ - CallRestAction.prototype.addCookie = function (key, value) { - this.cookies[key] = value; - }; - - /** - * Removes a cookie from the action - * - * @param {string} key - The key of the cookie - */ - CallRestAction.prototype.removeCookie = function (key) { - if (angular.isDefined(this.cookies[key])) { - delete this.cookies[key]; - } - }; - - /** - * Adds a header field entry to the action - * - * @param {string} key - The Http header field name - * @param {string} value - The Http header field value - */ - CallRestAction.prototype.addHeader = function (key, value) { - this.headers[key] = value; - }; - - /** - * Removes a header field entry - * - * @param {string} key - The key of the Http header entry - */ - CallRestAction.prototype.removeHeader = function (key) { - if (angular.isDefined(this.headers[key])) { - delete this.headers[key]; - } - }; - - /** - * @returns {string} - */ - CallRestAction.prototype.toString = function () { - return 'Make a "' + this.method + '" request to "' + this.url + '"'; - }; - - return CallRestAction; - } -}()); diff --git a/main/src/main/webapp/app/modules/actions/models/restActions/CheckAttributeExistsRestAction.js b/main/src/main/webapp/app/modules/actions/models/restActions/CheckAttributeExistsRestAction.js deleted file mode 100644 index ad61047b2..000000000 --- a/main/src/main/webapp/app/modules/actions/models/restActions/CheckAttributeExistsRestAction.js +++ /dev/null @@ -1,44 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.actions') - .factory('CheckAttributeExistsRestAction', CheckAttributeExistsRestActionFactory); - - CheckAttributeExistsRestActionFactory.$inject = ['AbstractAction', 'actionGroupTypes', 'actionTypes']; - - /** - * The factory for CheckAttributeExistsRestAction - * - * @param AbstractAction - * @param actionGroupTypes - * @param actionTypes - * @returns {CheckAttributeExistsRestAction} - * @constructor - */ - function CheckAttributeExistsRestActionFactory(AbstractAction, actionGroupTypes, actionTypes) { - - /** - * Checks in a HTTP response body that is formatted in JSON if a specific attribute exists. - * E.g. object.attribute.anotherAttribute - * - * @param {string} attribute - A JSON property - * @constructor - */ - function CheckAttributeExistsRestAction(attribute) { - AbstractAction.call(this, actionTypes[actionGroupTypes.REST].CHECK_ATTRIBUTE_EXISTS); - this.attribute = this.attribute = attribute || null; - } - - CheckAttributeExistsRestAction.prototype = Object.create(AbstractAction.prototype); - - /** - * @returns {string} - */ - CheckAttributeExistsRestAction.prototype.toString = function () { - return 'Check if the JSON of a HTTP response has attribute "' + this.attribute + '"'; - }; - - return CheckAttributeExistsRestAction; - } -}()); diff --git a/main/src/main/webapp/app/modules/actions/models/restActions/CheckAttributeTypeRestAction.js b/main/src/main/webapp/app/modules/actions/models/restActions/CheckAttributeTypeRestAction.js deleted file mode 100644 index 87de23291..000000000 --- a/main/src/main/webapp/app/modules/actions/models/restActions/CheckAttributeTypeRestAction.js +++ /dev/null @@ -1,45 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.actions') - .factory('CheckAttributeTypeRestAction', CheckAttributeTypeRestActionFactory); - - CheckAttributeTypeRestActionFactory.$inject = ['AbstractAction', 'actionGroupTypes', 'actionTypes']; - - /** - * The factory for CheckAttributeTypeRestAction - * - * @param AbstractAction - * @param actionGroupTypes - * @param actionTypes - * @returns {CheckAttributeTypeRestAction} - * @constructor - */ - function CheckAttributeTypeRestActionFactory(AbstractAction, actionGroupTypes, actionTypes) { - - /** - * Checks if a property of the JSON of a HTTP response has a specific type - * - * @param {string} attribute - The JSON property - * @param {string} jsonType - The Type in {INTEGER,STRING,BOOLEAN,OBJECT} - * @constructor - */ - function CheckAttributeTypeRestAction(attribute, jsonType) { - AbstractAction.call(this, actionTypes[actionGroupTypes.REST].CHECK_ATTRIBUTE_TYPE); - this.attribute = attribute || null; - this.jsonType = jsonType || null; - } - - CheckAttributeTypeRestAction.prototype = Object.create(AbstractAction.prototype); - - /** - * @returns {string} - */ - CheckAttributeTypeRestAction.prototype.toString = function () { - return 'Check the JSON of a HTTP response to have attribute "' + this.attribute + '" to be' + (this.regexp ? ' like ' : ' ') + '"' + this.value + '"'; - }; - - return CheckAttributeTypeRestAction; - } -}()); diff --git a/main/src/main/webapp/app/modules/actions/models/restActions/CheckAttributeValueRestAction.js b/main/src/main/webapp/app/modules/actions/models/restActions/CheckAttributeValueRestAction.js deleted file mode 100644 index d746fd97d..000000000 --- a/main/src/main/webapp/app/modules/actions/models/restActions/CheckAttributeValueRestAction.js +++ /dev/null @@ -1,48 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.actions') - .factory('CheckAttributeValueRestAction', CheckAttributeValueRestActionFactory); - - CheckAttributeValueRestActionFactory.$inject = ['AbstractAction', 'actionGroupTypes', 'actionTypes']; - - /** - * The factory for CheckAttributeValueRestAction - * - * @param AbstractAction - * @param actionGroupTypes - * @param actionTypes - * @returns {CheckAttributeValueRestAction} - * @constructor - */ - function CheckAttributeValueRestActionFactory(AbstractAction, actionGroupTypes, actionTypes) { - - /** - * Checks if a property of a JSON object in a HTTP response body has a specific value or matches a regular - * expression - * - * @param {string} attribute - The JSON property - * @param {string} value - The value that is searched for in the property - * @param {boolean} isRegexp - Whether the value is a regular expression - * @constructor - */ - function CheckAttributeValueRestAction(attribute, value, isRegexp) { - AbstractAction.call(this, actionTypes[actionGroupTypes.REST].CHECK_ATTRIBUTE_VALUE); - this.attribute = attribute || null; - this.value = value || null; - this.regexp = isRegexp || false - } - - CheckAttributeValueRestAction.prototype = Object.create(AbstractAction.prototype); - - /** - * @returns {string} - */ - CheckAttributeValueRestAction.prototype.toString = function () { - return 'Check the JSON of a HTTP response to have attribute "' + this.attribute + '" to be' + (this.regexp ? ' like ' : ' ') + '"' + this.value + '"'; - }; - - return CheckAttributeValueRestAction; - } -}()); diff --git a/main/src/main/webapp/app/modules/actions/models/restActions/CheckHTTPBodyTextRestAction.js b/main/src/main/webapp/app/modules/actions/models/restActions/CheckHTTPBodyTextRestAction.js deleted file mode 100644 index 81dcb0a82..000000000 --- a/main/src/main/webapp/app/modules/actions/models/restActions/CheckHTTPBodyTextRestAction.js +++ /dev/null @@ -1,45 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.actions') - .factory('CheckHTTPBodyTextRestAction', CheckHTTPBodyTextRestActionFactory); - - CheckHTTPBodyTextRestActionFactory.$inject = ['AbstractAction', 'actionGroupTypes', 'actionTypes']; - - /** - * The factory for CheckHTTPBodyTextRestAction - * - * @param AbstractAction - * @param actionGroupTypes - * @param actionTypes - * @returns {CheckHTTPBodyTextRestAction} - * @constructor - */ - function CheckHTTPBodyTextRestActionFactory(AbstractAction, actionGroupTypes, actionTypes) { - - /** - * Searches for a string value in the body of an HTTP response - * - * @param {string} value - The string that is searched for - * @param {boolean} isRegexp - Whether the value is interpreted as regular expression - * @constructor - */ - function CheckHTTPBodyTextRestAction(value, isRegexp) { - AbstractAction.call(this, actionTypes[actionGroupTypes.REST].CHECK_HTTP_BODY_TEXT); - this.value = value || null; - this.regexp = isRegexp || false; - } - - CheckHTTPBodyTextRestAction.prototype = Object.create(AbstractAction.prototype); - - /** - * @returns {string} - */ - CheckHTTPBodyTextRestAction.prototype.toString = function () { - return 'Search in the HTTP response body for ' + (this.regexp ? 'regexp' : 'string') + ' "' + this.value + '"'; - }; - - return CheckHTTPBodyTextRestAction; - } -}()); diff --git a/main/src/main/webapp/app/modules/actions/models/restActions/CheckHeaderFieldRestAction.js b/main/src/main/webapp/app/modules/actions/models/restActions/CheckHeaderFieldRestAction.js deleted file mode 100644 index f79b61568..000000000 --- a/main/src/main/webapp/app/modules/actions/models/restActions/CheckHeaderFieldRestAction.js +++ /dev/null @@ -1,47 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.actions') - .factory('CheckHeaderFieldRestAction', CheckHeaderFieldRestActionFactory); - - CheckHeaderFieldRestActionFactory.$inject = ['AbstractAction', 'actionGroupTypes', 'actionTypes']; - - /** - * The factory for CheckHeaderFieldRestAction - * - * @param AbstractAction - * @param actionGroupTypes - * @param actionTypes - * @returns {CheckHeaderFieldRestAction} - * @constructor - */ - function CheckHeaderFieldRestActionFactory(AbstractAction, actionGroupTypes, actionTypes) { - - /** - * Checks a value in the header fields of an HTTP response - * - * @param {string} key - The key of the header field - * @param {string} value - The expected value of the header field - * @param {boolean} isRegexp - Whether the value is interpreted as regular epxression - * @constructor - */ - function CheckHeaderFieldRestAction(key, value, isRegexp) { - AbstractAction.call(this, actionTypes[actionGroupTypes.REST].CHECK_HEADER_FIELD); - this.key = key || null; - this.value = value || null; - this.regexp = isRegexp || false; - } - - CheckHeaderFieldRestAction.prototype = Object.create(AbstractAction.prototype); - - /** - * @returns {string} - */ - CheckHeaderFieldRestAction.prototype.toString = function () { - return 'Check HTTP response header field "' + this.key + '" to be' + (this.regexp ? ' like ' : ' ') + '"' + this.value + '"'; - }; - - return CheckHeaderFieldRestAction; - } -}()); diff --git a/main/src/main/webapp/app/modules/actions/models/restActions/CheckStatusRestAction.js b/main/src/main/webapp/app/modules/actions/models/restActions/CheckStatusRestAction.js deleted file mode 100644 index 5e7a84598..000000000 --- a/main/src/main/webapp/app/modules/actions/models/restActions/CheckStatusRestAction.js +++ /dev/null @@ -1,43 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.actions') - .factory('CheckStatusRestAction', CheckStatusRestActionFactory); - - CheckStatusRestActionFactory.$inject = ['AbstractAction', 'actionGroupTypes', 'actionTypes']; - - /** - * The factory for CheckStatusRestAction - * - * @param AbstractAction - * @param actionGroupTypes - * @param actionTypes - * @returns {CheckStatusRestAction} - * @constructor - */ - function CheckStatusRestActionFactory(AbstractAction, actionGroupTypes, actionTypes) { - - /** - * Checks for the status code (e.g. 404) in an HTTP response - * - * @param {number} status - The status code - * @constructor - */ - function CheckStatusRestAction(status) { - AbstractAction.call(this, actionTypes[actionGroupTypes.REST].CHECK_STATUS); - this.status = status || null; - } - - CheckStatusRestAction.prototype = Object.create(AbstractAction.prototype); - - /** - * @returns {string} - */ - CheckStatusRestAction.prototype.toString = function () { - return 'Check HTTP response status to be "' + this.status + '"' - }; - - return CheckStatusRestAction; - } -}()); diff --git a/main/src/main/webapp/app/modules/actions/models/webActions/CheckForNodeWebAction.js b/main/src/main/webapp/app/modules/actions/models/webActions/CheckForNodeWebAction.js deleted file mode 100644 index 8ce36f9e1..000000000 --- a/main/src/main/webapp/app/modules/actions/models/webActions/CheckForNodeWebAction.js +++ /dev/null @@ -1,43 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.actions') - .factory('CheckForNodeWebAction', CheckForNodeWebActionFactory); - - CheckForNodeWebActionFactory.$inject = ['AbstractAction', 'actionGroupTypes', 'actionTypes']; - - /** - * The factory for CheckForNodeWebAction - * - * @param AbstractAction - * @param actionGroupTypes - * @param actionTypes - * @returns {CheckForNodeWebAction} - * @constructor - */ - function CheckForNodeWebActionFactory(AbstractAction, actionGroupTypes, actionTypes) { - - /** - * Searches for an element with a specific selector in the HTML document - * - * @param {string} value - The CSS selector of an element - * @constructor - */ - function CheckForNodeWebAction(value) { - AbstractAction.call(this, actionTypes[actionGroupTypes.WEB].CHECK_FOR_NODE); - this.value = value || null; - } - - CheckForNodeWebAction.prototype = Object.create(AbstractAction.prototype); - - /** - * @returns {string} - */ - CheckForNodeWebAction.prototype.toString = function () { - return 'Search for node "' + this.value + '"'; - }; - - return CheckForNodeWebAction; - } -}()); diff --git a/main/src/main/webapp/app/modules/actions/models/webActions/CheckForTextWebAction.js b/main/src/main/webapp/app/modules/actions/models/webActions/CheckForTextWebAction.js deleted file mode 100644 index e6f8fe365..000000000 --- a/main/src/main/webapp/app/modules/actions/models/webActions/CheckForTextWebAction.js +++ /dev/null @@ -1,45 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.actions') - .factory('CheckForTextWebAction', CheckForTextWebActionFactory); - - CheckForTextWebActionFactory.$inject = ['AbstractAction', 'actionGroupTypes', 'actionTypes']; - - /** - * The factory for CheckForTextWebAction - * - * @param AbstractAction - * @param actionGroupTypes - * @param actionTypes - * @returns {CheckForTextWebAction} - * @constructor - */ - function CheckForTextWebActionFactory(AbstractAction, actionGroupTypes, actionTypes) { - - /** - * Searches for a piece of text or a regular expression in the HTML document - * - * @param {string} value - The piece of text to look for - * @param {boolean} isRegexp - Whether the value is a regular expression - * @constructor - */ - function CheckForTextWebAction(value, isRegexp) { - AbstractAction.call(this, actionTypes[actionGroupTypes.WEB].CHECK_FOR_TEXT); - this.value = value || null; - this.regexp = isRegexp || false; - } - - CheckForTextWebAction.prototype = Object.create(AbstractAction.prototype); - - /** - * @returns {string} - */ - CheckForTextWebAction.prototype.toString = function () { - return 'Search for ' + (this.regexp ? 'regexp' : '') + ' "' + this.value + '" in the document'; - }; - - return CheckForTextWebAction; - } -}()); diff --git a/main/src/main/webapp/app/modules/actions/models/webActions/CheckPageTitleAction.js b/main/src/main/webapp/app/modules/actions/models/webActions/CheckPageTitleAction.js deleted file mode 100644 index 2f2efe34c..000000000 --- a/main/src/main/webapp/app/modules/actions/models/webActions/CheckPageTitleAction.js +++ /dev/null @@ -1,45 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.actions') - .factory('CheckPageTitleAction', CheckPageTitleActionFactory); - - CheckPageTitleActionFactory.$inject = ['AbstractAction', 'actionGroupTypes', 'actionTypes']; - - /** - * The factory for CheckForTextWebAction - * - * @param AbstractAction - * @param actionGroupTypes - * @param actionTypes - * @returns {CheckForTextWebAction} - * @constructor - */ - function CheckPageTitleActionFactory(AbstractAction, actionGroupTypes, actionTypes) { - - /** - * Searches for a piece of text or a regular expression in the HTML document - * - * @param {string} title - The page title to look for - * @param {boolean} regexp - If the title should be interpreted as regexp - * @constructor - */ - function CheckPageTitleAction(title, regexp) { - AbstractAction.call(this, actionTypes[actionGroupTypes.WEB].CHECK_PAGE_TITLE); - this.title = title || null; - this.regexp = regexp || false; - } - - CheckPageTitleAction.prototype = Object.create(AbstractAction.prototype); - - /** - * @returns {string} - */ - CheckPageTitleAction.prototype.toString = function () { - return 'Check the page title to equal "' + this.title + '"'; - }; - - return CheckPageTitleAction; - } -}()); diff --git a/main/src/main/webapp/app/modules/actions/models/webActions/ClearWebAction.js b/main/src/main/webapp/app/modules/actions/models/webActions/ClearWebAction.js deleted file mode 100644 index 24a76b71b..000000000 --- a/main/src/main/webapp/app/modules/actions/models/webActions/ClearWebAction.js +++ /dev/null @@ -1,43 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.actions') - .factory('ClearWebAction', ClearWebActionFactory); - - ClearWebActionFactory.$inject = ['AbstractAction', 'actionGroupTypes', 'actionTypes']; - - /** - * The factory for ClearWebAction - * - * @param AbstractAction - * @param actionGroupTypes - * @param actionTypes - * @returns {ClearWebAction} - * @constructor - */ - function ClearWebActionFactory(AbstractAction, actionGroupTypes, actionTypes) { - - /** - * Remove all inputs from an element - * - * @param {string} node - The CSS selector of the element - * @constructor - */ - function ClearWebAction(node) { - AbstractAction.call(this, actionTypes[actionGroupTypes.WEB].CLEAR); - this.node = node || null; - } - - ClearWebAction.prototype = Object.create(AbstractAction.prototype); - - /** - * @returns {string} - */ - ClearWebAction.prototype.toString = function () { - return 'Clear element "' + this.node + '"'; - }; - - return ClearWebAction; - } -}()); diff --git a/main/src/main/webapp/app/modules/actions/models/webActions/ClickLinkByTextWebAction.js b/main/src/main/webapp/app/modules/actions/models/webActions/ClickLinkByTextWebAction.js deleted file mode 100644 index d6486d6c1..000000000 --- a/main/src/main/webapp/app/modules/actions/models/webActions/ClickLinkByTextWebAction.js +++ /dev/null @@ -1,43 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.actions') - .factory('ClickLinkByTextWebAction', ClickLinkByTextWebActionFactory); - - ClickLinkByTextWebActionFactory.$inject = ['AbstractAction', 'actionGroupTypes', 'actionTypes']; - - /** - * The factory for ClickLinkByTextWebAction - * - * @param AbstractAction - * @param actionGroupTypes - * @param actionTypes - * @returns {ClickLinkByTextWebAction} - * @constructor - */ - function ClickLinkByTextWebActionFactory(AbstractAction, actionGroupTypes, actionTypes) { - - /** - * Clicks on a link with a specific text value - * - * @param {string} value - The text of the link - * @constructor - */ - function ClickLinkByTextWebAction(value) { - AbstractAction.call(this, actionTypes[actionGroupTypes.WEB].CLICK_LINK_BY_TEXT); - this.value = value || null; - } - - ClickLinkByTextWebAction.prototype = Object.create(AbstractAction.prototype); - - /** - * @returns {string} - */ - ClickLinkByTextWebAction.prototype.toString = function () { - return 'Click on link with text "' + this.value + '"'; - }; - - return ClickLinkByTextWebAction; - } -}()); diff --git a/main/src/main/webapp/app/modules/actions/models/webActions/ClickWebAction.js b/main/src/main/webapp/app/modules/actions/models/webActions/ClickWebAction.js deleted file mode 100644 index aa40f981e..000000000 --- a/main/src/main/webapp/app/modules/actions/models/webActions/ClickWebAction.js +++ /dev/null @@ -1,43 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.actions') - .factory('ClickWebAction', ClickWebActionFactory); - - ClickWebActionFactory.$inject = ['AbstractAction', 'actionGroupTypes', 'actionTypes']; - - /** - * The factory for ClickWebAction - * - * @param AbstractAction - * @param actionGroupTypes - * @param actionTypes - * @returns {ClickWebAction} - * @constructor - */ - function ClickWebActionFactory(AbstractAction, actionGroupTypes, actionTypes) { - - /** - * Clicks on an element - * - * @param {string} node - The CSS selector of an element - * @constructor - */ - function ClickWebAction(node) { - AbstractAction.call(this, actionTypes[actionGroupTypes.WEB].CLICK); - this.node = node || null; - } - - ClickWebAction.prototype = Object.create(AbstractAction.prototype); - - /** - * @returns {string} - */ - ClickWebAction.prototype.toString = function () { - return 'Click on element "' + this.node + '"'; - }; - - return ClickWebAction; - } -}()); diff --git a/main/src/main/webapp/app/modules/actions/models/webActions/FillWebAction.js b/main/src/main/webapp/app/modules/actions/models/webActions/FillWebAction.js deleted file mode 100644 index 08ce9864b..000000000 --- a/main/src/main/webapp/app/modules/actions/models/webActions/FillWebAction.js +++ /dev/null @@ -1,45 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.actions') - .factory('FillWebAction', FillWebActionFactory); - - FillWebActionFactory.$inject = ['AbstractAction', 'actionGroupTypes', 'actionTypes']; - - /** - * The factory for FillWebAction - * - * @param AbstractAction - * @param actionGroupTypes - * @param actionTypes - * @returns {FillWebAction} - * @constructor - */ - function FillWebActionFactory(AbstractAction, actionGroupTypes, actionTypes) { - - /** - * Fills an input element with a value - * - * @param {string} node - The CSS selector of an element - * @param {string} value - The value it should be filled with - * @constructor - */ - function FillWebAction(node, value) { - AbstractAction.call(this, actionTypes[actionGroupTypes.WEB].FILL); - this.node = node || null; - this.value = value || null - } - - FillWebAction.prototype = Object.create(AbstractAction.prototype); - - /** - * @returns {string} - */ - FillWebAction.prototype.toString = function () { - return 'Fill element "' + this.node + '" with "' + this.value + '"'; - }; - - return FillWebAction; - } -}()); diff --git a/main/src/main/webapp/app/modules/actions/models/webActions/GoToWebAction.js b/main/src/main/webapp/app/modules/actions/models/webActions/GoToWebAction.js deleted file mode 100644 index 13809f5e1..000000000 --- a/main/src/main/webapp/app/modules/actions/models/webActions/GoToWebAction.js +++ /dev/null @@ -1,44 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.actions') - .factory('GoToWebAction', GoToWebActionFactory); - - GoToWebActionFactory.$inject = ['AbstractAction', 'actionGroupTypes', 'actionTypes']; - - /** - * The factory for GoToWebAction - * - * @param ActionService - * @param AbstractAction - * @param actionGroupTypes - * @param actionTypes - * @returns {GoToWebAction} - * @constructor - */ - function GoToWebActionFactory(AbstractAction, actionGroupTypes, actionTypes) { - - /** - * Goes to a URL - * - * @param {string} url - The url that is called - * @constructor - */ - function GoToWebAction(url) { - AbstractAction.call(this, actionTypes[actionGroupTypes.WEB].GO_TO); - this.url = url || null; - } - - GoToWebAction.prototype = Object.create(AbstractAction.prototype); - - /** - * @returns {string} - */ - GoToWebAction.prototype.toString = function () { - return 'Go to URL "' + this.url + '"'; - }; - - return GoToWebAction; - } -}()); diff --git a/main/src/main/webapp/app/modules/actions/models/webActions/SelectWebAction.js b/main/src/main/webapp/app/modules/actions/models/webActions/SelectWebAction.js deleted file mode 100644 index f5210edb2..000000000 --- a/main/src/main/webapp/app/modules/actions/models/webActions/SelectWebAction.js +++ /dev/null @@ -1,47 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.actions') - .factory('SelectWebAction', SelectWebActionFactory); - - SelectWebActionFactory.$inject = ['AbstractAction', 'actionGroupTypes', 'actionTypes']; - - /** - * The factory for SelectWebAction - * - * @param AbstractAction - * @param actionGroupTypes - * @param actionTypes - * @returns {SelectWebAction} - * @constructor - */ - function SelectWebActionFactory(AbstractAction, actionGroupTypes, actionTypes) { - - /** - * Selects an entry from a select box - * - * @param {string} node - The CSS selector of an select element - * @param {string} value - The value of the select input that should be selected - * @param {string} selectBy - The type the option is selected by {'TEXT', 'VALUE', 'INDEX'} - * @constructor - */ - function SelectWebAction(node, value, selectBy) { - AbstractAction.call(this, actionTypes[actionGroupTypes.WEB].SELECT); - this.node = node || null; - this.value = value || null; - this.selectBy = selectBy || 'TEXT'; - } - - SelectWebAction.prototype = Object.create(AbstractAction.prototype); - - /** - * @returns {string} - */ - SelectWebAction.prototype.toString = function () { - return 'Select value "' + this.value + '" from select input "' + this.node + '"'; - }; - - return SelectWebAction; - } -}()); diff --git a/main/src/main/webapp/app/modules/actions/models/webActions/SubmitWebAction.js b/main/src/main/webapp/app/modules/actions/models/webActions/SubmitWebAction.js deleted file mode 100644 index 02d9b2cd3..000000000 --- a/main/src/main/webapp/app/modules/actions/models/webActions/SubmitWebAction.js +++ /dev/null @@ -1,43 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.actions') - .factory('SubmitWebAction', SubmitWebActionFactory); - - SubmitWebActionFactory.$inject = ['AbstractAction', 'actionGroupTypes', 'actionTypes']; - - /** - * The factory for SubmitWebAction - * - * @param AbstractAction - * @param actionGroupTypes - * @param actionTypes - * @returns {SubmitWebAction} - * @constructor - */ - function SubmitWebActionFactory(AbstractAction, actionGroupTypes, actionTypes) { - - /** - * Submits a form. Can also be applied to an input element of a form - * - * @param {string} node - The CSS selector of an element - * @constructor - */ - function SubmitWebAction(node) { - AbstractAction.call(this, actionTypes[actionGroupTypes.WEB].SUBMIT); - this.node = node || null; - } - - SubmitWebAction.prototype = Object.create(AbstractAction.prototype); - - /** - * @returns {string} - */ - SubmitWebAction.prototype.toString = function () { - return 'Submit element "' + this.node + '"'; - }; - - return SubmitWebAction; - } -}()); diff --git a/main/src/main/webapp/app/modules/actions/services/ActionBuilder.js b/main/src/main/webapp/app/modules/actions/services/ActionBuilder.js deleted file mode 100644 index dc025ef19..000000000 --- a/main/src/main/webapp/app/modules/actions/services/ActionBuilder.js +++ /dev/null @@ -1,87 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.actions') - .factory('ActionBuilder', ActionBuilderFactory); - - ActionBuilderFactory.$inject = ['ActionService', 'actionGroupTypes', 'actionTypes']; - - /** - * The factory for the action builder - * - * @param ActionService - * @param actionGroupTypes - * @param actionTypes - * @returns {ActionBuilder} - * @constructor - */ - function ActionBuilderFactory(ActionService, actionGroupTypes, actionTypes) { - - /** - * Is used to create instances of actions from objects or string types - * @constructor - */ - function ActionBuilder() { - } - - /** - * Creates a new action instance from a string type - * - * @param {string} type - The type of the action that should be created - * @returns {null|*} - The action instance - */ - ActionBuilder.prototype.createFromType = function (type) { - if (type !== undefined) { - for (var k in actionGroupTypes) { - for (var j in actionTypes[actionGroupTypes[k]]) { - if (type === actionTypes[actionGroupTypes[k]][j]) { - var Action = ActionService.get(actionGroupTypes[k], actionTypes[actionGroupTypes[k]][j]); - return new Action(); - } - } - } - return null; - } else { - throw new Error('Undefined ActionType Error'); - } - }; - - /** - * Creates a new action instance from a javascript object, e.g. from the API - * - * @param obj - The object the action is created from - * @returns {null|*} - The action instance - */ - ActionBuilder.prototype.createFromObject = function (obj) { - var action; - if (obj.type !== undefined) { - action = this.createFromType(obj.type); - for (var key in obj) { - if (key[0] !== '_') { - action.set(key, obj[key]); - } - } - return action; - } else { - throw new Error('Undefined ActionType Error'); - } - }; - - /** - * Creates a bunch of action instances from javascript objects - * - * @param {Object[]} objects - The list of objects the actions should be created from - * @returns {Array} - The list of actions - */ - ActionBuilder.prototype.createFromObjects = function (objects) { - var actions = []; - for (var i = 0; i < objects.length; i++) { - actions.push(this.createFromObject(objects[i])); - } - return actions; - }; - - return new ActionBuilder(); - } -}()); \ No newline at end of file diff --git a/main/src/main/webapp/app/modules/actions/services/ActionService.js b/main/src/main/webapp/app/modules/actions/services/ActionService.js deleted file mode 100644 index 4289e6616..000000000 --- a/main/src/main/webapp/app/modules/actions/services/ActionService.js +++ /dev/null @@ -1,51 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.actions') - .factory('ActionService', ActionService); - - /** - * Contains functions to instantiate new actions according to their group and action type - * - * @returns {{register: register, get: get}} - * @constructor - */ - function ActionService() { - - // = [actionGroupType][actionType] - var map = {}; - - return { - register: register, - get: get - }; - - /** - * Registers a new action so that it is available for others - * - * @param {number} actionGroupType - * @param {string} actionType - * @param {function} action - */ - function register(actionGroupType, actionType, action) { - if (map[actionGroupType] === undefined) { - map[actionGroupType] = function () { - }; - } - map[actionGroupType][actionType] = action; - return this; - } - - /** - * Gets the function to instantiate a specific action - * - * @param {number} actionGroupType - * @param {string} actionType - * @returns {function} - */ - function get(actionGroupType, actionType) { - return map[actionGroupType][actionType]; - } - } -}()); \ No newline at end of file diff --git a/main/src/main/webapp/app/modules/actions/views/executeSymbol.html b/main/src/main/webapp/app/modules/actions/views/executeSymbol.html deleted file mode 100644 index 4af117876..000000000 --- a/main/src/main/webapp/app/modules/actions/views/executeSymbol.html +++ /dev/null @@ -1,33 +0,0 @@ -

Execute Symbol

- -

- Execute the actions of another symbol -

-
-
-
- - -
- - - - -
-
-
- - -
-
- - - - -
-
\ No newline at end of file diff --git a/main/src/main/webapp/app/modules/core/constants.js b/main/src/main/webapp/app/modules/core/constants.js deleted file mode 100644 index 6f3d4cc21..000000000 --- a/main/src/main/webapp/app/modules/core/constants.js +++ /dev/null @@ -1,29 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.core') - - // make global libraries a constant for better testing - .constant('_', window._) // lodash - .constant('dagreD3', window.dagreD3) // dagreD3 - .constant('d3', window.d3) // d3 - .constant('graphlib', window.graphlib) // graphlib - - // paths that are used in the application - .constant('paths', { - api: { - URL: '/rest', - PROXY_URL: '/rest/proxy?url=' - }, - COMPONENTS: 'app/modules' - }) - - // learn algorithms - .constant('learnAlgorithms', { - LSTAR: 'LSTAR', - DHC: 'DHC', - DISCRIMINATION_TREE: 'DISCRIMINATION_TREE', - TTT: 'TTT' - }) -}()); \ No newline at end of file diff --git a/main/src/main/webapp/app/modules/core/controllers/CountersController.js b/main/src/main/webapp/app/modules/core/controllers/CountersController.js deleted file mode 100644 index b1e9038d0..000000000 --- a/main/src/main/webapp/app/modules/core/controllers/CountersController.js +++ /dev/null @@ -1,81 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.core') - .controller('CountersController', CountersController); - - CountersController.$inject = ['$scope', 'SessionService', 'CounterResource', 'ToastService', '_']; - - /** - * The controller for the page that lists all counters of a project in a list. It is also possible to delete them. - * - * Template: 'views/counters.html'; - * - * @param $scope - The projects scope - * @param Session - The SessionService - * @param Counters - The CounterResource - * @param Toast - The ToastService - * @param _ - Lodash - * @constructor - */ - function CountersController($scope, Session, Counters, Toast, _) { - - // the sessions project - var project = Session.project.get(); - - /** - * The counters of the project - * @type {{name: string, value: number, project: number}[]} - */ - $scope.counters = []; - - /** - * The selected counters objects - * @type {{name: string, value: number, project: number}[]} - */ - $scope.selectedCounters = []; - - // load all existing counters from the server - (function init() { - Counters.getAll(project.id) - .then(function (counters) { - $scope.counters = counters; - }); - }()); - - /** - * Delete a counter from the server and on success from scope - * - * @param {{name: string, value: number, project: number}} counter - The counter that should be deleted - */ - $scope.deleteCounter = function (counter) { - Counters.delete(project.id, counter.name) - .then(function () { - Toast.success('Counter "' + counter.name + '" deleted'); - _.remove($scope.counters, {name: counter.name}); - }) - .catch(function (response) { - Toast.danger('

Deleting counter "' + counter.name + '" failed

' + response.data.message); - }) - }; - - /** - * Delete all selected counters from the server and on success from scope - */ - $scope.deleteSelectedCounters = function () { - if ($scope.selectedCounters.length > 0) { - Counters.deleteSome(project.id, _.pluck($scope.selectedCounters, 'name')) - .then(function () { - Toast.success('Counters deleted'); - _.forEach($scope.selectedCounters, function (counter) { - _.remove($scope.counters, {name: counter.name}); - }) - }) - .catch(function (response) { - Toast.danger('

Deleting counters failed

' + response.data.message); - }) - } - } - } -}()); \ No newline at end of file diff --git a/main/src/main/webapp/app/modules/core/controllers/ErrorController.js b/main/src/main/webapp/app/modules/core/controllers/ErrorController.js deleted file mode 100644 index 5653ab038..000000000 --- a/main/src/main/webapp/app/modules/core/controllers/ErrorController.js +++ /dev/null @@ -1,35 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.core') - .controller('ErrorController', ErrorController); - - ErrorController.$inject = ['$scope', '$state', 'ErrorService']; - - /** - * The controller of the error page that displays a single error message - * - * Template: 'views/error.html' - * - * @param $scope - The controllers scope - * @param $state - The ui.router $state service - * @param Error - The ErrorService - * @constructor - */ - function ErrorController($scope, $state, Error) { - - /** - * The error message - * @type{string|null} - */ - $scope.errorMessage = null; - - var msg = Error.getErrorMessage(); - if (msg === null) { - $state.go('home') - } else { - $scope.errorMessage = msg; - } - } -}()); \ No newline at end of file diff --git a/main/src/main/webapp/app/modules/core/controllers/FilesController.js b/main/src/main/webapp/app/modules/core/controllers/FilesController.js deleted file mode 100644 index d2959f35c..000000000 --- a/main/src/main/webapp/app/modules/core/controllers/FilesController.js +++ /dev/null @@ -1,125 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.core') - .controller('FilesController', FilesController); - - FilesController.$inject = ['$scope', 'Upload', 'paths', 'ToastService', 'SessionService', 'FileResource', '_']; - - /** - * The controller that manages files of a project handles file uploads - * - * Template: 'views/files.html' - * - * @param $scope - angular $scope - * @param Upload - ngFileUpload Upload service - * @param paths - The applications paths constant - * @param Toast - The ToastService - * @param Session - The SessionService - * @param FileResource - The Resource that handles API requests for files - * @param _ - Lodash - * @constructor - */ - function FilesController($scope, Upload, paths, Toast, Session, FileResource, _) { - - var project = Session.project.get(); - - /** - * All project related files - * @type {{name: string, project: number}[]} - */ - $scope.files = []; - - /** - * The selected files - * @type {{name: string, project: number}[]} - */ - $scope.selectedFiles = []; - - /** - * The progress in percent of the current uploading file - * @type {number} - */ - $scope.progress = 0; - - /** - * The list of files to upload - * @type {null|File[]} - */ - $scope.filesToUpload = null; - - (function init() { - FileResource.getAll(project.id) - .then(function (files) { - $scope.files = files; - }).catch(function () { - - }); - }()); - - /** - * Remove a single file from the server and the list - * - * @param {string} file - The name of the file to delete - */ - $scope.deleteFile = function (file) { - FileResource.delete(project.id, file) - .then(function () { - Toast.success('File "' + file.name + '" has been deleted'); - _.remove($scope.files, function (f) { - return f.name === file.name; - }); - }) - }; - - /** - * Upload all chosen files piece by piece and add successfully deleted files to the list - */ - $scope.upload = function () { - var error = false; - var countFiles = $scope.files.length; - - function next() { - $scope.progress = 0; - if ($scope.filesToUpload.length > 0) { - var file = $scope.filesToUpload[0]; - Upload.upload({ - url: paths.api.URL + '/projects/' + project.id + '/files', - file: file - }).progress(function (evt) { - $scope.progress = parseInt(100.0 * evt.loaded / evt.total); - }).success(function (data) { - $scope.filesToUpload.shift(); - $scope.files.push(data); - next(); - }).error(function () { - error = true; - $scope.filesToUpload.shift(); - next(); - }) - } else { - if ($scope.files.length === countFiles) { - Toast.danger('Upload failed

No file could be uploaded

'); - } else { - if (error) { - Toast.info('Some files could not be uploaded'); - } else { - Toast.success('All files uploaded successfully'); - } - } - } - } - - next(); - }; - - /** - * Batch delete selected files - * TODO: call batch resource function as soon as there is an endpoint for that - */ - $scope.deleteSelectedFiles = function () { - _.forEach($scope.selectedFiles, $scope.deleteFile); - } - } -}()); \ No newline at end of file diff --git a/main/src/main/webapp/app/modules/core/controllers/HomeController.js b/main/src/main/webapp/app/modules/core/controllers/HomeController.js deleted file mode 100644 index d0f98a130..000000000 --- a/main/src/main/webapp/app/modules/core/controllers/HomeController.js +++ /dev/null @@ -1,53 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.core') - .controller('HomeController', HomeController); - - HomeController.$inject = ['$scope', '$state', 'ProjectResource', 'SessionService']; - - /** - * The controller for the landing page. It lists the projects. - * - * Template: 'views/home.html' - * - * @param $scope - The controllers scope - * @param $state - The ui.router $state service - * @param ProjectResource - The API resource for projects - * @param Session - The SessionService - * @constructor - */ - function HomeController($scope, $state, ProjectResource, Session) { - - /** - * The list of all created projects - * @type {Project[]} - */ - $scope.projects = []; - - (function init() { - - // redirect to the project dash page if one is open - if (Session.project.get() !== null) { - $state.go('project'); - } - - // get all projects from the server - ProjectResource.getAll() - .then(function (projects) { - $scope.projects = projects; - }); - }()); - - /** - * Opens a project by saving it into the session and redirect to the projects dashboard. - * - * @param {Project} project - The project that should be saved in the sessionStorage - */ - $scope.openProject = function (project) { - Session.project.save(project); - $state.go('project'); - } - } -}()); \ No newline at end of file diff --git a/main/src/main/webapp/app/modules/core/controllers/LearnResultsCompareController.js b/main/src/main/webapp/app/modules/core/controllers/LearnResultsCompareController.js deleted file mode 100644 index 2b5972496..000000000 --- a/main/src/main/webapp/app/modules/core/controllers/LearnResultsCompareController.js +++ /dev/null @@ -1,119 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.core') - .controller('LearnResultsCompareController', LearnResultsCompareController); - - LearnResultsCompareController.$inject = [ - '$scope', '$timeout', '$stateParams', 'SessionService', 'LearnResultResource', '_', 'ErrorService' - ]; - - /** - * The controller that handles the page for displaying multiple complete learn results in a slide show. - * - * Template: 'views/learn-result-compare.html' - * - * @param $scope - The controllers $scope - * @param $timeout - angular $timeout service - * @param $stateParams - The state parameters - * @param Session - The session service - * @param LearnResultResource - The API resource for learn results - * @param _ - Lodash - * @param Error - The ErrorService - * @constructor - */ - function LearnResultsCompareController($scope, $timeout, $stateParams, Session, LearnResultResource, _, Error) { - - // the project that is saved in the session - var project = Session.project.get(); - - /** - * All final learn results from all tests that were made for a project - * @type {LearnResult[]} - */ - $scope.results = []; - - /** - * The list of active panels where each panel contains a complete learn result set - * @type {LearnResult[][]} - */ - $scope.panels = []; - - /** - * The list of layout settings for the current hypothesis that is shown in a panel - * @type {Object[]} - */ - $scope.layoutSettings = []; - - // load all final learn results of all test an then load the complete test results from the test numbers - // that are passed from the url in the panels - (function init() { - if (angular.isUndefined($stateParams.testNos)) { - Error.setErrorMessage("There are no test numbers defined in the URL"); - Error.goToErrorPage(); - } - LearnResultResource.getAllFinal(project.id) - .then(function (results) { - $scope.results = results; - return $stateParams.testNos; - }) - .then(loadComplete); - }()); - - /** - * Loads a complete learn result set from a test number in the panel with a given index - * - * @param {String} testNos - The test numbers as concatenated string, separated by a ',' - * @param {number} index - The index of the panel the complete learn result should be displayed in - */ - function loadComplete(testNos, index) { - LearnResultResource.getComplete(project.id, testNos.split(',')) - .then(function (completeResults) { - _.forEach(completeResults, function (result) { - if (angular.isUndefined(index)) { - $scope.panels.push(result); - } else { - $scope.panels[index] = result; - } - }); - }) - .catch(function (response) { - Error.setErrorMessage(response.data.message); - Error.goToErrorPage(); - }) - } - - /** - * Loads a complete learn result set from a learn result in the panel with a given index - * - * @param {LearnResult} result - The learn result whose complete set should be loaded in a panel - * @param {number} index - The index of the panel the complete set should be displayed in - */ - $scope.fillPanel = function (result, index) { - loadComplete(result.testNo + '', index); - }; - - /** - * Adds a new empty panel - */ - $scope.addPanel = function () { - $scope.panels.push(null) - }; - - /** - * Removes a panel by a given index - * @param {number} index - The index of the panel to remove - */ - $scope.closePanel = function (index) { - $scope.panels[index] = null; - $timeout(function () { - $scope.panels.splice(index, 1); - }, 0); - $timeout(function () { - window.dispatchEvent(new Event('resize')); - }, 100) - } - } - -}()); \ No newline at end of file diff --git a/main/src/main/webapp/app/modules/core/controllers/LearnResultsController.js b/main/src/main/webapp/app/modules/core/controllers/LearnResultsController.js deleted file mode 100644 index 2237d50ad..000000000 --- a/main/src/main/webapp/app/modules/core/controllers/LearnResultsController.js +++ /dev/null @@ -1,102 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.core') - .controller('LearnResultsController', LearnResultsController); - - LearnResultsController.$inject = [ - '$scope', '$state', 'SessionService', 'LearnResultResource', 'PromptService', 'ToastService', '_' - ]; - - /** - * The controller for listing all final test results. - * - * Template: 'views/learn-results.html' - * - * @param $scope - The controllers scope - * @param $state - The ui.router $state service - * @param Session - The SessionService - * @param LearnResultResource - The API resource for learn results - * @param PromptService - The service for prompts - * @param Toast - The ToastService - * @param _ - Lodash - * @constructor - */ - function LearnResultsController($scope, $state, Session, LearnResultResource, PromptService, Toast, _) { - - // The project that is saved in the session - var project = Session.project.get(); - - /** - * All final test results of a project - * @type {LearnResult[]} - */ - $scope.results = []; - - /** - * The test results the user selected - * @type {LearnResult[]} - */ - $scope.selectedResults = []; - - (function init() { - - // get all final test results - LearnResultResource.getAllFinal(project.id) - .then(function (results) { - $scope.results = results; - }); - }()); - - /** - * Deletes a test result from the server after prompting the user for confirmation - * - * @param {LearnResult} result - The test result that should be deleted - */ - $scope.deleteResult = function (result) { - PromptService.confirm("Do you want to permanently delete this result? Changes cannot be undone.") - .then(function () { - LearnResultResource.delete(result) - .then(function () { - Toast.success('Learn result for test ' + result.testNo + ' deleted'); - _.remove($scope.results, {testNo: result.testNo}); - }) - .catch(function (response) { - Toast.danger('

Result deletion failed

' + response.data.message); - }); - }) - }; - - /** - * Deletes selected test results from the server after prompting the user for confirmation - */ - $scope.deleteResults = function () { - if ($scope.selectedResults.length > 0) { - PromptService.confirm("Do you want to permanently delete theses results? Changes cannot be undone.") - .then(function () { - LearnResultResource.delete($scope.selectedResults) - .then(function () { - Toast.success('Learn results deleted'); - _.forEach($scope.selectedResults, function (result) { - _.remove($scope.results, {testNo: result.testNo}) - }) - }) - .catch(function (response) { - Toast.danger('

Result deletion failed

' + response.data.message); - }); - }) - } - }; - - /** - * Opens the learning result compare view with the selected results opened - */ - $scope.openSelectedResults = function () { - if ($scope.selectedResults.length > 0) { - var testNos = _.pluck($scope.selectedResults, 'testNo'); - $state.go('learn.results.compare', {testNos: testNos.join(',')}) - } - } - } -}()); diff --git a/main/src/main/webapp/app/modules/core/controllers/LearnResultsStatisticsController.js b/main/src/main/webapp/app/modules/core/controllers/LearnResultsStatisticsController.js deleted file mode 100644 index 729f1b322..000000000 --- a/main/src/main/webapp/app/modules/core/controllers/LearnResultsStatisticsController.js +++ /dev/null @@ -1,165 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.core') - .controller('LearnResultsStatisticsController', LearnResultsStatisticsController); - - LearnResultsStatisticsController.$inject = [ - '$scope', 'SessionService', 'LearnResultResource', 'LearnerResultChartService' - ]; - - /** - * The controller for the learn result statistics page. - * - * Template: 'views/learn-results-statistics.html'. - * - * @param $scope - The controllers scope - * @param Session - The SessionService - * @param LearnResultResource - The API resource for learn results - * @param LearnerResultChartService - * @constructor - */ - function LearnResultsStatisticsController($scope, Session, LearnResultResource, LearnerResultChartService) { - - // The project that is stored in the session - var project = Session.project.get(); - - /** - * The enum that indicates which kind of chart should be displayed - * @type {{MULTIPLE_FINAL: number, MULTIPLE_COMPLETE: number}} - */ - $scope.chartModes = { - MULTIPLE_FINAL: 0, - MULTIPLE_COMPLETE: 1 - }; - - /** - * The map that indicated from which property of a Learner Result a chart should be created - * @type {{RESETS: string, SYMBOLS: string, DURATION: string}} - */ - $scope.chartProperties = LearnerResultChartService.properties; - - /** - * All final Learn Results from the project - * @type {LearnResult[]} - */ - $scope.results = []; - - /** - * The list of selected learn results - * @type {LearnResult[]} - */ - $scope.selectedResults = []; - - /** - * The mode of the displayed chart - * @type {null|number} - */ - $scope.selectedChartMode = null; - - /** - * The property of a learner result that is displayed in the chart - * @type {string} - */ - $scope.selectedChartProperty = $scope.chartProperties.MQS; - - /** - * @type {boolean} - */ - $scope.fullWidth = false; - - /** - * The n3 chart data for the directive - * @type {{data: null|Array, options: null|{}}} - */ - $scope.chartData = { - data: null, - options: null - }; - - // initialize the controller - (function init() { - - // get all final learn results of the project - LearnResultResource.getAllFinal(project.id) - .then(function (results) { - $scope.results = results; - }); - }()); - - /** - * Sets the selected learner result property from which the chart data should be created. Calls the methods - * to create the chart data based on the selected chart mode. - * - * @param {number} property - The learner result property - */ - $scope.selectChartProperty = function (property) { - $scope.selectedChartProperty = property; - if ($scope.selectedChartMode === $scope.chartModes.MULTIPLE_FINAL) { - $scope.createChartFromFinalResults(); - } else if ($scope.selectedChartMode === $scope.chartModes.MULTIPLE_COMPLETE) { - $scope.createChartFromCompleteResults(); - } - }; - - /** - * Creates n3 line chart data from the selected final learner results and saves it into the scope. Sets the - * displayable chart mode to MULTIPLE_FINAL - */ - $scope.createChartFromFinalResults = function () { - var chartData; - - if ($scope.selectedResults.length > 0) { - chartData = - LearnerResultChartService - .createDataFromMultipleFinalResults($scope.selectedResults, $scope.selectedChartProperty); - - $scope.chartData = { - data: chartData.data, - options: chartData.options - }; - - $scope.selectedChartMode = $scope.chartModes.MULTIPLE_FINAL; - } - }; - - /** - * Creates n3 area chart data from the selected learner results. Therefore makes an API request to fetch the - * complete data from each selected learner result and saves the chart data into the scope. Sets the - * displayable chart mode to MULTIPLE_COMPLETE - */ - $scope.createChartFromCompleteResults = function () { - var chartData; - - if ($scope.selectedResults.length > 0) { - LearnResultResource.getComplete(project.id, _.pluck($scope.selectedResults, 'testNo')) - .then(function (completeResults) { - chartData = - LearnerResultChartService - .createDataFromMultipleCompleteResults(completeResults, $scope.selectedChartProperty); - - $scope.chartData = { - data: chartData.data, - options: chartData.options - }; - - $scope.selectedChartMode = $scope.chartModes.MULTIPLE_COMPLETE; - }) - } - }; - - /** - * Resets the chart data and removes the selected chart mode so that the chart disappears and the list of - * learner results will be shown again - */ - $scope.back = function () { - $scope.selectedChartMode = null; - $scope.chartData = { - data: null, - options: null - }; - $scope.fullWidth = false; - } - } -}()); \ No newline at end of file diff --git a/main/src/main/webapp/app/modules/core/controllers/LearnSetupController.js b/main/src/main/webapp/app/modules/core/controllers/LearnSetupController.js deleted file mode 100644 index a4e4d1b6d..000000000 --- a/main/src/main/webapp/app/modules/core/controllers/LearnSetupController.js +++ /dev/null @@ -1,178 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.core') - .controller('LearnSetupController', LearnSetupController); - - LearnSetupController.$inject = [ - '$scope', '$state', 'SymbolGroupResource', 'SessionService', 'LearnConfiguration', 'LearnerService', - 'ToastService', '_', 'LearnResultResource' - ]; - - /** - * The controller that handles the preparation of a learn process. Lists all symbol groups and its visible symbols. - * - * Template 'views/learn-setup.html' - * - * @param $scope - The controllers scope - * @param $state - The ui.router $state service - * @param SymbolGroupResource - The API resource for symbol groups - * @param Session - The SessionService - * @param LearnConfiguration - * @param Learner - The API service for the learner - * @param Toast - The ToastService - * @param _ - Lodash - * @param LearnResultResource - The API resource for learn results - * @constructor - */ - function LearnSetupController($scope, $state, SymbolGroupResource, Session, LearnConfiguration, Learner, Toast, _, - LearnResultResource) { - - // the project that is stored in the session - var project = Session.project.get(); - - /** - * All symbol groups that belong the the sessions project - * @type {SymbolGroup[]} - */ - $scope.groups = []; - - /** - * The learn results of previous learn processes - * @type {learnResult[]} - */ - $scope.learnResults = []; - - /** - * A list of all symbols of all groups that is used in order to select them - * @type {Symbol[]} - */ - $scope.allSymbols = []; - - /** - * The configuration that is send to the server for learning - * @type {LearnConfiguration} - */ - $scope.learnConfiguration = new LearnConfiguration(); - - /** - * The symbol that should be used as a reset symbol - * @type {Symbol|null} - */ - $scope.resetSymbol = null; - - /** - * Indicates whether there is a learning process that can be continued (the last one) - * @type {boolean} - */ - $scope.canContinueLearnProcess = false; - - (function init() { - - // make sure that there isn't any other learn process active - // redirect to the load screen in case there is an active one - Learner.isActive() - .then(function (data) { - if (data.active) { - if (data.project == project.id) { - Toast.info('There is currently running a learn process.'); - $state.go('learn.start'); - } else { - Toast.danger('There is already running a test from another project.'); - $state.go('project') - } - } else { - - // load all symbols in case there isn't any active learning process - SymbolGroupResource.getAll(project.id, {embedSymbols: true}) - .then(function (groups) { - $scope.groups = groups; - $scope.allSymbols = _.flatten(_.pluck($scope.groups, 'symbols')); - }); - - // load learn results so that their configuration can be reused - LearnResultResource.getAllFinal(project.id) - .then(function (learnResults) { - $scope.learnResults = learnResults; - }) - } - }); - - // get the status to check if there is a learn process that can be continued - Learner.getStatus() - .then(function (data) { - $scope.canContinueLearnProcess = data !== null; - }); - }()); - - /** - * Sets the reset symbol - * - * @param {Symbol} symbol - The symbol that will be used to reset the sul - */ - $scope.setResetSymbol = function (symbol) { - $scope.resetSymbol = symbol; - }; - - /** - * Starts the learning process if symbols are selected and a reset symbol is defined. Redirects to the - * learning load screen on success. - */ - $scope.startLearning = function () { - var selectedSymbols; - - if ($scope.resetSymbol === null) { - Toast.danger('You must selected a reset symbol in order to start learning!'); - return; - } - - selectedSymbols = _.filter($scope.allSymbols, '_selected'); - - if (selectedSymbols.length > 0) { - _.forEach(selectedSymbols, function (symbol) { - $scope.learnConfiguration.addSymbol(symbol) - }); - - $scope.learnConfiguration.setResetSymbol($scope.resetSymbol); - - Learner.start(project.id, $scope.learnConfiguration) - .success(function () { - Toast.success('Learn process started successfully.'); - $state.go('learn.start'); - }) - .catch(function (response) { - Toast.danger('

Start learning failed

' + response.data.message); - }); - } else { - Toast.danger('You must at least select one symbol to start learning'); - } - }; - - $scope.reuseConfigurationFromResult = function (result) { - var config = result.configuration; - $scope.learnConfiguration.algorithm = config.algorithm; - $scope.learnConfiguration.eqOracle = config.eqOracle; - $scope.learnConfiguration.maxAmountOfStepsToLearn = config.maxAmountOfStepsToLearn; - - var ids = _.pluck(config.symbols, 'id'); - _.forEach($scope.groups, function (group) { - _.forEach(group.symbols, function (symbol) { - symbol._selected = _.indexOf(ids, symbol.id) > -1; - if (symbol.id === config.resetSymbol.id) { - $scope.resetSymbol = symbol; - } - }) - }) - }; - - /** - * Updates the learn configuration - * - * @param {LearnConfiguration} config - */ - $scope.updateLearnConfiguration = function (config) { - $scope.learnConfiguration = config; - }; - } -}()); \ No newline at end of file diff --git a/main/src/main/webapp/app/modules/core/controllers/LearnStartController.js b/main/src/main/webapp/app/modules/core/controllers/LearnStartController.js deleted file mode 100644 index 8e5ae7487..000000000 --- a/main/src/main/webapp/app/modules/core/controllers/LearnStartController.js +++ /dev/null @@ -1,167 +0,0 @@ -(function () { - - angular - .module('ALEX.core') - .controller('LearnStartController', LearnStartController); - - LearnStartController.$inject = [ - '$scope', '$interval', 'SessionService', 'LearnerService', 'LearnResultResource', 'ToastService', '_', - 'ErrorService', 'LearnConfiguration', 'CounterExampleService' - ]; - - /** - * The controller for showing a load screen during the learning and shows all learn results from the current test - * in the intermediate steps. - * - * Template: 'views/learn-start.html' - * - * @param $scope - The controllers scope - * @param $interval - The angular $interval service - * @param Session - The SessionService - * @param Learner - The API service for the learner - * @param LearnResultResource - The API resource for learn results - * @param Toast - The ToastService - * @param _ - Lodash - * @param Error - The ErrorService - * @param LearnConfiguration - * @param CounterExampleService - The service that contains the current counterexample - * @constructor - */ - function LearnStartController($scope, $interval, Session, Learner, LearnResultResource, Toast, _, Error, - LearnConfiguration, CounterExampleService) { - - // The project that is stored in the session - var project = Session.project.get(); - - // The interval object - var interval = null; - - // The time for the polling interval in ms - var intervalTime = 5000; - - /** - * The complete learn result until the most recent learned one - * @type {LearnResult[]} - */ - $scope.results = []; - - /** - * Indicates if polling the server for a test result is still active - * @type {boolean} - */ - $scope.active = false; - - /** - * Flag for showing or hiding the sidebar - * @type {boolean} - */ - $scope.showSidebar = false; - - /** - * The amount of executed MQs in the active learn process - * @type {number} - */ - $scope.mqsUsed; - - /** - * The time it took to learn - * @type {number} - */ - $scope.duration = 0; - - // initialize the controller - (function init() { - CounterExampleService.resetCurrentCounterexample(); - poll(); - - // stop polling when you leave the page - $scope.$on("$destroy", function () { - $interval.cancel(interval); - }); - }()); - - /** - * Checks every x seconds if the server has finished learning and sets the test if he did - */ - function poll() { - $scope.active = true; - interval = $interval(function () { - Learner.isActive() - .then(function (data) { - if (angular.isDefined(data.mqsUsed)) { - $scope.mqsUsed = data.mqsUsed; - } - - if (!data.active) { - Learner.getStatus().then(function (result) { - if (result.error) { - Error.setErrorMessage(result.errorText); - Error.goToErrorPage(); - } else { - loadComplete(result); - } - }); - $interval.cancel(interval); - $scope.active = false; - } - - if (data.statistics) { - $scope.mqsUsed = data.statistics.mqsUsed; - $scope.duration = Date.now() - data.statistics.startTime; - } - }); - }, intervalTime); - - // load the complete set of steps for the learn result - function loadComplete(result) { - LearnResultResource.getComplete(project.id, result.testNo) - .then(function (results) { - $scope.results = results; - }); - } - } - - /** - * Update the configuration for the continuing test when choosing eqOracle 'sample' and showing an intermediate - * hypothesis - * - * @param {LearnConfiguration} config - */ - $scope.updateLearnConfiguration = function (config) { - $scope.test.configuration = config; - }; - - /** - * Tell the server to continue learning with the new or old learn configuration when eqOracle type was 'sample' - */ - $scope.resumeLearning = function () { - var config = LearnConfiguration.build(_.last($scope.results).configuration).toLearnResumeConfiguration(); - - Learner.resume(project.id, _.last($scope.results).testNo, config) - .then(function () { - CounterExampleService.resetCurrentCounterexample(); - poll(); - }) - .catch(function (response) { - Toast.danger('

Resume learning failed!

' + response.data.message); - }) - }; - - /** - * Tell the learner to stop learning at the next possible time, when the next hypothesis is generated - */ - $scope.abort = function () { - if ($scope.active) { - Toast.info('The learner will stop with the next hypothesis'); - Learner.stop() - } - }; - - /** - * Shows or hides the sidebar - */ - $scope.toggleSidebar = function () { - $scope.showSidebar = !$scope.showSidebar; - }; - } -}()); diff --git a/main/src/main/webapp/app/modules/core/controllers/ProjectController.js b/main/src/main/webapp/app/modules/core/controllers/ProjectController.js deleted file mode 100644 index 07128fb96..000000000 --- a/main/src/main/webapp/app/modules/core/controllers/ProjectController.js +++ /dev/null @@ -1,22 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.core') - .controller('ProjectController', ProjectController); - - ProjectController.$inject = ['$scope', 'SessionService']; - - /** - * The controller that is responsible for the site '/project' and shows the dashboard of the project - * - * @param $scope - * @param SessionService - * @constructor - */ - function ProjectController($scope, SessionService) { - - /** The project that is stored in the sessionStorage **/ - $scope.project = SessionService.project.get(); - } -}()); diff --git a/main/src/main/webapp/app/modules/core/controllers/ProjectCreateController.js b/main/src/main/webapp/app/modules/core/controllers/ProjectCreateController.js deleted file mode 100644 index f5faa7517..000000000 --- a/main/src/main/webapp/app/modules/core/controllers/ProjectCreateController.js +++ /dev/null @@ -1,55 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.core') - .controller('ProjectCreateController', ProjectCreateController); - - ProjectCreateController.$inject = ['$scope', '$state', 'Project', 'ProjectResource', 'ToastService']; - - /** - * The controller that shows the form for creating new projects - * - * Template: 'views/project-create.html' - * - * @param $scope - The controllers scope - * @param $state - The ui.router $state service - * @param Project - The factory for Project objects - * @param ProjectResource - The API resource for projects - * @param Toast - The ToastService - * @constructor - */ - function ProjectCreateController($scope, $state, Project, ProjectResource, Toast) { - - /** - * The model for the new project - * @type {Project} - */ - $scope.project = new Project(); - - /** - * Make a call to the API to create a new project - */ - $scope.createProject = function () { - ProjectResource.create($scope.project) - .then(function (createdProject) { - Toast.success('Project "' + createdProject.name + '" created'); - $state.go('home'); - }) - .catch(function (response) { - Toast.danger('

Creation of project failed

' + response.data.message) - }) - }; - - $scope.fileLoaded = function (json) { - var project = angular.fromJson(json); - if (!project.name || !project.baseUrl) { - Toast.danger('The json file does not seem to be a valid project'); - } else { - $scope.project = project; - Toast.success('Project loaded from file') - } - $scope.$apply(); - } - } -}()); \ No newline at end of file diff --git a/main/src/main/webapp/app/modules/core/controllers/ProjectSettingsController.js b/main/src/main/webapp/app/modules/core/controllers/ProjectSettingsController.js deleted file mode 100644 index bbc893ef3..000000000 --- a/main/src/main/webapp/app/modules/core/controllers/ProjectSettingsController.js +++ /dev/null @@ -1,116 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.core') - .controller('ProjectSettingsController', ProjectSettingsController); - - ProjectSettingsController.$inject = [ - '$scope', '$state', 'ProjectResource', 'SessionService', 'PromptService', 'ToastService', 'LearnerService', - 'SymbolGroupResource', 'FileDownloadService', '_' - ]; - - /** - * The controller that handles the deleting and updating of a project. The page cannot be requested if the learner - * is actively learning the current project. Therefore it redirects to the projects dashboard. - * - * Template: '/views/project-settings.html' - * - * @param $scope - The controllers scope - * @param $state - The ui.router $state service - * @param ProjectResource - Project API Resource handler - * @param Session - The SessionService - * @param PromptService - The PromptService - * @param Toast - The ToastService - * @param Learner - The LearnerService for the API - * @param SymbolGroupResource - The SymbolGroupResource - * @param FileDownloadService - The FileDownloadService - * @param _ - Lodash - */ - function ProjectSettingsController($scope, $state, ProjectResource, Session, PromptService, Toast, Learner, - SymbolGroupResource, FileDownloadService, _) { - - /** - * The project that is stored in the session - * @type {Project} - **/ - $scope.project = Session.project.get(); - - (function init() { - - // check if the current project is used in learning and abort deletion - // because of unknown side effects - Learner.isActive() - .then(function (data) { - if (data.active && data.project === $scope.project.id) { - Toast.info('Cannot edit the project. A learning process is still active.'); - $state.go('project'); - } - }); - }()); - - /** - * Updates a project and saves the updated project in the sessionStorage - */ - $scope.updateProject = function () { - ProjectResource.update($scope.project) - .then(function (updatedProject) { - Toast.success('Project updated'); - Session.project.save(updatedProject); - }) - .catch(function () { - Toast.danger('

Project update failed!

The project seems to exists already.'); - }) - }; - - /** - * Prompts the user for confirmation and deletes the project on success. Redirects to '/home' when project - * was deleted and removes the project from the sessionStorage. - */ - $scope.deleteProject = function () { - var message = 'Do you really want to delete this project with all its symbols and test results? This process can not be undone.'; - PromptService.confirm(message) - .then(function () { - ProjectResource.delete($scope.project) - .then(function () { - Toast.success('Project ' + $scope.project.name + ' deleted'); - Session.project.remove(); - $state.go('home'); - }) - .catch(function (response) { - Toast.danger('

Deleting project failed

' + response.data.message); - }) - }) - }; - - /** - * Saves the project including symbol groups into a json file - */ - $scope.exportProject = function () { - SymbolGroupResource.getAll($scope.project.id, {embedSymbols: true}) - .then(function(groups){ - - var projectToExport = angular.copy($scope.project); - projectToExport.groups = groups; - - // prepare project for export - delete projectToExport.id; - _.forEach(projectToExport.groups, function(group){ - delete group.id; - delete group.project; - _.forEach(group.symbols, function(symbol){ - delete symbol.project; - delete symbol.group; - delete symbol.id; - delete symbol.revision; - }) - }); - - FileDownloadService.downloadJson(projectToExport) - .then(function(){ - Toast.success('Project exported'); - }); - }) - } - } -}()); \ No newline at end of file diff --git a/main/src/main/webapp/app/modules/core/controllers/SymbolsActionsController.js b/main/src/main/webapp/app/modules/core/controllers/SymbolsActionsController.js deleted file mode 100644 index 5e185da43..000000000 --- a/main/src/main/webapp/app/modules/core/controllers/SymbolsActionsController.js +++ /dev/null @@ -1,241 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.core') - .controller('SymbolsActionsController', SymbolsActionsController); - - SymbolsActionsController.$inject = [ - '$scope', '$stateParams', 'Symbol', 'SymbolResource', 'SessionService', 'ToastService', 'ErrorService', '_', - 'ActionBuilder', 'ClipboardService', '$state', 'PromptService' - ]; - - /** - * The controller that handles the page for managing all actions of a symbol. The symbol whose actions should be - * manages has to be defined in the url by its id. The URL /symbols/4/actions therefore manages the actions of the - * symbol with id 4. When no such id was found, the controller redirects to an error page. - * - * Template 'views/symbols-actions.html'. - * - * @param $scope - The controllers scope - * @param $stateParams - The parameters of the state - * @param Symbol - The factory for Symbol objects - * @param SymbolResource - The Symbol model - * @param Session - The session service - * @param Toast - The ToastService - * @param Error - The ErrorService - * @param _ - Lodash - * @param ActionBuilder - The ActionBuilder - * @param Clipboard - The ClipboardService - * @param $state - ui.router $state - * @param Prompt - PromptService - * @constructor - */ - function SymbolsActionsController($scope, $stateParams, Symbol, SymbolResource, Session, Toast, Error, _, - ActionBuilder, Clipboard, $state, Prompt) { - - /** - * A copy of $scope.symbol to revert unsaved changes - * @type {Symbol|null} - */ - var symbolCopy = null; - - /** - * The project that is stored in the session - * @type {Project} - */ - var project = Session.project.get(); - - /** - * The symbol whose actions are managed - * @type {Symbol|null} - */ - $scope.symbol = null; - - /** - * A map where actions can save temporary key value pairs - * @type {{}} - */ - $scope.map = {}; - - /** - * The list of selected actions - * @type {Object[]} - */ - $scope.selectedActions = []; - - /** - * Whether there are unsaved changes to the symbol - * @type {boolean} - */ - $scope.hasChanged = false; - - /** - * Options for ng-sortable directive from Sortable lib - * @type {{animation: number, onUpdate: Function}} - */ - $scope.sortableOptions = { - animation: 150, - onUpdate: function () { - setChanged(true); - } - }; - - /** - * Sets the flag that indicates if the symbol or its actions have changed - * - * @param {boolean} b - If the symbol has changed by any means - */ - function setChanged(b) { - $scope.hasChanged = b; - } - - // load all actions from the symbol - // redirect to an error page when the symbol from the url id cannot be found - (function init() { - SymbolResource.get(project.id, $stateParams.symbolId) - .then(function (symbol) { - - // create unique ids for actions so that they can be found - _.forEach(symbol.actions, function (action) { - action._id = _.uniqueId(); - }); - - // add symbol to scope and create a copy in order to revert changes - $scope.symbol = symbol; - $scope.symbolCopy = Symbol.build(symbol); - }) - .catch(function () { - Error.setErrorMessage('The symbol with the ID "' + $stateParams.symbolId + "' could not be found"); - Error.goToErrorPage(); - }); - - // show a confirm dialog if the user leaves the page without having saved changes and - // redirect to the state that the user was about to go to if he doesn't want to save changes - var offHandler = $scope.$on('$stateChangeStart', function (event, toState) { - if ($scope.hasChanged) { - event.preventDefault(); - Prompt.confirm('There are unsaved changes. Do you still want to continue and discard them?') - .then(function () { - offHandler(); - $state.go(toState); - }) - } - }); - }()); - - /** - * Deletes a list of actions - * - * @param {Object[]} actions - The actions to be deleted - */ - $scope.deleteActions = function (actions) { - if (actions.length > 0) { - _.forEach(actions, function (a) { - _.remove($scope.symbol.actions, {_id: a._id}); - _.remove($scope.selectedActions, {id: a._id}); - }); - Toast.success('Action' + (actions.length > 1 ? 's' : '') + ' deleted'); - setChanged(true); - } - }; - - /** - * Adds a new action to the list of actions of the symbol and gives it a temporary unique id - * - * @param {Object} action - */ - $scope.addAction = function (action) { - action._id = _.uniqueId(); - $scope.symbol.actions.push(action); - Toast.success('Action created'); - setChanged(true); - $scope.map = {}; - }; - - /** - * Updates an existing action - * - * @param {Object} updatedAction - */ - $scope.updateAction = function (updatedAction) { - var action = _.find($scope.symbol.actions, {_id: updatedAction._id}); - _.forIn(action, function (v, k) { - action[k] = updatedAction[k]; - }); - Toast.success('Action updated'); - setChanged(true); - $scope.map = {}; - }; - - /** - * Saves the changes that were made to the symbol by updating it on the server. - */ - $scope.saveChanges = function () { - - // update the copy for later reverting - var copy = Symbol.build($scope.symbol); - _.forEach(copy.actions, function (a) { - delete a._id; - delete a._selected; - }); - - // update the symbol - SymbolResource.update(copy) - .then(function (updatedSymbol) { - $scope.symbol.revision = updatedSymbol.revision; - symbolCopy = Symbol.build($scope.symbol); - Toast.success('Symbol ' + updatedSymbol.name + ' updated'); - setChanged(false); - $scope.hasUnsavedChanges = false; - }) - .catch(function (response) { - Toast.danger('

Error updating symbol

' + response.data.message); - }) - }; - - /** - * Copies actions to the clipboard - * - * @param {Object[]} actions - */ - $scope.copyActions = function (actions) { - Clipboard.copy('actions', angular.copy(actions)); - Toast.info(actions.length + ' action[s] copied to clipboard'); - }; - - /** - * Copies actions to the clipboard and removes them from the scope - * - * @param {Object[]} actions - */ - $scope.cutActions = function (actions) { - Clipboard.cut('actions', angular.copy(actions)); - $scope.deleteActions(actions); - Toast.info(actions.length + ' action[s] cut to clipboard'); - setChanged(true); - }; - - /** - * Pastes the actions from the clipboard to the end of of the action list - */ - $scope.pasteActions = function () { - var actions = Clipboard.paste('actions'); - if (actions !== null) { - _.forEach(ActionBuilder.createFromObjects(actions), $scope.addAction); - Toast.info(actions.length + 'action[s] pasted from clipboard'); - setChanged(true); - } - }; - - /** - * Toggles the disabled flag on an action - * - * @param {Object} action - */ - $scope.toggleDisableAction = function (action) { - action.disabled = !action.disabled; - setChanged(true); - } - } -}()); \ No newline at end of file diff --git a/main/src/main/webapp/app/modules/core/controllers/SymbolsController.js b/main/src/main/webapp/app/modules/core/controllers/SymbolsController.js deleted file mode 100644 index 0e5cedb94..000000000 --- a/main/src/main/webapp/app/modules/core/controllers/SymbolsController.js +++ /dev/null @@ -1,264 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.core') - .controller('SymbolsController', SymbolsController); - - SymbolsController.$inject = [ - '$scope', 'SessionService', 'Symbol', 'SymbolResource', 'SymbolGroupResource', '_', 'ToastService', - 'FileDownloadService' - ]; - - /** - * The controller that handles CRUD operations on symbols and symbol groups. - * - * Template 'views/symbols.html'. - * - * @param $scope - angular scope object - * @param Session - The SessionService - * @param Symbol - The Symbol factory - * @param SymbolResource - The Symbol API Resource handler - * @param SymbolGroupResource - The SymbolGroup factory - * @param _ - Lodash - * @param Toast - The ToastService - * @param FileDownloadService - The FileDownloadService - * @constructor - */ - function SymbolsController($scope, Session, Symbol, SymbolResource, SymbolGroupResource, _, Toast, - FileDownloadService) { - - /** - * The project that is saved in the session - * @type {Project} - */ - $scope.project = Session.project.get(); - - /** - * indicates if symbol groups are displayed collapsed - * @type {boolean} - */ - $scope.groupsCollapsed = false; - - /** - * The model for selected symbols - * @type {Symbol[]} - */ - $scope.selectedSymbols = []; - - /** - * The symbol groups that belong to the project - * @type {SymbolGroup[]} - */ - $scope.groups = []; - - // fetch all symbol groups and include all symbols - (function init() { - SymbolGroupResource.getAll($scope.project.id, {embedSymbols: true}) - .then(function (groups) { - $scope.groups = groups; - }) - }()); - - /** - * Finds the symbol group object from a given symbol. Returns undefined if no symbol group was found. - * - * @param symbol - The symbol whose group object should be found - * @returns {SymbolGroup|undefined} - The found symbol group or undefined - */ - function findGroupFromSymbol(symbol) { - return _.find($scope.groups, {id: symbol.group}); - } - - /** - * Extracts all symbols from all symbol groups and merges them into a single array - * - * @returns {Symbol[]} - */ - $scope.getAllSymbols = function () { - return _.flatten(_.pluck($scope.groups, 'symbols')); - }; - - /** - * Adds a single new symbol to the scope by finding its corresponding group and adding it there - * - * @param {Symbol} symbol - The symbol that should be added - */ - $scope.addSymbol = function (symbol) { - findGroupFromSymbol(symbol).symbols.push(symbol) - }; - - /** - * Removes a list of symbols from the scope by finding the group of each symbol and removing it from - * it - * - * @param {Symbol[]} symbols - The symbols that should be removed - */ - $scope.removeSymbols = function (symbols) { - var group; - _.forEach(symbols, function (symbol) { - //delete symbol._selected; - //delete symbol._collapsed; - group = findGroupFromSymbol(symbol); - _.remove(group.symbols, {id: symbol.id}); - }) - }; - - /** - * Updates an existing symbol - * - * @param {Symbol} updatedSymbol - The updated symbol object - */ - $scope.updateSymbol = function (updatedSymbol) { - $scope.updateSymbols([updatedSymbol]); - }; - - /** - * Updates multiple existing symbols - * - * @param {Symbol[]} updatedSymbols - The updated symbol objects - */ - $scope.updateSymbols = function (updatedSymbols) { - var group; - var i; - _.forEach(updatedSymbols, function (symbol) { - group = findGroupFromSymbol(symbol); - i = _.findIndex(group.symbols, {id: symbol.id}); - if (i > -1) { - group.symbols[i].name = symbol.name; - group.symbols[i].abbreviation = symbol.abbreviation; - group.symbols[i].group = symbol.group; - group.symbols[i].revision = symbol.revision; - } - }) - }; - - /** - * Moves a list of existing symbols into another group. - * - * @param {Symbol[]} symbols - The symbols that should be moved - * @param {SymbolGroup} group - The group the symbols should be moved into - */ - $scope.moveSymbolsToGroup = function (symbols, group) { - var grp = _.find($scope.groups, {id: group.id}); - - _.forEach(symbols, function (symbol) { - var g = _.find($scope.groups, {id: symbol.group}); - var i = _.findIndex(g.symbols, {id: symbol.id}); - g.symbols.splice(i, 1); - symbol.group = group.id; - grp.symbols.push(symbol); - }) - }; - - /** - * Deletes a single symbol from the server and from the scope if the deletion was successful - * - * @param {Symbol} symbol - The symbol to be deleted - */ - $scope.deleteSymbol = function (symbol) { - SymbolResource.delete(symbol) - .success(function () { - Toast.success('Symbol ' + symbol.name + ' deleted'); - $scope.removeSymbols([symbol]); - }) - .catch(function (response) { - Toast.danger('

Deleting symbol failed

' + response.data.message); - }) - }; - - /** - * Deletes all symbols that the user selected from the server and the scope, if the deletion was successful - */ - $scope.deleteSelectedSymbols = function () { - SymbolResource.delete($scope.selectedSymbols) - .success(function () { - Toast.success('Symbols deleted'); - $scope.removeSymbols($scope.selectedSymbols); - }) - .catch(function (response) { - Toast.danger('

Deleting symbols failed

' + response.data.message); - }) - }; - - /** - * Adds a new symbol group to the scope - * - * @param {SymbolGroup} group - The group that should be added - */ - $scope.addGroup = function (group) { - $scope.groups.push(group); - }; - - /** - * Updates a symbol group in the scope by changing its name property to the one of the groups that is passed - * as a parameter - * - * @param {SymbolGroup} updatedGroup - The updated symbol group - */ - $scope.updateGroup = function (updatedGroup) { - _.find($scope.groups, {id: updatedGroup.id}).name = updatedGroup.name; - }; - - /** - * Removes a symbol group from the scope and also removes its symbols - * - * @param {SymbolGroup} group - */ - $scope.deleteGroup = function (group) { - console.log(group.id); - $scope.removeSymbols(group.symbols); - _.remove($scope.groups, {id: group.id}); - }; - - /** - * Collapses all groups or expands them - */ - $scope.toggleCollapseAllGroups = function () { - $scope.groupsCollapsed = !$scope.groupsCollapsed; - for (var i = 0; i < $scope.groups.length; i++) { - $scope.groups[i]._collapsed = $scope.groupsCollapsed; - } - }; - - /** - * Deletes all properties that are not needed for downloading symbols which are the id, revision, project, group - * and hidden properties. They are removed so that they can later be uploaded and created like new symbols. - */ - $scope.exportSelectedSymbols = function () { - if ($scope.selectedSymbols.length > 0) { - var symbols = _(angular.copy($scope.selectedSymbols)) - .sortBy(function (symbol) { - return symbol.id; - }).value(); - - _.forEach(symbols, function (symbol) { - _.forEach(symbol.actions, function (action) { - if (action.type === 'executeSymbol') { - action.symbolToExecute.revision = 1; - _.forEach(symbols, function (s, j) { - if (s.id === action.symbolToExecute.id) { - action.symbolToExecute.id = j + 1; - } - }) - } - }); - delete symbol._selected; - delete symbol._collapsed; - delete symbol.revision; - delete symbol.project; - delete symbol.group; - delete symbol.hidden; - delete symbol.id; - }); - - FileDownloadService.downloadJson(symbols) - .then(function () { - Toast.success('Symbols exported') - }) - } else { - Toast.info('Select symbols you want to export') - } - } - } -}()); \ No newline at end of file diff --git a/main/src/main/webapp/app/modules/core/controllers/SymbolsHistoryController.js b/main/src/main/webapp/app/modules/core/controllers/SymbolsHistoryController.js deleted file mode 100644 index d1511637b..000000000 --- a/main/src/main/webapp/app/modules/core/controllers/SymbolsHistoryController.js +++ /dev/null @@ -1,83 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.core') - .controller('SymbolsHistoryController', SymbolsHistoryController); - - SymbolsHistoryController.$inject = [ - '$scope', '$stateParams', 'Symbol', 'SymbolResource', 'SessionService', 'ToastService', 'ErrorService' - ]; - - /** - * The controller for the page where the revision history if a symbol is listed and old revisions can be restored - * - * Template: 'views/symbols-history.html' - * - * @param $scope - The controllers scope - * @param $stateParams - The ui.router $stateParams service - * @param Symbol - The factory for Symbol objects - * @param SymbolResource - The factory for the Symbol model - * @param Session - The SessionService - * @param Toast - The ToastService - * @param Error - The ErrorService - * @constructor - */ - function SymbolsHistoryController($scope, $stateParams, Symbol, SymbolResource, Session, Toast, Error) { - - // The project in the session - var project = Session.project.get(); - - /** - * All revisions of a symbol - * @type {Symbol[]} - */ - $scope.revisions = []; - - /** - * The most current version of a symbol - * @type {Symbol} - */ - $scope.latestRevision = null; - - // init controller - (function init() { - - // load all revisions of the symbol whose id is passed in the URL - SymbolResource.getRevisions(project.id, $stateParams.symbolId) - .then(function (revisions) { - $scope.latestRevision = revisions.pop(); - $scope.revisions = revisions; - }) - .catch(function () { - Error.setErrorMessage('The symbol with the ID "' + $stateParams.symbolId + '" could not be found'); - Error.goToErrorPage(); - }) - }()); - - /** - * Restores a previous revision of a symbol by updating the latest with the properties of the revision - * - * @param {Symbol} revision - The revision of the symbol that should be restored - */ - $scope.restoreRevision = function (revision) { - var symbol = Symbol.build($scope.latestRevision); - - // copy all important properties from the revision to the latest - symbol.name = revision.name; - symbol.abbreviation = revision.abbreviation; - symbol.actions = revision.actions; - - // update symbol with new properties - SymbolResource.update(symbol) - .then(function (updatedSymbol) { - Toast.success('Updated symbol to revision ' + revision.revision + ''); - $scope.revisions.unshift($scope.latestRevision); - $scope.latestRevision = updatedSymbol; - }) - .catch(function (response) { - Toast.danger('

Update to revision failed

' + response.data.message); - }) - } - } -}()); \ No newline at end of file diff --git a/main/src/main/webapp/app/modules/core/controllers/SymbolsImportController.js b/main/src/main/webapp/app/modules/core/controllers/SymbolsImportController.js deleted file mode 100644 index c0d6450ac..000000000 --- a/main/src/main/webapp/app/modules/core/controllers/SymbolsImportController.js +++ /dev/null @@ -1,138 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.core') - .controller('SymbolsImportController', SymbolsImportController); - - SymbolsImportController.$inject = [ - '$scope', 'SessionService', 'Symbol', 'SymbolResource', '_', 'ToastService', 'actionTypes', 'actionGroupTypes', - ]; - - /** - * The controller that handles the import of symbols from a *.json file. - * - * Template: 'views/symbols-import.html' - * - * @param $scope - The controllers scope - * @param Session - The SessionService - * @param Symbol - The factory for Symbols - * @param SymbolResource - The Symbol API Resource handler - * @param _ - Lodash - * @param Toast - The ToastService - * @param actionTypes - The dictionary with action types - * @param actionGroupTypes - The enum with action group types - * @constructor - */ - function SymbolsImportController($scope, Session, Symbol, SymbolResource, _, Toast, actionTypes, actionGroupTypes) { - - // The project that is saved in the sessionStorage - var project = Session.project.get(); - - /** - * The symbols that will be uploaded - * @type {Symbol[]} - */ - $scope.symbols = []; - - /** - * The list of selected symbols - * @type {Symbol[]} - */ - $scope.selectedSymbols = []; - - /** - * If references to symbol ids from executeSymbol actions should be adjusted or not - * @type {boolean} - */ - $scope.adjustReferences = true; - - /** - * Creates instances of Symbols from the json string from the *.json file and puts them in the scope. - * - * @param {string} data - The json string of loaded symbols - */ - $scope.fileLoaded = function (data) { - try { - $scope.$apply(function () { - $scope.symbols = _.map(angular.fromJson(data), Symbol.build); - }); - } catch (e) { - Toast.danger('

Loading json file failed

' + e); - } - }; - - /** - * Makes an API request in order to create the selected symbols. Removes successfully created symbols from the - * scope. - */ - $scope.uploadSelectedSymbols = function () { - if ($scope.selectedSymbols.length > 0) { - SymbolResource.getAll(project.id) - .then(function (existingSymbols) { - var maxId = _.max(existingSymbols, 'id').id; - var symbols = _($scope.selectedSymbols) - .map(Symbol.build) - .forEach(function (symbol) { - delete symbol._collapsed; - delete symbol._selected; - - // search in all actions of all symbols for an action with the type EXECUTE_SYMBOL and - // adjust referenced ids according to the max. existing id - if (existingSymbols.length > 0 && $scope.adjustReferences) { - _.forEach(symbol.actions, function (action) { - if (action.type === actionTypes[actionGroupTypes.GENERAL].EXECUTE_SYMBOL) { - action.symbolToExecute.id += maxId; - } - }) - } - }) - .value(); - SymbolResource.create(project.id, symbols) - .then(function (createdSymbols) { - Toast.success('Symbols uploaded'); - _.forEach(createdSymbols, function (symbol) { - _.remove($scope.symbols, {name: symbol.name}) - }) - }) - .catch(function (response) { - Toast.danger('

Symbol upload failed

' + response.data.message) - }) - }); - } - }; - - /** - * Changes the name and/or the abbreviation a symbol before uploading it to prevent naming conflicts in the - * database. - * - * @param {Symbol} updatedSymbol - The updated symbol - * @param {Symbol} oldSymbol - The old symbol - */ - $scope.updateSymbol = function (updatedSymbol, oldSymbol) { - var symbol; - - // check whether name or abbreviation already exist and don't update symbol - if (angular.equals(updatedSymbol, oldSymbol)) { - return; - } else if (updatedSymbol.name !== oldSymbol.name && - updatedSymbol.abbreviation === oldSymbol.abbreviation) { - if (_.where($scope.symbols, {name: updatedSymbol.name}).length > 0) { - Toast.danger('Name ' + updatedSymbol.name + ' already exists'); - return; - } - } else if (updatedSymbol.abbreviation !== oldSymbol.abbreviation && - updatedSymbol.name === oldSymbol.name) { - if (_.where($scope.symbols, {abbreviation: updatedSymbol.abbreviation}).length > 0) { - Toast.danger('Abbreviation ' + updatedSymbol.abbreviation + ' already exists'); - return; - } - } - - // update symbol in scope - symbol = _.find($scope.symbols, {name: oldSymbol.name}); - symbol.name = updatedSymbol.name; - symbol.abbreviation = updatedSymbol.abbreviation; - } - } -}()); \ No newline at end of file diff --git a/main/src/main/webapp/app/modules/core/controllers/SymbolsTrashController.js b/main/src/main/webapp/app/modules/core/controllers/SymbolsTrashController.js deleted file mode 100644 index 930502859..000000000 --- a/main/src/main/webapp/app/modules/core/controllers/SymbolsTrashController.js +++ /dev/null @@ -1,86 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.core') - .controller('SymbolsTrashController', SymbolsTrashController); - - SymbolsTrashController.$inject = ['$scope', 'SessionService', 'Symbol', 'SymbolResource', '_', 'ToastService']; - - /** - * Lists all deleted symbols, what means the symbols where the property 'visible' == 'hidden'. Handles the recover - * of these symbols. By default, recovered symbols will be moved in the default group with the id 0. - * - * Template: 'views/symbols-trash.html' - * - * @param $scope - The controllers scope object - * @param Session - The SessionService - * @param Symbol - The Symbol factory - * @param SymbolResource - The Symbol API Resource handler - * @param _ - Lodash - * @param Toast - The ToastService - * @constructor - */ - function SymbolsTrashController($scope, Session, Symbol, SymbolResource, _, Toast) { - - // The project that is saved in the sessionStorage - var project = Session.project.get(); - - /** - * The list of deleted symbols - * @type {Symbol[]} - */ - $scope.symbols = []; - - /** - * The list of selected symbols - * @type {Symbol[]} - */ - $scope.selectedSymbols = []; - - // initialize controller scope variables - (function init() { - - // fetch all deleted symbols and save them in scope - SymbolResource.getAll(project.id, {deleted: true}) - .then(function (symbols) { - $scope.symbols = symbols; - }); - }()); - - /** - * Recovers a deleted symbol by calling the API and removes the recovered symbol from the symbol list on success - * - * @param {Symbol} symbol - The symbol that should be recovered from the trash - */ - $scope.recoverSymbol = function (symbol) { - SymbolResource.recover(symbol) - .success(function () { - Toast.success('Symbol ' + symbol.name + ' recovered'); - _.remove($scope.symbols, {id: symbol.id}); - }) - .catch(function (response) { - Toast.danger('

Error recovering symbol ' + symbol.name + '!

' + response.data.message); - }) - }; - - /** - * Recovers all symbols that were selected and calls $scope.recoverSymbol for each one - */ - $scope.recoverSelectedSymbols = function () { - if ($scope.selectedSymbols.length > 0) { - SymbolResource.recover($scope.selectedSymbols) - .success(function () { - Toast.success('Symbols recovered'); - _.forEach($scope.selectedSymbols, function (symbol) { - _.remove($scope.symbols, {id: symbol.id}) - }); - $scope.selectedSymbols = []; - }) - .catch(function (response) { - Toast.danger('

Error recovering symbols!

' + response.data.message); - }) - } - } - } -}()); \ No newline at end of file diff --git a/main/src/main/webapp/app/modules/core/directives/actionBar.js b/main/src/main/webapp/app/modules/core/directives/actionBar.js deleted file mode 100644 index 85eaa99dc..000000000 --- a/main/src/main/webapp/app/modules/core/directives/actionBar.js +++ /dev/null @@ -1,54 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.core') - .directive('actionBar', actionBar); - - actionBar.$inject = ['$window']; - - /** - * The directive that is used for the sticky sub navigation that mostly contains call to action buttons for the - * current view - * - * Use: '
' - * - * @param $window - angular $window - * @returns {{replace: boolean, transclude: boolean, template: string, link: link}} - */ - function actionBar($window) { - - var template = '' + - '
' + - '
' + - '
'; - - return { - replace: true, - transclude: true, - template: template, - link: link - }; - - function link(scope, el) { - var body = angular.element(document.body); - - $window.addEventListener('scroll', handleResize); - - scope.$on('$destroy', function () { - $window.removeEventListener('scroll', handleResize); - body.removeClass('has-fixed-action-bar'); - }); - - function handleResize() { - if ($window.scrollY >= 42) { - el.addClass('fixed'); - body.addClass('has-fixed-action-bar'); - } else { - el.removeClass('fixed'); - body.removeClass('has-fixed-action-bar'); - } - } - } - } -}()); diff --git a/main/src/main/webapp/app/modules/core/directives/checkbox.js b/main/src/main/webapp/app/modules/core/directives/checkbox.js deleted file mode 100644 index bbf86ccd2..000000000 --- a/main/src/main/webapp/app/modules/core/directives/checkbox.js +++ /dev/null @@ -1,87 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.core') - .directive('checkbox', checkbox) - .directive('checkboxMultiple', checkboxMultiple); - - /** - * Directive for replacing the default input[type='checkbox'] element to make it look the same in all browsers. - * Can select a single element and toggles the property '_selected' of it. - * - * The attribute 'model' should be the object that can be selected. - * - * Use: - * - * @returns {{restrict: string, template: string, scope: {model: string}, link: link}} - */ - function checkbox() { - var template = '' + - '' + - ' ' + - ''; - - return { - restrict: 'E', - template: template, - scope: { - model: '=' - }, - link: link - }; - - function link(scope, el) { - el.on('click', handleClick); - - function handleClick() { - scope.$apply(function () { - scope.model['_selected'] = !scope.model['_selected']; - }); - } - } - } - - /** - * Directive for replacing the default input[type='checkbox'] element to make it look the same in all browsers. - * Allows the selection of multiple elements at once by toggling the property '_selected' of each object. - * - * The attribute 'model' should be an array of objects or a function that returns one - * - * Use: - * - * @returns {{restrict: string, template: string, scope: {model: string}, link: link}} - */ - function checkboxMultiple() { - var template = '' + - '' + - ' ' + - ''; - - return { - restrict: 'E', - template: template, - scope: { - model: '=' - }, - link: link - }; - - function link(scope, el) { - scope.checked = false; - - el.on('click', handleClick); - - function handleClick() { - scope.checked = !scope.checked; - - scope.$apply(function () { - var items = angular.isFunction(scope.model) ? scope.model() : scope.model; - for (var i = 0; i < items.length; i++) { - items[i]['_selected'] = scope.checked; - } - }) - } - } - } -}()); \ No newline at end of file diff --git a/main/src/main/webapp/app/modules/core/directives/dashboardWidget.js b/main/src/main/webapp/app/modules/core/directives/dashboardWidget.js deleted file mode 100644 index e0fd0223b..000000000 --- a/main/src/main/webapp/app/modules/core/directives/dashboardWidget.js +++ /dev/null @@ -1,48 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.core') - .directive('dashboardWidget', dashboardWidget); - - dashboardWidget.$inject = ['paths']; - - /** - * The directive that is used as a container for a widget that is displayed on the dashboard. Child directives - * should require it with require: '^dashboardWidget' - * - * Use: ... - * - * @returns {{scope: {}, transclude: boolean, templateUrl: string, controller: *[]}} - */ - function dashboardWidget(paths) { - return { - scope: {}, - transclude: true, - templateUrl: paths.COMPONENTS + '/core/views/directives/dashboard-widget.html', - controller: ['$scope', controller] - }; - - /** - * The controller for the widget that child directives have to implement - * - * @param $scope - */ - function controller($scope) { - - /** - * The title of the widget - * @type {null|string} - */ - $scope.title = null; - - /** - * Sets the widgets title - * @param {string} title - The title of the widget - */ - this.setWidgetTitle = function (title) { - $scope.title = title; - } - } - } -}()); \ No newline at end of file diff --git a/main/src/main/webapp/app/modules/core/directives/discriminationTree.js b/main/src/main/webapp/app/modules/core/directives/discriminationTree.js deleted file mode 100644 index edb3b77ef..000000000 --- a/main/src/main/webapp/app/modules/core/directives/discriminationTree.js +++ /dev/null @@ -1,216 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.core') - .directive('discriminationTree', discriminationTree); - - discriminationTree.$inject = ['_', 'd3', 'dagreD3', 'graphlib', '$window']; - - /** - * The directive for displaying a discrimination tree in an svg element. Can be used as an attribute or an element. - * Expects another property 'data' which holds the string representation of the discrimination tree. - * - * Use it like: '' - * - * @param _ - Lodash - * @param d3 - D3 - * @param dagreD3 - dagreD3 - * @param graphlib - graphlib - * @param $window - angular $window object - * @returns {{scope: {data: string}, template: string, link: link}} - */ - function discriminationTree(_, d3, dagreD3, graphlib, $window) { - var template = '' + - '
' + - ' ' + - '
'; - - return { - scope: { - data: '=' - }, - template: template, - link: link - }; - - function link(scope, el) { - - var labelStyle = 'display: inline; font-weight: bold; line-height: 1; text-align: center; white-space: nowrap; vertical-align: baseline;'; - var labelStyleEdge = labelStyle + 'font-size: 10px'; - var labelStyleNode = labelStyle + 'font-size: 12px'; - - // the svg where the discrimination tree is drawn into - var svg = d3.select(el.find('svg')[0]); - - // the first g node of the svg for rendering - var svgGroup = d3.select(el.find('svg').find('g')[0]); - - // the parent of the svg to fit its size accordingly - var svgContainer = svg[0].parentNode; - - // render the new discrimination tree when property 'data' changes - scope.$watch('data', function (newValue) { - if (angular.isDefined(newValue)) { - var data = angular.fromJson(newValue); - var graph = createGraph(data); - var layoutedGraph = layout(graph); - render(layoutedGraph); - } - }); - - /** - * Creates a graph structure from a discrimination tree in order to layout it with the given dagreD3 library - * - * @param {Object} dt - The discrimination tree - * @returns {{nodes: Array, edges: Array}} - The tree as graph representation - */ - function createGraph(dt) { - - var nodes = []; - var edges = []; - - function createGraphData(node, parent) { - - // root without children - if (!node.children && parent === null) { - nodes.push(node.data); - return; - } - - // is leaf? - if (node.children.length === 0) { - return; - } - - // add node if not exists - if (!_.find(nodes, node.discriminator)) { - nodes.push(node.discriminator) - } - - if (parent !== null) { - edges.push({ - from: parent.discriminator, - to: node.discriminator, - label: node.edgeLabel - }); - } - - _.forEach(node.children, function (child) { - if (child.data) { - nodes.push(child.data); - edges.push({ - from: node.discriminator, - to: child.data, - label: child.edgeLabel - }); - } - }); - - _.forEach(node.children, function (child) { - if (child.discriminator) { - createGraphData(child, node); - } - }) - } - - createGraphData(dt, null); - - return { - nodes: nodes, - edges: edges - } - } - - /** - * Creates positions for nodes and edges of the discrimination tree that can be rendered with dagreD3 - * - * @param {Object} graph - The discrimination tree as graph - * @returns {exports.Graph} - The graph with positions of nodes - */ - function layout(graph) { - - // initialize graph - var _graph = new graphlib.Graph({ - directed: true - }); - _graph.setGraph({}); - - // add nodes to the graph - _.forEach(graph.nodes, function (node) { - _graph.setNode(node, { - shape: node[0] === 'q' ? 'rect' : 'circle', // draw a rectangle when node is a leaf - label: node, - width: 25, - style: 'fill: #fff; stroke: #000; stroke-width: 1', - labelStyle: labelStyleNode - }); - }); - - //add edges to the graph - _.forEach(graph.edges, function (edge) { - _graph.setEdge(edge.from, edge.to, { - lineInterpolate: 'basis', - style: "stroke: rgba(0, 0, 0, 0.3); stroke-width: 3; fill:none", - label: edge.label, - labelStyle: labelStyleEdge - }); - }); - - // layout - dagreD3.dagre.layout(_graph, {}); - - return _graph; - } - - /** - * Renders the discrimination tree in the svg with the dagreD3 library - * - * @param {exports.Graph} graph - The graph with position information - */ - function render(graph) { - - // render the graph - new dagreD3.render()(svgGroup, graph); - - // position it in the center of the svg parent - var xCenterOffset = (svgContainer.clientWidth - graph.graph().width) / 2; - svgGroup.attr("transform", "translate(" + xCenterOffset + ", 100)"); - - // swap defs and paths children of .edgepaths because arrows are not shown - // on export otherwise <.< - _.forEach(el.find('svg')[0].querySelectorAll('.edgePath'), function (edgePath) { - edgePath.insertBefore(edgePath.childNodes[1], edgePath.firstChild); - }); - - // Create and handle zoom & pan event - var zoom = d3.behavior.zoom().scaleExtent([0.1, 10]) - .translate([(svgContainer.clientWidth - graph.graph().width) / 2, 100]).on("zoom", zoomHandler); - zoom(svg); - - function zoomHandler() { - svgGroup.attr('transform', 'translate(' + zoom.translate() - + ')' + ' scale(' + zoom.scale() + ')'); - } - - // resize the svg to its parents size on window resize - // and call it once so that svg gets the proper dimensions - $window.addEventListener('resize', fitSize); - scope.$on('$destroy', function(){ - $window.removeEventListener('resize', fitSize); - }); - - function fitSize() { - svg.attr("width", svgContainer.clientWidth); - svg.attr("height", svgContainer.clientHeight); - } - - // in order to prevent only a white screen in some browsers, firing a resize event on the window - // displays the svg contents - window.setTimeout(function () { - window.dispatchEvent(new Event('resize')); - }, 100); - } - } - } -}()); \ No newline at end of file diff --git a/main/src/main/webapp/app/modules/core/directives/dispatchResize.js b/main/src/main/webapp/app/modules/core/directives/dispatchResize.js deleted file mode 100644 index f4c92925d..000000000 --- a/main/src/main/webapp/app/modules/core/directives/dispatchResize.js +++ /dev/null @@ -1,37 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.core') - .directive('dispatchResize', dispatchResize); - - /** - * This directive is used to fire a resize event to the window element with a given delay. Therefore it adds - * a click event to element the directive was used on. - * - * Directive must be used as attribute with a value that indicates how long resize event firing should be delayed - * (in ms). When no value is given, the resize event is fired directly with a delay of 0 ms. - * - * Use: - * - * @returns {{link: link}} - */ - function dispatchResize() { - return { - restrict: 'A', - link: link - }; - - function link(scope, el, attrs) { - el.on('click', function () { - var delay = 0; - if (attrs.dispatchResize && angular.isNumber(parseInt(attrs.dispatchResize))) { - delay = parseInt(attrs.dispatchResize); - } - window.setTimeout(function () { - window.dispatchEvent(new Event('resize')); - }, delay); - }) - } - } -}()); \ No newline at end of file diff --git a/main/src/main/webapp/app/modules/core/directives/downloadAsJson.js b/main/src/main/webapp/app/modules/core/directives/downloadAsJson.js deleted file mode 100644 index 6624fc472..000000000 --- a/main/src/main/webapp/app/modules/core/directives/downloadAsJson.js +++ /dev/null @@ -1,46 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.core') - .directive('downloadAsJson', downloadAsJson); - - downloadAsJson.$inject = ['FileDownloadService']; - - /** - * The directive that can be applied to any element as an attribute that downloads an object or an array as - * a *.json file. Attaches a click event to the element that downloads the file. Can only be used as attribute. - * - * Attribute 'data' has to be defined in order to work and has to be type of object, array or a function - * that returns an object or an array. - * - * Use it like '' - * - * @param FileDownloadService - * @returns {{restrict: string, scope: {data: string}, link: link}} - */ - function downloadAsJson(FileDownloadService) { - - // the directive - return { - restrict: 'A', - scope: { - data: '=' - }, - link: link - }; - - // the directives behaviour - function link(scope, el, attrs) { - el.on('click', function () { - if (angular.isDefined(scope.data)) { - var data = scope.data; - if (angular.isFunction(scope.data)) { - data = scope.data(); - } - FileDownloadService.downloadJson(data); - } - }); - } - } -}()); \ No newline at end of file diff --git a/main/src/main/webapp/app/modules/core/directives/downloadLearnerResultsAsCsv.js b/main/src/main/webapp/app/modules/core/directives/downloadLearnerResultsAsCsv.js deleted file mode 100644 index 73d868e9b..000000000 --- a/main/src/main/webapp/app/modules/core/directives/downloadLearnerResultsAsCsv.js +++ /dev/null @@ -1,76 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.core') - .directive('downloadLearnerResultsAsCsv', downloadLearnerResultsAsCsv); - - downloadLearnerResultsAsCsv.$inject = ['FileDownloadService', 'LearnResultResource']; - - /** - * The directive to download statistics from learner results as csv file. Attaches a click event to the directives - * element that starts the download. - * - * Expects an attribute "results" which value should be the learn results to download. - * Optional attribute "complete" with no value loads all steps of the first result and downloads these - * - * Use it like - * - * @param FileDownloadService - The service to download files - * @param LearnResultResource - The Resource for LearnResult - * @returns {{restrict: string, scope: {results: string}, link: link}} - */ - function downloadLearnerResultsAsCsv(FileDownloadService, LearnResultResource) { - return { - restrict: 'A', - scope: { - results: '=' - }, - link: link - }; - function link(scope, el, attrs) { - - // download csv on click - el.on('click', function () { - if (angular.isDefined(scope.results)) { - - // load all steps and load those - if (angular.isDefined(attrs.complete)) { - LearnResultResource.getComplete(scope.results[0].project, scope.results[0].testNo) - .then(function (results) { - FileDownloadService.downloadCSV(createCsvData(results)); - }) - } else { - FileDownloadService.downloadCSV(createCsvData(scope.results)); - } - } - }); - - /** - * Creates a csv string from learner results. - * - * @param {LearnResult[]} results - The learner results - * @returns {string} - The csv string from learn results - */ - function createCsvData(results) { - var csv = 'Project,Test No,Start Time,Step No,Algorithm,Eq Oracle,|Sigma|,#MQs,#EQs,#Symbol Calls,Duration (ms)\n'; - - for (var i = 0; i < results.length; i++) { - csv += results[i].project + ','; - csv += results[i].testNo + ','; - csv += '"' + results[i].statistics.startTime + '",'; - csv += results[i].stepNo + ','; - csv += results[i].configuration.algorithm + ','; - csv += results[i].configuration.eqOracle.type + ','; - csv += results[i].configuration.symbols.length + ','; - csv += results[i].statistics.mqsUsed + ','; - csv += results[i].statistics.eqsUsed + ','; - csv += results[i].statistics.symbolsUsed + ','; - csv += results[i].statistics.duration + '\n'; - } - - return csv; - } - } - } -}()); \ No newline at end of file diff --git a/main/src/main/webapp/app/modules/core/directives/downloadSvg.js b/main/src/main/webapp/app/modules/core/directives/downloadSvg.js deleted file mode 100644 index 42465786c..000000000 --- a/main/src/main/webapp/app/modules/core/directives/downloadSvg.js +++ /dev/null @@ -1,52 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.core') - .directive('downloadSvg', downloadSvg); - - downloadSvg.$inject = ['FileDownloadService']; - - /** - * The directive that lets you directly download an SVG element from the html page into a file. It attaches a click - * event to the element it was used on, that downloads the SVG. It can only be used as an attribute. - * - * Attribute 'downloadSvg' expects the id of the svg or a parent element - * Attribute 'adjustSize' can be true or false, default is true - * - * Use: ''. - * - * @param FileDownloadService - The service for downloading files - * @returns {{restrict: string, link: link}} - */ - function downloadSvg(FileDownloadService) { - return { - restrict: 'A', - scope: { - downloadSvg: '@', - adjustSize: '@' - }, - link: link - }; - - function link(scope, el) { - el.on('click', function () { - var svg; - var adjustSize = scope.adjustSize !== 'false'; - - // find the downloadable svg element - if (scope.downloadSvg) { - svg = document.querySelector(scope.downloadSvg); - if (svg !== null && svg.nodeName.toLowerCase() === 'svg') { - FileDownloadService.downloadSVG(svg, adjustSize); - } else { - svg = svg.querySelector('svg'); - if (svg !== null) { - FileDownloadService.downloadSVG(svg, adjustSize); - } - } - } - }); - } - } -}()); \ No newline at end of file diff --git a/main/src/main/webapp/app/modules/core/directives/downloadTableAsCsv.js b/main/src/main/webapp/app/modules/core/directives/downloadTableAsCsv.js deleted file mode 100644 index 0f7e532f9..000000000 --- a/main/src/main/webapp/app/modules/core/directives/downloadTableAsCsv.js +++ /dev/null @@ -1,91 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.core') - .directive('downloadTableAsCsv', downloadTableAsCsv); - - downloadTableAsCsv.$inject = ['FileDownloadService']; - - /** - * The directive that downloads a HTML table element as CSV. It attaches a click event to the directives element - * which downloads the file. The directive must be used as an attribute. - * - * It expects one attribute 'ancestorOrElement' which should contain the selector to the table or the an ancestor - * of the table. - * - * Use it like "" - * - * @param FileDownloadService - The service for downloading files - * @returns {{restrict: string, scope: {ancestorOrElement: string}, link: link}} - */ - function downloadTableAsCsv(FileDownloadService) { - return { - restrict: 'A', - scope: { - downloadTableAsCsv: '@' - }, - link: link - }; - - function link(scope, el) { - el.on('click', function () { - - // the table element - var table; - var csv; - - // find the downloadable table element - if (scope.downloadTableAsCsv) { - table = document.querySelector(scope.downloadTableAsCsv); - if (table !== null && table.nodeName.toLowerCase() === 'table') { - csv = createCSV(table); - } else { - table = table.querySelector('table'); - if (table !== null) { - csv = createCSV(table); - } - } - - // download it - if (angular.isDefined(csv)) { - FileDownloadService.downloadCSV(csv); - } - } - }); - - /** - * Creates CSV data from the entries of a HTML table element - * - * @param table - The table element that should be converted - * @returns {string} - The table as CSV string - */ - function createCSV(table) { - var head = table.querySelectorAll('thead th'); - var rows = table.querySelectorAll('tbody tr'); - var csv = ''; - - // add entries from table head - if (head.length > 0) { - for (var i = 0; i < head.length; i++) { - csv += head[i].textContent.replace(',', ' ') + (i === head.length - 1 ? '\n' : ','); - } - } - - // add entries from table row - if (rows.length > 0) { - for (i = 0; i < rows.length; i++) { - var tds = rows[i].querySelectorAll('td'); - if (tds.length > 0) { - for (var j = 0; j < tds.length; j++) { - csv += tds[j].textContent.replace(',', ' ') + (j === tds.length - 1 ? '\n' : ','); - } - } - } - } - - return csv; - } - } - } -}()); \ No newline at end of file diff --git a/main/src/main/webapp/app/modules/core/directives/dropdownHover.js b/main/src/main/webapp/app/modules/core/directives/dropdownHover.js deleted file mode 100644 index 6f0aa4dbd..000000000 --- a/main/src/main/webapp/app/modules/core/directives/dropdownHover.js +++ /dev/null @@ -1,36 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.core') - .directive('dropdownHover', dropdownHover); - - /** - * A directive in addition to the dropdown directive from ui-bootstrap. It opens the dropdown menu when entering the - * trigger element of the menu with the mouse so you don't have to click on it. Place it as attribute 'dropdown-hover' - * beside 'dropdown' in order to work as expected. - * - * @return {{require: string, link: link}} - */ - function dropdownHover() { - return { - restrict: 'A', - require: 'dropdown', - link: link - }; - - /** - * @param scope - * @param el - * @param attrs - * @param ctrl - the ui.bootstrap dropdown controller - */ - function link(scope, el, attrs, ctrl) { - el.on('mouseenter', function () { - scope.$apply(function () { - ctrl.toggle(true); - }) - }) - } - } -}()); \ No newline at end of file diff --git a/main/src/main/webapp/app/modules/core/directives/fileDropzone.js b/main/src/main/webapp/app/modules/core/directives/fileDropzone.js deleted file mode 100644 index 16a42cb74..000000000 --- a/main/src/main/webapp/app/modules/core/directives/fileDropzone.js +++ /dev/null @@ -1,79 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.core') - .directive('fileDropzone', fileDropzone); - - /** - * This directives makes any element a place to drop files from the local pc. Currently this directive only - * supports to read files as a text. It can only be used as an attribute. - * - * Attribute 'onLoaded' expects to be a function with one parameter that represents the value of the loaded - * file as string - * - * Use: '
' with function load(contents) { ... } - * - * @return {{restrict: string, scope: {onLoaded: string}, link: link}} - */ - function fileDropzone() { - return { - restrict: 'A', - scope: { - onLoaded: '&' - }, - link: link - }; - function link(scope, el) { - var reader = new FileReader(); - - // call the callback as soon as a file is loaded - reader.onload = function (e) { - if (angular.isDefined(scope.onLoaded)) { - scope.onLoaded()(e.target.result); - } - }; - - // attach some styles to the element on dragover etc. - el.on('dragover', function (e) { - e.preventDefault(); - e.stopPropagation(); - e.dataTransfer.dropEffect = 'copy'; - }); - - el.on('dragenter', function () { - el[0].style.outline = '2px solid rgba(0,0,0,0.2)'; - }).on('dragleave', function () { - el[0].style.outline = '0'; - }); - - el.on('drop', function (e) { - e.preventDefault(); - e.stopPropagation(); - el[0].style.outline = '0'; - readFiles(e.dataTransfer.files); - }); - - // create input field and simulate click - el.on('click', function () { - var input = document.createElement('input'); - input.setAttribute('type', 'file'); - input.addEventListener('change', function (e) { - readFiles(e.target.files); - }, false); - input.click(); - }); - - /** - * Read files as a text file - * - * @param files - */ - function readFiles(files) { - for (var i = 0; i < files.length; i++) { - reader.readAsText(files[i]); - } - } - } - } -}()); \ No newline at end of file diff --git a/main/src/main/webapp/app/modules/core/directives/htmlElementPicker.js b/main/src/main/webapp/app/modules/core/directives/htmlElementPicker.js deleted file mode 100644 index 932bcb7ed..000000000 --- a/main/src/main/webapp/app/modules/core/directives/htmlElementPicker.js +++ /dev/null @@ -1,352 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.core') - .directive('htmlElementPicker', htmlElementPicker) - .directive('htmlElementPickerWindow', htmlElementPickerWindow) - .factory('htmlElementPickerInstance', htmlElementPickerInstance); - - htmlElementPicker.$inject = ['$document', '$compile', 'htmlElementPickerInstance']; - htmlElementPickerWindow.$inject = ['$window', 'SessionService', 'paths', 'htmlElementPickerInstance']; - htmlElementPickerInstance.$inject = ['$q']; - - /** - * The directive that creates a new HTML element picker. Can only be used as an attribute and attaches a click - * event to the source element that opens the picker. On first start, it loads the page that is defined in the - * projects baseUrl. On following calls the last visited page is loaded. - * - * Attribute 'model' is the model for the XPath - * Attribute 'text' is the model for the .textContent value of the selected element - * - * Use: '' - * - * @param $document - angular document wrapper - * @param $compile - angular $compile service - * @param htmlElementPickerInstance - Holds the promise and methods for opening and closing the picker - * @returns {{restrict: string, scope: {selectorModel: string}, link: Function}} - */ - function htmlElementPicker($document, $compile, htmlElementPickerInstance) { - return { - restrict: 'A', - scope: { - selectorModel: '=model', - textModel: '=text' - }, - link: link - }; - function link(scope, el, attrs) { - - // The HTML picker element that is dynamically appended and removed to/from the pages DOM tree - var picker; - - el.on('click', function () { - - // create a new element picker under the current scope and append to the body - picker = $compile('')(scope); - $document.find('body').prepend(picker); - - // open the picker - htmlElementPickerInstance.open() - .then(function (data) { - - // copy the selected XPath and .textContent value to the scopes models - if (angular.isDefined(scope.selectorModel)) { - scope.selectorModel = data.xPath; - } - if (angular.isDefined(scope.textModel)) { - scope.textModel = data.textContent; - } - }) - - // remove the picker from the dom on close - .finally(function () { - picker.remove(); - }) - }) - } - } - - /** - * The instance for the HTML element picker that holds the promise offers methods to persist the last opened url as - * well as opening and closing the HTML element picker - * - * @returns {{close: Function, open: Function, setUrl: Function, getUrl: Function}} - */ - function htmlElementPickerInstance($q) { - - // the promise - var deferred; - - // the url was called at the last opening - var lastUrl = null; - - return { - close: close, - open: open, - setUrl: setUrl, - getUrl: getUrl - }; - - /** - * Resolves the promise with selected data or cancels the promise of no data is defined - * - * @param {Object} data - The object that should contain an XPath and a .textContent value - */ - function close(data) { - if (angular.isDefined(data)) { - deferred.resolve(data) - } else { - deferred.reject(); - } - } - - /** - * Creates the promise that is used to pass data between the directive and the picker - * - * @returns {d.promise|promise|qFactory.Deferred.promise|p.ready.promise|fd.g.promise} - */ - function open() { - deferred = $q.defer(); - return deferred.promise; - } - - function setUrl(url) { - lastUrl = url; - } - - function getUrl() { - return lastUrl; - } - } - - /** - * The actual HTML element picker. Handles the complete window including the selection of elements and loading - * of urls. Works as a 'mini embedded browser' - * - * Use: '' - * - * @param $window - angular window wrapper - * @param Session - The SessionService - * @param paths - The applications paths - * @param htmlElementPickerInstance - @see{@link htmlElementPickerInstance} - * @returns {{scope: {}, templateUrl: string, link: link}} - */ - function htmlElementPickerWindow($window, Session, paths, htmlElementPickerInstance) { - return { - restrict: 'E', - scope: {}, - templateUrl: paths.COMPONENTS + '/core/views/directives/html-element-picker.html', - link: link - }; - - function link(scope, el) { - - // the iframe where the projects site gets loaded into - var iframe = el.find('iframe'); - - // when moving with the mouse over an element, this elements gets saved in this variable in order to - // prevent multiple calls of getCssPath for the same element - var lastTarget = null; - - // the url of the proxy - var proxyUrl = null; - - /** - * flag for selection mode - * @type {boolean} - */ - scope.isSelectable = false; - - /** - * The XPath of the selected element - * @type {null|string} - */ - scope.selector = null; - - /** - * The element.textContent value - * @type {null|string} - */ - scope.textContent = null; - - /** - * The url that is loaded in the iframe - * @type {string} - */ - scope.url = null; - - /** - * The project in the session - * @type {null|Project} - */ - scope.project = null; - - /** - * Get the unique CSS XPath from selected Element - * http://stackoverflow.com/questions/4588119/get-elements-css-selector-without-element-id - * - * @param el - The element to get the unique css path from - * @returns {String} - The unique css path ot the element - * @private - */ - function getCssPath(el) { - - var names = []; - while (el.parentNode) { - if (el.id) { - names.unshift('#' + el.id); - break; - } else { - if (el == el.ownerDocument.documentElement) names.unshift(el.tagName); - else { - for (var c = 1, e = el; e.previousElementSibling; e = e.previousElementSibling, c++); - names.unshift(el.tagName + ":nth-child(" + c + ")"); - } - el = el.parentNode; - } - } - return names.join(" > "); - } - - /** - * Saves the element that is under the cursor so that it can be selected. Adds an outline to the element - * in order to highlight it. - * - * @param e - js event - * @returns {boolean} - */ - function handleMouseMove(e) { - if (lastTarget == e.target) { - return false; - } else { - if (lastTarget != null) { - lastTarget.style.outline = '0px' - } - lastTarget = e.target; - } - lastTarget.style.outline = '5px solid red'; - scope.selector = getCssPath(lastTarget); - - if (lastTarget.nodeName.toLowerCase() === 'input') { - scope.textContent = lastTarget.value; - } else { - scope.textContent = lastTarget.textContent; - } - - scope.$apply(); - } - - /** - * Removes the outline from the selected element, removes all events from the iframe and removes the - * keypress event. When this function is called the selected element is fixed and won't change by any - * further interaction with the iframe - * - * @param e - js event - */ - function handleClick(e) { - if (angular.isDefined(e)) { - e.preventDefault(); - e.stopPropagation(); - } - - if (lastTarget !== null) { - lastTarget.style.outline = '0px'; - } - lastTarget = null; - - angular.element(iframe.contents()[0].body).off('mousemove', handleMouseMove); - angular.element(iframe.contents()[0].body).off('click', handleClick); - angular.element(document.body).off('keyup', handleKeyUp); - } - - /** - * Calls handleClick() when control key is pressed to have an alternative for selecting a dom node without - * firing any click events on it. - * - * @param e - */ - function handleKeyUp(e) { - if (e.keyCode == 17) { // strg - handleClick(); - scope.isSelectable = false; - } - } - - // load project, create proxy address and load the last url in the iframe - function init() { - scope.project = Session.project.get(); - proxyUrl = $window.location.origin + paths.api.PROXY_URL; - - scope.url = htmlElementPickerInstance.getUrl(); - scope.loadUrl(); - } - - /** - * Loads an entered url into the iframe and handles the click on every a element - */ - scope.loadUrl = function () { - iframe[0].setAttribute('src', proxyUrl + scope.project.baseUrl + '/' + (scope.url === null ? '' : scope.url)); - iframe[0].onload = function () { - angular.element(iframe.contents()[0].body.getElementsByTagName('a')) - .on('click', function () { - if (!scope.isSelectable) { - var _this = this; - if (_this.getAttribute('href') !== '' && _this.getAttribute('href')[0] !== '#') { - scope.$apply(function () { - scope.url = decodeURIComponent(_this.getAttribute('href')) - .replace(proxyUrl + scope.project.baseUrl + '/', '') - }) - } - } - } - ) - } - }; - - /** - * Enables the selection mode and therefore adds events to the iframe - */ - scope.toggleSelection = function () { - if (!scope.isSelectable) { - var iframeBody = angular.element(iframe.contents()[0].body); - iframeBody.on('mousemove', handleMouseMove); - iframeBody.one('click', function (e) { - handleClick(e); - scope.$apply(function () { - scope.isSelectable = false; - }); - }); - angular.element(document.body).on('keyup', handleKeyUp); - } else { - handleClick(); - scope.selector = null; - } - scope.isSelectable = !scope.isSelectable; - }; - - - /** - * Makes the web element picker invisible and fires the close event - */ - scope.close = function () { - htmlElementPickerInstance.setUrl(scope.url); - htmlElementPickerInstance.close(); - }; - - /** - * Makes the web element Picker invisible and fires the ok event with the selector of the element that was - * selected. If no selector is defined, then it just closes the picker - */ - scope.ok = function () { - htmlElementPickerInstance.setUrl(scope.url); - htmlElementPickerInstance.close({ - xPath: scope.selector, - textContent: scope.textContent - }); - }; - - init(); - } - } -}()); \ No newline at end of file diff --git a/main/src/main/webapp/app/modules/core/directives/hypothesis.js b/main/src/main/webapp/app/modules/core/directives/hypothesis.js deleted file mode 100644 index 8b6edce92..000000000 --- a/main/src/main/webapp/app/modules/core/directives/hypothesis.js +++ /dev/null @@ -1,226 +0,0 @@ -(function () { - - angular.module('ALEX.core') - .directive('hypothesis', hypothesis); - - hypothesis.$inject = ['$window', 'paths', 'CounterExampleService', '_', 'dagreD3', 'd3', 'graphlib']; - - /** - * The directive that is used to display hypotheses. - * - * Attribute 'isSelectable' should only be true if it should be possible to select counterexamples from the - * hypothesis that are stored in the CounterExampleService. It is automatically 'false' if not defined - * - * Attribute 'layoutSettings' is optional. - * - * Use: - * - * @param $window - * @param paths - * @param CounterExampleService - * @param _ - * @param dagreD3 - * @param d3 - * @param graphlib - * @returns {{scope: {result: string, layoutSettings: string, isSelectable: string}, templateUrl: string, link: link}} - */ - function hypothesis($window, paths, CounterExampleService, _, dagreD3, d3, graphlib) { - return { - scope: { - result: '=', - layoutSettings: '=', - isSelectable: '@' - }, - templateUrl: paths.COMPONENTS + '/core/views/directives/hypothesis.html', - link: link - }; - function link(scope, el) { - var _svg; // the svg element - var _svgGroup; // the first g element in the svg the hypothesis is rendered into - var _svgContainer; // the parent of the svg element - var _graph; // the graphlib graph object that represents the hypothesis - var _renderer; // the dagreD3 renderer that renders _graph in _svgGroup - - var styles = { - edge: 'stroke: rgba(0, 0, 0, 0.3); stroke-width: 3; fill:none', - edgeLabel: 'display: inline; font-weight: bold; line-height: 1; text-align: center; white-space: nowrap; vertical-align: baseline; font-size: 10px', - nodeLabel: 'display: inline; font-weight: bold; line-height: 1; text-align: center; white-space: nowrap; vertical-align: baseline; font-size: 12px', - node: 'fill: #fff; stroke: #000; stroke-width: 1', - initNode: 'fill: #B3E6B3; stroke: #5cb85c; stroke-width: 3' - }; - - scope.$watch('result', function (result) { - if (angular.isDefined(result) && result !== null) { - createHypothesis(); - } - }); - - scope.$watch('layoutSettings', function (ls) { - if (angular.isDefined(ls)) { - createHypothesis(); - } - }); - - // create the hypothesis. Call this on re-render - function createHypothesis() { - clearSvg(); - init(); - layout(); - render(); - handleEvents(); - } - - function clearSvg() { - el.find('svg')[0].innerHTML = ''; - } - - function init() { - _svg = d3.select(el.find('svg')[0]); - _svgGroup = _svg.append("g"); - _svgContainer = _svg.node().parentNode; - - _svg.style({ - 'font-family': '"Helvetica Neue",Helvetica,Arial,sans-serif', - 'font-size': '12px', - 'line-height': '1.42857', - 'color': '#333' - }); - - _graph = new graphlib.Graph({ - directed: true, - multigraph: true - }); - - if (scope.layoutSettings !== null) { - _graph.setGraph({ - edgesep: scope.layoutSettings.edgesep, - nodesep: scope.layoutSettings.nodesep, - ranksep: scope.layoutSettings.ranksep - }); - } else { - _graph.setGraph({ - edgesep: 25 - }); - } - } - - function layout() { - var node; - - function createEdgeObject(label) { - return { - label: label, - labeloffset: 5, - lineInterpolate: 'basis', - style: styles.edge, - labelStyle: styles.edgeLabel - } - } - - // add nodes to the graph - for (var i = 0; i < scope.result.hypothesis.nodes.length; i++) { - node = scope.result.hypothesis.nodes[i]; - _graph.setNode(node.toString(), { - shape: 'circle', - label: node.toString(), - width: 25, - labelStyle: styles.nodeLabel, - style: node === scope.result.hypothesis.initNode ? styles.initNode : styles.node - }) - } - - // another format of a graph for merged multi edges - // graph = {: {: , ...}, ...} - var graph = {}; - - // build data structure for the alternative representation by - // pushing some data - _.forEach(scope.result.hypothesis.edges, function (edge) { - if (!graph[edge.from]) { - graph[edge.from] = {}; - graph[edge.from][edge.to] = [edge.input + "/" - + edge.output]; - } else { - if (!graph[edge.from][edge.to]) { - graph[edge.from][edge.to] = [edge.input + "/" - + edge.output]; - } else { - graph[edge.from][edge.to].push(edge.input + "/" - + edge.output); - } - } - }); - - // add edges to the rendered graph and combine - _.forEach(graph, function (k, from) { - _.forEach(k, function (labels, to) { - _graph.setEdge(from, to, createEdgeObject(labels.join('\n')), (from + '' + to)); - }); - }); - - // layout with dagre - dagreD3.dagre.layout(_graph, {}); - } - - function render() { - - // render the graph in the svg - _renderer = new dagreD3.render(); - _renderer(_svgGroup, _graph); - - // Center graph horizontally - var xCenterOffset = (_svgContainer.clientWidth - _graph.graph().width) / 2; - _svgGroup.attr("transform", "translate(" + xCenterOffset + ", 100)"); - - // swap defs and paths children of .edgepaths because arrows are not shown - // on export otherwise <.< - _.forEach(el.find('svg')[0].querySelectorAll('.edgePath'), function (edgePath) { - edgePath.insertBefore(edgePath.childNodes[1], edgePath.firstChild); - }) - } - - function handleEvents() { - var zoom; - - // attach click events for the selection of counter examples to the edge labels - // only if counterExamples is defined - if (angular.isDefined(scope.isSelectable)) { - _svg.selectAll('.edgeLabel tspan').on('click', function () { - var label = this.innerHTML.split('/'); // separate abbreviation from output - var abbreviation = label[0]; - var output = label[1]; - scope.$apply(function () { - CounterExampleService.addIOPairToCurrentCounterexample(abbreviation, output); - }); - }); - } - - // Create and handle zoom & pan event - zoom = d3.behavior.zoom().scaleExtent([0.1, 10]) - .translate([(_svgContainer.clientWidth - _graph.graph().width) / 2, 100]).on("zoom", zoomHandler); - zoom(_svg); - - function zoomHandler() { - _svgGroup.attr('transform', 'translate(' + zoom.translate() - + ')' + ' scale(' + zoom.scale() + ')'); - } - - // do this whole stuff so that the size of the svg adjusts to the window - $window.addEventListener('resize', fitSize); - scope.$on('$destroy', function () { - $window.removeEventListener('resize', fitSize); - }); - - function fitSize() { - _svg.attr("width", _svgContainer.clientWidth); - _svg.attr("height", _svgContainer.clientHeight); - } - - // prevent hypothesis not to be rendered instantly - window.setTimeout(function () { - window.dispatchEvent(new Event('resize')); - }, 100); - } - } - } -}()); \ No newline at end of file diff --git a/main/src/main/webapp/app/modules/core/directives/latestLearnResultWidget.js b/main/src/main/webapp/app/modules/core/directives/latestLearnResultWidget.js deleted file mode 100644 index c86ed947b..000000000 --- a/main/src/main/webapp/app/modules/core/directives/latestLearnResultWidget.js +++ /dev/null @@ -1,52 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.core') - .directive('latestLearnResultWidget', latestLearnResultWidget); - - latestLearnResultWidget.$inject = ['SessionService', 'LearnResultResource', 'paths']; - - /** - * The directive for the dashboard widget that displays information about the latest learning result, if there - * exists one. - * - * Use: - * - * - * - * @param Session - The SessionService - * @param LearnResultResource - The LearnResult factory - * @returns {{require: string, templateUrl: string, link: link}} - */ - function latestLearnResultWidget(Session, LearnResultResource, paths) { - return { - require: '^dashboardWidget', - templateUrl: paths.COMPONENTS + '/core/views/directives/latest-learn-result-widget.html', - link: link - }; - - /** - * @param scope - * @param el - * @param attrs - * @param ctrl - The controller of dashboardWidget - */ - function link(scope, el, attrs, ctrl) { - ctrl.setWidgetTitle('Latest Learn Result'); - - /** - * The latest learning result - * @type {null|LearnResult} - */ - scope.result = null; - - LearnResultResource.getAllFinal(Session.project.get().id) - .then(function (results) { - if (results.length > 0) { - scope.result = results[results.length - 1]; - } - }) - } - } -}()); \ No newline at end of file diff --git a/main/src/main/webapp/app/modules/core/directives/layout.js b/main/src/main/webapp/app/modules/core/directives/layout.js deleted file mode 100644 index 60876698c..000000000 --- a/main/src/main/webapp/app/modules/core/directives/layout.js +++ /dev/null @@ -1,52 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.core') - .controller('LayoutController', ['$scope', function ($scope) { - $scope.collapsed = false; - - this.toggleCollapsed = function () { - $scope.collapsed = !$scope.collapsed; - }; - - this.isCollapsed = function () { - return $scope.collapsed; - } - }]) - - .directive('layout', function () { - return { - scope: {}, - controller: 'LayoutController' - } - }) - - .directive('layoutToggleElement', function () { - return { - require: '^layout', - link: function (scope, el, attrs, ctrl) { - scope.$watch(ctrl.isCollapsed, function (collapsed) { - if (collapsed) { - el.addClass('layout-collapsed'); - } else { - el.removeClass('layout-collapsed'); - } - }); - } - } - }) - - .directive('layoutToggleButton', function () { - return { - require: '^layout', - link: function (scope, el, attrs, ctrl) { - el.on('click', function () { - scope.$apply(function () { - ctrl.toggleCollapsed(); - }); - }) - } - } - }) -}()); \ No newline at end of file diff --git a/main/src/main/webapp/app/modules/core/directives/learnResultComparePanel.js b/main/src/main/webapp/app/modules/core/directives/learnResultComparePanel.js deleted file mode 100644 index 9fb7339f5..000000000 --- a/main/src/main/webapp/app/modules/core/directives/learnResultComparePanel.js +++ /dev/null @@ -1,26 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.core') - .directive('learnResultComparePanel', learnResultComparePanel); - - function learnResultComparePanel() { - return { - scope: { - index: '@', - from: '@' - }, - link: link - }; - - function link(scope, el) { - scope.$watch('from', function () { - var from = scope.from || 1; - var index = scope.index || 0; - el[0].style.width = (100 / from) + '%'; - el[0].style.left = ((100 / from) * (index)) + '%'; - }); - } - } -}()); \ No newline at end of file diff --git a/main/src/main/webapp/app/modules/core/directives/learnResultList.js b/main/src/main/webapp/app/modules/core/directives/learnResultList.js deleted file mode 100644 index 66386e378..000000000 --- a/main/src/main/webapp/app/modules/core/directives/learnResultList.js +++ /dev/null @@ -1,27 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.core') - .directive('learnResultList', learnResultList) - .directive('learnResultListItem', learnResultListItem); - - learnResultListItem.$inject = ['paths']; - - function learnResultList() { - return { - transclude: true, - template: '
' - } - } - - function learnResultListItem(paths) { - return { - transclude: true, - scope: { - result: '=' - }, - templateUrl: paths.COMPONENTS + '/core/views/directives/learn-result-list-item.html' - } - } -}()); \ No newline at end of file diff --git a/main/src/main/webapp/app/modules/core/directives/learnResultPanel.js b/main/src/main/webapp/app/modules/core/directives/learnResultPanel.js deleted file mode 100644 index 580ea9077..000000000 --- a/main/src/main/webapp/app/modules/core/directives/learnResultPanel.js +++ /dev/null @@ -1,162 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.core') - .directive('learnResultPanel', learnResultPanel); - - learnResultPanel.$inject = ['paths', 'learnAlgorithms']; - - /** - * The directive that displays a browsable list of learn results. For each result, it can display the observation - * table, if L* was used, or the Discrimination Tree from the corresponding algorithm. - * - * It expects an attribute 'results' which should contain a list of the learn results that should be displayed. It - * can for example be the list of all intermediate results of a complete test or multiple single results from - * multiple tests. - * - * An additional attribute 'index' can be passed that markes the index of the panel in case there are multiple. - * - * Content that is written inside the tag will be displayed a the top right corner beside the index browser. So - * just add small texts or additional buttons in there. - * - * Use it like ' ... ' - * - * @param {Object} paths - The constant for application paths - * @param {Object} learnAlgorithms - The constant for available learn algorithms - * @returns {{scope: {results: string}, transclude: boolean, templateUrl: string, controller: *[]}} - */ - function learnResultPanel(paths, learnAlgorithms) { - return { - scope: { - results: '=', - index: '@' - }, - replace: true, - transclude: true, - templateUrl: paths.COMPONENTS + '/core/views/directives/learn-result-panel.html', - controller: ['$scope', link] - }; - - function link(scope) { - - /** - * Enum for displayable modes. - * 0 := show hypothesis - * 1 := show internal data structure - * @type {{HYPOTHESIS: number, INTERNAL: number}} - */ - scope.modes = { - HYPOTHESIS: 0, - OBSERVATION_TABLE: 1, - DISCRIMINATION_TREE: 2 - }; - - /** - * Available learn algorithms - * @type {Object} - */ - scope.learnAlgorithms = learnAlgorithms; - - /** - * The layout settings for the displayed hypothesis - * @type {null|Object} - */ - scope.layoutSettings = null; - - /** - * The mode that is used - * @type {number} - */ - scope.mode = scope.modes.HYPOTHESIS; - - /** - * The index of the step from the results that should be shown - * @type {number} - */ - scope.pointer = scope.results.length - 1; - - // adjust the pointer to show the latest result when learning with sample eq oracle - scope.$watch('results', function(results) { - scope.pointer = results.length - 1; - }); - - /** - * Checks if the property 'algorithmInformation' is define which holds the internal data structure - * for the algorithm of a learn result - * @returns {boolean|*} - */ - scope.hasInternalDataStructure = function () { - return scope.results[scope.pointer].configuration.algorithm !== learnAlgorithms.TTT; - }; - - /** - * Switches the mode to the one to display the internal data structure - */ - scope.showInternalDataStructure = function () { - switch (scope.results[scope.pointer].configuration.algorithm) { - case learnAlgorithms.LSTAR: - scope.mode = scope.modes.OBSERVATION_TABLE; - break; - case learnAlgorithms.DISCRIMINATION_TREE: - scope.mode = scope.modes.DISCRIMINATION_TREE; - break; - default: - break; - } - }; - - /** - * Updates the layoutSettings - * - * @param ls {Object} - The layoutSettings object - */ - scope.updateLayoutSettings = function (ls) { - scope.layoutSettings = ls; - }; - - /** - * Switches the mode to the one to display the hypothesis - */ - scope.showHypothesis = function () { - scope.mode = scope.modes.HYPOTHESIS; - }; - - /** - * Shows the first result of the test process - */ - scope.firstStep = function () { - scope.pointer = 0; - }; - - /** - * Shows the previous result of the test process or the last if the first one is displayed - */ - scope.previousStep = function () { - if (scope.pointer - 1 < 0) { - scope.lastStep(); - } else { - scope.pointer--; - } - }; - - /** - * Shows the next result of the test process or the first if the last one is displayed - */ - scope.nextStep = function () { - if (scope.pointer + 1 > scope.results.length - 1) { - scope.firstStep(); - } else { - scope.pointer++; - } - }; - - /** - * Shows the last result of the test process - */ - scope.lastStep = function () { - scope.pointer = scope.results.length - 1; - }; - } - } -}()); \ No newline at end of file diff --git a/main/src/main/webapp/app/modules/core/directives/learnerStatusWidget.js b/main/src/main/webapp/app/modules/core/directives/learnerStatusWidget.js deleted file mode 100644 index d0e2f0e63..000000000 --- a/main/src/main/webapp/app/modules/core/directives/learnerStatusWidget.js +++ /dev/null @@ -1,80 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.core') - .directive('learnerStatusWidget', learnerStatusWidget); - - learnerStatusWidget.$inject = ['LearnerService', 'ToastService', 'paths']; - - /** - * The directive of the dashboard widget that displays the current status of the learner - * - * Use: - * - * - * - * @param Learner - The LearnerService - * @param Toast - The ToastService - * @param paths - The applications paths constant - * @returns {{require: string, templateUrl: string, link: link}} - */ - function learnerStatusWidget(Learner, Toast, paths) { - return { - require: '^dashboardWidget', - templateUrl: paths.COMPONENTS + '/core/views/directives/learner-status-widget.html', - link: link - }; - - /** - * @param scope - * @param el - * @param attrs - * @param ctrl - The controller of dashboardWidget - */ - function link(scope, el, attrs, ctrl) { - ctrl.setWidgetTitle('Learner Status'); - - /** - * Whether the learner is actively learning an application - * @type {boolean} - */ - scope.isActive = false; - - /** - * Whether the learner has finished learning an application - * @type {boolean} - */ - scope.hasFinished = false; - - /** - * The intermediate or final learning result - * @type {null|LearnResult} - */ - scope.result = null; - - Learner.isActive() - .then(function (data) { - scope.isActive = data.active; - if (!data.active) { - Learner.getStatus() - .then(function (data) { - if (data !== null) { - scope.hasFinished = true; - scope.result = data; - } - }); - } - }); - - /** - * Induces the Learner to stop learning after the current hypothesis model - */ - scope.abort = function () { - Learner.stop().then(function () { - Toast.info('The Learner stops with the next hypothesis'); - }) - } - } - } -}()); \ No newline at end of file diff --git a/main/src/main/webapp/app/modules/core/directives/loadScreen.js b/main/src/main/webapp/app/modules/core/directives/loadScreen.js deleted file mode 100644 index a89ee6050..000000000 --- a/main/src/main/webapp/app/modules/core/directives/loadScreen.js +++ /dev/null @@ -1,49 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.core') - .directive('loadScreen', loadScreen); - - loadScreen.$inject = ['$http', 'paths']; - - /** - * The load screen that is shown during http requests. It lays over the application to prevent further - * interactions with the page. The navigation is still usable. Add it right after the body and give the element - * a high value for z-index in the stylesheet. - * - * Use is like ''. - * - * @param $http - The angular $http service - * @param {Object} paths - The constant with application paths - * @returns {{scope: {}, templateUrl: string, link: link}} - */ - function loadScreen($http, paths) { - return { - scope: {}, - templateUrl: paths.COMPONENTS + '/core/views/directives/load-screen.html', - link: link - }; - - function link(scope, el) { - - /** - * Shows if there are currently any active http requests going on - * - * @returns {boolean} - If there are any active requests - */ - scope.hasPendingRequests = function () { - return $http.pendingRequests.length > 0; - }; - - // watch the change of pendingRequests and change the visibility of the loadscreen - scope.$watch(scope.hasPendingRequests, function (value) { - if (value) { - el[0].style.display = 'block'; - } else { - el[0].style.display = 'none'; - } - }); - } - } -}()); \ No newline at end of file diff --git a/main/src/main/webapp/app/modules/core/directives/observationTable.js b/main/src/main/webapp/app/modules/core/directives/observationTable.js deleted file mode 100644 index 88342a83b..000000000 --- a/main/src/main/webapp/app/modules/core/directives/observationTable.js +++ /dev/null @@ -1,100 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.core') - .directive('observationTable', observationTable); - - observationTable.$inject = ['paths']; - - /** - * The directive that renders an observation table from an ascii representation into a html table. Can only be used - * as a tag. - * - * Attribute 'data' should be the ascii string representation of the table from the LearnLib. - * - * Use: - * - * @param paths - The applications paths constant - * @returns {{restrict: string, scope: {data: string}, link: link, templateUrl: string}} - */ - function observationTable(paths) { - return { - restrict: 'E', - scope: { - data: '=' - }, - link: link, - templateUrl: paths.COMPONENTS + '/core/views/directives/observation-table.html' - }; - - function link(scope) { - - // the object of the table for the template - scope.table = null; - - // render the observation table as soon as the data changes - scope.$watch('data', function (n) { - if (angular.isDefined(n)) { - createObservationTable(); - } - }); - - /** - * Parses the ascii representation of the observation table and stores it into scope.table - */ - function createObservationTable() { - - // init table structure - scope.table = { - header: [], - body: { - s1: [], - s2: [] - } - }; - - var rows = scope.data.split('\n'); // the rows of the table - var marker = 0; // a flag that is used to indicate on which set of the table I am - - if (rows.length > 1) { - for (var i = 0; i < rows.length - 1; i++) { - - // +=====+======+ ... + is checked - // before the third occurrence of this pattern we are in set S\Sigma - // after that we are in set S - if (new RegExp('^(\\+\\=+)+\\+$').test(rows[i])) { - marker++; - continue; - } - - // only check each second row because all others are only separators - if (i % 2 === 1) { - - //remove column separators and white spaces around the entry content - rows[i] = rows[i].split('|'); - rows[i].shift(); - rows[i].pop(); - for (var j = 0; j < rows[i].length; j++) { - rows[i][j] = rows[i][j].trim(); - } - - // fill the table - if (i === 1) { - scope.table.header = rows[i]; - } else { - - // depending on which set of the table i am - if (marker === 2) { - scope.table.body.s1.push(rows[i]); - } else { - scope.table.body.s2.push(rows[i]); - } - } - } - } - } - } - } - } -}()); \ No newline at end of file diff --git a/main/src/main/webapp/app/modules/core/directives/projectDetailsWidget.js b/main/src/main/webapp/app/modules/core/directives/projectDetailsWidget.js deleted file mode 100644 index a4425a792..000000000 --- a/main/src/main/webapp/app/modules/core/directives/projectDetailsWidget.js +++ /dev/null @@ -1,79 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.core') - .directive('projectDetailsWidget', projectDetailsWidget); - - projectDetailsWidget.$inject = ['SessionService', 'SymbolGroupResource', 'LearnResultResource', 'paths']; - - /** - * The directive for the dashboard widget that displays information about the current project. - * - * Use: - * - * - * - * @param Session - The SessionService - * @param SymbolGroupResource - The SymbolGroup Resource - * @param LearnResultResource - The LearnResult Resource - * @param paths - The applications paths constant - * @returns {{require: string, templateUrl: string, link: link}} - */ - function projectDetailsWidget(Session, SymbolGroupResource, LearnResultResource, paths) { - return { - require: '^dashboardWidget', - templateUrl: paths.COMPONENTS + '/core/views/directives/project-details-widget.html', - link: link - }; - - /** - * @param scope - * @param el - * @param attrs - * @param ctrl - The dashboardWidget controller - */ - function link(scope, el, attrs, ctrl) { - ctrl.setWidgetTitle('About this Project'); - - /** - * The project in sessionStorage - * @type {Project} - */ - scope.project = Session.project.get(); - - /** - * The number of symbol groups of the project - * @type {null|number} - */ - scope.numberOfGroups = null; - - /** - * The number of visible symbols of the project - * @type {null|number} - */ - scope.numberOfSymbols = null; - - /** - * The number of persisted test runs in the database - * @type {null|number} - */ - scope.numberOfTests = null; - - SymbolGroupResource.getAll(scope.project.id, {embedSymbols: true}) - .then(function (groups) { - scope.numberOfGroups = groups.length; - var counter = 0; - for (var i = 0; i < groups.length; i++) { - counter += groups[i].symbols.length; - } - scope.numberOfSymbols = counter; - }); - - LearnResultResource.getAllFinal(scope.project.id) - .then(function (results) { - scope.numberOfTests = results.length; - }) - } - } -}()); \ No newline at end of file diff --git a/main/src/main/webapp/app/modules/core/directives/responsiveIframe.js b/main/src/main/webapp/app/modules/core/directives/responsiveIframe.js deleted file mode 100644 index 2af65d498..000000000 --- a/main/src/main/webapp/app/modules/core/directives/responsiveIframe.js +++ /dev/null @@ -1,44 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.core') - .directive('responsiveIframe', responsiveIframe); - - responsiveIframe.$inject = ['$window']; - - /** - * This directive changes the dimensions of an element to its parent element. Optionally you can trigger this - * behaviour by passing the value 'true' to the parameter bindResize so that every time the window resizes, - * the dimensions of the element will be updated. - * - * @param $window - * @returns {{restrict: string, link: link}} - */ - function responsiveIframe($window) { - return { - restrict: 'A', - link: link - }; - - function link(scope, el) { - var parent = el.parent()[0]; - - $window.addEventListener('resize', fitToParent); - - scope.$on('$destroy', function(){ - $window.removeEventListener('resize', fitToParent) - }); - - /** - * Set the element to the dimensions of its parent - */ - function fitToParent() { - el[0].setAttribute('width', parent.offsetWidth); - el[0].setAttribute('height', parent.offsetHeight); - } - - fitToParent(); - } - } -}()); \ No newline at end of file diff --git a/main/src/main/webapp/app/modules/core/directives/sidebar.js b/main/src/main/webapp/app/modules/core/directives/sidebar.js deleted file mode 100644 index 6713157c5..000000000 --- a/main/src/main/webapp/app/modules/core/directives/sidebar.js +++ /dev/null @@ -1,70 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.core') - .directive('sidebar', navigation); - - navigation.$inject = ['paths', '$state', 'SessionService']; - - function navigation(paths, $state, Session) { - return { - scope: {}, - replace: true, - templateUrl: paths.COMPONENTS + '/core/views/directives/sidebar.html', - link: link - }; - - function link(scope) { - - /** - * The project that is stored in the session - * @type {Project|null} - */ - scope.project = Session.project.get(); - - /** - * Indicator for the collapsed state - * @type {boolean} - */ - scope.collapsed = false; - - // handle events and stuff - (function init() { - - // load project into scope when projectOpened is emitted - scope.$on('project.opened', function () { - scope.project = Session.project.get(); - }); - - // delete project from scope when projectOpened is emitted - scope.$on('project.closed', function () { - scope.project = null; - }); - }()); - - /** Removes the project object from the session and redirect to the start page */ - scope.closeProject = function () { - Session.project.remove(); - $state.go('home'); - }; - - /** Toggles the collapsed state **/ - scope.toggleCollapse = function () { - scope.collapsed = !scope.collapsed; - }; - - /** - * Checks if at least one of the passed arguments is the current state name. Arguments should be strings - * Use: isState('state1', 'state2', ...) - */ - scope.isState = function () { - var result = false; - for (var i = 0; i < arguments.length; i++) { - result = result || $state.current.name === arguments[i]; - } - return result; - }; - } - } -}()); \ No newline at end of file diff --git a/main/src/main/webapp/app/modules/core/directives/symbolGroupList.js b/main/src/main/webapp/app/modules/core/directives/symbolGroupList.js deleted file mode 100644 index f2eda2b94..000000000 --- a/main/src/main/webapp/app/modules/core/directives/symbolGroupList.js +++ /dev/null @@ -1,65 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.core') - .directive('symbolGroupList', symbolGroupList) - .directive('symbolGroupListItem', symbolGroupListItem) - .directive('symbolGroupListItemHeader', symbolGroupListItemHeader) - .directive('symbolGroupListItemContent', symbolGroupListItemContent); - - function symbolGroupList() { - return { - transclude: true, - template: '
' - } - } - - function symbolGroupListItem() { - return { - transclude: true, - scope: { - group: '=' - }, - template: '
' + - '
' + - '
', - controller: ['$scope', function (scope) { - this.getGroup = function () { - return scope.group - } - }] - } - } - - function symbolGroupListItemHeader() { - return { - require: '^symbolGroupListItem', - transclude: true, - template: '
' + - ' ' + - ' ' + - ' ' + - ' ' + - '
' + - '
' + - '


' + - ' ' + - ' Symbols' + - ' ' + - '
' + - '
', - link: function (scope, el, attrs, ctrl) { - scope.group = ctrl.getGroup(); - } - } - } - - function symbolGroupListItemContent() { - return { - transclude: true, - template: '
' - } - } -}()); \ No newline at end of file diff --git a/main/src/main/webapp/app/modules/core/directives/symbolList.js b/main/src/main/webapp/app/modules/core/directives/symbolList.js deleted file mode 100644 index 7e4bc097e..000000000 --- a/main/src/main/webapp/app/modules/core/directives/symbolList.js +++ /dev/null @@ -1,32 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.core') - .directive('symbolList', symbolList) - .directive('symbolListItem', symbolListItem); - - symbolListItem.$inject = ['paths']; - - function symbolList() { - return { - transclude: true, - template: '
' - } - } - - function symbolListItem(paths) { - return { - replace: true, - transclude: true, - scope: { - symbol: '=' - }, - templateUrl: paths.COMPONENTS + '/core/views/directives/symbol-list-item.html', - link: function (scope, el, attrs) { - scope.isSelectable = angular.isDefined(attrs.selectionModel) ? true : false; - scope.showActionsLink = angular.isDefined(attrs.showActionsLink) ? true : false; - } - } - } -}()); \ No newline at end of file diff --git a/main/src/main/webapp/app/modules/core/directives/viewHeading.js b/main/src/main/webapp/app/modules/core/directives/viewHeading.js deleted file mode 100644 index c5915419d..000000000 --- a/main/src/main/webapp/app/modules/core/directives/viewHeading.js +++ /dev/null @@ -1,33 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.core') - .directive('viewHeader', viewHeader); - - viewHeader.$inject = ['paths']; - - /** - * A directive that is used as a shortcut for the heading of a page to save some coding. Use it on every page that - * should have a header with a title and a sub-title. The directive accepts a parameter 'title' and 'subTile' which - * only accept static values. - * - * Is transcludable so that child elements can be added before the title. So just add buttons or additional text - * there. - * - * Use it like ' ... ' - * - * Template: 'views/directives/view-header.html' - * - * @returns {{scope: {title: string, subTitle: string}, transclude: boolean, templateUrl: string}} - */ - function viewHeader(paths) { - return { - scope: { - title: '@' - }, - transclude: true, - templateUrl: paths.COMPONENTS + '/core/views/directives/view-header.html' - } - } -}()); \ No newline at end of file diff --git a/main/src/main/webapp/app/modules/core/directives/widgets.js b/main/src/main/webapp/app/modules/core/directives/widgets.js deleted file mode 100644 index 138652fde..000000000 --- a/main/src/main/webapp/app/modules/core/directives/widgets.js +++ /dev/null @@ -1,238 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.core') - .directive('widget', widget) - .directive('counterexamplesWidget', counterexamplesWidget) - .directive('learnResumeSettingsWidget', learnResumeSettingsWidget); - - widget.$inject = ['paths']; - counterexamplesWidget.$inject = [ - 'paths', 'CounterExampleService', 'LearnerService', 'ToastService', 'SymbolResource', '$q' - ]; - learnResumeSettingsWidget.$inject = ['paths', 'EqOracle']; - - - /** - * The directive for displaying a widget without content. Use is a a wrapper for any content you like. - * - * Attribute 'title' {string} can be applied for displaying a widget title. - * - * Use: '' - * - * @param paths - The applications constant for paths - * @returns {{scope: {title: string}, templateUrl: string, transclude: boolean, link: link}} - */ - function widget(paths) { - return { - scope: { - title: '@' - }, - templateUrl: paths.COMPONENTS + '/core/views/directives/widget.html', - transclude: true, - link: link - }; - - function link(scope) { - - /** - * The title that should be displayed in the widget header - * @type {string} - */ - scope.title = scope.title || 'Untitled'; - } - } - - - /** - * The directive for the content of the counterexample widget that is used to create and test counterexamples. - * Should be included into a directive for visual appeal. - * - * Attribute 'counterexamples' {array} should be the model where the created counterexamples are put into. - * - * Use: '
' - * - * @param paths - The application paths constant - * @param CounterExampleService - The service for sharing a counterexample with a hypothesis - * @param Learner - The LearnerService for communication with the Learner - * @param Toast - The ToastService - * @param SymbolResource - * @param $q - The angular $q service - * @returns {{scope: {counterexamples: string}, templateUrl: string, link: link}} - */ - function counterexamplesWidget(paths, CounterExampleService, Learner, Toast, SymbolResource, $q) { - return { - scope: { - counterexamples: '=', - learnResult: '=' - }, - templateUrl: paths.COMPONENTS + '/core/views/directives/counterexamples-widget.html', - link: link - }; - - function link(scope) { - - var symbols = []; - - /** - * The array of input output pairs of the shared counterexample - * @type {Array} - */ - scope.counterExample = []; - - /** - * A list of counterexamples for editing purposes without manipulation the actual model - * @type {Object[]} - */ - scope.tmpCounterExamples = []; - - // update the model - function renewCounterexamples() { - scope.counterexamples = scope.tmpCounterExamples; - } - - function init() { - scope.counterExample = CounterExampleService.getCurrentCounterexample() - } - - init(); - - /** - * Removes a input output pair from the temporary counterexamples array. - * - * @param {number} i - The index of the pair to remove - */ - scope.removeInputOutputAt = function (i) { - scope.counterExample.splice(i, 1); - }; - - /** - * Adds a new counterexample to the scope and the model - */ - scope.testAndAddCounterExample = function () { - testCounterExample() - .then(function (counterexample) { - Toast.success('The selected word is a counterexample'); - - for (var i = 0; i < counterexample.length; i++) { - scope.counterExample[i].output = counterexample[i]; - } - scope.tmpCounterExamples.push(scope.counterExample); - CounterExampleService.resetCurrentCounterexample(); - renewCounterexamples(); - init(); - }) - .catch(function () { - Toast.danger('The word is not a counterexample'); - }) - }; - - /** - * Removes a counterexample from the temporary and the model - * - * @param {number} i - the index of the pair in the temporary list of counterexamples - */ - scope.removeCounterExampleAt = function (i) { - scope.tmpCounterExamples.splice(i, 1); - renewCounterexamples(); - }; - - /** - * Tests if the entered counterexample really is one by sending it to the server for testing purposes. - */ - function testCounterExample() { - var resetSymbol = scope.learnResult.configuration.resetSymbol; - var deferred = $q.defer(); - - if (symbols.length === 0) { - SymbolResource.getByIdRevisionPairs(scope.learnResult.project, - scope.learnResult.configuration.symbols) - .then(function (s) { - symbols = s; - test(); - }); - } else { - test(); - } - - function test() { - var testSymbols = []; - - // find id/revision pairs of symbols from abbreviation in learnResult - for (var i = 0; i < scope.counterExample.length; i++) { - for (var j = 0; j < symbols.length; j++) { - if (scope.counterExample[i].input === symbols[j].abbreviation) { - testSymbols.push(symbols[j].getIdRevisionPair()); - } - } - } - - Learner.isCounterexample(scope.learnResult.project, resetSymbol, testSymbols) - .then(function (ce) { - var ceFound = false; - for (var i = 0; i < ce.length; i++) { - if (ce[i] !== scope.counterExample[i].output) { - ceFound = true; - break; - } - } - if (ceFound) { - deferred.resolve(ce); - } else { - deferred.reject(); - } - }) - } - - return deferred.promise; - } - } - } - - - /** - * The directive for the widget of the sidebar where learn resume configurations can be edited. Should be included - * into a
directive for visual appeal. - * - * Expects an attribute 'learnConfiguration' attached to the element whose value should be a LearnConfiguration - * object. - * - * Use:
- * - * @param paths - The constant for applications paths - * @param EqOracle - * @returns {{scope: {learnConfiguration: string}, templateUrl: string, link: link}} - */ - function learnResumeSettingsWidget(paths, EqOracle) { - return { - scope: { - learnConfiguration: '=' - }, - templateUrl: paths.COMPONENTS + '/core/views/directives/learn-resume-settings-widget.html', - link: link - }; - - function link(scope) { - - /** - * The dictionary for eq oracle types - * @type {Object} - */ - scope.eqOracles = EqOracle.types; - - /** - * The selected eq oracle type from the select box - * @type {string} - */ - scope.selectedEqOracle = scope.learnConfiguration.eqOracle.type; - - /** - * Creates a new eq oracle object from the selected type and assigns it to the configuration - */ - scope.setEqOracle = function () { - scope.learnConfiguration.eqOracle = EqOracle.build(scope.selectedEqOracle); - }; - } - } -}()); \ No newline at end of file diff --git a/main/src/main/webapp/app/modules/core/filters/formatFilter.js b/main/src/main/webapp/app/modules/core/filters/formatFilter.js deleted file mode 100644 index 5b1629a98..000000000 --- a/main/src/main/webapp/app/modules/core/filters/formatFilter.js +++ /dev/null @@ -1,119 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.core') - .filter('formatEnumKey', formatEnumKey) - .filter('formatEqOracle', formatEqOracle) - .filter('formatAlgorithm', formatAlgorithm) - .filter('formatMilliseconds', formatMilliseconds); - - formatEqOracle.$inject = ['EqOracle']; - formatAlgorithm.$inject = ['learnAlgorithms']; - - /** - * The filter that formats something like 'A_CONSTANT_KEY' to 'A Constant Key' - * - * @returns {filter} - */ - function formatEnumKey() { - return filter; - - /** - * @param {string} string - The enum key in upper snake case format - * @returns {string} - */ - function filter(string) { - return string.toLowerCase().split('_').join(' ').replace(/\w\S*/g, function (txt) { - return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase(); - }) - } - } - - - /** - * The filter to format a EQ type constant to something more readable - * - * @param {Object} EqOracle - The eq oracle model - * @returns {filter} - */ - function formatEqOracle(EqOracle) { - return filter; - - /** - * @param {string} type - The eq oracle type - * @returns {string} - */ - function filter(type) { - switch (type) { - case EqOracle.types.RANDOM: - return 'Random Word'; - case EqOracle.types.COMPLETE: - return 'Complete'; - case EqOracle.types.SAMPLE: - return 'Sample'; - default: - return type; - } - } - } - - /** - * The filter to format a learn algorithm name to something more readable - - * @param {Object} learnAlgorithms - The dictionary of learn algorithms - * @returns {filter} - */ - function formatAlgorithm(learnAlgorithms) { - return filter; - - /** - * @param {string} name - the name of a learn algorithm - * @returns {string} - */ - function filter(name) { - switch (name) { - case learnAlgorithms.LSTAR: - return 'L*'; - case learnAlgorithms.DHC: - return 'DHC'; - case learnAlgorithms.TTT: - return 'TTT'; - case learnAlgorithms.DISCRIMINATION_TREE: - return 'Discrimination Tree'; - default: - return name; - } - } - } - - /** - * The filter takes a number representing milliseconds and formats it to [h] [min] s - * @returns {filter} - */ - function formatMilliseconds() { - return filter; - - /** - * @param ms - The number in ms to format - * @returns {string} - */ - function filter(ms) { - var hours, minutes, seconds; - - if (ms >= 3600000) { - hours = Math.floor(ms / 3600000); - ms = ms % 3600000; - minutes = Math.floor(ms / 60000); - seconds = Math.floor((ms % 60000) / 1000); - return hours + 'h ' + minutes + 'min ' + seconds + 's'; - } else if (ms >= 60000) { - minutes = Math.floor(ms / 60000); - return minutes + 'min ' + Math.floor((ms % 60000) / 1000) + 's'; - } else { - return Math.floor(ms / 1000) + 's'; - } - } - } - -}()); \ No newline at end of file diff --git a/main/src/main/webapp/app/modules/core/init.js b/main/src/main/webapp/app/modules/core/init.js deleted file mode 100644 index 3a191d696..000000000 --- a/main/src/main/webapp/app/modules/core/init.js +++ /dev/null @@ -1,33 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.core', []) - - .config(['ngToastProvider', 'selectionModelOptionsProvider', - function (ngToastProvider, selectionModelOptionsProvider) { - - // configure ngToast toast position - ngToastProvider.configure({ - verticalPosition: 'bottom', - horizontalPosition: 'center', - maxNumber: 1, - additionalClasses: 'animate-toast' - }); - - // default options for selection model - selectionModelOptionsProvider.set({ - selectedAttribute: '_selected', - selectedClass: 'selected', - mode: 'multiple', - cleanupStrategy: 'deselect' - }); - }]) - - .run(['$rootScope', '_', - function ($rootScope, _) { - - // make lodash available for use in templates - $rootScope._ = _; - }]) -}()); \ No newline at end of file diff --git a/main/src/main/webapp/app/modules/core/models/EqOracle.js b/main/src/main/webapp/app/modules/core/models/EqOracle.js deleted file mode 100644 index 5e24847b3..000000000 --- a/main/src/main/webapp/app/modules/core/models/EqOracle.js +++ /dev/null @@ -1,98 +0,0 @@ -(function () { - - angular - .module('ALEX.core') - .factory('EqOracle', EqOracleModel); - - /** - * Contains models for eq oracles - * - * @returns {{}} - * @constructor - */ - function EqOracleModel() { - - var EqOracle = {}; - - /** - * The dictionary of eq oracle types - * - * @type {{RANDOM: string, COMPLETE: string, SAMPLE: string}} - */ - EqOracle.types = { - RANDOM: 'random_word', - COMPLETE: 'complete', - SAMPLE: 'sample' - }; - - /** - * The model for an eq oracle that searches randomly for counter examples - * - * @param {number} minLength - The minimum length of a word that should be created - * @param {number} maxLength - The maximum length of a word that should be created - * @param {number} maxNoOfTests - The maximum number of words that are generated - * @constructor - */ - EqOracle.Random = function (minLength, maxLength, maxNoOfTests) { - this.type = EqOracle.types.RANDOM; - this.minLength = angular.isDefined(minLength) && minLength > 0 ? minLength : 1; - this.maxLength = angular.isDefined(maxLength) && maxLength > 0 ? maxLength : 1; - this.maxNoOfTests = angular.isDefined(maxNoOfTests) && maxNoOfTests > 0 ? maxNoOfTests : 1; - }; - - /** - * The model to the complete eq oracle - * - * @param {number} minDepth - The minimum depth - * @param {number} maxDepth - The maximum depth - * @constructor - */ - EqOracle.Complete = function (minDepth, maxDepth) { - this.type = EqOracle.types.COMPLETE; - this.minDepth = angular.isDefined(minDepth) && minDepth > 0 ? minDepth : 1; - this.maxDepth = angular.isDefined(maxDepth) && maxDepth > 0 ? maxDepth : 1; - }; - - /** - * The model of a sample eq oracle - * - * @param {{input: string, output: string}[]} counterExamples - * @constructor - */ - EqOracle.Sample = function (counterExamples) { - this.type = EqOracle.types.SAMPLE; - this.counterExamples = counterExamples || []; - }; - - /** - * Creates an instances of an eq oracle from an object or an eq oracle type - * - * @param {Object|string} data - The object presenting an eq oracle or the string of an eq oracle type - * @returns {*|null} - An instance of an eq oracle or null - */ - EqOracle.build = function (data) { - function create(data) { - switch (data.type) { - case EqOracle.types.RANDOM: - return new EqOracle.Random(data.minLength, data.maxLength, data.maxNoOfTests); - case EqOracle.types.COMPLETE: - return new EqOracle.Complete(data.minDepth, data.maxDepth); - case EqOracle.types.SAMPLE: - return new EqOracle.Sample(data.counterExamples); - default : - return null; - } - } - - if (angular.isObject(data)) { - return create(data); - } else if (angular.isString(data)) { - return create({type: data}) - } else { - return null; - } - }; - - return EqOracle; - } -}()); \ No newline at end of file diff --git a/main/src/main/webapp/app/modules/core/models/LearnConfiguration.js b/main/src/main/webapp/app/modules/core/models/LearnConfiguration.js deleted file mode 100644 index e7e3d672c..000000000 --- a/main/src/main/webapp/app/modules/core/models/LearnConfiguration.js +++ /dev/null @@ -1,89 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.core') - .factory('LearnConfiguration', LearnConfigurationFactory); - - LearnConfigurationFactory.$inject = ['learnAlgorithms', 'EqOracle']; - - /** - * The factory for creating LearnConfiguration objects that are used to configure a learning process - * - * @param learnAlgorithms - The dictionary for learning algorithms - * @param EqOracle - The factory for EQ-oracles - * @returns {LearnConfiguration} - * @constructor - */ - function LearnConfigurationFactory(learnAlgorithms, EqOracle) { - - /** - * The model for a learning configuration - * @constructor - */ - function LearnConfiguration() { - this.symbols = []; - this.maxAmountOfStepsToLearn = 0; - this.eqOracle = new EqOracle.Random(1, 10, 20); - this.algorithm = learnAlgorithms.TTT; - this.resetSymbol = null; - this.comment = null; - } - - /** - * Remove properties from a learning configuration that aren't needed for resuming a learning process - * - * @returns {LearnConfiguration} - The reduced learning configuration - */ - LearnConfiguration.prototype.toLearnResumeConfiguration = function () { - delete this.symbols; - delete this.algorithm; - delete this.resetSymbol; - delete this.comment; - return this; - }; - - /** - * Sets the reset symbols for the configuration - * - * @param {Symbol} symbol - */ - LearnConfiguration.prototype.setResetSymbol = function (symbol) { - this.resetSymbol = { - id: symbol.id, - revision: symbol.revision - }; - }; - - /** - * Adds a symbol to the configuration that should be used to learn an application - * - * @param {Symbol} symbol - */ - LearnConfiguration.prototype.addSymbol = function (symbol) { - this.symbols.push({ - id: symbol.id, - revision: symbol.revision - }); - }; - - /** - * Creates an instance of LearnConfiguration from an object - * - * @param {Object} data - The object that is used to create a LearnConfiguration from - * @returns {LearnConfiguration} - */ - LearnConfiguration.build = function (data) { - return angular.extend(new LearnConfiguration(), { - comment: data.comment, - symbols: data.symbols, - maxAmountOfStepsToLearn: data.maxAmountOfStepsToLearn, - algorithm: data.algorithm, - eqOracle: EqOracle.build(data.eqOracle), - resetSymbol: data.resetSymbol - }); - }; - - return LearnConfiguration; - } -}()); \ No newline at end of file diff --git a/main/src/main/webapp/app/modules/core/models/LearnResult.js b/main/src/main/webapp/app/modules/core/models/LearnResult.js deleted file mode 100644 index 2a9a4a7ea..000000000 --- a/main/src/main/webapp/app/modules/core/models/LearnResult.js +++ /dev/null @@ -1,80 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.core') - .factory('LearnResult', LearnResultFactory); - - LearnResultFactory.$inject = ['LearnConfiguration', '_']; - - /** - * The factory for the model of a learn result - * - * @param LearnConfiguration - The factory for LearnConfiguration - * @param _ - Lodash - * @returns {LearnResult} - * @constructor - */ - function LearnResultFactory(LearnConfiguration, _) { - - /** - * The model of a learn result - * - * @constructor - */ - function LearnResult() { - } - - /** - * Indicates if an error has been recorded during the step - * - * @returns {boolean} - */ - LearnResult.prototype.hasError = function () { - return angular.isDefined(this.error); - }; - - /** - * Creates a new instance of a LearnResult from an object - * - * @param {Object} data - The object the learn result should be build from - * @returns {LearnResult} - The instance of LearnResult from the data - */ - LearnResult.build = function (data) { - return angular.extend(new LearnResult(), { - configuration: LearnConfiguration.build(data.configuration), - hypothesis: data.hypothesis, - project: data.project, - sigma: data.sigma, - stepNo: data.stepNo, - testNo: data.testNo, - algorithmInformation: data.algorithmInformation, - statistics: data.statistics, - error: data.error, - errorText: data.errorText - }); - }; - - /** - * Creates LearnResult[s] from an API response - * - * @param response - * @returns {*} - */ - LearnResult.transformApiResponse = function (response) { - if (angular.isArray(response.data)) { - if (angular.isArray(response.data[0])) { - return _.forEach(response.data, function (completeResult) { - _.map(completeResult, LearnResult.build); - }) - } else { - return _.map(response.data, LearnResult.build); - } - } else { - return LearnResult.build(response.data); - } - }; - - return LearnResult; - } -}()); \ No newline at end of file diff --git a/main/src/main/webapp/app/modules/core/models/Project.js b/main/src/main/webapp/app/modules/core/models/Project.js deleted file mode 100644 index 372cfebfb..000000000 --- a/main/src/main/webapp/app/modules/core/models/Project.js +++ /dev/null @@ -1,70 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.core') - .factory('Project', ProjectFactory); - - ProjectFactory.$inject = ['_']; - - /** - * The factory for the model of a project. - * - * @param _ - Lodash - * @returns {Project} - * @constructor - */ - function ProjectFactory(_) { - - /** - * The model for a Project - * - * @param {string} name - The name of the project - * @param {string} baseUrl - The base URL of the project - * @param {string} description - The description of the project - * @constructor - */ - function Project(name, baseUrl, description) { - this.name = name || null; - this.baseUrl = baseUrl || null; - this.description = description || null; - } - - /** - * Create an instance of Project from an object - * - * @param {Object} data - The object the project is created from - * @returns {Project} - */ - Project.build = function (data) { - return angular.extend(new Project( - data.name, - data.baseUrl, - data.description - ), { - id: data.id, - groups: data.groups - }); - }; - - /** - * Create [an] instance[s] of Project from a HTTP response - * - * @param {Object} response - The response object from the API - * @returns {Project|Project[]} - The Project[s] - */ - Project.transformApiResponse = function (response) { - if (angular.isArray(response.data)) { - if (response.data.length > 0) { - return _.map(response.data, Project.build); - } else { - return []; - } - } else { - return Project.build(response.data); - } - }; - - return Project; - } -}()); \ No newline at end of file diff --git a/main/src/main/webapp/app/modules/core/models/Symbol.js b/main/src/main/webapp/app/modules/core/models/Symbol.js deleted file mode 100644 index 617fa8485..000000000 --- a/main/src/main/webapp/app/modules/core/models/Symbol.js +++ /dev/null @@ -1,97 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.core') - .factory('Symbol', SymbolModel); - - SymbolModel.$inject = ['_', 'ActionBuilder']; - - /** - * The factory for the symbol model. - * - * @param _ - Lodash - * @param ActionBuilder - The factory that creates Actions - * @returns {Symbol} - The symbol model - * @constructor - */ - function SymbolModel(_, ActionBuilder) { - - /** - * The symbol model - * - * @param {string} name - The name of the symbol - * @param {string} abbreviation - The abbreviation of the symbol - * @constructor - */ - function Symbol(name, abbreviation) { - this.name = name || null; - this.abbreviation = abbreviation || null; - this.actions = []; - } - - /** - * Builds a symbol instance from an object - * - * @param {Object} data - The data the symbol instance should be build from - * @returns {Symbol} - The symbol instance - */ - Symbol.build = function (data) { - return angular.extend(new Symbol( - data.name, - data.abbreviation - ), { - actions: data.actions ? ActionBuilder.createFromObjects(data.actions) : [], - id: data.id, - revision: data.revision, - project: data.project, - hidden: data.hidden, - group: data.group - }); - }; - - /** - * Creates [an] instance[s] of Symbol from a HTTP response - * - * @param {Object} response - The response object from the API - * @returns {Symbol|Symbol[]} - The Symbol[s] - */ - Symbol.transformApiResponse = function (response) { - if (angular.isArray(response.data)) { - if (response.data.length > 0) { - return _.map(response.data, Symbol.build); - } else { - return []; - } - } else { - return Symbol.build(response.data); - } - }; - - /** - * Counts the number of actions that are not skipped by the learner - * - * @returns {number} - The amount of enabled actions - */ - Symbol.prototype.countEnabledActions = function () { - for (var i = 0, c = 0; i < this.actions.length; i++) { - c += this.actions[i].disabled ? 0 : 1; - } - return c; - }; - - /** - * Get the id and revision of the symbol as a pair - * - * @returns {{id: number, revision: number}} - */ - Symbol.prototype.getIdRevisionPair = function () { - return { - id: this.id, - revision: this.revision - } - }; - - return Symbol; - } -}()); \ No newline at end of file diff --git a/main/src/main/webapp/app/modules/core/models/SymbolGroup.js b/main/src/main/webapp/app/modules/core/models/SymbolGroup.js deleted file mode 100644 index 769cca991..000000000 --- a/main/src/main/webapp/app/modules/core/models/SymbolGroup.js +++ /dev/null @@ -1,65 +0,0 @@ -(function () { - - angular - .module('ALEX.core') - .factory('SymbolGroup', SymbolGroupFactory); - - SymbolGroupFactory.$inject = ['Symbol', '_']; - - /** - * The service for the model of a symbol group - * - * @param Symbol - The Symbol model - * @param _ - Lodash - * @returns {SymbolGroup} - * @constructor - */ - function SymbolGroupFactory(Symbol, _) { - - /** - * The symbol group model - * - * @param {string} name - The name of the symbol group - * @constructor - */ - function SymbolGroup(name) { - this.name = name || null; - } - - /** - * Creates a SymbolGroup instance from an object - * - * @param {Object} data - * @returns {SymbolGroup} - */ - SymbolGroup.build = function (data) { - return angular.extend(new SymbolGroup(data.name), { - id: data.id, - project: data.project, - symbols: _.map(data.symbols, Symbol.build) - }); - }; - - /** - * Creates SymbolGroup object[s] from a HTTP response. Removes all hidden symbols because they aren't needed - * - * @param {Object} response - The HTTP response - * @returns {SymbolGroup|SymbolGroup[]} - */ - SymbolGroup.transformApiResponse = function (response) { - if (angular.isArray(response.data)) { - return _(response.data) - // remove hidden symbols because they are never needed - .forEach(function (group) { - group.symbols = _.filter(group.symbols, {hidden: false}) - }) - .map(SymbolGroup.build) - .value(); - } else { - return SymbolGroup.build(response.data); - } - }; - - return SymbolGroup; - } -}()); \ No newline at end of file diff --git a/main/src/main/webapp/app/modules/core/resources/CounterResource.js b/main/src/main/webapp/app/modules/core/resources/CounterResource.js deleted file mode 100644 index fd0513060..000000000 --- a/main/src/main/webapp/app/modules/core/resources/CounterResource.js +++ /dev/null @@ -1,74 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.core') - .factory('CounterResource', CounterResource); - - CounterResource.$inject = ['$http', 'paths']; - - /** - * The service that communicates with the API in order to read and delete counters. Counters are objects consisting - * of a unique 'name' property, a 'value' which holds the current value of the counter in the database and 'project' - * for the projects id. - * - * Example: {"name": "i", "value": 10, "project": 1} - * - * @param $http - angular $http service - * @param paths - application paths constants - * @returns {{getAll: getAll, delete: remove, deleteSome: deleteSome}} - * @constructor - */ - function CounterResource($http, paths) { - return { - getAll: getAll, - delete: remove, - deleteSome: deleteSome - }; - - /** - * Makes a GET request to /rest/projects/{projectId}/counters in order to fetch all counter of the current - * project. - * - * @param {number} projectId - The id of a project - * @returns angular promise object of the request - */ - function getAll(projectId) { - return $http.get(paths.api.URL + '/projects/' + projectId + '/counters') - .then(function (response) { - return response.data; - }) - } - - /** - * Makes a DELETE request to /rest/projects/{projectId}/counters/{counterName} in order to delete a counter from - * the database. - * - * @param {number} projectId - The id of a project - * @param {string} name - The name of a counter - * @returns angular promise object of the request - */ - function remove(projectId, name) { - return $http.delete(paths.api.URL + '/projects/' + projectId + '/counters/' + name) - .then(function (response) { - return response.data; - }) - } - - /** - * Makes a DELETE request to /rest/projects/{projectId}/counters/batch/{counterNames} in order to delete - * multiple counters from the database - * - * @param {number} projectId - The id of a project - * @param {string[]} names - A list of the names of counters - * @returns angular promise object of the request - */ - function deleteSome(projectId, names) { - var n = names.join(','); - return $http.delete(paths.api.URL + '/projects/' + projectId + '/counters/batch/' + n) - .then(function (response) { - return response.data; - }) - } - } -}()); \ No newline at end of file diff --git a/main/src/main/webapp/app/modules/core/resources/FileResource.js b/main/src/main/webapp/app/modules/core/resources/FileResource.js deleted file mode 100644 index 8c47da634..000000000 --- a/main/src/main/webapp/app/modules/core/resources/FileResource.js +++ /dev/null @@ -1,46 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.core') - .factory('FileResource', Resource); - - Resource.$inject = ['$http', 'paths']; - - /** - * The resource that handles API calls concerning the management of files. - * - * @param $http - The angular $http service - * @param paths - The ALEX paths constant - * @returns {{getAll: getAll, delete: remove}} - * @constructor - */ - function Resource($http, paths) { - return { - getAll: getAll, - delete: remove - }; - - /** - * Fetches all available files from the server that belong to a project - * - * @param {number} projectId - The id of the project - */ - function getAll(projectId) { - return $http.get(paths.api.URL + '/projects/' + projectId + '/files') - .then(function (response) { - return response.data; - }) - } - - /** - * Deletes a single file from the server - * - * @param {number} projectId - The id of the project - * @param {File} file - The file object to be deleted - */ - function remove(projectId, file) { - return $http.delete(paths.api.URL + '/projects/' + projectId + '/files/' + encodeURI(file.name)) - } - } -}()); \ No newline at end of file diff --git a/main/src/main/webapp/app/modules/core/resources/LearnResultResource.js b/main/src/main/webapp/app/modules/core/resources/LearnResultResource.js deleted file mode 100644 index f1d722553..000000000 --- a/main/src/main/webapp/app/modules/core/resources/LearnResultResource.js +++ /dev/null @@ -1,106 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.core') - .factory('LearnResultResource', Resource); - - Resource.$inject = ['$http', 'paths', 'LearnResult', '_']; - - /** - * The resource that handles http request to the API to do CRUD operations on learn results - * - * @param $http - The angular http service - * @param paths - The constant with application paths - * @param LearnResult - The factory for LearnResult objects - * @param _ - Lodash - * @returns {{getFinal: getFinal, getAllFinal: getAllFinal, getComplete: getComplete, delete: remove}} - * @constructor - */ - function Resource($http, paths, LearnResult, _) { - return { - getFinal: getFinal, - getAllFinal: getAllFinal, - getComplete: getComplete, - delete: remove - }; - - /** - * Makes a GET request to 'rest/projects/{projectId}/results in order to get all final learn results of all - * tests of a project. - * - * @param {number} projectId - The id of the project whose final learn results should be fetched - * @returns {*} - A promise with the learn results - */ - function getAllFinal(projectId) { - return $http.get(paths.api.URL + '/projects/' + projectId + '/results') - .then(LearnResult.transformApiResponse); - } - - /** - * Makes a GET request to 'rest/projects/{projectId}/results/{testNo}' in order to get the final cumulated - * learn result from a test - * - * @param {number} projectId - The id of the project - * @param {number} testNo - The number of the test run - * @returns {*} - A promise - */ - function getFinal(projectId, testNo) { - return $http.get(paths.api.URL + '/projects/' + projectId + '/results/' + testNo) - .then(LearnResult.transformApiResponse); - } - - /** - * Makes a GET request to 'rest/projects/{projectId}/results/{testNos}/complete in order to get all intermediate - * results of a test. - * - * @param {number} projectId - The id of the project of the test - * @param {number, number[]} testNos - The number[s] of the test[s] that should be completely fetched - * @returns {*} - A promise with a list of learn results - */ - function getComplete(projectId, testNos) { - if (angular.isArray(testNos)) { - return $http.get(paths.api.URL + '/projects/' + projectId + '/results/' + testNos.join(',') + '/complete') - .then(function (response) { - if (response.data.length > 0) { - if (!angular.isArray(response.data[0])) { - response.data.shift(); - return [LearnResult.transformApiResponse(response)] - } else { - _.forEach(response.data, function (data) { - data.shift(); // remove cumulated results from the beginning - }); - return LearnResult.transformApiResponse(response); - } - } else { - return [[]]; - } - }) - } else { - return $http.get(paths.api.URL + '/projects/' + projectId + '/results/' + testNos + '/complete') - .then(function (response) { - response.data.shift(); - return LearnResult.transformApiResponse(response); - }) - } - } - - - /** - * Makes a DELETE request to /rest/{projectId}/results/{testNos} in order to delete learning results - * - * @param {LearnResult|LearnResult[]} results - */ - function remove(results) { - var testNos, projectId; - if (angular.isArray(results)) { - testNos = _.pluck(results, 'testNo').join(','); - projectId = results[0].project; - } else { - testNos = results.testNo; - projectId = results.project; - } - return $http.delete(paths.api.URL + '/projects/' + projectId + '/results/' + testNos, {}) - } - } -}()); \ No newline at end of file diff --git a/main/src/main/webapp/app/modules/core/resources/ProjectResource.js b/main/src/main/webapp/app/modules/core/resources/ProjectResource.js deleted file mode 100644 index cb48c3ed0..000000000 --- a/main/src/main/webapp/app/modules/core/resources/ProjectResource.js +++ /dev/null @@ -1,81 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.core') - .factory('ProjectResource', Resource); - - Resource.$inject = ['$http', 'paths', 'Project']; - - /** - * The resource that handles http calls to the API to do CRUD operations on projects - * - * @param $http - The $http angular service - * @param paths - The constant with application paths - * @param Project - Project factory - * @returns {{getAll: getAll, get: get, create: create, update: update, delete: remove}} - * @constructor - */ - function Resource($http, paths, Project) { - return { - getAll: getAll, - get: get, - create: create, - update: update, - delete: remove - }; - - /** - * Make a GET http request to /rest/projects in order to fetch all existing projects - * - * @returns {*} - */ - function getAll() { - return $http.get(paths.api.URL + '/projects') - .then(Project.transformApiResponse); - } - - /** - * Make a GET http request to /rest/projects/{id} in order to fetch a single project by its id - * - * @param {number} id - The id of the project that should be fetched - * @return {*} - */ - function get(id) { - return $http.get(paths.api.URL + '/projects/' + id) - .then(Project.transformApiResponse); - } - - /** - * Make a POST http request to /rest/projects with a project object as data in order to create a new project - * - * @param {Project} project - The project that should be created - * @return {*} - */ - function create(project) { - return $http.post(paths.api.URL + '/projects', project) - .then(Project.transformApiResponse); - } - - /** - * Make a PUT http request to /rest/projects with a project as data in order to update an existing project - * - * @param {Project} project - The updated instance of a project that should be updated on the server - * @return {*} - */ - function update(project) { - return $http.put(paths.api.URL + '/projects/' + project.id, project) - .then(Project.transformApiResponse); - } - - /** - * Make a DELETE http request to /rest/projects in order to delete an existing project - * - * @param {Project} project - The project that should be deleted - * @returns {HttpPromise} - */ - function remove(project) { - return $http.delete(paths.api.URL + '/projects/' + project.id) - } - } -}()); \ No newline at end of file diff --git a/main/src/main/webapp/app/modules/core/resources/SymbolGroupResource.js b/main/src/main/webapp/app/modules/core/resources/SymbolGroupResource.js deleted file mode 100644 index ad404eb2f..000000000 --- a/main/src/main/webapp/app/modules/core/resources/SymbolGroupResource.js +++ /dev/null @@ -1,81 +0,0 @@ -(function () { - - angular - .module('ALEX.core') - .factory('SymbolGroupResource', Resource); - - Resource.$inject = ['$http', 'paths', 'SymbolGroup']; - - /** - * The resource that handles http requests to the API to do CRUD operations on symbol groups - * - * @param $http - The angular $http service - * @param paths - The applications paths constant - * @param SymbolGroup - The factory for SymbolGroup objects - * @returns {{getAll: getAll, create: create, update: update, delete: remove}} - * @constructor - */ - function Resource($http, paths, SymbolGroup) { - return { - getAll: getAll, - create: create, - update: update, - delete: remove - }; - - /** - * Makes a GET request to /rest/projects/{projectId}/groups in order to fetch all symbol groups of a project. - * As options, an object with a property 'embedSymbols' with a boolean property can be passed. If 'embedSymbols' - * is true, then all symbols of all symbol groups will be fetched, too. Otherwise an empty symbols array. - * - * @param {number} projectId - The id of the project whose projects should be fetched - * @param {Object} [options] - An object that can have a boolean property 'embedSymbols' - * @returns {*} - An angular promise - */ - function getAll(projectId, options) { - var query = ''; - - if (options && options.embedSymbols && options.embedSymbols === true) { - query = '?embed=symbols'; - } - - return $http.get(paths.api.URL + '/projects/' + projectId + '/groups' + query) - .then(SymbolGroup.transformApiResponse); - } - - /** - * Makes a POST request to /rest/projects/{projectId}/groups in order to create a new symbol group. - * - * @param {number} projectId - The id of the project of the symbol group - * @param {number} group - The object of the symbol group that should be created - * @returns {*} - An angular promise - */ - function create(projectId, group) { - return $http.post(paths.api.URL + '/projects/' + projectId + '/groups', group) - .then(SymbolGroup.transformApiResponse); - } - - /** - * Makes a PUT request to /rest/projects/{projectId}/groups in order to update an existing symbol group. - * - * @param {SymbolGroup} group - The symbol group that should be updated - * @returns {*} - An angular promise - */ - function update(group) { - return $http.put(paths.api.URL + '/projects/' + group.project + '/groups/' + group.id, group) - .then(SymbolGroup.transformApiResponse); - } - - /** - * Makes a DELETE request to /rest/projects/{projectId}/groups/{groupId} in order to delete an existing symbol - * group. When deleted successfully, the symbols that belonged to the group are moved to the default group with - * the id 0. - * - * @param {SymbolGroup} group - The symbol group that should be deleted - * @returns {*} - An angular promise - */ - function remove(group) { - return $http.delete(paths.api.URL + '/projects/' + group.project + '/groups/' + group.id) - } - } -}()); \ No newline at end of file diff --git a/main/src/main/webapp/app/modules/core/resources/SymbolResource.js b/main/src/main/webapp/app/modules/core/resources/SymbolResource.js deleted file mode 100644 index 3b9a8ca7f..000000000 --- a/main/src/main/webapp/app/modules/core/resources/SymbolResource.js +++ /dev/null @@ -1,180 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.core') - .factory('SymbolResource', Resource); - - Resource.$inject = ['$http', 'paths', '_', 'Symbol']; - - /** - * The resource that handles http requests to the API to do CRUD operations on symbols - * - * @param $http - The angular $http service - * @param paths - The constant with application paths - * @param _ - Lodash - * @param Symbol - The factory for Symbol objects - * @returns {{get: get, getAll: getAll, getRevisions: getRevisions, create: create, update: update, delete: remove, move: move, recover: recover}} - * @constructor - */ - function Resource($http, paths, _, Symbol) { - return { - get: get, - getAll: getAll, - getByIdRevisionPairs: getByIdRevisionPairs, - getRevisions: getRevisions, - create: create, - update: update, - delete: remove, - move: move, - recover: recover - }; - - /** - * Make a GET request to /rest/projects/{projectId}/symbols/{symbolId} in order to fetch the latest revision of - * a symbol. - * - * @param {number} projectId - The id of the project the symbol belongs to - * @param {number} symbolId - The id of the symbol that should be fetched - */ - function get(projectId, symbolId) { - return $http.get(paths.api.URL + '/projects/' + projectId + '/symbols/' + symbolId) - .then(Symbol.transformApiResponse); - } - - /** - * Make a GET request to /rest/projects/{projectId}/symbols in oder to fetch all symbols, that means all latest - * revisions from symbols. - * - * As options, you can pass an object {deleted: true} which will get all latest revisions from deleted symbols. - * - * @param {number} projectId - The id of the project the symbols belong to - * @param {{deleted:boolean}} options - The query options as described in the functions description - * @returns {*} - */ - function getAll(projectId, options) { - var query; - if (options && options.deleted && options.deleted === true) { - query = '?visibility=hidden'; - } - return $http.get(paths.api.URL + '/projects/' + projectId + '/symbols' + (query ? query : '')) - .then(Symbol.transformApiResponse); - } - - /** - * Gets a list of symbols by a list of id/revision pairs - * {id_1}:{rev_1},...,{id_n}:{rev_n} - * - * @param {number} projectId - The id of the project - * @param {{id:number,revision:number}[]} idRevisionPairs - The list of id/revision pairs - * @returns {*} - */ - function getByIdRevisionPairs(projectId, idRevisionPairs) { - var pairs = _.map(idRevisionPairs, function (pair) { - return pair.id + ':' + pair.revision - }).join(','); - - return $http.get(paths.api.URL + '/projects/' + projectId + '/symbols/batch/' + pairs) - .then(Symbol.transformApiResponse) - } - - /** - * Make a GET request to /rest/projects/{projectId}/symbols/{symbolId}/complete in order to fetch all revisions. - * of a symbol - * - * @param {number} projectId - The id of the project the symbol belongs to - * @param {number} symbolId - The id of the symbol whose revisions should be fetched - * @returns {*} - */ - function getRevisions(projectId, symbolId) { - return $http.get(paths.api.URL + '/projects/' + projectId + '/symbols/' + symbolId + '/complete') - .then(Symbol.transformApiResponse); - } - - /** - * Make a POST request to /rest/projects/{projectId}/symbols[/batch] in order to create [a] new symbol[s]. - * - * @param {number} projectId - The id of the project the symbol should belong to - * @param {Symbol|Symbol[]} symbols - The symbol[s] that should be created - */ - function create(projectId, symbols) { - if (angular.isArray(symbols)) { - return $http.post(paths.api.URL + '/projects/' + projectId + '/symbols/batch', symbols) - .then(Symbol.transformApiResponse); - } else { - return $http.post(paths.api.URL + '/projects/' + projectId + '/symbols', symbols) - .then(Symbol.transformApiResponse); - } - } - - /** - * Makes a PUT request to /rest/projects/{projectId}/symbols[/batch]/{symbolId[s]}/moveTo/{groupId} in order to - * move [a] symbol[s] to another group without creating a new revision - * - * @param {Symbol|Symbol[]} symbols - The symbol[s] to be moved to another group - * @param {SymbolGroup} group - The id of the symbol group - * @returns {HttpPromise} - */ - function move(symbols, group) { - if (angular.isArray(symbols)) { - var symbolIds = _.pluck(symbols, 'id').join(','); - return $http.put(paths.api.URL + '/projects/' + group.project + '/symbols/batch/' + symbolIds + '/moveTo/' + group.id, {}) - } else { - return $http.put(paths.api.URL + '/projects/' + group.project + '/symbols/' + symbols.id + '/moveTo/' + group.id, {}) - } - } - - /** - * Make a PUT request to /rest/projects/{projectId}/symbols[/batch]/{symbolIds} in order to update a bunch of - * symbols at once - * - * @param {Symbol|Symbol[]} symbols - The symbol[s] to be updated - * @returns {*} - */ - function update(symbols) { - if (angular.isArray(symbols)) { - var symbolIds = _.pluck(symbols, 'id').join(','); - return $http.put(paths.api.URL + '/projects/' + symbols[0].project + '/symbols/batch/' + symbolIds, symbols) - .then(Symbol.transformApiResponse); - } else { - var symbol = symbols; - return $http.put(paths.api.URL + '/projects/' + symbol.project + '/symbols/' + symbol.id, symbol) - .then(Symbol.transformApiResponse); - } - } - - /** - * Make a POST request to /rest/projects/{projectId}/symbols[/batch]/{symbolId[s]}/hide in order to hide - * [a] symbol[s]. - * - * @param {Symbol|Symbol[]} symbols - The the symbol[s] that should be deleted - * @returns {*} - */ - function remove(symbols) { - if (angular.isArray(symbols)) { - var symbolIds = _.pluck(symbols, 'id').join(','); - return $http.post(paths.api.URL + '/projects/' + symbols[0].project + '/symbols/batch/' + symbolIds + '/hide', {}) - } else { - var symbol = symbols; - return $http.post(paths.api.URL + '/projects/' + symbol.project + '/symbols/' + symbol.id + '/hide', {}) - } - } - - /** - * Makes a POST request to /rest/projects/{projectId}/symbols/symbolId/show in order to revert the deleting - * of a symbol. - * - * @param {Symbol|Symbol[]} symbols - The symbol that should be made visible again - * @returns {*} - */ - function recover(symbols) { - if (angular.isArray(symbols)) { - var symbolIds = _.pluck(symbols, 'id').join(','); - return $http.post(paths.api.URL + '/projects/' + symbols[0].project + '/symbols/batch/' + symbolIds + '/show', {}) - } else { - var symbol = symbols; - return $http.post(paths.api.URL + '/projects/' + symbol.project + '/symbols/' + symbol.id + '/show', {}) - } - } - } -}()); \ No newline at end of file diff --git a/main/src/main/webapp/app/modules/core/routes.js b/main/src/main/webapp/app/modules/core/routes.js deleted file mode 100644 index ea6bafbe8..000000000 --- a/main/src/main/webapp/app/modules/core/routes.js +++ /dev/null @@ -1,249 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.core') - .config([ - '$stateProvider', '$urlRouterProvider', 'paths', - config - ]) - .run([ - '$rootScope', '$state', 'SessionService', - run - ]); - - /** - * Define application routes - * - * @param $stateProvider - * @param $urlRouterProvider - * @param paths - */ - function config($stateProvider, $urlRouterProvider, paths) { - - // redirect to the start page when no other route fits - $urlRouterProvider.otherwise("/home"); - - $stateProvider - - // ========================================================= - // index route - - .state('home', { - url: '/home', - controller: 'HomeController', - templateUrl: paths.COMPONENTS + '/core/views/pages/home.html' - }) - - // ========================================================= - // project related routes - - .state('project', { - url: '/project', - views: { - '@': { - controller: 'ProjectController', - templateUrl: paths.COMPONENTS + '/core/views/pages/project.html' - } - }, - data: { - requiresProject: true - } - }) - .state('project.create', { - url: '/create', - views: { - '@': { - controller: 'ProjectCreateController', - templateUrl: paths.COMPONENTS + '/core/views/pages/project-create.html' - } - }, - data: { - requiresProject: false - } - }) - .state('project.settings', { - url: '/settings', - views: { - '@': { - templateUrl: paths.COMPONENTS + '/core/views/pages/project-settings.html', - controller: 'ProjectSettingsController' - } - } - }) - - // ========================================================= - // counter related routes - - .state('counters', { - url: '/counters', - views: { - '@': { - templateUrl: paths.COMPONENTS + '/core/views/pages/counters.html', - controller: 'CountersController' - } - }, - data: { - requiresProject: false - } - }) - - // ========================================================= - // symbol related routes - - .state('symbols', { - url: '/symbols', - views: { - '@': { - controller: 'SymbolsController', - templateUrl: paths.COMPONENTS + '/core/views/pages/symbols.html' - } - }, - data: { - requiresProject: true - } - }) - .state('symbols.trash', { - url: '/trash', - views: { - '@': { - controller: 'SymbolsTrashController', - templateUrl: paths.COMPONENTS + '/core/views/pages/symbols-trash.html' - } - } - }) - .state('symbols.history', { - url: '/{symbolId:int}/history', - views: { - '@': { - controller: 'SymbolsHistoryController', - templateUrl: paths.COMPONENTS + '/core/views/pages/symbols-history.html' - } - } - }) - .state('symbols.actions', { - url: '/{symbolId:int}/actions', - views: { - '@': { - controller: 'SymbolsActionsController', - templateUrl: paths.COMPONENTS + '/core/views/pages/symbols-actions.html' - } - } - - }) - .state('symbols.import', { - url: '/import', - views: { - '@': { - controller: 'SymbolsImportController', - templateUrl: paths.COMPONENTS + '/core/views/pages/symbols-import.html' - } - } - - }) - - // ========================================================= - // test and learn related routes - - .state('learn', { - abstract: true, - url: '/learn', - data: { - requiresProject: true - } - }) - .state('learn.setup', { - url: '/setup', - views: { - '@': { - controller: 'LearnSetupController', - templateUrl: paths.COMPONENTS + '/core/views/pages/learn-setup.html' - } - } - }) - .state('learn.start', { - url: '/start', - views: { - '@': { - controller: 'LearnStartController', - templateUrl: paths.COMPONENTS + '/core/views/pages/learn-start.html' - } - } - }) - .state('learn.results', { - url: '/results', - views: { - '@': { - controller: 'LearnResultsController', - templateUrl: paths.COMPONENTS + '/core/views/pages/learn-results.html' - } - } - }) - .state('learn.results.statistics', { - url: '/statistics', - views: { - '@': { - controller: 'LearnResultsStatisticsController', - templateUrl: paths.COMPONENTS + '/core/views/pages/learn-results-statistics.html' - } - } - }) - .state('learn.results.compare', { - url: '/compare/:testNos', - views: { - '@': { - controller: 'LearnResultsCompareController', - templateUrl: paths.COMPONENTS + '/core/views/pages/learn-results-compare.html' - } - } - }) - - // ========================================================= - // static pages related routes - - .state('about', { - url: '/about', - templateUrl: paths.COMPONENTS + '/core/views/pages/about.html', - data: { - requiresProject: false - } - }) - - .state('error', { - url: '/error', - controller: 'ErrorController', - templateUrl: paths.COMPONENTS + '/core/views/pages/error.html' - }) - - .state('files', { - url: '/files', - controller: 'FilesController', - templateUrl: paths.COMPONENTS + '/core/views/pages/files.html', - data: { - requiresProject: true - } - }) - } - - /** - * Validate routes on state change - * - * @param $rootScope - * @param $state - * @param SessionService - */ - function run($rootScope, $state, SessionService) { - - // route validation - $rootScope.$on("$stateChangeStart", stateChangeStart); - - function stateChangeStart(event, toState) { - if (toState.data) { - if (toState.data.requiresProject && SessionService.project.get() == null) { - $state.go("home"); - event.preventDefault(); - } - } - } - } -}()); \ No newline at end of file diff --git a/main/src/main/webapp/app/modules/core/services/ClipboardService.js b/main/src/main/webapp/app/modules/core/services/ClipboardService.js deleted file mode 100644 index e23583851..000000000 --- a/main/src/main/webapp/app/modules/core/services/ClipboardService.js +++ /dev/null @@ -1,80 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.core') - .factory('ClipboardService', ClipboardService); - - /** - * The factory for the Clipboard - * - * @returns {Clipboard} - * @constructor - */ - function ClipboardService() { - - /** - * The Clipboard - * @constructor - */ - function Clipboard() { - - /** - * The map of clipboard entries - * @type {{}} - */ - this.entries = {} - } - - /** - * The enum with clipboard modes. - * CUT - An object is only pasted once and then removed from clipboard - * COPY - An object stays in the clipboard after pasting - * @type {{CUT: number, COPY: number}} - */ - Clipboard.modes = { - CUT: 0, - COPY: 1 - }; - - /** - * @param {string} key - * @param {*} data - */ - Clipboard.prototype.copy = function (key, data) { - this.entries[key] = { - data: data, - mode: Clipboard.modes.COPY - } - }; - - /** - * @param {string} key - * @param {*} data - */ - Clipboard.prototype.cut = function (key, data) { - this.entries[key] = { - data: data, - mode: Clipboard.modes.CUT - } - }; - - /** - * @param {string} key - * @returns {*|null} - */ - Clipboard.prototype.paste = function (key) { - if (angular.isDefined(this.entries[key])) { - var data = this.entries[key].data; - if (this.entries[key].mode === Clipboard.modes.CUT) { - delete this.entries[key]; - } - return data; - } else { - return null; - } - }; - - return new Clipboard(); - } -}()); \ No newline at end of file diff --git a/main/src/main/webapp/app/modules/core/services/CounterExampleService.js b/main/src/main/webapp/app/modules/core/services/CounterExampleService.js deleted file mode 100644 index 5f141aa64..000000000 --- a/main/src/main/webapp/app/modules/core/services/CounterExampleService.js +++ /dev/null @@ -1,65 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.core') - .factory('CounterExampleService', CounterExampleService); - - CounterExampleService.$inject = []; - - /** - * The service that is used to share a counterexample between the counter example widget and a hypothesis. - * A counterexample is defined by a list of objects with input & output property - * - * @returns {{getCurrentCounterexample: getCurrentCounterexample, setCurrentCounterexample: setCurrentCounterexample, resetCurrentCounterexample: resetCurrentCounterexample, addIOPairToCurrentCounterexample: addIOPairToCurrentCounterexample}} - * @constructor - */ - function CounterExampleService() { - var counterexample = []; - - return { - getCurrentCounterexample: getCurrentCounterexample, - setCurrentCounterexample: setCurrentCounterexample, - resetCurrentCounterexample: resetCurrentCounterexample, - addIOPairToCurrentCounterexample: addIOPairToCurrentCounterexample - }; - - /** - * Gets the counterexample - * - * @returns {Object[]} - The counterexample - */ - function getCurrentCounterexample() { - return counterexample; - } - - /** - * Sets the counterexample - * - * @param {Object[]} ce - The list of input/output pairs that define a counterexample - */ - function setCurrentCounterexample(ce) { - counterexample = ce; - } - - /** - * Removes all input / output pairs from the counterexample - */ - function resetCurrentCounterexample() { - counterexample = []; - } - - /** - * Adds a new input / output pair to the counterexample - * - * @param {string} input - The input symbol - * @param {string} output - The output symbol - */ - function addIOPairToCurrentCounterexample(input, output) { - counterexample.push({ - input: input, - output: output - }) - } - } -}()); \ No newline at end of file diff --git a/main/src/main/webapp/app/modules/core/services/ErrorService.js b/main/src/main/webapp/app/modules/core/services/ErrorService.js deleted file mode 100644 index 6c36a4840..000000000 --- a/main/src/main/webapp/app/modules/core/services/ErrorService.js +++ /dev/null @@ -1,51 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.core') - .service('ErrorService', ErrorService); - - ErrorService.$inject = ['$state']; - - /** - * Used to store an error message and can redirect to the error page. - * - * @param $state - The ui.router $state service - * @returns {{getErrorMessage: getErrorMessage, setErrorMessage: setErrorMessage, goToErrorPage: goToErrorPage}} - * @constructor - */ - function ErrorService($state) { - var errorMessage = null; - - return { - getErrorMessage: getErrorMessage, - setErrorMessage: setErrorMessage, - goToErrorPage: goToErrorPage - }; - - /** - * Gets the error message and removes it from the service - * @returns {string|null} - */ - function getErrorMessage() { - var msg = errorMessage; - errorMessage = null; - return msg; - } - - /** - * Sets the error message - * @param {string} message - */ - function setErrorMessage(message) { - errorMessage = message; - } - - /** - * Redirects to the error page - */ - function goToErrorPage() { - $state.go('error'); - } - } -}()); \ No newline at end of file diff --git a/main/src/main/webapp/app/modules/core/services/FileDownloadService.js b/main/src/main/webapp/app/modules/core/services/FileDownloadService.js deleted file mode 100644 index 244a99e89..000000000 --- a/main/src/main/webapp/app/modules/core/services/FileDownloadService.js +++ /dev/null @@ -1,128 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.core') - .factory('FileDownloadService', FileDownloadService); - - FileDownloadService.$inject = ['PromptService']; - - /** - * The service that allows the file download of various filetypes: JSON, SVG, CSV. For each download, it prompts - * the user for a filename of the downloadable file. - * - * @param PromptService - The service to create prompts with - * @returns {{downloadJson: downloadJson, downloadCSV: downloadCSV, downloadSVG: downloadSVG}} - * @constructor - */ - function FileDownloadService(PromptService) { - return { - downloadJson: downloadJson, - downloadCSV: downloadCSV, - downloadSVG: downloadSVG - }; - - /** - * Downloads a file. - * - * @param {string} filename - The name of the file - * @param {string} fileExtension - The file extension of the file - * @param {string} href - The contents of the href attribute which holds the data of the file - * @private - */ - function _download(filename, fileExtension, href) { - - // create new link element with downloadable - var a = document.createElement('a'); - a.style.display = 'none'; - a.setAttribute('href', href); - a.setAttribute('target', '_blank'); - a.setAttribute('download', filename + '.' + fileExtension); - - // append link to the dom, fire click event and remove it - document.body.appendChild(a); - a.click(); - document.body.removeChild(a); - } - - /** - * Opens a prompt dialog that asks for a file name. - * - * @param {string} fileExtension - The file extension of the file that should be downloaded - * @returns {Promise} - The promise with the filename - * @private - */ - function _prompt(fileExtension) { - return PromptService.prompt('Enter a name for the ' + fileExtension + ' file.', { - regexp: /^[a-zA-Z0-9\.\-,_]+$/, - errorMsg: 'The name may not be empty and only consist of letters, numbers and the symbols ",._-".' - }) - } - - /** - * Downloads an object as a json file. Prompts for a file name. - * - * @param {Object} jsonObject - The object that should be downloaded - */ - function downloadJson(jsonObject) { - return _prompt('JSON') - .then(function (filename) { - var href = 'data:text/json;charset=utf-8,' + encodeURIComponent(angular.toJson(jsonObject)); - _download(filename, 'json', href); - }) - } - - /** - * Downloads a given string as csv file. Prompts for a filename. - * - * @param {string} csv - The string that represents the csv - */ - function downloadCSV(csv) { - _prompt('CSV') - .then(function (filename) { - var href = 'data:text/csv;charset=utf-8,' + encodeURIComponent(csv); - _download(filename, 'csv', href); - }) - } - - /** - * Downloads a SVG element as a svg file. Prompts for a filename. The hole g element of the svg is downloaded - * even if the g element is bigger than the svg element. - * - * @param {*|HTMLElement} svg - The svg element that should be downloaded - * @param {boolean} adjustSize - If the element should be scaled down to its original size or not - */ - function downloadSVG(svg, adjustSize) { - _prompt('SVG') - .then(function (filename) { - - // copy svg to prevent the svg being clipped due to the window size - var svgCopy = svg.cloneNode(true); - var g = svg.childNodes[0]; - - // set proper xml attributes for downloadable file - svgCopy.setAttribute('version', '1.1'); - svgCopy.setAttribute('xmlns', 'http://www.w3.org/2000/svg'); - - if (adjustSize) { - var scale = g.getTransformToElement(svg).a; - var dimension = svg.childNodes[0].getBoundingClientRect(); - var width = Math.ceil(dimension.width / scale) + 20; // use 20px as offset - var height = Math.ceil(dimension.height / scale) + 20; - - svgCopy.setAttribute('width', width); - svgCopy.setAttribute('height', height); - svgCopy.childNodes[0].setAttribute('transform', 'translate(10,10)'); - } - - // create serialized string from svg element and encode it in - // base64 otherwise the file will not be completely downloaded - // what results in errors opening the file - var svgString = new XMLSerializer().serializeToString(svgCopy); - var href = 'data:image/svg+xml;base64,\n' + window.btoa(svgString); - - _download(filename, 'svg', href); - }) - } - } -}()); \ No newline at end of file diff --git a/main/src/main/webapp/app/modules/core/services/LearnerResultChartService.js b/main/src/main/webapp/app/modules/core/services/LearnerResultChartService.js deleted file mode 100644 index 2597a719c..000000000 --- a/main/src/main/webapp/app/modules/core/services/LearnerResultChartService.js +++ /dev/null @@ -1,230 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.core') - .factory('LearnerResultChartService', LearnerResultChartService); - - LearnerResultChartService.$inject = ['_']; - - /** - * The service to create n3 line chart data from learner results. Can create bar chart data from multiple final - * learner results and area chart data from multiple complete learner results. - * - * @param _ - Lodash - * @returns {{createDataFromMultipleFinalResults: createDataFromMultipleFinalResults, createDataFromMultipleCompleteResults: createDataFromMultipleCompleteResults, properties: {MQS: string, EQS: string, SYMBOL_CALLS: string, SIGMA: string, DURATION: string}}} - * @constructor - */ - function LearnerResultChartService(_) { - - // The learner result properties - var properties = { - MQS: 'mqsUsed', - EQS: 'eqsUsed', - SYMBOL_CALLS: 'symbolsUsed', - SIGMA: 'sigma', - DURATION: 'duration' - }; - - // The available service data - return { - createDataFromMultipleFinalResults: createDataFromMultipleFinalResults, - createDataFromMultipleCompleteResults: createDataFromMultipleCompleteResults, - properties: properties - }; - - /** - * Creates bar chart data from a list of final learner results which includes the data itself and options. - * - * @param {LearnResult[]} results - The learner results from that the chart data should be created - * @param {string} property - The learner results property from that the data should be used - * @returns {{data: Array, options: {series: {y: string, color: string, type: string, axis: string, id: string}[], stacks: Array, axes: {x: {type: string, key: string}, y: {type: string, min: number}}, lineMode: string, tension: number, tooltip: {mode: string}, drawLegend: boolean, drawDots: boolean, columnsHGap: number}}} - */ - function createDataFromMultipleFinalResults(results, property) { - - var dataSets = []; - var dataValues = []; - var options = { - series: [ - { - y: "val_0", - color: "#4B6396", - type: "column", - axis: "y", - id: "series_0" - } - ], - stacks: [], - axes: {x: {type: "linear", key: "x"}, y: {type: "linear", min: 0}}, - lineMode: "linear", - tension: 0.7, - tooltip: {mode: "scrubber"}, - drawLegend: false, - drawDots: true, - columnsHGap: 3 - }; - - var statistics = _.pluck(results, 'statistics'); - - // extract values from learner results by a property - switch (property) { - case properties.MQS: - dataValues = _.pluck(statistics, properties.MQS); - break; - case properties.EQS: - dataValues = _.pluck(statistics, properties.EQS); - break; - case properties.SIGMA: - dataValues = _.map(_.pluck(results, properties.SIGMA), function (n) { - return n ? n.length : 0; - }); - break; - case properties.SYMBOL_CALLS: - dataValues = _.pluck(statistics, properties.SYMBOL_CALLS); - break; - case properties.DURATION: - dataValues = _.pluck(statistics, properties.DURATION); - break; - default : - break; - } - - // create n3 line chart dataSets from extracted values - for (var i = 0; i < dataValues.length; i++) { - dataSets.push({ - x: i, - val_0: dataValues[i] - }); - } - - // create dummy data so that bar gets displayed correctly - if (dataSets.length === 1) { - dataSets.push({ - x: 1, - val_1: 0 - }) - } - - // create x axis labels for each test result - options.axes.x.labelFunction = function (l) { - if (l % 1 == 0 && l >= 0 && l < results.length) { - return 'Test ' + results[l].testNo; - } - }; - - return { - data: dataSets, - options: options - }; - } - - /** - * Creates area chart data from a list of complete learner results which includes the data itself and options. - * - * @param {LearnResult[]} results - A list of complete learner results - * @param {String} property - The learner result property from which the chart data should be created - * @returns {{data: Array, options: {series: Array, stacks: Array, axes: {x: {type: string, key: string}, y: {type: string, min: number}}, lineMode: string, tension: number, tooltip: {mode: string}, drawLegend: boolean, drawDots: boolean, columnsHGap: number}}} - */ - function createDataFromMultipleCompleteResults(results, property) { - - var dataSets = []; - var dataValues = []; - var maxSteps = 0; - var options = { - series: [], - stacks: [], - axes: {x: {type: "linear", key: "x"}, y: {type: "linear", min: 0}}, - lineMode: "linear", - tension: 0.7, - tooltip: {mode: "scrubber"}, - drawLegend: true, - drawDots: true, - columnsHGap: 3 - }; - var colors = ['#4B6396', '#3BA3B8', '#3BB877', '#8ACF36', '#E8E835', '#F7821B', '#F74F1B', '#C01BF7']; - var i, j; - - // extract values from learner results by a property - switch (property) { - case properties.MQS: - _.forEach(results, function (result) { - dataValues.push(_(result).pluck('statistics').pluck(properties.MQS).value()); - }); - break; - case properties.EQS: - _.forEach(results, function (result) { - dataValues.push(_(result).pluck('statistics').pluck(properties.EQS).value()); - }); - break; - case properties.SIGMA: - _.forEach(results, function (result) { - dataValues.push(_.map(_.pluck(result, properties.SIGMA), function (n) { - return n ? n.length : 0; - })); - }); - break; - case properties.SYMBOL_CALLS: - _.forEach(results, function (result) { - dataValues.push(_(result).pluck('statistics').pluck(properties.SYMBOL_CALLS).value()); - }); - break; - case properties.DURATION: - _.forEach(results, function (result) { - dataValues.push(_(result).pluck('statistics').pluck(properties.DURATION).value()); - }); - break; - default : - break; - } - - // find value from test results where #steps is max - for (i = 0; i < dataValues.length; i++) { - if (dataValues[i].length > maxSteps) { - maxSteps = dataValues[i].length; - } - } - - // fill all other values with zeroes - for (i = 0; i < dataValues.length; i++) { - if (dataValues[i].length < maxSteps) { - for (j = dataValues[i].length; j < maxSteps; j++) { - dataValues[i][j] = 0; - } - } - } - - // create data sets - for (i = 0; i < maxSteps; i++) { - var data = {x: i}; - for (j = 0; j < dataValues.length; j++) { - data['val_' + j] = dataValues[j][i]; - } - dataSets.push(data); - } - - // create options for each test - for (i = 0; i < results.length; i++) { - options.series.push({ - y: 'val_' + i, - color: colors[i % colors.length], - type: 'area', - axis: 'y', - id: 'series_' + i, - label: 'Test ' + results[i][0].testNo - }) - } - - // create customs x axis labels - options.axes.x.labelFunction = function (l) { - if (l % 1 == 0 && l >= 0 && l < maxSteps) { - return 'Step ' + (l + 1); - } - }; - - return { - data: dataSets, - options: options - } - } - } -}()); \ No newline at end of file diff --git a/main/src/main/webapp/app/modules/core/services/LearnerService.js b/main/src/main/webapp/app/modules/core/services/LearnerService.js deleted file mode 100644 index e2cec4862..000000000 --- a/main/src/main/webapp/app/modules/core/services/LearnerService.js +++ /dev/null @@ -1,108 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.core') - .factory('LearnerService', LearnerService); - - LearnerService.$inject = ['$http', 'paths', 'LearnResult']; - - /** - * The service for interacting with the learner - * - * @param $http - The angular $http service - * @param paths - The applications paths constant - * @param LearnResult - The LearnResult factory - * @returns {{start: start, stop: stop, resume: resume, getStatus: getStatus, isActive: isActive, isCounterexample: isCounterexample}} - * @constructor - */ - function LearnerService($http, paths, LearnResult) { - return { - start: start, - stop: stop, - resume: resume, - getStatus: getStatus, - isActive: isActive, - isCounterexample: isCounterexample - }; - - /** - * Start the server side learning process of a project - * - * @param {number} projectId - * @param {LearnConfiguration} learnConfiguration - * @return {*} - */ - function start(projectId, learnConfiguration) { - return $http.post(paths.api.URL + '/learner/start/' + projectId, learnConfiguration); - } - - /** - * Try to force stop a running learning process of a project. May not necessarily work due to difficulties - * with the thread handling - * - * @return {*} - */ - function stop() { - return $http.get(paths.api.URL + '/learner/stop'); - } - - /** - * Resume a paused learning process where the eqOracle was 'sample' and the learn process was interrupted - * so that the ongoing process parameters could be defined - * - * @param {number} projectId - * @param {number} testNo - * @param {LearnConfiguration} learnConfiguration - * @return {*} - */ - function resume(projectId, testNo, learnConfiguration) { - return $http.post(paths.api.URL + '/learner/resume/' + projectId + '/' + testNo, learnConfiguration); - } - - /** - * Gets the learner result that includes the hypothesis. make sure isActive() returns true before calling this - * function - * - * @return {*} - */ - function getStatus() { - return $http.get(paths.api.URL + '/learner/status') - .then(function (response) { - return LearnResult.transformApiResponse(response); - }) - .catch(function () { - return null; - }) - } - - /** - * Check if the server is finished learning a project - * - * @return {*} - */ - function isActive() { - return $http.get(paths.api.URL + '/learner/active') - .then(function (response) { - return response.data; - }) - } - - /** - * Verifies a possible counterexample - * - * @param {number} projectId - * @param {{id: number, revision: number}} resetSymbol - The id/revision pair of the reset symbol - * @param {{id: number, revision: number}[]} symbols - The list of id/revision pairs of symbols - * @returns {*} - */ - function isCounterexample(projectId, resetSymbol, symbols) { - return $http.post(paths.api.URL + '/learner/outputs/' + projectId, { - resetSymbol: resetSymbol, - symbols: symbols - }).then(function (response) { - return response.data; - }) - } - } -}()); \ No newline at end of file diff --git a/main/src/main/webapp/app/modules/core/services/SessionService.js b/main/src/main/webapp/app/modules/core/services/SessionService.js deleted file mode 100644 index b52dd3dae..000000000 --- a/main/src/main/webapp/app/modules/core/services/SessionService.js +++ /dev/null @@ -1,57 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.core') - .factory('SessionService', SessionService); - - SessionService.$inject = ['$rootScope', 'Project']; - - /** - * The session that is used in this application to save data in the session storage of the browser to store data in - * between page refreshes in the same tab. So the project doesn't have to be fetched from the server every time the - * page refreshes - * - * @param $rootScope - * @param Project - * @return {{project: {get: get, save: save, remove: remove}}} - * @constructor - */ - function SessionService($rootScope, Project) { - return { - project: { - get: get, - save: save, - remove: remove - } - }; - - /** - * Get the stored project object from the session storage - * - * @return {Project} - */ - function get() { - var project = sessionStorage.getItem('project'); - return project === null ? null : Project.build(angular.fromJson(project)); - } - - /** - * Save a project into the session storage end emit the 'project.opened' event - * - * @param project - */ - function save(project) { - sessionStorage.setItem('project', angular.toJson(project)); - $rootScope.$broadcast('project.opened'); - } - - /** - * Remove the stored project from session storage an emit the 'project.closed' event - */ - function remove() { - sessionStorage.removeItem('project'); - $rootScope.$broadcast('project.closed'); - } - } -}()); \ No newline at end of file diff --git a/main/src/main/webapp/app/modules/core/services/ToastService.js b/main/src/main/webapp/app/modules/core/services/ToastService.js deleted file mode 100644 index 5a946acff..000000000 --- a/main/src/main/webapp/app/modules/core/services/ToastService.js +++ /dev/null @@ -1,66 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.core') - .service('ToastService', ToastService); - - ToastService.$inject = ['ngToast']; - - /** - * A service that is used as a wrapper around the ngToast module. - * - * @param ngToast - The ngToast service - * @returns {{success: success, danger: danger, info: info}} - * @constructor - */ - function ToastService(ngToast) { - - return { - success: success, - danger: danger, - info: info - }; - - /** - * Creates a toast message. - * - * @param {string} type - a bootstrap alert class type: 'success', 'error', 'info' etc. - * @param {string} message - The message to be displayed - */ - function createToast(type, message) { - ngToast.create({ - className: type, - content: message, - dismissButton: true - }); - } - - /** - * Create a success toast message - * - * @param {String} message - The message to be displayed - */ - function success(message) { - createToast('success', message); - } - - /** - * Create an error / danger toast message - * - * @param {String} message - The message to be displayed - */ - function danger(message) { - createToast('danger', message); - } - - /** - * Create an info toast message - * - * @param {String} message - The message to be displayed - */ - function info(message) { - createToast('info', message); - } - } -}()); \ No newline at end of file diff --git a/main/src/main/webapp/app/modules/core/views/directives/counterexamples-widget.html b/main/src/main/webapp/app/modules/core/views/directives/counterexamples-widget.html deleted file mode 100644 index 7444bcc7a..000000000 --- a/main/src/main/webapp/app/modules/core/views/directives/counterexamples-widget.html +++ /dev/null @@ -1,37 +0,0 @@ -
-

- Click on the labels of the hypothesis to create a counterexample. -

- -
-
- - - - {{io.input}} - - {{io.output}} - -
-
- -
- -
-
-
- -
    -
  • - - - -
    - - -
    -
  • -
\ No newline at end of file diff --git a/main/src/main/webapp/app/modules/core/views/directives/dashboard-widget.html b/main/src/main/webapp/app/modules/core/views/directives/dashboard-widget.html deleted file mode 100644 index 40d30e222..000000000 --- a/main/src/main/webapp/app/modules/core/views/directives/dashboard-widget.html +++ /dev/null @@ -1,6 +0,0 @@ -
-
- -
-
-
\ No newline at end of file diff --git a/main/src/main/webapp/app/modules/core/views/directives/html-element-picker.html b/main/src/main/webapp/app/modules/core/views/directives/html-element-picker.html deleted file mode 100644 index 8b64a23c1..000000000 --- a/main/src/main/webapp/app/modules/core/views/directives/html-element-picker.html +++ /dev/null @@ -1,50 +0,0 @@ -
- -
- - - -
- -
- -
diff --git a/main/src/main/webapp/app/modules/core/views/directives/hypothesis.html b/main/src/main/webapp/app/modules/core/views/directives/hypothesis.html deleted file mode 100644 index 5afcb2d55..000000000 --- a/main/src/main/webapp/app/modules/core/views/directives/hypothesis.html +++ /dev/null @@ -1,3 +0,0 @@ -
- -
\ No newline at end of file diff --git a/main/src/main/webapp/app/modules/core/views/directives/latest-learn-result-widget.html b/main/src/main/webapp/app/modules/core/views/directives/latest-learn-result-widget.html deleted file mode 100644 index 6273025c1..000000000 --- a/main/src/main/webapp/app/modules/core/views/directives/latest-learn-result-widget.html +++ /dev/null @@ -1,12 +0,0 @@ -
- There are no learn results in the database -
- -
-

The latest learning process started at - with EQ-oracle - and the - algorithm. -

- Check it out -
\ No newline at end of file diff --git a/main/src/main/webapp/app/modules/core/views/directives/learn-result-list-item.html b/main/src/main/webapp/app/modules/core/views/directives/learn-result-list-item.html deleted file mode 100644 index 3662ee1a5..000000000 --- a/main/src/main/webapp/app/modules/core/views/directives/learn-result-list-item.html +++ /dev/null @@ -1,26 +0,0 @@ -
- - - -
- -
- Failed - - Test No - - , - [] - -
- - Started: - -
- -
-
-
-
\ No newline at end of file diff --git a/main/src/main/webapp/app/modules/core/views/directives/learn-result-panel.html b/main/src/main/webapp/app/modules/core/views/directives/learn-result-panel.html deleted file mode 100644 index 7f994ff6d..000000000 --- a/main/src/main/webapp/app/modules/core/views/directives/learn-result-panel.html +++ /dev/null @@ -1,114 +0,0 @@ -
-
- - -
- -
- - -
- - - - - - - -
- - - - - -
-
- - -
- - -
- - -
- - -
- -
-
-
- -
- - - - - - - - - - - -
- -
-
-

There has been an error while learning

-

-
-
- -
-
\ No newline at end of file diff --git a/main/src/main/webapp/app/modules/core/views/directives/learn-resume-settings-widget.html b/main/src/main/webapp/app/modules/core/views/directives/learn-resume-settings-widget.html deleted file mode 100644 index 5dc37416b..000000000 --- a/main/src/main/webapp/app/modules/core/views/directives/learn-resume-settings-widget.html +++ /dev/null @@ -1,55 +0,0 @@ -
-
- -
- -
- -
- -
-
- -
- -
-
-
- -
- -
-
-
- -
- -
- -
-
-
- -
-
- -
- -
-
-
- -
- -
- -
-
- -
- - -
\ No newline at end of file diff --git a/main/src/main/webapp/app/modules/core/views/directives/learner-result-chart-multiple-final.html b/main/src/main/webapp/app/modules/core/views/directives/learner-result-chart-multiple-final.html deleted file mode 100644 index 1898b0a61..000000000 --- a/main/src/main/webapp/app/modules/core/views/directives/learner-result-chart-multiple-final.html +++ /dev/null @@ -1,22 +0,0 @@ -
- -
- -
- - - -
-
\ No newline at end of file diff --git a/main/src/main/webapp/app/modules/core/views/directives/learner-status-widget.html b/main/src/main/webapp/app/modules/core/views/directives/learner-status-widget.html deleted file mode 100644 index 02a40fd1d..000000000 --- a/main/src/main/webapp/app/modules/core/views/directives/learner-status-widget.html +++ /dev/null @@ -1,19 +0,0 @@ -
The Learner is not active. - Start learning your application! -
- -
The Learner is not active and created a model. - - Refine it, - have a look at it or - Start a new test! -
- -
- The Learner is currently learning an application. -
- -
\ No newline at end of file diff --git a/main/src/main/webapp/app/modules/core/views/directives/load-screen.html b/main/src/main/webapp/app/modules/core/views/directives/load-screen.html deleted file mode 100644 index 58e422826..000000000 --- a/main/src/main/webapp/app/modules/core/views/directives/load-screen.html +++ /dev/null @@ -1,5 +0,0 @@ -
-

- -

-
\ No newline at end of file diff --git a/main/src/main/webapp/app/modules/core/views/directives/observation-table.html b/main/src/main/webapp/app/modules/core/views/directives/observation-table.html deleted file mode 100644 index 6028cf04b..000000000 --- a/main/src/main/webapp/app/modules/core/views/directives/observation-table.html +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - -
\ No newline at end of file diff --git a/main/src/main/webapp/app/modules/core/views/directives/project-details-widget.html b/main/src/main/webapp/app/modules/core/views/directives/project-details-widget.html deleted file mode 100644 index 09c27b087..000000000 --- a/main/src/main/webapp/app/modules/core/views/directives/project-details-widget.html +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - -
Name
URL
#Groups
#Symbols
#Tests
\ No newline at end of file diff --git a/main/src/main/webapp/app/modules/core/views/directives/sidebar.html b/main/src/main/webapp/app/modules/core/views/directives/sidebar.html deleted file mode 100644 index 899b4886d..000000000 --- a/main/src/main/webapp/app/modules/core/views/directives/sidebar.html +++ /dev/null @@ -1,150 +0,0 @@ - diff --git a/main/src/main/webapp/app/modules/core/views/directives/symbol-list-item.html b/main/src/main/webapp/app/modules/core/views/directives/symbol-list-item.html deleted file mode 100644 index 4b872597a..000000000 --- a/main/src/main/webapp/app/modules/core/views/directives/symbol-list-item.html +++ /dev/null @@ -1,63 +0,0 @@ -
- - - -
-
- -

- - []
- - - Actions   - - - - Actions - - - - - - -
-
- -
    -
  • - - - Negate - - - - Ignore Failure - - - - -
  • -
-
-
-
-
\ No newline at end of file diff --git a/main/src/main/webapp/app/modules/core/views/directives/view-header.html b/main/src/main/webapp/app/modules/core/views/directives/view-header.html deleted file mode 100644 index 720427549..000000000 --- a/main/src/main/webapp/app/modules/core/views/directives/view-header.html +++ /dev/null @@ -1,6 +0,0 @@ -
-
-
-

-
-
\ No newline at end of file diff --git a/main/src/main/webapp/app/modules/core/views/directives/widget.html b/main/src/main/webapp/app/modules/core/views/directives/widget.html deleted file mode 100644 index 9eb2e813c..000000000 --- a/main/src/main/webapp/app/modules/core/views/directives/widget.html +++ /dev/null @@ -1,6 +0,0 @@ -
-
- -
-
-
\ No newline at end of file diff --git a/main/src/main/webapp/app/modules/core/views/pages/about.html b/main/src/main/webapp/app/modules/core/views/pages/about.html deleted file mode 100644 index 9c2a7c759..000000000 --- a/main/src/main/webapp/app/modules/core/views/pages/about.html +++ /dev/null @@ -1,57 +0,0 @@ - - -
-
-
-
- -
-

About ALEX

-
- -

Authors

- -

- Alexander Bainczyk
- -   - alexander.bainczyk@tu-dortmund.de - -

- -

- Alexander Schieweck
- -   - alexander.schieweck@tu-dortmund.de - -

-
- -
-

Help

-
- -

- For information on how to use the application or the RESTful API please have a look at the - documentation that is provided with Maven Site. If you are a developer, navigate to to root - directory and execute mvn site:run. -

-
- - -
-

REST API

-
- -

- ALEX provides a REST API to enable the manipulation of work objects via an HTTP interface. It is powered by - the famous Swagger Framework. -

- - View the docs -
-
-
-
-
\ No newline at end of file diff --git a/main/src/main/webapp/app/modules/core/views/pages/counters.html b/main/src/main/webapp/app/modules/core/views/pages/counters.html deleted file mode 100644 index 3c8c1abd4..000000000 --- a/main/src/main/webapp/app/modules/core/views/pages/counters.html +++ /dev/null @@ -1,50 +0,0 @@ - - - -
- - -
- -
-
-
- -
-
- -
- - Deleted counters will be created as soon as they are used in a learning process, starting with value 0. -
- -
- -
- - - -
- - -
- Value: -
-
-
- -
- There aren't any counters yet -
- -
-
\ No newline at end of file diff --git a/main/src/main/webapp/app/modules/core/views/pages/error.html b/main/src/main/webapp/app/modules/core/views/pages/error.html deleted file mode 100644 index fc831a5f5..000000000 --- a/main/src/main/webapp/app/modules/core/views/pages/error.html +++ /dev/null @@ -1,11 +0,0 @@ -
-
-
- - -

Something went wrong!

- -

-
-
-
\ No newline at end of file diff --git a/main/src/main/webapp/app/modules/core/views/pages/files.html b/main/src/main/webapp/app/modules/core/views/pages/files.html deleted file mode 100644 index 04899fbb1..000000000 --- a/main/src/main/webapp/app/modules/core/views/pages/files.html +++ /dev/null @@ -1,57 +0,0 @@ - - - -
- - -
-
- -
-
- -
- Drag & Drop files here or click to upload -
- -
-
- - -
- - -
- -
- -
-
- - - -
- - -
- -
-
- -
-
\ No newline at end of file diff --git a/main/src/main/webapp/app/modules/core/views/pages/home.html b/main/src/main/webapp/app/modules/core/views/pages/home.html deleted file mode 100644 index 591670e04..000000000 --- a/main/src/main/webapp/app/modules/core/views/pages/home.html +++ /dev/null @@ -1,32 +0,0 @@ -
- -
- -
- -

Projects

-
- -
- -
-

- -

-
- There is no description for this project - -

-
- -
- You haven't created a project yet. You can create a new one here and - start - testing it. -
- -
- -
\ No newline at end of file diff --git a/main/src/main/webapp/app/modules/core/views/pages/learn-results-compare.html b/main/src/main/webapp/app/modules/core/views/pages/learn-results-compare.html deleted file mode 100644 index db543218f..000000000 --- a/main/src/main/webapp/app/modules/core/views/pages/learn-results-compare.html +++ /dev/null @@ -1,63 +0,0 @@ -
- -
- - - - - - - -
-
- -
-
-
- -
    -
  • - - Failed - - Test No - - , - [] - -
    - -

    - Started: - -

    - -
  • -
- -
-
-
- -
- -
-
-
- Click to add a new panel or click on the green button on the bottom right -
-
- - -
- -
-
\ No newline at end of file diff --git a/main/src/main/webapp/app/modules/core/views/pages/learn-results-statistics.html b/main/src/main/webapp/app/modules/core/views/pages/learn-results-statistics.html deleted file mode 100644 index 21fd1aaca..000000000 --- a/main/src/main/webapp/app/modules/core/views/pages/learn-results-statistics.html +++ /dev/null @@ -1,139 +0,0 @@ - - - -
-
- - - -
- - -
- -
- - -
- -
- -
- -
- -
- -
- -
- -
-
-
- -
-
- -
- - - - - - -
- -
- - -
- -
- - - - - -
-
- - - - - - - - - - - - - - - - -
Test NoAlgorithmEq-oracle
{{result.configuration.eqOracle}}
- -
- -
- You have not run any tests yet. -
- -
-
\ No newline at end of file diff --git a/main/src/main/webapp/app/modules/core/views/pages/learn-results.html b/main/src/main/webapp/app/modules/core/views/pages/learn-results.html deleted file mode 100644 index 20917965b..000000000 --- a/main/src/main/webapp/app/modules/core/views/pages/learn-results.html +++ /dev/null @@ -1,62 +0,0 @@ - - - -
- - -
- - -
-
-
- -
-
- - - - -
- - -
- -
-
- -
- You have not run any tests yet or the active one is not finished. -
- -
-
\ No newline at end of file diff --git a/main/src/main/webapp/app/modules/core/views/pages/learn-setup.html b/main/src/main/webapp/app/modules/core/views/pages/learn-setup.html deleted file mode 100644 index 1a90cb0c2..000000000 --- a/main/src/main/webapp/app/modules/core/views/pages/learn-setup.html +++ /dev/null @@ -1,118 +0,0 @@ - - - - - -
- - -
- - -
- -
-
- -
- -

- Using algorithm - with EQ-Oracle - - ( - min-depth: , - max-depth: - ) - - - ( - min-length: , - max-length: , - #words: - ) - -

- -

- Please select a reset symbol by clicking on the blue circle -

- -

- Reset symbol is -

- -
-
- -
- -
- - - - - - - - - - - - - - - - Reset Symbol - - - - - - - - - -
- - - -
- -
-
\ No newline at end of file diff --git a/main/src/main/webapp/app/modules/core/views/pages/learn-start.html b/main/src/main/webapp/app/modules/core/views/pages/learn-start.html deleted file mode 100644 index 2046ee99e..000000000 --- a/main/src/main/webapp/app/modules/core/views/pages/learn-start.html +++ /dev/null @@ -1,56 +0,0 @@ - - -
- -
-
- -   Application is learning ... -
-
-

Statistics

- - - - - - - - - - -
Time passed
MQs executed - - Not yet available -
-
- -
- -
-
-
-
- -
-
-
-
- -
-
- - -
-
- -
- -
diff --git a/main/src/main/webapp/app/modules/core/views/pages/project-create.html b/main/src/main/webapp/app/modules/core/views/pages/project-create.html deleted file mode 100644 index a779a6378..000000000 --- a/main/src/main/webapp/app/modules/core/views/pages/project-create.html +++ /dev/null @@ -1,70 +0,0 @@ - - -
-
- -

Create a new project

-
- -
- Drag and drop *.json file here or click to import a project -
-
- -
- - -
- - -

The name of your project

- -
-
- Name must not be empty. -
- - - -
- - -

The url of your website

- -
-
- - Url must not be empty. - - - The url has to start with http(s):// and have a host name - -
- - - -
- - -

- If you want you can describe your new project with a few words -

- -
- - -
- -
- -
- -
-
\ No newline at end of file diff --git a/main/src/main/webapp/app/modules/core/views/pages/project-settings.html b/main/src/main/webapp/app/modules/core/views/pages/project-settings.html deleted file mode 100644 index 12172e2bb..000000000 --- a/main/src/main/webapp/app/modules/core/views/pages/project-settings.html +++ /dev/null @@ -1,64 +0,0 @@ - - -
-
- -
- - -
- - -

The name of your project

- -
-
- Name must not be empty. -
- - - -
- - -

The url of your website

- -
-
- - Url must not be empty. - - - The url has to start with http(s):// and have a host name - -
- - - -
- - -

- If you want you can describe your new project with a few words -

- -
- - -
- - Export - Delete -
- -
- -
-
\ No newline at end of file diff --git a/main/src/main/webapp/app/modules/core/views/pages/project.html b/main/src/main/webapp/app/modules/core/views/pages/project.html deleted file mode 100644 index 7788372da..000000000 --- a/main/src/main/webapp/app/modules/core/views/pages/project.html +++ /dev/null @@ -1,27 +0,0 @@ - - -
-
-
- -
- - - -
- -
- - - -
- -
- - - -
- -
-
-
diff --git a/main/src/main/webapp/app/modules/core/views/pages/symbols-actions.html b/main/src/main/webapp/app/modules/core/views/pages/symbols-actions.html deleted file mode 100644 index cfdf3beee..000000000 --- a/main/src/main/webapp/app/modules/core/views/pages/symbols-actions.html +++ /dev/null @@ -1,136 +0,0 @@ - - - - - - - - - -
- - - -
- -
- - -
- -
- - -
-
- -
-
- -
- There are unsaved changes made to the symbol -
- -
-
-
- - - -
- -
- - -
- - - - - - - - - - - - -
- - -
- -
-
-
-
- -
- You haven't created any actions for this symbol yet. -
- -
-
\ No newline at end of file diff --git a/main/src/main/webapp/app/modules/core/views/pages/symbols-history.html b/main/src/main/webapp/app/modules/core/views/pages/symbols-history.html deleted file mode 100644 index 22a97428d..000000000 --- a/main/src/main/webapp/app/modules/core/views/pages/symbols-history.html +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - -
-
- - - - Latest - - - - - Restore - - - - - -
-
- There are no older versions of this symbol that could be restored. -
- -
-
\ No newline at end of file diff --git a/main/src/main/webapp/app/modules/core/views/pages/symbols-import.html b/main/src/main/webapp/app/modules/core/views/pages/symbols-import.html deleted file mode 100644 index 17d093447..000000000 --- a/main/src/main/webapp/app/modules/core/views/pages/symbols-import.html +++ /dev/null @@ -1,50 +0,0 @@ - - - -
- - -
- -
- - -
-
-
-
- -
-
- -
- Drag and drop *.json file here or click to upload -
- - - - - - - - - -
-
\ No newline at end of file diff --git a/main/src/main/webapp/app/modules/core/views/pages/symbols-trash.html b/main/src/main/webapp/app/modules/core/views/pages/symbols-trash.html deleted file mode 100644 index b12ca48db..000000000 --- a/main/src/main/webapp/app/modules/core/views/pages/symbols-trash.html +++ /dev/null @@ -1,38 +0,0 @@ - - - -
- - - -
- -
-
-
- -
-
- -
- There aren't any deleted symbols. -
- - - - - - - Recover - - - - - -
-
\ No newline at end of file diff --git a/main/src/main/webapp/app/modules/core/views/pages/symbols.html b/main/src/main/webapp/app/modules/core/views/pages/symbols.html deleted file mode 100644 index 04392361a..000000000 --- a/main/src/main/webapp/app/modules/core/views/pages/symbols.html +++ /dev/null @@ -1,130 +0,0 @@ - - - - - -
- -
- - -
- - - - - -
- -
- - -
- -
- -
-
- -
-
- - - - - - - - - - - - - - - -
- - -
- -
-
- -
- -
-
- -
-
\ No newline at end of file diff --git a/main/src/main/webapp/app/modules/init.js b/main/src/main/webapp/app/modules/init.js deleted file mode 100644 index 2c4c1b6bc..000000000 --- a/main/src/main/webapp/app/modules/init.js +++ /dev/null @@ -1,25 +0,0 @@ -(function () { - 'use strict'; - - angular.module('ALEX', [ - - // modules from external libraries - 'ngAnimate', - 'ui.bootstrap', - 'ui.ace', - 'ui.router', - 'ngToast', - 'n3-line-chart', - 'selectionModel', - 'ng-sortable', - 'ngFileUpload', - - //all templates - 'templates-all', - - // application specific modules - 'ALEX.core', - 'ALEX.actions', - 'ALEX.modals' - ]) -}()); \ No newline at end of file diff --git a/main/src/main/webapp/app/modules/modals/controllers/ActionCreateModalController.js b/main/src/main/webapp/app/modules/modals/controllers/ActionCreateModalController.js deleted file mode 100644 index 93a78fe19..000000000 --- a/main/src/main/webapp/app/modules/modals/controllers/ActionCreateModalController.js +++ /dev/null @@ -1,93 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.modals') - .controller('ActionCreateModalController', ActionCreateModalController); - - ActionCreateModalController.$inject = [ - '$scope', '$modalInstance', 'modalData', 'actionTypes', 'actionGroupTypes', 'ActionBuilder', 'SymbolResource', 'SessionService' - ]; - - /** - * The controller for the modal dialog that handles the creation of a new action. - * - * The template can be found at 'views/modals/action-create-modal.html'. - * - * @param $scope - * @param $modalInstance - * @param modalData - * @param actionTypes - * @param actionGroupTypes - * @param ActionBuilder - * @param SymbolResource - * @param Session - * @constructor - */ - function ActionCreateModalController($scope, $modalInstance, modalData, actionTypes, actionGroupTypes, ActionBuilder, SymbolResource, Session) { - - var project = Session.project.get(); - - /** - * The constant for action group types - * @type {Object} - */ - $scope.actionGroupTypes = actionGroupTypes; - - /** - * The constant for action type names - * @type {Object} - */ - $scope.actionTypes = actionTypes; - - /** - * The model for the new action - * @type {null|Object} - */ - $scope.action = null; - - /** - * All symbols of the project - * @type {Array} - */ - $scope.symbols = []; - - (function init() { - SymbolResource.getAll(project.id) - .then(function (symbols) { - $scope.symbols = symbols; - }) - }()); - - /** - * Creates a new instance of an Action by a type that was clicked in the modal dialog. - * - * @param {string} type - The type of the action that should be created - */ - $scope.selectNewActionType = function (type) { - $scope.action = ActionBuilder.createFromType(type); - }; - - /** - * Closes the modal dialog an passes the created action back to the handle that called the modal - */ - $scope.createAction = function () { - $modalInstance.close($scope.action); - }; - - /** - * Creates a new action in the background without closing the dialog - */ - $scope.createActionAndContinue = function () { - modalData.addAction($scope.action); - $scope.action = null; - }; - - /** - * Closes the modal dialog without passing any data - */ - $scope.closeModal = function () { - $modalInstance.dismiss(); - }; - } -}()); \ No newline at end of file diff --git a/main/src/main/webapp/app/modules/modals/controllers/ActionEditModalController.js b/main/src/main/webapp/app/modules/modals/controllers/ActionEditModalController.js deleted file mode 100644 index 22a0903c3..000000000 --- a/main/src/main/webapp/app/modules/modals/controllers/ActionEditModalController.js +++ /dev/null @@ -1,76 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.modals') - .controller('ActionEditModalController', ActionEditModalController); - - ActionEditModalController.$inject = [ - '$scope', '$modalInstance', 'modalData', 'actionTypes', 'ActionBuilder', 'SymbolResource', 'SessionService' - ]; - - /** - * The controller for the modal dialog that handles the editing of an action. - * - * The template can be found at 'views/modals/action-edit-modal.html'. - * - * @param $scope - The controllers scope - * @param $modalInstance - The model instance - * @param modalData - The data that is passed to this controller - * @param actionTypes - The constant for action type names - * @param ActionBuilder - The Service for creating actions - * @param SymbolResource - The API Resource for symbols - * @param Session - The SessionService - * @constructor - */ - function ActionEditModalController($scope, $modalInstance, modalData, actionTypes, ActionBuilder, SymbolResource, Session) { - - // the project in the session - var project = Session.project.get(); - - /** - * The constant for actions type names - * @type {Object} - */ - $scope.actionTypes = actionTypes; - - /** - * The copy of the action that should be edited - * @type {Object} - */ - $scope.action = angular.copy(modalData.action); - - /** - * - * @type {Array} - */ - $scope.symbols = []; - - (function init() { - SymbolResource.getAll(project.id) - .then(function (symbols) { - $scope.symbols = symbols; - }) - }()); - - /** - * Close the modal dialog and pass the updated action to the handle that called it - */ - $scope.updateAction = function () { - - // because actions are identified by temporary id - // a new action has to be build and given the old id manually - var id = $scope.action._id; - $scope.action = ActionBuilder.createFromObject($scope.action); - $scope.action._id = id; - $modalInstance.close($scope.action); - }; - - /** - * Close the modal dialog without passing any data - */ - $scope.closeModal = function () { - $modalInstance.dismiss(); - } - } -}()); \ No newline at end of file diff --git a/main/src/main/webapp/app/modules/modals/controllers/ConfirmDialogController.js b/main/src/main/webapp/app/modules/modals/controllers/ConfirmDialogController.js deleted file mode 100644 index f38185b45..000000000 --- a/main/src/main/webapp/app/modules/modals/controllers/ConfirmDialogController.js +++ /dev/null @@ -1,39 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.modals') - .controller('ConfirmDialogController', ConfirmDialogController); - - ConfirmDialogController.$inject = ['$scope', '$modalInstance', 'modalData']; - - /** - * The controller that handles the confirm modal dialog. - * - * @param $scope - * @param $modalInstance - * @param modalData - * @constructor - */ - function ConfirmDialogController($scope, $modalInstance, modalData) { - - /** The text to be displayed **/ - $scope.text = modalData.text; - $scope.regexp = modalData.regexp; - $scope.errorMsg = modalData.errorMsg; - - /** - * Close the modal dialog - */ - $scope.ok = function () { - $modalInstance.close(); - }; - - /** - * Close the modal dialog - */ - $scope.close = function () { - $modalInstance.dismiss(); - } - } -}()); \ No newline at end of file diff --git a/main/src/main/webapp/app/modules/modals/controllers/HypothesisLayoutSettingsController.js b/main/src/main/webapp/app/modules/modals/controllers/HypothesisLayoutSettingsController.js deleted file mode 100644 index bf827e795..000000000 --- a/main/src/main/webapp/app/modules/modals/controllers/HypothesisLayoutSettingsController.js +++ /dev/null @@ -1,45 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.modals') - .controller('HypothesisLayoutSettingsController', HypothesisLayoutSettingsController); - - HypothesisLayoutSettingsController.$inject = ['$scope', '$modalInstance', 'modalData']; - - /** - * The controller that handles the modal dialog for changing the layout settings of a hyptothesis - * - * @param $scope - The controllers $scope - * @param $modalInstance - The ui.bootstrap $modalInstance service - * @param modalData - The data that is passed to the controller. Contains the object 'layoutSettings' - * @constructor - */ - function HypothesisLayoutSettingsController($scope, $modalInstance, modalData) { - var defaultLayoutProperties = { - nodesep: 50, - edgesep: 25, - ranksep: 50 - }; - - $scope.layoutSettings = {}; - - if (modalData.layoutSettings !== null) { - $scope.layoutSettings = modalData.layoutSettings; - } else { - $scope.layoutSettings = defaultLayoutProperties; - } - - $scope.update = function () { - $modalInstance.close($scope.layoutSettings); - }; - - $scope.close = function () { - $modalInstance.dismiss(); - }; - - $scope.defaultLayoutSettings = function () { - $scope.layoutSettings = defaultLayoutProperties; - }; - } -}()); \ No newline at end of file diff --git a/main/src/main/webapp/app/modules/modals/controllers/LearnResultDetailsModalController.js b/main/src/main/webapp/app/modules/modals/controllers/LearnResultDetailsModalController.js deleted file mode 100644 index b3dfd369a..000000000 --- a/main/src/main/webapp/app/modules/modals/controllers/LearnResultDetailsModalController.js +++ /dev/null @@ -1,48 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.modals') - .controller('LearnResultDetailsModalController', LearnResultDetailsModalController); - - LearnResultDetailsModalController.$inject = ['$scope', '$modalInstance', 'modalData', 'LearnResultResource']; - - /** - * The controller that is used to display the details of a learn result in a modal dialog. The data that is passed - * to this controller should be an object with a property 'result' which contains a learn result object. If none is - * given, nothing will be displayed. - * - * The template can be found at 'views/learn-result-details-modal.html'. - * - * @param $scope - The controllers scope - * @param $modalInstance - The ui.bootstrap $modalInstance service - * @param modalData - The data that is passed to the controller from its handle - * @param LearnResultResource - The Resource for LearnResult - * @constructor - */ - function LearnResultDetailsModalController($scope, $modalInstance, modalData, LearnResultResource) { - - $scope.tabs = [ - {heading: 'Current', result: modalData.result} - ]; - - if (modalData.result.stepNo > 0) { - LearnResultResource.getFinal(modalData.result.project, modalData.result.testNo) - .then(function (res) { - $scope.tabs.push({ - heading: 'Cumulated', - result: res - }) - }); - } else { - $scope.tabs[0].heading = 'Cumulated'; - } - - /** - * Close the modal dialog without passing any data - */ - $scope.ok = function () { - $modalInstance.dismiss(); - } - } -}()); \ No newline at end of file diff --git a/main/src/main/webapp/app/modules/modals/controllers/LearnSetupSettingsModalController.js b/main/src/main/webapp/app/modules/modals/controllers/LearnSetupSettingsModalController.js deleted file mode 100644 index b8288bccc..000000000 --- a/main/src/main/webapp/app/modules/modals/controllers/LearnSetupSettingsModalController.js +++ /dev/null @@ -1,70 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.modals') - .controller('LearnSetupSettingsModalController', LearnSetupSettingsModalController); - - LearnSetupSettingsModalController.$inject = [ - '$scope', '$modalInstance', 'modalData', 'learnAlgorithms', 'EqOracle', 'LearnConfiguration' - ]; - - /** - * The controller for the modal dialog where you can set the settings for an upcoming test run. Handles the template - * under 'views/modals/learn-setup-settings-modal.html'. Passes the edited instance of a LearnConfiguration on - * success. - * - * @param $scope - * @param $modalInstance - * @param modalData - The data that is passed to the controller. Must be an object with the property 'learnConfiguration' - * @param learnAlgorithms - The constants for learnAlgorithm names - * @param EqOracle - The model for an EqOracle - * @param LearnConfiguration - * @constructor - */ - function LearnSetupSettingsModalController($scope, $modalInstance, modalData, learnAlgorithms, EqOracle, LearnConfiguration) { - - /** - * The constants for eqOracles types - */ - $scope.eqOracles = EqOracle.types; - - /** - * The model for the select input that holds a type for an eqOracle - */ - $scope.selectedEqOracle = modalData.learnConfiguration.eqOracle.type; - - /** - * The constants for learnAlgorithm names - */ - $scope.learnAlgorithms = learnAlgorithms; - - /** - * The LearnConfiguration to be edited - * - * @type {LearnConfiguration} - */ - $scope.learnConfiguration = LearnConfiguration.build(modalData.learnConfiguration); - - /** - * Sets the Eq Oracle of the learn configuration depending on the selected value - */ - $scope.setEqOracle = function () { - $scope.learnConfiguration.eqOracle = EqOracle.build($scope.selectedEqOracle) - }; - - /** - * Close the modal dialog and pass the edited learn configuration instance. - */ - $scope.ok = function () { - $modalInstance.close($scope.learnConfiguration); - }; - - /** - * Close the modal dialog. - */ - $scope.closeModal = function () { - $modalInstance.dismiss(); - } - } -}()); \ No newline at end of file diff --git a/main/src/main/webapp/app/modules/modals/controllers/PromptDialogController.js b/main/src/main/webapp/app/modules/modals/controllers/PromptDialogController.js deleted file mode 100644 index 2ea3e51e6..000000000 --- a/main/src/main/webapp/app/modules/modals/controllers/PromptDialogController.js +++ /dev/null @@ -1,50 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.modals') - .controller('PromptDialogController', PromptDialogController); - - PromptDialogController.$inject = ['$scope', '$modalInstance', 'modalData']; - - /** - * The controller that handles the prompt modal dialog. - * - * @param $scope - * @param $modalInstance - * @param modalData - * @constructor - */ - function PromptDialogController($scope, $modalInstance, modalData) { - - /** The model for the input field for the user input **/ - $scope.userInput; - - /** The text to be displayed **/ - $scope.text = modalData.text; - - /** The regex the user input has to match **/ - $scope.inputPattern = modalData.regexp || ''; - - /** the message that is shown when the user input doesn't match the regex **/ - $scope.errorMsg = modalData.errorMsg || 'Unknown validation error'; - - /** - * Close the modal dialog and pass the user input - */ - $scope.ok = function () { - if ($scope.prompt_form.$valid) { - $modalInstance.close($scope.userInput); - } else { - $scope.prompt_form.submitted = true; - } - }; - - /** - * Close the modal dialog - */ - $scope.close = function () { - $modalInstance.dismiss(); - } - } -}()); \ No newline at end of file diff --git a/main/src/main/webapp/app/modules/modals/controllers/SymbolCreateModalController.js b/main/src/main/webapp/app/modules/modals/controllers/SymbolCreateModalController.js deleted file mode 100644 index d907186bf..000000000 --- a/main/src/main/webapp/app/modules/modals/controllers/SymbolCreateModalController.js +++ /dev/null @@ -1,93 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.modals') - .controller('SymbolCreateModalController', SymbolCreateModalController); - - SymbolCreateModalController.$inject = [ - '$scope', '$modalInstance', 'modalData', 'Symbol', 'SymbolResource', 'SymbolGroupResource', 'ToastService' - ]; - - /** - * Handles the behaviour of the modal to create a new symbol. - * - * The template for this modal can found at 'app/partials/modals/symbol-create-modal.html'. - * - * @param $scope - * @param $modalInstance - * @param modalData - * @param Symbol - * @param SymbolResource - * @param SymbolGroupResource - * @param Toast - * @constructor - */ - function SymbolCreateModalController($scope, $modalInstance, modalData, Symbol, SymbolResource, SymbolGroupResource, Toast) { - - // the id of the project the new symbol is created for - var projectId = null; - - /** - * The model of the symbol that will be created - * @type {Symbol} - */ - $scope.symbol = new Symbol(); - - /** - * An error message that can be displayed in the template - * @type {String|null} - */ - $scope.errorMsg = null; - - /** - * The list of available symbol groups where the new symbol could be created in - * @type {SymbolGroup[]} - */ - $scope.groups = []; - - /** - * The symbol group that is selected - * @type {null|SymbolGroup} - */ - $scope.selectedGroup = null; - - // fetch all symbol groups so that they can be selected in the template - (function init() { - projectId = modalData.projectId; - SymbolGroupResource.getAll(projectId) - .then(function (groups) { - $scope.groups = groups; - }); - }()); - - /** - * Makes a request to the API and create a new symbol. If the name of the group the user entered was not found - * the symbol will be put in the default group with the id 0. Closes the modal on success. - */ - $scope.createSymbol = function () { - $scope.errorMsg = null; - - var group = _.find($scope.groups, {name: $scope.selectedGroup}); - - // attach the new symbol to the default group in case none is specified - $scope.symbol.group = angular.isDefined(group) ? group.id : 0; - - SymbolResource.create(projectId, $scope.symbol) - .then(function (createdSymbol) { - Toast.success('Created symbol ' + createdSymbol.name + ''); - $modalInstance.close(createdSymbol); - }) - .catch(function (response) { - $scope.errorMsg = response.data.message; - }) - }; - - /** - * Closes the modal dialog - */ - $scope.closeModal = function () { - $modalInstance.dismiss(); - } - } -}()); \ No newline at end of file diff --git a/main/src/main/webapp/app/modules/modals/controllers/SymbolEditModalController.js b/main/src/main/webapp/app/modules/modals/controllers/SymbolEditModalController.js deleted file mode 100644 index 7a591dd1a..000000000 --- a/main/src/main/webapp/app/modules/modals/controllers/SymbolEditModalController.js +++ /dev/null @@ -1,81 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.modals') - .controller('SymbolEditModalController', SymbolEditModalController); - - SymbolEditModalController.$inject = [ - '$scope', '$modalInstance', 'modalData', 'Symbol', 'SymbolResource', 'ToastService' - ]; - - /** - * Handles the behaviour of the modal to edit an existing symbol and updates the edited symbol on the server. - * The corresponding template for this modal can found under 'app/partials/modals/symbol-edit-modal.html'. - * - * @param $scope - * @param $modalInstance - * @param modalData - * @param Symbol - * @param SymbolResource - * @param Toast - * @constructor - */ - function SymbolEditModalController($scope, $modalInstance, modalData, Symbol, SymbolResource, Toast) { - - /** - * The symbol that is passed to the modal as a copy in order to prevent two way binding in the template. - * @type {Symbol} - */ - $scope.symbol = Symbol.build(modalData.symbol); - - /** - * The error message that is displayed when update fails - * @type {null|string} - */ - $scope.errorMsg = null; - - // The copy of the symbol that will be passed back together with the updated one - var copy = Symbol.build($scope.symbol); - - /** - * Make a request to the API in order to update the symbol. Close the modal on success. - */ - $scope.updateSymbol = function () { - $scope.errorMsg = null; - - // remove the selection from the symbol in case there is any - delete $scope.symbol._selected; - delete $scope.symbol._collapsed; - - // do not update on server - if (angular.isDefined(modalData.updateOnServer) && !modalData.updateOnServer) { - $modalInstance.close({ - new: $scope.symbol, - old: copy - }); - return; - } - - // update the symbol and close the modal dialog on success with the updated symbol - SymbolResource.update($scope.symbol) - .then(function (updatedSymbol) { - Toast.success('Symbol updated'); - $modalInstance.close({ - new: updatedSymbol, - old: copy - }); - }) - .catch(function (response) { - $scope.errorMsg = response.data.message; - }) - }; - - /** - * Close the modal dialog - */ - $scope.closeModal = function () { - $modalInstance.dismiss(); - } - } -}()); \ No newline at end of file diff --git a/main/src/main/webapp/app/modules/modals/controllers/SymbolGroupCreateModalController.js b/main/src/main/webapp/app/modules/modals/controllers/SymbolGroupCreateModalController.js deleted file mode 100644 index 914a97c22..000000000 --- a/main/src/main/webapp/app/modules/modals/controllers/SymbolGroupCreateModalController.js +++ /dev/null @@ -1,87 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.modals') - .controller('SymbolGroupCreateModalController', SymbolGroupCreateModalController); - - SymbolGroupCreateModalController.$inject = [ - '$scope', '$modalInstance', 'modalData', 'SymbolGroup', 'SymbolGroupResource', '_', 'ToastService' - ]; - - /** - * The controller for the modal dialog that handles the creation of a new symbol group. - * - * The template can be found at 'views/modals/symbol-create-modal.html' - * - * @param $scope - * @param $modalInstance - * @param modalData - * @param SymbolGroup - * @param SymbolGroupResource - * @param _ - * @param Toast - * @constructor - */ - function SymbolGroupCreateModalController($scope, $modalInstance, modalData, SymbolGroup, SymbolGroupResource, _, Toast) { - - // the id of the project where the new symbol group should be created in - var projectId = modalData.projectId; - - /** - * The new symbol group - * @type {SymbolGroup} - */ - $scope.group = new SymbolGroup(); - - /** - * The list of all existing symbol groups. They are used in order to check if the name of the new symbol group - * already exists - * @type {SymbolGroup[]} - */ - $scope.groups = []; - - /** - * An error message that can be displayed in the modal template - * @type {String|null} - */ - $scope.errorMsg = null; - - // load all existing symbol groups - (function init() { - SymbolGroupResource.getAll(projectId) - .then(function (groups) { - $scope.groups = groups; - }); - }()); - - /** - * Creates a new symbol group and closes the modal on success and passes the newly created symbol group - */ - $scope.createGroup = function () { - $scope.errorMsg = null; - - var index = _.findIndex($scope.groups, {name: $scope.group.name}); - - if (index === -1) { - SymbolGroupResource.create(projectId, $scope.group) - .then(function (createdGroup) { - Toast.success('Symbol group ' + createdGroup.name + ' created'); - $modalInstance.close(createdGroup); - }) - .catch(function (response) { - $scope.errorMsg = response.data.message; - }); - } else { - $scope.errorMsg = 'The group name is already in use in this project'; - } - }; - - /** - * Close the modal. - */ - $scope.closeModal = function () { - $modalInstance.dismiss(); - } - } -}()); \ No newline at end of file diff --git a/main/src/main/webapp/app/modules/modals/controllers/SymbolGroupEditModalController.js b/main/src/main/webapp/app/modules/modals/controllers/SymbolGroupEditModalController.js deleted file mode 100644 index 02d194b72..000000000 --- a/main/src/main/webapp/app/modules/modals/controllers/SymbolGroupEditModalController.js +++ /dev/null @@ -1,86 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.modals') - .controller('SymbolGroupEditModalController', SymbolGroupEditModalController); - - SymbolGroupEditModalController.$inject = [ - '$scope', '$modalInstance', 'modalData', 'SymbolGroup', 'SymbolGroupResource', 'ToastService' - ]; - - /** - * The controller that handles the modal dialog for deleting and updating a symbol group. The modal data that is - * passed must have an property 'group' whose value should be an instance of SymbolGroup - * - * The template is at 'views/modals/symbol-group-edit-modal.html' - * - * @param $scope - * @param $modalInstance - * @param modalData - The data that is passed to this controller - * @param SymbolGroup - * @param SymbolGroupResource - * @param Toast - The ToastService - * @constructor - */ - function SymbolGroupEditModalController($scope, $modalInstance, modalData, SymbolGroup, SymbolGroupResource, Toast) { - - /** - * The symbol group that should be edited - * @type {SymbolGroup} - */ - $scope.group = SymbolGroup.build(modalData.group); - - /** - * An error message that can be displayed in the template - * @type {null|String} - */ - $scope.errorMsg = null; - - /** - * Updates the symbol group under edit and closes the modal dialog on success - */ - $scope.updateGroup = function () { - $scope.errorMsg = null; - - SymbolGroupResource.update($scope.group) - .then(function (updatedGroup) { - Toast.success('Group updated'); - $modalInstance.close({ - status: 'updated', - newGroup: updatedGroup, - oldGroup: modalData.group - }); - }) - .catch(function (response) { - $scope.errorMsg = response.data.message; - }) - }; - - /** - * Deletes the symbol group under edit and closes the modal dialog on success - */ - $scope.deleteGroup = function () { - $scope.errorMsg = null; - - SymbolGroupResource.delete($scope.group) - .then(function () { - Toast.success('Group ' + $scope.group.name + ' deleted'); - $modalInstance.close({ - status: 'deleted', - group: $scope.group - }); - }) - .catch(function (response) { - $scope.errorMsg = response.data.message; - }) - }; - - /** - * Closes the modal dialog - */ - $scope.closeModal = function () { - $modalInstance.dismiss(); - } - } -}()); \ No newline at end of file diff --git a/main/src/main/webapp/app/modules/modals/controllers/SymbolMoveModalController.js b/main/src/main/webapp/app/modules/modals/controllers/SymbolMoveModalController.js deleted file mode 100644 index 87046b987..000000000 --- a/main/src/main/webapp/app/modules/modals/controllers/SymbolMoveModalController.js +++ /dev/null @@ -1,87 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.modals') - .controller('SymbolMoveModalController', SymbolMoveModalController); - - SymbolMoveModalController.$inject = [ - '$scope', '$modalInstance', 'modalData', 'Symbol', 'SymbolResource', 'ToastService', '_' - ]; - - /** - * The controller that handles the moving of symbols into another group. - * - * The template can be found at 'views/modals/symbol-move-modal.html' - * - * @param $scope - * @param $modalInstance - * @param modalData - * @param Symbol - * @param SymbolResource - * @param Toast - * @param _ - * @constructor - */ - function SymbolMoveModalController($scope, $modalInstance, modalData, Symbol, SymbolResource, Toast, _) { - - /** - * The list of symbols that should be moved - * @type {Symbol[]} - */ - $scope.symbols = _.map(modalData.symbols, Symbol.build); - - /** - * The list of existing symbol groups - * @type {SymbolGroup[]} - */ - $scope.groups = modalData.groups; - - /** - * The symbol group the symbols should be moved into - * @type {SymbolGroup|null} - */ - $scope.selectedGroup = null; - - /** - * Moves the symbols into the selected group by changing the group property of each symbol and then batch - * updating them on the server - */ - $scope.moveSymbols = function () { - if ($scope.selectedGroup !== null) { - _.forEach($scope.symbols, function (symbol) { - delete symbol._selected; - symbol.group = $scope.selectedGroup.id; - }); - - SymbolResource.move($scope.symbols, $scope.selectedGroup) - .success(function () { - Toast.success('Symbols move to group ' + $scope.selectedGroup.name + ''); - $modalInstance.close({ - symbols: modalData.symbols, - group: $scope.selectedGroup - }); - }) - .catch(function (response) { - Toast.danger('

Moving symbols failed

' + response.data.message); - }) - } - }; - - /** - * Selects the group where the symbols should be moved into - * - * @param {SymbolGroup} group - */ - $scope.selectGroup = function (group) { - $scope.selectedGroup = $scope.selectedGroup === group ? null : group; - }; - - /** - * Close the modal dialog - */ - $scope.closeModal = function () { - $modalInstance.dismiss(); - } - } -}()); \ No newline at end of file diff --git a/main/src/main/webapp/app/modules/modals/controllers/VariablesCountersOccurenceModalController.js b/main/src/main/webapp/app/modules/modals/controllers/VariablesCountersOccurenceModalController.js deleted file mode 100644 index dd026dff2..000000000 --- a/main/src/main/webapp/app/modules/modals/controllers/VariablesCountersOccurenceModalController.js +++ /dev/null @@ -1,123 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.modals') - .controller('VariablesCountersOccurrenceModalController', VariablesCountersOccurrenceModalController); - - VariablesCountersOccurrenceModalController.$inject = [ - '$scope', '$modalInstance', 'SessionService', 'SymbolGroupResource', '_' - ]; - - /** - * The controller of the modal dialog that shows all occurrences of used variables and counters in a project in - * all visible symbols. - * - * @param $scope - The controllers scope - * @param $modalInstance - the ui.bootstrap $modalInstance service - * @param Session - The SessionService - * @param SymbolGroupResource - The API resource for symbol groups - * @param _ - Lodash - * @constructor - */ - function VariablesCountersOccurrenceModalController($scope, $modalInstance, Session, SymbolGroupResource, _) { - - // the project in the session - var project = Session.project.get(); - - /** - * The occurrences of all variables and counter that where found. - * - * occurrence object: {group: ..., symbol: ..., action: ..., name: ...} where - * group := symbol group name - * symbol := symbol name - * action := action position - * name := variable/counter name - * - * @type {null|{counters: Array, variables: Array}} - */ - $scope.occurrences = null; - - // load all symbol groups and symbols - SymbolGroupResource.getAll(project.id, {embedSymbols: true}) - .then(function (groups) { - $scope.occurrences = findOccurrences(groups); - }); - - /** - * Finds all occurrences of variables and counters in all existing actions of the project. - * - * @param {SymbolGroup[]} groups - All symbol groups - * @returns {{counters: Array, variables: Array}} - The occurrences - */ - function findOccurrences(groups) { - var occurrences = { - counters: [], - variables: [] - }; - - // list of found counters of a single action property - var foundCounters; - - // list of found variables of a single action property - var foundVariables; - - /** - * Creates an occurrence object - * - * @param {SymbolGroup} group - The symbol group where the variable/counter is found - * @param {Symbol} symbol - The symbol where the variable/counter is found - * @param {number} actionPos - The position of the action in the symbol - * @param {string} counterOrVariable - the name of the variable with prefix $ or # - * @returns {{group: (group.name|*), symbol: *, action: *, name: string}} - */ - function createOccurrence(group, symbol, actionPos, counterOrVariable) { - return { - group: group.name, - symbol: symbol.name, - action: actionPos, - name: counterOrVariable.substring(3, counterOrVariable.length - 2) - } - } - - // iterate over all groups, each symbol and each action - _.forEach(groups, function (group) { - _.forEach(group.symbols, function (symbol) { - - // don't check deleted symbols since they don't matter - if (!symbol.hidden) { - _.forEach(symbol.actions, function (action, i) { - - // check for each action property if a counter or a variable was found - for (var prop in action) { - if (action.hasOwnProperty(prop) && angular.isString(action[prop])) { - foundCounters = action[prop].match(/{{#(.*?)}}/g); - foundVariables = action[prop].match(/{{\$(.*?)}}/g); - - // add found variables and counters to occurrences - if (foundCounters !== null) { - _.forEach(foundCounters, function (counter) { - occurrences.counters.push(createOccurrence(group, symbol, i, counter)); - }) - } - if (foundVariables !== null) { - _.forEach(foundVariables, function (variable) { - occurrences.variables.push(createOccurrence(group, symbol, i, variable)); - }) - } - } - } - }) - } - }) - }); - - return occurrences; - } - - /** Close the modal dialog */ - $scope.close = function () { - $modalInstance.dismiss(); - } - } -}()); \ No newline at end of file diff --git a/main/src/main/webapp/app/modules/modals/directives/actionCreateModalHandle.js b/main/src/main/webapp/app/modules/modals/directives/actionCreateModalHandle.js deleted file mode 100644 index edb17f15f..000000000 --- a/main/src/main/webapp/app/modules/modals/directives/actionCreateModalHandle.js +++ /dev/null @@ -1,65 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.modals') - .directive('actionCreateModalHandle', actionCreateModalHandle); - - actionCreateModalHandle.$inject = ['$modal', 'paths']; - - /** - * The directive that is used to handle the modal dialog for creating an action. Must be used as an attribute for - * the attached element. It attaches a click event to the element that opens the modal dialog. Does NOT saves the - * action on the server. - * - * The directive excepts one additional attribute. 'onCreated' has to be a function with one parameter where the - * created action is passed on success. - * - * Can be used like this: '' - * - * @param $modal - The modal service - * @param paths - The applications paths constant - * @returns {{restrict: string, scope: {onCreated: string}, link: link}} - */ - function actionCreateModalHandle($modal, paths) { - - // the directive - return { - restrict: 'A', - scope: { - onCreated: '&' - }, - link: link - }; - - // handles the directives logic - function link(scope, el, attr) { - el.on('click', handleModal); - - function handleModal() { - - // create the modal - var modal = $modal.open({ - templateUrl: paths.COMPONENTS + '/modals/views/action-create-modal.html', - controller: 'ActionCreateModalController', - resolve: { - modalData: function () { - return { - addAction: function (action) { - if (action !== null) { - scope.onCreated()(action); - } - } - } - } - } - }); - - // call the callback on success - modal.result.then(function (action) { - scope.onCreated()(action); - }); - } - } - } -}()); \ No newline at end of file diff --git a/main/src/main/webapp/app/modules/modals/directives/actionEditModalHandle.js b/main/src/main/webapp/app/modules/modals/directives/actionEditModalHandle.js deleted file mode 100644 index 4f20d9283..000000000 --- a/main/src/main/webapp/app/modules/modals/directives/actionEditModalHandle.js +++ /dev/null @@ -1,62 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.modals') - .directive('actionEditModalHandle', actionEditModalHandle); - - actionEditModalHandle.$inject = ['$modal', 'paths']; - - /** - * The directive that is used to handle the modal dialog for editing an action. Must be used as an attribute for the - * attached element. It attaches a click event to the element that opens the modal dialog. Does NOT update the symbol - * with the new action. - * - * The directive excepts two additional attributes. 'action' has to contain the action object to be edited. - * 'onUpdated' has to be a function with one parameter where the updated action is passed on success. - * - * Can be used like this: '' - * - * @param $modal - The modal service - * @param paths - The applications paths constant - * @returns {{restrict: string, scope: {action: string, onUpdated: string}, link: link}} - */ - function actionEditModalHandle($modal, paths) { - - // the directive - return { - restrict: 'A', - scope: { - action: '=', - onUpdated: '&' - }, - link: link - }; - - // handles the directives logic - function link(scope, el, attr) { - el.on('click', handleModal); - - function handleModal() { - - // create and open the modal dialog - var modal = $modal.open({ - templateUrl: paths.COMPONENTS + '/modals/views/action-edit-modal.html', - controller: 'ActionEditModalController', - resolve: { - modalData: function () { - return { - action: scope.action - }; - } - } - }); - - // when successfully creating an action, call the callback function and pass the updated action - modal.result.then(function (action) { - scope.onUpdated()(action); - }); - } - } - } -}()); \ No newline at end of file diff --git a/main/src/main/webapp/app/modules/modals/directives/hypothesisLayoutSettingsModalHandle.js b/main/src/main/webapp/app/modules/modals/directives/hypothesisLayoutSettingsModalHandle.js deleted file mode 100644 index 519a257c5..000000000 --- a/main/src/main/webapp/app/modules/modals/directives/hypothesisLayoutSettingsModalHandle.js +++ /dev/null @@ -1,58 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.modals') - .directive('hypothesisLayoutSettingsModalHandle', hypothesisLayoutSettingsModalHandle); - - hypothesisLayoutSettingsModalHandle.$inject = ['$modal', 'paths']; - - /** - * The directive that handles the opening of the modal dialog for layout setting of a hypothesis. Has to be used - * as attribute. It attaches a click event to its element that opens the modal dialog. - * - * The corresponding controller should inject 'modalData' {Object}. It holds a property 'layoutSettings' which - * contains the layoutSettings model. - * - * Attribute 'layoutSettings' {Object} should be the model that is passed to the hypothesis directive. - * Attribute 'onUpdate' {function} should be a callback function with a single parameter for the settings - * - * Use: '' - * - * @param $modal - The ui.boostrap $modal service - * @param paths - The constant with application paths - * @returns {{restrict: string, scope: {layoutSettings: string}, link: link}} - */ - function hypothesisLayoutSettingsModalHandle($modal, paths) { - return { - restrict: 'A', - scope: { - layoutSettings: '=', - onUpdate: '&' - }, - link: link - }; - - function link(scope, el) { - el.on('click', handleClick); - - function handleClick() { - var modal = $modal.open({ - templateUrl: paths.COMPONENTS + '/modals/views/hypothesis-layout-settings-modal.html', - controller: 'HypothesisLayoutSettingsController', - resolve: { - modalData: function () { - return { - layoutSettings: angular.copy(scope.layoutSettings) - } - } - } - }); - - modal.result.then(function (layoutSettings) { - scope.onUpdate()(layoutSettings); - }) - } - } - } -}()); \ No newline at end of file diff --git a/main/src/main/webapp/app/modules/modals/directives/learnResultDetailsModalHandle.js b/main/src/main/webapp/app/modules/modals/directives/learnResultDetailsModalHandle.js deleted file mode 100644 index 3063dfd9d..000000000 --- a/main/src/main/webapp/app/modules/modals/directives/learnResultDetailsModalHandle.js +++ /dev/null @@ -1,52 +0,0 @@ -(function () { - - angular - .module('ALEX.modals') - .directive('learnResultDetailsModalHandle', learnResultDetailsModalHandle); - - learnResultDetailsModalHandle.$inject = ['$modal', 'paths']; - - /** - * The directive that handles the modal dialog for displaying details about a learn result. Can only be used as - * an attribute and expects a second attribute 'result' which should be the LearnResult whose details should be - * shown. Attaches a click event on the element that opens the modal. - * - * Use it like this: '' - * - * @param $modal - The modal service - * @param paths - The application paths constant - * @returns {{restrict: string, scope: {result: string}, link: link}} - */ - function learnResultDetailsModalHandle($modal, paths) { - - // the directive - return { - restrict: 'A', - scope: { - result: '=' - }, - link: link - }; - - // the behaviour of the directive - function link(scope, el, attrs) { - el.on('click', handleModal); - - function handleModal() { - if (angular.isDefined(scope.result)) { - $modal.open({ - templateUrl: paths.COMPONENTS + '/modals/views/learn-result-details-modal.html', - controller: 'LearnResultDetailsModalController', - resolve: { - modalData: function () { - return { - result: scope.result - } - } - } - }) - } - } - } - } -}()); \ No newline at end of file diff --git a/main/src/main/webapp/app/modules/modals/directives/learnSetupSettingsModalHandle.js b/main/src/main/webapp/app/modules/modals/directives/learnSetupSettingsModalHandle.js deleted file mode 100644 index e33ad6e76..000000000 --- a/main/src/main/webapp/app/modules/modals/directives/learnSetupSettingsModalHandle.js +++ /dev/null @@ -1,55 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.modals') - .directive('learnSetupSettingsModalHandle', learnSetupSettingsModalHandle); - - learnSetupSettingsModalHandle.$inject = ['$modal', 'paths']; - - /** - * The directive that handles the opening of the modal dialog for manipulating a learn configuration. Can only be - * used as an attribute and attaches a click event to the source element that opens the modal. - * - * Attribute 'learnConfiguration' should be the model with a LearnConfiguration object instance. - * Attribute 'onOk' should be a callback function with one parameter where the modified config is passed. - * - * @param $modal - The ui.boostrap $modal service - * @param paths - The applications path constant - * @returns {{restrict: string, scope: {learnConfiguration: string, onOk: string}, link: link}} - */ - function learnSetupSettingsModalHandle($modal, paths) { - - // the directive - return { - restrict: 'A', - scope: { - learnConfiguration: '=', - onOk: '&' - }, - link: link - }; - - // the directives behaviour - function link(scope, el, attr) { - el.on('click', function () { - var modal = $modal.open({ - templateUrl: paths.COMPONENTS + '/modals/views/learn-setup-settings-modal.html', - controller: 'LearnSetupSettingsModalController', - resolve: { - modalData: function () { - return { - learnConfiguration: scope.learnConfiguration - }; - } - } - }); - modal.result.then(function (learnConfiguration) { - if (angular.isDefined(scope.onOk)) { - scope.onOk()(learnConfiguration); - } - }); - }); - } - } -}()); \ No newline at end of file diff --git a/main/src/main/webapp/app/modules/modals/directives/symbolCreateModalHandle.js b/main/src/main/webapp/app/modules/modals/directives/symbolCreateModalHandle.js deleted file mode 100644 index afbf0709c..000000000 --- a/main/src/main/webapp/app/modules/modals/directives/symbolCreateModalHandle.js +++ /dev/null @@ -1,53 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.modals') - .directive('symbolCreateModalHandle', symbolCreateModalHandle); - - symbolCreateModalHandle.$inject = ['$modal', 'paths']; - - /** - * The directive that handles the modal window for the creation of a new symbol. It attaches an click event to the - * attached element that opens the modal dialog. - * - * Use it as an Attribute like 'symbol-create-modal-handle' and add an attribute 'project-id' with the id of the - * project and an attribute 'on-created' which expects a callback function from the directives parent controller. - * The callback function should have one parameter that will be the newly created symbol. - * - * @param $modal - The $modal service - * @param paths - The constants for application paths - * @returns {{restrict: string, scope: {projectId: string, onCreated: string}, link: link}} - */ - function symbolCreateModalHandle($modal, paths) { - return { - restrict: 'A', - scope: { - projectId: '@', - onCreated: '&' - }, - link: link - }; - function link(scope, el) { - el.on('click', handleModal); - - function handleModal() { - var modal = $modal.open({ - templateUrl: paths.COMPONENTS + '/modals/views/symbol-create-modal.html', - controller: 'SymbolCreateModalController', - resolve: { - modalData: function () { - return { - projectId: scope.projectId - } - } - } - }); - - modal.result.then(function (symbol) { - scope.onCreated()(symbol); - }) - } - } - } -}()); \ No newline at end of file diff --git a/main/src/main/webapp/app/modules/modals/directives/symbolEditModalHandle.js b/main/src/main/webapp/app/modules/modals/directives/symbolEditModalHandle.js deleted file mode 100644 index 9fae90de4..000000000 --- a/main/src/main/webapp/app/modules/modals/directives/symbolEditModalHandle.js +++ /dev/null @@ -1,62 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.modals') - .directive('symbolEditModalHandle', symbolEditModalHandle); - - symbolEditModalHandle.$inject = ['$modal', 'paths']; - - /** - * The directive that handles the modal window for the editing of a new symbol. It attaches an click event to the - * attached element that opens the modal dialog. - * - * Use it as an attribute like 'symbol-edit-modal-handle' and add an attribute 'on-created' which expects a callback - * function from the directives parent controller. The callback function should have one parameter that will be the - * newly updated symbol. - * - * @param $modal - The $modal service - * @param paths - The constants for application paths - * @returns {{restrict: string, scope: {symbol: string, onUpdated: string}, link: link}} - */ - function symbolEditModalHandle($modal, paths) { - - return { - restrict: 'A', - scope: { - symbol: '=', - onUpdated: '&', - updateOnServer: '=' - }, - link: link - }; - - /** - * @param scope - * @param el - * @param attrs - */ - function link(scope, el) { - el.on('click', handleModal); - - function handleModal() { - var modal = $modal.open({ - templateUrl: paths.COMPONENTS + '/modals/views/symbol-edit-modal.html', - controller: 'SymbolEditModalController', - resolve: { - modalData: function () { - return { - symbol: scope.symbol, - updateOnServer: scope.updateOnServer - }; - } - } - }); - - modal.result.then(function (symbol) { - scope.onUpdated()(symbol.new, symbol.old); - }) - } - } - } -}()); \ No newline at end of file diff --git a/main/src/main/webapp/app/modules/modals/directives/symbolGroupCreateModalHandle.js b/main/src/main/webapp/app/modules/modals/directives/symbolGroupCreateModalHandle.js deleted file mode 100644 index 2e47dcaf9..000000000 --- a/main/src/main/webapp/app/modules/modals/directives/symbolGroupCreateModalHandle.js +++ /dev/null @@ -1,51 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.modals') - .directive('symbolGroupCreateModalHandle', symbolGroupCreateModalHandle); - - symbolGroupCreateModalHandle.$inject = ['$modal', 'paths']; - - /** - * The directive for handling the opening of the modal for creating a new symbol group. Can only be used as - * an attribute and attaches a click event to its source element. - * - * Use: '' - * - * @param $modal - The ui.bootstrap $modal service - * @param paths - The applications paths constant - * @returns {{restrict: string, scope: {projectId: string, onCreated: string}, link: link}} - */ - function symbolGroupCreateModalHandle($modal, paths) { - return { - restrict: 'A', - scope: { - projectId: '@', - onCreated: '&' - }, - link: link - }; - - function link(scope, el, attrs) { - el.on('click', handleModal); - - function handleModal() { - var modal = $modal.open({ - templateUrl: paths.COMPONENTS + '/modals/views/symbol-group-create-modal.html', - controller: 'SymbolGroupCreateModalController', - resolve: { - modalData: function () { - return { - projectId: scope.projectId - }; - } - } - }); - modal.result.then(function (group) { - scope.onCreated()(group); - }) - } - } - } -}()); \ No newline at end of file diff --git a/main/src/main/webapp/app/modules/modals/directives/symbolGroupEditModalHandle.js b/main/src/main/webapp/app/modules/modals/directives/symbolGroupEditModalHandle.js deleted file mode 100644 index 20cc2bacd..000000000 --- a/main/src/main/webapp/app/modules/modals/directives/symbolGroupEditModalHandle.js +++ /dev/null @@ -1,61 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.modals') - .directive('symbolGroupEditModalHandle', symbolGroupEditModalHandle); - - symbolGroupEditModalHandle.$inject = ['$modal', 'paths']; - - /** - * The directive that handles the opening of the modal for editing or deleting a symbol group. Can only be used as - * attribute and attaches a click event to the source element that opens the modal. - * - * Attribute 'group' - The model for the symbol group - * Attribute 'onUpdated' - The callback function with two parameters. First the updated and second the old group - * Attribute 'onDeleted' - The callback function with one parameter - the deleted group object - * - * Use: '' - * - * @param $modal - The ui.bootstrap $modal service - * @param paths - The applications paths constant - * @returns {{scope: {group: string, onUpdated: string, onDeleted: string}, link: link}} - */ - function symbolGroupEditModalHandle($modal, paths) { - return { - restrict: 'A', - scope: { - group: '=', - onUpdated: '&', - onDeleted: '&' - }, - link: link - }; - - function link(scope, el, attrs) { - el.on('click', handleModal); - - function handleModal() { - var modal = $modal.open({ - templateUrl: paths.COMPONENTS + '/modals/views/symbol-group-edit-modal.html', - controller: 'SymbolGroupEditModalController', - resolve: { - modalData: function () { - return { - group: scope.group - } - } - } - }); - - modal.result.then(function (data) { - if (data.status === 'updated') { - scope.onUpdated()(data.newGroup, data.oldGroup); - } else if (data.status === 'deleted') { - scope.onDeleted()(data.group); - } - }) - } - } - } -}()); \ No newline at end of file diff --git a/main/src/main/webapp/app/modules/modals/directives/symbolMoveModalHandle.js b/main/src/main/webapp/app/modules/modals/directives/symbolMoveModalHandle.js deleted file mode 100644 index a3c5077bd..000000000 --- a/main/src/main/webapp/app/modules/modals/directives/symbolMoveModalHandle.js +++ /dev/null @@ -1,50 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.modals') - .directive('symbolMoveModalHandle', symbolMoveModalHandle); - - symbolMoveModalHandle.$inject = ['$modal', 'paths']; - - /** - * The directive for handling the opening of the modal for moving symbols into another group. Can only be used as - * an attribute and attaches a click event to its source element. - * - * Use: '' - * - * @param $modal - The ui.bootstrap $modal service - * @param paths - The applications paths constant - * @returns {{scope: {symbols: string, groups: string, onMoved: string}, link: link}} - */ - function symbolMoveModalHandle($modal, paths) { - return { - restrict: 'A', - scope: { - symbols: '=', - groups: '=', - onMoved: '&' - }, - link: link - }; - function link(scope, el, attrs) { - el.on('click', function () { - var modal = $modal.open({ - templateUrl: paths.COMPONENTS + '/modals/views/symbol-move-modal.html', - controller: 'SymbolMoveModalController', - resolve: { - modalData: function () { - return { - symbols: scope.symbols, - groups: scope.groups - } - } - } - }); - modal.result.then(function (data) { - scope.onMoved()(data.symbols, data.group); - }) - }); - } - } -}()); \ No newline at end of file diff --git a/main/src/main/webapp/app/modules/modals/directives/variablesCountersOccurrenceModalHandle.js b/main/src/main/webapp/app/modules/modals/directives/variablesCountersOccurrenceModalHandle.js deleted file mode 100644 index 33f190979..000000000 --- a/main/src/main/webapp/app/modules/modals/directives/variablesCountersOccurrenceModalHandle.js +++ /dev/null @@ -1,34 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.modals') - .directive('variablesCountersOccurrenceModalHandle', variablesCountersOccurrenceModalHandle); - - variablesCountersOccurrenceModalHandle.$inject = ['$modal', 'paths']; - - /** - * The directive that handles the opening of the modal dialog that shows occurrences of variables and counters. Can - * only be used as an attribute and attaches a click event to the element that opens the modal.# - * - * Use: - * - * @param $modal - The ui.bootstrap $modal service - * @param paths - The application constant with paths - * @returns {{restrict: string, link: link}} - */ - function variablesCountersOccurrenceModalHandle($modal, paths) { - return { - restrict: 'A', - link: link - }; - function link(scope, el, attrs) { - el.on('click', function () { - var modal = $modal.open({ - templateUrl: paths.COMPONENTS + '/modals/views/variables-counters-occurrence-modal.html', - controller: 'VariablesCountersOccurrenceModalController' - }); - }); - } - } -}()); \ No newline at end of file diff --git a/main/src/main/webapp/app/modules/modals/init.js b/main/src/main/webapp/app/modules/modals/init.js deleted file mode 100644 index b8438e525..000000000 --- a/main/src/main/webapp/app/modules/modals/init.js +++ /dev/null @@ -1,6 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.modals', []); -}()); \ No newline at end of file diff --git a/main/src/main/webapp/app/modules/modals/services/PromptService.js b/main/src/main/webapp/app/modules/modals/services/PromptService.js deleted file mode 100644 index 359ca6036..000000000 --- a/main/src/main/webapp/app/modules/modals/services/PromptService.js +++ /dev/null @@ -1,68 +0,0 @@ -(function () { - 'use strict'; - - angular - .module('ALEX.modals') - .service('PromptService', PromptService); - - PromptService.$inject = ['$modal']; - - /** - * @param $modal - * @returns {{prompt: prompt, confirm: confirm}} - * @constructor - */ - function PromptService($modal) { - - // the available service methods - return { - prompt: prompt, - confirm: confirm - }; - - /** - * Opens the prompt dialog. - * - * @param text {string} - The text to display - * @param options {{regexp: string, errorMsg: string}} - * @return {*} - The modal result promise - */ - function prompt(text, options) { - var modal = $modal.open({ - templateUrl: 'app/modules/modals/views/prompt-dialog.html', - controller: 'PromptDialogController', - resolve: { - modalData: function () { - return { - text: text, - regexp: options.regexp, - errorMsg: options.errorMsg - }; - } - } - }); - return modal.result; - } - - /** - * Opens the confirm dialog - * - * @param text - The text to be displayed in the confirm dialog - * @returns {*} - The modal result promise - */ - function confirm(text) { - var modal = $modal.open({ - templateUrl: 'app/modules/modals/views/confirm-dialog.html', - controller: 'ConfirmDialogController', - resolve: { - modalData: function () { - return { - text: text - }; - } - } - }); - return modal.result; - } - } -}()); \ No newline at end of file diff --git a/main/src/main/webapp/app/modules/modals/views/action-create-modal.html b/main/src/main/webapp/app/modules/modals/views/action-create-modal.html deleted file mode 100644 index b84ad2c36..000000000 --- a/main/src/main/webapp/app/modules/modals/views/action-create-modal.html +++ /dev/null @@ -1,52 +0,0 @@ - - -
- - - - - -
\ No newline at end of file diff --git a/main/src/main/webapp/app/modules/modals/views/symbol-create-modal.html b/main/src/main/webapp/app/modules/modals/views/symbol-create-modal.html deleted file mode 100644 index f4fb10a3c..000000000 --- a/main/src/main/webapp/app/modules/modals/views/symbol-create-modal.html +++ /dev/null @@ -1,51 +0,0 @@ - - -
- - - - - -
\ No newline at end of file diff --git a/main/src/main/webapp/app/modules/modals/views/symbol-edit-modal.html b/main/src/main/webapp/app/modules/modals/views/symbol-edit-modal.html deleted file mode 100644 index 8330117f6..000000000 --- a/main/src/main/webapp/app/modules/modals/views/symbol-edit-modal.html +++ /dev/null @@ -1,43 +0,0 @@ - - -
- - - - - -
\ No newline at end of file diff --git a/main/src/main/webapp/app/modules/modals/views/symbol-group-create-modal.html b/main/src/main/webapp/app/modules/modals/views/symbol-group-create-modal.html deleted file mode 100644 index 6eb5041f9..000000000 --- a/main/src/main/webapp/app/modules/modals/views/symbol-group-create-modal.html +++ /dev/null @@ -1,33 +0,0 @@ - - -
- - - - - -
\ No newline at end of file diff --git a/main/src/main/webapp/app/modules/modals/views/symbol-group-edit-modal.html b/main/src/main/webapp/app/modules/modals/views/symbol-group-edit-modal.html deleted file mode 100644 index 166344f9a..000000000 --- a/main/src/main/webapp/app/modules/modals/views/symbol-group-edit-modal.html +++ /dev/null @@ -1,34 +0,0 @@ - - -
- - - - - -
\ No newline at end of file diff --git a/main/src/main/webapp/app/modules/modals/views/symbol-move-modal.html b/main/src/main/webapp/app/modules/modals/views/symbol-move-modal.html deleted file mode 100644 index 2fb3bf025..000000000 --- a/main/src/main/webapp/app/modules/modals/views/symbol-move-modal.html +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - diff --git a/main/src/main/webapp/app/modules/modals/views/variables-counters-occurrence-modal.html b/main/src/main/webapp/app/modules/modals/views/variables-counters-occurrence-modal.html deleted file mode 100644 index 31736773e..000000000 --- a/main/src/main/webapp/app/modules/modals/views/variables-counters-occurrence-modal.html +++ /dev/null @@ -1,52 +0,0 @@ - - - - - \ No newline at end of file diff --git a/main/src/main/webapp/app/stylesheets/_bootstrap.scss b/main/src/main/webapp/app/stylesheets/_bootstrap.scss deleted file mode 100644 index fcfc9e29d..000000000 --- a/main/src/main/webapp/app/stylesheets/_bootstrap.scss +++ /dev/null @@ -1,40 +0,0 @@ -// overwrite default variables -$font-size-base: 12px !default; - -// Core variables and mixins -@import "../../bower_components/bootstrap-sass/assets/stylesheets/bootstrap/variables"; -@import "../../bower_components/bootstrap-sass/assets/stylesheets/bootstrap/mixins"; - -// Reset and dependencies -@import "../../bower_components/bootstrap-sass/assets/stylesheets/bootstrap/normalize"; - -// Core CSS -@import "../../bower_components/bootstrap-sass/assets/stylesheets/bootstrap/scaffolding"; -@import "../../bower_components/bootstrap-sass/assets/stylesheets/bootstrap/type"; -@import "../../bower_components/bootstrap-sass/assets/stylesheets/bootstrap/code"; -@import "../../bower_components/bootstrap-sass/assets/stylesheets/bootstrap/grid"; -@import "../../bower_components/bootstrap-sass/assets/stylesheets/bootstrap/tables"; -@import "../../bower_components/bootstrap-sass/assets/stylesheets/bootstrap/forms"; -@import "../../bower_components/bootstrap-sass/assets/stylesheets/bootstrap/buttons"; - -// Components -@import "../../bower_components/bootstrap-sass/assets/stylesheets/bootstrap/component-animations"; -@import "../../bower_components/bootstrap-sass/assets/stylesheets/bootstrap/dropdowns"; -@import "../../bower_components/bootstrap-sass/assets/stylesheets/bootstrap/button-groups"; -@import "../../bower_components/bootstrap-sass/assets/stylesheets/bootstrap/input-groups"; -@import "../../bower_components/bootstrap-sass/assets/stylesheets/bootstrap/navs"; -@import "../../bower_components/bootstrap-sass/assets/stylesheets/bootstrap/navbar"; -@import "../../bower_components/bootstrap-sass/assets/stylesheets/bootstrap/labels"; -@import "../../bower_components/bootstrap-sass/assets/stylesheets/bootstrap/badges"; -@import "../../bower_components/bootstrap-sass/assets/stylesheets/bootstrap/alerts"; -@import "../../bower_components/bootstrap-sass/assets/stylesheets/bootstrap/list-group"; -@import "../../bower_components/bootstrap-sass/assets/stylesheets/bootstrap/panels"; -@import "../../bower_components/bootstrap-sass/assets/stylesheets/bootstrap/close"; - -// Components w/ JavaScript -@import "../../bower_components/bootstrap-sass/assets/stylesheets/bootstrap/modals"; -@import "../../bower_components/bootstrap-sass/assets/stylesheets/bootstrap/tooltip"; - -// Utility classes -@import "../../bower_components/bootstrap-sass/assets/stylesheets/bootstrap/utilities"; -@import "../../bower_components/bootstrap-sass/assets/stylesheets/bootstrap/responsive-utilities"; diff --git a/main/src/main/webapp/app/stylesheets/_font-awesome.scss b/main/src/main/webapp/app/stylesheets/_font-awesome.scss deleted file mode 100644 index bfd9b6f21..000000000 --- a/main/src/main/webapp/app/stylesheets/_font-awesome.scss +++ /dev/null @@ -1,14 +0,0 @@ -$fa-font-path: "./fonts" !default; - -@import "../../bower_components/font-awesome/scss/variables"; -@import "../../bower_components/font-awesome/scss/mixins"; -@import "../../bower_components/font-awesome/scss/path"; -@import "../../bower_components/font-awesome/scss/core"; -@import "../../bower_components/font-awesome/scss/larger"; -@import "../../bower_components/font-awesome/scss/fixed-width"; -@import "../../bower_components/font-awesome/scss/list"; -@import "../../bower_components/font-awesome/scss/bordered-pulled"; -@import "../../bower_components/font-awesome/scss/animated"; -@import "../../bower_components/font-awesome/scss/rotated-flipped"; -@import "../../bower_components/font-awesome/scss/stacked"; -@import "../../bower_components/font-awesome/scss/icons"; \ No newline at end of file diff --git a/main/src/main/webapp/app/stylesheets/_html-element-picker.scss b/main/src/main/webapp/app/stylesheets/_html-element-picker.scss deleted file mode 100644 index 08a4bdd0d..000000000 --- a/main/src/main/webapp/app/stylesheets/_html-element-picker.scss +++ /dev/null @@ -1,72 +0,0 @@ -#web-element-picker-wrapper { - position: fixed; - top: 0; - bottom: 0; - width: 100%; - z-index: 1200; - background: rgba(0, 0, 0, 0.75); -} - -#web-element-picker { - position: absolute; - top: 25px; - bottom: 25px; - left: 25px; - right: 25px; - border: 3px solid #555; - box-shadow: 0px 2px 10px #000; - z-index: 1210; - - .navbar { - border-radius: 0; - display: flex; - flex-flow: row wrap; - - .url-bar, .selection-bar, .button-bar { - flex: 1 100%; - } - - .url-bar { - flex: 0 0 auto; - order: 1; - } - .selection-bar { - flex: 1 0 0px; - order: 2; - padding-right: 15px; - - .btn-group { - display: flex; - flex-flow: row wrap; - - .selection-bar-toggle-btn { - flex: 0 0 auto; - order: 1 - } - .selection-bar-selector { - flex: 1 0 0px; - order: 2; - text-align: left; - } - } - - } - .button-bar { - flex: 0 0 auto; - order: 3; - padding-right: 15px - } - } - - .iframe-wrapper { - position: absolute; - overflow: hidden; - background: #fff; - top: 52px; - bottom: 0; - width: 100%; - iframe { - border: none; - } - } -} \ No newline at end of file diff --git a/main/src/main/webapp/app/images/favicon.png b/main/src/main/webapp/assets/images/favicon.png similarity index 100% rename from main/src/main/webapp/app/images/favicon.png rename to main/src/main/webapp/assets/images/favicon.png diff --git a/src/site/resources/images/logo.png b/main/src/main/webapp/assets/images/logo.png similarity index 100% rename from src/site/resources/images/logo.png rename to main/src/main/webapp/assets/images/logo.png diff --git a/main/src/main/webapp/bower.json b/main/src/main/webapp/bower.json deleted file mode 100644 index 6ad05f987..000000000 --- a/main/src/main/webapp/bower.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "name": "ALEX", - "version": "1.0.0", - "main": "./app.html", - "description": "An Interface for Active Automata Learning", - "dependencies": { - "angular": "~1.4.1", - "angular-route": "~1.4.1", - "angular-animate": "~1.4.1", - "angular-ui-router": "~0.2.15", - "angular-bootstrap": "~0.12.1", - "ace": "~1.1.9", - "angular-ui-ace": "~0.2.3", - "ngtoast": "~1.5.4", - "n3-line-chart": "~1.1.9", - "angular-selection-model": "~0.8.4", - "ng-file-upload": "~5.0.9", - "Sortable": "RubaXa/Sortable#~1.2.0", - "d3": "~3.5.5", - "lodash": "3.1.0", - "dagre-d3": "~0.4.8", - "font-awesome": "~4.3.0", - "bootstrap-sass": "twbs/bootstrap-sass#~3.3.5" - }, - "devDependencies": {}, - "resolutions": { - "angular": "~1.4.1", - "lodash": "3.1.0" - } -} diff --git a/main/src/main/webapp/gruntfile.js b/main/src/main/webapp/gruntfile.js index a78a8ca7e..5d9681b26 100644 --- a/main/src/main/webapp/gruntfile.js +++ b/main/src/main/webapp/gruntfile.js @@ -1,44 +1,31 @@ module.exports = function (grunt) { - var scripts = [ - 'app/modules/init.js', - - // core module - 'app/modules/core/init.js', - 'app/modules/core/constants.js', - 'app/modules/core/routes.js', - 'app/modules/core/controllers/*.js', - 'app/modules/core/directives/*.js', - 'app/modules/core/filters/*.js', - 'app/modules/core/models/*.js', - 'app/modules/core/resources/*.js', - 'app/modules/core/services/*.js', - - // actions module - 'app/modules/actions/init.js', - 'app/modules/actions/constants.js', - 'app/modules/actions/directives/**/*.js', - 'app/modules/actions/services/**/*.js', - 'app/modules/actions/models/**/*.js', - - // modals module - 'app/modules/modals/init.js', - 'app/modules/modals/controllers/*.js', - 'app/modules/modals/directives/*.js', - 'app/modules/modals/services/*.js' + var libraries = [ + 'node_modules/ace-builds/src/ace.js', + 'node_modules/ace-builds/src/theme-eclipse.js', + 'node_modules/ace-builds/src/mode-json.js', + 'node_modules/angular/angular.js', + 'node_modules/angular-animate/angular-animate.js', + 'node_modules/angular-sanitize/angular-sanitize.js', + 'node_modules/angular-messages/angular-messages.js', + 'node_modules/angular-ui-ace/src/ui-ace.js', + 'node_modules/n3-charts/build/LineChart.js', + 'node_modules/selection-model/dist/selection-model.js' ]; grunt .initConfig({ - pkg: grunt.file.readJSON('bower.json'), + pkg: grunt.file.readJSON('package.json'), uglify: { - options: { - banner: '/*! <%= pkg.name %> <%= grunt.template.today("yyyy-mm-dd") %> */\n' - }, app: { files: { - './app/app.min.js': ['./app/app.js'] + './dist/alex.min.js': ['./dist/alex.js'] + } + }, + libs: { + files: { + './dist/libs.min.js': ['./dist/libs.js'] } } }, @@ -47,70 +34,53 @@ module.exports = function (grunt) { options: { separator: ';\n' }, - app: { - src: ['app/templates.js', scripts], - dest: './app/app.js' - }, libs: { - src: [ - 'bower_components/angular/angular.min.js', - 'bower_components/angular-ui-router/release/angular-ui-router.min.js', - 'bower_components/angular-animate/angular-animate.min.js', - 'bower_components/lodash/lodash.min.js', - 'bower_components/angular-selection-model/dist/selection-model.min.js', - 'bower_components/angular-ui-ace/ui-ace.min.js', - 'bower_components/ace-builds/src-min/ace.js', - 'bower_components/ace-builds/src-min/theme-eclipse.js', - 'bower_components/ace-builds/src-min/mode-json.js', - 'bower_components/ngtoast/dist/ngToast.min.js', - 'bower_components/angular-sanitize/angular-sanitize.min.js', - 'bower_components/angular-bootstrap/ui-bootstrap.min.js', - 'bower_components/angular-bootstrap/ui-bootstrap-tpls.min.js', - 'bower_components/d3/d3.min.js', - 'bower_components/graphlib/dist/graphlib.core.min.js', - 'bower_components/dagre/dist/dagre.core.min.js', - 'bower_components/dagre-d3/dist/dagre-d3.core.min.js', - 'bower_components/n3-line-chart/build/line-chart.min.js', - 'bower_components/Sortable/Sortable.min.js', - 'bower_components/Sortable/ng-sortable.js', - 'bower_components/ng-file-upload/ng-file-upload.min.js' - ], - dest: 'app/libs.min.js' + src: libraries, + dest: 'dist/libs.js' } }, html2js: { options: { useStrict: true, - base: '../webapp' + base: '../webapp/src', + module: 'ALEX.templates', + singleModule: true, + htmlmin: { + collapseBooleanAttributes: false, + collapseWhitespace: true, + removeAttributeQuotes: false, + removeComments: true, + removeEmptyAttributes: false, + removeRedundantAttributes: false, + removeScriptTypeAttributes: false, + removeStyleLinkTypeAttributes: false + } }, all: { - src: [ - 'app/modules/core/views/**/*.html', - 'app/modules/actions/views/*.html', - 'app/modules/modals/views/*.html'], - dest: 'app/templates.js' + src: ['src/html/**/*.html'], + dest: 'dist/alex.templates.js' } }, watch: { scripts: { - files: ['app/modules/**/*.js'], + files: ['src/js/**/*.js'], tasks: ['build-js'], options: { spawn: false } }, sass: { - files: ['app/stylesheets/**/*.scss'], + files: ['src/scss/**/*.scss'], tasks: ['build-css'], options: { spawn: false } }, html: { - files: ['app/modules/**/*.html'], - tasks: ['build-js'], + files: ['src/html/**/*.html'], + tasks: ['build-html'], options: { spawn: false } @@ -123,69 +93,78 @@ module.exports = function (grunt) { }, dist: { files: { - 'app/stylesheets/style.css': 'app/stylesheets/style.scss' + 'dist/style.css': 'src/scss/style.scss' } } }, - karma: { - unit: { - configFile: 'tests/unit/karma.conf.js' - } - }, - cssmin: { target: { files: { - 'app/style.min.css': [ - 'bower_components/ngtoast/dist/ngToast.min.css', - 'bower_components/codemirror/lib/codemirror.css', - 'app/stylesheets/style.css' + 'dist/style.min.css': [ + 'node_modules/n3-charts/build/LineChart.css', + 'node_modules/angular-dragula/dist/dragula.min.css', + 'node_modules/angular-toastr/dist/angular-toastr.min.css', + 'dist/style.css' ] } } }, - clean: { - js: ["app/templates.js"] + postcss: { + options: { + map: false, + processors: [ + require('autoprefixer')({ + browsers: 'last 2 versions' + }) + ] + }, + dist: { + src: 'dist/style.css' + } + }, + + ngAnnotate: { + options: { + singleQuotes: true + }, + dist: { + files: { + 'dist/alex.js': ['dist/alex.js'] + } + } }, - bower: { - install: { + browserify: { + dist: { + files: { + 'dist/alex.js': ['src/js/index.js'] + }, options: { - targetDir: './bower_components/', - verbose: true, - copy: false + transform: [['babelify', { + sourceMap: false, + presets: ['es2015'], + compact: false + }]] } } }, - copy: { - fonts: { - files: [ - { - expand: true, - flatten: true, - src: ['bower_components/font-awesome/fonts/**'], - dest: 'app/fonts', - filter: 'isFile' - } - ] + karma: { + unit: { + configFile: 'tests/karma.conf.unit.js' } }, - postcss: { - options: { - map: false, - - processors : [ - require('autoprefixer-core')({ - browsers: 'last 2 versions' - }) - ] - }, + jshint: { dist: { - src: 'app/stylesheets/style.css' + src: ['src/js/**/*.js'] + }, + options: { + 'esnext': true, + 'laxbreak': true, + '-W053': true } } }); @@ -197,13 +176,14 @@ module.exports = function (grunt) { grunt.loadNpmTasks('grunt-sass'); grunt.loadNpmTasks('grunt-html2js'); grunt.loadNpmTasks('grunt-karma'); - grunt.loadNpmTasks('grunt-contrib-clean'); - grunt.loadNpmTasks('grunt-bower-task'); - grunt.loadNpmTasks('grunt-contrib-copy'); grunt.loadNpmTasks('grunt-postcss'); - - grunt.registerTask('build-js', ['html2js', 'concat', 'uglify', 'clean']); - grunt.registerTask('build-css', ['sass', 'postcss', 'cssmin', 'copy:fonts']); - grunt.registerTask('default', ['build-js', 'build-css']); - grunt.registerTask('test-unit', ['karma']); + grunt.loadNpmTasks('grunt-ng-annotate'); + grunt.loadNpmTasks('grunt-browserify'); + grunt.loadNpmTasks('grunt-contrib-jshint'); + + grunt.registerTask('build-js', ['browserify', 'ngAnnotate', 'uglify:app']); + grunt.registerTask('build-css', ['sass', 'postcss', 'cssmin']); + grunt.registerTask('build-html', ['html2js']); + grunt.registerTask('default', ['build-html', 'concat:libs', 'build-js', 'uglify:libs', 'build-css']); + grunt.registerTask('test', ['karma:unit']); }; diff --git a/main/src/main/webapp/index.html b/main/src/main/webapp/index.html index 6177edf86..34589fdc0 100644 --- a/main/src/main/webapp/index.html +++ b/main/src/main/webapp/index.html @@ -1,24 +1,21 @@ - + - - - - - + + ALEX - Automata Learning Experience + + - - - - - + - + -
+ + + \ No newline at end of file diff --git a/main/src/main/webapp/package.json b/main/src/main/webapp/package.json index 72af4529a..0df4607ae 100644 --- a/main/src/main/webapp/package.json +++ b/main/src/main/webapp/package.json @@ -1,33 +1,71 @@ { "name": "ALEX", "version": "1.0.0", - "description": "An Interface for Active Automata Learning", - "repository": "https://projekte.itmc.tu-dortmund.de/projects/learnlibweb", - "main": "app.html", + "description": "An interface for Active Automata Learning", + "repository": { + "type": "git", + "url": "https://github.com/LearnLib/alex.git" + }, + "licence": { + "type": "Apache-2.0", + "url": "http://www.apache.org/licenses/LICENSE-2.0" + }, + "contributors": [ + { + "name": "Alexander Bainczyk", + "email": "alexander.bainczyk@tu-dortmund.de" + }, + { + "name": "Alexander Schieweck", + "email": "alexander.bainczyk@tu-dortmund.de" + } + ], + "main": "index.html", + "dependencies": { + "ace-builds": "^1.2.2", + "angular": "^1.5.0", + "angular-animate": "^1.5.0", + "angular-dragula": "^1.1.9", + "angular-jwt": "0.0.9", + "angular-messages": "^1.5.0", + "angular-sanitize": "^1.5.0", + "angular-toastr": "^1.7.0", + "angular-ui-ace": "^0.2.3", + "angular-ui-bootstrap": "^1.1.2", + "angular-ui-router": "^0.2.17", + "bootstrap-sass": "^3.3.6", + "d3": "^3.5.9", + "dagre-d3": "^0.4.17", + "font-awesome": "^4.4.0", + "install": "^0.3.0", + "lodash": "^3.10.1", + "n3-charts": "^2.0.3", + "ng-file-upload": "^10.0.2", + "npm": "^3.4.0", + "selection-model": "^0.10.0" + }, "devDependencies": { - "angular-mocks": "^1.3.15", - "autoprefixer-core": "^5.2.1", + "angular-mocks": "^1.5.0", + "autoprefixer": "^6.2.2", + "babel-preset-es2015": "^6.5.0", + "babelify": "^7.2.0", + "browserify-istanbul": "^0.2.1", "grunt": "^0.4.5", - "grunt-bower-task": "^0.4.0", - "grunt-contrib-clean": "^0.6.0", - "grunt-contrib-concat": "^0.5.0", - "grunt-contrib-copy": "~0.8.0", - "grunt-contrib-cssmin": "^0.12.2", - "grunt-contrib-jasmine": "^0.8.2", - "grunt-contrib-uglify": "^0.7.0", + "grunt-browserify": "^4.0.1", + "grunt-contrib-concat": "^0.5.1", + "grunt-contrib-cssmin": "^0.14.0", + "grunt-contrib-jshint": "^1.0.0", + "grunt-contrib-uglify": "^0.10.0", "grunt-contrib-watch": "^0.6.1", - "grunt-html2js": "^0.3.0", - "grunt-karma": "^0.10.1", - "grunt-postcss": "^0.5.5", - "grunt-remove": "^0.1.0", - "grunt-sass": "~1.0.0", - "grunt-template-jasmine-istanbul": "^0.3.3", - "jasmine-core": "^2.2.0", - "karma": "^0.12.32", - "karma-chrome-launcher": "^0.1.12", - "karma-cli": "0.0.4", - "karma-coverage": "^0.3.1", - "karma-jasmine": "^0.3.5", - "node-sass": "~3.2.0" + "grunt-html2js": "^0.3.5", + "grunt-karma": "^0.12.1", + "grunt-ng-annotate": "^1.0.1", + "grunt-postcss": "^0.7.1", + "grunt-sass": "^1.1.0", + "karma": "^0.13.15", + "karma-browserify": "^5.0.1", + "karma-chrome-launcher": "^0.2.2", + "karma-coverage": "^0.5.3", + "karma-jasmine": "^0.3.7" } } diff --git a/main/src/main/webapp/restdocs/index.html b/main/src/main/webapp/restdocs/index.html index 7e1d6ff32..c431ed748 100644 --- a/main/src/main/webapp/restdocs/index.html +++ b/main/src/main/webapp/restdocs/index.html @@ -31,6 +31,8 @@ $('pre code').each(function(i, e) { hljs.highlightBlock(e) }); + + addApiKeyAuthorization(); }, onFailure: function(data) { log("Unable to Load SwaggerUI"); @@ -72,6 +74,19 @@ }, showRequestHeaders: false }); + + /* see https://github.com/swagger-api/swagger-ui/issues/818 for more details. */ + function addApiKeyAuthorization(){ + var key = encodeURIComponent( $('#input_apiKey')[0].value ); + if(key && key.trim() != "") { + var apiKeyAuth = new SwaggerClient.ApiKeyAuthorization( "Authorization", "Bearer " + key, "header" ); + window.swaggerUi.api.clientAuthorizations.add( "bearer", apiKeyAuth ); + log( "Set bearer token: " + key ); + } + } + + $('#input_apiKey').change(addApiKeyAuthorization); + window.swaggerUi.load(); function log() { @@ -87,7 +102,10 @@ diff --git a/main/src/main/webapp/restdocs/swagger-ui/css/print.css b/main/src/main/webapp/restdocs/swagger-ui/css/print.css index cd3aa8b6a..c90e9f568 100644 --- a/main/src/main/webapp/restdocs/swagger-ui/css/print.css +++ b/main/src/main/webapp/restdocs/swagger-ui/css/print.css @@ -807,6 +807,9 @@ outline: 2px solid black; outline-color: #cc0000; } +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content form select[name='parameterContentType'] { + max-width: 300px; +} .swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content div.response div.block pre { font-family: "Anonymous Pro", "Menlo", "Consolas", "Bitstream Vera Sans Mono", "Courier New", monospace; padding: 10px; diff --git a/main/src/main/webapp/restdocs/swagger-ui/css/screen.css b/main/src/main/webapp/restdocs/swagger-ui/css/screen.css index d6ed90bb7..96f54548c 100644 --- a/main/src/main/webapp/restdocs/swagger-ui/css/screen.css +++ b/main/src/main/webapp/restdocs/swagger-ui/css/screen.css @@ -807,6 +807,9 @@ outline: 2px solid black; outline-color: #cc0000; } +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content form select[name='parameterContentType'] { + max-width: 300px; +} .swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content div.response div.block pre { font-family: "Anonymous Pro", "Menlo", "Consolas", "Bitstream Vera Sans Mono", "Courier New", monospace; padding: 10px; @@ -1155,8 +1158,16 @@ .swagger-section .auth { float: right; } -.swagger-section #api_information_panel { - position: absolute; +.swagger-section .api-ic { + height: 18px; + vertical-align: middle; + display: inline-block; + background: url(../images/explorer_icons.png) no-repeat; +} +.swagger-section .api-ic .api_information_panel { + position: relative; + margin-top: 20px; + margin-left: -5px; background: #FFF; border: 1px solid #ccc; border-radius: 5px; @@ -1167,34 +1178,32 @@ color: black; padding: 5px; } -.swagger-section #api_information_panel p .api-msg-enabled { +.swagger-section .api-ic .api_information_panel p .api-msg-enabled { color: green; } -.swagger-section #api_information_panel p .api-msg-disabled { +.swagger-section .api-ic .api_information_panel p .api-msg-disabled { color: red; } -.swagger-section .api-ic { - height: 18px; - vertical-align: middle; - display: inline-block; - background: url(../images/explorer_icons.png) no-repeat; +.swagger-section .api-ic:hover .api_information_panel { + position: absolute; + display: block; } .swagger-section .ic-info { background-position: 0 0; width: 18px; - margin-top: -7px; + margin-top: -6px; margin-left: 4px; } .swagger-section .ic-warning { background-position: -60px 0; width: 18px; - margin-top: -7px; + margin-top: -6px; margin-left: 4px; } .swagger-section .ic-error { background-position: -30px 0; width: 18px; - margin-top: -7px; + margin-top: -6px; margin-left: 4px; } .swagger-section .ic-off { diff --git a/main/src/main/webapp/restdocs/swagger-ui/lib/swagger-oauth.js b/main/src/main/webapp/restdocs/swagger-ui/lib/swagger-oauth.js index fed588c63..3bb1c2773 100644 --- a/main/src/main/webapp/restdocs/swagger-ui/lib/swagger-oauth.js +++ b/main/src/main/webapp/restdocs/swagger-ui/lib/swagger-oauth.js @@ -5,6 +5,8 @@ var clientId; var realm; var oauth2KeyName; var redirect_uri; +var clientSecret; +var scopeSeparator; function handleLogin() { var scopes = []; @@ -40,6 +42,7 @@ function handleLogin() { appName = window.swaggerUi.api.info.title; } + $('.api-popup-dialog').remove(); popupDialog = $( [ '
', @@ -151,7 +154,7 @@ function handleLogin() { url += '&redirect_uri=' + encodeURIComponent(redirectUrl); url += '&realm=' + encodeURIComponent(realm); url += '&client_id=' + encodeURIComponent(clientId); - url += '&scope=' + encodeURIComponent(scopes.join(' ')); + url += '&scope=' + encodeURIComponent(scopes.join(scopeSeparator)); url += '&state=' + encodeURIComponent(state); window.open(url); @@ -164,8 +167,8 @@ function handleLogin() { function handleLogout() { - for(key in window.authorizations.authz){ - window.authorizations.remove(key) + for(key in window.swaggerUi.api.clientAuthorizations.authz){ + window.swaggerUi.api.clientAuthorizations.remove(key) } window.enabledScopes = null; $('.api-ic.ic-on').addClass('ic-off'); @@ -184,7 +187,9 @@ function initOAuth(opts) { popupMask = (o.popupMask||$('#api-common-mask')); popupDialog = (o.popupDialog||$('.api-popup-dialog')); clientId = (o.clientId||errors.push('missing client id')); + clientSecret = (o.clientSecret||errors.push('missing client secret')); realm = (o.realm||errors.push('missing realm')); + scopeSeparator = (o.scopeSeparator||' '); if(errors.length > 0){ log('auth unable initialize oauth: ' + errors); @@ -206,6 +211,7 @@ function initOAuth(opts) { window.processOAuthCode = function processOAuthCode(data) { var params = { 'client_id': clientId, + 'client_secret': clientSecret, 'code': data.code, 'grant_type': 'authorization_code', 'redirect_uri': redirect_uri @@ -240,7 +246,7 @@ window.onOAuthComplete = function onOAuthComplete(token) { if(b){ // if all roles are satisfied var o = null; - $.each($('.auth #api_information_panel'), function(k, v) { + $.each($('.auth .api-ic .api_information_panel'), function(k, v) { var children = v; if(children && children.childNodes) { var requiredScopes = []; @@ -257,7 +263,7 @@ window.onOAuthComplete = function onOAuthComplete(token) { } } if(diff.length > 0){ - o = v.parentNode; + o = v.parentNode.parentNode; $(o.parentNode).find('.api-ic.ic-on').addClass('ic-off'); $(o.parentNode).find('.api-ic.ic-on').removeClass('ic-on'); @@ -266,7 +272,7 @@ window.onOAuthComplete = function onOAuthComplete(token) { $(o).find('.api-ic').removeClass('ic-error'); } else { - o = v.parentNode; + o = v.parentNode.parentNode; $(o.parentNode).find('.api-ic.ic-off').addClass('ic-on'); $(o.parentNode).find('.api-ic.ic-off').removeClass('ic-off'); diff --git a/main/src/main/webapp/restdocs/swagger-ui/o2c.html b/main/src/main/webapp/restdocs/swagger-ui/o2c.html deleted file mode 100644 index 88e8bf114..000000000 --- a/main/src/main/webapp/restdocs/swagger-ui/o2c.html +++ /dev/null @@ -1,20 +0,0 @@ - \ No newline at end of file diff --git a/main/src/main/webapp/restdocs/swagger-ui/swagger-ui.js b/main/src/main/webapp/restdocs/swagger-ui/swagger-ui.js index cb460bbba..d64e532f5 100644 --- a/main/src/main/webapp/restdocs/swagger-ui/swagger-ui.js +++ b/main/src/main/webapp/restdocs/swagger-ui/swagger-ui.js @@ -1,8 +1,8 @@ /** * swagger-ui - Swagger UI is a dependency-free collection of HTML, JavaScript, and CSS assets that dynamically generate beautiful documentation from a Swagger-compliant API - * @version v2.1.0 + * @version v2.1.3 * @link http://swagger.io - * @license Apache 2.0 + * @license Apache-2.0 */ (function(){this["Handlebars"] = this["Handlebars"] || {}; this["Handlebars"]["templates"] = this["Handlebars"]["templates"] || {}; @@ -10,10 +10,10 @@ this["Handlebars"]["templates"]["apikey_button_view"] = Handlebars.template({"co var helper, functionType="function", helperMissing=helpers.helperMissing, escapeExpression=this.escapeExpression; return "\n
\n
\n
\n \n \n
\n
\n\n"; + + "
\n \n \n
\n\n"; },"useData":true}); this["Handlebars"]["templates"]["basic_auth_button_view"] = Handlebars.template({"compiler":[6,">= 2.0.0-beta.1"],"main":function(depth0,helpers,partials,data) { - return "
\n
\n
\n
\n \n
\n \n \n
\n
\n\n"; + return "
\n
\n
\n
\n \n
\n \n \n
\n
\n\n"; },"useData":true}); this["Handlebars"]["templates"]["content_type"] = Handlebars.template({"1":function(depth0,helpers,partials,data) { var stack1, buffer = ""; @@ -31,7 +31,7 @@ this["Handlebars"]["templates"]["content_type"] = Handlebars.template({"1":funct },"4":function(depth0,helpers,partials,data) { return " \n"; },"compiler":[6,">= 2.0.0-beta.1"],"main":function(depth0,helpers,partials,data) { - var stack1, helper, functionType="function", helperMissing=helpers.helperMissing, escapeExpression=this.escapeExpression, buffer = "\n'; } return new Handlebars.SafeString(result); @@ -319,15 +327,15 @@ this["Handlebars"]["templates"]["main"] = Handlebars.template({"1":function(dept var stack1, lambda=this.lambda, escapeExpression=this.escapeExpression; return ""; + + "\" data-sw-translate>Terms of service"; },"6":function(depth0,helpers,partials,data) { var stack1, lambda=this.lambda, escapeExpression=this.escapeExpression; - return ""; },"12":function(depth0,helpers,partials,data) { var stack1, lambda=this.lambda, escapeExpression=this.escapeExpression; return "\n"; },"16":function(depth0,helpers,partials,data) { var helper, functionType="function", helperMissing=helpers.helperMissing, escapeExpression=this.escapeExpression; - return "

Response Class (Status " + return "

Response Class (Status " + escapeExpression(((helper = (helper = helpers.successCode || (depth0 != null ? depth0.successCode : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"successCode","hash":{},"data":data}) : helper))) + ")

\n

\n
\n
\n"; },"18":function(depth0,helpers,partials,data) { - return "

Parameters

\n \n \n \n \n \n \n \n \n \n \n \n\n \n
ParameterValueDescriptionParameter TypeData Type
\n"; + return "

Parameters

\n \n \n \n \n \n \n \n \n \n \n \n\n \n
ParameterValueDescriptionParameter TypeData Type
\n"; },"20":function(depth0,helpers,partials,data) { - return "
\n

Response Messages

\n \n \n \n \n \n \n \n \n \n \n\n \n
HTTP Status CodeReasonResponse ModelHeaders
\n"; + return "
\n

Response Messages

\n \n \n \n \n \n \n \n \n \n \n\n \n
HTTP Status CodeReasonResponse ModelHeaders
\n"; },"22":function(depth0,helpers,partials,data) { return ""; },"24":function(depth0,helpers,partials,data) { - return "
\n"; + return "
\n \n \n \n
\n"; },"26":function(depth0,helpers,partials,data) { - return "

Request Headers

\n
\n"; + return "

Request Headers

\n
\n"; },"compiler":[6,">= 2.0.0-beta.1"],"main":function(depth0,helpers,partials,data) { var stack1, helper, options, functionType="function", helperMissing=helpers.helperMissing, escapeExpression=this.escapeExpression, blockHelperMissing=helpers.blockHelperMissing, buffer = "\n
    \n
  • Response Body\n
    \n

    Response Code

    \n
    \n

    Response Headers

    \n
    \n
\n \n \n \n"; + return buffer + "

Response Body

\n
\n

Response Code

\n
\n

Response Headers

\n
\n \n \n \n \n"; },"useData":true}); this["Handlebars"]["templates"]["param_list"] = Handlebars.template({"1":function(depth0,helpers,partials,data) { return " required"; @@ -525,7 +533,7 @@ this["Handlebars"]["templates"]["param_list"] = Handlebars.template({"1":functio + escapeExpression(((helper = (helper = helpers.valueId || (depth0 != null ? depth0.valueId : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"valueId","hash":{},"data":data}) : helper))) + "'>" + escapeExpression(((helper = (helper = helpers.name || (depth0 != null ? depth0.name : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"name","hash":{},"data":data}) : helper))) - + "\n\n \n Raw\n \n"; + + "' data-sw-translate>Raw\n \n"; },"compiler":[6,">= 2.0.0-beta.1"],"main":function(depth0,helpers,partials,data) { var stack1, helper, options, functionType="function", helperMissing=helpers.helperMissing, escapeExpression=this.escapeExpression, blockHelperMissing=helpers.blockHelperMissing, buffer = "
\n

\n Show/Hide\n \n
  • \n Show/Hide\n
  • \n
  • \n \n List Operations\n \n
  • \n
  • \n \n List Operations\n \n
  • \n
  • \n \n Expand Operations\n \n
  • \n"; + + "\" data-sw-translate>\n Expand Operations\n \n \n"; stack1 = helpers['if'].call(depth0, (depth0 != null ? depth0.url : depth0), {"name":"if","hash":{},"fn":this.program(3, data),"inverse":this.noop,"data":data}); if (stack1 != null) { buffer += stack1; } return buffer + " \n

    \n - ]]> - - - - false - false - false - - 1 - - - 2 - - - - - false - - - - false - - - - false - - - - false - - - - false - - - - false - - - - false - - - - false - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/standalone/pom.xml b/standalone/pom.xml deleted file mode 100644 index 5549c485a..000000000 --- a/standalone/pom.xml +++ /dev/null @@ -1,97 +0,0 @@ - - - 4.0.0 - - - de.learnlib.alex - alex-parent - 1.0-SNAPSHOT - ../pom.xml - - - standalone - - ALEX - Standalone - - A standalone Jetty to run ALEX. - - - - - org.eclipse.jetty - jetty-server - ${jetty.version} - - - org.eclipse.jetty - jetty-webapp - ${jetty.version} - - - org.eclipse.jetty - jetty-servlets - ${jetty.version} - - - - - - - org.apache.maven.plugins - maven-assembly-plugin - ${assembly-plugin.version} - - - package - - single - - - - - de.learnlib.alex.standalone.Main - - - - jar-with-dependencies - - - - - - - org.apache.maven.plugins - maven-jar-plugin - 2.6 - - - - de.learnlib.alex.standalone.Main - - - - - - - - - - - - org.apache.maven.plugins - maven-project-info-reports-plugin - ${reports-plugin.version} - - - - index - - - - - - - - \ No newline at end of file diff --git a/standalone/src/main/java/de/learnlib/alex/standalone/Main.java b/standalone/src/main/java/de/learnlib/alex/standalone/Main.java deleted file mode 100644 index 1bf0709e5..000000000 --- a/standalone/src/main/java/de/learnlib/alex/standalone/Main.java +++ /dev/null @@ -1,57 +0,0 @@ -package de.learnlib.alex.standalone; - -import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.servlets.GzipFilter; -import org.eclipse.jetty.webapp.WebAppContext; - -import javax.servlet.DispatcherType; -import java.io.File; -import java.net.URLDecoder; -import java.util.EnumSet; - -public class Main { - - private static final String PORT_ARGUMENT="-port="; - private static final int PORT_NUMBER=8000; - - public static void main(String[] args) throws Exception { - int port = readPortNumberOrFail(args); - - String path = Main.class.getProtectionDomain().getCodeSource().getLocation().getPath(); - String decodedPath = URLDecoder.decode(path, "UTF-8"); - File standaloneJar = new File(decodedPath); - String folder = standaloneJar.getParent(); - File applicationWar = new File(folder + "/ALEX.war"); - - WebAppContext webapp = new WebAppContext(); - webapp.setContextPath("/"); - webapp.setWar(applicationWar.getAbsolutePath()); - webapp.addFilter(GzipFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST)); - - Server server = new Server(port); - server.setHandler(webapp); - server.start(); - server.join(); - } - - private static int readPortNumberOrFail(String[] args) { - int port = PORT_NUMBER; - if (args.length > 0) { - if (args.length == 1 && args[0].startsWith(PORT_ARGUMENT)) { - String portAsString = args[0].replaceFirst(PORT_ARGUMENT, ""); - try { - port = Integer.valueOf(portAsString); - } catch (NumberFormatException e) { - System.err.println("Unknown port number '" + portAsString + "'!"); - System.exit(2); - } - } else { - System.err.println("Unknown commandline argument!"); - System.err.println("Usage: " + PORT_ARGUMENT + PORT_NUMBER); - System.exit(1); - } - } - return port; - } - -} diff --git a/standalone/src/site/markdown/usage.md b/standalone/src/site/markdown/usage.md deleted file mode 100644 index 063798345..000000000 --- a/standalone/src/site/markdown/usage.md +++ /dev/null @@ -1,19 +0,0 @@ -Usage -===== - -Set Up ------- - -Command Line Arguments ----------------------- -The server can be customized by providing additional command line arguments, e.g.: - -```bash -java -jar alex-standalone.jar -port=9090 -``` - -The available arguments are: - -| Argument | Description | -|-------------|-------------------------------------------------------------| -| -port=PORT | Change the port of the server to PORT (default port: 8000). | diff --git a/standalone/src/site/resources/css/bootstrap-responsive.min.css b/standalone/src/site/resources/css/bootstrap-responsive.min.css deleted file mode 120000 index a4d199547..000000000 --- a/standalone/src/site/resources/css/bootstrap-responsive.min.css +++ /dev/null @@ -1 +0,0 @@ -../../../../../src/site/resources/css/bootstrap-responsive.min.css \ No newline at end of file diff --git a/standalone/src/site/resources/css/bootstrap.min.css b/standalone/src/site/resources/css/bootstrap.min.css deleted file mode 120000 index 5e3fc7732..000000000 --- a/standalone/src/site/resources/css/bootstrap.min.css +++ /dev/null @@ -1 +0,0 @@ -../../../../../src/site/resources/css/bootstrap.min.css \ No newline at end of file diff --git a/standalone/src/site/resources/js/bootstrap.min.js b/standalone/src/site/resources/js/bootstrap.min.js deleted file mode 120000 index 59bc73727..000000000 --- a/standalone/src/site/resources/js/bootstrap.min.js +++ /dev/null @@ -1 +0,0 @@ -../../../../../src/site/resources/js/bootstrap.min.js \ No newline at end of file diff --git a/standalone/src/site/site.xml b/standalone/src/site/site.xml deleted file mode 100644 index bb22e139c..000000000 --- a/standalone/src/site/site.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - -