├── .dockerignore ├── .gitattributes ├── .github └── workflows │ └── maven.yml ├── .gitignore ├── .gitlab-ci.yml ├── .mvn └── wrapper │ ├── MavenWrapperDownloader.java │ └── maven-wrapper.properties ├── .slugignore ├── .tern-project ├── .travis.yml ├── Dockerfile ├── LICENSE ├── Procfile ├── README.md ├── app.json ├── doc ├── CMD_Documentation.pdf └── CMD_Presentation.pdf ├── docker-compose.yml ├── docker ├── mysql.dockerfile └── scripts │ ├── jboss-configure-mysql-datasource.sh │ └── jboss-configure-postgresql-datasource.sh ├── frontend ├── .browserslistrc ├── .eslintrc.js ├── .gitignore ├── .jshintrc ├── README.md ├── babel.config.js ├── package-lock.json ├── package.json ├── postcss.config.js ├── postscript.js ├── public │ ├── favicon.ico │ └── index.html ├── src │ ├── App.vue │ ├── assets │ │ ├── background.jpeg │ │ ├── cmd_logo.png │ │ ├── logo.png │ │ ├── social.jpg │ │ ├── welcome_logo.png │ │ └── work.jpeg │ ├── components │ │ ├── Document.vue │ │ ├── Drawer.vue │ │ ├── Editor.vue │ │ ├── ErrorPage.vue │ │ ├── ForbiddenPage.vue │ │ ├── Login.vue │ │ ├── Preview.vue │ │ └── Welcome.vue │ ├── main.js │ ├── plugins │ │ ├── axios.js │ │ ├── chat.js │ │ ├── snotify.js │ │ └── vuetify.js │ ├── router │ │ └── index.js │ └── store │ │ ├── index.js │ │ └── modules │ │ ├── app.js │ │ └── login.js └── vue.config.js ├── heroku.yml ├── mvnw ├── mvnw.cmd ├── pom.xml ├── resources ├── 3-tier-architecture.jpg ├── AppExampleDisplay.png └── client-server-architecture.jpg ├── sql ├── cmd.mwb └── cmd.sql ├── src ├── main │ ├── java │ │ └── org │ │ │ └── dhbw │ │ │ └── mosbach │ │ │ └── ai │ │ │ └── cmd │ │ │ ├── crdt │ │ │ ├── ActiveDocument.java │ │ │ ├── Message.java │ │ │ └── MessageBroker.java │ │ │ ├── db │ │ │ ├── CollaboratorDao.java │ │ │ ├── DocDao.java │ │ │ ├── HistoryDao.java │ │ │ ├── RepoDao.java │ │ │ └── UserDao.java │ │ │ ├── model │ │ │ ├── Collaborator.java │ │ │ ├── Doc.java │ │ │ ├── History.java │ │ │ ├── Repo.java │ │ │ └── User.java │ │ │ ├── security │ │ │ └── Hashing.java │ │ │ ├── services │ │ │ ├── AuthenticationService.java │ │ │ ├── CollaboratorService.java │ │ │ ├── DocumentService.java │ │ │ ├── RestEndpoint.java │ │ │ ├── RootService.java │ │ │ ├── ServiceEndpoints.java │ │ │ ├── mapper │ │ │ │ ├── JsonParseMapper.java │ │ │ │ ├── MapperJsonResponse.java │ │ │ │ ├── MethodNotAllowedMapper.java │ │ │ │ ├── NotAcceptableMapper.java │ │ │ │ ├── NotFoundMapper.java │ │ │ │ └── NotSupportedMapper.java │ │ │ ├── payload │ │ │ │ ├── CollaboratorInsertionModel.java │ │ │ │ ├── CollaboratorRemovalModel.java │ │ │ │ ├── DocumentAccessModel.java │ │ │ │ ├── DocumentInsertionModel.java │ │ │ │ ├── DocumentRemovalModel.java │ │ │ │ ├── DocumentTransferModel.java │ │ │ │ ├── LoginModel.java │ │ │ │ ├── Payload.java │ │ │ │ ├── PayloadParameters.java │ │ │ │ └── RegisterModel.java │ │ │ ├── response │ │ │ │ ├── BadRequest.java │ │ │ │ ├── DocumentListResponse.java │ │ │ │ ├── Forbidden.java │ │ │ │ ├── InternalServerError.java │ │ │ │ ├── LoginUserResponse.java │ │ │ │ ├── ResponseObject.java │ │ │ │ ├── ResponseParameters.java │ │ │ │ ├── Status.java │ │ │ │ ├── Success.java │ │ │ │ ├── Unauthorized.java │ │ │ │ └── entity │ │ │ │ │ ├── DocumentIcon.java │ │ │ │ │ └── DocumentListEntity.java │ │ │ ├── serialize │ │ │ │ ├── LocalDateTimeDeserializer.java │ │ │ │ └── LocalDateTimeSerializer.java │ │ │ └── validation │ │ │ │ ├── ModelValidation.java │ │ │ │ ├── ValidationResult.java │ │ │ │ ├── authentication │ │ │ │ ├── LoginValidation.java │ │ │ │ └── RegisterValidation.java │ │ │ │ ├── basic │ │ │ │ ├── BasicCollaboratorValidation.java │ │ │ │ ├── BasicDocumentValidation.java │ │ │ │ ├── BasicFieldValidation.java │ │ │ │ └── BasicUserValidation.java │ │ │ │ ├── collaborator │ │ │ │ ├── CollaboratorInsertionValidation.java │ │ │ │ └── CollaboratorRemovalValidation.java │ │ │ │ └── document │ │ │ │ ├── DocumentAccessValidation.java │ │ │ │ ├── DocumentInsertionValidation.java │ │ │ │ ├── DocumentRemovalValidation.java │ │ │ │ └── DocumentTransferValidation.java │ │ │ ├── session │ │ │ └── SessionUtil.java │ │ │ ├── util │ │ │ ├── CmdConfig.java │ │ │ ├── HasAccess.java │ │ │ ├── MatchTools.java │ │ │ └── MessageType.java │ │ │ └── websocket │ │ │ ├── Endpoint.java │ │ │ ├── MessageDecoder.java │ │ │ └── MessageEncoder.java │ ├── resources │ │ └── META-INF │ │ │ ├── beans.xml │ │ │ ├── log4j.properties │ │ │ └── persistence.xml │ └── webapp │ │ └── .gitkeep └── test │ ├── java │ └── org │ │ └── dhbw │ │ └── mosbach │ │ └── ai │ │ └── cmd │ │ ├── crdt │ │ ├── ActiveDocumentTest.java │ │ └── MessageBrokerTest.java │ │ ├── db │ │ ├── DocDaoIT.java │ │ └── UserDaoIT.java │ │ ├── extension │ │ └── listener │ │ │ ├── FailsafeTestPrintListener.java │ │ │ ├── SurefireTestPrintListener.java │ │ │ ├── log │ │ │ ├── LogPrefix.java │ │ │ └── TestLogger.java │ │ │ ├── result │ │ │ ├── ResultAccumulator.java │ │ │ ├── RunTime.java │ │ │ └── TestSuiteResult.java │ │ │ └── util │ │ │ ├── DescriptionScanner.java │ │ │ └── TestCasePrinter.java │ │ ├── security │ │ └── HashingTest.java │ │ ├── services │ │ ├── AuthenticationServiceIT.java │ │ ├── CollaboratorServiceIT.java │ │ ├── DocumentServiceIT.java │ │ ├── helper │ │ │ ├── Authenticator.java │ │ │ ├── ClientJson.java │ │ │ └── JsonUtil.java │ │ └── payload │ │ │ └── TestLoginModel.java │ │ └── test │ │ ├── config │ │ ├── DeploymentConfig.java │ │ ├── TestConfig.java │ │ ├── TestUser.java │ │ └── TestUsers.java │ │ ├── helper │ │ ├── DeploymentPackager.java │ │ └── PasswordGenerator.java │ │ ├── include │ │ ├── DependencyIncludes.java │ │ └── PackageIncludes.java │ │ └── resources │ │ ├── Datasets.java │ │ └── Scripts.java │ └── resources │ ├── META-INF │ └── test-persistence.xml │ ├── WEB-INF │ └── web.xml │ ├── arquillian-cube.xml │ ├── arquillian-standalone-h2db.xml │ ├── arquillian.xml │ ├── datasets │ ├── documents.yml │ ├── repos.yml │ └── users.yml │ ├── docker │ ├── Dockerfile │ └── docker-compose.yml │ └── scripts │ ├── disableReferentialIntegrity.sql │ └── resetTableIdentities.sql └── system.properties /.dockerignore: -------------------------------------------------------------------------------- 1 | ### Dockerignore List 2 | 3 | # Git Files 4 | .git/ 5 | .gitignore 6 | .gitattributes 7 | 8 | # Travis File 9 | .travis.yml 10 | 11 | # README file 12 | README.md 13 | 14 | # Maven Wrapper JAR 15 | .mvn/wrapper/maven-wrapper.jar 16 | 17 | # Compiled class file 18 | *.class 19 | 20 | # Log file 21 | *.log 22 | 23 | # BlueJ files 24 | *.ctxt 25 | 26 | # Mobile Tools for Java (J2ME) 27 | .mtj.tmp/ 28 | 29 | # Package Files # 30 | *.jar 31 | *.nar 32 | *.ear 33 | *.zip 34 | *.tar.gz 35 | *.rar 36 | 37 | # Other Binaries 38 | *.7z 39 | *.dmg 40 | *.gz 41 | *.iso 42 | *.tar 43 | *.sar 44 | 45 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 46 | hs_err_pid* 47 | 48 | # Eclipse 49 | .classpath 50 | .project 51 | .settings/ 52 | 53 | # IntelliJ project files 54 | *.iml 55 | *.iws 56 | *.ipr 57 | .idea/ 58 | 59 | # Mac OS X 60 | .DS_Store 61 | 62 | # Windows thumbnail cache files 63 | Thumbs.db 64 | ehthumbs.db 65 | ehthumbs_vista.db 66 | 67 | # Dump file 68 | *.stackdump 69 | 70 | # Folder config file 71 | [Dd]esktop.ini 72 | 73 | # Maven 74 | log/ 75 | pom.xml.tag 76 | pom.xml.releaseBackup 77 | pom.xml.versionsBackup 78 | pom.xml.next 79 | release.properties 80 | dependency-reduced-pom.xml 81 | buildNumber.properties 82 | .mvn/timing.properties 83 | target/ 84 | 85 | # Vim Temporary Files 86 | *.swp 87 | 88 | # Node modules 89 | frontend/node_modules/ 90 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | *.bat text eol=crlf 3 | *.java text eol=lf 4 | *.sh text eol=lf 5 | -------------------------------------------------------------------------------- /.github/workflows/maven.yml: -------------------------------------------------------------------------------- 1 | name: Collaborative Markdown Editor 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v1 12 | - name: Set up JDK 11 13 | uses: actions/setup-java@v1 14 | with: 15 | java-version: 11 16 | - name: Build with Maven 17 | run: mvn -B integration-test 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled class file 2 | *.class 3 | 4 | # Log file 5 | *.log 6 | 7 | # BlueJ files 8 | *.ctxt 9 | 10 | # Mobile Tools for Java (J2ME) 11 | .mtj.tmp/ 12 | 13 | # Package Files # 14 | *.jar 15 | *.war 16 | *.nar 17 | *.ear 18 | *.zip 19 | *.tar.gz 20 | *.rar 21 | 22 | # Other Binaries 23 | *.7z 24 | *.dmg 25 | *.gz 26 | *.iso 27 | *.tar 28 | *.sar 29 | 30 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 31 | hs_err_pid* 32 | 33 | # Eclipse 34 | .classpath 35 | .project 36 | .settings/ 37 | 38 | # IntelliJ project files 39 | *.iml 40 | *.iws 41 | *.ipr 42 | .idea/ 43 | 44 | # Mac OS X 45 | .DS_Store 46 | 47 | # Windows thumbnail cache files 48 | Thumbs.db 49 | ehthumbs.db 50 | ehthumbs_vista.db 51 | 52 | # Dump file 53 | *.stackdump 54 | 55 | # Folder config file 56 | [Dd]esktop.ini 57 | 58 | # Maven 59 | target/ 60 | pom.xml.tag 61 | pom.xml.releaseBackup 62 | pom.xml.versionsBackup 63 | pom.xml.next 64 | release.properties 65 | dependency-reduced-pom.xml 66 | buildNumber.properties 67 | .mvn/timing.properties 68 | .mvn/wrapper/maven-wrapper.jar 69 | 70 | # Vim Temporary Files 71 | *.swp 72 | 73 | # Built frontend 74 | src/main/webapp/css 75 | src/main/webapp/img 76 | src/main/webapp/js 77 | src/main/webapp/favicon.ico 78 | src/main/webapp/index.html 79 | 80 | # Node modules 81 | frontend/node_modules/ 82 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | stages: 2 | - verify 3 | - build 4 | - test 5 | 6 | cache: 7 | paths: 8 | - frontend/node_modules 9 | 10 | lint_frontend: 11 | image: "node:10.6" 12 | stage: verify 13 | script: 14 | - cd frontend/ 15 | - npm install 16 | - npm run lint 17 | 18 | build_app: 19 | image: "maven:3.6.1-slim" 20 | stage: build 21 | script: mvn install -DskipTests=true -Dmaven.javadoc.skip=true -V 22 | 23 | run_frontend_tests: 24 | image: node:10.16 25 | stage: test 26 | script: 27 | - cd frontend 28 | - npm run test 29 | 30 | run_backend_tests: 31 | image: "maven:3.6.1-slim" 32 | stage: test 33 | script: 34 | - mvn verify -P skip-frontend-build,arquillian-wildfly-managed 35 | artifacts: 36 | paths: 37 | - target/CMD.war 38 | 39 | run_docker_build: 40 | image: "tiangolo/docker-with-compose:latest" 41 | stage: test 42 | script: 43 | - docker-compose build 44 | -------------------------------------------------------------------------------- /.mvn/wrapper/MavenWrapperDownloader.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2007-present the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import java.io.File; 18 | import java.io.FileInputStream; 19 | import java.io.FileOutputStream; 20 | import java.io.IOException; 21 | import java.net.Authenticator; 22 | import java.net.PasswordAuthentication; 23 | import java.net.URL; 24 | import java.nio.channels.Channels; 25 | import java.nio.channels.ReadableByteChannel; 26 | import java.util.Properties; 27 | 28 | public class MavenWrapperDownloader { 29 | 30 | private static final String WRAPPER_VERSION = "0.5.4"; 31 | /** 32 | * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. 33 | */ 34 | private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/" 35 | + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + " .jar"; 36 | 37 | /** 38 | * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to 39 | * use instead of the default one. 40 | */ 41 | private static final String MAVEN_WRAPPER_PROPERTIES_PATH = 42 | ".mvn/wrapper/maven-wrapper.properties"; 43 | 44 | /** 45 | * Path where the maven-wrapper.jar will be saved to. 46 | */ 47 | private static final String MAVEN_WRAPPER_JAR_PATH = 48 | ".mvn/wrapper/maven-wrapper.jar"; 49 | 50 | /** 51 | * Name of the property which should be used to override the default download url for the wrapper. 52 | */ 53 | private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; 54 | 55 | public static void main(String[] args) { 56 | System.out.println("- Downloader started"); 57 | File baseDirectory = new File(args[0]); 58 | System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); 59 | 60 | // If the maven-wrapper.properties exists, read it and check if it contains a custom 61 | // wrapperUrl parameter. 62 | File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); 63 | String url = DEFAULT_DOWNLOAD_URL; 64 | if (mavenWrapperPropertyFile.exists()) { 65 | FileInputStream mavenWrapperPropertyFileInputStream = null; 66 | try { 67 | mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); 68 | Properties mavenWrapperProperties = new Properties(); 69 | mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); 70 | url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); 71 | } catch (IOException e) { 72 | System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); 73 | } finally { 74 | try { 75 | if (mavenWrapperPropertyFileInputStream != null) { 76 | mavenWrapperPropertyFileInputStream.close(); 77 | } 78 | } catch (IOException e) { 79 | // Ignore ... 80 | } 81 | } 82 | } 83 | System.out.println("- Downloading from: " + url); 84 | 85 | File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); 86 | if (!outputFile.getParentFile().exists()) { 87 | if (!outputFile.getParentFile().mkdirs()) { 88 | System.out.println( 89 | "- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'"); 90 | } 91 | } 92 | System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); 93 | try { 94 | downloadFileFromURL(url, outputFile); 95 | System.out.println("Done"); 96 | System.exit(0); 97 | } catch (Throwable e) { 98 | System.out.println("- Error downloading"); 99 | e.printStackTrace(); 100 | System.exit(1); 101 | } 102 | } 103 | 104 | private static void downloadFileFromURL(String urlString, File destination) throws Exception { 105 | if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) { 106 | String username = System.getenv("MVNW_USERNAME"); 107 | char[] password = System.getenv("MVNW_PASSWORD").toCharArray(); 108 | Authenticator.setDefault(new Authenticator() { 109 | @Override 110 | protected PasswordAuthentication getPasswordAuthentication() { 111 | return new PasswordAuthentication(username, password); 112 | } 113 | }); 114 | } 115 | URL website = new URL(urlString); 116 | ReadableByteChannel rbc; 117 | rbc = Channels.newChannel(website.openStream()); 118 | FileOutputStream fos = new FileOutputStream(destination); 119 | fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); 120 | fos.close(); 121 | rbc.close(); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.0/apache-maven-3.6.0-bin.zip 2 | wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.4/maven-wrapper-0.5.4.jar 3 | -------------------------------------------------------------------------------- /.slugignore: -------------------------------------------------------------------------------- 1 | # Ignore these files and directories for the slug on Heroku 2 | # not needed to run the application 3 | 4 | # Compiled class file 5 | *.class 6 | 7 | # Log files 8 | *.log 9 | 10 | # BlueJ files 11 | *.ctxt 12 | 13 | # Mobile Tools for Java (J2ME) 14 | .mtj.tmp/ 15 | 16 | # Package Files # 17 | *.jar 18 | *.war 19 | *.nar 20 | *.ear 21 | *.zip 22 | *.tar.gz 23 | *.rar 24 | 25 | # Other Binaries 26 | *.7z 27 | *.dmg 28 | *.gz 29 | *.iso 30 | *.tar 31 | *.sar 32 | 33 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 34 | hs_err_pid* 35 | 36 | # Node Modules 37 | frontend/node_modules/ 38 | 39 | # PDFs 40 | *.pdf 41 | 42 | # Resources 43 | /resources/ 44 | 45 | # Project files 46 | .tern-project 47 | 48 | # SQL Database Schema creation scripts 49 | /sql/ 50 | 51 | # Git Files 52 | .gitattributes 53 | .gitignore 54 | 55 | # Travis 56 | .travis.yml 57 | 58 | # Docker 59 | .dockerignore 60 | Dockerfile.mysql 61 | Dockerfile.wildfly 62 | docker-compose.yml 63 | 64 | # Maven 65 | target/ 66 | pom.xml.tag 67 | pom.xml.releaseBackup 68 | pom.xml.versionsBackup 69 | pom.xml.next 70 | release.properties 71 | dependency-reduced-pom.xml 72 | buildNumber.properties 73 | .mvn/timing.properties 74 | .mvn/wrapper/maven-wrapper.jar 75 | 76 | # Eclipse 77 | .classpath 78 | .project 79 | .settings/ 80 | 81 | # IntelliJ project files 82 | *.iml 83 | *.iws 84 | *.ipr 85 | .idea/ 86 | 87 | # Mac OS X 88 | .DS_Store 89 | 90 | # Windows thumbnail cache files 91 | Thumbs.db 92 | ehthumbs.db 93 | ehthumbs_vista.db 94 | 95 | # Dump file 96 | *.stackdump 97 | 98 | # Folder config file 99 | [Dd]esktop.ini 100 | -------------------------------------------------------------------------------- /.tern-project: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": { 3 | "guess-types": { 4 | 5 | }, 6 | "outline": { 7 | 8 | }, 9 | "angular": { 10 | 11 | } 12 | }, 13 | "libs": [ 14 | "browser" 15 | ] 16 | } -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | jdk: oraclejdk11 3 | 4 | # Cache Maven local repository and node 5 | # modules 6 | cache: 7 | directories: 8 | - ${HOME}/.m2 # Maven repository 9 | - frontend/node_modules # NPM modules 10 | 11 | install: 12 | - ./mvnw install -DskipTests=true -Dmaven.javadoc.skip=true -B -V 13 | 14 | script: 15 | - cd frontend/ && npm run test && cd .. 16 | - ./mvnw test -B 17 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Specify WildFly version and app home 2 | # directory as arguments 3 | ARG WILDFLY_VERSION=16.0.0.Final 4 | 5 | # 1. Stage: Build frontend and backend 6 | # Use Maven base image 7 | FROM maven:3.6.0-jdk-11-slim AS build 8 | 9 | ENV APP_HOME /app 10 | 11 | COPY frontend ${APP_HOME}/frontend 12 | COPY src ${APP_HOME}/src 13 | COPY pom.xml ${APP_HOME}/pom.xml 14 | 15 | # Set the working directory to the app home directory 16 | WORKDIR ${APP_HOME} 17 | 18 | # Install Git as this is required for a custom 19 | # npm dependency 20 | RUN apt-get update && apt-get --assume-yes install git 21 | 22 | # Build the frontend and backend of the application 23 | RUN mvn install -P deployment -DskipTests=true -Dmaven.javadoc.skip=true -B -V 24 | 25 | # 2. Stage: Configure WildFly and deploy application 26 | # Use the WildFly base image to deploy the built application 27 | FROM jboss/wildfly:${WILDFLY_VERSION} AS server 28 | 29 | ARG buildno 30 | 31 | # Set the working directory to the WildFly home directory 32 | WORKDIR ${JBOSS_HOME} 33 | 34 | # Ports used by the WildFly instance 35 | ENV WEBAPP_PORT 8080/tcp 36 | ENV ADMINISTRATION_PORT 9990/tcp 37 | 38 | # WildFly settings 39 | ENV WILDFLY_USER wildfly 40 | ENV WILDFLY_PASSWORD wildfly 41 | 42 | # MySQL version 43 | ENV MYSQL_VERSION 8.0.15 44 | 45 | # Environment variables for the WildFly datasource 46 | ENV DB_NAME cmd 47 | ENV DB_USER mysqluser 48 | ENV DB_PASSWORD mysqlpass 49 | ENV DB_PORT 3306 50 | ENV DB_URI mysql-instance:${DB_PORT} 51 | 52 | # Path variables 53 | ENV JBOSS_CLI ${JBOSS_HOME}/bin/jboss-cli.sh 54 | ENV DEPLOYMENT_DIR ${JBOSS_HOME}/standalone/deployments 55 | ENV APP_DIR ./collaborative-markdown-editor 56 | 57 | RUN echo "Build Number: ${buildno}" 58 | 59 | # Add the configuration script to add MySQL driver 60 | # and datasource to the WildFly instance 61 | COPY docker/scripts/jboss-configure-mysql-datasource.sh . 62 | 63 | # Switch temporarily to root to get the permission to 64 | # make the configuration script executable 65 | USER root 66 | RUN chmod +x ./jboss-configure-mysql-datasource.sh 67 | USER jboss 68 | 69 | # Execute the configuration script 70 | RUN ./jboss-configure-mysql-datasource.sh 71 | 72 | # Remove the script 73 | RUN rm -f ./jboss-configure-datasource.sh 74 | 75 | # Deploy the application as ROOT app to WildFly 76 | COPY --from=build /app/target/ROOT.war ${DEPLOYMENT_DIR}/ 77 | 78 | # Expose the webapp port 79 | EXPOSE ${WEBAPP_PORT} 80 | 81 | # Start the WildFly 82 | CMD ["/opt/jboss/wildfly/bin/standalone.sh", "-b", "0.0.0.0", "-bmanagement", "0.0.0.0"] 83 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: $JBOSS_HOME/bin/standalone.sh -b 0.0.0.0 -Djboss.http.port=$PORT 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Collaborative Markdown Editor [![Build Status](https://travis-ci.com/mortenterhart/collaborative-markdown-editor.svg?branch=master)](https://travis-ci.com/mortenterhart/collaborative-markdown-editor) 2 | 3 | [![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy) 4 | [![Deploy](https://deploy.zeet.co/collaborative-markdown-editor.svg)](https://deploy.zeet.co/?url=https://github.com/mortenterhart/collaborative-markdown-editor) 5 | 6 | A Markdown Editor with collaboration capabilities. Start with new documents, add your friends and 7 | colleagues to your document and share amazing ideas. 8 | 9 | ![Exemplary View of the Application](resources/AppExampleDisplay.png) 10 | 11 | ## Features 12 | 13 | * User Management 14 | * Users can create and delete documents in their work space 15 | * Synchronization of the clients to be able to collaborate 16 | * Markdown syntax support in the editor 17 | * WYSIWYG toolbar for Markdown 18 | * Live Preview of editor content rendered in Markdown 19 | * Add and remove collaborators to a project 20 | * Preview who is working on a document 21 | * Transfer the ownership of a document 22 | * Chat to communicate with other users currently working on the same document 23 | * Chat commands 24 | 25 | ## Project Idea 26 | 27 | Our idea was born in the 4th semester. To prepare for the exams, Morten in particular summarized 28 | some lecture scripts and used the online markdown editor StackEdit for this purpose. Markdown is 29 | a markup language for structuring and formatting text documents with little syntax. The big problem 30 | with StackEdit, however, was that it is not multi-user. 31 | 32 | ## Setup 33 | 34 | ### Docker-Compose 35 | 36 | First you should check which versions of Docker and Docker-Compose you have. It should be at least 37 | Docker version 18.0 and Docker-Compose 1.23, so that the declared features are fully supported. To 38 | build and start the containers you can use the simple command 39 | 40 | ```bash 41 | docker-compose up 42 | ``` 43 | 44 | right from the project directory. A whole series of operations are now performed here, which are 45 | listed below. 46 | 47 | 1. The basic images `mysql` and `jboss/wildfly` are downloaded from Docker Hub. 48 | 2. The MySQL image is built, a root user and a database user are set up, and the database is 49 | initialized. 50 | 3. The WildFly image is built. 51 | 4. First, Maven is used to build the application in another container. This includes building 52 | the frontend directly, since the Maven frontend plugin is used. 53 | 5. An administrator account is added to the WildFly. 54 | 6. The MySQL connector is downloaded and installed at WildFly. In addition, a data source is 55 | set up directly to allow the connection to the database in the other container. 56 | 7. The WAR archive previously built by Maven is deployed to WildFly. 57 | 8. Wildfly and MySQL are started simultaneously. 58 | 59 | Open your browser at http://localhost:8080 and see the editor appearing. 60 | 61 | ### Maven Deploy 62 | 63 | Maven Deploy can be used as an alternative to the Docker image. This is made possible by the 64 | Wildfly Maven plugin. Again, the plugin downloads and starts a Wildfly 16.0.0.Final and 65 | configures it with MySQL Connector and Datasource. 66 | 67 | To do this, the Wildfly must first be started with the command 68 | 69 | ```bash 70 | mvn wildfly:start 71 | ``` 72 | 73 | This is required to then add the MySQL connector and data source and deploy the application. 74 | Before you can continue, however, you must make sure that a MySQL database is present on the 75 | machine and running. The connection information (JDBC URL, username, password) must be stored 76 | in Maven `pom.xml` in the properties 77 | 78 | * `wildfly.datasource.jdbc.url` 79 | * `wildfly.datasource.username` 80 | * `wildfly.datasource.password` 81 | 82 | to perform the complete Maven lifecycle from compiling the software, building the Vue.js frontend, 83 | generating a JaCoCo coverage report, packing the application into a WAR archive, configuring the 84 | Wildfly and deploying the application. 85 | 86 | ```bash 87 | mvn wildfly:deploy -P deployment -DskipTests=true 88 | ``` 89 | 90 | Your Wildfly should now be deploying your application and you can verify by visiting http://localhost:8080. 91 | 92 | ## Architecture 93 | 94 | ### Client-Server-Model 95 | 96 | ![Client-Server-Architecture](resources/client-server-architecture.jpg) 97 | 98 | ### 3-Tier Architecture 99 | 100 | ![3-Tier Architecture](resources/3-tier-architecture.jpg) 101 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Collaborative Markdown Editor", 3 | "description": "A collaborative Markdown Editor to write documents with other people and share ideas", 4 | "website": "https://collaborative-markdown-editor.herokuapp.com", 5 | "repository": "https://github.com/mortenterhart/collaborative-markdown-editor", 6 | "logo": "https://raw.githubusercontent.com/mortenterhart/collaborative-markdown-editor/master/frontend/src/assets/cmd_logo.png", 7 | 8 | "scripts": { 9 | "postdeploy": "./mvnw clean" 10 | }, 11 | 12 | "env": { 13 | "MAVEN_CUSTOM_OPTS": { 14 | "value": "-P deployment -DskipTests -Dit.skipTests", 15 | "description": "Maven options to build in deployment mode and to skip tests", 16 | "required": true 17 | } 18 | }, 19 | 20 | "addons": [ 21 | "heroku-postgresql" 22 | ], 23 | 24 | "buildpacks": [ 25 | { 26 | "url": "heroku/java" 27 | }, 28 | { 29 | "url": "https://github.com/mortenterhart/heroku-buildpack-wildfly" 30 | }, 31 | { 32 | "url": "https://github.com/mortenterhart/heroku-buildpack-wildfly-postgresql" 33 | } 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /doc/CMD_Documentation.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mortenterhart/collaborative-markdown-editor/50ba075d605336ed727324dda2aa960255af01c2/doc/CMD_Documentation.pdf -------------------------------------------------------------------------------- /doc/CMD_Presentation.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mortenterhart/collaborative-markdown-editor/50ba075d605336ed727324dda2aa960255af01c2/doc/CMD_Presentation.pdf -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | # Docker-Compose file for the Collaborative Markdown Editor 2 | # 3 | # This compose file defines the 'webapp' and 'mysql' services 4 | # which are automatically built using the Dockerfiles and 5 | # configured using external scripts from the repository. 6 | 7 | version: "3.6" 8 | 9 | services: 10 | webapp: 11 | image: "cmd-wildfly-webapp:latest" 12 | hostname: cmd-wildfly-instance 13 | container_name: cmd-wildfly-webapp 14 | build: 15 | context: . 16 | dockerfile: Dockerfile 17 | args: 18 | buildno: 1 19 | ports: 20 | - "8080:8080" 21 | links: 22 | - "mysql:mysql-instance" 23 | networks: 24 | - cmd_network 25 | restart: on-failure 26 | depends_on: 27 | - mysql 28 | 29 | mysql: 30 | image: "mysql:8.0.15" 31 | hostname: mysql-instance 32 | container_name: cmd-mysql-instance 33 | environment: 34 | MYSQL_ROOT_PASSWORD: mysqlroot 35 | MYSQL_USER: mysqluser 36 | MYSQL_PASSWORD: mysqlpass 37 | MYSQL_DATABASE: cmd 38 | MYSQL_PORT: 3306 39 | deploy: 40 | mode: replicated 41 | replicas: 5 42 | restart_policy: 43 | condition: on-failure 44 | volumes: 45 | - type: volume 46 | source: mysql_data 47 | target: /var/lib/mysql 48 | volume: 49 | nocopy: true 50 | networks: 51 | - cmd_network 52 | ports: 53 | - "3306:3306" 54 | restart: on-failure 55 | 56 | volumes: 57 | mysql_data: 58 | name: cmd-mysql-data 59 | labels: 60 | org.dhbw.mosbach.cmd.license: LGPL 3.0 61 | 62 | networks: 63 | cmd_network: 64 | name: cmd_network 65 | -------------------------------------------------------------------------------- /docker/mysql.dockerfile: -------------------------------------------------------------------------------- 1 | # Specify the MySQL version to be downloaded 2 | ARG MYSQL_VERSION=8.0.15 3 | 4 | # Pull the official MySQL image 5 | FROM mysql:${MYSQL_VERSION} AS database 6 | 7 | ARG buildno 8 | 9 | # Configure MySQL root user 10 | ENV MYSQL_ROOT_PASSWORD mysqlroot 11 | 12 | # Configure MySQL database user 13 | ENV MYSQL_USER mysqluser 14 | ENV MYSQL_PASSWORD mysqlpass 15 | 16 | # Configure MySQL database 17 | ENV MYSQL_DATABASE cmd 18 | ENV MYSQL_PORT 3306 19 | 20 | RUN echo "Build Number: ${buildno}" 21 | 22 | # Expose the standard MySQL port 23 | EXPOSE ${MYSQL_PORT} 24 | -------------------------------------------------------------------------------- /docker/scripts/jboss-configure-mysql-datasource.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | RED=$'\e[31m' 4 | GREEN=$'\e[32m' 5 | RESET=$'\e[0m' 6 | 7 | function info() { 8 | echo -e "${GREEN}==> $*${RESET}" 9 | } 10 | 11 | function error() { 12 | echo -e "${RED}==> ERROR: $*${RESET}" 13 | } 14 | 15 | function safe_run() { 16 | "$@" 17 | local exit_code=$? 18 | if [ "${exit_code}" -ne 0 ]; then 19 | error "The command '$*' exited with non-zero exit code ${exit_code}." 20 | exit "${exit_code}" 21 | fi 22 | } 23 | 24 | function check_server_state() { 25 | ${JBOSS_CLI} -c ":read-attribute(name=server-state)" 26 | } 27 | 28 | info "Setting up MySQL ${MYSQL_VERSION} Datasource for WildFly ${WILDFLY_VERSION}" 29 | 30 | info "Adding WildFly administration user" 31 | safe_run ${JBOSS_HOME}/bin/add-user.sh --user "${WILDFLY_USER}" --password "${WILDFLY_PASSWORD}" --silent 32 | 33 | info "Starting WildFly server" 34 | ${JBOSS_HOME}/bin/standalone.sh & 35 | 36 | info "Waiting for the server to boot" 37 | until check_server_state 2> /dev/null | grep -q running; do 38 | sleep 1; 39 | done 40 | info "WildFly has started up" 41 | 42 | info "Downloading MySQL driver" 43 | MYSQL_CONNECTOR="mysql-connector-java-${MYSQL_VERSION}.jar" 44 | MYSQL_CONNECTOR_LOCATION="/tmp/${MYSQL_CONNECTOR}" 45 | MYSQL_CONNECTOR_URL="https://repo.maven.apache.org/maven2/mysql/mysql-connector-java/${MYSQL_VERSION}/${MYSQL_CONNECTOR}" 46 | safe_run curl --location \ 47 | --output "${MYSQL_CONNECTOR_LOCATION}" \ 48 | --url "${MYSQL_CONNECTOR_URL}" 49 | 50 | info "Download completed, verifying SHA1 checksum for ${MYSQL_CONNECTOR}" 51 | MYSQL_CONNECTOR_SHA1="${MYSQL_CONNECTOR}.sha1" 52 | MYSQL_CONNECTOR_SHA1_LOCATION="/tmp/${MYSQL_CONNECTOR_SHA1}" 53 | safe_run curl --location \ 54 | --output "${MYSQL_CONNECTOR_SHA1_LOCATION}" \ 55 | --url "https://repo.maven.apache.org/maven2/mysql/mysql-connector-java/${MYSQL_VERSION}/${MYSQL_CONNECTOR_SHA1}" 56 | 57 | if ! printf "%s %s\n" "$(cat "${MYSQL_CONNECTOR_SHA1_LOCATION}")" "${MYSQL_CONNECTOR_LOCATION}" | sha1sum --check; then 58 | error "Invalid SHA1 checksum for ${MYSQL_CONNECTOR} downloaded from ${MYSQL_CONNECTOR_URL}" 59 | exit 1 60 | fi 61 | info "Verification successful" 62 | 63 | info "Adding MySQL module" 64 | MODULE_NAME="com.mysql" 65 | safe_run ${JBOSS_CLI} --connect \ 66 | --command="module add --name=${MODULE_NAME} --resources=${MYSQL_CONNECTOR_LOCATION} --dependencies=javax.api,javax.transaction.api" 67 | 68 | info "Installing MySQL driver" 69 | safe_run ${JBOSS_CLI} --connect \ 70 | --command="/subsystem=datasources/jdbc-driver=mysql:add(driver-name=mysql,driver-module-name=${MODULE_NAME},driver-class-name=com.mysql.cj.jdbc.Driver,driver-xa-datasource-class-name=com.mysql.cj.jdbc.MysqlXADataSource)" 71 | 72 | info "Creating a new datasource" 73 | safe_run ${JBOSS_CLI} --connect --command="data-source add 74 | --name=${DB_NAME}DS 75 | --jndi-name=java:jboss/datasources/cmdDS 76 | --user-name=${DB_USER} 77 | --password=${DB_PASSWORD} 78 | --driver-name=mysql 79 | --connection-url=jdbc:mysql://${DB_URI}/${DB_NAME} 80 | --max-pool-size=25 81 | --blocking-timeout-wait-millis=5000 82 | --enabled=true 83 | --jta=true 84 | --use-ccm=false 85 | --valid-connection-checker-class-name=org.jboss.jca.adapters.jdbc.extensions.mysql.MySQLValidConnectionChecker 86 | --background-validation=true 87 | --exception-sorter-class-name=org.jboss.jca.adapters.jdbc.extensions.mysql.MySQLExceptionSorter" 88 | 89 | info "Shutting down WildFly and Cleaning up" 90 | safe_run ${JBOSS_CLI} --connect --command=":shutdown" 91 | 92 | rm -rf ${JBOSS_HOME}/standalone/configuration/standalone_xml_history/ ${JBOSS_HOME}/standalone/log/* 93 | rm -f /tmp/*.jar /tmp/*.sha1 94 | -------------------------------------------------------------------------------- /docker/scripts/jboss-configure-postgresql-datasource.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | RED=$'\e[31m' 4 | GREEN=$'\e[32m' 5 | RESET=$'\e[0m' 6 | 7 | function info() { 8 | echo -e "${GREEN}==> $*${RESET}" 9 | } 10 | 11 | function error() { 12 | echo -e "${RED}==> ERROR: $*${RESET}" 13 | } 14 | 15 | function safe_run() { 16 | "$@" 17 | local exit_code=$? 18 | if [ "${exit_code}" -ne 0 ]; then 19 | error "The command '$*' exited with non-zero exit code ${exit_code}." 20 | exit "${exit_code}" 21 | fi 22 | } 23 | 24 | function check_server_state() { 25 | ${JBOSS_CLI} -c ":read-attribute(name=server-state)" 26 | } 27 | 28 | info "Setting up PostgreSQL Datasource for WilFly ${WILDFLY_VERSION}\n" 29 | 30 | info "Adding WildFly administration user" 31 | safe_run ${JBOSS_HOME}/bin/add-user.sh --user "${WILDFLY_USER}" --password "${WILDFLY_PASSWORD}" --silent 32 | 33 | info "Starting WildFly server" 34 | ${JBOSS_HOME}/bin/standalone.sh & 35 | 36 | info "Waiting for the server to boot" 37 | until check_server_state 2> /dev/null | grep -q running; do 38 | sleep 1; 39 | done 40 | info "WildFly has started up" 41 | 42 | info "Downloading PostgreSQL driver" 43 | POSTGRESQL_CONNECTOR="/tmp/postgresql-${POSTGRESQL_VERSION}.jar" 44 | MAVEN_DOWNLOAD_URL="http://central.maven.org/maven2/org/postgresql/postgresql/${POSTGRESQL_VERSION}/postgresql-${POSTGRESQL_VERSION}.jar" 45 | safe_run curl --location \ 46 | --output "${POSTGRESQL_CONNECTOR}" \ 47 | --url "${MAVEN_DOWNLOAD_URL}" 48 | 49 | info "PostgreSQL Driver downloaded, verifying SHA1 checksum" 50 | if ! sha1sum "${POSTGRESQL_CONNECTOR}" | grep "${POSTGRESQL_SHA1}" > /dev/null; then 51 | error "Invalid SHA1 for ${POSTGRESQL_CONNECTOR} downloaded from ${MAVEN_DOWNLOAD_URL}" 52 | exit 1 53 | fi 54 | info "Verified successfully" 55 | 56 | info "Adding PostgreSQL module" 57 | MODULE_NAME="org.postgresql" 58 | safe_run ${JBOSS_CLI} --connect \ 59 | --command="module add --name=${MODULE_NAME} --resources=${POSTGRESQL_CONNECTOR} --dependencies=javax.api,javax.transaction.api" 60 | 61 | info "Installing PostgreSQL driver" 62 | safe_run ${JBOSS_CLI} --connect \ 63 | --command="/subsystem=datasources/jdbc-driver=postgresql:add(driver-name=postgresql,driver-module-name=${MODULE_NAME},driver-xa-datasource-class-name=org.postgresql.xa.PGXADataSource)" 64 | 65 | info "Creating a new datasource" 66 | safe_run ${JBOSS_CLI} --connect --command="data-source add 67 | --name=${DB_NAME}DS 68 | --jndi-name=java:jboss/datasources/cmdDS 69 | --driver-name=postgresql 70 | --connection-url=${DATABASE_URL} 71 | --max-pool-size=25 72 | --blocking-timeout-wait-millis=5000 73 | --enabled=true 74 | --jta=true 75 | --use-ccm=false 76 | --valid-connection-checker-class-name=org.jboss.jca.adapters.jdbc.extensions.postgres.PostgreSQLValidConnectionChecker 77 | --background-validation=true 78 | --exception-sorter-class-name=org.jboss.jca.adapters.jdbc.extensions.postgres.PostgreSQLExceptionSorter" 79 | 80 | info "Shutting down WildFly and Cleaning up" 81 | safe_run ${JBOSS_CLI} --connect --command=":shutdown" 82 | 83 | rm -rf ${JBOSS_HOME}/standalone/configuration/standalone_xml_history/ ${JBOSS_HOME}/standalone/log/* 84 | rm -f /tmp/*.jar 85 | 86 | info "PostgreSQL Datasource configuration done" 87 | -------------------------------------------------------------------------------- /frontend/.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | not ie <= 8 4 | -------------------------------------------------------------------------------- /frontend/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | node: true 5 | }, 6 | 'extends': [ 7 | 'plugin:vue/essential', 8 | 'eslint:recommended' 9 | ], 10 | rules: { 11 | 'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off', 12 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off' 13 | }, 14 | parserOptions: { 15 | parser: 'babel-eslint' 16 | } 17 | }; 18 | -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | # local env files 6 | .env.local 7 | .env.*.local 8 | 9 | # Log files 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | 14 | # Editor directories and files 15 | .idea 16 | .vscode 17 | *.suo 18 | *.ntvs* 19 | *.njsproj 20 | *.sln 21 | *.sw* 22 | -------------------------------------------------------------------------------- /frontend/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "esversion": 6 3 | } 4 | -------------------------------------------------------------------------------- /frontend/README.md: -------------------------------------------------------------------------------- 1 | # cme 2 | 3 | ## Project setup 4 | ``` 5 | npm install 6 | ``` 7 | 8 | ### Compiles and hot-reloads for development 9 | ``` 10 | npm run serve 11 | ``` 12 | 13 | ### Compiles and minifies for production 14 | ``` 15 | npm run build 16 | ``` 17 | 18 | ### Run your tests 19 | ``` 20 | npm run test 21 | ``` 22 | 23 | ### Lints and fixes files 24 | ``` 25 | npm run lint 26 | ``` 27 | 28 | ### Customize configuration 29 | See [Configuration Reference](https://cli.vuejs.org/config/). 30 | -------------------------------------------------------------------------------- /frontend/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/app' 4 | ] 5 | }; 6 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "collaborative-markdown-editor-frontend", 3 | "version": "1.0.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve", 7 | "build": "vue-cli-service build", 8 | "lint": "vue-cli-service lint", 9 | "postbuild": "node postscript.js" 10 | }, 11 | "dependencies": { 12 | "axios": "^0.21.1", 13 | "core-js": "^2.6.9", 14 | "debounce": "^1.2.0", 15 | "js-cookie": "^2.2.1", 16 | "marked": "^0.7.0", 17 | "vue": "^2.6.6", 18 | "vue-axios": "^2.1.4", 19 | "vue-beautiful-chat": "git+https://github.com/MSkrzypietz/vue-beautiful-chat.git", 20 | "vue-router": "^3.1.3", 21 | "vue-simplemde": "^0.5.2", 22 | "vue-snotify": "^3.2.1", 23 | "vuetify": "^1.5.18", 24 | "vuex": "^3.1.1", 25 | "vuex-persistedstate": "^2.5.4" 26 | }, 27 | "devDependencies": { 28 | "@vue/cli-plugin-babel": "^3.11.0", 29 | "@vue/cli-plugin-eslint": "^4.3.0", 30 | "@vue/cli-service": "^4.2.3", 31 | "babel-eslint": "^10.0.3", 32 | "eslint": "^5.8.0", 33 | "eslint-plugin-vue": "^5.2.3", 34 | "node-sass": "^4.13.1", 35 | "sass-loader": "^8.0.2", 36 | "stylus": "^0.54.7", 37 | "stylus-loader": "^3.0.1", 38 | "vue-cli-plugin-vuetify": "^0.5.0", 39 | "vue-template-compiler": "^2.5.21", 40 | "vuetify-loader": "^1.4.3" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /frontend/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | autoprefixer: {} 4 | } 5 | }; 6 | -------------------------------------------------------------------------------- /frontend/postscript.js: -------------------------------------------------------------------------------- 1 | const exec = require('child_process').exec; 2 | 3 | function log(error, stdout, stderr) { 4 | console.log(stdout); 5 | console.log(stderr); 6 | } 7 | 8 | const os = require('os'); 9 | 10 | const webappPath = "../src/main/webapp"; 11 | 12 | if (os.type() === 'Darwin' || os.type() === 'Linux') { 13 | exec(`rm -r "${webappPath}"/*`, log); 14 | exec(`mv dist/* "${webappPath}"`, log); 15 | } else if (os.type() === 'Windows_NT') { 16 | exec(`del /S /Q "${webappPath}"\\*`, log); 17 | exec(`xcopy dist\\* "${webappPath}"\\* /s /e /i /Y`, log); 18 | } else { 19 | throw new Error("Unsupported OS found: " + os.type()); 20 | } 21 | -------------------------------------------------------------------------------- /frontend/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mortenterhart/collaborative-markdown-editor/50ba075d605336ed727324dda2aa960255af01c2/frontend/public/favicon.ico -------------------------------------------------------------------------------- /frontend/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Collaborative Markdown Editor 9 | 10 | 11 | 12 | 13 | 16 |
17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /frontend/src/App.vue: -------------------------------------------------------------------------------- 1 | 70 | 71 | 103 | -------------------------------------------------------------------------------- /frontend/src/assets/background.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mortenterhart/collaborative-markdown-editor/50ba075d605336ed727324dda2aa960255af01c2/frontend/src/assets/background.jpeg -------------------------------------------------------------------------------- /frontend/src/assets/cmd_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mortenterhart/collaborative-markdown-editor/50ba075d605336ed727324dda2aa960255af01c2/frontend/src/assets/cmd_logo.png -------------------------------------------------------------------------------- /frontend/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mortenterhart/collaborative-markdown-editor/50ba075d605336ed727324dda2aa960255af01c2/frontend/src/assets/logo.png -------------------------------------------------------------------------------- /frontend/src/assets/social.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mortenterhart/collaborative-markdown-editor/50ba075d605336ed727324dda2aa960255af01c2/frontend/src/assets/social.jpg -------------------------------------------------------------------------------- /frontend/src/assets/welcome_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mortenterhart/collaborative-markdown-editor/50ba075d605336ed727324dda2aa960255af01c2/frontend/src/assets/welcome_logo.png -------------------------------------------------------------------------------- /frontend/src/assets/work.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mortenterhart/collaborative-markdown-editor/50ba075d605336ed727324dda2aa960255af01c2/frontend/src/assets/work.jpeg -------------------------------------------------------------------------------- /frontend/src/components/ErrorPage.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 27 | 28 | 41 | -------------------------------------------------------------------------------- /frontend/src/components/ForbiddenPage.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 22 | 23 | 36 | -------------------------------------------------------------------------------- /frontend/src/components/Preview.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 18 | 19 | 27 | -------------------------------------------------------------------------------- /frontend/src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import './plugins/vuetify'; 3 | import './plugins/snotify'; 4 | import './plugins/axios'; 5 | import './plugins/chat'; 6 | import store from './store'; 7 | import App from './App.vue'; 8 | import router from './router'; 9 | 10 | Vue.prototype.location = window.location; 11 | 12 | Vue.config.productionTip = false; 13 | 14 | new Vue({ 15 | render: h => h(App), 16 | store, 17 | router, 18 | }).$mount('#app'); 19 | -------------------------------------------------------------------------------- /frontend/src/plugins/axios.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import axios from 'axios'; 3 | import VueAxios from 'vue-axios'; 4 | 5 | Vue.use(VueAxios, axios.create({ 6 | baseURL: location.origin + location.pathname.replace(/\/?$/, "") + '/api' 7 | })); 8 | -------------------------------------------------------------------------------- /frontend/src/plugins/chat.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import Chat from 'vue-beautiful-chat'; 3 | 4 | Vue.use(Chat); 5 | -------------------------------------------------------------------------------- /frontend/src/plugins/snotify.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import Snotify from 'vue-snotify'; 3 | import 'vue-snotify/styles/material.css'; 4 | 5 | Vue.use(Snotify, { 6 | toast: { 7 | timeout: 4000 // default duration 8 | } 9 | }); -------------------------------------------------------------------------------- /frontend/src/plugins/vuetify.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import Vuetify from 'vuetify/lib'; 3 | import 'vuetify/src/stylus/app.styl'; 4 | 5 | Vue.use(Vuetify, { 6 | iconfont: 'md', 7 | }); 8 | -------------------------------------------------------------------------------- /frontend/src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import VueRouter from 'vue-router'; 3 | import Welcome from '../components/Welcome'; 4 | import Document from '../components/Document'; 5 | import ErrorPage from '../components/ErrorPage'; 6 | import ForbiddenPage from '../components/ForbiddenPage'; 7 | 8 | Vue.use(VueRouter); 9 | 10 | const routes = [ 11 | { path: '/', component: Welcome }, 12 | { path: '/doc/:id', component: Document, name: 'document' }, 13 | { path: '/Forbidden', component: ForbiddenPage }, 14 | { path: '*', component: ErrorPage } 15 | ]; 16 | 17 | export default new VueRouter({ 18 | base: location.pathname, 19 | routes 20 | }); 21 | -------------------------------------------------------------------------------- /frontend/src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import Vuex from 'vuex'; 3 | import login from './modules/login'; 4 | import app from './modules/app'; 5 | import createPersistedState from 'vuex-persistedstate'; 6 | 7 | Vue.use(Vuex); 8 | 9 | const debug = process.env.NODE_ENV !== 'production'; 10 | 11 | export default new Vuex.Store({ 12 | modules: { 13 | login, 14 | app 15 | }, 16 | strict: debug, 17 | plugins: [createPersistedState({ 18 | paths: ['login', 'app.currentDocument', 'app.title'] 19 | })], 20 | }); 21 | -------------------------------------------------------------------------------- /frontend/src/store/modules/app.js: -------------------------------------------------------------------------------- 1 | const state = { 2 | title: '', 3 | currentDocument: { repo: { owner: { id: -1, name: '' } } }, 4 | editorKey: 0, 5 | otherCollaborators: [] 6 | }; 7 | 8 | const getters = {}; 9 | 10 | const actions = {}; 11 | 12 | const mutations = { 13 | setTitle: (state, newValue) => { 14 | state.title = newValue; 15 | }, 16 | setOtherCollaborators: (state, newValue) => { 17 | state.otherCollaborators = newValue; 18 | }, 19 | addCollaborator: (state, user) => { 20 | for (let i = 0; i < state.otherCollaborators.length; i++) { 21 | if (state.otherCollaborators[i].name === user.name) { 22 | return; 23 | } 24 | } 25 | 26 | state.otherCollaborators = [...state.otherCollaborators, user]; 27 | }, 28 | removeCollaborator: (state, user) => { 29 | for (let i = 0; i < state.otherCollaborators.length; i++) { 30 | if (state.otherCollaborators[i].name === user.name) { 31 | state.otherCollaborators.splice(i, 1); 32 | return; 33 | } 34 | } 35 | }, 36 | incEditorKey: (state) => { 37 | state.editorKey++; 38 | }, 39 | setCurrentDocument: (state, newValue) => { 40 | state.currentDocument = newValue; 41 | } 42 | }; 43 | 44 | export default { 45 | namespaced: true, 46 | state, 47 | getters, 48 | actions, 49 | mutations 50 | }; 51 | -------------------------------------------------------------------------------- /frontend/src/store/modules/login.js: -------------------------------------------------------------------------------- 1 | const state = { 2 | user: { id: 0, name: "", mail: "" }, 3 | showLoginDialog: false, 4 | isLoggedIn: false, 5 | }; 6 | 7 | const getters = {}; 8 | 9 | const actions = {}; 10 | 11 | const mutations = { 12 | showLoginDialog(state) { 13 | state.showLoginDialog = true; 14 | }, 15 | hideLoginDialog(state) { 16 | state.showLoginDialog = false; 17 | }, 18 | setIsLoggedIn: (state, newValue) => { 19 | state.isLoggedIn = newValue; 20 | }, 21 | setUser: (state, newValue) => { 22 | state.user = newValue; 23 | } 24 | }; 25 | 26 | export default { 27 | namespaced: true, 28 | state, 29 | getters, 30 | actions, 31 | mutations 32 | }; 33 | -------------------------------------------------------------------------------- /frontend/vue.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | publicPath: './' 3 | }; 4 | -------------------------------------------------------------------------------- /heroku.yml: -------------------------------------------------------------------------------- 1 | build: 2 | docker: 3 | web: Dockerfile 4 | mysql: docker/mysql.dockerfile 5 | config: 6 | buildno: 1 7 | run: 8 | web: 9 | command: 10 | - /opt/jboss/wildfly/bin/standalone.sh -b 0.0.0.0 -bmanagement 0.0.0.0 -Djboss.http.port=$PORT 11 | mysql: 12 | command: 13 | - mysqld 14 | -------------------------------------------------------------------------------- /resources/3-tier-architecture.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mortenterhart/collaborative-markdown-editor/50ba075d605336ed727324dda2aa960255af01c2/resources/3-tier-architecture.jpg -------------------------------------------------------------------------------- /resources/AppExampleDisplay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mortenterhart/collaborative-markdown-editor/50ba075d605336ed727324dda2aa960255af01c2/resources/AppExampleDisplay.png -------------------------------------------------------------------------------- /resources/client-server-architecture.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mortenterhart/collaborative-markdown-editor/50ba075d605336ed727324dda2aa960255af01c2/resources/client-server-architecture.jpg -------------------------------------------------------------------------------- /sql/cmd.mwb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mortenterhart/collaborative-markdown-editor/50ba075d605336ed727324dda2aa960255af01c2/sql/cmd.mwb -------------------------------------------------------------------------------- /src/main/java/org/dhbw/mosbach/ai/cmd/crdt/ActiveDocument.java: -------------------------------------------------------------------------------- 1 | package org.dhbw.mosbach.ai.cmd.crdt; 2 | 3 | import org.dhbw.mosbach.ai.cmd.model.Doc; 4 | 5 | import javax.websocket.Session; 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | 9 | /** 10 | * Representation of an active document which is being worked on by n users at the same time 11 | * 12 | * @author 3040018 13 | */ 14 | public class ActiveDocument { 15 | 16 | private static final int QUEUE_LIMIT = 10; 17 | 18 | private Doc doc; 19 | private int state; 20 | private List users; 21 | private List latestTransforms; 22 | 23 | public ActiveDocument() { 24 | this.state = 0; 25 | this.users = new ArrayList<>(); 26 | this.latestTransforms = new ArrayList<>(); 27 | } 28 | 29 | public ActiveDocument(Doc doc, int state) { 30 | this.doc = doc; 31 | this.state = state; 32 | this.users = new ArrayList<>(); 33 | this.latestTransforms = new ArrayList<>(); 34 | } 35 | 36 | public Doc getDoc() { 37 | return doc; 38 | } 39 | 40 | public void setDoc(Doc doc) { 41 | this.doc = doc; 42 | } 43 | 44 | public int getState() { 45 | return state; 46 | } 47 | 48 | public void setState(int state) { 49 | this.state = state; 50 | } 51 | 52 | public List getUsers() { 53 | return users; 54 | } 55 | 56 | public void setUsers(List users) { 57 | this.users = users; 58 | } 59 | 60 | public List getLatestTransforms() { 61 | return latestTransforms; 62 | } 63 | 64 | public void setLatestTransforms(List latestTransforms) { 65 | this.latestTransforms = latestTransforms; 66 | } 67 | 68 | /** 69 | * Inserts a message from a user at a given index 70 | * 71 | * @param msg Given message 72 | */ 73 | public void insert(Message msg) { 74 | this.doc.setContent(new StringBuilder() 75 | .append(doc.getContent(), 0, msg.getCursorPos()) 76 | .append(msg.getMsg()) 77 | .append(doc.getContent().substring(msg.getCursorPos())) 78 | .toString()); 79 | this.state++; 80 | appendTransform(msg); 81 | } 82 | 83 | /** 84 | * Deletes a message from a user of a given length at a given index 85 | * 86 | * @param msg Given message 87 | */ 88 | public void del(Message msg) { 89 | this.doc.setContent(new StringBuilder() 90 | .append(doc.getContent(), 0, msg.getCursorPos()) 91 | .append(doc.getContent().substring(msg.getCursorPos() + msg.getMsg().length())) 92 | .toString()); 93 | this.state++; 94 | appendTransform(msg); 95 | } 96 | 97 | /** 98 | * Performs the lost operations on a given message 99 | * 100 | * @param msg Given message 101 | * @param docState Given doc state 102 | */ 103 | public void makeConsistent(Message msg, int docState) { 104 | int delta = QUEUE_LIMIT - (msg.getDocState() - docState); 105 | int cursorPos = msg.getCursorPos(); 106 | 107 | for (int i = delta; i < QUEUE_LIMIT; i++) { 108 | 109 | Message queuedMsg = this.latestTransforms.get(i); 110 | 111 | if (msg.getCursorPos() > queuedMsg.getCursorPos()) { 112 | switch (queuedMsg.getMessageType()) { 113 | case Insert: 114 | cursorPos += queuedMsg.getMsg().length(); 115 | break; 116 | case Delete: 117 | cursorPos -= queuedMsg.getMsg().length(); 118 | break; 119 | default: 120 | throw new RuntimeException("Invalid MessageType used for normal editor messages"); 121 | } 122 | } 123 | } 124 | msg.setCursorPos(cursorPos); 125 | } 126 | 127 | /** 128 | * Add a new transform to the list of applied transforms in the active doc 129 | * 130 | * @param msg Given message to append 131 | */ 132 | private void appendTransform(Message msg) { 133 | msg.setDocState(msg.getDocState() + 1); 134 | 135 | if (this.latestTransforms.isEmpty()) { 136 | this.latestTransforms.add(msg); 137 | } else { 138 | if (this.latestTransforms.size() == QUEUE_LIMIT) { 139 | this.latestTransforms.remove(0); 140 | } 141 | 142 | this.latestTransforms.add(msg); 143 | } 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /src/main/java/org/dhbw/mosbach/ai/cmd/crdt/Message.java: -------------------------------------------------------------------------------- 1 | package org.dhbw.mosbach.ai.cmd.crdt; 2 | 3 | import org.dhbw.mosbach.ai.cmd.util.MessageType; 4 | 5 | /** 6 | * A message sent by a client to the server 7 | * 8 | * @author 3040018 9 | */ 10 | public class Message { 11 | 12 | private int userId; 13 | private int docId; 14 | private int cursorPos; 15 | private int docState; 16 | private String msg; 17 | private MessageType messageType; 18 | 19 | public int getUserId() { 20 | return userId; 21 | } 22 | 23 | public void setUserId(int userId) { 24 | this.userId = userId; 25 | } 26 | 27 | public int getDocId() { 28 | return docId; 29 | } 30 | 31 | public void setDocId(int docId) { 32 | this.docId = docId; 33 | } 34 | 35 | public int getCursorPos() { 36 | return cursorPos; 37 | } 38 | 39 | public void setCursorPos(int cursorPos) { 40 | this.cursorPos = cursorPos; 41 | } 42 | 43 | public int getDocState() { 44 | return docState; 45 | } 46 | 47 | public void setDocState(int docState) { 48 | this.docState = docState; 49 | } 50 | 51 | public String getMsg() { 52 | return msg; 53 | } 54 | 55 | public void setMsg(String msg) { 56 | this.msg = msg; 57 | } 58 | 59 | public MessageType getMessageType() { 60 | return messageType; 61 | } 62 | 63 | public void setMessageType(MessageType messageType) { 64 | this.messageType = messageType; 65 | } 66 | 67 | @Override 68 | public String toString() { 69 | return new StringBuilder() 70 | .append("\tuserId: " + this.userId + "\n") 71 | .append("\tdocId: " + this.docId + "\n") 72 | .append("\tmessageType: " + this.messageType.toString() + "\n") 73 | .append("\tcursorPos: " + this.cursorPos + "\n") 74 | .append("\tmsg: " + this.msg + "\n") 75 | .toString(); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/main/java/org/dhbw/mosbach/ai/cmd/crdt/MessageBroker.java: -------------------------------------------------------------------------------- 1 | package org.dhbw.mosbach.ai.cmd.crdt; 2 | 3 | import org.dhbw.mosbach.ai.cmd.util.CmdConfig; 4 | import org.dhbw.mosbach.ai.cmd.util.MessageType; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | 8 | import javax.json.Json; 9 | import javax.websocket.Session; 10 | import java.util.List; 11 | 12 | /** 13 | * Wrapper class to keep a consistent document state and publish updates to all the connected clients 14 | * 15 | * @author 3040018 16 | */ 17 | public class MessageBroker { 18 | 19 | private static final Logger log = LoggerFactory.getLogger(MessageBroker.class); 20 | 21 | /** 22 | * Transforms a given message according to the provided MessageType 23 | * 24 | * @param msg Given message 25 | * @param activeDocument Current active document 26 | */ 27 | public void transform(Message msg, ActiveDocument activeDocument) { 28 | if (msg.getDocState() < activeDocument.getState()) { 29 | activeDocument.makeConsistent(msg, activeDocument.getState()); 30 | } 31 | 32 | switch (msg.getMessageType()) { 33 | case Insert: 34 | activeDocument.insert(msg); 35 | break; 36 | case Delete: 37 | activeDocument.del(msg); 38 | break; 39 | case UserJoined: /*No transform needed for these message types*/ 40 | case UserLeft: 41 | case ContentInit: 42 | case DocumentTitle: 43 | case UsersInit: 44 | case ChatMessage: 45 | break; 46 | default: 47 | throw new RuntimeException("Provided unsupported message type for transform."); 48 | } 49 | } 50 | 51 | /** 52 | * Creates a message if a user joined or left a document, so the other users can be notified. 53 | * Also creates a message to send the doc contents if a user just joined 54 | * 55 | * @param docId Given document id 56 | * @param msg Given message content depending on the type of system message 57 | * @param messageType Given message type 58 | * @return A message object 59 | */ 60 | public Message createSystemMessage(int userId, int docId, int docState, String msg, MessageType messageType) { 61 | Message message = new Message(); 62 | 63 | message.setCursorPos(-1); 64 | message.setDocState(docState); 65 | message.setDocId(docId); 66 | message.setMessageType(messageType); 67 | message.setMsg(msg); 68 | message.setUserId(userId); 69 | 70 | return message; 71 | } 72 | 73 | /** 74 | * Publishes the changes to the current document to the other clients 75 | * 76 | * @param msg Given message 77 | * @param activeDocument Current active document 78 | */ 79 | public void publishToOtherUsers(Message msg, ActiveDocument activeDocument, Session currentUserSession) { 80 | for (Session session : activeDocument.getUsers()) { 81 | try { 82 | if (session != currentUserSession) { 83 | session.getBasicRemote().sendObject(msg); 84 | } 85 | } catch (Exception e) { 86 | log.error(e.getLocalizedMessage(), e); 87 | } 88 | } 89 | } 90 | 91 | /** 92 | * Publishes a message to a single user only. Used to initially 93 | * send the doc contents to a user upon connecting 94 | * 95 | * @param msg Given message 96 | * @param session Session of user to send the message to 97 | */ 98 | public void publishToSingleUser(Message msg, Session session) { 99 | try { 100 | session.getBasicRemote().sendObject(msg); 101 | } catch (Exception e) { 102 | log.error(e.getLocalizedMessage(), e); 103 | } 104 | } 105 | 106 | /** 107 | * Gets the list of active users on a doc and returns them as a JSON array 108 | * 109 | * @param users Given active user within a doc 110 | * @param currentUser User requesting the list, to exclude himself 111 | * @return A list of user names formatted as a JSON array 112 | */ 113 | public String getActiveUsers(List users, Session currentUser) { 114 | if (users.size() <= 1) { // Return empty array if the user is alone 115 | return "[]"; 116 | } 117 | 118 | StringBuilder sb = new StringBuilder(); 119 | sb.append("["); 120 | 121 | for (Session session : users) { 122 | if (session != currentUser) { 123 | sb.append(formatUserMessage(session)) 124 | .append(","); 125 | } 126 | } 127 | 128 | sb.deleteCharAt(sb.length() - 1); 129 | sb.append("]"); 130 | 131 | return sb.toString(); 132 | } 133 | 134 | /** 135 | * Takes a single user session and formats a JSON message to provide user details 136 | * 137 | * @param session Given user session 138 | * @return A JSON object with the id and name of the user 139 | */ 140 | public String formatUserMessage(Session session) { 141 | return Json.createObjectBuilder() 142 | .add("id", session.getUserProperties().get(CmdConfig.SESSION_USERID).toString()) 143 | .add("name", session.getUserProperties().get(CmdConfig.SESSION_USERNAME).toString()) 144 | .add("imageUrl", "https://ui-avatars.com/api/?name=" + session.getUserProperties().get(CmdConfig.SESSION_USERNAME).toString() + "&background=0D8ABC&color=fff") 145 | .build() 146 | .toString(); 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /src/main/java/org/dhbw/mosbach/ai/cmd/db/DocDao.java: -------------------------------------------------------------------------------- 1 | package org.dhbw.mosbach.ai.cmd.db; 2 | 3 | import org.dhbw.mosbach.ai.cmd.model.Doc; 4 | import org.dhbw.mosbach.ai.cmd.model.User; 5 | import org.dhbw.mosbach.ai.cmd.util.CmdConfig; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | 9 | import javax.enterprise.context.Dependent; 10 | import javax.persistence.EntityManager; 11 | import javax.persistence.NoResultException; 12 | import javax.persistence.PersistenceContext; 13 | import javax.transaction.Transactional; 14 | import java.util.ArrayList; 15 | import java.util.List; 16 | 17 | /** 18 | * Dao class for doc interactions with the database 19 | * 20 | * @author 3040018 21 | */ 22 | @Dependent 23 | public class DocDao { 24 | 25 | private static final Logger log = LoggerFactory.getLogger(DocDao.class); 26 | 27 | @PersistenceContext(unitName = CmdConfig.JPA_UNIT_NAME) 28 | private EntityManager em; 29 | 30 | /** 31 | * Add an entry to the doc table 32 | * 33 | * @param d Given doc object 34 | */ 35 | @Transactional 36 | public void createDoc(Doc d) { 37 | this.em.persist(d); 38 | log.debug("Created new document '{}' in database", d.getName()); 39 | } 40 | 41 | /** 42 | * Get a single document from the database based on the given id 43 | * 44 | * @param id Given document id 45 | * @return A doc object if one was found, null otherwise 46 | */ 47 | @Transactional 48 | public Doc getDoc(int id) { 49 | Doc doc = null; 50 | 51 | try { 52 | doc = (Doc) this.em 53 | .createQuery("SELECT d FROM Doc d WHERE d.id=:doc_id") 54 | .setParameter("doc_id", id) 55 | .getSingleResult(); 56 | } catch (NoResultException e) { 57 | return null; 58 | } 59 | 60 | return doc; 61 | } 62 | 63 | /** 64 | * Get all the documents where the given user is the owner 65 | * 66 | * @param u Given user 67 | * @return A list of docs, null if nothing is found 68 | */ 69 | @SuppressWarnings("unchecked") 70 | @Transactional 71 | public List getDocsOwnedBy(User u) { 72 | List docs = new ArrayList<>(); 73 | 74 | try { 75 | docs = (List) this.em 76 | .createQuery("SELECT d FROM Doc d WHERE d.repo.owner.id=:user_id ORDER BY d.ctime DESC") 77 | .setParameter("user_id", u.getId()) 78 | .getResultList(); 79 | } catch (NoResultException e) { 80 | return null; 81 | } 82 | 83 | return docs; 84 | } 85 | 86 | /** 87 | * Get all the documents where the given user is a collaborator 88 | * 89 | * @param u Given user 90 | * @return A list of docs, null if nothing is found 91 | */ 92 | @SuppressWarnings("unchecked") 93 | @Transactional 94 | public List getDocsCollaboratedBy(User u) { 95 | List docs = new ArrayList<>(); 96 | 97 | try { 98 | docs = (List) this.em 99 | .createQuery("SELECT d FROM Doc d, Collaborator c WHERE d.id = c.doc.id AND c.user.id = :userId ORDER BY d.ctime DESC") 100 | .setParameter("userId", u.getId()) 101 | .getResultList(); 102 | } catch (NoResultException e) { 103 | return null; 104 | } 105 | 106 | return docs; 107 | } 108 | 109 | /** 110 | * Removes a given document based on the id from the database. 111 | * 112 | * @param d the document to be removed 113 | * @return the number of updated rows 114 | */ 115 | @Transactional 116 | public int removeDoc(Doc d) { 117 | log.debug("Removed document '{}' from database", d.getId()); 118 | return this.em.createQuery("DELETE FROM Doc d WHERE d.id = :doc_id") 119 | .setParameter("doc_id", d.getId()) 120 | .executeUpdate(); 121 | } 122 | 123 | /** 124 | * Update a certain document in the database 125 | * 126 | * @param d Given doc object 127 | * @return The number of updated rows 128 | */ 129 | @Transactional 130 | public int updateDoc(Doc d) { 131 | log.debug("Updated document '{}' with new content", d.getId()); 132 | return this.em.createQuery("UPDATE Doc d SET d.content=:content, d.uuser=:uuser WHERE d.id=:id") 133 | .setParameter("content", d.getContent()) 134 | .setParameter("uuser", d.getUuser()) 135 | .setParameter("id", d.getId()) 136 | .executeUpdate(); 137 | } 138 | 139 | /** 140 | * Transfer the ownership of a doc to another user 141 | * 142 | * @param d Given doc 143 | * @return The number of updated rows 144 | */ 145 | @Transactional 146 | public int transferRepo(Doc d) { 147 | log.debug("Transferred ownership of document '{}' to user '{}'", d.getId(), d.getRepo().getOwner().getName()); 148 | return this.em.createQuery("UPDATE Doc d SET d.repo=:repo, d.uuser=:uuser WHERE d.id=:id") 149 | .setParameter("repo", d.getRepo()) 150 | .setParameter("uuser", d.getUuser()) 151 | .setParameter("id", d.getId()) 152 | .executeUpdate(); 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /src/main/java/org/dhbw/mosbach/ai/cmd/db/HistoryDao.java: -------------------------------------------------------------------------------- 1 | package org.dhbw.mosbach.ai.cmd.db; 2 | 3 | import org.dhbw.mosbach.ai.cmd.model.Doc; 4 | import org.dhbw.mosbach.ai.cmd.model.History; 5 | import org.dhbw.mosbach.ai.cmd.util.CmdConfig; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | 9 | import javax.enterprise.context.Dependent; 10 | import javax.persistence.EntityManager; 11 | import javax.persistence.NoResultException; 12 | import javax.persistence.PersistenceContext; 13 | import javax.transaction.Transactional; 14 | import java.util.ArrayList; 15 | import java.util.List; 16 | 17 | /** 18 | * Dao class for history interactions with the database 19 | * 20 | * @author 3040018 21 | */ 22 | @Dependent 23 | public class HistoryDao { 24 | 25 | private static final Logger log = LoggerFactory.getLogger(HistoryDao.class); 26 | 27 | @PersistenceContext(unitName = CmdConfig.JPA_UNIT_NAME) 28 | private EntityManager em; 29 | 30 | /** 31 | * Add an entry to the history table 32 | * Checks if the latest available hash matches the current hash and 33 | * does not create a new entry if it does. 34 | * 35 | * @param h Given history object 36 | */ 37 | @Transactional 38 | public void createHistory(History h) { 39 | History latestHistory = getLatestHistoryForDoc(h.getDoc()); 40 | 41 | if (latestHistory != null && latestHistory.getHash().equals(h.getHash())) { 42 | return; 43 | } 44 | 45 | this.em.persist(h); 46 | 47 | log.debug("Created a new history entry in database"); 48 | } 49 | 50 | /** 51 | * Get the latest history entry for a given document 52 | * 53 | * @param d Given doc object 54 | * @return A history object, if an entry was found, null otherwise 55 | */ 56 | @SuppressWarnings("unchecked") 57 | public History getLatestHistoryForDoc(Doc d) { 58 | List history = null; 59 | 60 | try { 61 | history = (List) this.em 62 | .createQuery("Select h FROM History h WHERE h.doc.id=:doc_id ORDER BY h.ctime DESC") 63 | .setParameter("doc_id", d.getId()) 64 | .getResultList(); 65 | if (history == null || history.isEmpty()) { 66 | return null; 67 | } 68 | } catch (NoResultException e) { 69 | return null; 70 | } 71 | 72 | return history.get(0); 73 | } 74 | 75 | /** 76 | * Gets a full list of a documents history 77 | * 78 | * @param d Given document 79 | * @return A list of history entries if there are any, null otherwise 80 | */ 81 | @SuppressWarnings("unchecked") 82 | @Transactional 83 | public List getFullHistoryForDoc(Doc d) { 84 | List fullHistory = new ArrayList<>(); 85 | 86 | try { 87 | fullHistory = (List) this.em 88 | .createQuery("Select h FROM History h WHERE h.doc.id=:doc_id ORDER BY h.ctime DESC") 89 | .setParameter("doc_id", d.getId()) 90 | .getResultList(); 91 | } catch (NoResultException e) { 92 | return null; 93 | } 94 | 95 | return fullHistory; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/main/java/org/dhbw/mosbach/ai/cmd/db/RepoDao.java: -------------------------------------------------------------------------------- 1 | package org.dhbw.mosbach.ai.cmd.db; 2 | 3 | import org.dhbw.mosbach.ai.cmd.model.Repo; 4 | import org.dhbw.mosbach.ai.cmd.model.User; 5 | import org.dhbw.mosbach.ai.cmd.util.CmdConfig; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | 9 | import javax.enterprise.context.Dependent; 10 | import javax.persistence.EntityManager; 11 | import javax.persistence.NoResultException; 12 | import javax.persistence.PersistenceContext; 13 | import javax.transaction.Transactional; 14 | 15 | /** 16 | * Dao class for repo interactions with the database 17 | * 18 | * @author 3040018 19 | */ 20 | @Dependent 21 | public class RepoDao { 22 | 23 | private static final Logger log = LoggerFactory.getLogger(RepoDao.class); 24 | 25 | @PersistenceContext(unitName = CmdConfig.JPA_UNIT_NAME) 26 | private EntityManager em; 27 | 28 | /** 29 | * Create a repo entry in the database for a user 30 | * 31 | * @param repo Given repo object 32 | */ 33 | @Transactional 34 | public void createRepo(Repo repo) { 35 | this.em.persist(repo); 36 | log.debug("Created repository for user '{}'", repo.getOwner().getName()); 37 | } 38 | 39 | /** 40 | * Get a repo from the database based on the given user 41 | * 42 | * @param user Given user 43 | * @return A Repo object, if one was found with the user id, otherwise it returns null 44 | */ 45 | public Repo getRepo(User user) { 46 | Repo repo = null; 47 | 48 | try { 49 | repo = (Repo) this.em 50 | .createQuery("SELECT r FROM Repo r WHERE r.owner.id=:userid") 51 | .setParameter("userid", user.getId()) 52 | .getSingleResult(); 53 | } catch (NoResultException e) { 54 | return null; 55 | } 56 | 57 | return repo; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/org/dhbw/mosbach/ai/cmd/db/UserDao.java: -------------------------------------------------------------------------------- 1 | package org.dhbw.mosbach.ai.cmd.db; 2 | 3 | import org.dhbw.mosbach.ai.cmd.model.Repo; 4 | import org.dhbw.mosbach.ai.cmd.model.User; 5 | import org.dhbw.mosbach.ai.cmd.util.CmdConfig; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | 9 | import javax.enterprise.context.Dependent; 10 | import javax.inject.Inject; 11 | import javax.persistence.EntityManager; 12 | import javax.persistence.NoResultException; 13 | import javax.persistence.PersistenceContext; 14 | import javax.transaction.Transactional; 15 | 16 | /** 17 | * Dao class for user interactions with the database 18 | * 19 | * @author 3040018 20 | */ 21 | @Dependent 22 | public class UserDao { 23 | 24 | private static final Logger log = LoggerFactory.getLogger(UserDao.class); 25 | 26 | @PersistenceContext(unitName = CmdConfig.JPA_UNIT_NAME) 27 | private EntityManager em; 28 | 29 | @Inject 30 | private RepoDao repoDao; 31 | 32 | /** 33 | * Create a user to register them and also create a repo for them. 34 | * 35 | * @param u Given User object 36 | */ 37 | @Transactional 38 | public void createUser(User u) { 39 | this.em.persist(u); 40 | 41 | Repo repo = new Repo(); 42 | repo.setOwner(u); 43 | 44 | repoDao.createRepo(repo); 45 | 46 | log.debug("Created new user '{}' in database", u.getName()); 47 | } 48 | 49 | /** 50 | * Get a user entry from the database based on the provided username. 51 | * 52 | * @param username Given username 53 | * @return A User object, if one was found with the username, otherwise it returns null 54 | */ 55 | public User getUserByName(String username) { 56 | User user = null; 57 | 58 | try { 59 | user = (User) this.em 60 | .createQuery("SELECT u FROM User u WHERE LOWER(u.name)=:username") 61 | .setParameter("username", username.toLowerCase()) 62 | .getSingleResult(); 63 | } catch (NoResultException e) { 64 | return null; 65 | } 66 | 67 | return user; 68 | } 69 | 70 | /** 71 | * Get a user entry from the database based on the provided id. 72 | * 73 | * @param id Given id 74 | * @return A User object, if one was found with the id, otherwise it returns null 75 | */ 76 | public User getUserById(int id) { 77 | User user = null; 78 | 79 | try { 80 | user = (User) this.em 81 | .createQuery("SELECT u FROM User u WHERE u.id=:id") 82 | .setParameter("id", id) 83 | .getSingleResult(); 84 | } catch (NoResultException e) { 85 | return null; 86 | } 87 | 88 | return user; 89 | } 90 | 91 | /** 92 | * Update the username, mail and password of a certain user 93 | * 94 | * @param user the user 95 | * @return The number of updated rows 96 | */ 97 | @Transactional 98 | public int updateUser(User user) { 99 | log.debug("Updated user '{}' with name, mail and password", user.getName()); 100 | return this.em.createQuery("UPDATE User u SET u.name=:name, u.mail=:mail, u.password=:password WHERE u.id=:id") 101 | .setParameter("name", user.getName()) 102 | .setParameter("mail", user.getMail()) 103 | .setParameter("password", user.getPassword()) 104 | .setParameter("id", user.getId()) 105 | .executeUpdate(); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/main/java/org/dhbw/mosbach/ai/cmd/model/Collaborator.java: -------------------------------------------------------------------------------- 1 | package org.dhbw.mosbach.ai.cmd.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnore; 4 | import com.fasterxml.jackson.databind.annotation.JsonDeserialize; 5 | import com.fasterxml.jackson.databind.annotation.JsonSerialize; 6 | import org.dhbw.mosbach.ai.cmd.services.serialize.LocalDateTimeDeserializer; 7 | import org.dhbw.mosbach.ai.cmd.services.serialize.LocalDateTimeSerializer; 8 | import org.dhbw.mosbach.ai.cmd.util.HasAccess; 9 | 10 | import javax.persistence.Column; 11 | import javax.persistence.Entity; 12 | import javax.persistence.EnumType; 13 | import javax.persistence.Enumerated; 14 | import javax.persistence.FetchType; 15 | import javax.persistence.GeneratedValue; 16 | import javax.persistence.GenerationType; 17 | import javax.persistence.Id; 18 | import javax.persistence.JoinColumn; 19 | import javax.persistence.ManyToOne; 20 | import javax.persistence.PrePersist; 21 | import javax.persistence.Table; 22 | import java.time.LocalDateTime; 23 | 24 | /** 25 | * Model class for the 'collaborators' table. 26 | * 27 | * @author 3040018 28 | */ 29 | @Entity 30 | @Table(name = "collaborators") 31 | public class Collaborator { 32 | 33 | @Id 34 | @GeneratedValue(strategy = GenerationType.IDENTITY) 35 | @Column 36 | private int id; 37 | 38 | @ManyToOne(fetch = FetchType.EAGER) 39 | @JoinColumn(name = "FK_USERS") 40 | private User user; 41 | 42 | @ManyToOne(fetch = FetchType.EAGER) 43 | @JoinColumn(name = "FK_DOCS") 44 | @JsonIgnore 45 | private Doc doc; 46 | 47 | @Enumerated(EnumType.STRING) 48 | @Column(name = "HAS_ACCESS") 49 | private HasAccess hasAccess; 50 | 51 | @Column(name = "CTIME") 52 | @JsonSerialize(using = LocalDateTimeSerializer.class) 53 | @JsonDeserialize(using = LocalDateTimeDeserializer.class) 54 | private LocalDateTime ctime; 55 | 56 | @PrePersist 57 | private void onInsert() { 58 | this.ctime = LocalDateTime.now(); 59 | } 60 | 61 | public int getId() { 62 | return id; 63 | } 64 | 65 | public void setId(int id) { 66 | this.id = id; 67 | } 68 | 69 | public User getUser() { 70 | return user; 71 | } 72 | 73 | public void setUser(User user) { 74 | this.user = user; 75 | } 76 | 77 | public Doc getDoc() { 78 | return doc; 79 | } 80 | 81 | public void setDoc(Doc doc) { 82 | this.doc = doc; 83 | } 84 | 85 | public HasAccess hasAccess() { 86 | return hasAccess; 87 | } 88 | 89 | public void setHasAccess(HasAccess hasAccess) { 90 | this.hasAccess = hasAccess; 91 | } 92 | 93 | public LocalDateTime getCtime() { 94 | return ctime; 95 | } 96 | 97 | public void setCtime(LocalDateTime ctime) { 98 | this.ctime = ctime; 99 | } 100 | 101 | @Override 102 | public int hashCode() { 103 | final int prime = 31; 104 | int result = 1; 105 | result = prime * result + ((ctime == null) ? 0 : ctime.hashCode()); 106 | result = prime * result + ((doc == null) ? 0 : doc.hashCode()); 107 | result = prime * result + ((hasAccess == null) ? 0 : hasAccess.hashCode()); 108 | result = prime * result + id; 109 | result = prime * result + ((user == null) ? 0 : user.hashCode()); 110 | return result; 111 | } 112 | 113 | @Override 114 | public boolean equals(Object obj) { 115 | if (this == obj) { 116 | return true; 117 | } 118 | if (obj == null) { 119 | return false; 120 | } 121 | if (getClass() != obj.getClass()) { 122 | return false; 123 | } 124 | 125 | Collaborator other = (Collaborator) obj; 126 | if (ctime == null) { 127 | if (other.ctime != null) { 128 | return false; 129 | } 130 | } else if (!ctime.equals(other.ctime)) { 131 | return false; 132 | } 133 | if (doc == null) { 134 | if (other.doc != null) { 135 | return false; 136 | } 137 | } else if (!doc.equals(other.doc)) { 138 | return false; 139 | } 140 | if (hasAccess != other.hasAccess) { 141 | return false; 142 | } 143 | if (id != other.id) { 144 | return false; 145 | } 146 | if (user == null) { 147 | if (other.user != null) { 148 | return false; 149 | } 150 | } else if (!user.equals(other.user)) { 151 | return false; 152 | } 153 | return true; 154 | } 155 | 156 | @Override 157 | public String toString() { 158 | return new StringBuilder() 159 | .append("User: \n") 160 | .append("\tid: " + this.id + "\n") 161 | .append("\tUser: " + this.user.getName() + "\n") 162 | .append("\tDocument: " + this.doc.getId() + "\n") 163 | .append("\tHas access: " + this.hasAccess.getHasAccessString() + "\n") 164 | .append("\tCreated: " + this.ctime + "\n") 165 | .toString(); 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /src/main/java/org/dhbw/mosbach/ai/cmd/model/History.java: -------------------------------------------------------------------------------- 1 | package org.dhbw.mosbach.ai.cmd.model; 2 | 3 | import com.fasterxml.jackson.databind.annotation.JsonDeserialize; 4 | import com.fasterxml.jackson.databind.annotation.JsonSerialize; 5 | import org.dhbw.mosbach.ai.cmd.services.serialize.LocalDateTimeDeserializer; 6 | import org.dhbw.mosbach.ai.cmd.services.serialize.LocalDateTimeSerializer; 7 | 8 | import javax.persistence.Column; 9 | import javax.persistence.Entity; 10 | import javax.persistence.FetchType; 11 | import javax.persistence.GeneratedValue; 12 | import javax.persistence.GenerationType; 13 | import javax.persistence.Id; 14 | import javax.persistence.JoinColumn; 15 | import javax.persistence.ManyToOne; 16 | import javax.persistence.PrePersist; 17 | import javax.persistence.Table; 18 | import java.time.LocalDateTime; 19 | 20 | /** 21 | * Model class for the 'history' table. 22 | * 23 | * @author 3040018 24 | */ 25 | @Entity 26 | @Table(name = "history") 27 | public class History { 28 | 29 | @Id 30 | @GeneratedValue(strategy = GenerationType.IDENTITY) 31 | @Column 32 | private int id; 33 | 34 | @Column(name = "CONTENT") 35 | private String content; 36 | 37 | @Column(name = "HASH") 38 | private String hash; 39 | 40 | @Column(name = "CTIME") 41 | @JsonSerialize(using = LocalDateTimeSerializer.class) 42 | @JsonDeserialize(using = LocalDateTimeDeserializer.class) 43 | private LocalDateTime ctime; 44 | 45 | @ManyToOne(fetch = FetchType.EAGER) 46 | @JoinColumn(name = "FK_DOCS") 47 | private Doc doc; 48 | 49 | @PrePersist 50 | private void onInsert() { 51 | this.ctime = LocalDateTime.now(); 52 | } 53 | 54 | public int getId() { 55 | return id; 56 | } 57 | 58 | public void setId(int id) { 59 | this.id = id; 60 | } 61 | 62 | public String getContent() { 63 | return content; 64 | } 65 | 66 | public void setContent(String content) { 67 | this.content = content; 68 | } 69 | 70 | public String getHash() { 71 | return hash; 72 | } 73 | 74 | public void setHash(String hash) { 75 | this.hash = hash; 76 | } 77 | 78 | public LocalDateTime getCtime() { 79 | return ctime; 80 | } 81 | 82 | public void setCtime(LocalDateTime ctime) { 83 | this.ctime = ctime; 84 | } 85 | 86 | public Doc getDoc() { 87 | return doc; 88 | } 89 | 90 | public void setDoc(Doc doc) { 91 | this.doc = doc; 92 | } 93 | 94 | @Override 95 | public int hashCode() { 96 | final int prime = 31; 97 | int result = 1; 98 | result = prime * result + ((content == null) ? 0 : content.hashCode()); 99 | result = prime * result + ((ctime == null) ? 0 : ctime.hashCode()); 100 | result = prime * result + ((doc == null) ? 0 : doc.hashCode()); 101 | result = prime * result + ((hash == null) ? 0 : hash.hashCode()); 102 | result = prime * result + id; 103 | return result; 104 | } 105 | 106 | @Override 107 | public boolean equals(Object obj) { 108 | if (this == obj) { 109 | return true; 110 | } 111 | if (obj == null) { 112 | return false; 113 | } 114 | if (getClass() != obj.getClass()) { 115 | return false; 116 | } 117 | 118 | History other = (History) obj; 119 | if (content == null) { 120 | if (other.content != null) { 121 | return false; 122 | } 123 | } else if (!content.equals(other.content)) { 124 | return false; 125 | } 126 | if (ctime == null) { 127 | if (other.ctime != null) { 128 | return false; 129 | } 130 | } else if (!ctime.equals(other.ctime)) { 131 | return false; 132 | } 133 | if (doc == null) { 134 | if (other.doc != null) { 135 | return false; 136 | } 137 | } else if (!doc.equals(other.doc)) { 138 | return false; 139 | } 140 | if (hash == null) { 141 | if (other.hash != null) { 142 | return false; 143 | } 144 | } else if (!hash.equals(other.hash)) 145 | return false; 146 | if (id != other.id) { 147 | return false; 148 | } 149 | return true; 150 | } 151 | 152 | @Override 153 | public String toString() { 154 | return new StringBuilder() 155 | .append("History: \n") 156 | .append("\tid: " + this.id + "\n") 157 | .append("\tHash: " + this.hash + "\n") 158 | .append("\tCreated: " + this.ctime + "\n") 159 | .append("\tDocument: " + this.doc.getId() + "\n") 160 | .append("\tContent: " + this.content + "\n") 161 | .toString(); 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /src/main/java/org/dhbw/mosbach/ai/cmd/model/Repo.java: -------------------------------------------------------------------------------- 1 | package org.dhbw.mosbach.ai.cmd.model; 2 | 3 | import javax.persistence.Column; 4 | import javax.persistence.Entity; 5 | import javax.persistence.FetchType; 6 | import javax.persistence.GeneratedValue; 7 | import javax.persistence.GenerationType; 8 | import javax.persistence.Id; 9 | import javax.persistence.JoinColumn; 10 | import javax.persistence.OneToOne; 11 | import javax.persistence.Table; 12 | 13 | /** 14 | * Model class for the 'repos' table. 15 | * 16 | * @author 3040018 17 | */ 18 | @Entity 19 | @Table(name = "repos") 20 | public class Repo { 21 | 22 | @Id 23 | @GeneratedValue(strategy = GenerationType.IDENTITY) 24 | @Column 25 | private int id; 26 | 27 | @OneToOne(fetch = FetchType.EAGER) 28 | @JoinColumn(name = "FK_USERS") 29 | private User owner; 30 | 31 | public int getId() { 32 | return id; 33 | } 34 | 35 | public void setId(int id) { 36 | this.id = id; 37 | } 38 | 39 | public User getOwner() { 40 | return owner; 41 | } 42 | 43 | public void setOwner(User owner) { 44 | this.owner = owner; 45 | } 46 | 47 | @Override 48 | public int hashCode() { 49 | final int prime = 31; 50 | int result = 1; 51 | result = prime * result + id; 52 | result = prime * result + ((owner == null) ? 0 : owner.hashCode()); 53 | return result; 54 | } 55 | 56 | @Override 57 | public boolean equals(Object obj) { 58 | if (this == obj) { 59 | return true; 60 | } 61 | if (obj == null) { 62 | return false; 63 | } 64 | if (getClass() != obj.getClass()) { 65 | return false; 66 | } 67 | 68 | Repo other = (Repo) obj; 69 | if (id != other.id) { 70 | return false; 71 | } 72 | if (owner == null) { 73 | if (other.owner != null) { 74 | return false; 75 | } 76 | } else if (!owner.equals(other.owner)) { 77 | return false; 78 | } 79 | return true; 80 | } 81 | 82 | @Override 83 | public String toString() { 84 | return new StringBuilder() 85 | .append("Repo: \n") 86 | .append("\tid: " + this.id + "\n") 87 | .append("\tOwner: " + this.owner.getName() + "\n") 88 | .toString(); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/main/java/org/dhbw/mosbach/ai/cmd/security/Hashing.java: -------------------------------------------------------------------------------- 1 | package org.dhbw.mosbach.ai.cmd.security; 2 | 3 | import org.dhbw.mosbach.ai.cmd.util.CmdConfig; 4 | import org.mindrot.jbcrypt.BCrypt; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | 8 | import java.math.BigInteger; 9 | import java.nio.charset.StandardCharsets; 10 | import java.security.MessageDigest; 11 | import java.security.NoSuchAlgorithmException; 12 | 13 | /** 14 | * Utility to hash passwords using the BCrypt algorithm 15 | * 16 | * @author 3040018 17 | */ 18 | public class Hashing { 19 | 20 | private static final Logger log = LoggerFactory.getLogger(Hashing.class); 21 | 22 | /** 23 | * Hash a password with the BCrypt algorithm 24 | * 25 | * @param password Given password 26 | * @return The hashed password 27 | */ 28 | public String hashPassword(String password) { 29 | return password != null && !password.isEmpty() ? 30 | BCrypt.hashpw(password, BCrypt.gensalt(12)) : null; 31 | } 32 | 33 | /** 34 | * Check if a password matches the hash from the database 35 | * 36 | * @param password Given password 37 | * @param hash Given password hash 38 | * @return True, if the password matches the hashed password, false otherwise 39 | */ 40 | public boolean checkPassword(String password, String hash) { 41 | return password != null && !password.isEmpty() && 42 | hash != null && !hash.isEmpty() && 43 | BCrypt.checkpw(password, hash); 44 | } 45 | 46 | /** 47 | * Hash the content of a doc for comparison purposes. 48 | * SHA-1 is used since it does not have to be cryptographically secure 49 | * but instead needs to be quite fast. 50 | * 51 | * @param content Given content 52 | * @return A hex representation of the SHA-1 hash of the content, if the 53 | * given String is null or empty it returns the SHA-1 hash for an empty input 54 | */ 55 | public String hashDocContent(String content) { 56 | try { 57 | MessageDigest md = MessageDigest.getInstance(CmdConfig.HASH_DOC_CONTENT); 58 | 59 | if (content != null && !content.isEmpty()) { 60 | return new BigInteger(1, md.digest(content.getBytes(StandardCharsets.UTF_8))).toString(16); 61 | } else { 62 | // SHA-1 hash for input of "" 63 | return "da39a3ee5e6b4b0d3255bfef95601890afd80709"; 64 | } 65 | } catch (NoSuchAlgorithmException e) { 66 | log.error(e.getLocalizedMessage(), e); 67 | return null; 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/main/java/org/dhbw/mosbach/ai/cmd/services/RestEndpoint.java: -------------------------------------------------------------------------------- 1 | package org.dhbw.mosbach.ai.cmd.services; 2 | 3 | /** 4 | * The {@code RestEndpoint} is an abstraction of a REST compliant endpoint 5 | * and facilitates user interactions with the backend of the application. 6 | * 7 | * Implementations of this interface can provide several services which are 8 | * accessed by a distinct resource locator commonly referred to as URL. Each 9 | * service defines a HTTP method that needs to be applied in order to behave 10 | * as the method assumes. For example, a service removing a document could be 11 | * accessed by the {@code DELETE} method of the HTTP protocol. 12 | * 13 | * REST endpoints gain access to the user session by injecting the user's HTTP 14 | * request in form of {@link javax.servlet.http.HttpServletRequest} as a 15 | * contextual injection. The {@link org.dhbw.mosbach.ai.cmd.session.SessionUtil} 16 | * directly provides such injection and besides offers access to the session 17 | * information such as the user object or operations such as invalidating the 18 | * current session. 19 | * 20 | * An implementation of a REST endpoint may provide services which accept 21 | * request models that contain the actual payload of a user request and return 22 | * a response with appropriate information about the processing status of the 23 | * request. In any case, request and response models should not be {@code null}. 24 | * 25 | * @author 6694964 26 | * @version 1.3 27 | * 28 | * @see AuthenticationService 29 | * @see CollaboratorService 30 | * @see DocumentService 31 | * @see org.dhbw.mosbach.ai.cmd.session.SessionUtil 32 | */ 33 | public interface RestEndpoint { 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/org/dhbw/mosbach/ai/cmd/services/RootService.java: -------------------------------------------------------------------------------- 1 | package org.dhbw.mosbach.ai.cmd.services; 2 | 3 | import javax.enterprise.context.ApplicationScoped; 4 | import javax.ws.rs.ApplicationPath; 5 | import javax.ws.rs.core.Application; 6 | 7 | /** 8 | * The {@code RootService} is the main initialization point for the underlying 9 | * REST compliant endpoints by extending the {@link Application} in order to 10 | * create an operational JAX-RS application. It defines the components and supplies 11 | * additional metadata to the underlying services such as the application root 12 | * path used to create the basic path prefix for all underlying services. 13 | * 14 | * The underlying REST endpoints may extend this root service and create own 15 | * services under the root path prefix to offer certain functionality. The root 16 | * service makes no assumptions about the purpose of functionality that can be 17 | * provided by a service. 18 | * 19 | * @author 6694964 20 | * @version 1.3 21 | * 22 | * @see RestEndpoint 23 | * @see AuthenticationService 24 | * @see CollaboratorService 25 | * @see DocumentService 26 | */ 27 | @ApplicationScoped 28 | @ApplicationPath(ServiceEndpoints.PATH_API_ROOT) 29 | public class RootService extends Application implements RestEndpoint { 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/org/dhbw/mosbach/ai/cmd/services/ServiceEndpoints.java: -------------------------------------------------------------------------------- 1 | package org.dhbw.mosbach.ai.cmd.services; 2 | 3 | /** 4 | * This abstract class defines constants for the URL paths referencing the available 5 | * API endpoints. The paths can be hierarchically ordered so that they appear behind 6 | * each other in the URL path. 7 | * 8 | * @author 6694964 9 | * @version 1.3 10 | * 11 | * @see RootService 12 | * @see AuthenticationService 13 | * @see CollaboratorService 14 | * @see DocumentService 15 | */ 16 | public abstract class ServiceEndpoints { 17 | 18 | private ServiceEndpoints() { 19 | } 20 | 21 | /** 22 | * The path prefix for all other endpoints pretended by the {@link RootService} 23 | */ 24 | public static final String PATH_API_ROOT = "/api"; 25 | 26 | /** 27 | * The path prefix for services in the {@link AuthenticationService} 28 | */ 29 | public static final String PATH_AUTHENTICATION = "/authentication"; 30 | 31 | /** 32 | * The path prefix for services in the {@link DocumentService} 33 | */ 34 | public static final String PATH_DOCUMENT = "/document"; 35 | 36 | /** 37 | * The path prefix for services in the {@link CollaboratorService} 38 | */ 39 | public static final String PATH_COLLABORATOR = "/collaborators"; 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/org/dhbw/mosbach/ai/cmd/services/mapper/JsonParseMapper.java: -------------------------------------------------------------------------------- 1 | package org.dhbw.mosbach.ai.cmd.services.mapper; 2 | 3 | import com.fasterxml.jackson.core.JsonLocation; 4 | import com.fasterxml.jackson.core.JsonParseException; 5 | 6 | import javax.json.Json; 7 | import javax.json.JsonObject; 8 | import javax.ws.rs.core.MediaType; 9 | import javax.ws.rs.core.Response; 10 | import javax.ws.rs.ext.ExceptionMapper; 11 | import javax.ws.rs.ext.Provider; 12 | 13 | /** 14 | * @author 6694964 15 | * @version 1.1 16 | */ 17 | @Provider 18 | public class JsonParseMapper implements ExceptionMapper { 19 | 20 | /** 21 | * Map an exception to a {@link Response}. Returning 22 | * {@code null} results in a {@link Response.Status#NO_CONTENT} 23 | * response. Throwing a runtime exception results in a 24 | * {@link Response.Status#INTERNAL_SERVER_ERROR} response. 25 | * 26 | * @param exception the exception to map to a response. 27 | * @return a response mapped from the supplied exception. 28 | */ 29 | @Override 30 | public Response toResponse(JsonParseException exception) { 31 | JsonLocation location = exception.getLocation(); 32 | 33 | JsonObject jsonResponse = MapperJsonResponse.createResponseBuilder(Response.Status.BAD_REQUEST, exception.getMessage()) 34 | .add(MapperJsonResponse.SOURCE_LOCATION, Json.createObjectBuilder() 35 | .add("line", location.getLineNr()) 36 | .add("column", location.getColumnNr()) 37 | .add("byteOffset", location.getByteOffset())) 38 | .build(); 39 | 40 | return Response.status(Response.Status.BAD_REQUEST) 41 | .type(MediaType.APPLICATION_JSON_TYPE) 42 | .entity(jsonResponse.toString()) 43 | .build(); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/org/dhbw/mosbach/ai/cmd/services/mapper/MapperJsonResponse.java: -------------------------------------------------------------------------------- 1 | package org.dhbw.mosbach.ai.cmd.services.mapper; 2 | 3 | import javax.json.Json; 4 | import javax.json.JsonObjectBuilder; 5 | import javax.ws.rs.core.Response; 6 | 7 | /** 8 | * @author 6694964 9 | * @version 1.0 10 | */ 11 | public abstract class MapperJsonResponse { 12 | 13 | private MapperJsonResponse() { 14 | } 15 | 16 | public static final String HTTP_STATUS = "status"; 17 | 18 | public static final String STATUS_CODE = "code"; 19 | 20 | public static final String STATUS_DESCRIPTION = "description"; 21 | 22 | public static final String MESSAGE = "message"; 23 | 24 | public static final String REQUESTED_RESOURCE = "requestedResource"; 25 | 26 | public static final String ALLOWED_METHODS = "allowedMethods"; 27 | 28 | public static final String UNSUPPORTED_CONTENT_TYPE = "unsupportedContentType"; 29 | 30 | public static final String EXPECTED_CONTENT_TYPE = "expectedContentType"; 31 | 32 | public static final String UNAVAILABLE_REPRESENTATION = "unavailableRepresentation"; 33 | 34 | public static final String CAUSE = "cause"; 35 | 36 | public static final String SOURCE_LOCATION = "sourceLocation"; 37 | 38 | public static JsonObjectBuilder createResponseBuilder(Response.Status status, String message) { 39 | return Json.createObjectBuilder() 40 | .add(HTTP_STATUS, Json.createObjectBuilder() 41 | .add(STATUS_CODE, status.getStatusCode()) 42 | .add(STATUS_DESCRIPTION, status.getReasonPhrase())) 43 | .add(MESSAGE, message); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/org/dhbw/mosbach/ai/cmd/services/mapper/MethodNotAllowedMapper.java: -------------------------------------------------------------------------------- 1 | package org.dhbw.mosbach.ai.cmd.services.mapper; 2 | 3 | import javax.json.Json; 4 | import javax.json.JsonArray; 5 | import javax.json.JsonArrayBuilder; 6 | import javax.json.JsonObject; 7 | import javax.ws.rs.HttpMethod; 8 | import javax.ws.rs.NotAllowedException; 9 | import javax.ws.rs.core.HttpHeaders; 10 | import javax.ws.rs.core.MediaType; 11 | import javax.ws.rs.core.Response; 12 | import javax.ws.rs.ext.ExceptionMapper; 13 | import javax.ws.rs.ext.Provider; 14 | import java.util.Set; 15 | 16 | /** 17 | * @author 6694964 18 | * @version 1.0 19 | */ 20 | @Provider 21 | public class MethodNotAllowedMapper implements ExceptionMapper { 22 | 23 | /** 24 | * Map an exception to a {@link Response}. Returning 25 | * {@code null} results in a {@link Response.Status#NO_CONTENT} 26 | * response. Throwing a runtime exception results in a 27 | * {@link Response.Status#INTERNAL_SERVER_ERROR} response. 28 | * 29 | * @param exception the exception to map to a response. 30 | * @return a response mapped from the supplied exception. 31 | */ 32 | @Override 33 | public Response toResponse(NotAllowedException exception) { 34 | Set allowedMethods = exception.getResponse().getAllowedMethods(); 35 | String allowHeader = allowedMethods.stream().reduce((m1, m2) -> m1 + ", " + m2).orElse(HttpMethod.OPTIONS); 36 | 37 | JsonObject jsonResponse = MapperJsonResponse.createResponseBuilder(Response.Status.METHOD_NOT_ALLOWED, exception.getMessage()) 38 | .add(MapperJsonResponse.ALLOWED_METHODS, createJsonArray(allowedMethods)) 39 | .build(); 40 | 41 | return Response.status(Response.Status.METHOD_NOT_ALLOWED) 42 | .type(MediaType.APPLICATION_JSON_TYPE) 43 | .entity(jsonResponse) 44 | .header(HttpHeaders.ALLOW, allowHeader) 45 | .build(); 46 | } 47 | 48 | private JsonArray createJsonArray(Set methods) { 49 | JsonArrayBuilder jsonArray = Json.createArrayBuilder(); 50 | methods.forEach(m -> jsonArray.add(m)); 51 | 52 | return jsonArray.build(); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/org/dhbw/mosbach/ai/cmd/services/mapper/NotAcceptableMapper.java: -------------------------------------------------------------------------------- 1 | package org.dhbw.mosbach.ai.cmd.services.mapper; 2 | 3 | import javax.json.JsonObject; 4 | import javax.servlet.http.HttpServletRequest; 5 | import javax.ws.rs.NotAcceptableException; 6 | import javax.ws.rs.core.Context; 7 | import javax.ws.rs.core.HttpHeaders; 8 | import javax.ws.rs.core.MediaType; 9 | import javax.ws.rs.core.Response; 10 | import javax.ws.rs.core.Variant; 11 | import javax.ws.rs.ext.ExceptionMapper; 12 | import javax.ws.rs.ext.Provider; 13 | 14 | /** 15 | * @author 6694964 16 | * @version 1.0 17 | */ 18 | @Provider 19 | public class NotAcceptableMapper implements ExceptionMapper { 20 | 21 | @Context 22 | private HttpServletRequest request; 23 | 24 | /** 25 | * Map an exception to a {@link Response}. Returning 26 | * {@code null} results in a {@link Response.Status#NO_CONTENT} 27 | * response. Throwing a runtime exception results in a 28 | * {@link Response.Status#INTERNAL_SERVER_ERROR} response. 29 | * 30 | * @param exception the exception to map to a response. 31 | * @return a response mapped from the supplied exception. 32 | */ 33 | @Override 34 | public Response toResponse(NotAcceptableException exception) { 35 | String acceptHeader = request.getHeader(HttpHeaders.ACCEPT); 36 | JsonObject jsonResponse = MapperJsonResponse.createResponseBuilder(Response.Status.NOT_ACCEPTABLE, exception.getMessage()) 37 | .add(MapperJsonResponse.CAUSE, "The accepted representation types are not available at the server: " + acceptHeader) 38 | .add(MapperJsonResponse.UNAVAILABLE_REPRESENTATION, acceptHeader) 39 | .build(); 40 | 41 | return Response.notAcceptable(Variant.mediaTypes(MediaType.APPLICATION_JSON_TYPE).build()) 42 | .type(MediaType.APPLICATION_JSON_TYPE) 43 | .entity(jsonResponse.toString()).type(MediaType.APPLICATION_JSON_TYPE) 44 | .build(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/org/dhbw/mosbach/ai/cmd/services/mapper/NotFoundMapper.java: -------------------------------------------------------------------------------- 1 | package org.dhbw.mosbach.ai.cmd.services.mapper; 2 | 3 | import javax.json.JsonObject; 4 | import javax.ws.rs.NotFoundException; 5 | import javax.ws.rs.core.Context; 6 | import javax.ws.rs.core.MediaType; 7 | import javax.ws.rs.core.Response; 8 | import javax.ws.rs.core.UriInfo; 9 | import javax.ws.rs.ext.ExceptionMapper; 10 | import javax.ws.rs.ext.Provider; 11 | 12 | /** 13 | * @author 6694964 14 | * @version 1.0 15 | */ 16 | @Provider 17 | public class NotFoundMapper implements ExceptionMapper { 18 | 19 | @Context 20 | private UriInfo uriInfo; 21 | 22 | /** 23 | * Map an exception to a {@link Response}. Returning 24 | * {@code null} results in a {@link Response.Status#NO_CONTENT} 25 | * response. Throwing a runtime exception results in a 26 | * {@link Response.Status#INTERNAL_SERVER_ERROR} response. 27 | * 28 | * @param exception the exception to map to a response. 29 | * @return a response mapped from the supplied exception. 30 | */ 31 | @Override 32 | public Response toResponse(NotFoundException exception) { 33 | JsonObject jsonResponse = MapperJsonResponse.createResponseBuilder(Response.Status.NOT_FOUND, exception.getMessage()) 34 | .add(MapperJsonResponse.REQUESTED_RESOURCE, uriInfo.getAbsolutePath().toString()) 35 | .build(); 36 | 37 | return Response.status(Response.Status.NOT_FOUND) 38 | .type(MediaType.APPLICATION_JSON_TYPE) 39 | .entity(jsonResponse.toString()) 40 | .build(); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/org/dhbw/mosbach/ai/cmd/services/mapper/NotSupportedMapper.java: -------------------------------------------------------------------------------- 1 | package org.dhbw.mosbach.ai.cmd.services.mapper; 2 | 3 | import javax.json.JsonObject; 4 | import javax.servlet.http.HttpServletRequest; 5 | import javax.ws.rs.NotSupportedException; 6 | import javax.ws.rs.core.Context; 7 | import javax.ws.rs.core.HttpHeaders; 8 | import javax.ws.rs.core.MediaType; 9 | import javax.ws.rs.core.Response; 10 | import javax.ws.rs.ext.ExceptionMapper; 11 | import javax.ws.rs.ext.Provider; 12 | 13 | /** 14 | * @author 6694964 15 | * @version 1.0 16 | */ 17 | @Provider 18 | public class NotSupportedMapper implements ExceptionMapper { 19 | 20 | @Context 21 | private HttpServletRequest request; 22 | 23 | /** 24 | * Map an exception to a {@link Response}. Returning 25 | * {@code null} results in a {@link Response.Status#NO_CONTENT} 26 | * response. Throwing a runtime exception results in a 27 | * {@link Response.Status#INTERNAL_SERVER_ERROR} response. 28 | * 29 | * @param exception the exception to map to a response. 30 | * @return a response mapped from the supplied exception. 31 | */ 32 | @Override 33 | public Response toResponse(NotSupportedException exception) { 34 | String unsupportedMediaType = request.getHeader(HttpHeaders.CONTENT_TYPE); 35 | if (unsupportedMediaType == null) { 36 | unsupportedMediaType = ""; 37 | } 38 | 39 | JsonObject jsonResponse = MapperJsonResponse.createResponseBuilder(Response.Status.UNSUPPORTED_MEDIA_TYPE, exception.getMessage()) 40 | .add(MapperJsonResponse.UNSUPPORTED_CONTENT_TYPE, unsupportedMediaType) 41 | .add(MapperJsonResponse.EXPECTED_CONTENT_TYPE, MediaType.APPLICATION_JSON) 42 | .build(); 43 | 44 | return Response.status(Response.Status.UNSUPPORTED_MEDIA_TYPE) 45 | .type(MediaType.APPLICATION_JSON_TYPE) 46 | .entity(jsonResponse.toString()) 47 | .build(); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/org/dhbw/mosbach/ai/cmd/services/payload/CollaboratorInsertionModel.java: -------------------------------------------------------------------------------- 1 | package org.dhbw.mosbach.ai.cmd.services.payload; 2 | 3 | import com.fasterxml.jackson.annotation.JsonCreator; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | 6 | /** 7 | * @author 6694964 8 | * @version 1.2 9 | */ 10 | public class CollaboratorInsertionModel implements Payload { 11 | 12 | @JsonProperty(value = PayloadParameters.DOCUMENT_ID, required = true) 13 | private int documentId; 14 | 15 | @JsonProperty(value = PayloadParameters.COLLABORATOR_USERNAME, required = true) 16 | private String collaboratorUsername; 17 | 18 | @JsonCreator 19 | public CollaboratorInsertionModel(@JsonProperty(PayloadParameters.DOCUMENT_ID) int documentId, 20 | @JsonProperty(PayloadParameters.COLLABORATOR_USERNAME) String collaboratorUsername) { 21 | this.documentId = documentId; 22 | this.collaboratorUsername = collaboratorUsername; 23 | } 24 | 25 | public int getDocumentId() { 26 | return documentId; 27 | } 28 | 29 | public String getCollaboratorUsername() { 30 | return collaboratorUsername; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/org/dhbw/mosbach/ai/cmd/services/payload/CollaboratorRemovalModel.java: -------------------------------------------------------------------------------- 1 | package org.dhbw.mosbach.ai.cmd.services.payload; 2 | 3 | import com.fasterxml.jackson.annotation.JsonCreator; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | 6 | /** 7 | * @author 6694964 8 | * @version 1.2 9 | */ 10 | public class CollaboratorRemovalModel implements Payload { 11 | 12 | @JsonProperty(value = PayloadParameters.DOCUMENT_ID, required = true) 13 | private int documentId; 14 | 15 | @JsonProperty(value = PayloadParameters.COLLABORATOR_ID, required = true) 16 | private int collaboratorId; 17 | 18 | @JsonCreator 19 | public CollaboratorRemovalModel(@JsonProperty(PayloadParameters.DOCUMENT_ID) int documentId, 20 | @JsonProperty(PayloadParameters.COLLABORATOR_ID) int collaboratorId) { 21 | this.documentId = documentId; 22 | this.collaboratorId = collaboratorId; 23 | } 24 | 25 | public int getDocumentId() { 26 | return documentId; 27 | } 28 | 29 | public int getCollaboratorId() { 30 | return collaboratorId; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/org/dhbw/mosbach/ai/cmd/services/payload/DocumentAccessModel.java: -------------------------------------------------------------------------------- 1 | package org.dhbw.mosbach.ai.cmd.services.payload; 2 | 3 | import com.fasterxml.jackson.annotation.JsonCreator; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | 6 | /** 7 | * @author 6694964 8 | * @version 1.2 9 | */ 10 | public class DocumentAccessModel implements Payload { 11 | 12 | @JsonProperty(value = PayloadParameters.DOCUMENT_ID, required = true) 13 | private int documentId; 14 | 15 | @JsonCreator 16 | public DocumentAccessModel(@JsonProperty(PayloadParameters.DOCUMENT_ID) int documentId) { 17 | this.documentId = documentId; 18 | } 19 | 20 | public int getDocumentId() { 21 | return documentId; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/org/dhbw/mosbach/ai/cmd/services/payload/DocumentInsertionModel.java: -------------------------------------------------------------------------------- 1 | package org.dhbw.mosbach.ai.cmd.services.payload; 2 | 3 | import com.fasterxml.jackson.annotation.JsonCreator; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | 6 | /** 7 | * @author 6694964 8 | * @version 1.2 9 | */ 10 | public class DocumentInsertionModel implements Payload { 11 | 12 | @JsonProperty(value = PayloadParameters.DOCUMENT_NAME, required = true) 13 | private String documentName; 14 | 15 | @JsonCreator 16 | public DocumentInsertionModel(@JsonProperty(PayloadParameters.DOCUMENT_NAME) String documentName) { 17 | this.documentName = documentName; 18 | } 19 | 20 | public String getDocumentName() { 21 | return documentName; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/org/dhbw/mosbach/ai/cmd/services/payload/DocumentRemovalModel.java: -------------------------------------------------------------------------------- 1 | package org.dhbw.mosbach.ai.cmd.services.payload; 2 | 3 | import com.fasterxml.jackson.annotation.JsonCreator; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | 6 | /** 7 | * @author 6694964 8 | * @version 1.2 9 | */ 10 | public class DocumentRemovalModel implements Payload { 11 | 12 | @JsonProperty(value = PayloadParameters.DOCUMENT_ID, required = true) 13 | private int documentId; 14 | 15 | @JsonCreator 16 | public DocumentRemovalModel(@JsonProperty(PayloadParameters.DOCUMENT_ID) int documentId) { 17 | this.documentId = documentId; 18 | } 19 | 20 | public int getDocumentId() { 21 | return documentId; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/org/dhbw/mosbach/ai/cmd/services/payload/DocumentTransferModel.java: -------------------------------------------------------------------------------- 1 | package org.dhbw.mosbach.ai.cmd.services.payload; 2 | 3 | import com.fasterxml.jackson.annotation.JsonCreator; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | 6 | /** 7 | * @author 6694964 8 | * @version 1.2 9 | */ 10 | public class DocumentTransferModel implements Payload { 11 | 12 | @JsonProperty(value = PayloadParameters.DOCUMENT_ID, required = true) 13 | private int documentId; 14 | 15 | @JsonProperty(value = PayloadParameters.NEW_OWNER_NAME, required = true) 16 | private String newOwnerName; 17 | 18 | @JsonCreator 19 | public DocumentTransferModel(@JsonProperty(PayloadParameters.DOCUMENT_ID) int documentId, 20 | @JsonProperty(PayloadParameters.NEW_OWNER_NAME) String newOwnerName) { 21 | this.documentId = documentId; 22 | this.newOwnerName = newOwnerName; 23 | } 24 | 25 | public int getDocumentId() { 26 | return documentId; 27 | } 28 | 29 | public String getNewOwnerName() { 30 | return newOwnerName; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/org/dhbw/mosbach/ai/cmd/services/payload/LoginModel.java: -------------------------------------------------------------------------------- 1 | package org.dhbw.mosbach.ai.cmd.services.payload; 2 | 3 | import com.fasterxml.jackson.annotation.JsonCreator; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | 6 | /** 7 | * @author 6694964 8 | * @version 1.2 9 | */ 10 | public class LoginModel implements Payload { 11 | 12 | @JsonProperty(value = PayloadParameters.USERNAME, required = true) 13 | private String username; 14 | 15 | @JsonProperty(value = PayloadParameters.PASSWORD, required = true) 16 | private String password; 17 | 18 | @JsonCreator 19 | public LoginModel(@JsonProperty(PayloadParameters.USERNAME) String username, 20 | @JsonProperty(PayloadParameters.PASSWORD) String password) { 21 | this.username = username; 22 | this.password = password; 23 | } 24 | 25 | public String getUsername() { 26 | return username; 27 | } 28 | 29 | public String getPassword() { 30 | return password; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/org/dhbw/mosbach/ai/cmd/services/payload/Payload.java: -------------------------------------------------------------------------------- 1 | package org.dhbw.mosbach.ai.cmd.services.payload; 2 | 3 | /** 4 | * Base payload type for all request models 5 | * 6 | * @author 6694964 7 | * @version 1.0 8 | */ 9 | public interface Payload { 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/org/dhbw/mosbach/ai/cmd/services/payload/PayloadParameters.java: -------------------------------------------------------------------------------- 1 | package org.dhbw.mosbach.ai.cmd.services.payload; 2 | 3 | /** 4 | * @author 6694964 5 | * @version 1.2 6 | */ 7 | public abstract class PayloadParameters { 8 | 9 | private PayloadParameters() { 10 | } 11 | 12 | public static final String USERNAME = "username"; 13 | 14 | public static final String PASSWORD = "password"; 15 | 16 | public static final String EMAIL = "email"; 17 | 18 | public static final String DOCUMENT_NAME = "documentName"; 19 | 20 | public static final String DOCUMENT_ID = "documentId"; 21 | 22 | public static final String COLLABORATOR_ID = "collaboratorId"; 23 | 24 | public static final String COLLABORATOR_USERNAME = "collaboratorUsername"; 25 | 26 | public static final String NEW_OWNER_NAME = "newOwnerName"; 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/org/dhbw/mosbach/ai/cmd/services/payload/RegisterModel.java: -------------------------------------------------------------------------------- 1 | package org.dhbw.mosbach.ai.cmd.services.payload; 2 | 3 | import com.fasterxml.jackson.annotation.JsonCreator; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | 6 | /** 7 | * @author 6694964 8 | * @version 1.2 9 | */ 10 | public class RegisterModel implements Payload { 11 | 12 | @JsonProperty(value = PayloadParameters.USERNAME, required = true) 13 | private String username; 14 | 15 | @JsonProperty(value = PayloadParameters.EMAIL, required = true) 16 | private String email; 17 | 18 | @JsonProperty(value = PayloadParameters.PASSWORD, required = true) 19 | private String password; 20 | 21 | @JsonCreator 22 | public RegisterModel(@JsonProperty(PayloadParameters.USERNAME) String username, 23 | @JsonProperty(PayloadParameters.EMAIL) String email, 24 | @JsonProperty(PayloadParameters.PASSWORD) String password) { 25 | this.username = username; 26 | this.email = email; 27 | this.password = password; 28 | } 29 | 30 | public String getUsername() { 31 | return username; 32 | } 33 | 34 | public String getEmail() { 35 | return email; 36 | } 37 | 38 | public String getPassword() { 39 | return password; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/org/dhbw/mosbach/ai/cmd/services/response/BadRequest.java: -------------------------------------------------------------------------------- 1 | package org.dhbw.mosbach.ai.cmd.services.response; 2 | 3 | import javax.validation.constraints.NotNull; 4 | import javax.ws.rs.core.Response; 5 | 6 | /** 7 | * @author 6694964 8 | * @version 1.1 9 | */ 10 | public class BadRequest extends ResponseObject { 11 | 12 | public BadRequest(String message) { 13 | super(Response.Status.BAD_REQUEST, message); 14 | } 15 | 16 | public BadRequest(@NotNull String format, Object... args) { 17 | this(String.format(format, args)); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/org/dhbw/mosbach/ai/cmd/services/response/DocumentListResponse.java: -------------------------------------------------------------------------------- 1 | package org.dhbw.mosbach.ai.cmd.services.response; 2 | 3 | import com.fasterxml.jackson.annotation.JsonCreator; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | import org.dhbw.mosbach.ai.cmd.services.response.entity.DocumentListEntity; 6 | 7 | import java.util.List; 8 | 9 | /** 10 | * @author 6694964 11 | * @version 1.3 12 | */ 13 | public class DocumentListResponse extends Success { 14 | 15 | @JsonProperty(value = ResponseParameters.DOCUMENT_LIST, required = true) 16 | private final List documents; 17 | 18 | @JsonCreator 19 | public DocumentListResponse(@JsonProperty(ResponseParameters.DOCUMENT_LIST) List documents, 20 | @JsonProperty(ResponseParameters.MESSAGE) String message) { 21 | super(message); 22 | this.documents = documents; 23 | } 24 | 25 | public DocumentListResponse(List documents, String format, Object... args) { 26 | super(format, args); 27 | this.documents = documents; 28 | } 29 | 30 | public List getDocuments() { 31 | return documents; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/org/dhbw/mosbach/ai/cmd/services/response/Forbidden.java: -------------------------------------------------------------------------------- 1 | package org.dhbw.mosbach.ai.cmd.services.response; 2 | 3 | import javax.ws.rs.core.Response; 4 | 5 | /** 6 | * @author 6694964 7 | * @version 1.1 8 | */ 9 | public class Forbidden extends ResponseObject { 10 | 11 | public Forbidden(String message) { 12 | super(Response.Status.FORBIDDEN, message); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/org/dhbw/mosbach/ai/cmd/services/response/InternalServerError.java: -------------------------------------------------------------------------------- 1 | package org.dhbw.mosbach.ai.cmd.services.response; 2 | 3 | import javax.ws.rs.core.Response; 4 | 5 | /** 6 | * @author 6694964 7 | * @version 1.1 8 | */ 9 | public class InternalServerError extends ResponseObject { 10 | 11 | public InternalServerError(String message) { 12 | super(Response.Status.INTERNAL_SERVER_ERROR, message); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/org/dhbw/mosbach/ai/cmd/services/response/LoginUserResponse.java: -------------------------------------------------------------------------------- 1 | package org.dhbw.mosbach.ai.cmd.services.response; 2 | 3 | import com.fasterxml.jackson.annotation.JsonCreator; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | import org.dhbw.mosbach.ai.cmd.model.User; 6 | 7 | /** 8 | * @author 6694964 9 | * @version 1.3 10 | */ 11 | public class LoginUserResponse extends Success { 12 | 13 | @JsonProperty(value = ResponseParameters.USER, required = true) 14 | private final User user; 15 | 16 | @JsonCreator 17 | public LoginUserResponse(@JsonProperty(ResponseParameters.USER) User user, 18 | @JsonProperty(ResponseParameters.MESSAGE) String message) { 19 | super(message); 20 | this.user = user; 21 | } 22 | 23 | public LoginUserResponse(User user, String format, Object... args) { 24 | super(format, args); 25 | this.user = user; 26 | } 27 | 28 | public User getUser() { 29 | return user; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/org/dhbw/mosbach/ai/cmd/services/response/ResponseObject.java: -------------------------------------------------------------------------------- 1 | package org.dhbw.mosbach.ai.cmd.services.response; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | 5 | import javax.ws.rs.core.MediaType; 6 | import javax.ws.rs.core.Response; 7 | 8 | /** 9 | * @author 6694964 10 | * @version 1.1 11 | */ 12 | public class ResponseObject { 13 | 14 | @JsonProperty(value = ResponseParameters.HTTP_STATUS, required = true) 15 | private final Status status; 16 | 17 | @JsonProperty(value = ResponseParameters.MESSAGE, required = true) 18 | private final String message; 19 | 20 | public ResponseObject(Response.Status status, String message) { 21 | this.status = new Status(status); 22 | this.message = message; 23 | } 24 | 25 | public Response buildResponse() { 26 | return Response.status(status.getCode()).type(MediaType.APPLICATION_JSON_TYPE).entity(this).build(); 27 | } 28 | 29 | public Status getStatus() { 30 | return status; 31 | } 32 | 33 | public String getMessage() { 34 | return message; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/org/dhbw/mosbach/ai/cmd/services/response/ResponseParameters.java: -------------------------------------------------------------------------------- 1 | package org.dhbw.mosbach.ai.cmd.services.response; 2 | 3 | /** 4 | * @author 6694964 5 | * @version 1.1 6 | */ 7 | public abstract class ResponseParameters { 8 | 9 | private ResponseParameters() { 10 | } 11 | 12 | public static final String HTTP_STATUS = "status"; 13 | 14 | public static final String STATUS_CODE = "code"; 15 | 16 | public static final String STATUS_DESCRIPTION = "description"; 17 | 18 | public static final String MESSAGE = "message"; 19 | 20 | public static final String DOCUMENT = "document"; 21 | 22 | public static final String DOCUMENT_LIST = "documents"; 23 | 24 | public static final String DOCUMENT_ICON = "icon"; 25 | 26 | public static final String COLLABORATOR_LIST = "collaborators"; 27 | 28 | public static final String USER = "user"; 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/org/dhbw/mosbach/ai/cmd/services/response/Status.java: -------------------------------------------------------------------------------- 1 | package org.dhbw.mosbach.ai.cmd.services.response; 2 | 3 | import com.fasterxml.jackson.annotation.JsonCreator; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | 6 | import javax.ws.rs.core.Response; 7 | 8 | /** 9 | * @author 6694964 10 | * @version 1.1 11 | */ 12 | public class Status { 13 | 14 | @JsonProperty(value = ResponseParameters.STATUS_CODE, required = true) 15 | private final int code; 16 | 17 | @JsonProperty(value = ResponseParameters.STATUS_DESCRIPTION, required = true) 18 | private final String description; 19 | 20 | public Status(Response.Status status) { 21 | this.code = status.getStatusCode(); 22 | this.description = status.getReasonPhrase(); 23 | } 24 | 25 | @JsonCreator 26 | private Status(@JsonProperty(ResponseParameters.STATUS_CODE) int code, 27 | @JsonProperty(ResponseParameters.STATUS_DESCRIPTION) String description) { 28 | this.code = code; 29 | this.description = description; 30 | } 31 | 32 | public int getCode() { 33 | return code; 34 | } 35 | 36 | public String getDescription() { 37 | return description; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/org/dhbw/mosbach/ai/cmd/services/response/Success.java: -------------------------------------------------------------------------------- 1 | package org.dhbw.mosbach.ai.cmd.services.response; 2 | 3 | import javax.ws.rs.core.Response; 4 | 5 | /** 6 | * @author 6694964 7 | * @version 1.1 8 | */ 9 | public class Success extends ResponseObject { 10 | 11 | public Success(String message) { 12 | super(Response.Status.OK, message); 13 | } 14 | 15 | public Success(String format, Object... args) { 16 | this(String.format(format, args)); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/org/dhbw/mosbach/ai/cmd/services/response/Unauthorized.java: -------------------------------------------------------------------------------- 1 | package org.dhbw.mosbach.ai.cmd.services.response; 2 | 3 | import javax.ws.rs.core.Response; 4 | 5 | /** 6 | * @author 6694964 7 | * @version 1.1 8 | */ 9 | public class Unauthorized extends ResponseObject { 10 | 11 | public Unauthorized(String message) { 12 | super(Response.Status.UNAUTHORIZED, message); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/org/dhbw/mosbach/ai/cmd/services/response/entity/DocumentIcon.java: -------------------------------------------------------------------------------- 1 | package org.dhbw.mosbach.ai.cmd.services.response.entity; 2 | 3 | import com.fasterxml.jackson.annotation.JsonCreator; 4 | import com.fasterxml.jackson.annotation.JsonValue; 5 | 6 | /** 7 | * @author 6694964 8 | * @version 1.2 9 | */ 10 | public enum DocumentIcon { 11 | 12 | PERSON("person"), 13 | GROUP("group"); 14 | 15 | private String identifier; 16 | 17 | @JsonCreator 18 | private DocumentIcon(String identifier) { 19 | this.identifier = identifier; 20 | } 21 | 22 | public String getIdentifier() { 23 | return identifier; 24 | } 25 | 26 | @Override 27 | @JsonValue 28 | public String toString() { 29 | return identifier; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/org/dhbw/mosbach/ai/cmd/services/response/entity/DocumentListEntity.java: -------------------------------------------------------------------------------- 1 | package org.dhbw.mosbach.ai.cmd.services.response.entity; 2 | 3 | import com.fasterxml.jackson.annotation.JsonCreator; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | import org.dhbw.mosbach.ai.cmd.model.Collaborator; 6 | import org.dhbw.mosbach.ai.cmd.model.Doc; 7 | import org.dhbw.mosbach.ai.cmd.services.response.ResponseParameters; 8 | 9 | import java.util.List; 10 | 11 | /** 12 | * @author 6694964 13 | * @version 1.3 14 | */ 15 | public class DocumentListEntity { 16 | 17 | @JsonProperty(value = ResponseParameters.DOCUMENT_ICON, required = true) 18 | private final DocumentIcon icon; 19 | 20 | @JsonProperty(value = ResponseParameters.DOCUMENT, required = true) 21 | private final Doc document; 22 | 23 | @JsonProperty(value = ResponseParameters.COLLABORATOR_LIST, required = true) 24 | private final List collaborators; 25 | 26 | @JsonCreator 27 | public DocumentListEntity(@JsonProperty(ResponseParameters.DOCUMENT_ICON) DocumentIcon icon, 28 | @JsonProperty(ResponseParameters.DOCUMENT) Doc document, 29 | @JsonProperty(ResponseParameters.COLLABORATOR_LIST) List collaborators) { 30 | this.icon = icon; 31 | this.document = document; 32 | this.collaborators = collaborators; 33 | } 34 | 35 | public DocumentIcon getIcon() { 36 | return icon; 37 | } 38 | 39 | public Doc getDocument() { 40 | return document; 41 | } 42 | 43 | public List getCollaborators() { 44 | return collaborators; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/org/dhbw/mosbach/ai/cmd/services/serialize/LocalDateTimeDeserializer.java: -------------------------------------------------------------------------------- 1 | package org.dhbw.mosbach.ai.cmd.services.serialize; 2 | 3 | import com.fasterxml.jackson.core.JsonParser; 4 | import com.fasterxml.jackson.core.JsonProcessingException; 5 | import com.fasterxml.jackson.databind.DeserializationContext; 6 | import com.fasterxml.jackson.databind.JsonDeserializer; 7 | 8 | import java.io.IOException; 9 | import java.time.LocalDateTime; 10 | 11 | /** 12 | * @author 6694964 13 | * @version 1.1 14 | */ 15 | public class LocalDateTimeDeserializer extends JsonDeserializer { 16 | 17 | private static final long serialVersionUID = 1470228654468269002L; 18 | 19 | /** 20 | * Method that can be called to ask implementation to deserialize 21 | * JSON content into the value type this serializer handles. 22 | * Returned instance is to be constructed by method itself. 23 | *

24 | * Pre-condition for this method is that the parser points to the 25 | * first event that is part of value to deserializer (and which 26 | * is never JSON 'null' literal, more on this below): for simple 27 | * types it may be the only value; and for structured types the 28 | * Object start marker or a FIELD_NAME. 29 | *

30 | *

31 | * The two possible input conditions for structured types result 32 | * from polymorphism via fields. In the ordinary case, Jackson 33 | * calls this method when it has encountered an OBJECT_START, 34 | * and the method implementation must advance to the next token to 35 | * see the first field name. If the application configures 36 | * polymorphism via a field, then the object looks like the following. 37 | *

38 |      *      {
39 |      *          "@class": "class name",
40 |      *          ...
41 |      *      }
42 |      *  
43 | * Jackson consumes the two tokens (the @class field name 44 | * and its value) in order to learn the class and select the deserializer. 45 | * Thus, the stream is pointing to the FIELD_NAME for the first field 46 | * after the @class. Thus, if you want your method to work correctly 47 | * both with and without polymorphism, you must begin your method with: 48 | *
49 |      *       if (p.getCurrentToken() == JsonToken.START_OBJECT) {
50 |      *         p.nextToken();
51 |      *       }
52 |      *  
53 | * This results in the stream pointing to the field name, so that 54 | * the two conditions align. 55 | *

56 | * Post-condition is that the parser will point to the last 57 | * event that is part of deserialized value (or in case deserialization 58 | * fails, event that was not recognized or usable, which may be 59 | * the same event as the one it pointed to upon call). 60 | *

61 | * Note that this method is never called for JSON null literal, 62 | * and thus deserializers need (and should) not check for it. 63 | * 64 | * @param jsonParser Parsed used for reading JSON content 65 | * @param context Context that can be used to access information about 66 | * this deserialization activity. 67 | * @return Deserialized value 68 | */ 69 | @Override 70 | public LocalDateTime deserialize(JsonParser jsonParser, DeserializationContext context) throws IOException, JsonProcessingException { 71 | return LocalDateTime.parse(jsonParser.readValueAs(String.class)); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/main/java/org/dhbw/mosbach/ai/cmd/services/serialize/LocalDateTimeSerializer.java: -------------------------------------------------------------------------------- 1 | package org.dhbw.mosbach.ai.cmd.services.serialize; 2 | 3 | import com.fasterxml.jackson.core.JsonGenerator; 4 | import com.fasterxml.jackson.databind.JsonSerializer; 5 | import com.fasterxml.jackson.databind.SerializerProvider; 6 | import org.dhbw.mosbach.ai.cmd.util.CmdConfig; 7 | 8 | import java.io.IOException; 9 | import java.time.LocalDateTime; 10 | 11 | /** 12 | * @author 6694964 13 | * @version 1.1 14 | */ 15 | public class LocalDateTimeSerializer extends JsonSerializer { 16 | 17 | private static final long serialVersionUID = -7292425596342149264L; 18 | 19 | /** 20 | * Method that can be called to ask implementation to serialize 21 | * values of type this serializer handles. 22 | * 23 | * @param date Value to serialize; can not be null. 24 | * @param gen Generator used to output resulting Json content 25 | * @param serializers Provider that can be used to get serializers for 26 | */ 27 | @Override 28 | public void serialize(LocalDateTime date, JsonGenerator gen, SerializerProvider serializers) throws IOException { 29 | gen.writeString(date.format(CmdConfig.API_DATE_FORMATTER)); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/org/dhbw/mosbach/ai/cmd/services/validation/ModelValidation.java: -------------------------------------------------------------------------------- 1 | package org.dhbw.mosbach.ai.cmd.services.validation; 2 | 3 | import org.dhbw.mosbach.ai.cmd.services.payload.Payload; 4 | 5 | import javax.validation.constraints.NotNull; 6 | 7 | /** 8 | * The {@code ModelValidation} is the base contract for all classes offering validation 9 | * functionalities to the REST endpoint services. It defines a method for validating 10 | * the payload of an incoming API request for data integrity, coherence and consistency. 11 | * 12 | * The implementing method returns a {@link ValidationResult} which contains the result 13 | * of the validation by specifying the status and an explanatory message about the 14 | * circumstances if and why the validation succeeded or failed. The message is then 15 | * redirected to the user via the service response who can correct his request according 16 | * to these requirements. 17 | * 18 | * The type parameter {@link T} represents the model class which is utilized by the 19 | * JAX-RS library to carry the request payload within its attributes. The service passes 20 | * the payload as is to the validation method in order to investigate its contents before 21 | * using the values provided in the request. 22 | * 23 | * @param the model type used as payload to the service 24 | * 25 | * @author 6694964 26 | * @version 1.1 27 | * 28 | * @see ValidationResult 29 | * @see Payload 30 | */ 31 | public interface ModelValidation { 32 | 33 | /** 34 | * Checks the passed payload of the respective request model type for validity. The 35 | * validation may include examinations for existence of resources such as documents 36 | * and users, the presence of privileges to access a resource or perform a specific 37 | * operation and the check for certain constraints required for this operation. 38 | * 39 | * The {@code validate} method follows the principle of error handling descending from 40 | * the Go programming language. Accordingly, a potential error in the validation is 41 | * returned as an unsuccessful {@link ValidationResult} using a sufficient error message 42 | * rather than throwing an exception which requires additional mappers in the services 43 | * to handle those exceptions. A successful validation is returned in the same manner 44 | * as a successful validation result. 45 | * 46 | * In any case, the method should accept a non-null payload and return a non-null 47 | * validation result. In case the payload should be {@code null} this method should 48 | * answer with an unsuccessful validation result containing an internal server error 49 | * response. 50 | * 51 | * @param payload the provided non-null payload to the service 52 | * @return a non-null validation result indicating if the validation was successful 53 | * or not 54 | */ 55 | @NotNull 56 | public ValidationResult validate(@NotNull T payload); 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/org/dhbw/mosbach/ai/cmd/services/validation/basic/BasicDocumentValidation.java: -------------------------------------------------------------------------------- 1 | package org.dhbw.mosbach.ai.cmd.services.validation.basic; 2 | 3 | import org.dhbw.mosbach.ai.cmd.db.DocDao; 4 | import org.dhbw.mosbach.ai.cmd.model.Doc; 5 | import org.dhbw.mosbach.ai.cmd.model.User; 6 | import org.dhbw.mosbach.ai.cmd.services.response.BadRequest; 7 | import org.dhbw.mosbach.ai.cmd.services.validation.ValidationResult; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | 11 | import javax.enterprise.context.RequestScoped; 12 | import javax.inject.Inject; 13 | 14 | /** 15 | * @author 6694964 16 | * @version 1.1 17 | */ 18 | @RequestScoped 19 | public class BasicDocumentValidation { 20 | 21 | /** 22 | * Private logging instance to log validation operations 23 | */ 24 | private static final Logger log = LoggerFactory.getLogger(BasicDocumentValidation.class); 25 | 26 | @Inject 27 | private DocDao docDao; 28 | 29 | private Doc foundDocument; 30 | 31 | public ValidationResult checkDocumentExists(int documentId) { 32 | Doc document = docDao.getDoc(documentId); 33 | if (document == null) { 34 | return ValidationResult.response(new BadRequest("Document with id '%d' does not exist.", documentId)); 35 | } 36 | 37 | foundDocument = document; 38 | return ValidationResult.success("Document with id '%d' exists.", documentId); 39 | } 40 | 41 | public ValidationResult checkUserIsDocumentOwner(Doc document, User user) { 42 | if (user.getId() != document.getRepo().getOwner().getId()) { 43 | return ValidationResult.response(new BadRequest("Applied user is not the document owner")); 44 | } 45 | 46 | return ValidationResult.success("Applied user is the document owner"); 47 | } 48 | 49 | public Doc getFoundDocument() { 50 | Doc document = foundDocument; 51 | foundDocument = null; 52 | return document; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/org/dhbw/mosbach/ai/cmd/services/validation/basic/BasicFieldValidation.java: -------------------------------------------------------------------------------- 1 | package org.dhbw.mosbach.ai.cmd.services.validation.basic; 2 | 3 | import org.dhbw.mosbach.ai.cmd.services.response.BadRequest; 4 | import org.dhbw.mosbach.ai.cmd.services.validation.ValidationResult; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | 8 | import javax.enterprise.context.RequestScoped; 9 | import javax.validation.constraints.NotNull; 10 | 11 | /** 12 | * @author 6694964 13 | * @version 1.1 14 | */ 15 | @RequestScoped 16 | public class BasicFieldValidation { 17 | 18 | /** 19 | * Private logging instance to log validation operations 20 | */ 21 | private static final Logger log = LoggerFactory.getLogger(BasicFieldValidation.class); 22 | 23 | @NotNull 24 | public ValidationResult checkSpecified(@NotNull String fieldName, String fieldValue) { 25 | if (fieldValue == null || fieldValue.isEmpty()) { 26 | return ValidationResult.response(new BadRequest("%s was not specified", capitalize(fieldName))); 27 | } 28 | 29 | return ValidationResult.success("Field '%s' has been specified", fieldName); 30 | } 31 | 32 | private String capitalize(String field) { 33 | if (field == null || field.length() == 0) { 34 | return field; 35 | } 36 | 37 | return Character.toUpperCase(field.charAt(0)) + (field.length() > 1 ? field.substring(1) : ""); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/org/dhbw/mosbach/ai/cmd/services/validation/basic/BasicUserValidation.java: -------------------------------------------------------------------------------- 1 | package org.dhbw.mosbach.ai.cmd.services.validation.basic; 2 | 3 | import org.dhbw.mosbach.ai.cmd.db.UserDao; 4 | import org.dhbw.mosbach.ai.cmd.model.User; 5 | import org.dhbw.mosbach.ai.cmd.services.response.BadRequest; 6 | import org.dhbw.mosbach.ai.cmd.services.validation.ValidationResult; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | 10 | import javax.enterprise.context.RequestScoped; 11 | import javax.inject.Inject; 12 | import javax.validation.constraints.NotNull; 13 | 14 | /** 15 | * The {@code BasisUserValidation} offers basic validation functionality for usernames 16 | * by checking if a specified username is defined in the database. This is accomplished 17 | * by injecting database access objects (DAOs) into this managed class to allow access 18 | * to database lookup operations. 19 | * 20 | * Considering that loading a user from the database is an expensive operation the loaded 21 | * user is cached within the {@code foundUser} attribute. This way the service calling 22 | * validation methods contained in this class does not need to load the user again, but 23 | * can fetch the cached user via the getter method {@link BasicUserValidation#getFoundUser}. 24 | * Accordingly, there is no duplication of database operations increasing performance. 25 | * 26 | * If the cached user is accessed via the mentioned getter method the cache attribute is 27 | * set back to {@code null} to clear the cache for next checks and to signalize other 28 | * services using these validation methods that there has been no user yet that was loaded 29 | * from the database. 30 | * 31 | * @author 6694964 32 | * @version 1.1 33 | * 34 | * @see UserDao 35 | * @see User 36 | * @see org.dhbw.mosbach.ai.cmd.services.validation.ModelValidation 37 | */ 38 | @RequestScoped 39 | public class BasicUserValidation { 40 | 41 | /** 42 | * Private logging instance to log validation operations 43 | */ 44 | private static final Logger log = LoggerFactory.getLogger(BasicUserValidation.class); 45 | 46 | /** 47 | * Injected field for loading user objects from the database 48 | */ 49 | @Inject 50 | private UserDao userDao; 51 | 52 | /** 53 | * Cached instance of the last loaded user to be conveyed by a service 54 | * in order to reduce database operations. Once accessed, this user 55 | * attribute is cleared back to {@code null}. 56 | */ 57 | private User foundUser; 58 | 59 | /** 60 | * Checks if a user with the supplied username exists in the database by using 61 | * lookup operations provided by the {@link UserDao}. If the user exists in the 62 | * database the loaded user instance is cached in the {@code foundUser} attribute 63 | * and can be fetched using the getter method. A successful validation result is 64 | * returned. 65 | * 66 | * If the user does not exist, the validation method returns an unsuccessful 67 | * validation result. 68 | * 69 | * @param username the username for the user to be checked for existence 70 | * @return a successful validation result if the user exists, otherwise an 71 | * unsuccessful validation result. 72 | */ 73 | @NotNull 74 | public ValidationResult checkUserExists(String username) { 75 | User user = userDao.getUserByName(username); 76 | if (user == null) { 77 | return ValidationResult.response(new BadRequest("User '%s' does not exist", username)); 78 | } 79 | 80 | foundUser = user; 81 | return ValidationResult.success("Username '%s' already exists", username); 82 | } 83 | 84 | /** 85 | * Fetches the cached user attribute after checking for existence of a specified 86 | * username using {@link BasicUserValidation#checkUserExists(String)}. If the 87 | * method has found a concrete user this user object is returned. If the method 88 | * was not invoked before or if no user was found it returns {@code null}. 89 | * 90 | * @return the loaded user object from the database if one was found, null otherwise. 91 | */ 92 | public User getFoundUser() { 93 | User user = foundUser; 94 | foundUser = null; 95 | return user; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/main/java/org/dhbw/mosbach/ai/cmd/services/validation/document/DocumentAccessValidation.java: -------------------------------------------------------------------------------- 1 | package org.dhbw.mosbach.ai.cmd.services.validation.document; 2 | 3 | import org.dhbw.mosbach.ai.cmd.model.Doc; 4 | import org.dhbw.mosbach.ai.cmd.services.payload.DocumentAccessModel; 5 | import org.dhbw.mosbach.ai.cmd.services.payload.Payload; 6 | import org.dhbw.mosbach.ai.cmd.services.response.InternalServerError; 7 | import org.dhbw.mosbach.ai.cmd.services.validation.ModelValidation; 8 | import org.dhbw.mosbach.ai.cmd.services.validation.ValidationResult; 9 | import org.dhbw.mosbach.ai.cmd.services.validation.basic.BasicDocumentValidation; 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | 13 | import javax.enterprise.context.RequestScoped; 14 | import javax.inject.Inject; 15 | import javax.validation.constraints.NotNull; 16 | 17 | /** 18 | * @author 6694964 19 | * @version 1.1 20 | */ 21 | @RequestScoped 22 | public class DocumentAccessValidation implements ModelValidation { 23 | 24 | /** 25 | * Private logging instance to log validation operations 26 | */ 27 | private static final Logger log = LoggerFactory.getLogger(DocumentAccessValidation.class); 28 | 29 | @Inject 30 | private BasicDocumentValidation basicDocumentValidation; 31 | 32 | private Doc foundDocument; 33 | 34 | /** 35 | * Checks the passed payload of the respective document access model type for 36 | * validity. The validation includes: 37 | * 38 | *

    39 | *
  • check for the existence of the document pointed to by the document id.
  • 40 | *
41 | * 42 | * The actual investigation of document access occurs in the document access service 43 | * itself because it is the task of the service rather than the validation to examine 44 | * whether the user has access to a specific document. This validation only takes care 45 | * of the request which contains the document id. 46 | * 47 | * The {@code validate} method follows the principle of error handling descending from 48 | * the Go programming language. Accordingly, a potential error in the validation is 49 | * returned as an unsuccessful {@link ValidationResult} using a sufficient error message 50 | * rather than throwing an exception which requires additional mappers in the services 51 | * to handle those exceptions. A successful validation is returned in the same manner 52 | * as a successful validation result. 53 | * 54 | * In any case, the method should accept a non-null payload and return a non-null 55 | * validation result. In case the payload should be {@code null} this method should 56 | * answer with an unsuccessful validation result containing an internal server error 57 | * response. 58 | * 59 | * @param model the provided non-null document access model from the document service 60 | * @return a non-null validation result indicating if the document access validation 61 | * was successful or not 62 | * @see ModelValidation#validate(Payload) 63 | * @see org.dhbw.mosbach.ai.cmd.services.DocumentService 64 | */ 65 | @Override 66 | @NotNull 67 | public ValidationResult validate(@NotNull DocumentAccessModel model) { 68 | if (model == null) { 69 | return ValidationResult.response(new InternalServerError("DocumentAccessModel is null")); 70 | } 71 | 72 | final int documentId = model.getDocumentId(); 73 | 74 | final ValidationResult documentExistenceCheck = basicDocumentValidation.checkDocumentExists(documentId); 75 | if (documentExistenceCheck.isInvalid()) { 76 | return documentExistenceCheck; 77 | } 78 | 79 | foundDocument = basicDocumentValidation.getFoundDocument(); 80 | 81 | return ValidationResult.success("Access to this document may be checked"); 82 | } 83 | 84 | public Doc getFoundDocument() { 85 | return foundDocument; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/main/java/org/dhbw/mosbach/ai/cmd/util/CmdConfig.java: -------------------------------------------------------------------------------- 1 | package org.dhbw.mosbach.ai.cmd.util; 2 | 3 | import java.time.format.DateTimeFormatter; 4 | 5 | /** 6 | * Configuration for certain globally reused variables 7 | * 8 | * @author 3040018 9 | */ 10 | public abstract class CmdConfig { 11 | 12 | private CmdConfig() { 13 | } 14 | 15 | /** 16 | * Session attribute name and form parameter name for storing user name 17 | */ 18 | public static final String SESSION_USERNAME = "username"; 19 | 20 | /** 21 | * Session attribute name for storing user id 22 | */ 23 | public static final String SESSION_USERID = "userId"; 24 | 25 | /** 26 | * Session attribute to show if a user is logged in or not 27 | */ 28 | public static final String SESSION_IS_LOGGED_IN = "isLoggedIn"; 29 | 30 | /** 31 | * Session attribute to store a user object 32 | */ 33 | public static final String SESSION_USER = "user"; 34 | 35 | /** 36 | * Unit name for JPA / JTA 37 | */ 38 | public static final String JPA_UNIT_NAME = "cmd"; 39 | 40 | /** 41 | * Hashing algorithm for the doc content and history database table 42 | */ 43 | public static final String HASH_DOC_CONTENT = "SHA-1"; 44 | 45 | /** 46 | * Date Formatter used to format dates in date fields of API responses 47 | */ 48 | public static final DateTimeFormatter API_DATE_FORMATTER = DateTimeFormatter.ISO_DATE_TIME; 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/org/dhbw/mosbach/ai/cmd/util/HasAccess.java: -------------------------------------------------------------------------------- 1 | package org.dhbw.mosbach.ai.cmd.util; 2 | 3 | import com.fasterxml.jackson.annotation.JsonValue; 4 | 5 | /** 6 | * Utility enum to set if a collaborator has access to a doc 7 | * 8 | * @author spa102716 9 | */ 10 | public enum HasAccess { 11 | 12 | Y("Y"), 13 | N("N"); 14 | 15 | private String hasAccessString; 16 | 17 | private HasAccess(String hasAccessString) { 18 | this.hasAccessString = hasAccessString; 19 | } 20 | 21 | public String getHasAccessString() { 22 | return this.hasAccessString; 23 | } 24 | 25 | @JsonValue 26 | public boolean hasAccess() { 27 | return this.equals(Y); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/org/dhbw/mosbach/ai/cmd/util/MatchTools.java: -------------------------------------------------------------------------------- 1 | package org.dhbw.mosbach.ai.cmd.util; 2 | 3 | import java.util.Objects; 4 | 5 | public final class MatchTools { 6 | 7 | private MatchTools() { 8 | } 9 | 10 | public static String findDisparateMatches(String regex, String potion) { 11 | return Objects.requireNonNull(potion).replaceAll(regex, ""); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/org/dhbw/mosbach/ai/cmd/util/MessageType.java: -------------------------------------------------------------------------------- 1 | package org.dhbw.mosbach.ai.cmd.util; 2 | 3 | /** 4 | * Message types send via web sockets 5 | * 6 | * @author 3040018 7 | */ 8 | public enum MessageType { 9 | 10 | Insert("Insert"), 11 | Delete("Delete"), 12 | UserJoined("UserJoined"), 13 | UserLeft("UserLeft"), 14 | ContentInit("ContentInit"), 15 | DocumentTitle("DocumentTitle"), 16 | UsersInit("UsersInit"), 17 | ChatMessage("ChatMessage"), 18 | WrongDocId("WrongDocId"); 19 | 20 | private String messageType; 21 | 22 | private MessageType(String messageType) { 23 | this.messageType = messageType; 24 | } 25 | 26 | @Override 27 | public String toString() { 28 | return this.messageType; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/org/dhbw/mosbach/ai/cmd/websocket/MessageDecoder.java: -------------------------------------------------------------------------------- 1 | package org.dhbw.mosbach.ai.cmd.websocket; 2 | 3 | import org.dhbw.mosbach.ai.cmd.crdt.Message; 4 | import org.dhbw.mosbach.ai.cmd.util.MessageType; 5 | 6 | import javax.json.Json; 7 | import javax.json.JsonObject; 8 | import javax.json.JsonReader; 9 | import javax.websocket.DecodeException; 10 | import javax.websocket.Decoder; 11 | import javax.websocket.EndpointConfig; 12 | import java.io.StringReader; 13 | 14 | /** 15 | * Decoder class for messages to use in the web socket endpoint 16 | * 17 | * @author 3040018 18 | */ 19 | public class MessageDecoder implements Decoder.Text { 20 | 21 | @Override 22 | public void init(EndpointConfig config) { 23 | } 24 | 25 | @Override 26 | public void destroy() { 27 | } 28 | 29 | @Override 30 | public Message decode(String jsonMessage) throws DecodeException { 31 | JsonObject jsonMsg = Json.createReader(new StringReader(jsonMessage)).readObject(); 32 | 33 | Message message = new Message(); 34 | 35 | message.setUserId(jsonMsg.getInt("userId")); 36 | message.setDocId(jsonMsg.getInt("docId")); 37 | message.setCursorPos(jsonMsg.getInt("cursorPos")); 38 | message.setDocState(jsonMsg.getInt("docState")); 39 | message.setMsg(jsonMsg.getString("msg") == null ? "" : jsonMsg.getString("msg")); 40 | message.setMessageType(MessageType.valueOf(jsonMsg.getString("messageType"))); 41 | 42 | return message; 43 | } 44 | 45 | @Override 46 | public boolean willDecode(String jsonMessage) { 47 | try { 48 | Json.createReader(new StringReader(jsonMessage)).readObject(); 49 | return true; 50 | } catch (Exception e) { 51 | return false; 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/org/dhbw/mosbach/ai/cmd/websocket/MessageEncoder.java: -------------------------------------------------------------------------------- 1 | package org.dhbw.mosbach.ai.cmd.websocket; 2 | 3 | import org.dhbw.mosbach.ai.cmd.crdt.Message; 4 | 5 | import javax.json.Json; 6 | import javax.json.JsonObject; 7 | import javax.websocket.EncodeException; 8 | import javax.websocket.Encoder; 9 | import javax.websocket.EndpointConfig; 10 | 11 | /** 12 | * Encoder class for messages to use in the web socket endpoint 13 | * 14 | * @author 3040018 15 | */ 16 | public class MessageEncoder implements Encoder.Text { 17 | 18 | @Override 19 | public void init(EndpointConfig config) { 20 | } 21 | 22 | @Override 23 | public void destroy() { 24 | } 25 | 26 | @Override 27 | public String encode(Message message) throws EncodeException { 28 | JsonObject jsonObject = Json.createObjectBuilder() 29 | .add("userId", message.getUserId()) 30 | .add("docId", message.getDocId()) 31 | .add("cursorPos", message.getCursorPos()) 32 | .add("docState", message.getDocState()) 33 | .add("msg", message.getMsg() == null ? "" : message.getMsg()) 34 | .add("messageType", message.getMessageType().toString()) 35 | .build(); 36 | 37 | return jsonObject.toString(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/beans.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/log4j.properties: -------------------------------------------------------------------------------- 1 | log4j.rootLogger=DEBUG,stdout 2 | log4j.logger.com.endeca=INFO 3 | # Logger for crawl metrics 4 | log4j.logger.com.endeca.itl.web.metrics=INFO 5 | log4j.appender.stdout=org.apache.log4j.ConsoleAppender 6 | log4j.appender.stdout.layout=org.apache.log4j.PatternLayout 7 | log4j.appender.stdout.layout.ConversionPattern=%p\t%d{ISO8601}\t%r\t%c\t[%t]\t%m%n 8 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/persistence.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | org.hibernate.jpa.HibernatePersistenceProvider 10 | java:jboss/datasources/cmdDS 11 | org.dhbw.mosbach.ai.cmd.model.Collaborator 12 | org.dhbw.mosbach.ai.cmd.model.Doc 13 | org.dhbw.mosbach.ai.cmd.model.History 14 | org.dhbw.mosbach.ai.cmd.model.Repo 15 | org.dhbw.mosbach.ai.cmd.model.User 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/main/webapp/.gitkeep: -------------------------------------------------------------------------------- 1 | Keep this file here to keep the directory being tracked by Git. 2 | 3 | This directory is required by Vue Frontend builds that are run by Maven because 4 | the output files will be copied here to be packed into the WAR file for Wildfly. 5 | -------------------------------------------------------------------------------- /src/test/java/org/dhbw/mosbach/ai/cmd/crdt/ActiveDocumentTest.java: -------------------------------------------------------------------------------- 1 | package org.dhbw.mosbach.ai.cmd.crdt; 2 | 3 | import org.dhbw.mosbach.ai.cmd.model.Doc; 4 | import org.junit.Before; 5 | import org.junit.Test; 6 | 7 | import static org.junit.Assert.assertEquals; 8 | 9 | /** 10 | * Testing the ActiveDocument class 11 | * 12 | * @author 3040018 13 | */ 14 | public class ActiveDocumentTest { 15 | 16 | private ActiveDocument activeDocument; 17 | private Message msg; 18 | 19 | @Before 20 | public void init() { 21 | Doc doc = new Doc(); 22 | doc.setId(1); 23 | doc.setContent("Test content"); 24 | 25 | msg = new Message(); 26 | msg.setCursorPos(2); 27 | msg.setMsg("abc"); 28 | 29 | activeDocument = new ActiveDocument(); 30 | activeDocument.setDoc(doc); 31 | activeDocument.setState(0); 32 | } 33 | 34 | @Test 35 | public void testInsert() { 36 | activeDocument.insert(msg); 37 | 38 | assertEquals(1, activeDocument.getState()); 39 | assertEquals("Teabcst content", activeDocument.getDoc().getContent()); 40 | } 41 | 42 | @Test 43 | public void testDel() { 44 | activeDocument.del(msg); 45 | 46 | assertEquals(1, activeDocument.getState()); 47 | assertEquals("Tecontent", activeDocument.getDoc().getContent()); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/test/java/org/dhbw/mosbach/ai/cmd/crdt/MessageBrokerTest.java: -------------------------------------------------------------------------------- 1 | package org.dhbw.mosbach.ai.cmd.crdt; 2 | 3 | import org.dhbw.mosbach.ai.cmd.model.Doc; 4 | import org.dhbw.mosbach.ai.cmd.util.MessageType; 5 | import org.junit.Before; 6 | import org.junit.Test; 7 | 8 | import static org.junit.Assert.assertEquals; 9 | 10 | /** 11 | * Testing the MessageBroker class 12 | * 13 | * @author 3040018 14 | */ 15 | public class MessageBrokerTest { 16 | 17 | private MessageBroker messageBroker; 18 | private ActiveDocument activeDocument; 19 | private Message msg; 20 | 21 | @Before 22 | public void init() { 23 | messageBroker = new MessageBroker(); 24 | 25 | Doc doc = new Doc(); 26 | doc.setId(1); 27 | doc.setContent("Test content"); 28 | 29 | activeDocument = new ActiveDocument(); 30 | activeDocument.setDoc(doc); 31 | activeDocument.setState(0); 32 | 33 | msg = new Message(); 34 | msg.setCursorPos(2); 35 | msg.setMsg("abc"); 36 | } 37 | 38 | @Test 39 | public void testTransformInsert() { 40 | msg.setMessageType(MessageType.Insert); 41 | 42 | messageBroker.transform(msg, activeDocument); 43 | 44 | assertEquals(1, activeDocument.getState()); 45 | assertEquals("Teabcst content", activeDocument.getDoc().getContent()); 46 | } 47 | 48 | @Test 49 | public void testTransformDel() { 50 | msg.setMessageType(MessageType.Delete); 51 | 52 | messageBroker.transform(msg, activeDocument); 53 | 54 | assertEquals(1, activeDocument.getState()); 55 | assertEquals("Tecontent", activeDocument.getDoc().getContent()); 56 | } 57 | 58 | @Test 59 | public void testTransformNoTransform() { 60 | msg.setMessageType(MessageType.ChatMessage); 61 | 62 | messageBroker.transform(msg, activeDocument); 63 | 64 | assertEquals(0, activeDocument.getState()); 65 | assertEquals("Test content", activeDocument.getDoc().getContent()); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/test/java/org/dhbw/mosbach/ai/cmd/db/DocDaoIT.java: -------------------------------------------------------------------------------- 1 | package org.dhbw.mosbach.ai.cmd.db; 2 | 3 | import org.dhbw.mosbach.ai.cmd.model.Doc; 4 | import org.dhbw.mosbach.ai.cmd.model.Repo; 5 | import org.dhbw.mosbach.ai.cmd.test.config.DeploymentConfig; 6 | import org.dhbw.mosbach.ai.cmd.test.config.TestUser; 7 | import org.dhbw.mosbach.ai.cmd.test.config.TestUsers; 8 | import org.dhbw.mosbach.ai.cmd.test.helper.DeploymentPackager; 9 | import org.dhbw.mosbach.ai.cmd.test.include.PackageIncludes; 10 | import org.dhbw.mosbach.ai.cmd.test.resources.Datasets; 11 | import org.dhbw.mosbach.ai.cmd.test.resources.Scripts; 12 | import org.jboss.arquillian.container.test.api.Deployment; 13 | import org.jboss.arquillian.junit.Arquillian; 14 | import org.jboss.arquillian.persistence.ApplyScriptAfter; 15 | import org.jboss.arquillian.persistence.Cleanup; 16 | import org.jboss.arquillian.persistence.CleanupStrategy; 17 | import org.jboss.arquillian.persistence.DataSeedStrategy; 18 | import org.jboss.arquillian.persistence.DataSource; 19 | import org.jboss.arquillian.persistence.PersistenceTest; 20 | import org.jboss.arquillian.persistence.SeedDataUsing; 21 | import org.jboss.arquillian.persistence.TestExecutionPhase; 22 | import org.jboss.arquillian.persistence.UsingDataSet; 23 | import org.jboss.arquillian.test.api.ArquillianResource; 24 | import org.jboss.arquillian.transaction.api.annotation.TransactionMode; 25 | import org.jboss.arquillian.transaction.api.annotation.Transactional; 26 | import org.jboss.shrinkwrap.api.formatter.Formatters; 27 | import org.jboss.shrinkwrap.api.spec.WebArchive; 28 | import org.junit.Assert; 29 | import org.junit.Before; 30 | import org.junit.Test; 31 | import org.junit.runner.RunWith; 32 | import org.slf4j.Logger; 33 | import org.slf4j.LoggerFactory; 34 | 35 | import javax.inject.Inject; 36 | import java.net.URL; 37 | 38 | @RunWith(Arquillian.class) 39 | @DataSource(DeploymentConfig.DEFAULT_DATA_SOURCE) 40 | @SeedDataUsing(DataSeedStrategy.CLEAN_INSERT) 41 | @Transactional(TransactionMode.COMMIT) 42 | @Cleanup(phase = TestExecutionPhase.AFTER, strategy = CleanupStrategy.STRICT) 43 | @ApplyScriptAfter({ Scripts.DISABLE_REFERENTIAL_INTEGRITY, Scripts.RESET_TABLE_IDENTITIES }) 44 | @PersistenceTest 45 | public class DocDaoIT { 46 | 47 | private static final Logger log = LoggerFactory.getLogger(DocDaoIT.class); 48 | 49 | @Deployment(name = DeploymentConfig.DEPLOYMENT_NAME) 50 | public static WebArchive createDeployment() { 51 | final WebArchive war = DeploymentPackager.createDeployment(DeploymentConfig.DEPLOYMENT_NAME) 52 | .addBeansAndPersistenceDefinition() 53 | .addTestResources() 54 | .addPackages(PackageIncludes.DOC_DAO) 55 | .packageWebArchive(); 56 | 57 | log.info(war.toString(Formatters.VERBOSE)); 58 | 59 | return war; 60 | } 61 | 62 | @ArquillianResource 63 | private URL deploymentUrl; 64 | 65 | @Inject 66 | private DocDao docDao; 67 | 68 | @Inject 69 | private RepoDao repoDao; 70 | 71 | @Before 72 | public void printDeploymentInfo() { 73 | log.info("Deployed at {}", deploymentUrl.toString()); 74 | } 75 | 76 | @Test 77 | @UsingDataSet({ Datasets.USERS, Datasets.REPOS }) 78 | public void testCreateDoc() { 79 | TestUser owner = TestUsers.VUEJS; 80 | Repo repo = repoDao.getRepo(owner); 81 | System.out.println(owner); 82 | System.out.println(repo); 83 | 84 | Doc doc = new Doc(); 85 | doc.setName("Arquillian Integration Test"); 86 | doc.setContent("This is an Arquillian integration test"); 87 | doc.setRepo(repo); 88 | doc.setCuser(owner); 89 | doc.setUuser(owner); 90 | 91 | docDao.createDoc(doc); 92 | 93 | Doc loadedDoc = docDao.getDoc(doc.getId()); 94 | 95 | Assert.assertNotNull(loadedDoc); 96 | Assert.assertEquals(doc.getId(), loadedDoc.getId()); 97 | Assert.assertEquals(doc.getName(), loadedDoc.getName()); 98 | Assert.assertEquals(doc.getContent(), loadedDoc.getContent()); 99 | Assert.assertEquals(doc.getRepo(), loadedDoc.getRepo()); 100 | Assert.assertEquals(doc.getCuser(), loadedDoc.getCuser()); 101 | Assert.assertEquals(doc.getUuser(), loadedDoc.getUuser()); 102 | } 103 | 104 | @Test 105 | @UsingDataSet(Datasets.DOCUMENTS) 106 | public void testGetDoc() { 107 | 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/test/java/org/dhbw/mosbach/ai/cmd/extension/listener/SurefireTestPrintListener.java: -------------------------------------------------------------------------------- 1 | package org.dhbw.mosbach.ai.cmd.extension.listener; 2 | 3 | import org.dhbw.mosbach.ai.cmd.extension.listener.result.RunTime; 4 | import org.dhbw.mosbach.ai.cmd.extension.listener.result.TestSuiteResult; 5 | import org.dhbw.mosbach.ai.cmd.extension.listener.util.DescriptionScanner; 6 | import org.dhbw.mosbach.ai.cmd.extension.listener.util.TestCasePrinter; 7 | import org.junit.runner.Description; 8 | import org.junit.runner.Result; 9 | import org.junit.runner.notification.Failure; 10 | import org.junit.runner.notification.RunListener; 11 | import org.junit.runner.notification.RunListener.ThreadSafe; 12 | 13 | import java.util.concurrent.atomic.AtomicInteger; 14 | 15 | @ThreadSafe 16 | public class SurefireTestPrintListener extends RunListener { 17 | 18 | private final TestCasePrinter testPrinter = new TestCasePrinter(); 19 | private final DescriptionScanner scanner = new DescriptionScanner(); 20 | 21 | private RunTime suiteRunTime = new RunTime(); 22 | private RunTime testRunTime = new RunTime(); 23 | 24 | private String testClassName; 25 | private AtomicInteger testNumber = new AtomicInteger(1); 26 | 27 | /** 28 | * Called before any tests have been run. This may be called on an 29 | * arbitrary thread. 30 | * 31 | * @param description describes the tests to be run 32 | */ 33 | @Override 34 | public void testRunStarted(Description description) throws Exception { 35 | testPrinter.printTestSuiteStarted(description); 36 | 37 | Class testClass = scanner.extractFirstTestClass(description); 38 | this.testClassName = testClass.getName(); 39 | 40 | testPrinter.printTestAnnotations(testClass.getAnnotations()); 41 | 42 | suiteRunTime.setStartTime(System.currentTimeMillis()); 43 | } 44 | 45 | /** 46 | * Called when all tests have finished. This may be called on an 47 | * arbitrary thread. 48 | * 49 | * @param result the summary of the test run, including all the tests that failed 50 | */ 51 | @Override 52 | public void testRunFinished(Result result) throws Exception { 53 | suiteRunTime.setEndTime(System.currentTimeMillis()); 54 | 55 | testPrinter.printTestEndResults(new TestSuiteResult(result, suiteRunTime), testClassName); 56 | } 57 | 58 | /** 59 | * Called when an atomic test is about to be started. 60 | * 61 | * @param description the description of the test that is about to be run 62 | * (generally a class and method name) 63 | */ 64 | @Override 65 | public void testStarted(Description description) throws Exception { 66 | testPrinter.printTestStarted(testNumber.get(), description); 67 | testPrinter.printTestAnnotations(description.getAnnotations()); 68 | 69 | testRunTime.setStartTime(System.currentTimeMillis()); 70 | } 71 | 72 | /** 73 | * Called when an atomic test has finished, whether the test succeeds or fails. 74 | * 75 | * @param description the description of the test that just ran 76 | */ 77 | @Override 78 | public void testFinished(Description description) throws Exception { 79 | testRunTime.setEndTime(System.currentTimeMillis()); 80 | 81 | testPrinter.printTestFinished(testNumber.get(), description, testRunTime); 82 | testNumber.incrementAndGet(); 83 | } 84 | 85 | /** 86 | * Called when an atomic test fails, or when a listener throws an exception. 87 | * 88 | *

In the case of a failure of an atomic test, this method will be called 89 | * with the same {@code Description} passed to 90 | * {@link #testStarted(Description)}, from the same thread that called 91 | * {@link #testStarted(Description)}. 92 | * 93 | *

In the case of a listener throwing an exception, this will be called with 94 | * a {@code Description} of {@link Description#TEST_MECHANISM}, and may be called 95 | * on an arbitrary thread. 96 | * 97 | * @param failure describes the test that failed and the exception that was thrown 98 | */ 99 | @Override 100 | public void testFailure(Failure failure) throws Exception { 101 | testPrinter.printTestFailure(testNumber.get(), failure); 102 | } 103 | 104 | /** 105 | * Called when an atomic test flags that it assumes a condition that is 106 | * false 107 | * 108 | * @param failure describes the test that failed and the 109 | * {@link org.junit.AssumptionViolatedException} that was thrown 110 | */ 111 | @Override 112 | public void testAssumptionFailure(Failure failure) { 113 | testPrinter.printTestAssumptionFailure(testNumber.get(), failure); 114 | } 115 | 116 | /** 117 | * Called when a test will not be run, generally because a test method is annotated 118 | * with {@link org.junit.Ignore}. 119 | * 120 | * @param description describes the test that will not be run 121 | */ 122 | @Override 123 | public void testIgnored(Description description) throws Exception { 124 | testPrinter.printTestIgnored(testNumber.get(), description); 125 | testNumber.incrementAndGet(); 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/test/java/org/dhbw/mosbach/ai/cmd/extension/listener/log/LogPrefix.java: -------------------------------------------------------------------------------- 1 | package org.dhbw.mosbach.ai.cmd.extension.listener.log; 2 | 3 | public enum LogPrefix { 4 | INFO("INFO"), 5 | ERROR("ERROR"); 6 | 7 | private String prefix; 8 | 9 | private LogPrefix(String prefix) { 10 | this.prefix = prefix; 11 | } 12 | 13 | @Override 14 | public String toString() { 15 | return "[" + prefix + "] "; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/test/java/org/dhbw/mosbach/ai/cmd/extension/listener/log/TestLogger.java: -------------------------------------------------------------------------------- 1 | package org.dhbw.mosbach.ai.cmd.extension.listener.log; 2 | 3 | import org.junit.runner.notification.Failure; 4 | 5 | public class TestLogger { 6 | 7 | public void info(String message) { 8 | System.out.println(LogPrefix.INFO + message); 9 | } 10 | 11 | public void info(String format, Object... args) { 12 | info(String.format(format, args)); 13 | } 14 | 15 | public void error(String message) { 16 | System.err.println(LogPrefix.ERROR + message); 17 | } 18 | 19 | public void error(String format, Object... args) { 20 | error(String.format(format, args)); 21 | } 22 | 23 | public void stackTrace(Failure failure) { 24 | String trace = failure.getTrace(); 25 | error(trace.replaceAll("\n", "\n" + LogPrefix.ERROR)); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/test/java/org/dhbw/mosbach/ai/cmd/extension/listener/result/ResultAccumulator.java: -------------------------------------------------------------------------------- 1 | package org.dhbw.mosbach.ai.cmd.extension.listener.result; 2 | 3 | import org.junit.runner.notification.Failure; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | import java.util.concurrent.atomic.AtomicInteger; 8 | 9 | public class ResultAccumulator { 10 | 11 | private AtomicInteger runCount = new AtomicInteger(0); 12 | private AtomicInteger failureCount = new AtomicInteger(0); 13 | private AtomicInteger ignoreCount = new AtomicInteger(0); 14 | 15 | private List failures = new ArrayList<>(); 16 | 17 | public void incrementRunCount() { 18 | this.runCount.incrementAndGet(); 19 | } 20 | 21 | public int getRunCount() { 22 | return runCount.get(); 23 | } 24 | 25 | public void incrementFailureCount() { 26 | this.failureCount.incrementAndGet(); 27 | } 28 | 29 | public int getFailureCount() { 30 | return failureCount.get(); 31 | } 32 | 33 | public void incrementIgnoreCount() { 34 | this.ignoreCount.incrementAndGet(); 35 | } 36 | 37 | public int getIgnoreCount() { 38 | return ignoreCount.get(); 39 | } 40 | 41 | public void addFailure(Failure failure) { 42 | this.failures.add(failure); 43 | } 44 | 45 | public List getFailures() { 46 | return failures; 47 | } 48 | 49 | public TestSuiteResult buildResult(RunTime runTime) { 50 | return new TestSuiteResult(this, runTime); 51 | } 52 | 53 | public void reset() { 54 | this.runCount.set(0); 55 | this.failureCount.set(0); 56 | this.ignoreCount.set(0); 57 | this.failures.clear(); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/test/java/org/dhbw/mosbach/ai/cmd/extension/listener/result/RunTime.java: -------------------------------------------------------------------------------- 1 | package org.dhbw.mosbach.ai.cmd.extension.listener.result; 2 | 3 | import java.util.Objects; 4 | import java.util.concurrent.atomic.AtomicLong; 5 | 6 | public class RunTime { 7 | 8 | public static final RunTime ZERO = new RunTime(); 9 | 10 | private AtomicLong startTime = new AtomicLong(0); 11 | private AtomicLong endTime = new AtomicLong(0); 12 | 13 | public long getStartTime() { 14 | return startTime.get(); 15 | } 16 | 17 | public void setStartTime(long startTime) { 18 | this.startTime.set(startTime); 19 | } 20 | 21 | public long getEndTime() { 22 | return endTime.get(); 23 | } 24 | 25 | public void setEndTime(long endTime) { 26 | this.endTime.set(endTime); 27 | } 28 | 29 | public long getRunTime() { 30 | return endTime.addAndGet(-startTime.get()); 31 | } 32 | 33 | public boolean isZero() { 34 | return this.equals(ZERO); 35 | } 36 | 37 | @Override 38 | public String toString() { 39 | return String.format("%.3f", getRunTime() / 1000.0); 40 | } 41 | 42 | @Override 43 | public boolean equals(Object o) { 44 | if (this == o) { 45 | return true; 46 | } 47 | if (o == null || getClass() != o.getClass()) { 48 | return false; 49 | } 50 | 51 | RunTime runTime = (RunTime) o; 52 | return Objects.equals(startTime, runTime.startTime) && 53 | Objects.equals(endTime, runTime.endTime); 54 | } 55 | 56 | @Override 57 | public int hashCode() { 58 | return Objects.hash(startTime, endTime); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/test/java/org/dhbw/mosbach/ai/cmd/extension/listener/result/TestSuiteResult.java: -------------------------------------------------------------------------------- 1 | package org.dhbw.mosbach.ai.cmd.extension.listener.result; 2 | 3 | import org.junit.runner.Result; 4 | import org.junit.runner.notification.Failure; 5 | 6 | import java.io.Serializable; 7 | import java.util.List; 8 | 9 | public class TestSuiteResult implements Serializable { 10 | 11 | private static final long serialVersionUID = -3536821423119548367L; 12 | 13 | private final RunTime runTime; 14 | private final int runCount; 15 | private final int failureCount; 16 | private final int ignoreCount; 17 | 18 | private final List failures; 19 | 20 | public TestSuiteResult(Result sourceResult, RunTime runTime) { 21 | this.runTime = runTime; 22 | this.runCount = sourceResult.getRunCount(); 23 | this.failureCount = sourceResult.getFailureCount(); 24 | this.ignoreCount = sourceResult.getIgnoreCount(); 25 | this.failures = sourceResult.getFailures(); 26 | } 27 | 28 | TestSuiteResult(ResultAccumulator accumulator, RunTime runTime) { 29 | this.runTime = runTime; 30 | this.runCount = accumulator.getRunCount(); 31 | this.failureCount = accumulator.getFailureCount(); 32 | this.ignoreCount = accumulator.getIgnoreCount(); 33 | this.failures = accumulator.getFailures(); 34 | } 35 | 36 | public boolean wasSuccessful() { 37 | return failureCount == 0; 38 | } 39 | 40 | public RunTime getRunTime() { 41 | return runTime; 42 | } 43 | 44 | public int getRunCount() { 45 | return runCount; 46 | } 47 | 48 | public int getFailureCount() { 49 | return failureCount; 50 | } 51 | 52 | public int getIgnoreCount() { 53 | return ignoreCount; 54 | } 55 | 56 | public List getFailures() { 57 | return failures; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/test/java/org/dhbw/mosbach/ai/cmd/extension/listener/util/DescriptionScanner.java: -------------------------------------------------------------------------------- 1 | package org.dhbw.mosbach.ai.cmd.extension.listener.util; 2 | 3 | import org.junit.runner.Description; 4 | 5 | public final class DescriptionScanner { 6 | 7 | public Class extractFirstTestClass(Description description) { 8 | if (description == null) { 9 | return void.class; 10 | } 11 | 12 | if (description.getTestClass() != null) { 13 | return description.getTestClass(); 14 | } 15 | 16 | for (Description child : description.getChildren()) { 17 | if (child != null && child.getTestClass() != null) { 18 | return child.getTestClass(); 19 | } 20 | } 21 | 22 | return void.class; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/test/java/org/dhbw/mosbach/ai/cmd/security/HashingTest.java: -------------------------------------------------------------------------------- 1 | package org.dhbw.mosbach.ai.cmd.security; 2 | 3 | import org.junit.Before; 4 | import org.junit.Test; 5 | 6 | import static org.junit.Assert.*; 7 | 8 | /** 9 | * Testing the Hashing class 10 | * 11 | * @author 3040018 12 | */ 13 | public class HashingTest { 14 | 15 | private Hashing hashing; 16 | 17 | @Before 18 | public void init() { 19 | hashing = new Hashing(); 20 | } 21 | 22 | @Test 23 | public void testHashPassword() { 24 | String password = "blabla12"; 25 | String hash = hashing.hashPassword(password); 26 | 27 | String password1 = "blabla12"; 28 | String hash1 = hashing.hashPassword(password1); 29 | 30 | // Make sure the passwords are different due to salting the hash 31 | assertNotEquals(hash, hash1); 32 | } 33 | 34 | @Test 35 | public void testCheckPassword() { 36 | String correctPassword = "test2"; 37 | String wrongPassword = "testtest"; 38 | String hash = "$2a$12$Q7pgb8v0v3Oj0ZmbvFoU9uRulY0BNMZMjfnZLo9ofpGdIq4f7oVX2"; 39 | 40 | assertTrue(hashing.checkPassword(correctPassword, hash)); 41 | assertFalse(hashing.checkPassword(wrongPassword, hash)); 42 | } 43 | 44 | @Test 45 | public void testHashDocContent() { 46 | String content = "test content 123 !öäüß"; 47 | String hash = hashing.hashDocContent(content); 48 | String expectedHash = "d2a43c8e0de0fe56ec69d70f3540682ab30684c1"; 49 | 50 | assertEquals(expectedHash, hash); 51 | } 52 | 53 | @Test 54 | public void testHashDocEmptyContent() { 55 | 56 | String emptyContent = ""; 57 | String nullContent = null; 58 | String expectedHash = "da39a3ee5e6b4b0d3255bfef95601890afd80709"; 59 | 60 | String hashEmptyContent = hashing.hashDocContent(emptyContent); 61 | String hashNullContent = hashing.hashDocContent(nullContent); 62 | 63 | assertEquals(expectedHash, hashEmptyContent); 64 | assertEquals(expectedHash, hashNullContent); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/test/java/org/dhbw/mosbach/ai/cmd/services/helper/Authenticator.java: -------------------------------------------------------------------------------- 1 | package org.dhbw.mosbach.ai.cmd.services.helper; 2 | 3 | import org.dhbw.mosbach.ai.cmd.services.ServiceEndpoints; 4 | import org.dhbw.mosbach.ai.cmd.services.payload.TestLoginModel; 5 | import org.dhbw.mosbach.ai.cmd.test.config.TestConfig; 6 | import org.dhbw.mosbach.ai.cmd.test.config.TestUser; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | 10 | import javax.ws.rs.client.ClientBuilder; 11 | import javax.ws.rs.client.Entity; 12 | import javax.ws.rs.client.WebTarget; 13 | import javax.ws.rs.core.MediaType; 14 | import javax.ws.rs.core.Response; 15 | import java.net.URI; 16 | 17 | public final class Authenticator { 18 | 19 | private static final Logger log = LoggerFactory.getLogger(Authenticator.class); 20 | 21 | private static final String API_PREFIX = ServiceEndpoints.PATH_API_ROOT.substring(1); 22 | 23 | private Authenticator() { 24 | } 25 | 26 | public static Response authenticate(URI deploymentBaseURI, TestUser testUser) { 27 | final WebTarget target = ClientBuilder.newClient().target(deploymentBaseURI.resolve(API_PREFIX + TestConfig.AUTHENTICATION_LOGIN_PATH)); 28 | 29 | log.info("POST {}", target.getUri().getPath()); 30 | 31 | return target.request(MediaType.APPLICATION_JSON) 32 | .post(Entity.json(new TestLoginModel(testUser))); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/test/java/org/dhbw/mosbach/ai/cmd/services/helper/ClientJson.java: -------------------------------------------------------------------------------- 1 | package org.dhbw.mosbach.ai.cmd.services.helper; 2 | 3 | import java.util.Map; 4 | import java.util.TreeMap; 5 | 6 | public final class ClientJson { 7 | 8 | private ClientJson() { 9 | } 10 | 11 | public static Builder newBuilder() { 12 | return new Builder(); 13 | } 14 | 15 | public static final class Builder { 16 | 17 | private Map mappings = new TreeMap<>(); 18 | 19 | private Builder() { 20 | } 21 | 22 | public Builder addKey(String key, Object value) { 23 | mappings.put(key, value); 24 | return this; 25 | } 26 | 27 | @Override 28 | public String toString() { 29 | final StringBuilder builder = new StringBuilder("{"); 30 | mappings.forEach((key, value) -> { 31 | builder.append("\"").append(escapeJson(key)).append("\":"); 32 | 33 | if (value == null) { 34 | builder.append("null"); 35 | } else if (value instanceof Byte) { 36 | builder.append((byte) value); 37 | } else if (value instanceof Short) { 38 | builder.append((short) value); 39 | } else if (value instanceof Integer) { 40 | builder.append((int) value); 41 | } else if (value instanceof Long) { 42 | builder.append((long) value); 43 | } else if (value instanceof Float) { 44 | builder.append((float) value); 45 | } else if (value instanceof Double) { 46 | builder.append((double) value); 47 | } else if (value instanceof Character) { 48 | builder.append((char) value); 49 | } else if (value instanceof Boolean) { 50 | builder.append((boolean) value); 51 | } else { 52 | builder.append("\"").append(escapeJson(value.toString())).append("\""); 53 | } 54 | 55 | builder.append(","); 56 | }); 57 | 58 | if (!mappings.isEmpty()) { 59 | builder.replace(builder.length() - 1, builder.length(), ""); 60 | } 61 | 62 | builder.append("}"); 63 | return builder.toString(); 64 | } 65 | 66 | private String escapeJson(String value) { 67 | return value.replaceAll("([\"\'\\\\])", "\\\\$1"); 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/test/java/org/dhbw/mosbach/ai/cmd/services/helper/JsonUtil.java: -------------------------------------------------------------------------------- 1 | package org.dhbw.mosbach.ai.cmd.services.helper; 2 | 3 | import com.fasterxml.jackson.core.JsonProcessingException; 4 | import com.fasterxml.jackson.databind.ObjectMapper; 5 | import org.dhbw.mosbach.ai.cmd.services.payload.Payload; 6 | import org.dhbw.mosbach.ai.cmd.services.response.ResponseObject; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | 10 | import java.io.IOException; 11 | 12 | public final class JsonUtil { 13 | 14 | private static final Logger log = LoggerFactory.getLogger(JsonUtil.class); 15 | 16 | private ObjectMapper mapper = new ObjectMapper(); 17 | 18 | public String prettyPrint(String json) { 19 | try { 20 | Object jsonObject = mapper.readValue(json, Object.class); 21 | return mapper.writerWithDefaultPrettyPrinter().writeValueAsString(jsonObject); 22 | } catch (IOException exc) { 23 | log.error("Could not pretty print json: " + exc.getLocalizedMessage(), exc); 24 | } 25 | 26 | return null; 27 | } 28 | 29 | public String serialize(T object) { 30 | try { 31 | return mapper.writeValueAsString(object); 32 | } catch (JsonProcessingException jpe) { 33 | log.error(jpe.getLocalizedMessage(), jpe); 34 | } 35 | 36 | return null; 37 | } 38 | 39 | public T deserialize(String json, Class type) { 40 | try { 41 | return mapper.readValue(json, type); 42 | } catch (IOException exc) { 43 | log.error("Could not deserialize json: " + exc.getLocalizedMessage(), exc); 44 | } 45 | 46 | return null; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/test/java/org/dhbw/mosbach/ai/cmd/services/payload/TestLoginModel.java: -------------------------------------------------------------------------------- 1 | package org.dhbw.mosbach.ai.cmd.services.payload; 2 | 3 | import org.dhbw.mosbach.ai.cmd.test.config.TestUser; 4 | 5 | public class TestLoginModel extends LoginModel { 6 | 7 | public TestLoginModel(TestUser user) { 8 | super(user.getName(), user.getPassword()); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/test/java/org/dhbw/mosbach/ai/cmd/test/config/DeploymentConfig.java: -------------------------------------------------------------------------------- 1 | package org.dhbw.mosbach.ai.cmd.test.config; 2 | 3 | public final class DeploymentConfig { 4 | 5 | public static final String DEPLOYMENT_NAME = "cmd"; 6 | 7 | public static final String DEFAULT_DATA_SOURCE = "java:jboss/datasources/cmdDS"; 8 | 9 | public static final String PROJECT_POM = "pom.xml"; 10 | 11 | private static final String META_INF = "META-INF"; 12 | 13 | public static final String BEANS_XML = META_INF + "/beans.xml"; 14 | 15 | public static final String PERSISTENCE_XML = META_INF + "/persistence.xml"; 16 | 17 | public static final String TEST_PERSISTENCE_XML = META_INF + "/test-persistence.xml"; 18 | 19 | public static final String LOG4J_PROPERTIES = META_INF + "/log4j.properties"; 20 | } 21 | -------------------------------------------------------------------------------- /src/test/java/org/dhbw/mosbach/ai/cmd/test/config/TestConfig.java: -------------------------------------------------------------------------------- 1 | package org.dhbw.mosbach.ai.cmd.test.config; 2 | 3 | public abstract class TestConfig { 4 | 5 | public static final String API_PREFIX = "api"; 6 | 7 | public static final String AUTHENTICATION_LOGIN_PATH = "/authentication/login"; 8 | 9 | public static final String AUTHENTICATION_LOGOUT_PATH = "/authentication/logout"; 10 | 11 | public static final String AUTHENTICATION_REGISTER_PATH = "/authentication/register"; 12 | 13 | public static final String DOCUMENT_ADD_PATH = "/document/add"; 14 | 15 | public static final String DOCUMENT_REMOVE_PATH = "/document/remove"; 16 | 17 | public static final String DOCUMENT_ACCESS_PATH = "/document/hasAccess"; 18 | 19 | public static final String DOCUMENT_ALL_PATH = "/document/all"; 20 | 21 | public static final String DOCUMENT_TRANSFER_PATH = "/document/transferOwnership"; 22 | 23 | public static final String COLLABORATOR_ADD_PATH = "/collaborators/add"; 24 | 25 | public static final String COLLABORATOR_REMOVE_PATH = "/collaborators/remove"; 26 | 27 | public static final String JSESSIONID = "JSESSIONID"; 28 | 29 | public static final int PASSWORD_LENGTH = 30; 30 | } 31 | -------------------------------------------------------------------------------- /src/test/java/org/dhbw/mosbach/ai/cmd/test/config/TestUser.java: -------------------------------------------------------------------------------- 1 | package org.dhbw.mosbach.ai.cmd.test.config; 2 | 3 | import org.dhbw.mosbach.ai.cmd.model.User; 4 | 5 | public class TestUser extends User implements Cloneable { 6 | 7 | private static final long serialVersionUID = 5846623425879886960L; 8 | 9 | TestUser(int userId, String username, String email, String password) { 10 | this.setId(userId); 11 | this.setName(username); 12 | this.setMail(email); 13 | this.setPassword(password); 14 | } 15 | 16 | @Override 17 | public TestUser clone() { 18 | try { 19 | return (TestUser) super.clone(); 20 | } catch (CloneNotSupportedException exc) { 21 | exc.printStackTrace(); 22 | } 23 | 24 | return null; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/test/java/org/dhbw/mosbach/ai/cmd/test/config/TestUsers.java: -------------------------------------------------------------------------------- 1 | package org.dhbw.mosbach.ai.cmd.test.config; 2 | 3 | public abstract class TestUsers { 4 | 5 | public static final TestUser JACKSON = new TestUser(1, "jackson", "jackson@fasterxml.com", "yh8-HFb1$sX86WBZ6(t@Cstgz[&JIV"); 6 | 7 | public static final TestUser APP_USER = new TestUser(2, "appuser", "sample@appuser.com", "4I<{UsLh1Tct"); 12 | 13 | public static final TestUser VUEJS = new TestUser(5, "vuejs", "frontend@vuejs.org", "L@C2<9\"%&OKy(x'B/v2i]w... classes) { 65 | archive.addClasses(classes); 66 | return this; 67 | } 68 | 69 | public WebArchive packageWebArchive() { 70 | return archive; 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/test/java/org/dhbw/mosbach/ai/cmd/test/helper/PasswordGenerator.java: -------------------------------------------------------------------------------- 1 | package org.dhbw.mosbach.ai.cmd.test.helper; 2 | 3 | import java.security.SecureRandom; 4 | import java.util.Random; 5 | 6 | public class PasswordGenerator { 7 | 8 | private static final String ALPHA_UPPER = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; 9 | private static final String ALPHA_LOWER = "abcdefghijklmnopqrstuvwxyz"; 10 | private static final String DIGITS = "0123456789"; 11 | private static final String SYMBOLS = "^$*.[]{}()?-\"§!@#%&/\\,><':;|_~`"; 12 | private static final String CHAR_SET = ALPHA_UPPER + ALPHA_LOWER + DIGITS + SYMBOLS; 13 | 14 | private Random random = new SecureRandom(); 15 | 16 | public String generateSecurePassword(int length) { 17 | // Only allow password lengths that are at least 8 18 | if (length < 8) { 19 | throw new IllegalArgumentException("length has to be at least 8, but is " + length); 20 | } 21 | 22 | // Use at least one uppercase and lowercase character, one digit 23 | // and one symbol in the password 24 | char[] password = new char[length]; 25 | password[0] = ALPHA_UPPER.charAt(random.nextInt(ALPHA_UPPER.length())); 26 | password[1] = ALPHA_LOWER.charAt(random.nextInt(ALPHA_LOWER.length())); 27 | password[2] = DIGITS.charAt(random.nextInt(DIGITS.length())); 28 | password[3] = SYMBOLS.charAt(random.nextInt(SYMBOLS.length())); 29 | 30 | // Fill the last slots with random characters from the 31 | // whole character set 32 | for (int i = 4; i < length; i++) { 33 | password[i] = CHAR_SET.charAt(random.nextInt(CHAR_SET.length())); 34 | } 35 | 36 | // Shuffle each character 37 | for (int i = 0; i < password.length; i++) { 38 | int pos = random.nextInt(password.length); 39 | char temp = password[i]; 40 | password[i] = password[pos]; 41 | password[pos] = temp; 42 | } 43 | 44 | return new String(password); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/test/java/org/dhbw/mosbach/ai/cmd/test/include/DependencyIncludes.java: -------------------------------------------------------------------------------- 1 | package org.dhbw.mosbach.ai.cmd.test.include; 2 | 3 | public enum DependencyIncludes { 4 | USER_DAO("org.mindrot:jbcrypt:0.4"); 5 | 6 | private String[] dependencies; 7 | 8 | private DependencyIncludes(String... dependencies) { 9 | this.dependencies = dependencies; 10 | } 11 | 12 | public String[] getDependencies() { 13 | return dependencies; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/test/java/org/dhbw/mosbach/ai/cmd/test/include/PackageIncludes.java: -------------------------------------------------------------------------------- 1 | package org.dhbw.mosbach.ai.cmd.test.include; 2 | 3 | public enum PackageIncludes { 4 | 5 | AUTHENTICATION_SERVICE( 6 | "org/dhbw/mosbach/ai/cmd/db", 7 | "org/dhbw/mosbach/ai/cmd/model", 8 | "org/dhbw/mosbach/ai/cmd/security", 9 | "org/dhbw/mosbach/ai/cmd/services", 10 | "org/dhbw/mosbach/ai/cmd/session", 11 | "org/dhbw/mosbach/ai/cmd/util", 12 | "org/dhbw/mosbach/ai/cmd/test/config" 13 | ), 14 | 15 | DOCUMENT_SERVICE( 16 | "org/dhbw/mosbach/ai/cmd/db", 17 | "org/dhbw/mosbach/ai/cmd/model", 18 | "org/dhbw/mosbach/ai/cmd/security", 19 | "org/dhbw/mosbach/ai/cmd/services", 20 | "org/dhbw/mosbach/ai/cmd/session", 21 | "org/dhbw/mosbach/ai/cmd/util", 22 | "org/dhbw/mosbach/ai/cmd/test/config" 23 | ), 24 | 25 | COLLABORATOR_SERVICE( 26 | "org/dhbw/mosbach/ai/cmd/db", 27 | "org/dhbw/mosbach/ai/cmd/model", 28 | "org/dhbw/mosbach/ai/cmd/security", 29 | "org/dhbw/mosbach/ai/cmd/services", 30 | "org/dhbw/mosbach/ai/cmd/session", 31 | "org/dhbw/mosbach/ai/cmd/util", 32 | "org/dhbw/mosbach/ai/cmd/test/config" 33 | ), 34 | 35 | USER_DAO( 36 | "org/dhbw/mosbach/ai/cmd/db", 37 | "org/dhbw/mosbach/ai/cmd/model", 38 | "org/dhbw/mosbach/ai/cmd/security", 39 | "org/dhbw/mosbach/ai/cmd/services/serialize", 40 | "org/dhbw/mosbach/ai/cmd/util", 41 | "org/dhbw/mosbach/ai/cmd/test/config", 42 | "org/dhbw/mosbach/ai/cmd/test/helper" 43 | ), 44 | 45 | DOC_DAO( 46 | "org/dhbw/mosbach/ai/cmd/db", 47 | "org/dhbw/mosbach/ai/cmd/model", 48 | "org/dhbw/mosbach/ai/cmd/security", 49 | "org/dhbw/mosbach/ai/cmd/services/serialize", 50 | "org/dhbw/mosbach/ai/cmd/util", 51 | "org/dhbw/mosbach/ai/cmd/test/config" 52 | ); 53 | 54 | private String[] packages; 55 | 56 | private PackageIncludes(String... packages) { 57 | this.packages = packages; 58 | } 59 | 60 | public String[] getPackages() { 61 | return packages; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/test/java/org/dhbw/mosbach/ai/cmd/test/resources/Datasets.java: -------------------------------------------------------------------------------- 1 | package org.dhbw.mosbach.ai.cmd.test.resources; 2 | 3 | public final class Datasets { 4 | 5 | private Datasets() { 6 | } 7 | 8 | public static final String DOCUMENTS = "datasets/documents.yml"; 9 | 10 | public static final String USERS = "datasets/users.yml"; 11 | 12 | public static final String REPOS = "datasets/repos.yml"; 13 | } 14 | -------------------------------------------------------------------------------- /src/test/java/org/dhbw/mosbach/ai/cmd/test/resources/Scripts.java: -------------------------------------------------------------------------------- 1 | package org.dhbw.mosbach.ai.cmd.test.resources; 2 | 3 | public final class Scripts { 4 | 5 | private Scripts() { 6 | } 7 | 8 | public static final String DISABLE_REFERENTIAL_INTEGRITY = "scripts/disableReferentialIntegrity.sql"; 9 | 10 | public static final String RESET_TABLE_IDENTITIES = "scripts/resetTableIdentities.sql"; 11 | } 12 | -------------------------------------------------------------------------------- /src/test/resources/META-INF/test-persistence.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | org.hibernate.jpa.HibernatePersistenceProvider 10 | java:jboss/datasources/cmdDS 11 | org.dhbw.mosbach.ai.cmd.model.Collaborator 12 | org.dhbw.mosbach.ai.cmd.model.Doc 13 | org.dhbw.mosbach.ai.cmd.model.History 14 | org.dhbw.mosbach.ai.cmd.model.Repo 15 | org.dhbw.mosbach.ai.cmd.model.User 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/test/resources/WEB-INF/web.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | resteasy-servlet 10 | /api/* 11 | 12 | 13 | 14 | 15 | resteasy.scan 16 | true 17 | 18 | 19 | 20 | 21 | resteasy.servlet.mapping.prefix 22 | /api 23 | 24 | 25 | 26 | 27 | org.jboss.resteasy.plugins.server.servlet.ResteasyBootstrap 28 | 29 | 30 | 31 | 32 | resteasy-servlet 33 | 34 | org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /src/test/resources/arquillian-cube.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | STARTORCONNECTANDLEAVE 10 | 11 | 12 | 13 | 1.39 14 | http://localhost:2375 15 | src/test/resources/docker/docker-compose.yml 16 | COMPOSE 17 | 18 | arquillian-cube-wildfly-test: 19 | await: 20 | strategy: log 21 | match: 'Admin console listening' 22 | 23 | 24 | 25 | 26 | 27 | ${jboss.home} 28 | ${jboss.standalone.config:standalone.xml} 29 | 30 | -Djboss.socket.binding.port-offset=10000 -Xms512m -Xmx1024m -XX:MaxPermSize=512m --add-modules java.se 31 | 19990 32 | 33 | 34 | 35 | 36 | ${wildfly.datasource.jndi-name} 37 | 38 | 39 | 40 | org.dbunit.ext.h2.H2DataTypeFactory 41 | true 42 | true 43 | 44 | 45 | 46 | ${extension.webdriver} 47 | 48 | 49 | 50 | ARQUILLIAN_ 51 | 52 | 53 | -------------------------------------------------------------------------------- /src/test/resources/arquillian.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | ${jboss.home} 11 | ${jboss.standalone.config:standalone.xml} 12 | 13 | -Djboss.socket.binding.port-offset=10000 -Xms512m -Xmx1024m --add-modules java.se -showversion 14 | 19990 15 | 16 | 17 | 18 | 19 | ${wildfly.datasource.jndi-name} 20 | COMMIT 21 | CLEAN_INSERT 22 | 23 | 24 | 25 | org.dbunit.ext.h2.H2DataTypeFactory 26 | true 27 | true 28 | 29 | 30 | 31 | ${extension.webdriver} 32 | 33 | 34 | 35 | ARQUILLIAN_ 36 | 37 | 38 | -------------------------------------------------------------------------------- /src/test/resources/datasets/documents.yml: -------------------------------------------------------------------------------- 1 | users: 2 | - id: 1 3 | name: jackson 4 | mail: jackson@fasterxml.com 5 | password: "$2a$12$rCpWwNOiPtXYAImWxrlF.OPnwJAvi4Adg2l./dYZ5yOl8VsvDBiwy" 6 | ctime: "2019-05-05 18:54:01" 7 | utime: "2019-05-05 18:54:01" 8 | - id: 2 9 | name: appuser 10 | mail: sample@appuser.com 11 | password: "$2a$12$wTi0Vsomd.2Uy5/B0BTF.egDVF1WTGMcVZ4AoBxQgMwGV/EZPN0qu" 12 | ctime: "2019-05-06 01:19:34" 13 | utime: "2019-05-06 01:19:34" 14 | - id: 3 15 | name: admin 16 | mail: admin@cmd.com 17 | password: "$2a$12$YrClkNlU2ATy.UnkPE0dyeLMp2LMqFCA4V51HNTEpe4oA.9Z5DzvS" 18 | ctime: "2019-05-09 14:24:56" 19 | utime: "2019-05-09 14:24:56" 20 | - id: 4 21 | name: wildfly 22 | mail: user@wildfly.org 23 | password: "$2a$12$MOg1cujn5RfrnNxVirB55ufvxlHL8882prZSWjDEg5.fHjl82fpp2" 24 | ctime: "2019-05-11 18:08:16" 25 | utime: "2019-05-11 18:08:16" 26 | - id: 5 27 | name: vuejs 28 | mail: frontend@vuejs.org 29 | password: "$2a$12$ec3hnq3dIDz2uTDcXZwut.7r8LTrMqsa98t4STM8lB2mk4vHrHwse" 30 | ctime: "2019-05-12 21:33:45" 31 | utime: "2019-05-12 21:33:45" 32 | 33 | repos: 34 | - id: 1 35 | fk_users: 1 36 | - id: 2 37 | fk_users: 2 38 | - id: 3 39 | fk_users: 3 40 | - id: 4 41 | fk_users: 4 42 | - id: 5 43 | fk_users: 5 44 | 45 | docs: 46 | - id: 1 47 | name: Testdocument 48 | content: > 49 | # Main Heading 50 | ctime: "2019-05-07 22:23:09" 51 | utime: "2019-06-23 23:47:16" 52 | cuser: 2 53 | fk_repos: 2 54 | uuser: 5 55 | - id: 2 56 | name: Lecture Java EE 57 | content: > 58 | # Java EE 59 | ctime: "2019-06-23 14:53:42" 60 | utime: "2019-06-23 18:00:24" 61 | cuser: 3 62 | fk_repos: 4 63 | uuser: 4 64 | - id: 3 65 | name: Collaborative Markdown Editor 66 | content: > 67 | # This is our Collaborative Markdown Editor 68 | 69 | Insert your document here. 70 | ctime: "2019-06-25 22:24:19" 71 | utime: "2019-06-26 23:37:22" 72 | cuser: 2 73 | fk_repos: 2 74 | uuser: 4 75 | 76 | collaborators: 77 | - id: 1 78 | ctime: "2019-05-11 17:40:10" 79 | has_access: Y 80 | fk_docs: 1 81 | fk_users: 5 82 | - id: 2 83 | ctime: "2019-05-11 17:40:13" 84 | has_access: Y 85 | fk_docs: 1 86 | fk_users: 1 87 | - id: 3 88 | ctime: "2019-05-11 18:26:22" 89 | has_access: Y 90 | fk_docs: 1 91 | fk_users: 4 92 | - id: 4 93 | ctime: "2019-06-24 19:01:18" 94 | has_access: Y 95 | fk_docs: 2 96 | fk_users: 3 97 | - id: 5 98 | ctime: "2019-06-23 14:46:27" 99 | has_access: Y 100 | fk_docs: 2 101 | fk_users: 1 102 | - id: 6 103 | ctime: "2019-06-23 14:47:47" 104 | has_access: Y 105 | fk_docs: 2 106 | fk_users: 5 107 | -------------------------------------------------------------------------------- /src/test/resources/datasets/repos.yml: -------------------------------------------------------------------------------- 1 | repos: 2 | - id: 1 3 | fk_users: 1 4 | - id: 2 5 | fk_users: 2 6 | - id: 3 7 | fk_users: 3 8 | - id: 4 9 | fk_users: 4 10 | - id: 5 11 | fk_users: 5 12 | -------------------------------------------------------------------------------- /src/test/resources/datasets/users.yml: -------------------------------------------------------------------------------- 1 | users: 2 | - id: 1 3 | name: jackson 4 | mail: jackson@fasterxml.com 5 | password: "$2a$12$rCpWwNOiPtXYAImWxrlF.OPnwJAvi4Adg2l./dYZ5yOl8VsvDBiwy" 6 | ctime: "2019-05-05 18:54:01" 7 | utime: "2019-05-05 18:54:01" 8 | - id: 2 9 | name: appuser 10 | mail: sample@appuser.com 11 | password: "$2a$12$wTi0Vsomd.2Uy5/B0BTF.egDVF1WTGMcVZ4AoBxQgMwGV/EZPN0qu" 12 | ctime: "2019-05-06 01:19:34" 13 | utime: "2019-05-06 01:19:34" 14 | - id: 3 15 | name: admin 16 | mail: admin@cmd.com 17 | password: "$2a$12$YrClkNlU2ATy.UnkPE0dyeLMp2LMqFCA4V51HNTEpe4oA.9Z5DzvS" 18 | ctime: "2019-05-09 14:24:56" 19 | utime: "2019-05-09 14:24:56" 20 | - id: 4 21 | name: wildfly 22 | mail: user@wildfly.org 23 | password: "$2a$12$MOg1cujn5RfrnNxVirB55ufvxlHL8882prZSWjDEg5.fHjl82fpp2" 24 | ctime: "2019-05-11 18:08:16" 25 | utime: "2019-05-11 18:08:16" 26 | - id: 5 27 | name: vuejs 28 | mail: frontend@vuejs.org 29 | password: "$2a$12$ec3hnq3dIDz2uTDcXZwut.7r8LTrMqsa98t4STM8lB2mk4vHrHwse" 30 | ctime: "2019-05-12 21:33:45" 31 | utime: "2019-05-12 21:33:45" 32 | -------------------------------------------------------------------------------- /src/test/resources/docker/Dockerfile: -------------------------------------------------------------------------------- 1 | # Specify WildFly version and app home 2 | # directory as arguments 3 | ARG WILDFLY_VERSION=16.0.0.Final 4 | 5 | # 1. Stage: Build frontend and backend 6 | # Use Maven base image 7 | FROM maven:3.6.0-jdk-11-slim AS build 8 | 9 | ENV APP_HOME /app 10 | 11 | COPY frontend ${APP_HOME}/frontend 12 | COPY src ${APP_HOME}/src 13 | COPY pom.xml ${APP_HOME}/pom.xml 14 | 15 | # Set the working directory to the app home directory 16 | WORKDIR ${APP_HOME} 17 | 18 | # Install Git as this is required for a custom 19 | # npm dependency 20 | RUN apt-get update && apt-get --assume-yes install git 21 | 22 | # Build the frontend and backend of the application 23 | RUN mvn install -P deployment -DskipTests=true -Dmaven.javadoc.skip=true -B -V 24 | 25 | # 2. Stage: Configure WildFly and deploy application 26 | # Use the WildFly base image to deploy the built application 27 | FROM jboss/wildfly:${WILDFLY_VERSION} AS server 28 | 29 | ARG buildno 30 | 31 | # Set the working directory to the WildFly home directory 32 | WORKDIR ${JBOSS_HOME} 33 | 34 | # Ports used by the WildFly instance 35 | ENV WEBAPP_PORT 8080/tcp 36 | ENV ADMINISTRATION_PORT 9990/tcp 37 | 38 | # WildFly settings 39 | ENV WILDFLY_USER wildfly 40 | ENV WILDFLY_PASSWORD wildfly 41 | 42 | # MySQL version 43 | ENV MYSQL_VERSION 8.0.15 44 | 45 | # Environment variables for the WildFly datasource 46 | ENV DB_NAME cmd 47 | ENV DB_USER mysqluser 48 | ENV DB_PASSWORD mysqlpass 49 | ENV DB_PORT 3306 50 | ENV DB_URI mysql-instance:${DB_PORT} 51 | 52 | # Path variables 53 | ENV JBOSS_CLI ${JBOSS_HOME}/bin/jboss-cli.sh 54 | ENV DEPLOYMENT_DIR ${JBOSS_HOME}/standalone/deployments 55 | ENV APP_DIR ./collaborative-markdown-editor 56 | 57 | RUN echo "Build Number: ${buildno}" 58 | 59 | # Execute the configuration script to add MySQL driver 60 | # and datasource to the WildFly instance 61 | COPY ./jboss-configure-mysql-datasource.sh . 62 | RUN ./jboss-configure-mysql-datasource.sh 63 | 64 | # Remove the script 65 | RUN rm -f ./jboss-configure-datasource.sh 66 | 67 | # Deploy the application as ROOT app to WildFly 68 | COPY --from=build /app/target/ROOT.war ${DEPLOYMENT_DIR}/ 69 | 70 | # Expose the webapp port 71 | EXPOSE ${WEBAPP_PORT} 72 | 73 | # Start the WildFly 74 | CMD ["/opt/jboss/wildfly/bin/standalone.sh", "-b", "0.0.0.0", "-bmanagement", "0.0.0.0"] 75 | -------------------------------------------------------------------------------- /src/test/resources/docker/docker-compose.yml: -------------------------------------------------------------------------------- 1 | # Docker-Compose file for the Collaborative Markdown Editor 2 | # 3 | # This compose file defines the 'webapp' and 'mysql' services 4 | # which are automatically built using the Dockerfiles and 5 | # configured using external scripts from the repository. 6 | 7 | version: "3" 8 | 9 | services: 10 | arquillian-cube-wildfly-test: 11 | build: src/test/resources/docker 12 | #image: "jboss/wildfly:16.0.0.Final" 13 | #hostname: cmd-wildfly-instance 14 | #container_name: cmd-wildfly-webapp 15 | #buildImage: 16 | # dockerfileLocation: src/test/resources/docker 17 | # dockerfileName: Dockerfile 18 | # noCache: true 19 | # remove: true 20 | # args: 21 | # buildno: 1 22 | ports: 23 | - "8080:8080" 24 | # links: 25 | # - "mysql:mysql-instance" 26 | networks: 27 | - cmd_network 28 | # depends_on: 29 | # - mysql 30 | 31 | # mysql: 32 | # image: "mysql:8.0.15" 33 | #hostname: mysql-instance 34 | #container_name: cmd-mysql-instance 35 | #buildImage: 36 | # dockerfileLocation: src/test/resources/docker 37 | # dockerfileName: Dockerfile.mysql 38 | # noCache: true 39 | # remove: true 40 | # args: 41 | # buildno: 2 42 | # networks: 43 | # - cmd_network 44 | # ports: 45 | # - "3306:3306" 46 | 47 | networks: 48 | cmd_network: 49 | name: cmd_network 50 | -------------------------------------------------------------------------------- /src/test/resources/scripts/disableReferentialIntegrity.sql: -------------------------------------------------------------------------------- 1 | // Disable the referential integrity checks in H2 2 | // required to cleanup the database properly 3 | SET REFERENTIAL_INTEGRITY FALSE; 4 | -------------------------------------------------------------------------------- /src/test/resources/scripts/resetTableIdentities.sql: -------------------------------------------------------------------------------- 1 | // Reset the identity counters of all tables 2 | // after each test to ensure new id counters 3 | // for every following test 4 | 5 | // Reset users.id 6 | ALTER TABLE IF EXISTS `users` 7 | ALTER COLUMN `id` 8 | RESTART WITH 1; 9 | 10 | // Reset repos.id 11 | ALTER TABLE IF EXISTS `repos` 12 | ALTER COLUMN `id` 13 | RESTART WITH 1; 14 | 15 | // Reset docs.id 16 | ALTER TABLE IF EXISTS `docs` 17 | ALTER COLUMN `id` 18 | RESTART WITH 1; 19 | 20 | // Reset collaborators.id 21 | ALTER TABLE IF EXISTS `collaborators` 22 | ALTER COLUMN `id` 23 | RESTART WITH 1; 24 | 25 | // Reset history.id 26 | ALTER TABLE IF EXISTS `history` 27 | ALTER COLUMN `id` 28 | RESTART WITH 1; 29 | -------------------------------------------------------------------------------- /system.properties: -------------------------------------------------------------------------------- 1 | java.runtime.version=11 2 | wildfly.version=16.0.0.Final 3 | postgresql.driver.version=42.2.8 4 | --------------------------------------------------------------------------------