Merge remote-tracking branch 'origin/spike/ATN+HST'

# Conflicts:
#	.gitignore
#	TODO.md
#	tools/todo-progress
#	tools/todo-progress.gnuplot
This commit is contained in:
Michael Hoennig 2022-08-04 11:08:26 +02:00
commit 9945eddf7a
802 changed files with 5793 additions and 92048 deletions

9
.aliases Normal file
View File

@ -0,0 +1,9 @@
alias gw='./gradlew'
alias pg-sql-run='docker run --name hsadmin-ng-postgres -e POSTGRES_PASSWORD=password -p 5432:5432 -d postgres:13.7-bullseye'
alias pg-sql-stop='docker stop hsadmin-ng-postgres'
alias pg-sql-start='docker container start hsadmin-ng-postgres'
alias pg-sql-remove='docker rm hsadmin-ng-postgres'
alias pg-sql-reset='pg-sql-stop; pg-sql-remove; pg-sql-run'
alias pg-sql-backup='docker exec -i hsadmin-ng-postgres /usr/bin/pg_dump --clean --create -U postgres postgres | gzip -9'
alias pg-sql-restore='gunzip --stdout | docker exec -i hsadmin-ng-postgres psql -U postgres -d postgres'

View File

@ -18,7 +18,3 @@ insert_final_newline = true
[*.md]
trim_trailing_whitespace = false
[package.json]
indent_style = space
indent_size = 2

View File

@ -1,5 +0,0 @@
{
"hooks": {
"pre-commit": "lint-staged"
}
}

View File

@ -1,61 +0,0 @@
{
"name": "Asset",
"fields": [
{
"fieldName": "documentDate",
"fieldType": "LocalDate",
"fieldValidateRules": [
"required"
]
},
{
"fieldName": "valueDate",
"fieldType": "LocalDate",
"fieldValidateRules": [
"required"
]
},
{
"fieldName": "action",
"fieldType": "AssetAction",
"fieldValues": "PAYMENT,HANDOVER,ADOPTION,LOSS,CLEARING,PAYBACK",
"fieldValidateRules": [
"required"
]
},
{
"fieldName": "amount",
"fieldType": "BigDecimal",
"fieldValidateRules": [
"required"
]
},
{
"fieldName": "remark",
"fieldType": "String",
"fieldValidateRules": [
"maxlength"
],
"fieldValidateRulesMaxlength": 160
}
],
"relationships": [
{
"relationshipType": "many-to-one",
"otherEntityName": "membership",
"otherEntityRelationshipName": "asset",
"relationshipValidateRules": "required",
"relationshipName": "membership",
"otherEntityField": "admissionDocumentDate"
}
],
"changelogDate": "20190507105335",
"entityTableName": "asset",
"dto": "mapstruct",
"pagination": "infinite-scroll",
"service": "serviceClass",
"jpaMetamodelFiltering": true,
"fluentMethods": true,
"clientRootFolder": "",
"applications": "*"
}

View File

@ -1,154 +0,0 @@
{
"name": "Customer",
"fields": [
{
"fieldName": "reference",
"fieldType": "Integer",
"fieldValidateRules": [
"required",
"unique",
"min",
"max"
],
"fieldValidateRulesMin": 10000,
"fieldValidateRulesMax": 99999
},
{
"fieldName": "prefix",
"fieldType": "String",
"fieldValidateRules": [
"required",
"maxlength",
"unique",
"pattern"
],
"fieldValidateRulesMaxlength": 3,
"fieldValidateRulesPattern": "[a-z][a-z0-9]+"
},
{
"fieldName": "name",
"fieldType": "String",
"fieldValidateRules": [
"required",
"maxlength"
],
"fieldValidateRulesMaxlength": 80
},
{
"fieldName": "kind",
"fieldType": "CustomerKind",
"fieldValues": "NATURAL,LEGAL",
"fieldValidateRules": [
"required"
]
},
{
"fieldName": "birthDate",
"fieldType": "LocalDate"
},
{
"fieldName": "birthPlace",
"fieldType": "String",
"fieldValidateRules": [
"maxlength"
],
"fieldValidateRulesMaxlength": 80
},
{
"fieldName": "registrationCourt",
"fieldType": "String",
"fieldValidateRules": [
"maxlength"
],
"fieldValidateRulesMaxlength": 80
},
{
"fieldName": "registrationNumber",
"fieldType": "String",
"fieldValidateRules": [
"maxlength"
],
"fieldValidateRulesMaxlength": 80
},
{
"fieldName": "vatRegion",
"fieldType": "VatRegion",
"fieldValues": "DOMESTIC,EU,OTHER",
"fieldValidateRules": [
"required"
]
},
{
"fieldName": "vatNumber",
"fieldType": "String",
"fieldValidateRules": [
"maxlength"
],
"fieldValidateRulesMaxlength": 40
},
{
"fieldName": "contractualSalutation",
"fieldType": "String",
"fieldValidateRules": [
"maxlength"
],
"fieldValidateRulesMaxlength": 80
},
{
"fieldName": "contractualAddress",
"fieldType": "String",
"fieldValidateRules": [
"required",
"maxlength"
],
"fieldValidateRulesMaxlength": 400
},
{
"fieldName": "billingSalutation",
"fieldType": "String",
"fieldValidateRules": [
"maxlength"
],
"fieldValidateRulesMaxlength": 80
},
{
"fieldName": "billingAddress",
"fieldType": "String",
"fieldValidateRules": [
"maxlength"
],
"fieldValidateRulesMaxlength": 400
},
{
"fieldName": "remark",
"fieldType": "String",
"fieldValidateRules": [
"maxlength"
],
"fieldValidateRulesMaxlength": 160
}
],
"relationships": [
{
"relationshipType": "one-to-many",
"otherEntityName": "membership",
"otherEntityRelationshipName": "customer",
"relationshipName": "membership"
},
{
"relationshipType": "one-to-many",
"otherEntityName": "sepaMandate",
"otherEntityRelationshipName": "customer",
"relationshipName": "sepamandate"
}
],
"changelogDate": "20190507105332",
"entityTableName": "customer",
"dto": "mapstruct",
"pagination": "infinite-scroll",
"service": "serviceClass",
"jpaMetamodelFiltering": true,
"fluentMethods": true,
"clientRootFolder": "",
"applications": "*"
}

View File

@ -1,66 +0,0 @@
{
"name": "Membership",
"fields": [
{
"fieldName": "admissionDocumentDate",
"fieldType": "LocalDate",
"fieldValidateRules": [
"required"
]
},
{
"fieldName": "cancellationDocumentDate",
"fieldType": "LocalDate"
},
{
"fieldName": "memberFromDate",
"fieldType": "LocalDate",
"fieldValidateRules": [
"required"
]
},
{
"fieldName": "memberUntilDate",
"fieldType": "LocalDate"
},
{
"fieldName": "remark",
"fieldType": "String",
"fieldValidateRules": [
"maxlength"
],
"fieldValidateRulesMaxlength": 160
}
],
"relationships": [
{
"relationshipType": "one-to-many",
"otherEntityName": "share",
"otherEntityRelationshipName": "membership",
"relationshipName": "share"
},
{
"relationshipType": "one-to-many",
"otherEntityName": "asset",
"otherEntityRelationshipName": "membership",
"relationshipName": "asset"
},
{
"relationshipType": "many-to-one",
"otherEntityName": "customer",
"otherEntityRelationshipName": "membership",
"relationshipValidateRules": "required",
"relationshipName": "customer",
"otherEntityField": "prefix"
}
],
"changelogDate": "20190507105333",
"entityTableName": "membership",
"dto": "mapstruct",
"pagination": "infinite-scroll",
"service": "serviceClass",
"jpaMetamodelFiltering": true,
"fluentMethods": true,
"clientRootFolder": "",
"applications": "*"
}

View File

@ -1,84 +0,0 @@
{
"name": "SepaMandate",
"fields": [
{
"fieldName": "reference",
"fieldType": "String",
"fieldValidateRules": [
"maxlength",
"unique",
"required"
],
"fieldValidateRulesMaxlength": 40
},
{
"fieldName": "iban",
"fieldType": "String",
"fieldValidateRules": [
"maxlength"
],
"fieldValidateRulesMaxlength": 34
},
{
"fieldName": "bic",
"fieldType": "String",
"fieldValidateRules": [
"maxlength"
],
"fieldValidateRulesMaxlength": 11
},
{
"fieldName": "grantingDocumentDate",
"fieldType": "LocalDate",
"fieldValidateRules": [
"required"
]
},
{
"fieldName": "revokationDocumentDate",
"fieldType": "LocalDate"
},
{
"fieldName": "validFromDate",
"fieldType": "LocalDate",
"fieldValidateRules": [
"required"
]
},
{
"fieldName": "validUntilDate",
"fieldType": "LocalDate"
},
{
"fieldName": "lastUsedDate",
"fieldType": "LocalDate"
},
{
"fieldName": "remark",
"fieldType": "String",
"fieldValidateRules": [
"maxlength"
],
"fieldValidateRulesMaxlength": 160
}
],
"relationships": [
{
"relationshipType": "many-to-one",
"otherEntityName": "customer",
"otherEntityRelationshipName": "sepamandate",
"relationshipValidateRules": "required",
"relationshipName": "customer",
"otherEntityField": "prefix"
}
],
"changelogDate": "20190507105336",
"entityTableName": "sepa_mandate",
"dto": "mapstruct",
"pagination": "infinite-scroll",
"service": "serviceClass",
"jpaMetamodelFiltering": true,
"fluentMethods": true,
"clientRootFolder": "",
"applications": "*"
}

View File

@ -1,61 +0,0 @@
{
"name": "Share",
"fields": [
{
"fieldName": "documentDate",
"fieldType": "LocalDate",
"fieldValidateRules": [
"required"
]
},
{
"fieldName": "valueDate",
"fieldType": "LocalDate",
"fieldValidateRules": [
"required"
]
},
{
"fieldName": "action",
"fieldType": "ShareAction",
"fieldValues": "SUBSCRIPTION,CANCELLATION",
"fieldValidateRules": [
"required"
]
},
{
"fieldName": "quantity",
"fieldType": "Integer",
"fieldValidateRules": [
"required"
]
},
{
"fieldName": "remark",
"fieldType": "String",
"fieldValidateRules": [
"maxlength"
],
"fieldValidateRulesMaxlength": 160
}
],
"relationships": [
{
"relationshipType": "many-to-one",
"otherEntityName": "membership",
"otherEntityRelationshipName": "share",
"relationshipValidateRules": "required",
"relationshipName": "membership",
"otherEntityField": "admissionDocumentDate"
}
],
"changelogDate": "20190507105334",
"entityTableName": "share",
"dto": "mapstruct",
"pagination": "infinite-scroll",
"service": "serviceClass",
"jpaMetamodelFiltering": true,
"fluentMethods": true,
"clientRootFolder": "",
"applications": "*"
}

View File

@ -1,47 +0,0 @@
{
"name": "UserRoleAssignment",
"fields": [
{
"fieldName": "entityTypeId",
"fieldType": "String",
"fieldValidateRules": [
"required",
"maxlength"
],
"fieldValidateRulesMaxlength": 32
},
{
"fieldName": "entityObjectId",
"fieldType": "Long",
"fieldValidateRules": [
"required"
]
},
{
"fieldName": "assignedRole",
"fieldType": "UserRole",
"fieldValues": "HOSTMASTER,ADMIN,SUPPORTER,CONTRACTUAL_CONTACT,FINANCIAL_CONTACT,TECHNICAL_CONTACT,CUSTOMER_USER",
"fieldValidateRules": [
"required"
]
}
],
"relationships": [
{
"relationshipType": "many-to-one",
"otherEntityName": "user",
"otherEntityRelationshipName": "required",
"relationshipName": "user",
"otherEntityField": "login"
}
],
"changelogDate": "20190507105342",
"entityTableName": "user_role_assignment",
"dto": "no",
"pagination": "infinite-scroll",
"service": "serviceClass",
"jpaMetamodelFiltering": true,
"fluentMethods": true,
"clientRootFolder": "",
"applications": "*"
}

View File

@ -1,3 +0,0 @@
node_modules
target
package-lock.json

View File

@ -1,12 +0,0 @@
# Prettier configuration
printWidth: 140
singleQuote: true
tabWidth: 4
useTabs: false
# js and ts rules:
arrowParens: avoid
# jsx and tsx rules:
jsxBracketSameLine: false

8
.run/00-util.sql.run.xml Normal file
View File

@ -0,0 +1,8 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="00-util.sql" type="DatabaseScript" editBeforeRun="true" nameIsGenerated="true">
<script-file value="$PROJECT_DIR$/sql/00-util.sql" />
<script-mode>FILE</script-mode>
<data-source id="58980aaf-09d7-4782-a6fa-859aa1fc3986" namespace="database/&quot;postgres&quot;/schema/&quot;public&quot;" />
<method v="2" />
</configuration>
</component>

View File

@ -0,0 +1,8 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="10-rbac-base.sql" type="DatabaseScript" editBeforeRun="true" nameIsGenerated="true">
<script-file value="$PROJECT_DIR$/sql/10-rbac-base.sql" />
<script-mode>FILE</script-mode>
<data-source id="58980aaf-09d7-4782-a6fa-859aa1fc3986" namespace="database/&quot;postgres&quot;/schema/&quot;public&quot;" />
<method v="2" />
</configuration>
</component>

View File

@ -0,0 +1,8 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="12-rbac-role-builder.sql" type="DatabaseScript" editBeforeRun="true" nameIsGenerated="true">
<script-file value="$PROJECT_DIR$/sql/12-rbac-role-builder.sql" />
<script-mode>FILE</script-mode>
<data-source id="58980aaf-09d7-4782-a6fa-859aa1fc3986" namespace="database/&quot;postgres&quot;/schema/&quot;public&quot;" />
<method v="2" />
</configuration>
</component>

View File

@ -0,0 +1,8 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="18--rbac-all.sql" type="DatabaseScript" editBeforeRun="true" nameIsGenerated="true">
<script-file value="$PROJECT_DIR$/sql/18--rbac-all.sql" />
<script-mode>FILE</script-mode>
<data-source id="58980aaf-09d7-4782-a6fa-859aa1fc3986" namespace="database/&quot;postgres&quot;/schema/&quot;public&quot;" />
<method v="2" />
</configuration>
</component>

View File

@ -0,0 +1,8 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="20-hs-base.sql" type="DatabaseScript" editBeforeRun="true" nameIsGenerated="true">
<script-file value="$PROJECT_DIR$/sql/20-hs-base.sql" />
<script-mode>FILE</script-mode>
<data-source id="58980aaf-09d7-4782-a6fa-859aa1fc3986" namespace="database/&quot;postgres&quot;/schema/&quot;public&quot;" />
<method v="2" />
</configuration>
</component>

View File

@ -0,0 +1,8 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="21-hs-customer.sql" type="DatabaseScript" editBeforeRun="true" nameIsGenerated="true">
<script-file value="$PROJECT_DIR$/sql/21-hs-customer.sql" />
<script-mode>FILE</script-mode>
<data-source id="58980aaf-09d7-4782-a6fa-859aa1fc3986" namespace="database/&quot;postgres&quot;/schema/&quot;public&quot;" />
<method v="2" />
</configuration>
</component>

View File

@ -0,0 +1,8 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="22-hs-packages.sql" type="DatabaseScript" editBeforeRun="true" nameIsGenerated="true">
<script-file value="$PROJECT_DIR$/sql/22-hs-packages.sql" />
<script-mode>FILE</script-mode>
<data-source id="58980aaf-09d7-4782-a6fa-859aa1fc3986" namespace="database/&quot;postgres&quot;/schema/&quot;public&quot;" />
<method v="2" />
</configuration>
</component>

View File

@ -0,0 +1,8 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="23-hs-unixuser.sql" type="DatabaseScript" editBeforeRun="true" nameIsGenerated="true">
<script-file value="$PROJECT_DIR$/sql/23-hs-unixuser.sql" />
<script-mode>FILE</script-mode>
<data-source id="58980aaf-09d7-4782-a6fa-859aa1fc3986" namespace="database/&quot;postgres&quot;/schema/&quot;public&quot;" />
<method v="2" />
</configuration>
</component>

View File

@ -0,0 +1,8 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="24-hs-domain.sql" type="DatabaseScript" editBeforeRun="true" nameIsGenerated="true">
<script-file value="$PROJECT_DIR$/sql/24-hs-domain.sql" />
<script-mode>FILE</script-mode>
<data-source id="58980aaf-09d7-4782-a6fa-859aa1fc3986" namespace="database/&quot;postgres&quot;/schema/&quot;public&quot;" />
<method v="2" />
</configuration>
</component>

View File

@ -0,0 +1,8 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="25-hs-emailaddress.sql" type="DatabaseScript" editBeforeRun="true" nameIsGenerated="true">
<script-file value="$PROJECT_DIR$/sql/25-hs-emailaddress.sql" />
<script-mode>FILE</script-mode>
<data-source id="58980aaf-09d7-4782-a6fa-859aa1fc3986" namespace="database/&quot;postgres&quot;/schema/&quot;public&quot;" />
<method v="2" />
</configuration>
</component>

View File

@ -0,0 +1,8 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="29-hs-statistics.sql" type="DatabaseScript" editBeforeRun="true" nameIsGenerated="true">
<script-file value="$PROJECT_DIR$/sql/29-hs-statistics.sql" />
<script-mode>FILE</script-mode>
<data-source id="58980aaf-09d7-4782-a6fa-859aa1fc3986" namespace="database/&quot;postgres&quot;/schema/&quot;public&quot;" />
<method v="2" />
</configuration>
</component>

View File

@ -0,0 +1,18 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="run all up to statistics" type="DatabaseScript" editBeforeRun="true">
<script-file value="$PROJECT_DIR$/sql/00-util.sql" />
<script-file value="$PROJECT_DIR$/sql/10-rbac-base.sql" />
<script-file value="$PROJECT_DIR$/sql/12-rbac-role-builder.sql" />
<script-file value="$PROJECT_DIR$/sql/18-rbac-statistics.sql" />
<script-file value="$PROJECT_DIR$/sql/20-hs-base.sql" />
<script-file value="$PROJECT_DIR$/sql/21-hs-customer.sql" />
<script-file value="$PROJECT_DIR$/sql/22-hs-packages.sql" />
<script-file value="$PROJECT_DIR$/sql/23-hs-unixuser.sql" />
<script-file value="$PROJECT_DIR$/sql/24-hs-domain.sql" />
<script-file value="$PROJECT_DIR$/sql/25-hs-emailaddress.sql" />
<script-file value="$PROJECT_DIR$/sql/29-hs-statistics.sql" />
<script-mode>FILE</script-mode>
<data-source id="58980aaf-09d7-4782-a6fa-859aa1fc3986" namespace="database/&quot;postgres&quot;/schema/&quot;public&quot;" />
<method v="2" />
</configuration>
</component>

View File

@ -1,38 +0,0 @@
{
"generator-jhipster": {
"promptValues": {
"packageName": "org.hostsharing.hsadminng",
"nativeLanguage": "de"
},
"jhipsterVersion": "5.8.2",
"applicationType": "monolith",
"baseName": "hsadminNg",
"packageName": "org.hostsharing.hsadminng",
"packageFolder": "org/hostsharing/hsadminng",
"serverPort": "8080",
"authenticationType": "jwt",
"cacheProvider": "ehcache",
"enableHibernateCache": false,
"websocket": false,
"databaseType": "sql",
"devDatabaseType": "h2Memory",
"prodDatabaseType": "postgresql",
"searchEngine": false,
"messageBroker": false,
"serviceDiscoveryType": false,
"buildTool": "gradle",
"enableSwaggerCodegen": true,
"jwtSecretKey": "ZDFlMDUzODIzMTUzZDEwZjExN2E5ZjAzY2VhZmYzNDE1YjhlYWUxZGRhMGU3ODZiNjRkNjVlNzEwZjExYWY4YzczM2NlYzI5YWE1OTRkNWM0YThlYjZjZjA5Zjc5YWJkOTgzYjdhZjQxZWQyZGUyYjFlYjI5ZDE3NmE4M2UzYjQ=",
"clientFramework": "angularX",
"useSass": false,
"clientPackageManager": "npm",
"testFrameworks": ["cucumber"],
"jhiPrefix": "jhi",
"entitySuffix": "",
"dtoSuffix": "DTO",
"otherModules": [],
"enableTranslation": true,
"nativeLanguage": "de",
"languages": ["de", "en"]
}
}

View File

@ -1,196 +0,0 @@
# hsadminNg
This application was generated using JHipster 5.8.2, you can find documentation and help at [https://www.jhipster.tech/documentation-archive/v5.8.2](https://www.jhipster.tech/documentation-archive/v5.8.2).
## Development
Before you can build this project, you must install and configure the following dependencies on your machine:
1. [Node.js][]: We use Node to run a development web server and build the project.
Depending on your system, you can install Node either from source or as a pre-packaged bundle.
After installing Node, you should be able to run the following command to install development tools.
You will only need to run this command when dependencies change in [package.json](package.json).
npm install
We use npm scripts and [Webpack][] as our build system.
Run the following commands in two separate terminals to create a blissful development experience where your browser
auto-refreshes when files change on your hard drive.
./gradlew
npm start
Npm is also used to manage CSS and JavaScript dependencies used in this application. You can upgrade dependencies by
specifying a newer version in [package.json](package.json). You can also run `npm update` and `npm install` to manage dependencies.
Add the `help` flag on any command to see how you can use it. For example, `npm help update`.
The `npm run` command will list all of the scripts available to run for this project.
### Service workers
Service workers are commented by default, to enable them please uncomment the following code.
- The service worker registering script in index.html
```html
<script>
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('./service-worker.js').then(function() {
console.log('Service Worker Registered');
});
}
</script>
```
Note: workbox creates the respective service worker and dynamically generate the `service-worker.js`
### Managing dependencies
For example, to add [Leaflet][] library as a runtime dependency of your application, you would run following command:
npm install --save --save-exact leaflet
To benefit from TypeScript type definitions from [DefinitelyTyped][] repository in development, you would run following command:
npm install --save-dev --save-exact @types/leaflet
Then you would import the JS and CSS files specified in library's installation instructions so that [Webpack][] knows about them:
Edit [src/main/webapp/app/vendor.ts](src/main/webapp/app/vendor.ts) file:
```
import 'leaflet/dist/leaflet.js';
```
Edit [src/main/webapp/content/css/vendor.css](src/main/webapp/content/css/vendor.css) file:
```
@import '~leaflet/dist/leaflet.css';
```
Note: there are still few other things remaining to do for Leaflet that we won't detail here.
For further instructions on how to develop with JHipster, have a look at [Using JHipster in development][].
### Using angular-cli
You can also use [Angular CLI][] to generate some custom client code.
For example, the following command:
ng generate component my-component
will generate few files:
create src/main/webapp/app/my-component/my-component.component.html
create src/main/webapp/app/my-component/my-component.component.ts
update src/main/webapp/app/app.module.ts
### Doing API-First development using openapi-generator
[OpenAPI-Generator]() is configured for this application. You can generate API code from the `src/main/resources/swagger/api.yml` definition file by running:
```bash
./gradlew openApiGenerate
```
Then implements the generated delegate classes with `@Service` classes.
To edit the `api.yml` definition file, you can use a tool such as [Swagger-Editor](). Start a local instance of the swagger-editor using docker by running: `docker-compose -f src/main/docker/swagger-editor.yml up -d`. The editor will then be reachable at [http://localhost:7742](http://localhost:7742).
Refer to [Doing API-First development][] for more details.
## Building for production
To optimize the hsadminNg application for production, run:
./gradlew -Pprod clean bootWar
This will concatenate and minify the client CSS and JavaScript files. It will also modify `index.html` so it references these new files.
To ensure everything worked, run:
java -jar build/libs/*.war
Then navigate to [http://localhost:8080](http://localhost:8080) in your browser.
Refer to [Using JHipster in production][] for more details.
## Testing
To launch your application's tests, run:
./gradlew test
### Client tests
Unit tests are run by [Jest][] and written with [Jasmine][]. They're located in [src/test/javascript/](src/test/javascript/) and can be run with:
npm test
For more information, refer to the [Running tests page][].
### Code quality
Sonar is used to analyse code quality. You can start a local Sonar server (accessible on http://localhost:9001) with:
```
docker-compose -f src/main/docker/sonar.yml up -d
```
Then, run a Sonar analysis:
```
./gradlew -Pprod clean test sonarqube
```
For more information, refer to the [Code quality page][].
## Using Docker to simplify development (optional)
You can use Docker to improve your JHipster development experience. A number of docker-compose configuration are available in the [src/main/docker](src/main/docker) folder to launch required third party services.
For example, to start a postgresql database in a docker container, run:
docker-compose -f src/main/docker/postgresql.yml up -d
To stop it and remove the container, run:
docker-compose -f src/main/docker/postgresql.yml down
You can also fully dockerize your application and all the services that it depends on.
To achieve this, first build a docker image of your app by running:
./gradlew bootWar -Pprod jibDockerBuild
Then run:
docker-compose -f src/main/docker/app.yml up -d
For more information refer to [Using Docker and Docker-Compose][], this page also contains information on the docker-compose sub-generator (`jhipster docker-compose`), which is able to generate docker configurations for one or several JHipster applications.
## Continuous Integration (optional)
To configure CI for your project, run the ci-cd sub-generator (`jhipster ci-cd`), this will let you generate configuration files for a number of Continuous Integration systems. Consult the [Setting up Continuous Integration][] page for more information.
[jhipster homepage and latest documentation]: https://www.jhipster.tech
[jhipster 5.8.2 archive]: https://www.jhipster.tech/documentation-archive/v5.8.2
[using jhipster in development]: https://www.jhipster.tech/documentation-archive/v5.8.2/development/
[using docker and docker-compose]: https://www.jhipster.tech/documentation-archive/v5.8.2/docker-compose
[using jhipster in production]: https://www.jhipster.tech/documentation-archive/v5.8.2/production/
[running tests page]: https://www.jhipster.tech/documentation-archive/v5.8.2/running-tests/
[code quality page]: https://www.jhipster.tech/documentation-archive/v5.8.2/code-quality/
[setting up continuous integration]: https://www.jhipster.tech/documentation-archive/v5.8.2/setting-up-ci/
[node.js]: https://nodejs.org/
[yarn]: https://yarnpkg.org/
[webpack]: https://webpack.github.io/
[angular cli]: https://cli.angular.io/
[browsersync]: http://www.browsersync.io/
[jest]: https://facebook.github.io/jest/
[jasmine]: http://jasmine.github.io/2.0/introduction.html
[protractor]: https://angular.github.io/protractor/
[leaflet]: http://leafletjs.com/
[definitelytyped]: http://definitelytyped.org/
[openapi-generator]: https://openapi-generator.tech
[swagger-editor]: http://editor.swagger.io
[doing api-first development]: https://www.jhipster.tech/documentation-archive/v5.8.2/doing-api-first-development/

57
Jenkinsfile vendored
View File

@ -1,57 +0,0 @@
#!/usr/bin/env groovy
node {
withEnv(["PATH=$HOME/bin:$PATH"]) {
stage('checkout') {
checkout scm
}
stage('check java') {
sh "java -version"
}
stage('clean+spotless') {
sh "chmod +x gradlew"
sh "./gradlew clean spotlessCheck --no-daemon"
}
stage('npm install') {
sh "./gradlew npm_install -PnodeInstall --no-daemon"
}
stage('backend tests') {
try {
sh "./gradlew test -PnodeInstall --no-daemon"
} catch (err) {
throw err
} finally {
junit '**/build/**/TEST-*.xml'
}
}
stage('backend check') {
try {
sh "./gradlew check -PnodeInstall --no-daemon"
} catch (err) {
throw err
} finally {
archiveArtifacts artifacts: '**/build/reports/jacoco/test/html/', fingerprint: true
}
}
stage('frontend tests') {
try {
sh "./gradlew npm_run_test -PnodeInstall --no-daemon"
} catch (err) {
throw err
} finally {
junit '**/build/test-results/TESTS-*.xml'
}
}
stage('packaging') {
sh "./gradlew bootWar -x test -Pprod -PnodeInstall --no-daemon"
archiveArtifacts artifacts: '**/build/libs/*.war', fingerprint: true
}
}
}

26
LICENSE.md Normal file
View File

@ -0,0 +1,26 @@
The MIT License (MIT)
=====================
Copyright ©2022 Michael Hönnig
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the “Software”), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

454
README.md
View File

@ -1,350 +1,332 @@
# hsadminNg Development
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
This documents gives an overview of the development environment and tools.
For architecture consider the files in the `doc` and `adr` folder.
- [Setting up the Development Environment](#setting-up-the-development-environment)
- [Frequent Tasks](#frequent-tasks)
- [Building the Application with Test Execution](#building-the-application-with-test-execution)
- [Starting the Application](#starting-the-application)
- [Running JUnit tests with branch coverage](#running-junit-tests-with-branch-coverage)
- [HOWTO Commits](#howto-commits)
- [Creating HOWTO Commits](#creating-howto-commits)
- [Special Build Tasks](#special-build-tasks)
- [Spotless Formatting](#spotless-formatting)
- [Mutation Testing PiTest](#mutation-testing-pitest)
- [Git Workflow for JHipster Generator](#git-workflow-for-jhipster-generator)
- [Generating the Table of Contents for Markdown](#generating-the-table-of-contents-for-markdown)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
<!-- generated TOC begin: -->
- [Setting up the Development Environment](#setting-up-the-development-environment)
- [SDKMAN](#sdkman)
- [PostgreSQL Server](#postgresql-server)
- [Markdown](#markdown)
- [Render Markdown embedded PlantUML](#render-markdown-embedded-plantuml)
- [Other Tools](#other-tools)
- [Running the SQL files](#running-the-sql-files)
- [For RBAC](#for-rbac)
- [For Historization](#for-historization)
<!-- generated TOC end. -->
## Setting up the Development Environment
You'll often need to execute `./gradlew`, therefore we suggest to define this alias:
All instructions assume that you're using a current _Linux_ or _MacOS_ operating system.
Everything is tested on _Ubuntu Linux 22.04_ and _MacOS Monterey (12.4)_.
alias gw='./gradlew'
To be able to build and run the Java Spring Boot application, you need the following tools:
TODO: Instructions for setting up the dev environment from scratch.
- Docker 20.x (on MacOS you also need *Docker Desktop* or similar)
- PostgreSQL Server 13.7-bullseye (see instructions below to install and run in Docker)
- Java JDK 17.x
- Gradle in some not too outdated version (7.4 will be installed via wrapper)
## Frequent Tasks
You also might need an IDE (e.g. *IntelliJ IDEA* or *Eclipse* or *VS Code* with *[STS](https://spring.io/tools)* and a GUI Frontend for *PostgreSQL* like *Postbird*.
### Building the Application with Test Execution
If you have at least Docker, the Java JDK and Gradle installed in appropriate versions and in your `PATH`, then you can start like this:
gw build
cd your-hsadmin-ng-directory
gradle wrapper # downloads Gradle 7.5 into the project
source .aliases # creates some comforable bash aliases, e.g. 'gw'='./gradlew'
### Starting the Application
gw test # compiles and runs unit- and integration-tests
pg-sql-run # downloads + runs PostgreSQL in a Docker container on localhost:5432
gw bootRun # compiles and runs the application on localhost:8080
To use an **H2 in-memory database** populated with sample-data.
# the following command should reply with "pong":
curl http://localhost:8080/api/ping
gw bootRun
# the following command should return a JSON array with just all customers:
curl \
-H 'current-user: mike@hostsharing.net' \
http://localhost:8080/api/customers
To use an **H2 file-based database**, start the application with the h2file profile:
# the following command should return a JSON array with just all packages visible for the admin of the customer aab:
curl \
-H 'current-user: mike@hostsharing.net' -H 'assumed-roles: customer#aab.admin' \
http://localhost:8080/api/packages
gw bootRun -Ph2file
gw bootRun -Ph2file -Psample-data # populated with sample data
# add a new customer
curl \
-H 'current-user: mike@hostsharing.net' -H "Content-Type: application/json" \
-d '{ "prefix":"baa", "reference":80001, "adminUserName":"admin@baa.example.com" }' \
-X POST http://localhost:8080/api/customers
To use a **local Postgres database**, first prepare your environment:
If you wonder who 'mike@hostsharing.net' and 'sven@hostsharing.net' are and where the data comes from:
Mike and Sven are just example Hostsharing hostmaster accounts as part of the example data which is automatically inserted in Testcontainers and Development environments.
Also try for example 'admin@aaa.example.com' or 'unknown@example.org'.
export HSADMINNG_DB_URL='jdbc:postgresql://localhost:5432/DBNAME'
export HSADMINNG_DB_USER='DBUSER'
export HSADMINNG_DB_PASS='DBPASS'
If you want a formatted JSON output, you can pipe the result to `jq` or similar.
Where `DBNAME`, `DBUSER` and `DBPASS` are replaced by your credentials.
And to see the full, currently implemented, API, open http://localhost:8080/swagger-ui/index.html.
Then start the application with the pgsql profile:
If you still need to install some of these tools, find some hints in the next chapters.
gw bootRun -Ppgsql
gw bootRun -Ppgsql -Psample-data # populated with sample data
To use a **remote Postgres database** on a Hostsharing server,
### SDKMAN
autossh -M 0 -o "ServerAliveInterval 60" -o "ServerAliveCountMax 3" \
-f -N -L 55432:127.0.0.1:5432 "xyz00@xyz.hostsharing.net"
*SdkMan* is not necessary, but helpful to install and switch between different versions of SDKs (Software-Development-Kits) and development tools in general, e.g. *JDK* and *Gradle*.
It is available for _Linux_ and _MacOS_, _WSL_, _Cygwin_, _Solaris_ and _FreeBSD_.
Then prepare your environment, e.g. like this:
You can get it from: https://sdkman.io/.
export HSADMINNG_DB_URL='jdbc:postgresql://localhost:55432/xyz00_hsadminng'
export HSADMINNG_DB_USER='xyz00_hsadminng'
export HSADMINNG_DB_PASS='whatever'
<big>**&#9888;**</big>
Yeah, the `curl ... | bash` install method looks quite scary;
but in a development environment you're downloading executables all the time,
e.g. through `npm`, `Maven` or `Gradle` when downloading dependencies.
Thus, maybe you should at least use a separate Linux account for development.
In all cases, you can also **specify the port** to used for the application via environment:
Once it's installed, you can install *JDK* and *Gradle*:
SERVER_PORT=8081 gw bootRun ...
sdk install java 17.0.3-tem
sdk install gradle
For starting the JVM of the application in **debug-mode**, add `--debug-jvm` to any of the options above, e.g.
sdk use java 17.0.3-tem # use this to switch between installed JDK versions
gw bootRun -Ppgsql -Psample-data --debug-jvm
### Running JUnit tests with branch coverage
### PostgreSQL Server
#### for IntelliJ IDEA
You could use any PostgreSQL Server (from version 13 on) installed on your machine.
You might amend the port and user settings in `src/main/resources/application.yml`, though.
see: https://confluence.jetbrains.com/display/IDEADEV/IDEA+Coverage+Runner
But the easiest way to run PostgreSQL is via Docker.
Either apply it to specific test configurations or,
better, delete the previous test configurations and amend the JUnit template.
Initially, pull an image compatible to current PostgreSQL version of Hostsharing:
## HOWTO Commits
docker pull postgres:13.7-bullseye
There are git tags on some commits which show how to add certain features.
<big>**&#9888;**</big>
If we switch the version, please also amend the documentation as well as the aliases file. Thanks!
Find all of such tags with:
Create and run a container with the given PostgreSQL version:
git tag | grep HOWTO
docker run --name hsadmin-ng-postgres -e POSTGRES_PASSWORD=password -p 5432:5432 -d postgres:13.7-bullseye
### Creating HOWTO Commits
# or via alias:
pg-sql-run
If you want to add such a commit, make sure that it contains no clutter
(no changes which are not necessary for whatever the commit is about to explain),
and is complete with all unit tests, code coverage, pitest and other checks.
Otherwise the next developer would run into the same problems again.
To check if the PostgreSQL container is running, the following command should list a container with the name "hsadmin-ng-postgres":
One way to keep the commit clean, is to develop it on a local branch.
If any other changes (e.g. bugfixes, API extensions etc.) are necessary,
apply these only to the master or cherry-pick just these to the master,
then rebase your local branch. Do not forget to run all checks locally:
docker container ls
gw clean check pitest # might need over an hour
Stop the PostgreSQL container:
docker stop hsadmin-ng-postgres
# or via alias: pg-sql-stop
(Check the PiTest section for speeding up mutation testing.)
Start the PostgreSQL container again:
To create and push a new tag use:
docker container start hsadmin-ng-postgres
# or via alias: pg-sql-start
git tag HOWTO-... master
git push origin HOWTO-...
Remove the PostgreSQL container:
To moved an existing the tag to another commit (here current master again), do this:
docker rm hsadmin-ng-postgres
# or via alias:
pg-sql-remove
git tag --force HOWTO-... master
git push --force origin HOWTO-...
To reset to a clean database, use:
## Special Build Tasks
pg-sql-stop; pg-sql-remove; pg-sql-run
Besides common build tasks like `build`, `test` or `bootRun` this projects has some not so common tasks which are explained in this section.
# or via alias:
pg-sql-reset
### Spotless Formatting
After the PostgreSQL container is removed, you need to create it again as shown in "Create and run ..." above.
To make sure that no IDE auto-formatter destroys the git history of any file and
especially to avoid merge conflicts from JHipster generated files after these had been changed,
we are using a standard formatter enforced by _spotless_, which is based on the standard Eclipse formatter.
Given the container is running, to create a backup in ~/backup, run:
The rules can be checked and applied with these commands:
docker exec -i hsadmin-ng-postgres /usr/bin/pg_dump --clean --create -U postgres postgres | gzip -9 > ~/backup/hsadmin-ng-postgres.sql.gz
gw spotlessCheck
gw spotlessApply
# or via alias:
pg-sql-backup >~/backup/hsadmin-ng-postgres.sql.gz
The spotlessCheck task is included as an early step in our Jenkins build pipeline.
Therefore wrong formatting is automatically detected.
Our configuration can be found under the directory `cfg/spotless`.
Currently we only have specific rules for _\*.java_-files and their import-order.
Again, given the container is running, to restore the backup from ~/backup, run:
#### Our Changes to the Standard Eclipse Formatter
gunzip --stdout --keep ~/backup/hsadmin-ng-postgres.sql.gz | docker exec -i hsadmin-ng-postgres psql -U postgres -d postgres
We amended the Standard Eclipse Formatter in these respects:
# or via alias:
pg-sql-restore <~/backup/hsadmin-ng-postgres.sql.gz
- Lines of code are never joined, thus the developer has control about linebreaks,
which is important for readability in some implementations like toString().
- Lines in comments are never joined either, because that often destroys readable stucture.
- Parts of files can be excluded from getting formatted, by using `@formatter:off` and `@formatter:on` in a comment.
See for example in class `SecurityConfiguration`.
#### Pre-Commit Hook
### Markdown
If you like, you could add this code to the _pre-commit or \_pre_push_ hook\_ in your `.git/hooks` directory:
To generate the TOC (Table of Contents), a little bash script from a
[Blog Article](https://medium.com/@acrodriguez/one-liner-to-generate-a-markdown-toc-f5292112fd14) was used.
if ! ./gradlew spotlessCheck; then
exit 1
fi
To render the Markdown files, especially to watch embedded PlantUML diagrams, you can use one of the following methods:
#### The Tagged Spotless Commit
#### Render Markdown embedded PlantUML
The commit which introduces the spotless configuration is tagged.
Through this tag it can easily be cherry-picked in the JHipster workflow.
Can you see the following diagram right in your IDE?
I mean a real graphic diagram, not just some markup code.
If you need to amend the commit tagged 'spotless', e.g. to change the spotless configuration,
it can be done with these steps:
```plantuml
@startuml
me -> you: Can you see this diagram?
you -> me: Sorry, I don't :-(
me -> you: Install some tooling!
@enduml
```
git tag REAL-HEAD
git reset --hard spotless^
git cherry-pick -n spotless
...
git add .
# do NOT run: gw spotlessApply yet!
# for the case you have a commit hook which runs spotlessCheck:
git commit --no-verify
git tag --force spotless
git push --no-verify origin spotless
git reset --hard REAL-HEAD
git tag -d REAL-HEAD
If not, you need to install some tooling.
### Mutation Testing PiTest
##### for IntelliJ IDEA (or derived products)
./gradlew pitest
You just need the bundled Markdown plugin enabled and install and activate the PlantUML plugin in its settings:
Runs (almost) all JUnit tests under mutation testing.
Mutation testing is a means to determine the quality of the tests.
jetbrains://idea/settings?name=Languages+%26+Frameworks--Markdown
On Jenkins, the results can be found in the build artifacts under:
You might also need to install Graphviz on your operating system.
For Debian-based Linux systems this might work:
- https://ci.hostsharing.net/job/hsadmin-ng-pitest/XX/artifact/build/reports/pitest/index.html
```sh
sudo apt install graphviz
```
Where XX is the build number. Or for the latest build under:
- https://ci.hostsharing.net/job/hsadmin-ng-pitest/lastCompletedBuild/artifact/build/reports/pitest/index.html
##### Ubuntu Linux command line
#### Some Background Information on Mutation Testing
```sh
sudo apt-get install pandoc texlive-latex-base texlive-fonts-recommended texlive-extra-utils texlive-latex-extra pandoc-plantuml-filter
```
PiTest does it with these steps:
```sh
pandoc --filter pandoc-plantuml rbac.md -o rbac.pdf
```
- initially PiTest checks which production code is executed by which tests
- if the tests don't pass, it stops
- otherwise the production code is 'mutated' and PiTest checks whether this makes a test fail ('mutant killed')
- Finally it checks thresholds for coverage and mutant killing.
##### for other IDEs / operating systems
More information about can be found here:
If you have figured out how it works, please add instructions above this section.
- PiTest: http://pitest.org/
- gradle-plugin: https://gradle-pitest-plugin.solidsoft.info/
### Other Tools
#### How to Configure PiTest
**jq**: a JSON formatter.
On _Debian_'oid systems you can install it with `sudo apt-get install jq`.
On _MacOS_ you can install it with `brew install jq`, given you have _brew_ installed.
These thresholds can be configured in `build.gradle`,
but we should generally not lower these.
## Running the SQL files
There is also a list of excluded files, all generated by JHipster or MapStruct, not containing any changes by us.
### For RBAC
As you might figure, mutation testing is CPU-hungry.
To limit load in our Jenkins build server, it only uses 2 CPU threads, thus it needs over an hour.
The Schema is automatically created via *Liquibase*, a database migration library.
Currently, also some test data is automatically created.
If you want to spend more CPU threads on your local system, you can change that via command line:
To increase the amount of test data, increase the number of generated customers in `2022-07-28-051-hs-customer.sql` and run that
gw pitest -Doverride.pitest.threads=7
If you already have data, e.g. for customers 0..999 (thus with reference numbers 10000..10999) and want to add another 1000 customers, amend the for loop to 1000...1999 and also uncomment and amend the `CONTINUE WHEN` or `WHERE` conditions in the other test data generators, using the first new customer reference number (in the example that's 11000).
I suggest to leave one CPU thread for other tasks or your might lag extremely.
### For Historization
### Git Workflow for JHipster Generator
The historization is not yet integrated into the *Liquibase*-scripts.
You can explore the prototype as follows:
The following workflow steps make sure that
- start with an empty database
(the example tables are currently not compatible with RBAC),
- then run `historization.sql` in the database,
- finally run `examples.sql` in the database.
- JHipster re-imports work properly,
- the git history of changes to the JDL-files, the generated code and the master is comprehensible,
- and merging newly generated code to the master branch is smooth.
## Coding Guidelines
It uses a git branch `jhipster-generated` to track the history of the JDL model file and the generated source code.
Applying commits which contain non-generated changes to that branch breaks the normal git history for generated files.
Therefore, this documentation is also not available in that branch.
Thus:
### Directory and Package Structure
**MANUAL STEP before starting:** Copy this workflow documentation, because this file will be gone once you switched the branch.
Generally, the standard Java directory structure is used, where productive and test code are sparated like this:
| WARNING: The following steps are just a guideline. You should understand what you are doing! |
| -------------------------------------------------------------------------------------------- |
```
src
main/
java/
net.hostsharing.hasadminng/
resources/
test/
java/
net.hostsharing.hasadminng/
resources/
```
The Java package structure below contains:
#### 1. Preparing the `jhipster-generated` git Branch
- config and global (utility) packages,
these should not access any other packages within the project
- rbac, containing all packages related to the RBAC subsystem
- hs, containing Hostsharing business object related packages
This step assumes that the latest `*.jdl` files are on the `HEAD` of the `jhipster-generated` git branch.
On a re-import of a JDL-file, JHipster does not remove any generated classes which belong to entities deleted from the JDL-file.
Therefore, the project has to be reset to a clean state before changes to the JDL file can be re-imported.
We have not yet finally tested a simplified workflow for just adding new entities or properties.
Underneath of rbac and hs, the structure is business oriented, NOT technical / layer -oriented.
A git tag `jdl-base` is assumed to sit on the base commit after the application was generated, but before any entities were imported.
Some of these rules are checked with *ArchUnit* unit tests.
git checkout jhipster-generated
git pull
git tag REAL-HEAD
git reset --hard jdl-base
git clean -f -d
git cherry-pick -n spotless
git reset --soft REAL-HEAD
git checkout REAL-HEAD src/main/jdl/customer.jdl
git checkout REAL-HEAD src/main/jdl/accessrights.jdl
git checkout REAL-HEAD src/main/jdl/... # once there are more
git tag -d REAL-HEAD
### Spotless Code Formatting
#### 2. Amending and Re-Importing the JDL
Code formatting for Java is checked via *spotless*.
The formatting style can be checked with this command:
**MANUAL STEP:** First apply all necessary changes to the JDL files.
Then re-import like this:
```shell
gw spotlessCheck
```
# (Re-) Importing
jhipster import-jdl src/main/jdl/customer.jdl
jhipster import-jdl src/main/jdl/accessrights.jdl
jhipster import-jdl src/main/jdl/... # once there are more
This task is also included in `gw build`.
For smoothly being able to merge, we need the same formatting in the generated code as on the master:
To apply formatting rules, use:
gw spotlessApply
```shell
gw spotlessApply
```
#### 3. Committing our Changes
git add .
git commit -m"..."
## How To
#### 4. Merging our Changes to the `master` Branch
### How to Use a Persistent Database for Integration Tests?
git checkout master
git pull
Usually, the `DataJpaTest` integration tests run against a database in a temporary docker container.
As soon as the test ends, the database is gone; this might make debugging difficult.
**MANUAL STEP:** If you've renamed any identifiers, use the refactoring feature of your IDE to rename in master as well.
To avoid oodles of merge-conflicts, you need to do that **BEFORE MERGING!**
Commit any of such changes, if any.
Alternatively
Now we can finally merge our changes to master.
If the persistent database and the temporary database show different results, one of these reasons could be the cause:
git merge jhipster-generated
1. You might have some changesets only running in either context,
check the `context: ...` in the changeset control lines.
2. You might have changes in the database which interfere with the tests,
e.g. from a previous run of tests or manually applied.
It's best to run `pg-sql-reset && gw bootRun` before each test run, to have a clean database.
## How to Amend Liquibase SQL Changesets?
It's a good idea doing this step in an IDE because it makes conflict resolving much easier.
Typical merge conflicts stem from:
Liquibase changesets are meant to be immutable and based on each other.
That means, once a changeset is written, it never changes, not even a whitespace or comment.
Liquibase is a *database migration tool*, not a *database initialization tool*.
- Random numbers in test data of `*IntTest.java` files.
- Timestamps in Liquibase-xml-Files.
This, if you need to add change a table, stored procedure or whatever,
create a new changeset and apply `ALTER`, `DROP`, `CREATE OR REPLACE` or whatever SQL commands to perform your changes.
These changes will be automatically applied once the application starts up again.
This way, any staging or production database will always match the application code.
Now, I suggest to run all tests locally:
But, during initial development that can be a big hassle because the database structure changes a lot in that stage.
Also, the actual structure of the database won't be easily recognized anymore through lots of migration changesets.
gw clean test
Therefore, during initial development, it's good approach just to amend the existing changesets and delete the database:
Once everything works again, we can push our new version:
```shell
pg-sql-reset
gw bootRun
```
git push
<big>**&#9888;**</big>
Just don't forget switching to the migration mode, once there is a production database!
#### 5. General Aftermath
## Further Documentation
Think about which additional code could be effected by your JDL-changes!
Files which are not at all in the `jhipster-generated` branch, don't show conflicts even though they might need changes.
Here some examples for amendments to be done:
- in `historicization_*.xml`: the columns or their constraints
- `sampledata/*.xml/csv`
If you find more of such general cases, please add them here!
#### 6. Special Aftermath for new Entities
Because we have added quite some functionality, after introducing new entities, there is a lot more to amend.
Here some issues to consider:
- add sample-data for the new entity
- internal (Angular) frontend: add table filters
- internal (Angular) frontend: amend input fields for multiline, if applicable
- internal (Angular) frontend: check if dates are properly formatted
- \*Mapper: add displayLabel for entity itself and parents
- \*DTO: add access-right annotations with customized JSON serializer/deserializer
- Validator: implement entity-based validator and call it in the generated service
- external API: add new type to client library
WARNING: This list is most likely incomplete. Pleas add any new found issue!
For many of these issues look for HOWTO-commits in git or HOWTO comments in the source code.
### Generating the Table of Contents for Markdown
This README file contains a table of contents generated by _doctoc_.
It's quite simple to use:
npm install -g doctoc
doctoc --maxlevel 3 README.md
Further information can be found [https://github.com/thlorenz/doctoc/blob/master/README.md](on the _doctoc_ github page).
- the `doc` directory contains architecture concepts and a glossary
- TODO.md tracks requirements and progress for the contract of the initial project,
please do not amend anything in this document

BIN
TODO-progress.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

View File

@ -1,39 +0,0 @@
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "projects",
"projects": {
"hsadmin-ng": {
"root": "",
"sourceRoot": "src/main/webapp",
"projectType": "application",
"architect": {}
}
},
"defaultProject": "hsadmin-ng",
"cli": {
"packageManager": "npm"
},
"schematics": {
"@schematics/angular:component": {
"inlineStyle": true,
"inlineTemplate": false,
"spec": false,
"prefix": "jhi",
"styleExt": "css"
},
"@schematics/angular:directive": {
"spec": false,
"prefix": "jhi"
},
"@schematics/angular:guard": {
"spec": false
},
"@schematics/angular:pipe": {
"spec": false
},
"@schematics/angular:service": {
"spec": false
}
}
}

View File

@ -1,24 +0,0 @@
// Behaviour tests based on a deployed application.
task cucumberTest(type: Test) {
description = "Execute cucumber BDD tests."
group = "verification"
include '**/CucumberTest*'
// uncomment if the tests reports are not generated
// see https://github.com/jhipster/generator-jhipster/pull/2771 and https://github.com/jhipster/generator-jhipster/pull/4484
// ignoreFailures true
reports.html.enabled = false
}
check.dependsOn cucumberTest
task testReport(type: TestReport) {
destinationDir = file("$buildDir/reports/tests")
reportOn test
}
task cucumberTestReport(type: TestReport) {
destinationDir = file("$buildDir/reports/tests")
reportOn cucumberTest
}

View File

@ -1,84 +0,0 @@
// Checks code coverage of JUnit based tests.
apply plugin: 'jacoco'
jacoco {
toolVersion = "0.8.3a"
}
test.finalizedBy jacocoTestReport
check.dependsOn jacocoTestCoverageVerification
// Only for purely JHipster/MapStruct generated classes.
// Please do NOT add any self coded classes!
// Keep in mind, git will blame you ;-)
def jhipsterGeneratedClassesWithDecentCoverage = [
'org.hostsharing.hsadminng.repository.CustomAuditEventRepository',
'org.hostsharing.hsadminng.service.ContactQueryService',
'org.hostsharing.hsadminng.service.UserService',
'org.hostsharing.hsadminng.service.CustomerContactQueryService'
]
// Only for purely JHipster/MapStruct generated classes.
// Please do NOT add any self coded classes!
// Keep in mind, git will blame you ;-)
def jhipsterGeneratedClassesWithLowCoverage = [
'org.hostsharing.hsadminng.service.MailService',
'org.hostsharing.hsadminng.security.SecurityUtils',
'org.hostsharing.hsadminng.config.DefaultProfileUtil',
'org.hostsharing.hsadminng.config.WebConfigurer',
'org.hostsharing.hsadminng.web.rest.AccountResource',
'org.hostsharing.hsadminng.web.rest.errors.ExceptionTranslator',
'org.hostsharing.hsadminng.web.rest.errors.CustomParameterizedException',
'org.hostsharing.hsadminng.config.audit.AuditEventConverter',
'org.hostsharing.hsadminng.security.jwt.TokenProvider',
'org.hostsharing.hsadminng.aop.logging.LoggingAspect',
'org.hostsharing.hsadminng.HsadminNgApp',
'*.*QueryService',
'*.*Configuration',
'*MapperImpl',
'*Criteria',
'*_'
]
def specialExceptions = [
// lots of unreachable code due to error handling / verifications
'org.hostsharing.hsadminng.service.accessfilter.JSonAccessFilter',
'org.hostsharing.hsadminng.service.util.ReflectionUtil'
]
jacocoTestCoverageVerification {
violationRules {
rule {
element = 'CLASS'
limit {
counter = 'BRANCH'
value = 'COVEREDRATIO'
// Increasing the threshold is fine, decreasing is not.
// Keep in mind, git will blame you ;-)
minimum = 0.95
}
excludes = jhipsterGeneratedClassesWithDecentCoverage + jhipsterGeneratedClassesWithLowCoverage + specialExceptions
}
rule {
element = 'CLASS'
limit {
counter = 'BRANCH'
value = 'COVEREDRATIO'
minimum = 0.80
}
includes = jhipsterGeneratedClassesWithDecentCoverage
}
rule {
element = 'CLASS'
limit {
counter = 'LINE'
value = 'COVEREDRATIO'
minimum = 0.85
}
includes = specialExceptions
}
}
}

View File

@ -1,36 +0,0 @@
// PiTest based mutation testing
pitest {
targetClasses = ['org.hostsharing.hsadminng.*']
excludedClasses = [
// Unit Testing Spring configurations makes little sense in most cases.
'org.hostsharing.hsadminng.config.*',
'org.hostsharing.hsadminng.ApplicationWebXml',
'org.hostsharing.hsadminng.HsadminNgApp',
// Unit testing this would need PowerMock and
// blackbox testing of random values has little value.
'org.hostsharing.hsadminng.service.util.RandomUtil',
// The following are mostly generated classes,
// as soon as we amend these, consider removing the exclude.
'org.hostsharing.hsadminng.**Criteria',
'org.hostsharing.hsadminng.**MapperImpl',
'org.hostsharing.hsadminng.aop.logging.*',
'org.hostsharing.hsadminng.web.rest.vm.*',
'org.hostsharing.hsadminng.security.jwt.TokenProvider',
'org.hostsharing.hsadminng.web.api.*' // API helpers, not the API itself
]
threads = 2
// Do not set these limit lower! 96% each might sound great, but keep in mind:
// 91%*91% means that ~8% of the code are NOT properly covered by automated tests
// (100%-94%*96% = ~8%). Not counting defects which come through missing code :-)
coverageThreshold = 94
mutationThreshold = 96
outputFormats = ['XML', 'HTML']
timestampedReports = false
verbose = false
}

View File

@ -1,15 +0,0 @@
// apply and check standard formatting of files
apply plugin: "com.diffplug.gradle.spotless"
spotless {
// automatically format source files, see https://github.com/diffplug/spotless
java {
licenseHeader '// Licensed under Apache-2.0'
importOrderFile 'cfg/spotless/hsadminng.importorder'
eclipse().configFile 'cfg/spotless/eclipse_formatter.xml'
target 'src/main/**/*.java', 'src/test/**/*.java'
removeUnusedImports()
endWithNewline()
}
}

View File

@ -1,297 +1,68 @@
import org.gradle.internal.os.OperatingSystem
buildscript {
repositories {
mavenLocal()
mavenCentral()
maven { url "http://repo.spring.io/plugins-release" }
maven { url "https://plugins.gradle.org/m2/" }
}
dependencies {
classpath "org.springframework.boot:spring-boot-gradle-plugin:${spring_boot_version}"
classpath "io.spring.gradle:propdeps-plugin:0.0.10.RELEASE"
classpath "org.openapitools:openapi-generator-gradle-plugin:3.3.0"
classpath "gradle.plugin.com.gorylenko.gradle-git-properties:gradle-git-properties:1.5.2"
//jhipster-needle-gradle-buildscript-dependency - JHipster will add additional gradle build script plugins here
classpath "com.diffplug.spotless:spotless-plugin-gradle:3.22.0"
classpath 'org.owasp:dependency-check-gradle:4.0.2'
}
}
plugins {
id "org.sonarqube" version "2.6.2"
id "net.ltgt.apt-eclipse" version "0.19"
id "net.ltgt.apt-idea" version "0.19"
id "net.ltgt.apt" version "0.19"
id "io.spring.dependency-management" version "1.0.6.RELEASE"
id "com.moowork.node" version "1.2.0"
id 'org.liquibase.gradle' version '2.0.1'
id 'info.solidsoft.pitest' version '1.4.0'
//jhipster-needle-gradle-plugins - JHipster will add additional gradle plugins here
id 'java'
id 'org.springframework.boot' version '2.7.2'
id 'io.spring.dependency-management' version '1.0.12.RELEASE'
id "com.diffplug.spotless" version "6.9.0"
}
apply plugin: 'java'
apply plugin: 'org.owasp.dependencycheck'
sourceCompatibility=1.8
targetCompatibility=1.8
apply plugin: 'maven'
apply plugin: 'org.springframework.boot'
apply plugin: 'war'
apply plugin: 'propdeps'
apply plugin: 'com.moowork.node'
apply plugin: 'io.spring.dependency-management'
apply plugin: 'idea'
apply from: 'build-jacoco.gradle'
apply from: 'build-pitest.gradle'
apply from: 'build-spotless.gradle'
idea {
module {
excludeDirs += files('node_modules')
}
}
dependencyManagement {
imports {
mavenBom 'io.github.jhipster:jhipster-dependencies:' + jhipster_dependencies_version
//jhipster-needle-gradle-dependency-management - JHipster will add additional dependencies management here
}
}
defaultTasks 'bootRun'
group = 'org.hostsharing.hsadminng'
group = 'net.hostsharing'
version = '0.0.1-SNAPSHOT'
description = ''
bootWar {
mainClassName = 'org.hostsharing.hsadminng.HsadminNgApp'
}
war {
webAppDirName = 'build/www/'
enabled = true
extension = 'war.original'
}
springBoot {
mainClassName = 'org.hostsharing.hsadminng.HsadminNgApp'
}
if (OperatingSystem.current().isWindows()) {
// https://stackoverflow.com/questions/40037487/the-filename-or-extension-is-too-long-error-using-gradle
task classpathJar(type: Jar) {
dependsOn configurations.runtime
appendix = 'classpath'
doFirst {
manifest {
attributes 'Class-Path': configurations.runtime.files.collect {
it.toURI().toURL().toString().replaceFirst(/file:\/+/, '/').replaceAll(' ', '%20')
}.join(' ')
}
}
}
bootRun {
dependsOn classpathJar
doFirst {
classpath = files("$buildDir/classes/java/main", "$buildDir/resources/main", classpathJar.archivePath)
}
}
}
test {
exclude '**/CucumberTest*'
// uncomment if the tests reports are not generated
// see https://github.com/jhipster/generator-jhipster/pull/2771 and https://github.com/jhipster/generator-jhipster/pull/4484
// ignoreFailures true
reports.html.enabled = false
}
task cucumberTest(type: Test) {
description = "Execute cucumber BDD tests."
group = "verification"
include '**/CucumberTest*'
// uncomment if the tests reports are not generated
// see https://github.com/jhipster/generator-jhipster/pull/2771 and https://github.com/jhipster/generator-jhipster/pull/4484
// ignoreFailures true
reports.html.enabled = false
}
check.dependsOn cucumberTest
task testReport(type: TestReport) {
destinationDir = file("$buildDir/reports/tests")
reportOn test
}
task cucumberTestReport(type: TestReport) {
destinationDir = file("$buildDir/reports/tests")
reportOn cucumberTest
}
apply from: 'gradle/docker.gradle'
apply from: 'gradle/sonar.gradle'
apply from: 'gradle/swagger.gradle'
//jhipster-needle-gradle-apply-from - JHipster will add additional gradle scripts to be applied here
if (project.hasProperty('prod')) {
apply from: 'gradle/profile_prod.gradle'
} else {
apply from: 'gradle/profile_dev.gradle'
}
if (!project.hasProperty('runList')) {
project.ext.runList = 'main'
}
project.ext.diffChangelogFile = 'src/main/resources/config/liquibase/changelog/' + new Date().format('yyyyMMddHHmmss') + '_changelog.xml'
liquibase {
activities {
main {
driver ''
url ''
username 'hsadminNg'
password ''
changeLogFile 'src/main/resources/config/liquibase/master.xml'
defaultSchemaName ''
logLevel 'debug'
classpath 'src/main/resources/'
if (project.hasProperty('sample-data')) {
contexts 'sample-data'
}
}
diffLog {
driver ''
url ''
username 'hsadminNg'
password ''
changeLogFile project.ext.diffChangelogFile
referenceUrl 'hibernate:spring:org.hostsharing.hsadminng.domain?dialect=&amp;hibernate.physical_naming_strategy=org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy&amp;hibernate.implicit_naming_strategy=org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy'
defaultSchemaName ''
logLevel 'debug'
classpath "$buildDir/classes/java/main"
}
}
runList = project.ext.runList
}
sourceCompatibility = '17'
configurations {
providedRuntime
compile.exclude module: "spring-boot-starter-tomcat"
compileOnly {
extendsFrom annotationProcessor
}
}
repositories {
mavenLocal()
mavenCentral()
jcenter()
//jhipster-needle-gradle-repositories - JHipster will add additional repositories
}
ext {
set('testcontainersVersion', "1.17.3")
}
// wrapper
dependencies {
// Use ", version: jhipster_dependencies_version, changing: true" if you want
// to use a SNAPSHOT release instead of a stable release
compile group: "io.github.jhipster", name: "jhipster-framework"
compile "org.springframework.boot:spring-boot-starter-cache"
compile "io.dropwizard.metrics:metrics-core"
compile 'io.micrometer:micrometer-registry-prometheus'
compile "net.logstash.logback:logstash-logback-encoder"
compile "com.fasterxml.jackson.datatype:jackson-datatype-hppc"
compile "com.fasterxml.jackson.datatype:jackson-datatype-jsr310"
compile "com.fasterxml.jackson.datatype:jackson-datatype-hibernate5"
compile "com.fasterxml.jackson.core:jackson-annotations"
compile "com.fasterxml.jackson.core:jackson-databind"
compile "com.fasterxml.jackson.module:jackson-module-afterburner"
compile "javax.cache:cache-api"
compile "org.hibernate:hibernate-core"
compile "com.zaxxer:HikariCP"
compile "org.apache.commons:commons-lang3"
compile "commons-io:commons-io"
compile "javax.transaction:javax.transaction-api"
compile "org.ehcache:ehcache"
compile "org.hibernate:hibernate-entitymanager"
compile "org.hibernate:hibernate-envers"
compile "org.hibernate.validator:hibernate-validator"
compile "org.liquibase:liquibase-core"
compile "com.mattbertolini:liquibase-slf4j"
liquibaseRuntime "org.liquibase:liquibase-core"
liquibaseRuntime "org.liquibase.ext:liquibase-hibernate5:${liquibase_hibernate5_version}"
liquibaseRuntime sourceSets.main.compileClasspath
compile "org.springframework.boot:spring-boot-loader-tools"
compile "org.springframework.boot:spring-boot-starter-mail"
compile "org.springframework.boot:spring-boot-starter-logging"
compile "org.springframework.boot:spring-boot-starter-actuator"
compile "org.springframework.boot:spring-boot-starter-aop"
compile "org.springframework.boot:spring-boot-starter-data-jpa"
compile "org.springframework.boot:spring-boot-starter-security"
compile ("org.springframework.boot:spring-boot-starter-web") {
exclude module: 'spring-boot-starter-tomcat'
}
compile "org.springframework.boot:spring-boot-starter-undertow"
compile "org.springframework.boot:spring-boot-starter-thymeleaf"
compile "org.zalando:problem-spring-web:0.24.0-RC.0"
compile "org.springframework.boot:spring-boot-starter-cloud-connectors"
compile "org.springframework.security:spring-security-config"
compile "org.springframework.security:spring-security-data"
compile "org.springframework.security:spring-security-web"
compile "io.jsonwebtoken:jjwt-api"
runtime "io.jsonwebtoken:jjwt-impl"
runtime "io.jsonwebtoken:jjwt-jackson"
compile ("io.springfox:springfox-swagger2") {
exclude module: 'mapstruct'
}
compile "io.springfox:springfox-bean-validators"
compile "org.postgresql:postgresql"
liquibaseRuntime "org.postgresql:postgresql"
compile "org.mapstruct:mapstruct-jdk8:${mapstruct_version}"
annotationProcessor "org.mapstruct:mapstruct-processor:${mapstruct_version}"
annotationProcessor "org.hibernate:hibernate-jpamodelgen"
annotationProcessor ("org.springframework.boot:spring-boot-configuration-processor") {
exclude group: 'com.vaadin.external.google', module: 'android-json'
}
testCompile "com.jayway.jsonpath:json-path"
testCompile "io.cucumber:cucumber-junit"
testCompile "io.cucumber:cucumber-spring"
testCompile ("org.springframework.boot:spring-boot-starter-test") {
exclude group: 'com.vaadin.external.google', module: 'android-json'
}
testCompile "org.springframework.security:spring-security-test"
testCompile "org.springframework.boot:spring-boot-test"
testCompile "org.assertj:assertj-core"
testCompile "junit:junit"
testCompile "org.mockito:mockito-core"
testCompile "com.mattbertolini:liquibase-slf4j"
testCompile "org.hamcrest:hamcrest-library"
testCompile "com.h2database:h2"
liquibaseRuntime "com.h2database:h2"
//jhipster-needle-gradle-dependency - JHipster will add additional dependencies here
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-data-rest'
implementation 'org.springframework.boot:spring-boot-starter-jdbc'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springdoc:springdoc-openapi-ui:1.6.9'
implementation 'org.liquibase:liquibase-core'
implementation 'com.vladmihalcea:hibernate-types-55:2.17.1'
compileOnly 'org.projectlombok:lombok'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
runtimeOnly 'org.postgresql:postgresql'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.testcontainers:testcontainers'
testImplementation 'org.testcontainers:junit-jupiter'
testImplementation 'org.testcontainers:postgresql'
testImplementation 'com.tngtech.archunit:archunit-junit5:1.0.0-rc1'
}
task cleanResources(type: Delete) {
delete 'build/resources'
}
wrapper {
gradleVersion = '4.10.2'
}
task stage(dependsOn: 'bootWar') {
}
if (project.hasProperty('nodeInstall')) {
node {
version = "${node_version}"
npmVersion = "${npm_version}"
yarnVersion = "${yarn_version}"
download = false
dependencyManagement {
imports {
mavenBom "org.testcontainers:testcontainers-bom:${testcontainersVersion}"
}
}
tasks.named('test') {
useJUnitPlatform()
}
spotless {
java {
removeUnusedImports()
endWithNewline()
toggleOffOn()
}
}

View File

@ -1,315 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<profiles version="13">
<profile kind="CodeFormatterProfile" name="Eclipse Formatter" version="13">
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_ellipsis" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_for_statment" value="common_lines"/>
<setting id="org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_method_invocation" value="common_lines"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_after_imports" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_switch_statement" value="common_lines"/>
<setting id="org.eclipse.jdt.core.formatter.comment.format_javadoc_comments" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.indentation.size" value="4"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_enum_constant_declaration" value="common_lines"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.disabling_tag" value="@formatter:off"/>
<setting id="org.eclipse.jdt.core.formatter.continuation_indentation" value="2"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_enum_constants" value="49"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_imports" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_after_package" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_binary_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_if_while_statement" value="common_lines"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant" value="82"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.indent_root_tags" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.enabling_tag" value="@formatter:on"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.count_line_length_from_starting_position" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_parameterized_type_references" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_enum_constant" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column" value="false"/>
<setting id="org.eclipse.jdt.core.compiler.problem.enumIdentifier" value="error"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.indent_statements_compare_to_block" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.line_length" value="128"/>
<setting id="org.eclipse.jdt.core.formatter.use_on_off_tags" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_method_declaration" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch" value="82"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_binary_expression" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_catch_clause" value="common_lines"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call" value="82"/>
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_block" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_lambda_body" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.compact_else_if" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration" value="48"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_type_parameters" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation" value="48"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.compiler.problem.assertIdentifier" value="error"/>
<setting id="org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_try" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_binary_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_unary_operator" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer" value="48"/>
<setting id="org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_annotation" value="common_lines"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_ellipsis" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_try_resources" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.format_line_comments" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.align_type_members_on_columns" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_assignment" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_module_statements" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_conditional_expression" value="48"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_block_in_case" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.format_header" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression" value="48"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while" value="insert"/>
<setting id="org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode" value="enabled"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_method_declaration" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.join_wrapped_lines" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_before_conditional_operator" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.align_fields_grouping_blank_lines" value="2147483647"/>
<setting id="org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_resources_in_try" value="82"/>
<setting id="org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_try_clause" value="common_lines"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation" value="80"/>
<setting id="org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column" value="false"/>
<setting id="org.eclipse.jdt.core.compiler.source" value="1.8"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.tabulation.size" value="4"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.format_source_code" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_try" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_try_resources" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_field" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer" value="2"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_method" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_before_assignment_operator" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement" value="do not insert"/>
<setting id="org.eclipse.jdt.core.compiler.codegen.targetPlatform" value="1.8"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_switch" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_type_annotation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.format_html" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_method_delcaration" value="common_lines"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_compact_if" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.indent_empty_lines" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_type_arguments" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_unary_operator" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation" value="48"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_label" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_member_type" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression" value="48"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_try" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.format_block_comments" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_lambda_arrow" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration" value="48"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.indent_statements_compare_to_body" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_multiple_fields" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_array_initializer" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_before_binary_operator" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch" value="do not insert"/>
<setting id="org.eclipse.jdt.core.compiler.compliance" value="1.8"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_lambda_declaration" value="common_lines"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_enum_constant" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_type_declaration" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_package" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_expressions_in_for_loop_header" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_lambda_arrow" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.join_lines_in_comments" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.indent_parameter_description" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.tabulation.char" value="space"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_between_import_groups" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.lineSplit" value="128"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch" value="insert"/>
</profile>
</profiles>

View File

@ -1,8 +0,0 @@
#Organize Import Order
6=javax
5=java
4=org
3=com
2=
1=org.hostsharing
0=\#

View File

@ -1,267 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<profiles version="11">
<profile kind="CodeFormatterProfile" name="CleanCode JS-Formatter" version="11">
<setting id="org.eclipse.wst.jsdt.core.formatter.alignment_for_binary_expression" value="16"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_opening_brace_in_block" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_comma_in_enum_declarations" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.brace_position_for_enum_declaration" value="end_of_line"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_opening_paren_in_annotation" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.never_indent_block_comments_on_first_column" value="false"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.comment.format_header" value="false"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_comma_in_method_declaration_throws" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.compact_else_if" value="true"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment" value="false"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_closing_paren_in_for" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_opening_paren_in_synchronized" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.align_type_members_on_columns" value="false"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.blank_lines_before_first_class_body_declaration" value="0"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.keep_else_statement_on_same_line" value="false"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_opening_paren_in_while" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body" value="0"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call" value="16"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_colon_in_object_initializer" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_comma_in_type_arguments" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_new_line_after_comma_in_objlit_initializer" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_opening_paren_in_switch" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_comma_in_for_increments" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.wrap_before_binary_operator" value="true"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_closing_paren_in_method_declaration" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.put_empty_statement_on_new_line" value="true"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration" value="16"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_comma_in_allocation_expression" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_comma_in_type_parameters" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_unary_operator" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.alignment_for_arguments_in_enum_constant" value="16"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.indentation.size" value="2"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.brace_position_for_annotation_type_declaration" value="end_of_line"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_closing_paren_in_switch" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.alignment_for_assignment" value="0"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_opening_paren_in_for" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_opening_paren_in_enum_constant" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.alignment_for_multiple_fields" value="16"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.alignment_for_selector_in_method_invocation" value="16"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_and_in_type_parameter" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.comment.insert_new_line_for_parameter" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_comma_in_for_inits" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_opening_brace_in_type_declaration" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_closing_paren_in_enum_constant" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.compiler.compliance" value="1.5"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_between_empty_braces_in_array_initializer" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.alignment_for_parameters_in_constructor_declaration" value="16"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_new_line_after_opening_brace_in_objlit_initializer" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_comma_in_array_initializer" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.blank_lines_after_package" value="1"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.blank_lines_before_method" value="1"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.compiler.problem.assertIdentifier" value="error"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.brace_position_for_constructor_declaration" value="end_of_line"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_comma_in_allocation_expression" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.tabulation.size" value="2"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_comma_in_annotation" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_semicolon" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_opening_bracket_in_array_reference" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_opening_paren_in_annotation" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_at_in_annotation_type_declaration" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_closing_paren_in_method_invocation" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_closing_paren_in_synchronized" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_colon_in_default" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.alignment_for_superclass_in_type_declaration" value="16"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.brace_position_for_enum_constant" value="end_of_line"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.comment.format_source_code" value="true"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.indent_statements_compare_to_block" value="true"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_at_in_annotation_type_declaration" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_opening_paren_in_catch" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_opening_paren_in_while" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_colon_in_for" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.alignment_for_superinterfaces_in_type_declaration" value="16"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.continuation_indentation" value="2"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_closing_paren_in_annotation" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_opening_paren_in_method_invocation" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_parenthesized_expression_in_throw" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_colon_in_case" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.blank_lines_after_imports" value="1"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.keep_empty_objlit_initializer_on_one_line" value="false"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.alignment_for_expressions_in_array_initializer" value="16"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.indent_switchstatements_compare_to_switch" value="false"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.keep_then_statement_on_same_line" value="false"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_new_line_in_empty_method_body" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_new_line_before_else_in_if_statement" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_binary_operator" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.blank_lines_between_import_groups" value="1"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_parenthesized_expression_in_return" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.blank_lines_before_field" value="0"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header" value="true"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_colon_in_labeled_statement" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.compiler.source" value="1.5"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.alignment_for_conditional_expression" value="80"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_binary_operator" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_opening_brace_in_array_initializer" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.comment.insert_new_line_before_root_tags" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.comment.format_block_comments" value="true"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_new_line_before_catch_in_try_statement" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.comment.format_html" value="true"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.blank_lines_before_package" value="0"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_new_line_in_empty_type_declaration" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_between_empty_parens_in_method_declaration" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.brace_position_for_objlit_initializer" value="end_of_line"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_opening_paren_in_enum_constant" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_assignment_operator" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_opening_paren_in_if" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_opening_paren_in_catch" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_comma_in_type_arguments" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_opening_paren_in_for" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.alignment_for_arguments_in_allocation_expression" value="16"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_comma_in_annotation" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression" value="16"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_comma_in_for_increments" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.keep_empty_array_initializer_on_one_line" value="false"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header" value="true"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header" value="true"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.blank_lines_before_new_chunk" value="1"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_new_line_before_finally_in_try_statement" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_opening_brace_in_method_declaration" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_colon_in_for" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_between_brackets_in_array_type_reference" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.brace_position_for_block" value="end_of_line"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_closing_paren_in_cast" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.indent_empty_lines" value="false"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_closing_paren_in_while" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.blank_lines_before_member_type" value="1"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.indent_breaks_compare_to_cases" value="true"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.blank_lines_before_imports" value="1"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_colon_in_conditional" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_colon_in_object_initializer" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_opening_bracket_in_array_reference" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_comma_in_superinterfaces" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_comma_in_type_parameters" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_unary_operator" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_opening_paren_in_if" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.blank_lines_between_type_declarations" value="0"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.indent_switchstatements_compare_to_cases" value="true"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_between_empty_parens_in_method_invocation" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_opening_paren_in_synchronized" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.brace_position_for_switch" value="end_of_line"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_semicolon_in_for" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.indent_statements_compare_to_body" value="true"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_colon_in_case" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_new_line_before_while_in_do_statement" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_new_line_in_empty_block" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_comma_in_for_inits" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.comment.clear_blank_lines_in_block_comment" value="false"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_new_line_in_empty_enum_constant" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_closing_brace_in_block" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.indent_body_declarations_compare_to_type_header" value="true"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_new_line_at_end_of_file_if_missing" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.brace_position_for_anonymous_type_declaration" value="end_of_line"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_new_line_before_closing_brace_in_objlit_initializer" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.comment.indent_root_tags" value="false"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_colon_in_assert" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_question_in_conditional" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_opening_paren_in_method_declaration" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.brace_position_for_block_in_case" value="end_of_line"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_opening_paren_in_method_declaration" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_postfix_operator" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_closing_paren_in_if" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_opening_brace_in_switch" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.comment.format_javadoc_comments" value="true"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.alignment_for_arguments_in_method_invocation" value="16"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_colon_in_assert" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_comma_in_superinterfaces" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_new_line_in_empty_annotation_declaration" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_closing_paren_in_catch" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.continuation_indentation_for_objlit_initializer" value="1"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_comma_in_array_initializer" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_colon_in_conditional" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.format_guardian_clause_on_one_line" value="false"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.comment.format_line_comments" value="false"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_opening_paren_in_method_invocation" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_opening_brace_in_enum_constant" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.compiler.codegen.targetPlatform" value="1.5"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_closing_bracket_in_array_reference" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_semicolon_in_for" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.brace_position_for_array_initializer" value="end_of_line"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.lineSplit" value="180"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.tabulation.char" value="space"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_at_in_annotation" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.alignment_for_throws_clause_in_method_declaration" value="16"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_new_line_in_empty_enum_declaration" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_postfix_operator" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.keep_imple_if_on_one_line" value="false"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_comma_in_method_declaration_throws" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_opening_paren_in_cast" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_ellipsis" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.use_tabs_only_for_leading_indentations" value="false"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_closing_paren_in_cast" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.brace_position_for_type_declaration" value="end_of_line"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_question_in_conditional" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_new_line_after_annotation" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_ellipsis" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_closing_brace_in_array_initializer" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_opening_paren_in_switch" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_colon_in_labeled_statement" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.alignment_for_enum_constants" value="0"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.brace_position_for_method_declaration" value="end_of_line"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_assignment_operator" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.alignment_for_compact_if" value="16"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.comment.line_length" value="80"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.alignment_for_parameters_in_method_declaration" value="48"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_prefix_operator" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.continuation_indentation_for_array_initializer" value="2"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.number_of_empty_lines_to_preserve" value="1"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_comma_in_enum_declarations" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments" value="insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.never_indent_line_comments_on_first_column" value="false"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration" value="16"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_before_prefix_operator" value="do not insert"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.comment.indent_parameter_description" value="false"/>
<setting id="org.eclipse.wst.jsdt.core.formatter.insert_space_between_empty_parens_in_enum_constant" value="do not insert"/>
</profile>
</profiles>

View File

@ -0,0 +1,160 @@
# Use VIEWs with JOIN into Permission-Assignments for Row-Level-Security
**Status:**
- [x] proposed by Michael Hönnig
- [ ] accepted by (Participants)
- [ ] rejected by (Participants)
- [ ] superseded by (superseding ADR)
## Context and Problem Statement
We need to decide how to apply the access rules defined in our RBAC system to the visibility of table rows for the accessing user.
The core problem here is, that in our RBAC system, determining the permissions of the accessing user has to consider a hierarchy of roles.
### Technical Background
The session variable `hsadminng.currentUser` contains the accessing (domain-level) user, which is unrelated to the PostgreSQL user).
Given is a stored function `isPermissionGrantedToSubject` which detects if the accessing user has a given permission (e.g. 'view').
Given is also a stored function `queryAllPermissionsOfSubjectId` which returns the flattened view to all permissions assigned to the given accessing user.
In the following code snippets `customer` is just an example domain table.
## Considered Options
* Perform Visibility-Checks programmatically in the Backend
* Add Visibility-Checks in the Backend
* POLICY with ENABLE ROW LEVEL SECURITY
* VIEW-RULE with ON SELECT DO INSTEAD
* VIEW with JOIN into Flattened Permissions
### Perform Visibility-Checks programmatically in the Backend
In this solution, the database ignores row level visibility and returns all rows which match a given query. Afterwards, the result is filtered programmatically with Java-code in the backend.
#### Advantages
Very flexible access, programmatic, rules could be implemented.
The role-hierarchy and permissions for currently logged-in users user could be cached in the backend.
The access logic can be tested in pure Java unit tests.
At least regarding this aspect, an in-memory database could be used for integration testing; though the recursive Role-evaluation uses PostgreSQL features anyway.
#### Disadvantages
It's inefficient when initial query is not very restrictive, e.g. as on overview pages in a frontend, which often show all accessible objects, large parts or even whole database tables need to be transferred from the database to the backend.
It's error-prone and security leaks can happen too easily, because after every query the access rights for all participating joins have to be considered.
### Add Visibility-Checks in the Backend
In this solution again, the database ignores row level visibility and returns all rows which match a given query. And the backend adds filter conditions to each query sent to the database.
#### Advantages
At least regarding this aspect, an in-memory database could be used for integration testing.
#### Disadvantages
It's error-prone and security leaks can happen too easily, because for every query the access rights for all participating joins have to be considered.
### POLICY with ENABLE ROW LEVEL SECURITY
For restricted DB-users, which are used by the backend, access to rows is filtered using a policy:
SET SESSION AUTHORIZATION DEFAULT;
CREATE ROLE restricted;
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO restricted;
ALTER TABLE customer ENABLE ROW LEVEL SECURITY;
CREATE POLICY customer_policy ON customer
FOR SELECT
TO restricted
USING (
isPermissionGrantedToSubject(findPermissionId('customer', id, 'view'), currentUserId())
);
SET SESSION AUTHORIZATION restricted;
SET hsadminng.currentUser TO 'alex@example.com';
SELECT * from customer; -- will only return visible rows
#### Advantages
Using POLICY together with ENABLE ROW LEVEL SECURITY is the PostgreSQL native mechanism to control access to data on the role level. Therefore, it looked like an obvious and elegant solution.
Every access at from the backend is under access control at the database level.
### Disadvantages
Unfortunately security mechanisms in PostgreSQL prevent the query optimizer to work well beyond ownership barriers (session user vs. table owner) and a SELECT from a table with 1 million objects needed over 30 seconds with our hierarchical RBAC policy.
We are bound to PostgreSQL, including integration tests and testing the RBAC system itself.
### VIEW-RULE with ON SELECT DO INSTEAD
SET SESSION SESSION AUTHORIZATION DEFAULT;
CREATE VIEW cust_view AS
SELECT * FROM customer;
CREATE OR REPLACE RULE "_RETURN" AS
ON SELECT TO cust_view
DO INSTEAD
SELECT * FROM customer WHERE isPermissionGrantedToSubject(findPermissionId('customer', id, 'view'), currentUserId());
SET SESSION AUTHORIZATION restricted;
SET hsadminng.currentUser TO 'alex@example.com';
SELECT * from customer; -- will only return visible rows
#### Advantages
Every access at from the backend is under access control at the database level.
Also using ON UPDATE etc., original tables could be completely hidden from the backend, and thus improved security.
### Disadvantages
Unfortunately security mechanisms in PostgreSQL prevent the query optimizer to work well beyond ownership barriers (session user vs. table owner) and a SELECT from a table with 1 million objects needed over 30 seconds with our hierarchical RBAC policy.
We are bound to PostgreSQL, including integration tests and testing the RBAC system itself.
An extra view needed for every table.
### VIEW with JOIN into flattened permissions
We do not access the tables directly from the backend, but via views which join the flattened permissions
SET SESSION SESSION AUTHORIZATION DEFAULT;
CREATE OR REPLACE VIEW cust_view AS
SELECT c.id, c.reference, c.prefix
FROM customer AS c
JOIN queryAllPermissionsOfSubjectId(currentUserId()) AS p
ON p.tableName='customer' AND p.rowId=c.id AND p.op='view';
GRANT ALL PRIVILEGES ON cust_view TO restricted;
SET SESSION SESSION AUTHORIZATION restricted;
SET hsadminng.currentUser TO 'alex@example.com';
SELECT * from cust_view; -- will only return visible rows
Alternatively the JOIN could also be applied in a "ON SELECT DO INSTEAD"-RULE, if there is any advantage for later features.
#### Advantages
Every access at from the backend is under access control at the database level.
No special PostgreSQL features needed; though the recursive Role-evaluation uses PostgreSQL features anyway.
Very fast, on my laptop a SELECT * FROM a table with 1 million rows just took about 50ms.
Also using ON UPDATE etc., original tables could be completely hidden from the backend, and thus improved security.
### Disadvantages
An extra view needed for every table.
## Decision Outcome
We chose the option **"VIEW with JOIN into flattened permissions"** because it supports the best combination of performance and security with almost no disadvantge.

64
doc/glossary.md Normal file
View File

@ -0,0 +1,64 @@
### hsadminNg Glossary
This is a collection of terms used in this project, which either might not be generally known or unclear in meaning.
If you miss something, please add it with a `TODO` marker.
#### Blackbox-Test
A blackbox-test does not know and not consider such internals of an implementation, it just tests externally observable behaviour.
#### Business Object
Used in the RBAC-system to refer to an object from the business realm.
The usual term is *domain object* but in our context, the term *domain* could be too easily confused with a DNS *Internet domain*.
#### Dummy
A *dummy* is a kind of *Test-Double* which replaces a real dependency which is not really needed in the test case.
#### Fake
A *fake* is a kind of *Test-Double* without using any library, but rather a manual fake implementation of a dependency.
#### Mock
A *mock* is a kind of *Test-Double* which can be configured to behaviours as needed by a test-case.
Often the term "mock" is used in a generic way, because typical mocking libraries like *Mockito* can also be used as dummies or spies and can replace fakes.
#### RBAC
Abbreviation for *Role Based Access Control*.
A system to control access to business objects by defining users, roles, and permissions.
See also [The INCITS 359-2012 Standard](https://www.techstreet.com/standards/incits-359-2012?product_id=1837530).
In our case we are implementing a hierarchical RBAC for a hierarchical and dynamic business object structure.
More information can be found in our [RBAC Architecture Document](rbac.md).
#### Tenant
*Tenant* is one of the standard roles of Hostsharing's RBAC system.
It is assigned as a sub-role to those who have rights on sub-objects of a business object.
Usually, tenants can only view the contents.
Generally, tenant roles only apply for the mere existence, id and name of a business object,
not for internal details.
E.g. a tenant of a customer could be the administrator of a hosting package of that customer.
They can view some identifying information of that customer, but not view their billing and banking information.
#### Whitebox-Test
A whitebox-test knows and considers the internals of an implementation, e.g. it knows which dependencies it needs and can test special, implementation-dependent cases.
#### Test-Double
A "double" is a general term for something which replaces a real implementation of a dependency of the unit under test.
This can be a "dummy", a "fake", a "mock", a "spy" or a "stub".

635
doc/rbac.md Normal file
View File

@ -0,0 +1,635 @@
## *hsadmin-ng*'s Role-Based-Access-Management (RBAC)
The requirements of *hsadmin-ng* include table-m row- and column-level-security for read and write access to business-objects.
More precisely, any access has to be controlled according to given rules depending on the accessing users, their roles and the accessed business-object.
Further, roles and business-objects are hierarchical.
To avoid misunderstandings, we are using the term "business-object" what's usually called a "domain-object".
But as we are in the context of a webhosting infrastructure provider, "domain" would have a double meaning.
Our implementation is based on Role-Based-Access-Management (RBAC) in conjunction with views and triggers on the business-objects.
As far as possible, we are using the same terms as defined in the RBAC standard, for our function names though, we chose more expressive names.
In RBAC, subjects can be assigned to roles, roles can be hierarchical and eventually have assigned permissions.
A permission allows a specific operation (e.g. view or edit) on a specific (business-) object.
You can find the entity structure as a UML class diagram as follows:
```plantuml
@startuml
' left to right direction
top to bottom direction
' hide the ugly E in a circle left to the entity name
hide circle
' use right-angled line routing
skinparam linetype ortho
package RBAC {
' forward declarations
entity RbacUser
together {
entity RbacRole
entity RbacPermission
RbacUser -[hidden]> RbacRole
RbacRole -[hidden]> RbacUser
}
together {
entity RbacGrant
enum RbacReferenceType
entity RbacReference
}
RbacReference -[hidden]> RbacReferenceType
entity RbacGrant {
ascendantUuid: uuid(RbackReference)
descendantUuid: uuid(RbackReference)
auto
}
RbacGrant o-u-> RbacReference
RbacGrant o-u-> RbacReference
enum RbacReferenceType {
RbacUser
RbacRole
RbacPermission
}
RbacReferenceType ..> RbacUser
RbacReferenceType ..> RbacRole
RbacReferenceType ..> RbacPermission
entity RbacReference {
*uuid : uuid <<generated>>
--
type : RbacReferenceType
}
RbacReference o--> RbacReferenceType
entity RbacUser {
*uuid : uuid <<generated>>
--
name : varchar
}
RbacUser o-- RbacReference
entity RbacRole {
*uuid : uuid(RbacReference)
--
name : varchar
}
RbacRole o-- RbacReference
together {
enum RbacOperation
entity RbacObject
}
entity RbacPermission {
*uuid : uuid(RbacReference)
--
objectUuid: RbacObject
op: RbacOperation
}
RbacPermission o-- RbacReference
RbacPermission o-- RbacOperation
RbacPermission *-- RbacObject
enum RbacOperation {
add-package
add-domain
add-unixuser
...
view
edit
delete
}
entity RbacObject {
*uuid : uuid <<generated>>
--
objectTable: varchar
}
RbacObject o- "Business Objects"
}
package "Business Objects" {
entity package
package *--u- RbacObject
entity customer
customer *--u- RbacObject
entity "..." as moreBusinessObjects
moreBusinessObjects *-u- RbacObject
}
@enduml
```
### The RBAC Entity Types
#### RbacReference
An *RbacReference* is a generalization of all entity types which participate in the hierarchical role system, defined via *RbacGrant*.
The primary key of the *RbacReference* and its referred object is always identical.
#### RbacReferenceType
The enum *RbacReferenceType* describes the type of reference.
It's only needed to make it easier to find the referred object in *RbacUser*, *RbacRole* or *RbacPermission*.
#### RbacUser
An *RbacUser* is a type of RBAC-subject which references a login account outside this system, identified by a name (usually an email-address).
*RbacUser*s can be assigned to multiple *RbacRole*s, through which they can get permissions to *RbacObject*s.
The primary key of the *RbacUser* is identical to its related *RbacReference*.
#### RbacRole
An *RbacRole* represents a collection of directly or indirectly assigned *RbacPermission*s.
Each *RbacRole* can be assigned to *RbacUser*s or to another *RbacRole*.
Both kinds of assignments are represented via *RbacGrant*.
*RbacRole* entities can *RbacObject*s, or more precise
#### RbacPermission
An *RbacPermission* allows a specific *RbacOperation* on a specific *RbacObject*.
#### RbacOperation
An *RbacOperation* determines, <u>what</u> an *RbacPermission* allows to do.
It can be one of:
- **'add-...'** - permits creating new instances of specific entity types underneath the object specified by the permission, e.g. "add-package"
- **'view'** - permits reading the contents of the object specified by the permission
- **'edit'** - change the contents of the object specified by the permission
- **'delete'** - delete the object specified by the permission
- **'\*'**
This list is extensible according to the needs of the access rule system.
Please notice, that there is no **create** operation to create new instances of unrelated business-object-types.
For such a singleton business-object-type, e.g. *Organization" or "Hostsharing" has to be defined, and its single entity is referred in the permission.
Only with this rule, the foreign key in *RbacPermission* can be defined as `NOT NULL`.
#### RbacGrant
The *RbacGrant* entities represent the access-rights structure from *RbacUser*s via hierarchical *RbacRoles* down to *RbacPermission*s.
The core SQL queries to determine access rights are all recursive queries on the *RbacGrant* table.
### Role naming
The naming pattern of a role is important to be able to address specific roles.
E.g. if a new package is added, the admin-role of the related customer has to be addressed.
There can be global roles like 'administrators'.
Most roles, though, are specific for certain business-objects and automatically generated as such:
business-object-table#business-object-name.relative-role
Where *business-object-table* is the name of the SQL table of the business object (e.g *customer* or 'package'),
*business-object-name* is generated from an immutable business key(e.g. a prefix like 'xyz' or 'xyz00')
and the *relative-role*' describes the role relative to the referenced business-object as follows:
#### owner
The owner-role is granted to the subject which created the business object.
E.g. for a new *customer* it would be granted to 'administrators' and for a new *package* to the 'customer#...admin'.
Whoever has the owner-role assigned can do everything with the related business-object, including deleting (or deactivating) it.
In most cases, the permissions to other operations than 'delete' are granted through the 'admin' role.
By this, all roles ob sub-objects, which are assigned to the 'admin' role, are also granted to the 'owner'.
#### admin
The admin-role is granted to a role of those subjects who manage the business object.
E.g. a 'package' is manged by the admin of the customer.
Whoever has the admin-role assigned, do everything with the related business-object, including deleting (or deactivating) it.
In most cases, the permissions to the 'view' operation is granted through the 'tenant' role.
By this, all roles ob sub-objects, which are assigned to the 'tenent' role, are also granted to the 'admin'.
#### tenant
The tenant-role is granted to everybody who needs to be able to view the business-object.
Usually all owners, admins and tenants of sub-objects get this role granted.
Some business-objects only have very limited data directly in the main business-object and store more sensitive data in special sub-objects (e.g. 'customer-details') to which tenants of sub-objects of the main-object (e.g. package admins) do not get view permission.
## Example Users, Roles, Permissions and Business-Objects
The following diagram shows how users, roles and permissions could be granted access to operations on business objects.
```plantuml
@startuml
' left to right direction
top to bottom direction
' hide the ugly E in a circle left to the entity name
hide circle
' use right-angled line routing
' skinparam linetype ortho
package RbacUsers {
object UserMike
object UserSuse
object UserPaul
}
package RbacRoles {
object RoleAdministrators
object RoleCustXyz_Owner
object RoleCustXyz_Admin
object RolePackXyz00_Owner
}
RbacUsers -[hidden]> RbacRoles
package RbacPermissions {
object PermCustXyz_View
object PermCustXyz_Edit
object PermCustXyz_Delete
object PermCustXyz_AddPackage
object PermPackXyz00_View
object PermPackXyz00_Edit
object PermPackXyz00_Delete
object PermPackXyz00_AddUser
}
RbacRoles -[hidden]> RbacPermissions
package BusinessObjects {
object CustXyz
object PackXyz00
}
RbacPermissions -[hidden]> BusinessObjects
UserMike o---> RoleAdministrators
UserSuse o--> RoleCustXyz_Admin
UserPaul o--> RolePackXyz00_Owner
RoleAdministrators o..> RoleCustXyz_Owner
RoleCustXyz_Owner o-> RoleCustXyz_Admin
RoleCustXyz_Admin o-> RolePackXyz00_Owner
RoleCustXyz_Owner o--> PermCustXyz_Edit
RoleCustXyz_Owner o--> PermCustXyz_Delete
RoleCustXyz_Admin o--> PermCustXyz_View
RoleCustXyz_Admin o--> PermCustXyz_AddPackage
RolePackXyz00_Owner o--> PermPackXyz00_View
RolePackXyz00_Owner o--> PermPackXyz00_Edit
RolePackXyz00_Owner o--> PermPackXyz00_Delete
RolePackXyz00_Owner o--> PermPackXyz00_AddUser
PermCustXyz_View o--> CustXyz
PermCustXyz_Edit o--> CustXyz
PermCustXyz_Delete o--> CustXyz
PermCustXyz_AddPackage o--> CustXyz
PermPackXyz00_View o--> PackXyz00
PermPackXyz00_Edit o--> PackXyz00
PermPackXyz00_Delete o--> PackXyz00
PermPackXyz00_AddUser o--> PackXyz00
@enduml
```
## Business-Object-Tables, Triggers and Views
To support the RBAC system, for each business-object-table, some more artifacts are created in the database:
- a `BEFORE INSERT TRIGGER` which creates the related *RbacObject* instance,
- an `AFTER INSERT TRIGGER` which creates the related *RbacRole*s, *RbacPermission*s together with their related *RbacReference*s as well as *RbacGrant*s,
- a restricted view (e.g. *customer_rv*) through which restricted users can access the underlying data.
Not yet implemented, but planned are these actions:
- an `ON DELETE ... DO INSTEAD` rule to allow `SQL DELETE` if applicable for the business-object-table and the user has 'delete' permission,
- an `ON UPDATE ... DO INSTEAD` rule to allow `SQL UPDATE` if the user has 'edit' right,
- an `ON INSERT ... DO INSTEAD` rule to allow `SQL INSERT` if the user has 'add-..' right to the parent-business-object.
The restricted view takes the current user from a session property and applies the hierarchy of its roles all the way down to the permissions related to the respective business-object-table.
This way, each user can only view the data they have 'view'-permission for, only create those they have 'add-...'-permission, only update those they have 'edit'- and only delete those they have 'delete'-permission to.
### Current User
The current use is taken from the session variable `hsadminng.currentUser` which contains the name of the user as stored in the
*RbacUser*s table. Example:
SET LOCAL hsadminng.currentUser = 'mike@hostsharing.net';
That user is also used for historicization and audit log, but which is a different topic.
### Assuming Roles
If the session variable `hsadminng.assumedRoles` is set to a non-empty value, its content is interpreted as a list of semicolon-separated role names.
Example:
SET LOCAL hsadminng.assumedRoles = 'customer#aab.admin;customer#aac.admin';
In this case, not the current user but the assumed roles are used as a starting point for any further queries.
Roles which are not granted to the current user, directly or indirectly, cannot be assumed.
### Example
A full example is shown here:
BEGIN TRANSACTION;
SET SESSION SESSION AUTHORIZATION restricted;
SET LOCAL hsadminng.currentUser = 'mike@hostsharing.net';
SET LOCAL hsadminng.assumedRoles = 'customer#aab.admin;customer#aac.admin';
SELECT c.prefix, p.name as "package", ema.localPart || '@' || dom.name as "email-address"
FROM emailaddress_rv ema
JOIN domain_rv dom ON dom.uuid = ema.domainuuid
JOIN unixuser_rv uu ON uu.uuid = dom.unixuseruuid
JOIN package_rv p ON p.uuid = uu.packageuuid
JOIN customer_rv c ON c.uuid = p.customeruuid;
END TRANSACTION;
## Roles and Their Assignments for Certain Business Objects
To give you an overview of the business-object-types for the following role-examples,
check this diagram:
```plantuml
@startuml
left to right direction
' top to bottom direction
' hide the ugly E in a circle left to the entity name
hide circle
' use right-angled line routing
' skinparam linetype ortho
entity EMailAddress
entity Domain
Domain o-- "*" EMailAddress
entity UnixUser
UnixUser o-- "*" Domain
entity Package
Package o.. "*" UnixUser
entity Customer
Customer o-- "*" Package
@enduml
```
It's mostly an example hierarchy of business-object-types, but resembles a part of Hostsharing's actual hosting infrastructure.
The following diagrams show which roles are created for each business-object-type
and how they relate to roles from other business-object-types.
### Customer Roles
The highest level of the business-object-type-hierarchy is the *Customer*.
```plantuml
@startuml
' left to right direction
top to bottom direction
' hide the ugly E in a circle left to the entity name
hide circle
' use right-angled line routing
' skinparam linetype ortho
' needs PlantUML 1.2021.14 as Markdown plugin
allow_mixing
entity "BObj customer#xyz" as boCustXyz
together {
entity "Perm customer#xyz *" as permCustomerXyzAll
permCustomerXyzAll --> boCustXyz
entity "Perm customer#xyz add-package" as permCustomerXyzAddPack
permCustomerXyzAddPack --> boCustXyz
entity "Perm customer#xyz view" as permCustomerXyzView
permCustomerXyzView --> boCustXyz
}
entity "Role customer#xyz.tenant" as roleCustXyzTenant
roleCustXyzTenant --> permCustomerXyzView
entity "Role customer#xyz.admin" as roleCustXyzAdmin
roleCustXyzAdmin --> roleCustXyzTenant
roleCustXyzAdmin --> permCustomerXyzAddPack
entity "Role customer#xyz.owner" as roleCustXyzOwner
roleCustXyzOwner ..> roleCustXyzAdmin
roleCustXyzOwner --> permCustomerXyzAll
actor "Customer XYZ Admin" as actorCustXyzAdmin
actorCustXyzAdmin --> roleCustXyzAdmin
entity "Role administrators" as roleAdmins
roleAdmins --> roleCustXyzOwner
actor "Any Hostmaster" as actorHostmaster
actorHostmaster --> roleAdmins
@enduml
```
As you can see, there something special:
From the 'Role customer#xyz.owner' to the 'Role customer#xyz.admin' there is a dashed line, whereas all other lines are solid lines.
Solid lines means, that one role is granted to another and followed in all queries to the restricted views.
The dashed line means that one role is granted to another but not automatically followed in queries to the restricted views.
The reason here is that otherwise simply too many objects would be accessible to those with the 'administrators' role and all queries would be slowed down vastly.
Grants which are not followed are still valid grants for `hsadminng.assumedRoles`.
Thus, if you want to access anything below a customer, assume its role first.
There is actually another speciality in the customer roles:
For all others, a user defined by the customer gets the owner role assigned, just for the customer, the owner's role is assigned to the 'administrators' role.
### Package Roles
One example of the business-object-type-level right below is the *Package*.
```plantuml
@startuml
' left to right direction
top to bottom direction
' hide the ugly E in a circle left to the entity name
hide circle
' use right-angled line routing
' skinparam linetype ortho
' needs PlantUML 1.2021.14 as Markdown plugin
allow_mixing
entity "BObj package#xyz00" as boPacXyz00
together {
entity "Perm package#xyz00 *" as permPackageXyzAll
permPackageXyzAll --> boPacXyz00
entity "Perm package#xyz00 add-unixuser" as permPacXyz00AddUser
permPacXyz00AddUser --> boPacXyz00
entity "Perm package#xyz00 edit" as permPacXyz00Edit
permPacXyz00Edit --> boPacXyz00
entity "Perm package#xyz00 view" as permPacXyz00View
permPacXyz00View --> boPacXyz00
}
package {
entity "Role customer#xyz.tenant" as roleCustXyzTenant
entity "Role customer#xyz.admin" as roleCustXyzAdmin
entity "Role customer#xyz.owner" as roleCustXyzOwner
}
package {
entity "Role package#xyz00.owner" as rolePacXyz00Owner
entity "Role package#xyz00.admin" as rolePacXyz00Admin
entity "Role package#xyz00.tenant" as rolePacXyz00Tenant
}
rolePacXyz00Tenant --> permPacXyz00View
rolePacXyz00Tenant --> roleCustXyzTenant
rolePacXyz00Owner --> rolePacXyz00Admin
rolePacXyz00Owner --> permPackageXyzAll
roleCustXyzAdmin --> rolePacXyz00Owner
roleCustXyzAdmin --> roleCustXyzTenant
roleCustXyzOwner ..> roleCustXyzAdmin
rolePacXyz00Admin --> rolePacXyz00Tenant
rolePacXyz00Admin --> permPacXyz00AddUser
rolePacXyz00Admin --> permPacXyz00Edit
actor "Package XYZ00 Admin" as actorPacXyzAdmin
actorPacXyzAdmin -l-> rolePacXyz00Admin
actor "Customer XYZ Admin" as actorCustXyzAdmin
actorCustXyzAdmin --> roleCustXyzAdmin
entity "Role administrators" as roleAdmins
roleAdmins --> roleCustXyzOwner
actor "Any Hostmaster" as actorHostmaster
actorHostmaster --> roleAdmins
@enduml
```
Initially, the customer's admin role is assigned to the package owner role.
They can use the package's admin role to hand over most management functionality to a third party.
The 'administrators' can get access through an assumed customer's admin role or directly by assuming the package's owner or admin role.
## Performance
We did not define maximum response time in our requirements,
but set a target of 7.000 customers, 15.000 packages, 150.000 Unix users, 100.000 domains and 500.000 email-addresses.
For such a dataset the response time for typical queries from a UI should be acceptable.
Also, when adding data beyond these quantities, increase in response time should be roughly linear or below.
For this, we increased the dataset by 14% and then by another 25%, ending up with 10.000 customers, almost 25.000 packages, over 174.000 unix users, over 120.000 domains and almost 750.000 email-addresses.
The performance test suite comprised 8 SELECT queries issued by an administrator, mostly with two assumed customer owner roles.
The tests started with finding a specific customer and ended with listing all accessible email-addresses joined with their domains, unix-users, packages and customers.
Find the SQL script here: `28-hs-tests.sql`.
### Two View Query Variants
We have tested two variants of the query for the restricted view,
both utilizing a PostgreSQL function like this:
FUNCTION queryAccessibleObjectUuidsOfSubjectIds(
requiredOp RbacOp,
forObjectTable varchar,
subjectIds uuid[],
maxObjects integer = 16000)
RETURNS SETOF uuid
The function returns all object uuids for which the given subjectIds (user o assumed roles) have a permission or required operation.
Let's have a look at the two view queries:
#### Using WHERE ... IN
CREATE OR REPLACE VIEW customer_rv AS
SELECT DISTINCT target.*
FROM customer AS target
WHERE target.uuid IN (
SELECT uuid
FROM queryAccessibleObjectUuidsOfSubjectIds(
'view', 'customer', currentSubjectIds()));
This view should be automatically updatable.
Where, for updates, we actually have to check for 'edit' instead of 'view' operation, which makes it a bit more complicated.
With the larger dataset, the test suite initially needed over 7 seconds with this view query.
At this point the second variant was tried.
But after the initial query, the execution time was drastically reduced,
even with different query values.
Looks like the query optimizer needed some statistics to find the best path.
#### Using A JOIN
CREATE OR REPLACE VIEW customer_rv AS
SELECT DISTINCT target.*
FROM customer AS target
JOIN queryAccessibleObjectUuidsOfSubjectIds(
'view', 'customer', currentSubjectIds()) AS allowedObjId
ON target.uuid = allowedObjId;
This view cannot is not updatable automatically,
but it was quite fast from the beginning.
### Performance Results
The following table shows the average between the second and the third repeat of the test-suite:
| Dataset | using JOIN | using WHERE IN |
|----------------:|-----------:|---------------:|
| 7000 customers | 670ms | 1040ms |
| 10000 customers | 1050ms | 1125ms |
| +43% | +57% | +8% |
The JOIN-variant is still faster, but the growth in execution time exceeded the growth of the dataset.
The WHERE-IN-variant is about 50% slower on the smaller dataset, but almost keeps its performance on the larger dataset.
Both variants a viable option, depending on other needs, e.g. updatable views.

100
doc/test-concept.md Normal file
View File

@ -0,0 +1,100 @@
## Test-Concept
<!-- generated TOC begin: -->
- [Unit-Tests](#unit-tests)
- [REST-Tests](#rest-tests)
- [Integration-Tests](#integration-tests)
- [Acceptance-Tests](#acceptance-tests)
- [Performance-Tests](#performance-tests)
- [System-Integration-Tests](#system-integration-tests)
<!-- generated TOC end. -->
### General Issues
The following test concept uses terms like "double" and "mock" (maybe in inflected form like "mocking" or "mocked"), "whitebox-test" and "blackbox-tests" and "test-fixture".
Please look up their definition in the [glossary](glossary.md)
Where our APIs should be designed in a way that it's possible, using a mocking library like *Mockito* often leads to shorter test code.
### Kinds of Tests
Depending on the concrete aspects which we want to test, we are using different kinds of tests as described as follows.
#### Unit-Tests
In this project a *Unit* for *UnitTests* can be a single method (function), a class or even a group of classes which express a common concept.
The unit are technically whitebox-tests and count into test-code-coverage.
But the whitebox-knowledge should only be used for the text-fixture.
Unit-Test in this project are implemented with *JUnit Jupiter*, *Mockito* and *AssertJ*.
Unit-Tests do not use any external systems, not even a database.
They just test the unit, not any dependencies or proper integration with dependencies.
Such tests usually run very fast and should test all branches.
These Tests are always named `...UnitTest` and can automatically run in the build-process.
#### REST-Tests
At the level of REST-Controllers, *Spring's* `WebMvcTest`, a special kind of Unit-Test, are utilized.
Such tests issue REST-requests through a mocked REST-Layer and therefore use the controllers similar to a real client.
Otherwise, the implementation technologies are like those of Unit-Tests.
Being unit-tests, also REST-tests are whitebox-tests and count into test-code-coverage.
Like other Unit-Tests, REST-Test do not use any external systems, not even a database.
They just test the REST-related parts of the unit, e.g. URL-Mappings, HTTP-Headers and proper JSON encoding of request and response data.
Other dependencies and integrations with such are not tested on this level.
Such tests usually run very fast, but should focus on REST-specific issues, leaving branch-testing to pure Unit-Tests.
These Tests are always named `...RestTest` and can automatically run in the build-process.
#### Integration-Tests
Integration-Tests in this context mean integration with support systems like databases or messaging-systems, but not integration with external systems.
Integration-tests, are blackbox-tests and do <u>not</u> count into test-code-coverage.
Such tests are implemented with *JUnit Jupiter* through some sort of `@SpringBootTest`, e.g. `DataJpaTest` and usually utilize *Testcontainers* and *Docker* to wrap the supporting system, e.g. the *PostgreSQL* database.
*Mockito* can also be used for this kind of tests, to separate multiple integrations.
Integration-Tests are relatively slow and therefore should focus on the integration.
Internal issues should be tested through Unit-Tests.
These Tests are always named `...IntegrationTest` and can automatically run in the build-process.
#### Acceptance-Tests
We define Acceptance-Tests as test which describe user-stories, respectively high-level business requirements.
Acceptance-Tests run on a fully integrated and deployed system with deployed doubles for external systems.
Acceptance-tests, are blackbox-tests and do <u>not</u> count into test-code-coverage.
TODO: Complete the Acceptance-Tests test concept.
#### Performance-Tests
Performance-critical scenarios have to be identified and a special performance-test has to be implemented.
The implementation-technologie depends on the scenario.
Performance-tests, are blackbox-tests and do <u>not</u> count into test-code-coverage.
Such tests usually are very slow and should not be automatically run in the build-pipeline but manually, after critical areas have been changed.
#### System-Integration-Tests
We define System-Integration-Tests as test in which this system is deployed in a production-like environment to test integration with external systems.
System-Integration-tests, are blackbox-tests and do <u>not</u> count into test-code-coverage.
TODO: Complete the System-Integration-Tests test concept.

View File

@ -1,53 +0,0 @@
rootProject.name=hsadmin-ng
profile=dev
# Build properties
node_version=10.15.3
npm_version=6.4.1
yarn_version=1.13.0
# Dependency versions
jhipster_dependencies_version=2.1.1
# The spring-boot version should match the one managed by
# https://mvnrepository.com/artifact/io.github.jhipster/jhipster-dependencies/${jhipster_dependencies_version}
spring_boot_version=2.0.8.RELEASE
# The hibernate version should match the one managed by
# https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-dependencies/${spring-boot.version} -->
hibernate_version=5.2.17.Final
mapstruct_version=1.2.0.Final
liquibase_hibernate5_version=3.6
liquibaseTaskPrefix=liquibase
# jhipster-needle-gradle-property - JHipster will add additional properties here
## below are some of the gradle performance improvement settings that can be used as required, these are not enabled by default
## The Gradle daemon aims to improve the startup and execution time of Gradle.
## The daemon is enabled by default in Gradle 3+ setting this to false will disable this.
## TODO: disable daemon on CI, since builds should be clean and reliable on servers
## https://docs.gradle.org/current/userguide/gradle_daemon.html#sec:ways_to_disable_gradle_daemon
## un comment the below line to disable the daemon
#org.gradle.daemon=false
## Specifies the JVM arguments used for the daemon process.
## The setting is particularly useful for tweaking memory settings.
## Default value: -Xmx1024m -XX:MaxPermSize=256m
## un comment the below line to override the daemon defaults
#org.gradle.jvmargs=-Xmx1024m -XX:MaxPermSize=256m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
## When configured, Gradle will run in incubating parallel mode.
## This option should only be used with decoupled projects. More details, visit
## http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
## un comment the below line to enable parallel mode
#org.gradle.parallel=true
## Enables new incubating mode that makes Gradle selective when configuring projects.
## Only relevant projects are configured which results in faster builds for large multi-projects.
## http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:configuration_on_demand
## un comment the below line to enable the selective mode
#org.gradle.configureondemand=true

View File

@ -1,35 +0,0 @@
buildscript {
repositories {
gradlePluginPortal()
}
dependencies {
classpath "gradle.plugin.com.google.cloud.tools:jib-gradle-plugin:0.9.11"
}
}
apply plugin: com.google.cloud.tools.jib.gradle.JibPlugin
jib {
from {
image = 'openjdk:8-jre-alpine'
}
to {
image = 'hsadminng:latest'
}
container {
entrypoint = ['sh', '-c', 'chmod +x /entrypoint.sh && sync && /entrypoint.sh']
ports = ['8080']
environment = [
SPRING_OUTPUT_ANSI_ENABLED: 'ALWAYS',
JHIPSTER_SLEEP: '0'
]
useCurrentTimestamp = true
}
}
task copyWwwIntoStatic (type: Copy) {
from 'build/www/'
into 'build/resources/main/static'
}
jibDockerBuild.dependsOn copyWwwIntoStatic

View File

@ -1,72 +0,0 @@
import org.gradle.internal.os.OperatingSystem
apply plugin: 'org.springframework.boot'
apply plugin: 'com.moowork.node'
dependencies {
compile "org.springframework.boot:spring-boot-devtools"
compile "com.h2database:h2"
}
def profiles = "";
if (project.hasProperty('pgsql')) {
profiles += 'pgsql'
} else if (project.hasProperty('h2file')) {
profiles += 'h2file'
} else {
profiles += 'h2mem'
}
if (project.hasProperty('no-liquibase')) {
profiles += ',no-liquibase'
}
if (project.hasProperty('tls')) {
profiles += ',tls'
}
println 'activating profiles: ' + profiles
springBoot {
buildInfo {
properties {
time = null
}
}
}
bootRun {
args = []
}
task webpackBuildDev(type: NpmTask) {
inputs.files(fileTree('src/main/webapp/'))
def webpackDevFiles = fileTree('webpack//')
webpackDevFiles.exclude('webpack.prod.js')
inputs.files(webpackDevFiles)
outputs.files(fileTree("build/www/"))
dependsOn npmInstall
args = ["run", "webpack:build"]
}
task copyIntoStatic (type: Copy) {
from 'build/www/'
into 'build/resources/main/static'
}
processResources {
filesMatching('**/application.yml') {
filter {
it.replace('#project.version#', version)
}
filter {
it.replace('#spring.profiles.active#', profiles)
}
}
}
processResources.dependsOn webpackBuildDev
copyIntoStatic.dependsOn processResources
bootJar.dependsOn copyIntoStatic

View File

@ -1,63 +0,0 @@
apply plugin: 'org.springframework.boot'
apply plugin: 'com.gorylenko.gradle-git-properties'
apply plugin: 'com.moowork.node'
dependencies {
testCompile "com.h2database:h2"
}
def profiles = 'prod'
if (project.hasProperty('no-liquibase')) {
profiles += ',no-liquibase'
}
if (project.hasProperty('swagger')) {
profiles += ',swagger'
}
springBoot {
buildInfo()
}
bootRun {
args = []
}
task webpack_test(type: NpmTask, dependsOn: 'npm_install') {
args = ["run", "webpack:test"]
}
task webpack(type: NpmTask, dependsOn: 'npm_install') {
args = ["run", "webpack:prod"]
}
task copyIntoStatic (type: Copy) {
from 'build/www/'
into 'build/resources/main/static'
}
processResources {
filesMatching('**/application.yml') {
filter {
it.replace('#project.version#', version)
}
filter {
it.replace('#spring.profiles.active#', profiles)
}
}
}
generateGitProperties {
onlyIf {
!source.isEmpty()
}
}
gitProperties {
keys = ['git.branch', 'git.commit.id.abbrev', 'git.commit.id.describe']
}
test.dependsOn webpack_test
processResources.dependsOn webpack
copyIntoStatic.dependsOn processResources
bootJar.dependsOn copyIntoStatic

View File

@ -1,47 +0,0 @@
apply plugin: "org.sonarqube"
apply plugin: 'jacoco'
jacoco {
toolVersion = '0.8.2'
}
jacocoTestReport {
reports {
xml.enabled true
}
}
sonarqube {
properties {
property "sonar.host.url", "http://localhost:9001"
property "sonar.exclusions", "src/main/webapp/content/**/*.*,src/main/webapp/i18n/*.js, build/www/**/*.*"
property "sonar.issue.ignore.multicriteria", "S3437,S4502,S4684,UndocumentedApi,BoldAndItalicTagsCheck"
// Rule https://sonarcloud.io/coding_rules?open=Web%3ABoldAndItalicTagsCheck&rule_key=Web%3ABoldAndItalicTagsCheck is ignored. Even if we agree that using the "i" tag is an awful practice, this is what is recommended by http://fontawesome.io/examples/
property "sonar.issue.ignore.multicriteria.BoldAndItalicTagsCheck.resourceKey", ">src/main/webapp/app/**/*.*"
property "sonar.issue.ignore.multicriteria.BoldAndItalicTagsCheck.ruleKey", "Web:BoldAndItalicTagsCheck"
// Rule https://sonarcloud.io/coding_rules?open=squid%3AS3437&rule_key=squid%3AS3437 is ignored, as a JPA-managed field cannot be transient
property "sonar.issue.ignore.multicriteria.S3437.resourceKey", "src/main/java/**/*"
property "sonar.issue.ignore.multicriteria.S3437.ruleKey", "squid:S3437"
// Rule https://sonarcloud.io/coding_rules?open=squid%3AUndocumentedApi&rule_key=squid%3AUndocumentedApi is ignored, as we want to follow "clean code" guidelines and classes, methods and arguments names should be self-explanatory
property "sonar.issue.ignore.multicriteria.UndocumentedApi.resourceKey", "src/main/java/**/*"
property "sonar.issue.ignore.multicriteria.UndocumentedApi.ruleKey", "squid:UndocumentedApi"
// Rule https://sonarcloud.io/coding_rules?open=squid%3AS4502&rule_key=squid%3AS4502 is ignored, as for JWT tokens we are not subject to CSRF attack
property "sonar.issue.ignore.multicriteria.S4502.resourceKey", "src/main/java/**/*"
property "sonar.issue.ignore.multicriteria.S4502.ruleKey", "squid:S4502"
// Rule https://sonarcloud.io/coding_rules?open=squid%3AS4684&rule_key=squid%3AS4684
property "sonar.issue.ignore.multicriteria.S4684.resourceKey", "src/main/java/**/*"
property "sonar.issue.ignore.multicriteria.S4684.ruleKey", "squid:S4684"
property "sonar.jacoco.reportPaths", "${project.buildDir}/jacoco/test.exec"
property "sonar.java.codeCoveragePlugin", "jacoco"
property "sonar.typescript.lcov.reportPaths", "${project.buildDir}/test-results/lcov.info"
property "sonar.junit.reportPaths", "${project.buildDir}/test-results"
property "sonar.sources", "${project.projectDir}/src/main/"
property "sonar.tests", "${project.projectDir}/src/test/"
}
}

View File

@ -1,28 +0,0 @@
/*
* Plugin that provides API-first development using OpenAPI-generator to
* generate Spring-MVC endpoint stubs at compile time from an OpenAPI definition file
*/
apply plugin: 'org.openapi.generator'
openApiGenerate {
generatorName = "spring"
inputSpec = "$rootDir/src/main/resources/swagger/api.yml".toString()
outputDir = "$buildDir/openapi".toString()
apiPackage = "org.hostsharing.hsadminng.web.api"
modelPackage = "org.hostsharing.hsadminng.web.api.model"
apiFilesConstrainedTo = [""]
modelFilesConstrainedTo = [""]
supportingFilesConstrainedTo = ["ApiUtil.java"]
configOptions = [delegatePattern: "true"]
validateSpec = true
}
sourceSets {
main {
java {
srcDir file("${project.buildDir.path}/openapi/src/main/java")
}
}
}
compileJava.dependsOn("openApiGenerate")

Binary file not shown.

View File

@ -1,6 +0,0 @@
#Sun Apr 28 06:46:48 CEST 2019
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-all.zip

View File

@ -1,3 +0,0 @@
dependencies {
compile "org.springframework.cloud:spring-cloud-starter-zipkin"
}

292
gradlew vendored
View File

@ -1,78 +1,129 @@
#!/usr/bin/env sh
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
##
## Gradle start up script for UN*X
##
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
APP_BASE_NAME=${0##*/}
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
MAX_FD=maximum
warn () {
echo "$*"
}
} >&2
die () {
echo
echo "$*"
echo
exit 1
}
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD="$JAVA_HOME/bin/java"
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
@ -81,7 +132,7 @@ Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
JAVACMD=java
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
@ -89,84 +140,101 @@ location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=$(save "$@")
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")"
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"

55
gradlew.bat vendored
View File

@ -1,4 +1,20 @@
@if "%DEBUG%" == "" @echo off
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@ -9,19 +25,22 @@
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
if "%DIRNAME%"=="" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
if %ERRORLEVEL% equ 0 goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
@ -35,7 +54,7 @@ goto fail
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
@ -45,38 +64,26 @@ echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal

18180
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,126 +0,0 @@
{
"name": "hsadmin-ng",
"version": "0.0.0",
"description": "Description for hsadminNg",
"private": true,
"license": "UNLICENSED",
"cacheDirectories": [
"node_modules"
],
"dependencies": {
"@angular/common": "7.2.4",
"@angular/compiler": "7.2.4",
"@angular/core": "7.2.4",
"@angular/forms": "7.2.4",
"@angular/platform-browser": "7.2.4",
"@angular/platform-browser-dynamic": "7.2.4",
"@angular/router": "7.2.4",
"@fortawesome/angular-fontawesome": "0.3.0",
"@fortawesome/fontawesome-svg-core": "1.2.14",
"@fortawesome/free-solid-svg-icons": "5.7.1",
"@ng-bootstrap/ng-bootstrap": "4.0.2",
"@ngx-translate/core": "11.0.1",
"@ngx-translate/http-loader": "4.0.0",
"bootstrap": "4.2.1",
"core-js": "2.6.4",
"moment": "2.24.0",
"ng-jhipster": "0.9.1",
"ngx-cookie": "2.0.1",
"ngx-infinite-scroll": "7.0.1",
"ngx-webstorage": "2.0.1",
"rxjs": "6.4.0",
"swagger-ui": "2.2.10",
"tslib": "1.9.3",
"zone.js": "0.8.29"
},
"devDependencies": {
"@angular/cli": "7.3.1",
"@angular/compiler-cli": "7.2.4",
"@ngtools/webpack": "7.3.1",
"@types/jest": "24.0.0",
"@types/node": "10.12.24",
"angular-router-loader": "0.8.5",
"angular2-template-loader": "0.6.2",
"autoprefixer": "9.4.7",
"browser-sync": "2.26.3",
"browser-sync-webpack-plugin": "2.2.2",
"cache-loader": "2.0.1",
"codelyzer": "4.5.0",
"copy-webpack-plugin": "4.6.0",
"css-loader": "2.1.0",
"file-loader": "3.0.1",
"fork-ts-checker-webpack-plugin": "0.5.2",
"friendly-errors-webpack-plugin": "1.7.0",
"generator-jhipster": "5.8.2",
"html-loader": "0.5.5",
"html-webpack-plugin": "3.2.0",
"husky": "1.3.1",
"jest": "24.1.0",
"jest-junit": "6.2.1",
"jest-preset-angular": "6.0.2",
"jest-sonar-reporter": "2.0.0",
"lint-staged": "8.1.3",
"merge-jsons-webpack-plugin": "1.0.18",
"mini-css-extract-plugin": "0.5.0",
"moment-locales-webpack-plugin": "1.0.7",
"optimize-css-assets-webpack-plugin": "5.0.1",
"prettier": "1.16.4",
"reflect-metadata": "0.1.13",
"rimraf": "2.6.3",
"simple-progress-webpack-plugin": "1.1.2",
"style-loader": "0.23.1",
"terser-webpack-plugin": "1.2.2",
"thread-loader": "2.1.2",
"to-string-loader": "1.1.5",
"ts-loader": "5.3.3",
"tslint": "5.12.1",
"tslint-config-prettier": "1.18.0",
"tslint-loader": "3.6.0",
"typescript": "3.2.4",
"postcss-loader": "3.0.0",
"webpack": "4.29.3",
"webpack-cli": "3.2.3",
"webpack-dev-server": "3.1.14",
"webpack-merge": "4.2.1",
"webpack-notifier": "1.7.0",
"webpack-visualizer-plugin": "0.1.11",
"workbox-webpack-plugin": "3.6.3",
"write-file-webpack-plugin": "4.5.0"
},
"engines": {
"node": ">=8.9.0"
},
"lint-staged": {
"{,src/**/}*.{md,json,ts,css,scss}": [
"prettier --write",
"git add"
]
},
"scripts": {
"prettier:format": "prettier --write \"{,src/**/}*.{md,json,ts,css,scss}\"",
"lint": "tslint --project tsconfig.json -e 'node_modules/**'",
"lint:fix": "npm run lint -- --fix",
"ngc": "ngc -p tsconfig-aot.json",
"cleanup": "rimraf build/{aot,www}",
"clean-www": "rimraf build//www/app/{src,build/}",
"start": "npm run webpack:dev",
"start-tls": "npm run webpack:dev -- --env.tls",
"serve": "npm run start",
"build": "npm run webpack:prod",
"test": "npm run lint && jest --coverage --logHeapUsage -w=2 --config src/test/javascript/jest.conf.js",
"test:watch": "npm run test -- --watch",
"webpack:dev": "npm run webpack-dev-server -- --config webpack/webpack.dev.js --inline --hot --port=9060 --watch-content-base --env.stats=minimal",
"webpack:dev-verbose": "npm run webpack-dev-server -- --config webpack/webpack.dev.js --inline --hot --port=9060 --watch-content-base --profile --progress --env.stats=normal",
"webpack:build:main": "npm run webpack -- --config webpack/webpack.dev.js --env.stats=minimal",
"webpack:build": "npm run cleanup && npm run webpack:build:main",
"webpack:prod:main": "npm run webpack -- --config webpack/webpack.prod.js --profile",
"webpack:prod": "npm run cleanup && npm run webpack:prod:main && npm run clean-www",
"webpack:test": "npm run test",
"webpack-dev-server": "node --max_old_space_size=4096 node_modules/webpack-dev-server/bin/webpack-dev-server.js",
"webpack": "node --max_old_space_size=4096 node_modules/webpack/bin/webpack.js"
},
"jestSonar": {
"reportPath": "build/test-results/jest",
"reportFile": "TESTS-results-sonar.xml"
}
}

View File

@ -1,5 +0,0 @@
module.exports = {
plugins: [
require('autoprefixer')
]
}

View File

@ -1,7 +0,0 @@
{
"*": {
"target": "http://localhost:8080",
"secure": false,
"loglevel": "debug"
}
}

134
sql/28-hs-tests.sql Normal file
View File

@ -0,0 +1,134 @@
ABORT;
SET SESSION SESSION AUTHORIZATION DEFAULT;
-- there are some random ractors in test data generation, thus a range has to be accepted
CREATE OR REPLACE PROCEDURE expectBetween(actualCount integer, expectedFrom integer, expectedTo integer)
LANGUAGE plpgsql AS $$
BEGIN
IF NOT actualCount BETWEEN expectedFrom AND expectedTo THEN
RAISE EXCEPTION 'count expected to be between % and %, but got %', expectedFrom, expectedTo, actualCount;
END IF;
END; $$;
DO LANGUAGE plpgsql $$
DECLARE
resultCount integer;
BEGIN
-- hostmaster accessing a single customer
SET SESSION SESSION AUTHORIZATION restricted;
SET LOCAL hsadminng.currentUser = 'mike@hostsharing.net';
SET LOCAL hsadminng.assumedRoles = '';
-- SELECT *
SELECT count(*) INTO resultCount
from customer_rv c
where c.prefix='aab';
call expectBetween(resultCount, 1, 1);
-- hostmaster listing all customers
SET SESSION SESSION AUTHORIZATION restricted;
SET LOCAL hsadminng.currentUser = 'mike@hostsharing.net';
SET LOCAL hsadminng.assumedRoles = '';
-- SELECT *
SELECT count(*) INTO resultCount
FROM customer_rv;
call expectBetween(resultCount, 10, 20000);
-- customer admin listing all their packages
SET SESSION SESSION AUTHORIZATION restricted;
SET LOCAL hsadminng.currentUser = 'admin@aae.example.com';
SET LOCAL hsadminng.assumedRoles = '';
-- SELECT *
SELECT count(*) INTO resultCount
FROM package_rv;
call expectBetween(resultCount, 2, 10);
-- cutomer admin listing all their unix users
SET SESSION SESSION AUTHORIZATION restricted;
SET LOCAL hsadminng.currentUser = 'admin@aae.example.com';
SET LOCAL hsadminng.assumedRoles = '';
-- SELECT *
SELECT count(*) INTO resultCount
FROM unixuser_rv;
call expectBetween(resultCount, 20, 50);
-- hostsharing admin assuming customer role and listing all accessible packages
SET SESSION SESSION AUTHORIZATION restricted;
SET LOCAL hsadminng.currentUser = 'mike@hostsharing.net';
SET LOCAL hsadminng.assumedRoles = 'customer#aaa.admin;customer#aab.admin';
-- SELECT *
SELECT count(*) INTO resultCount
FROM package_rv p;
call expectBetween(resultCount, 2, 10);
-- hostsharing admin assuming two customer admin roles and listing all accessible unixusers
SET SESSION SESSION AUTHORIZATION restricted;
SET LOCAL hsadminng.currentUser = 'mike@hostsharing.net';
SET LOCAL hsadminng.assumedRoles = 'customer#aab.admin;customer#aac.admin';
-- SELECT c.prefix, c.reference, uu.*
SELECT count(*) INTO resultCount
FROM unixuser_rv uu
JOIN package_rv p ON p.uuid = uu.packageuuid
JOIN customer_rv c ON c.uuid = p.customeruuid;
call expectBetween(resultCount, 40, 60);
-- hostsharing admin assuming two customer admin roles and listing all accessible domains
-- ABORT; START TRANSACTION;
SET SESSION SESSION AUTHORIZATION restricted;
SET LOCAL hsadminng.currentUser = 'mike@hostsharing.net';
SET LOCAL hsadminng.assumedRoles = 'customer#aac.admin;customer#aad.admin';
-- SELECT p.name, uu.name, dom.name
SELECT count(*) INTO resultCount
FROM domain_rv dom
JOIN unixuser_rv uu ON uu.uuid = dom.unixuseruuid
JOIN package_rv p ON p.uuid = uu.packageuuid
JOIN customer_rv c ON c.uuid = p.customeruuid;
call expectBetween(resultCount, 20, 40);
-- hostsharing admin assuming two customer admin roles and listing all accessible email addresses
-- ABORT; START TRANSACTION;
SET SESSION SESSION AUTHORIZATION restricted;
SET LOCAL hsadminng.currentUser = 'mike@hostsharing.net';
SET LOCAL hsadminng.assumedRoles = 'customer#aae.admin;customer#aaf.admin';
-- SELECT c.prefix, p.name as "package", ema.localPart || '@' || dom.name as "email-address"
SELECT count(*) INTO resultCount
FROM emailaddress_rv ema
JOIN domain_rv dom ON dom.uuid = ema.domainuuid
JOIN unixuser_rv uu ON uu.uuid = dom.unixuseruuid
JOIN package_rv p ON p.uuid = uu.packageuuid
JOIN customer_rv c ON c.uuid = p.customeruuid;
call expectBetween(resultCount, 100, 300);
-- ~170ms
END; $$;
/*
=== with 7000 customers ===
1. 7105 vs 801 ms
2. 960 vs. 649 ms
3. 970 vs. 670 ms
no count required factor table
1 7 000 7 000 1.000 customers
2 17 436 15 000 1.162 packages
3 174 360 150 000 1.162 unixuser
4 105 206 100 000 1.052 domain
5 526 030 500 000 1.052 emailaddress
=== with 10000 customers (+43%) ===
1. 7491 vs. 1189 ms (-1%)
2. 1049 ms (+31%)
3. 1028 ms (+53%)
in average +9,33%
no count required factor table
1 10 000 7 000 1.429 customers
2 24 904 15 000 1.660 packages
3 249 040 150 000 1.660 unixuser
4 149 946 100 000 1.499 domain
5 749 730 500 000 1.499 emailaddress
*/

53
sql/examples.sql Normal file
View File

@ -0,0 +1,53 @@
-- ========================================================
-- First Example Entity with History
-- --------------------------------------------------------
CREATE TABLE IF NOT EXISTS customer (
"id" SERIAL PRIMARY KEY,
"reference" int not null unique, -- 10000-99999
"prefix" character(3) unique
);
CALL create_historicization('customer');
-- ========================================================
-- Second Example Entity with History
-- --------------------------------------------------------
CREATE TABLE IF NOT EXISTS package_type (
"id" serial PRIMARY KEY,
"name" character varying(8)
);
CALL create_historicization('package_type');
-- ========================================================
-- Third Example Entity with History
-- --------------------------------------------------------
CREATE TABLE IF NOT EXISTS package (
"id" serial PRIMARY KEY,
"name" character varying(5),
"customer_id" INTEGER REFERENCES customer(id)
);
CALL create_historicization('package');
-- ========================================================
-- query historical data
-- --------------------------------------------------------
ABORT;
BEGIN TRANSACTION;
SET LOCAL hsadminng.currentUser TO 'mih42_customer_aaa';
SET LOCAL hsadminng.currentTask TO 'adding customer_aaa';
INSERT INTO package (customer_id, name) VALUES (10000, 'aaa00');
COMMIT;
-- Usage:
SET hsadminng.timestamp TO '2022-07-12 08:53:27.723315';
SET hsadminng.timestamp TO '2022-07-12 11:38:27.723315';
SELECT * FROM customer_hv p WHERE prefix = 'aaa';

166
sql/historization.sql Normal file
View File

@ -0,0 +1,166 @@
-- ========================================================
-- Historization
-- --------------------------------------------------------
CREATE TABLE "tx_history" (
"tx_id" BIGINT NOT NULL UNIQUE,
"tx_timestamp" TIMESTAMP NOT NULL,
"user" VARCHAR(64) NOT NULL, -- references postgres user
"task" VARCHAR NOT NULL
);
CREATE TYPE "operation" AS ENUM ('INSERT', 'UPDATE', 'DELETE', 'TRUNCATE');
-- see https://www.postgresql.org/docs/current/plpgsql-trigger.html
CREATE OR REPLACE FUNCTION historicize()
RETURNS trigger
LANGUAGE plpgsql STRICT AS $$
DECLARE
currentUser VARCHAR(64);
currentTask varchar;
"row" RECORD;
"alive" BOOLEAN;
"sql" varchar;
BEGIN
-- determine user_id
BEGIN
currentUser := current_setting('hsadminng.currentUser');
EXCEPTION WHEN OTHERS THEN
currentUser := NULL;
END;
IF (currentUser IS NULL OR currentUser = '') THEN
RAISE EXCEPTION 'hsadminng.currentUser must be defined, please use "SET LOCAL ...;"';
END IF;
RAISE NOTICE 'currentUser: %', currentUser;
-- determine task
currentTask = current_setting('hsadminng.currentTask');
IF (currentTask IS NULL OR length(currentTask) < 12) THEN
RAISE EXCEPTION 'hsadminng.currentTask (%) must be defined and min 12 characters long, please use "SET LOCAL ...;"', currentTask;
END IF;
RAISE NOTICE 'currentTask: %', currentTask;
IF (TG_OP = 'INSERT') OR (TG_OP = 'UPDATE') THEN
"row" := NEW;
"alive" := TRUE;
ELSE -- DELETE or TRUNCATE
"row" := OLD;
"alive" := FALSE;
END IF;
sql := format('INSERT INTO tx_history VALUES (txid_current(), now(), %1L, %2L) ON CONFLICT DO NOTHING', currentUser, currentTask);
RAISE NOTICE 'sql: %', sql;
EXECUTE sql;
sql := format('INSERT INTO %3$I_versions VALUES (DEFAULT, txid_current(), %1$L, %2$L, $1.*)', TG_OP, alive, TG_TABLE_NAME);
RAISE NOTICE 'sql: %', sql;
EXECUTE sql USING "row";
RETURN "row";
END; $$;
CREATE OR REPLACE PROCEDURE create_historical_view(baseTable varchar)
LANGUAGE plpgsql AS $$
DECLARE
createTriggerSQL varchar;
viewName varchar;
versionsTable varchar;
createViewSQL varchar;
baseCols varchar;
BEGIN
viewName = quote_ident(format('%s_hv', baseTable));
versionsTable = quote_ident(format('%s_versions', baseTable));
baseCols = (SELECT string_agg(quote_ident(column_name), ', ')
FROM information_schema.columns
WHERE table_schema = 'public' AND table_name = baseTable);
createViewSQL = format(
'CREATE OR REPLACE VIEW %1$s AS' ||
'(' ||
' SELECT %2$s' ||
' FROM %3$s' ||
' WHERE alive = TRUE' ||
' AND version_id IN' ||
' (' ||
' SELECT max(vt.version_id) AS history_id' ||
' FROM %3$s AS vt' ||
' JOIN tx_history as txh ON vt.tx_id = txh.tx_id' ||
' WHERE txh.tx_timestamp <= current_setting(''hsadminng.timestamp'')::timestamp' ||
' GROUP BY id' ||
' )' ||
')',
viewName, baseCols, versionsTable
);
RAISE NOTICE 'sql: %', createViewSQL;
EXECUTE createViewSQL;
createTriggerSQL = 'CREATE TRIGGER ' || baseTable || '_historicize' ||
' AFTER INSERT OR DELETE OR UPDATE ON ' || baseTable ||
' FOR EACH ROW EXECUTE PROCEDURE historicize()';
RAISE NOTICE 'sql: %', createTriggerSQL;
EXECUTE createTriggerSQL;
END; $$;
CREATE OR REPLACE PROCEDURE create_historicization(baseTable varchar)
LANGUAGE plpgsql AS $$
DECLARE
createHistTableSql varchar;
createTriggerSQL varchar;
viewName varchar;
versionsTable varchar;
createViewSQL varchar;
baseCols varchar;
BEGIN
-- create the history table
createHistTableSql = '' ||
'CREATE TABLE ' || baseTable || '_versions (' ||
' version_id serial PRIMARY KEY,' ||
' tx_id bigint NOT NULL REFERENCES tx_history(tx_id),' ||
' trigger_op operation NOT NULL,' ||
' alive boolean not null,' ||
' LIKE ' || baseTable ||
' EXCLUDING CONSTRAINTS' ||
' EXCLUDING STATISTICS' ||
')';
RAISE NOTICE 'sql: %', createHistTableSql;
EXECUTE createHistTableSql;
-- create the historical view
viewName = quote_ident(format('%s_hv', baseTable));
versionsTable = quote_ident(format('%s_versions', baseTable));
baseCols = (SELECT string_agg(quote_ident(column_name), ', ')
FROM information_schema.columns
WHERE table_schema = 'public' AND table_name = baseTable);
createViewSQL = format(
'CREATE OR REPLACE VIEW %1$s AS' ||
'(' ||
' SELECT %2$s' ||
' FROM %3$s' ||
' WHERE alive = TRUE' ||
' AND version_id IN' ||
' (' ||
' SELECT max(vt.version_id) AS history_id' ||
' FROM %3$s AS vt' ||
' JOIN tx_history as txh ON vt.tx_id = txh.tx_id' ||
' WHERE txh.tx_timestamp <= current_setting(''hsadminng.timestamp'')::timestamp' ||
' GROUP BY id' ||
' )' ||
')',
viewName, baseCols, versionsTable
);
RAISE NOTICE 'sql: %', createViewSQL;
EXECUTE createViewSQL;
createTriggerSQL = 'CREATE TRIGGER ' || baseTable || '_historicize' ||
' AFTER INSERT OR DELETE OR UPDATE ON ' || baseTable ||
' FOR EACH ROW EXECUTE PROCEDURE historicize()';
RAISE NOTICE 'sql: %', createTriggerSQL;
EXECUTE createTriggerSQL;
END; $$;

View File

@ -1,105 +0,0 @@
--
-- Historization
--
CREATE TABLE history (
history_id serial PRIMARY KEY,
history_transaction bigint NOT NULL UNIQUE,
history_timestamp timestamp NOT NULL
);
CREATE FUNCTION historicize() RETURNS trigger
AS $$
BEGIN
IF (TG_OP = 'INSERT') OR (TG_OP = 'UPDATE') THEN
EXECUTE 'INSERT INTO history VALUES (DEFAULT, txid_current(), now()) ON CONFLICT DO NOTHING';
EXECUTE format('INSERT INTO %I_history VALUES (DEFAULT, txid_current(), False, $1.*)', TG_TABLE_NAME) USING NEW;
RETURN NEW;
ELSE
EXECUTE 'INSERT INTO history VALUES (DEFAULT, txid_current(), now()) ON CONFLICT DO NOTHING';
EXECUTE format('INSERT INTO %I_history VALUES (DEFAULT, txid_current(), True, $1.*)', TG_TABLE_NAME) USING OLD;
RETURN OLD;
END IF;
END;
$$
LANGUAGE plpgsql;
--
-- Entity with History
--
CREATE TABLE person (
id serial PRIMARY KEY,
name character varying(50) NOT NULL UNIQUE,
email character varying(50) NOT NULL UNIQUE
);
CREATE TABLE person_history (
history_id serial PRIMARY KEY,
history_transaction bigint NOT NULL REFERENCES history(history_transaction),
history_tombstone boolean NOT NULL,
id integer NOT NULL,
name character varying(50) NOT NULL,
email character varying(50) NOT NULL
);
CREATE TRIGGER person_historicize AFTER INSERT OR DELETE OR UPDATE ON person FOR EACH ROW EXECUTE PROCEDURE historicize();
--
-- Sample data
--
INSERT INTO person (name, email) VALUES ('michael', 'michael@hierweck.de');
INSERT INTO person (name, email) VALUES ('annika', 'annika@hierweck.de');
UPDATE person SET email='mh@hierweck.de' WHERE name='michael';
UPDATE person SET email='ah@hierweck.de' WHERE name='annika';
DELETE FROM person WHERE name='michael';
DELETE FROM person WHERE name='annika';
INSERT INTO person (name, email) VALUES ('michael', 'michael@hierweck.de');
INSERT INTO person (name, email) VALUES ('annika', 'annika@hierweck.de');
BEGIN;
INSERT INTO person (name, email) VALUES ('mx', 'mx@hierweck.de');
INSERT INTO person (name, email) VALUES ('ax', 'ax@hierweck.de');
UPDATE person SET email='mxx@hierweck.de' WHERE name='mx';
UPDATE person SET email='axx@hierweck.de' WHERE name='ax';
COMMIT;
--
-- Approach 1: Function
--
--
-- Usage:
--
-- SELECT * FROM person_history(12345, 'name');
--
CREATE OR REPLACE FUNCTION person_history(transaction bigint, VARIADIC groupby text[]) RETURNS TABLE (
history_id integer,
history_transaction bigint,
history_tombstone boolean,
id integer,
name character varying(50),
email character varying(50)
)
AS $$
BEGIN
RETURN QUERY EXECUTE format('SELECT * FROM person_history WHERE history_id IN (SELECT max(history_id) AS history_id FROM person_history WHERE history_transaction <= $1 GROUP BY %s)', array_to_string(groupby, ', ')) USING transaction;
END;
$$
LANGUAGE plpgsql;
--
-- Approach 2: View
--
-- Usage:
--
-- SET history_transaction = 12345;
-- SELECT * FROM person_history_view;
--
CREATE VIEW person_history_view
AS (SELECT * FROM person_history WHERE history_id IN (SELECT max(history_id) AS history_id FROM person_history WHERE history_transaction <= current_setting('history.transaction')::bigint GROUP BY name));

50
sql/rbac-tests.sql Normal file
View File

@ -0,0 +1,50 @@
-- ========================================================
-- Some Tests
-- --------------------------------------------------------
select isGranted(findRoleId('administrators'), findRoleId('package#aaa00.owner'));
select isGranted(findRoleId('package#aaa00.owner'), findRoleId('administrators'));
-- call grantRoleToRole(findRoleId('package#aaa00.owner'), findRoleId('administrators'));
-- call grantRoleToRole(findRoleId('administrators'), findRoleId('package#aaa00.owner'));
select count(*)
FROM queryAllPermissionsOfSubjectIdForObjectUuids(findRbacUser('sven@hostsharing.net'),
ARRAY(select uuid from customer where reference < 1100000));
select count(*)
FROM queryAllPermissionsOfSubjectId(findRbacUser('sven@hostsharing.net'));
select *
FROM queryAllPermissionsOfSubjectId(findRbacUser('alex@example.com'));
select *
FROM queryAllPermissionsOfSubjectId(findRbacUser('rosa@example.com'));
select *
FROM queryAllRbacUsersWithPermissionsFor(findPermissionId('customer',
(SELECT uuid FROM RbacObject WHERE objectTable = 'customer' LIMIT 1),
'add-package'));
select *
FROM queryAllRbacUsersWithPermissionsFor(findPermissionId('package',
(SELECT uuid FROM RbacObject WHERE objectTable = 'package' LIMIT 1),
'delete'));
DO LANGUAGE plpgsql
$$
DECLARE
userId uuid;
result bool;
BEGIN
userId = findRbacUser('mike@hostsharing.net');
result = (SELECT * FROM isPermissionGrantedToSubject(findPermissionId('package', 94928, 'add-package'), userId));
IF (result) THEN
RAISE EXCEPTION 'expected permission NOT to be granted, but it is';
end if;
result = (SELECT * FROM isPermissionGrantedToSubject(findPermissionId('package', 94928, 'view'), userId));
IF (NOT result) THEN
RAISE EXCEPTION 'expected permission to be granted, but it is NOT';
end if;
RAISE LOG 'isPermissionGrantedToSubjectId test passed';
END;
$$;

View File

@ -0,0 +1,89 @@
-- ========================================================
-- Options for SELECT under RBAC rules
-- --------------------------------------------------------
-- access control via view policy and isPermissionGrantedToSubject - way too slow (33 s 617ms for 1 million rows)
SET SESSION AUTHORIZATION DEFAULT;
CREATE ROLE admin;
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO admin;
CREATE ROLE restricted;
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO restricted;
SET SESSION AUTHORIZATION DEFAULT;
ALTER TABLE customer DISABLE ROW LEVEL SECURITY;
ALTER TABLE customer ENABLE ROW LEVEL SECURITY;
ALTER TABLE customer FORCE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS customer_policy ON customer;
CREATE POLICY customer_policy ON customer
FOR SELECT
TO restricted
USING (
-- id=1000
isPermissionGrantedToSubject(findPermissionId('customer', id, 'view'), currentUserId())
);
SET SESSION AUTHORIZATION restricted;
SET hsadminng.currentUser TO 'alex@example.com';
SELECT * from customer;
-- access control via view-rule and isPermissionGrantedToSubject - way too slow (35 s 580 ms for 1 million rows)
SET SESSION SESSION AUTHORIZATION DEFAULT;
DROP VIEW cust_view;
CREATE VIEW cust_view AS
SELECT * FROM customer;
CREATE OR REPLACE RULE "_RETURN" AS
ON SELECT TO cust_view
DO INSTEAD
SELECT * FROM customer WHERE isPermissionGrantedToSubject(findPermissionId('customer', id, 'view'), currentUserId());
SELECT * from cust_view LIMIT 10;
select queryAllPermissionsOfSubjectId(findRbacUser('mike@hostsharing.net'));
-- access control via view-rule with join to recursive permissions - really fast (38ms for 1 million rows)
SET SESSION SESSION AUTHORIZATION DEFAULT;
ALTER TABLE customer ENABLE ROW LEVEL SECURITY;
DROP VIEW IF EXISTS cust_view;
CREATE OR REPLACE VIEW cust_view AS
SELECT *
FROM customer;
CREATE OR REPLACE RULE "_RETURN" AS
ON SELECT TO cust_view
DO INSTEAD
SELECT c.uuid, c.reference, c.prefix FROM customer AS c
JOIN queryAllPermissionsOfSubjectId(currentUserId()) AS p
ON p.objectTable='customer' AND p.objectUuid=c.uuid AND p.op in ('*', 'view');
GRANT ALL PRIVILEGES ON cust_view TO restricted;
SET SESSION SESSION AUTHORIZATION restricted;
SET hsadminng.currentUser TO 'alex@example.com';
SELECT * from cust_view;
-- access control via view with join to recursive permissions - really fast (38ms for 1 million rows)
SET SESSION SESSION AUTHORIZATION DEFAULT;
ALTER TABLE customer ENABLE ROW LEVEL SECURITY;
DROP VIEW IF EXISTS cust_view;
CREATE OR REPLACE VIEW cust_view AS
SELECT c.uuid, c.reference, c.prefix
FROM customer AS c
JOIN queryAllPermissionsOfSubjectId(currentUserId()) AS p
ON p.objectUuid=c.uuid AND p.op in ('*', 'view');
GRANT ALL PRIVILEGES ON cust_view TO restricted;
SET SESSION SESSION AUTHORIZATION restricted;
-- SET hsadminng.currentUser TO 'alex@example.com';
SET hsadminng.currentUser TO 'mike@hostsharing.net';
-- SET hsadminng.currentUser TO 'aaaaouq@example.com';
SELECT * from cust_view where reference=1144150;
select rr.uuid, rr.type from RbacGrants g
join RbacReference RR on g.ascendantUuid = RR.uuid
where g.descendantUuid in (
select uuid from queryAllPermissionsOfSubjectId(findRbacUser('alex@example.com'))
where objectTable='customer' and op in ('*', 'view'));
call grantRoleToUser(findRoleId('customer#aaa.admin'), findRbacUser('aaaaouq@example.com'));
select queryAllPermissionsOfSubjectId(findRbacUser('aaaaouq@example.com'));

2
sql/rbac.sql Normal file
View File

@ -0,0 +1,2 @@

View File

@ -1,14 +0,0 @@
# https://docs.docker.com/engine/reference/builder/#dockerignore-file
classes/
generated-sources/
generated-test-sources/
h2db/
maven-archiver/
maven-status/
reports/
surefire-reports/
test-classes/
test-results/
www/
!*.jar
!*.war

View File

@ -1,20 +0,0 @@
FROM openjdk:8-jre-alpine
ENV SPRING_OUTPUT_ANSI_ENABLED=ALWAYS \
JHIPSTER_SLEEP=0 \
JAVA_OPTS=""
# Add a jhipster user to run our application so that it doesn't need to run as root
RUN adduser -D -s /bin/sh jhipster
WORKDIR /home/jhipster
ADD entrypoint.sh entrypoint.sh
RUN chmod 755 entrypoint.sh && chown jhipster:jhipster entrypoint.sh
USER jhipster
ENTRYPOINT ["./entrypoint.sh"]
EXPOSE 8080
ADD *.war app.war

View File

@ -1,15 +0,0 @@
version: '2'
services:
hsadminng-app:
image: hsadminng
environment:
- _JAVA_OPTIONS=-Xmx512m -Xms256m
- SPRING_PROFILES_ACTIVE=prod,swagger
- SPRING_DATASOURCE_URL=jdbc:postgresql://hsadminng-postgresql:5432/hsadminNg
- JHIPSTER_SLEEP=10 # gives time for the database to boot before the application
ports:
- 8080:8080
hsadminng-postgresql:
extends:
file: postgresql.yml
service: hsadminng-postgresql

View File

@ -1,4 +0,0 @@
#!/bin/sh
echo "The application will start in ${JHIPSTER_SLEEP}s..." && sleep ${JHIPSTER_SLEEP}
exec java ${JAVA_OPTS} -Djava.security.egd=file:/dev/./urandom -jar "${HOME}/app.war" "$@"

View File

@ -1,15 +0,0 @@
version: '2'
services:
jenkins:
image: jenkins:latest
ports:
- 49001:8080
- 50000:50000
# uncomment for docker in docker
#privileged: true
#volumes:
# enable persistent volume (warning: make sure that the local jenkins_home folder is created)
#- ~/volumes/jenkins_home:/var/jenkins_home
# mount docker sock and binary for docker in docker (only works on linux)
#- /var/run/docker.sock:/var/run/docker.sock
#- /usr/bin/docker:/usr/bin/docker

View File

@ -1,11 +0,0 @@
version: '2'
services:
hsadminng-postgresql:
image: postgres:10.4
# volumes:
# - ~/volumes/jhipster/hsadminNg/postgresql/:/var/lib/postgresql/data/
environment:
- POSTGRES_USER=hsadminNg
- POSTGRES_PASSWORD=
ports:
- 5432:5432

View File

@ -1,7 +0,0 @@
version: '2'
services:
hsadminng-sonar:
image: sonarqube:7.1
ports:
- 9001:9000
- 9092:9092

View File

@ -1,6 +0,0 @@
version: '2'
services:
swagger-editor:
image: swaggerapi/swagger-editor:latest
ports:
- 7742:8080

View File

@ -0,0 +1,13 @@
package net.hostsharing.hsadminng;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class HsadminNgApplication {
public static void main(String[] args) {
SpringApplication.run(HsadminNgApplication.class, args);
}
}

View File

@ -0,0 +1,16 @@
package net.hostsharing.hsadminng;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class TestController {
@ResponseBody
@RequestMapping(value = "/api/ping", method = RequestMethod.GET)
public String ping() {
return "pong\n";
}
}

View File

@ -0,0 +1,14 @@
package net.hostsharing.hsadminng.config;
import com.vladmihalcea.hibernate.type.array.StringArrayType;
import org.hibernate.dialect.PostgreSQL95Dialect;
@SuppressWarnings("unused") // configured in application.yml
public class PostgreSQL95CustomDialect extends PostgreSQL95Dialect {
public PostgreSQL95CustomDialect() {
this.registerHibernateType(2003, StringArrayType.class.getName());
this.registerHibernateType(1111, "pg-uuid");
}
}

View File

@ -0,0 +1,51 @@
package net.hostsharing.hsadminng.context;
import org.springframework.stereotype.Service;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.transaction.Transactional;
@Service
public class Context {
@PersistenceContext
private EntityManager em;
@Transactional(Transactional.TxType.MANDATORY)
public void setCurrentUser(final String userName) {
em.createNativeQuery(
String.format(
"set local hsadminng.currentUser = '%s';",
userName
)
).executeUpdate();
assumeNoSpecialRole();
}
public String getCurrentUser() {
return String.valueOf(em.createNativeQuery("select currentUser()").getSingleResult());
}
@Transactional(Transactional.TxType.MANDATORY)
public void assumeRoles(final String roles) {
em.createNativeQuery(
String.format(
"set local hsadminng.assumedRoles = '%s';",
roles
)
).executeUpdate();
}
@Transactional(Transactional.TxType.MANDATORY)
public void assumeNoSpecialRole() {
em.createNativeQuery(
"set local hsadminng.assumedRoles = '';"
).executeUpdate();
}
public String[] getAssumedRoles() {
return (String[]) em.createNativeQuery("select assumedRoles()").getSingleResult();
}
}

View File

@ -0,0 +1,63 @@
package net.hostsharing.hsadminng.errors;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Getter;
import org.springframework.core.NestedExceptionUtils;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.orm.jpa.JpaSystemException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
import java.time.LocalDateTime;
@ControllerAdvice
public class RestResponseEntityExceptionHandler
extends ResponseEntityExceptionHandler {
@ExceptionHandler(DataIntegrityViolationException.class)
protected ResponseEntity<CustomErrorResponse> handleConflict(
final RuntimeException exc, final WebRequest request) {
return new ResponseEntity<>(
new CustomErrorResponse(request.getContextPath(), exc, HttpStatus.CONFLICT), HttpStatus.CONFLICT);
}
@ExceptionHandler(JpaSystemException.class)
protected ResponseEntity<CustomErrorResponse> handleJpaExceptions(
final RuntimeException exc, final WebRequest request) {
return new ResponseEntity<>(
new CustomErrorResponse(request.getContextPath(), exc, HttpStatus.FORBIDDEN), HttpStatus.FORBIDDEN);
}
}
@Getter
class CustomErrorResponse {
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd hh:mm:ss")
private final LocalDateTime timestamp;
private final String path;
private final int status;
private final String error;
private final String message;
public CustomErrorResponse(final String path, final RuntimeException exc, final HttpStatus status) {
this.timestamp = LocalDateTime.now();
this.path = path;
this.status = status.value();
this.error = status.getReasonPhrase();
this.message = firstLine(NestedExceptionUtils.getMostSpecificCause(exc).getMessage());
}
private String firstLine(final String message) {
return message.split("\\r|\\n|\\r\\n", 0)[0];
}
}

View File

@ -0,0 +1,51 @@
package net.hostsharing.hsadminng.hs.hscustomer;
import net.hostsharing.hsadminng.context.Context;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.transaction.Transactional;
import java.util.List;
import java.util.UUID;
@RestController
public class CustomerController {
@Autowired
private Context context;
@Autowired
private CustomerRepository customerRepository;
@GetMapping(value = "/api/customers")
@Transactional
public List<CustomerEntity> listCustomers(
@RequestHeader(value = "current-user") String userName,
@RequestHeader(value = "assumed-roles", required = false) String assumedRoles,
@RequestParam(required = false) String prefix
) {
context.setCurrentUser(userName);
if (assumedRoles != null && !assumedRoles.isBlank()) {
context.assumeRoles(assumedRoles);
}
return customerRepository.findCustomerByOptionalPrefixLike(prefix);
}
@PostMapping(value = "/api/customers")
@Transactional
public CustomerEntity addCustomer(
@RequestHeader(value = "current-user") String userName,
@RequestHeader(value = "assumed-roles", required = false) String assumedRoles,
@RequestBody CustomerEntity customer
) {
context.setCurrentUser(userName);
if (assumedRoles != null && !assumedRoles.isBlank()) {
context.assumeRoles(assumedRoles);
}
if (customer.getUuid() == null) {
customer.setUuid(UUID.randomUUID());
}
return customerRepository.save(customer);
}
}

View File

@ -0,0 +1,22 @@
package net.hostsharing.hsadminng.hs.hscustomer;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import javax.persistence.*;
import java.util.UUID;
@Entity
@Table(name = "customer_rv")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class CustomerEntity {
private @Id UUID uuid;
private String prefix;
private int reference;
private @Column(name="adminusername")String adminUserName;
}

View File

@ -0,0 +1,21 @@
package net.hostsharing.hsadminng.hs.hscustomer;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.Repository;
import org.springframework.data.repository.query.Param;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
public interface CustomerRepository extends Repository<CustomerEntity, UUID> {
Optional<CustomerEntity> findByUuid(UUID id);
@Query("SELECT c FROM CustomerEntity c WHERE :prefix is null or c.prefix like concat(:prefix, '%')")
List<CustomerEntity> findCustomerByOptionalPrefixLike(@Param("prefix") String prefix);
CustomerEntity save(final CustomerEntity entity);
}

View File

@ -0,0 +1,33 @@
package net.hostsharing.hsadminng.hs.hspackage;
import net.hostsharing.hsadminng.context.Context;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.transaction.Transactional;
import java.util.List;
@RestController
public class PackageController {
@Autowired
private Context context;
@Autowired
private PackageRepository packageRepository;
@RequestMapping(value = "/api/packages", method = RequestMethod.GET)
@Transactional
public List<PackageEntity> listPackages(
@RequestHeader(value = "current-user") String userName,
@RequestHeader(value = "assumed-roles", required = false) String assumedRoles,
@RequestParam(required = false) String name
) {
context.setCurrentUser(userName);
if (assumedRoles != null && !assumedRoles.isBlank()) {
context.assumeRoles(assumedRoles);
}
return packageRepository.findAllByOptionalNameLike(name);
}
}

View File

@ -0,0 +1,25 @@
package net.hostsharing.hsadminng.hs.hspackage;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import net.hostsharing.hsadminng.hs.hscustomer.CustomerEntity;
import javax.persistence.*;
import java.util.UUID;
@Entity
@Table(name = "package_rv")
@Getter
@NoArgsConstructor
@AllArgsConstructor
public class PackageEntity {
private @Id UUID uuid;
private String name;
@ManyToOne(optional = false)
@JoinColumn(name = "customeruuid")
private CustomerEntity customer;
}

View File

@ -0,0 +1,13 @@
package net.hostsharing.hsadminng.hs.hspackage;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.Repository;
import java.util.List;
import java.util.UUID;
public interface PackageRepository extends Repository<PackageEntity, UUID> {
@Query("SELECT p FROM PackageEntity p WHERE :name is null or p.name like concat(:name, '%')")
List<PackageEntity> findAllByOptionalNameLike(final String name);
}

View File

@ -0,0 +1,34 @@
package net.hostsharing.hsadminng.rbac.rbacrole;
import net.hostsharing.hsadminng.context.Context;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RestController;
import javax.transaction.Transactional;
@RestController
public class RbacRoleController {
@Autowired
private Context context;
@Autowired
private RbacRoleRepository rbacRoleRepository;
@GetMapping(value = "/api/rbacroles")
@Transactional
public Iterable<RbacRoleEntity> listCustomers(
@RequestHeader(value = "current-user") String userName,
@RequestHeader(value = "assumed-roles", required = false) String assumedRoles
) {
context.setCurrentUser(userName);
if (assumedRoles != null && !assumedRoles.isBlank()) {
context.assumeRoles(assumedRoles);
}
return rbacRoleRepository.findAll();
}
}

View File

@ -0,0 +1,38 @@
package net.hostsharing.hsadminng.rbac.rbacrole;
import lombok.*;
import org.hibernate.annotations.Formula;
import org.springframework.data.annotation.Immutable;
import javax.persistence.*;
import java.util.UUID;
@Entity
@Table(name = "rbacrole_rv")
@Getter
@Setter
@ToString
@Immutable
@NoArgsConstructor
@AllArgsConstructor
public class RbacRoleEntity {
@Id
private UUID uuid;
@Column(name="objectuuid")
private UUID objectUuid;
@Column(name="objecttable")
private String objectTable;
@Column(name="objectidname")
private String objectIdName;
@Column(name="roletype")
@Enumerated(EnumType.STRING)
private RbacRoleType roleType;
@Formula("objectTable||'#'||objectIdName||'.'||roleType")
private String roleName;
}

View File

@ -0,0 +1,16 @@
package net.hostsharing.hsadminng.rbac.rbacrole;
import org.springframework.data.repository.Repository;
import java.util.List;
import java.util.UUID;
public interface RbacRoleRepository extends Repository<RbacRoleEntity, UUID> {
/**
* Returns all instances of the type.
*
* @return all entities
*/
List<RbacRoleEntity> findAll();
}

View File

@ -0,0 +1,5 @@
package net.hostsharing.hsadminng.rbac.rbacrole;
public enum RbacRoleType {
owner, admin, tenant
}

View File

@ -1,23 +0,0 @@
// Licensed under Apache-2.0
package org.hostsharing.hsadminng;
import org.hostsharing.hsadminng.config.DefaultProfileUtil;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
/**
* This is a helper Java class that provides an alternative to creating a web.xml.
* This will be invoked only when the application is deployed to a Servlet container like Tomcat, JBoss etc.
*/
public class ApplicationWebXml extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
/**
* set a default to use when no profile is configured.
*/
DefaultProfileUtil.addDefaultProfile(application.application());
return application.sources(HsadminNgApp.class);
}
}

View File

@ -1,111 +0,0 @@
// Licensed under Apache-2.0
package org.hostsharing.hsadminng;
import org.hostsharing.hsadminng.config.ApplicationProperties;
import org.hostsharing.hsadminng.config.DefaultProfileUtil;
import org.hostsharing.hsadminng.service.accessfilter.Role;
import io.github.jhipster.config.JHipsterConstants;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.liquibase.LiquibaseProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.core.env.Environment;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Arrays;
import java.util.Collection;
import javax.annotation.PostConstruct;
@SpringBootApplication
@EnableConfigurationProperties({ LiquibaseProperties.class, ApplicationProperties.class })
public class HsadminNgApp {
private static final Logger log = LoggerFactory.getLogger(HsadminNgApp.class);
private final Environment env;
public HsadminNgApp(Environment env) {
this.env = env;
// TODO mhoennig rather use @PostConstruct or something more decentral
Role.init();
}
/**
* Initializes hsadminNg.
* <p>
* Spring profiles can be configured with a program argument --spring.profiles.active=your-active-profile
* <p>
* You can find more information on how profiles work with JHipster on
* <a href="https://www.jhipster.tech/profiles/">https://www.jhipster.tech/profiles/</a>.
*/
@PostConstruct
public void initApplication() {
Collection<String> activeProfiles = Arrays.asList(env.getActiveProfiles());
if (activeProfiles.contains(JHipsterConstants.SPRING_PROFILE_DEVELOPMENT)
&& activeProfiles.contains(JHipsterConstants.SPRING_PROFILE_PRODUCTION)) {
log.error(
"You have misconfigured your application! It should not run " +
"with both the 'dev' and 'prod' profiles at the same time.");
}
if (activeProfiles.contains(JHipsterConstants.SPRING_PROFILE_DEVELOPMENT)
&& activeProfiles.contains(JHipsterConstants.SPRING_PROFILE_CLOUD)) {
log.error(
"You have misconfigured your application! It should not " +
"run with both the 'dev' and 'cloud' profiles at the same time.");
}
}
/**
* Main method, used to run the application.
*
* @param args the command line arguments
*/
public static void main(String[] args) {
SpringApplication app = new SpringApplication(HsadminNgApp.class);
DefaultProfileUtil.addDefaultProfile(app);
Environment env = app.run(args).getEnvironment();
logApplicationStartup(env);
}
private static void logApplicationStartup(Environment env) {
String protocol = "http";
if (env.getProperty("server.ssl.key-store") != null) {
protocol = "https";
}
String serverPort = env.getProperty("server.port");
String contextPath = env.getProperty("server.servlet.context-path");
if (StringUtils.isBlank(contextPath)) {
contextPath = "/";
}
String hostAddress = "localhost";
try {
hostAddress = InetAddress.getLocalHost().getHostAddress();
} catch (UnknownHostException e) {
log.warn("The host name could not be determined, using `localhost` as fallback");
}
log.info(
"\n----------------------------------------------------------\n\t" +
"Application '{}' is running! Access URLs:\n\t" +
"Local: \t\t{}://localhost:{}{}\n\t" +
"External: \t{}://{}:{}{}\n\t" +
"Profile(s): \t{}\n----------------------------------------------------------",
env.getProperty("spring.application.name"),
protocol,
serverPort,
contextPath,
protocol,
hostAddress,
serverPort,
contextPath,
env.getActiveProfiles());
}
}

View File

@ -1,116 +0,0 @@
// Licensed under Apache-2.0
package org.hostsharing.hsadminng.aop.logging;
import io.github.jhipster.config.JHipsterConstants;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.env.Environment;
import java.util.Arrays;
/**
* Aspect for logging execution of service and repository Spring components.
*
* By default, it only runs with the "dev" profile.
*/
@Aspect
public class LoggingAspect {
private final Logger log = LoggerFactory.getLogger(this.getClass());
private final Environment env;
public LoggingAspect(Environment env) {
this.env = env;
}
/**
* Pointcut that matches all repositories, services and Web REST endpoints.
*/
@Pointcut("within(@org.springframework.stereotype.Repository *)" +
" || within(@org.springframework.stereotype.Service *)" +
" || within(@org.springframework.web.bind.annotation.RestController *)")
public void springBeanPointcut() {
// Method is empty as this is just a Pointcut, the implementations are in the advices.
}
/**
* Pointcut that matches all Spring beans in the application's main packages.
*/
@Pointcut("within(org.hostsharing.hsadminng.repository..*)" +
" || within(org.hostsharing.hsadminng.service..*)" +
" || within(org.hostsharing.hsadminng.web.rest..*)")
public void applicationPackagePointcut() {
// Method is empty as this is just a Pointcut, the implementations are in the advices.
}
/**
* Advice that logs methods throwing exceptions.
*
* @param joinPoint join point for advice
* @param e exception
*/
@AfterThrowing(pointcut = "applicationPackagePointcut() && springBeanPointcut()", throwing = "e")
public void logAfterThrowing(JoinPoint joinPoint, Throwable e) {
if (env.acceptsProfiles(JHipsterConstants.SPRING_PROFILE_DEVELOPMENT)) {
log.error(
"Exception in {}.{}() with cause = \'{}\' and exception = \'{}\'",
joinPoint.getSignature().getDeclaringTypeName(),
joinPoint.getSignature().getName(),
e.getCause() != null ? e.getCause() : "NULL",
e.getMessage(),
e);
} else {
log.error(
"Exception in {}.{}() with cause = {}",
joinPoint.getSignature().getDeclaringTypeName(),
joinPoint.getSignature().getName(),
e.getCause() != null ? e.getCause() : "NULL");
}
}
/**
* Advice that logs when a method is entered and exited.
*
* @param joinPoint join point for advice
* @return result
* @throws Throwable throws IllegalArgumentException
*/
@Around("applicationPackagePointcut() && springBeanPointcut()")
public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
if (log.isDebugEnabled()) {
log.debug(
"Enter: {}.{}() with argument[s] = {}",
joinPoint.getSignature().getDeclaringTypeName(),
joinPoint.getSignature().getName(),
Arrays.toString(joinPoint.getArgs()));
}
try {
Object result = joinPoint.proceed();
if (log.isDebugEnabled()) {
log.debug(
"Exit: {}.{}() with result = {}",
joinPoint.getSignature().getDeclaringTypeName(),
joinPoint.getSignature().getName(),
result);
}
return result;
} catch (IllegalArgumentException e) {
log.error(
"Illegal argument: {} in {}.{}()",
Arrays.toString(joinPoint.getArgs()),
joinPoint.getSignature().getDeclaringTypeName(),
joinPoint.getSignature().getName());
throw e;
}
}
}

View File

@ -1,15 +0,0 @@
// Licensed under Apache-2.0
package org.hostsharing.hsadminng.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* Properties specific to Hsadmin Ng.
* <p>
* Properties are configured in the application.yml file.
* See {@link io.github.jhipster.config.JHipsterProperties} for a good example.
*/
@ConfigurationProperties(prefix = "application", ignoreUnknownFields = false)
public class ApplicationProperties {
}

View File

@ -1,60 +0,0 @@
// Licensed under Apache-2.0
package org.hostsharing.hsadminng.config;
import io.github.jhipster.async.ExceptionHandlingAsyncTaskExecutor;
import io.github.jhipster.config.JHipsterProperties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.aop.interceptor.SimpleAsyncUncaughtExceptionHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.*;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
@Configuration
@EnableAsync
@EnableScheduling
public class AsyncConfiguration implements AsyncConfigurer, SchedulingConfigurer {
private final Logger log = LoggerFactory.getLogger(AsyncConfiguration.class);
private final JHipsterProperties jHipsterProperties;
public AsyncConfiguration(JHipsterProperties jHipsterProperties) {
this.jHipsterProperties = jHipsterProperties;
}
@Override
@Bean(name = "taskExecutor")
public Executor getAsyncExecutor() {
log.debug("Creating Async Task Executor");
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(jHipsterProperties.getAsync().getCorePoolSize());
executor.setMaxPoolSize(jHipsterProperties.getAsync().getMaxPoolSize());
executor.setQueueCapacity(jHipsterProperties.getAsync().getQueueCapacity());
executor.setThreadNamePrefix("hsadmin-ng-Executor-");
return new ExceptionHandlingAsyncTaskExecutor(executor);
}
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return new SimpleAsyncUncaughtExceptionHandler();
}
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
taskRegistrar.setScheduler(scheduledTaskExecutor());
}
@Bean
public Executor scheduledTaskExecutor() {
return Executors.newScheduledThreadPool(jHipsterProperties.getAsync().getCorePoolSize());
}
}

View File

@ -1,45 +0,0 @@
// Licensed under Apache-2.0
package org.hostsharing.hsadminng.config;
import io.github.jhipster.config.JHipsterProperties;
import org.ehcache.config.builders.*;
import org.ehcache.jsr107.Eh107Configuration;
import org.springframework.boot.autoconfigure.cache.JCacheManagerCustomizer;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.*;
import java.time.Duration;
@Configuration
@EnableCaching
public class CacheConfiguration {
private final javax.cache.configuration.Configuration<Object, Object> jcacheConfiguration;
public CacheConfiguration(JHipsterProperties jHipsterProperties) {
JHipsterProperties.Cache.Ehcache ehcache = jHipsterProperties.getCache().getEhcache();
jcacheConfiguration = Eh107Configuration.fromEhcacheCacheConfiguration(
CacheConfigurationBuilder.newCacheConfigurationBuilder(
Object.class,
Object.class,
ResourcePoolsBuilder.heap(ehcache.getMaxEntries()))
.withExpiry(
ExpiryPolicyBuilder.timeToLiveExpiration(Duration.ofSeconds(ehcache.getTimeToLiveSeconds())))
.build());
}
@Bean
public JCacheManagerCustomizer cacheManagerCustomizer() {
return cm -> {
cm.createCache(org.hostsharing.hsadminng.repository.UserRepository.USERS_BY_LOGIN_CACHE, jcacheConfiguration);
cm.createCache(org.hostsharing.hsadminng.repository.UserRepository.USERS_BY_EMAIL_CACHE, jcacheConfiguration);
// jhipster-needle-ehcache-add-entry
cm.createCache(
org.hostsharing.hsadminng.repository.UserRoleAssignmentRepository.CURRENT_USER_ROLE_ASSIGNMENTS_CACHE,
jcacheConfiguration);
};
}
}

View File

@ -1,28 +0,0 @@
// Licensed under Apache-2.0
package org.hostsharing.hsadminng.config;
import io.github.jhipster.config.JHipsterConstants;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.config.java.AbstractCloudConfig;
import org.springframework.context.annotation.*;
import javax.sql.DataSource;
@Configuration
@Profile(JHipsterConstants.SPRING_PROFILE_CLOUD)
public class CloudDatabaseConfiguration extends AbstractCloudConfig {
private final Logger log = LoggerFactory.getLogger(CloudDatabaseConfiguration.class);
private static final String CLOUD_CONFIGURATION_HIKARI_PREFIX = "spring.datasource.hikari";
@Bean
@ConfigurationProperties(CLOUD_CONFIGURATION_HIKARI_PREFIX)
public DataSource dataSource() {
log.info("Configuring JDBC datasource from a cloud provider");
return connectionFactory().dataSource();
}
}

View File

@ -1,18 +0,0 @@
// Licensed under Apache-2.0
package org.hostsharing.hsadminng.config;
/**
* Application constants.
*/
public final class Constants {
// Regex for acceptable logins
public static final String LOGIN_REGEX = "^[_.@A-Za-z0-9-]*$";
public static final String SYSTEM_ACCOUNT = "system";
public static final String ANONYMOUS_USER = "anonymoususer";
public static final String DEFAULT_LANGUAGE = "de";
private Constants() {
}
}

View File

@ -1,60 +0,0 @@
// Licensed under Apache-2.0
package org.hostsharing.hsadminng.config;
import io.github.jhipster.config.JHipsterConstants;
import io.github.jhipster.config.h2.H2ConfigurationHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.core.env.Environment;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import java.sql.SQLException;
@Configuration
@EnableJpaRepositories("org.hostsharing.hsadminng.repository")
@EnableJpaAuditing(auditorAwareRef = "springSecurityAuditorAware")
@EnableTransactionManagement
public class DatabaseConfiguration {
private final Logger log = LoggerFactory.getLogger(DatabaseConfiguration.class);
private final Environment env;
public DatabaseConfiguration(Environment env) {
this.env = env;
}
/**
* Open the TCP port for the H2 database, so it is available remotely.
*
* @return the H2 database TCP server
* @throws SQLException if the server failed to start
*/
@Bean(initMethod = "start", destroyMethod = "stop")
@Profile(JHipsterConstants.SPRING_PROFILE_DEVELOPMENT)
public Object h2TCPServer() throws SQLException {
String port = getValidPortForH2();
log.debug("H2 database is available on port {}", port);
return H2ConfigurationHelper.createServer(port);
}
private String getValidPortForH2() {
int port = Integer.parseInt(env.getProperty("server.port"));
if (port < 10000) {
port = 10000 + port;
} else {
if (port < 63536) {
port = port + 2000;
} else {
port = port - 2000;
}
}
return String.valueOf(port);
}
}

View File

@ -1,21 +0,0 @@
// Licensed under Apache-2.0
package org.hostsharing.hsadminng.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.format.FormatterRegistry;
import org.springframework.format.datetime.standard.DateTimeFormatterRegistrar;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* Configure the converters to use the ISO format for dates by default.
*/
@Configuration
public class DateTimeFormatConfiguration implements WebMvcConfigurer {
@Override
public void addFormatters(FormatterRegistry registry) {
DateTimeFormatterRegistrar registrar = new DateTimeFormatterRegistrar();
registrar.setUseIsoFormat(true);
registrar.registerFormatters(registry);
}
}

View File

@ -1,52 +0,0 @@
// Licensed under Apache-2.0
package org.hostsharing.hsadminng.config;
import io.github.jhipster.config.JHipsterConstants;
import org.springframework.boot.SpringApplication;
import org.springframework.core.env.Environment;
import java.util.*;
/**
* Utility class to load a Spring profile to be used as default
* when there is no <code>spring.profiles.active</code> set in the environment or as command line argument.
* If the value is not available in <code>application.yml</code> then <code>dev</code> profile will be used as default.
*/
public final class DefaultProfileUtil {
private static final String SPRING_PROFILE_DEFAULT = "spring.profiles.default";
private DefaultProfileUtil() {
}
/**
* Set a default to use when no profile is configured.
*
* @param app the Spring application
*/
public static void addDefaultProfile(SpringApplication app) {
Map<String, Object> defProperties = new HashMap<>();
/*
* The default profile to use when no other profiles are defined
* This cannot be set in the <code>application.yml</code> file.
* See https://github.com/spring-projects/spring-boot/issues/1219
*/
defProperties.put(SPRING_PROFILE_DEFAULT, JHipsterConstants.SPRING_PROFILE_DEVELOPMENT);
app.setDefaultProperties(defProperties);
}
/**
* Get the profiles that are applied else get default profiles.
*
* @param env spring environment
* @return profiles
*/
public static String[] getActiveProfiles(Environment env) {
String[] profiles = env.getActiveProfiles();
if (profiles.length == 0) {
return env.getDefaultProfiles();
}
return profiles;
}
}

View File

@ -1,64 +0,0 @@
// Licensed under Apache-2.0
package org.hostsharing.hsadminng.config;
import com.fasterxml.jackson.datatype.hibernate5.Hibernate5Module;
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.module.afterburner.AfterburnerModule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.zalando.problem.ProblemModule;
import org.zalando.problem.violations.ConstraintViolationProblemModule;
@Configuration
public class JacksonConfiguration {
/**
* Support for Java date and time API.
*
* @return the corresponding Jackson module.
*/
@Bean
public JavaTimeModule javaTimeModule() {
return new JavaTimeModule();
}
@Bean
public Jdk8Module jdk8TimeModule() {
return new Jdk8Module();
}
/*
* Support for Hibernate types in Jackson.
*/
@Bean
public Hibernate5Module hibernate5Module() {
return new Hibernate5Module();
}
/*
* Jackson Afterburner module to speed up serialization/deserialization.
*/
@Bean
public AfterburnerModule afterburnerModule() {
return new AfterburnerModule();
}
/*
* Module for serialization/deserialization of RFC7807 Problem.
*/
@Bean
ProblemModule problemModule() {
return new ProblemModule();
}
/*
* Module for serialization/deserialization of ConstraintViolationProblem.
*/
@Bean
ConstraintViolationProblemModule constraintViolationProblemModule() {
return new ConstraintViolationProblemModule();
}
}

View File

@ -1,52 +0,0 @@
// Licensed under Apache-2.0
package org.hostsharing.hsadminng.config;
import io.github.jhipster.config.JHipsterConstants;
import io.github.jhipster.config.liquibase.AsyncSpringLiquibase;
import liquibase.integration.spring.SpringLiquibase;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.liquibase.LiquibaseProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.core.task.TaskExecutor;
import javax.sql.DataSource;
@Configuration
public class LiquibaseConfiguration {
private final Logger log = LoggerFactory.getLogger(LiquibaseConfiguration.class);
private final Environment env;
public LiquibaseConfiguration(Environment env) {
this.env = env;
}
@Bean
public SpringLiquibase liquibase(
@Qualifier("taskExecutor") TaskExecutor taskExecutor,
DataSource dataSource,
LiquibaseProperties liquibaseProperties) {
// Use liquibase.integration.spring.SpringLiquibase if you don't want Liquibase to start asynchronously
SpringLiquibase liquibase = new AsyncSpringLiquibase(taskExecutor, env);
liquibase.setDataSource(dataSource);
liquibase.setChangeLog("classpath:config/liquibase/master.xml");
liquibase.setContexts(liquibaseProperties.getContexts());
liquibase.setDefaultSchema(liquibaseProperties.getDefaultSchema());
liquibase.setDropFirst(liquibaseProperties.isDropFirst());
liquibase.setChangeLogParameters(liquibaseProperties.getParameters());
if (env.acceptsProfiles(JHipsterConstants.SPRING_PROFILE_NO_LIQUIBASE)) {
liquibase.setShouldRun(false);
} else {
liquibase.setShouldRun(liquibaseProperties.isEnabled());
log.debug("Configuring Liquibase");
}
return liquibase;
}
}

Some files were not shown because too many files have changed in this diff Show More