- Jhipster angular app in which I have opened an AJP port on 8008
This commit is contained in:
3
.bowerrc
Normal file
3
.bowerrc
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"directory": "src/main/webapp/bower_components"
|
||||
}
|
||||
24
.editorconfig
Normal file
24
.editorconfig
Normal file
@@ -0,0 +1,24 @@
|
||||
# EditorConfig helps developers define and maintain consistent
|
||||
# coding styles between different editors and IDEs
|
||||
# editorconfig.org
|
||||
|
||||
root = true
|
||||
|
||||
[*]
|
||||
|
||||
# Change these settings to your own preference
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
|
||||
# We recommend you to keep these unchanged
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
[{package,bower}.json]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
21
.gitattributes
vendored
Normal file
21
.gitattributes
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
# All text files should have the "lf" (Unix) line endings
|
||||
* text eol=lf
|
||||
|
||||
# Explicitly declare text files you want to always be normalized and converted
|
||||
# to native line endings on checkout.
|
||||
*.java text
|
||||
*.js text
|
||||
*.css text
|
||||
*.html text
|
||||
|
||||
# Denote all files that are truly binary and should not be modified.
|
||||
*.png binary
|
||||
*.jpg binary
|
||||
*.jar binary
|
||||
*.pdf binary
|
||||
*.eot binary
|
||||
*.ttf binary
|
||||
*.gzip binary
|
||||
*.gz binary
|
||||
*.ai binary
|
||||
*.eps binary
|
||||
123
.gitignore
vendored
Normal file
123
.gitignore
vendored
Normal file
@@ -0,0 +1,123 @@
|
||||
######################
|
||||
# Project Specific
|
||||
######################
|
||||
/src/main/webapp/dist
|
||||
|
||||
######################
|
||||
# Node
|
||||
######################
|
||||
/node/**
|
||||
/node_modules/**
|
||||
|
||||
######################
|
||||
# SASS
|
||||
######################
|
||||
.sass-cache/**
|
||||
|
||||
######################
|
||||
# Eclipse
|
||||
######################
|
||||
*.pydevproject
|
||||
.project
|
||||
.metadata
|
||||
/bin/**
|
||||
/tmp/**
|
||||
/tmp/**/*
|
||||
*.tmp
|
||||
*.bak
|
||||
*.swp
|
||||
*~.nib
|
||||
local.properties
|
||||
.classpath
|
||||
.settings/**
|
||||
.loadpath
|
||||
/src/main/resources/rebel.xml
|
||||
|
||||
# External tool builders
|
||||
.externalToolBuilders/**
|
||||
|
||||
# Locally stored "Eclipse launch configurations"
|
||||
*.launch
|
||||
|
||||
# CDT-specific
|
||||
.cproject
|
||||
|
||||
# PDT-specific
|
||||
.buildpath
|
||||
|
||||
######################
|
||||
# Intellij
|
||||
######################
|
||||
.idea/**
|
||||
*.iml
|
||||
*.iws
|
||||
*.ipr
|
||||
*.ids
|
||||
*.orig
|
||||
|
||||
######################
|
||||
# Maven
|
||||
######################
|
||||
/log/**
|
||||
/target/**
|
||||
|
||||
######################
|
||||
# Gradle
|
||||
######################
|
||||
.gradle/**
|
||||
|
||||
######################
|
||||
# Package Files
|
||||
######################
|
||||
*.jar
|
||||
*.war
|
||||
*.ear
|
||||
*.db
|
||||
|
||||
######################
|
||||
# Windows
|
||||
######################
|
||||
# Windows image file caches
|
||||
Thumbs.db
|
||||
|
||||
# Folder config file
|
||||
Desktop.ini
|
||||
|
||||
######################
|
||||
# Mac OSX
|
||||
######################
|
||||
.DS_Store
|
||||
.svn
|
||||
|
||||
# Thumbnails
|
||||
._*
|
||||
|
||||
# Files that might appear on external disk
|
||||
.Spotlight-V100
|
||||
.Trashes
|
||||
|
||||
######################
|
||||
# Directories
|
||||
######################
|
||||
/build/**
|
||||
/bin/**
|
||||
/spring_loaded/**
|
||||
/deploy/**
|
||||
|
||||
######################
|
||||
# Logs
|
||||
######################
|
||||
*.log
|
||||
|
||||
######################
|
||||
# Others
|
||||
######################
|
||||
*.class
|
||||
*.*~
|
||||
*~
|
||||
.merge_file*
|
||||
|
||||
######################
|
||||
# Gradle Wrapper
|
||||
######################
|
||||
!gradle/wrapper/gradle-wrapper.jar
|
||||
22
.jshintrc
Normal file
22
.jshintrc
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"node": true,
|
||||
"esnext": true,
|
||||
"bitwise": true,
|
||||
"camelcase": true,
|
||||
"curly": true,
|
||||
"eqeqeq": true,
|
||||
"immed": true,
|
||||
"indent": 4,
|
||||
"latedef": true,
|
||||
"newcap": true,
|
||||
"noarg": true,
|
||||
"quotmark": "single",
|
||||
"regexp": true,
|
||||
"undef": true,
|
||||
"unused": true,
|
||||
"strict": true,
|
||||
"trailing": true,
|
||||
"smarttabs": true,
|
||||
"white": true,
|
||||
"predef": ["angular"]
|
||||
}
|
||||
21
.yo-rc.json
Normal file
21
.yo-rc.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"generator-jhipster": {
|
||||
"baseName": "openAjpPort",
|
||||
"packageName": "mark.quinn",
|
||||
"packageFolder": "mark/quinn",
|
||||
"authenticationType": "session",
|
||||
"hibernateCache": "no",
|
||||
"clusteredHttpSession": "no",
|
||||
"websocket": "no",
|
||||
"databaseType": "sql",
|
||||
"devDatabaseType": "h2Memory",
|
||||
"prodDatabaseType": "mysql",
|
||||
"searchEngine": "no",
|
||||
"useCompass": false,
|
||||
"buildTool": "maven",
|
||||
"frontendBuilder": "grunt",
|
||||
"javaVersion": "8",
|
||||
"enableTranslation": false,
|
||||
"rememberMeKey": "d9443054ecffc6ae41b0912a82deb97699482f86"
|
||||
}
|
||||
}
|
||||
398
Gruntfile.js
Normal file
398
Gruntfile.js
Normal file
@@ -0,0 +1,398 @@
|
||||
// Generated on 2015-11-08 using generator-jhipster 2.19.0
|
||||
'use strict';
|
||||
var fs = require('fs');
|
||||
|
||||
var parseString = require('xml2js').parseString;
|
||||
// Returns the second occurence of the version number
|
||||
var parseVersionFromPomXml = function() {
|
||||
var version;
|
||||
var pomXml = fs.readFileSync('pom.xml', "utf8");
|
||||
parseString(pomXml, function (err, result){
|
||||
version = result.project.version[0];
|
||||
});
|
||||
return version;
|
||||
};
|
||||
|
||||
// usemin custom step
|
||||
var useminAutoprefixer = {
|
||||
name: 'autoprefixer',
|
||||
createConfig: function(context, block) {
|
||||
if(block.src.length === 0) {
|
||||
return {};
|
||||
} else {
|
||||
return require('grunt-usemin/lib/config/cssmin').createConfig(context, block) // Reuse cssmins createConfig
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = function (grunt) {
|
||||
require('load-grunt-tasks')(grunt);
|
||||
require('time-grunt')(grunt);
|
||||
|
||||
grunt.initConfig({
|
||||
yeoman: {
|
||||
// configurable paths
|
||||
app: require('./bower.json').appPath || 'app',
|
||||
dist: 'src/main/webapp/dist'
|
||||
},
|
||||
watch: {
|
||||
bower: {
|
||||
files: ['bower.json'],
|
||||
tasks: ['wiredep']
|
||||
},
|
||||
ngconstant: {
|
||||
files: ['Gruntfile.js', 'pom.xml'],
|
||||
tasks: ['ngconstant:dev']
|
||||
}
|
||||
},
|
||||
autoprefixer: {
|
||||
// src and dest is configured in a subtask called "generated" by usemin
|
||||
},
|
||||
wiredep: {
|
||||
app: {
|
||||
src: ['src/main/webapp/index.html'],
|
||||
exclude: [
|
||||
/angular-i18n/ // localizations are loaded dynamically
|
||||
]
|
||||
},
|
||||
test: {
|
||||
src: 'src/test/javascript/karma.conf.js',
|
||||
exclude: [/angular-i18n/, /angular-scenario/],
|
||||
ignorePath: /\.\.\/\.\.\//, // remove ../../ from paths of injected javascripts
|
||||
devDependencies: true,
|
||||
fileTypes: {
|
||||
js: {
|
||||
block: /(([\s\t]*)\/\/\s*bower:*(\S*))(\n|\r|.)*?(\/\/\s*endbower)/gi,
|
||||
detect: {
|
||||
js: /'(.*\.js)'/gi
|
||||
},
|
||||
replace: {
|
||||
js: '\'{{filePath}}\','
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
browserSync: {
|
||||
dev: {
|
||||
bsFiles: {
|
||||
src : [
|
||||
'src/main/webapp/**/*.html',
|
||||
'src/main/webapp/**/*.json',
|
||||
'src/main/webapp/assets/styles/**/*.css',
|
||||
'src/main/webapp/scripts/**/*.js',
|
||||
'src/main/webapp/assets/images/**/*.{png,jpg,jpeg,gif,webp,svg}',
|
||||
'tmp/**/*.{css,js}'
|
||||
]
|
||||
}
|
||||
},
|
||||
options: {
|
||||
watchTask: true,
|
||||
proxy: "localhost:8080"
|
||||
}
|
||||
},
|
||||
clean: {
|
||||
dist: {
|
||||
files: [{
|
||||
dot: true,
|
||||
src: [
|
||||
'.tmp',
|
||||
'<%= yeoman.dist %>/*',
|
||||
'!<%= yeoman.dist %>/.git*'
|
||||
]
|
||||
}]
|
||||
},
|
||||
server: '.tmp'
|
||||
},
|
||||
jshint: {
|
||||
options: {
|
||||
jshintrc: '.jshintrc'
|
||||
},
|
||||
all: [
|
||||
'Gruntfile.js',
|
||||
'src/main/webapp/scripts/app.js',
|
||||
'src/main/webapp/scripts/app/**/*.js',
|
||||
'src/main/webapp/scripts/components/**/*.js'
|
||||
]
|
||||
},
|
||||
concat: {
|
||||
// src and dest is configured in a subtask called "generated" by usemin
|
||||
},
|
||||
uglifyjs: {
|
||||
// src and dest is configured in a subtask called "generated" by usemin
|
||||
},
|
||||
rev: {
|
||||
dist: {
|
||||
files: {
|
||||
src: [
|
||||
'<%= yeoman.dist %>/scripts/**/*.js',
|
||||
'<%= yeoman.dist %>/assets/styles/**/*.css',
|
||||
'<%= yeoman.dist %>/assets/images/**/*.{png,jpg,jpeg,gif,webp,svg}',
|
||||
'<%= yeoman.dist %>/assets/fonts/*'
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
useminPrepare: {
|
||||
html: 'src/main/webapp/**/*.html',
|
||||
options: {
|
||||
dest: '<%= yeoman.dist %>',
|
||||
flow: {
|
||||
html: {
|
||||
steps: {
|
||||
js: ['concat', 'uglifyjs'],
|
||||
css: ['cssmin', useminAutoprefixer] // Let cssmin concat files so it corrects relative paths to fonts and images
|
||||
},
|
||||
post: {}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
usemin: {
|
||||
html: ['<%= yeoman.dist %>/**/*.html'],
|
||||
css: ['<%= yeoman.dist %>/assets/styles/**/*.css'],
|
||||
js: ['<%= yeoman.dist %>/scripts/**/*.js'],
|
||||
options: {
|
||||
assetsDirs: ['<%= yeoman.dist %>', '<%= yeoman.dist %>/assets/styles', '<%= yeoman.dist %>/assets/images', '<%= yeoman.dist %>/assets/fonts'],
|
||||
patterns: {
|
||||
js: [
|
||||
[/(assets\/images\/.*?\.(?:gif|jpeg|jpg|png|webp|svg))/gm, 'Update the JS to reference our revved images']
|
||||
]
|
||||
},
|
||||
dirs: ['<%= yeoman.dist %>']
|
||||
}
|
||||
},
|
||||
imagemin: {
|
||||
dist: {
|
||||
files: [{
|
||||
expand: true,
|
||||
cwd: 'src/main/webapp/assets/images',
|
||||
src: '**/*.{jpg,jpeg}', // we don't optimize PNG files as it doesn't work on Linux. If you are not on Linux, feel free to use '**/*.{png,jpg,jpeg}'
|
||||
dest: '<%= yeoman.dist %>/assets/images'
|
||||
}]
|
||||
}
|
||||
},
|
||||
svgmin: {
|
||||
dist: {
|
||||
files: [{
|
||||
expand: true,
|
||||
cwd: 'src/main/webapp/assets/images',
|
||||
src: '**/*.svg',
|
||||
dest: '<%= yeoman.dist %>/assets/images'
|
||||
}]
|
||||
}
|
||||
},
|
||||
cssmin: {
|
||||
// src and dest is configured in a subtask called "generated" by usemin
|
||||
},
|
||||
ngtemplates: {
|
||||
dist: {
|
||||
cwd: 'src/main/webapp',
|
||||
src: ['scripts/app/**/*.html', 'scripts/components/**/*.html',],
|
||||
dest: '.tmp/templates/templates.js',
|
||||
options: {
|
||||
module: 'openajpportApp',
|
||||
usemin: 'scripts/app.js',
|
||||
htmlmin: '<%= htmlmin.dist.options %>'
|
||||
}
|
||||
}
|
||||
},
|
||||
htmlmin: {
|
||||
dist: {
|
||||
options: {
|
||||
removeCommentsFromCDATA: true,
|
||||
// https://github.com/yeoman/grunt-usemin/issues/44
|
||||
collapseWhitespace: true,
|
||||
collapseBooleanAttributes: true,
|
||||
conservativeCollapse: true,
|
||||
removeAttributeQuotes: true,
|
||||
removeRedundantAttributes: true,
|
||||
useShortDoctype: true,
|
||||
removeEmptyAttributes: true,
|
||||
keepClosingSlash: true
|
||||
},
|
||||
files: [{
|
||||
expand: true,
|
||||
cwd: '<%= yeoman.dist %>',
|
||||
src: ['*.html'],
|
||||
dest: '<%= yeoman.dist %>'
|
||||
}]
|
||||
}
|
||||
},
|
||||
// Put files not handled in other tasks here
|
||||
copy: {
|
||||
fonts: {
|
||||
files: [{
|
||||
expand: true,
|
||||
dot: true,
|
||||
flatten: true,
|
||||
cwd: 'src/main/webapp',
|
||||
dest: '<%= yeoman.dist %>/assets/fonts',
|
||||
src: [
|
||||
'bower_components/bootstrap/fonts/*.*'
|
||||
]
|
||||
}]
|
||||
},
|
||||
dist: {
|
||||
files: [{
|
||||
expand: true,
|
||||
dot: true,
|
||||
cwd: 'src/main/webapp',
|
||||
dest: '<%= yeoman.dist %>',
|
||||
src: [
|
||||
'*.html',
|
||||
'scripts/**/*.html',
|
||||
'assets/images/**/*.{png,gif,webp,jpg,jpeg,svg}',
|
||||
'assets/fonts/*'
|
||||
]
|
||||
}, {
|
||||
expand: true,
|
||||
cwd: '.tmp/assets/images',
|
||||
dest: '<%= yeoman.dist %>/assets/images',
|
||||
src: [
|
||||
'generated/*'
|
||||
]
|
||||
}]
|
||||
},
|
||||
generateOpenshiftDirectory: {
|
||||
expand: true,
|
||||
dest: 'deploy/openshift',
|
||||
src: [
|
||||
'pom.xml',
|
||||
'src/main/**'
|
||||
]
|
||||
}
|
||||
},
|
||||
karma: {
|
||||
unit: {
|
||||
configFile: 'src/test/javascript/karma.conf.js',
|
||||
singleRun: true
|
||||
}
|
||||
},
|
||||
ngAnnotate: {
|
||||
dist: {
|
||||
files: [{
|
||||
expand: true,
|
||||
cwd: '.tmp/concat/scripts',
|
||||
src: '*.js',
|
||||
dest: '.tmp/concat/scripts'
|
||||
}]
|
||||
}
|
||||
},
|
||||
buildcontrol: {
|
||||
options: {
|
||||
commit: true,
|
||||
push: false,
|
||||
connectCommits: false,
|
||||
message: 'Built %sourceName% from commit %sourceCommit% on branch %sourceBranch%'
|
||||
},
|
||||
openshift: {
|
||||
options: {
|
||||
dir: 'deploy/openshift',
|
||||
remote: 'openshift',
|
||||
branch: 'master'
|
||||
}
|
||||
}
|
||||
},
|
||||
ngconstant: {
|
||||
options: {
|
||||
name: 'openajpportApp',
|
||||
deps: false,
|
||||
wrap: '"use strict";\n// DO NOT EDIT THIS FILE, EDIT THE GRUNT TASK NGCONSTANT SETTINGS INSTEAD WHICH GENERATES THIS FILE\n{%= __ngModule %}'
|
||||
},
|
||||
dev: {
|
||||
options: {
|
||||
dest: 'src/main/webapp/scripts/app/app.constants.js'
|
||||
},
|
||||
constants: {
|
||||
ENV: 'dev',
|
||||
VERSION: parseVersionFromPomXml()
|
||||
}
|
||||
},
|
||||
prod: {
|
||||
options: {
|
||||
dest: '.tmp/scripts/app/app.constants.js'
|
||||
},
|
||||
constants: {
|
||||
ENV: 'prod',
|
||||
VERSION: parseVersionFromPomXml()
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
grunt.registerTask('serve', [
|
||||
'clean:server',
|
||||
'wiredep',
|
||||
'ngconstant:dev',
|
||||
'browserSync',
|
||||
'watch'
|
||||
]);
|
||||
|
||||
grunt.registerTask('server', function (target) {
|
||||
grunt.log.warn('The `server` task has been deprecated. Use `grunt serve` to start a server.');
|
||||
grunt.task.run([target ? ('serve:' + target) : 'serve']);
|
||||
});
|
||||
|
||||
grunt.registerTask('test', [
|
||||
'clean:server',
|
||||
'wiredep:test',
|
||||
'ngconstant:dev',
|
||||
'karma'
|
||||
]);
|
||||
|
||||
grunt.registerTask('build', [
|
||||
'clean:dist',
|
||||
'wiredep:app',
|
||||
'ngconstant:prod',
|
||||
'useminPrepare',
|
||||
'ngtemplates',
|
||||
'imagemin',
|
||||
'svgmin',
|
||||
'concat',
|
||||
'copy:fonts',
|
||||
'copy:dist',
|
||||
'ngAnnotate',
|
||||
'cssmin',
|
||||
'autoprefixer',
|
||||
'uglify',
|
||||
'rev',
|
||||
'usemin',
|
||||
'htmlmin'
|
||||
]);
|
||||
|
||||
grunt.registerTask('appendSkipBower', 'Force skip of bower for Gradle', function () {
|
||||
|
||||
if (!grunt.file.exists(filepath)) {
|
||||
// Assume this is a maven project
|
||||
return true;
|
||||
}
|
||||
|
||||
var fileContent = grunt.file.read(filepath);
|
||||
var skipBowerIndex = fileContent.indexOf("skipBower=true");
|
||||
|
||||
if (skipBowerIndex != -1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
grunt.file.write(filepath, fileContent + "\nskipBower=true\n");
|
||||
});
|
||||
|
||||
grunt.registerTask('buildOpenshift', [
|
||||
'test',
|
||||
'build',
|
||||
'copy:generateOpenshiftDirectory',
|
||||
]);
|
||||
|
||||
grunt.registerTask('deployOpenshift', [
|
||||
'test',
|
||||
'build',
|
||||
'copy:generateOpenshiftDirectory',
|
||||
'buildcontrol:openshift'
|
||||
]);
|
||||
|
||||
grunt.registerTask('default', [
|
||||
'test',
|
||||
'build'
|
||||
]);
|
||||
};
|
||||
31
bower.json
Normal file
31
bower.json
Normal file
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"name": "openAjpPort",
|
||||
"version": "0.0.0",
|
||||
"appPath": "src/main/webapp",
|
||||
"testPath": "src/test/javascript/spec",
|
||||
"dependencies": {
|
||||
"bootstrap": "3.3.4",
|
||||
"modernizr": "2.8.3",
|
||||
"jquery": "2.1.4",
|
||||
"json3": "3.3.2",
|
||||
"angular-bootstrap": "0.13.1",
|
||||
"angular-ui-router": "0.2.15",
|
||||
"angular": "1.4.3",
|
||||
"angular-resource": "1.4.3",
|
||||
"angular-cookies": "1.4.3",
|
||||
"angular-sanitize": "1.4.3",
|
||||
"angular-local-storage": "0.2.0",
|
||||
"angular-cache-buster": "0.4.3",
|
||||
"ngInfiniteScroll": "1.2.0",
|
||||
"ng-file-upload": "5.0.9"
|
||||
},
|
||||
"devDependencies": {
|
||||
"angular-mocks": "1.4.3",
|
||||
"angular-scenario": "1.4.3"
|
||||
},
|
||||
"resolutions": {
|
||||
"angular": "1.4.3",
|
||||
"angular-cookies": "1.4.3",
|
||||
"jquery": "2.1.4"
|
||||
}
|
||||
}
|
||||
56
package.json
Normal file
56
package.json
Normal file
@@ -0,0 +1,56 @@
|
||||
{
|
||||
"name": "openajpport",
|
||||
"version": "0.0.0",
|
||||
"description": "Description for openAjpPort",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
},
|
||||
"devDependencies": {
|
||||
"grunt": "0.4.5",
|
||||
"grunt-autoprefixer": "2.2.0",
|
||||
"grunt-build-control": "0.3.0",
|
||||
"grunt-wiredep": "2.0.0",
|
||||
"grunt-browser-sync": "2.1.2",
|
||||
"grunt-contrib-copy": "0.8.0",
|
||||
"grunt-contrib-clean": "0.6.0",
|
||||
"grunt-contrib-concat": "0.5.1",
|
||||
"grunt-contrib-cssmin": "0.11.0",
|
||||
"grunt-contrib-htmlmin": "0.4.0",
|
||||
"grunt-contrib-imagemin": "0.9.3",
|
||||
"grunt-contrib-jshint": "0.11.0",
|
||||
"grunt-contrib-uglify": "0.8.0",
|
||||
"grunt-contrib-watch": "0.6.1",
|
||||
"grunt-modernizr": "0.6.0",
|
||||
"grunt-ng-annotate": "0.10.0",
|
||||
"grunt-ng-constant": "1.1.0",
|
||||
"grunt-rev": "0.1.0",
|
||||
"grunt-svgmin": "2.0.1",
|
||||
"grunt-text-replace": "0.4.0",
|
||||
"grunt-usemin": "3.0.0",
|
||||
"grunt-angular-templates":"0.5.7",
|
||||
"load-grunt-tasks": "3.1.0",
|
||||
"grunt-karma": "0.11.0",
|
||||
"time-grunt": "1.1.0",
|
||||
"event-stream": "3.3.1",
|
||||
"jshint-stylish": "1.0.1",
|
||||
"karma-script-launcher": "0.1.0",
|
||||
"karma-chrome-launcher": "0.1.12",
|
||||
"karma-html2js-preprocessor": "0.1.0",
|
||||
"karma-jasmine": "0.3.5",
|
||||
"karma-requirejs": "0.2.2",
|
||||
"karma-phantomjs-launcher": "0.2.0",
|
||||
"phantomjs": "1.9.17",
|
||||
"karma": "0.12.35",
|
||||
"generator-jhipster": "2.19.0",
|
||||
"lodash": "3.3.1",
|
||||
"xml2js": "0.4.5",
|
||||
"yo": ">=1.3.0",
|
||||
"requirejs": "2.1",
|
||||
"jasmine-core": "2.1.0",
|
||||
"zeparser": "0.0.7",
|
||||
"wiredep": "2.2.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.12.0"
|
||||
}
|
||||
}
|
||||
694
pom.xml
Normal file
694
pom.xml
Normal file
@@ -0,0 +1,694 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<version>1.2.5.RELEASE</version>
|
||||
<relativePath />
|
||||
</parent>
|
||||
|
||||
<groupId>mark.quinn</groupId>
|
||||
<artifactId>openajpport</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<packaging>war</packaging>
|
||||
|
||||
<name>openAjpPort</name>
|
||||
|
||||
<prerequisites>
|
||||
<maven>3.0.0</maven>
|
||||
</prerequisites>
|
||||
|
||||
<properties>
|
||||
<HikariCP.version>2.3.7</HikariCP.version>
|
||||
<assertj-core.version>3.1.0</assertj-core.version>
|
||||
<awaitility.version>1.4.0</awaitility.version>
|
||||
<commons-io.version>2.4</commons-io.version>
|
||||
<commons-lang.version>2.6</commons-lang.version>
|
||||
<gatling-maven-plugin.version>2.1.6</gatling-maven-plugin.version>
|
||||
<gatling.version>2.1.6</gatling.version>
|
||||
<hibernate.version>4.3.6.Final</hibernate.version>
|
||||
<java.version>1.8</java.version>
|
||||
<javax.inject.version>1</javax.inject.version>
|
||||
<joda-time-hibernate.version>1.3</joda-time-hibernate.version>
|
||||
<json-path.version>0.9.1</json-path.version>
|
||||
<liquibase-hibernate4.version>3.5</liquibase-hibernate4.version>
|
||||
<liquibase-slf4j.version>1.2.1</liquibase-slf4j.version>
|
||||
<liquibase.version>3.3.2</liquibase.version>
|
||||
<mapstruct.version>1.0.0.CR1</mapstruct.version>
|
||||
<maven-enforcer-plugin.version>1.3.1</maven-enforcer-plugin.version>
|
||||
<maven-sortpom-plugin.version>2.3.0</maven-sortpom-plugin.version>
|
||||
<maven.build.timestamp.format>yyyyMMddHHmmss</maven.build.timestamp.format>
|
||||
<maven.compiler.source>${java.version}</maven.compiler.source>
|
||||
<maven.compiler.target>${java.version}</maven.compiler.target>
|
||||
<metrics-spark-reporter.version>1.2</metrics-spark-reporter.version>
|
||||
<metrics-spring.version>3.0.4</metrics-spring.version>
|
||||
<run.addResources>false</run.addResources>
|
||||
<sonar-maven-plugin.version>2.3</sonar-maven-plugin.version>
|
||||
<spring-security.version>4.0.1.RELEASE</spring-security.version>
|
||||
<springfox.version>2.0.3</springfox.version>
|
||||
<usertype-core.version>4.0.0.GA</usertype-core.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.datatype</groupId>
|
||||
<artifactId>jackson-datatype-hibernate4</artifactId>
|
||||
<version>${jackson.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.datatype</groupId>
|
||||
<artifactId>jackson-datatype-hppc</artifactId>
|
||||
<version>${jackson.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.datatype</groupId>
|
||||
<artifactId>jackson-datatype-joda</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.datatype</groupId>
|
||||
<artifactId>jackson-datatype-json-org</artifactId>
|
||||
<version>${jackson.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.h2database</groupId>
|
||||
<artifactId>h2</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.jayway.awaitility</groupId>
|
||||
<artifactId>awaitility</artifactId>
|
||||
<version>${awaitility.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.jayway.jsonpath</groupId>
|
||||
<artifactId>json-path</artifactId>
|
||||
<version>${json-path.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.mattbertolini</groupId>
|
||||
<artifactId>liquibase-slf4j</artifactId>
|
||||
<version>${liquibase-slf4j.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.ryantenney.metrics</groupId>
|
||||
<artifactId>metrics-spring</artifactId>
|
||||
<version>${metrics-spring.version}</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<artifactId>metrics-annotation</artifactId>
|
||||
<groupId>com.codahale.metrics</groupId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<artifactId>metrics-core</artifactId>
|
||||
<groupId>com.codahale.metrics</groupId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<artifactId>metrics-healthchecks</artifactId>
|
||||
<groupId>com.codahale.metrics</groupId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.zaxxer</groupId>
|
||||
<artifactId>HikariCP</artifactId>
|
||||
<version>${HikariCP.version}</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<artifactId>tools</artifactId>
|
||||
<groupId>com.sun</groupId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<!-- The HikariCP Java Agent is disabled by default, as it is experimental
|
||||
<dependency>
|
||||
<groupId>com.zaxxer</groupId>
|
||||
<artifactId>HikariCP-agent</artifactId>
|
||||
<version>${HikariCP.version}</version>
|
||||
</dependency>
|
||||
-->
|
||||
<dependency>
|
||||
<groupId>commons-io</groupId>
|
||||
<artifactId>commons-io</artifactId>
|
||||
<version>${commons-io.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-lang</groupId>
|
||||
<artifactId>commons-lang</artifactId>
|
||||
<version>${commons-lang.version}</version>
|
||||
</dependency>
|
||||
<!-- reporting -->
|
||||
<dependency>
|
||||
<groupId>fr.ippon.spark.metrics</groupId>
|
||||
<artifactId>metrics-spark-reporter</artifactId>
|
||||
<version>${metrics-spark-reporter.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.dropwizard.metrics</groupId>
|
||||
<artifactId>metrics-annotation</artifactId>
|
||||
<version>${dropwizard-metrics.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.dropwizard.metrics</groupId>
|
||||
<artifactId>metrics-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.dropwizard.metrics</groupId>
|
||||
<artifactId>metrics-graphite</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.dropwizard.metrics</groupId>
|
||||
<artifactId>metrics-healthchecks</artifactId>
|
||||
<version>${dropwizard-metrics.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.dropwizard.metrics</groupId>
|
||||
<artifactId>metrics-json</artifactId>
|
||||
<version>${dropwizard-metrics.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.dropwizard.metrics</groupId>
|
||||
<artifactId>metrics-jvm</artifactId>
|
||||
<version>${dropwizard-metrics.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.dropwizard.metrics</groupId>
|
||||
<artifactId>metrics-servlet</artifactId>
|
||||
<version>${dropwizard-metrics.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.dropwizard.metrics</groupId>
|
||||
<artifactId>metrics-servlets</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<artifactId>metrics-healthchecks</artifactId>
|
||||
<groupId>io.dropwizard.metrics</groupId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.gatling.highcharts</groupId>
|
||||
<artifactId>gatling-charts-highcharts</artifactId>
|
||||
<version>${gatling.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.springfox</groupId>
|
||||
<artifactId>springfox-swagger-ui</artifactId>
|
||||
<version>${springfox.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.springfox</groupId>
|
||||
<artifactId>springfox-swagger2</artifactId>
|
||||
<version>${springfox.version}</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<artifactId>mapstruct</artifactId>
|
||||
<groupId>org.mapstruct</groupId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.inject</groupId>
|
||||
<artifactId>javax.inject</artifactId>
|
||||
<version>${javax.inject.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>joda-time</groupId>
|
||||
<artifactId>joda-time</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>joda-time</groupId>
|
||||
<artifactId>joda-time-hibernate</artifactId>
|
||||
<version>${joda-time-hibernate.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>mysql</groupId>
|
||||
<artifactId>mysql-connector-java</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.assertj</groupId>
|
||||
<artifactId>assertj-core</artifactId>
|
||||
<version>${assertj-core.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.hibernate</groupId>
|
||||
<artifactId>hibernate-envers</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.hibernate</groupId>
|
||||
<artifactId>hibernate-validator</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jadira.usertype</groupId>
|
||||
<artifactId>usertype.core</artifactId>
|
||||
<version>${usertype-core.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.liquibase</groupId>
|
||||
<artifactId>liquibase-core</artifactId>
|
||||
<version>${liquibase.version}</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<artifactId>jetty-servlet</artifactId>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mapstruct</groupId>
|
||||
<artifactId>mapstruct-jdk8</artifactId>
|
||||
<version>${mapstruct.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-context-support</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-actuator</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-autoconfigure</artifactId>
|
||||
<version>${spring-boot.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-loader-tools</artifactId>
|
||||
<version>${spring-boot.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-aop</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-jpa</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-logging</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-mail</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-security</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-thymeleaf</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<artifactId>spring-boot-starter-tomcat</artifactId>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<!-- Spring Cloud -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-cloudfoundry-connector</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-localconfig-connector</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-spring-service-connector</artifactId>
|
||||
</dependency>
|
||||
<!-- security -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-data</artifactId>
|
||||
<version>${spring-security.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<resources>
|
||||
<resource>
|
||||
<filtering>true</filtering>
|
||||
<directory>src/main/resources</directory>
|
||||
<includes>
|
||||
<include>**/*.xml</include>
|
||||
</includes>
|
||||
</resource>
|
||||
<resource>
|
||||
<filtering>false</filtering>
|
||||
<directory>src/main/resources</directory>
|
||||
<excludes>
|
||||
<exclude>**/*.xml</exclude>
|
||||
</excludes>
|
||||
</resource>
|
||||
</resources>
|
||||
<pluginManagement>
|
||||
<plugins>
|
||||
<!--This plugin's configuration is used to store Eclipse m2e settings
|
||||
only. It has no influence on the Maven build itself.-->
|
||||
<plugin>
|
||||
<groupId>org.eclipse.m2e</groupId>
|
||||
<artifactId>lifecycle-mapping</artifactId>
|
||||
<version>1.0.0</version>
|
||||
<configuration>
|
||||
<lifecycleMappingMetadata>
|
||||
<pluginExecutions>
|
||||
<pluginExecution>
|
||||
<pluginExecutionFilter>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-dependency-plugin</artifactId>
|
||||
<versionRange>[1.0,)</versionRange>
|
||||
<goals>
|
||||
<goal>copy</goal>
|
||||
</goals>
|
||||
</pluginExecutionFilter>
|
||||
<action>
|
||||
<execute />
|
||||
</action>
|
||||
</pluginExecution>
|
||||
<pluginExecution>
|
||||
<pluginExecutionFilter>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-enforcer-plugin</artifactId>
|
||||
<versionRange>[1.0,)</versionRange>
|
||||
<goals>
|
||||
<goal>enforce</goal>
|
||||
</goals>
|
||||
</pluginExecutionFilter>
|
||||
<action>
|
||||
<execute />
|
||||
</action>
|
||||
</pluginExecution>
|
||||
<pluginExecution>
|
||||
<pluginExecutionFilter>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>exec-maven-plugin</artifactId>
|
||||
<versionRange>[1.2.1,)</versionRange>
|
||||
<goals>
|
||||
<goal>java</goal>
|
||||
<goal>exec</goal>
|
||||
</goals>
|
||||
</pluginExecutionFilter>
|
||||
<action>
|
||||
<ignore />
|
||||
</action>
|
||||
</pluginExecution>
|
||||
</pluginExecutions>
|
||||
</lifecycleMappingMetadata>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</pluginManagement>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>com.google.code.sortpom</groupId>
|
||||
<artifactId>maven-sortpom-plugin</artifactId>
|
||||
<version>${maven-sortpom-plugin.version}</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>verify</phase>
|
||||
<goals>
|
||||
<goal>sort</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<sortProperties>true</sortProperties>
|
||||
<nrOfIndentSpace>4</nrOfIndentSpace>
|
||||
<sortDependencies>groupId,artifactId</sortDependencies>
|
||||
<sortPlugins>groupId,artifactId</sortPlugins>
|
||||
<keepBlankLines>true</keepBlankLines>
|
||||
<expandEmptyElements>false</expandEmptyElements>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>io.gatling</groupId>
|
||||
<artifactId>gatling-maven-plugin</artifactId>
|
||||
<version>${gatling-maven-plugin.version}</version>
|
||||
<configuration>
|
||||
<configFolder>src/test/gatling/conf</configFolder>
|
||||
<dataFolder>src/test/gatling/data</dataFolder>
|
||||
<resultsFolder>target/gatling/results</resultsFolder>
|
||||
<bodiesFolder>src/test/gatling/bodies</bodiesFolder>
|
||||
<simulationsFolder>src/test/gatling/simulations</simulationsFolder>
|
||||
<!-- This will force Gatling to ask which simulation to run
|
||||
This is useful when you have multiple simulations -->
|
||||
<simulationClass>*</simulationClass>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-eclipse-plugin</artifactId>
|
||||
<configuration>
|
||||
<downloadSources>true</downloadSources>
|
||||
<downloadJavadocs>true</downloadJavadocs>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-enforcer-plugin</artifactId>
|
||||
<version>${maven-enforcer-plugin.version}</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>enforce-versions</id>
|
||||
<goals>
|
||||
<goal>enforce</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<rules>
|
||||
<requireMavenVersion>
|
||||
<message>You are running an older version of Maven. JHipster requires at least Maven 3.0</message>
|
||||
<version>[3.0.0,)</version>
|
||||
</requireMavenVersion>
|
||||
<requireJavaVersion>
|
||||
<message>You are running an older version of Java. JHipster requires at least JDK ${java.version}</message>
|
||||
<version>[${java.version}.0,)</version>
|
||||
</requireJavaVersion>
|
||||
</rules>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<configuration>
|
||||
<argLine>-Xmx256m</argLine>
|
||||
<!-- Force alphabetical order to have a reproducible build -->
|
||||
<runOrder>alphabetical</runOrder>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-war-plugin</artifactId>
|
||||
<configuration>
|
||||
<packagingExcludes>WEB-INF/lib/tomcat-*.jar</packagingExcludes>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.bsc.maven</groupId>
|
||||
<artifactId>maven-processor-plugin</artifactId>
|
||||
<version>2.2.4</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>process</id>
|
||||
<phase>generate-sources</phase>
|
||||
<goals>
|
||||
<goal>process</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.mapstruct</groupId>
|
||||
<artifactId>mapstruct-processor</artifactId>
|
||||
<version>${mapstruct.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<configuration>
|
||||
<defaultOutputDirectory>${project.build.directory}/generated-sources</defaultOutputDirectory>
|
||||
<processors>
|
||||
<processor>org.mapstruct.ap.MappingProcessor</processor>
|
||||
</processors>
|
||||
<options>
|
||||
<mapstruct.suppressGeneratorTimestamp>true</mapstruct.suppressGeneratorTimestamp>
|
||||
<mapstruct.defaultComponentModel>spring</mapstruct.defaultComponentModel>
|
||||
</options>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>sonar-maven-plugin</artifactId>
|
||||
<version>${sonar-maven-plugin.version}</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.liquibase</groupId>
|
||||
<artifactId>liquibase-maven-plugin</artifactId>
|
||||
<version>${liquibase.version}</version>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.javassist</groupId>
|
||||
<artifactId>javassist</artifactId>
|
||||
<version>3.18.2-GA</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.liquibase.ext</groupId>
|
||||
<artifactId>liquibase-hibernate4</artifactId>
|
||||
<version>${liquibase-hibernate4.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-jpa</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<configuration>
|
||||
<changeLogFile>src/main/resources/config/liquibase/master.xml</changeLogFile>
|
||||
<diffChangeLogFile>src/main/resources/config/liquibase/changelog/${maven.build.timestamp}_changelog.xml</diffChangeLogFile>
|
||||
<driver />
|
||||
<url />
|
||||
<defaultSchemaName />
|
||||
<username />
|
||||
<password />
|
||||
<referenceUrl>hibernate:spring:mark.quinn.domain?dialect=</referenceUrl>
|
||||
<verbose>true</verbose>
|
||||
<logging>debug</logging>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<jvmArguments>-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005</jvmArguments>
|
||||
<arguments>
|
||||
<argument>--spring.profiles.active=dev</argument>
|
||||
</arguments>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>dev</id>
|
||||
<activation>
|
||||
<activeByDefault>true</activeByDefault>
|
||||
</activation>
|
||||
<properties>
|
||||
<!-- log configuration -->
|
||||
<logback.loglevel>DEBUG</logback.loglevel>
|
||||
</properties>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-tomcat</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</profile>
|
||||
<profile>
|
||||
<id>fast</id>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<arguments>
|
||||
<argument>--spring.profiles.active=dev,fast</argument>
|
||||
</arguments>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
<properties>
|
||||
<!-- log configuration -->
|
||||
<logback.loglevel>DEBUG</logback.loglevel>
|
||||
</properties>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-undertow</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</profile>
|
||||
<profile>
|
||||
<id>prod</id>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>com.github.trecloux</groupId>
|
||||
<artifactId>yeoman-maven-plugin</artifactId>
|
||||
<version>0.4</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>run-frontend-build</id>
|
||||
<phase>generate-resources</phase>
|
||||
<goals>
|
||||
<goal>build</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<buildTool>grunt</buildTool>
|
||||
<buildArgs>build --force --no-color</buildArgs>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<yeomanProjectDirectory>${project.basedir}</yeomanProjectDirectory>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-clean-plugin</artifactId>
|
||||
<version>2.5</version>
|
||||
<configuration>
|
||||
<filesets>
|
||||
<fileset>
|
||||
<directory>src/main/webapp/dist</directory>
|
||||
</fileset>
|
||||
<fileset>
|
||||
<directory>.tmp</directory>
|
||||
</fileset>
|
||||
<fileset>
|
||||
<directory>node_modules</directory>
|
||||
</fileset>
|
||||
</filesets>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<arguments>
|
||||
<argument>--spring.profiles.active=prod</argument>
|
||||
</arguments>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
<properties>
|
||||
<!-- log configuration -->
|
||||
<logback.loglevel>INFO</logback.loglevel>
|
||||
</properties>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-tomcat</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</profile>
|
||||
</profiles>
|
||||
</project>
|
||||
143
src/main/java/mark/quinn/Application.java
Normal file
143
src/main/java/mark/quinn/Application.java
Normal file
@@ -0,0 +1,143 @@
|
||||
package mark.quinn;
|
||||
|
||||
import org.apache.catalina.connector.Connector;
|
||||
import mark.quinn.config.Constants;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.actuate.autoconfigure.MetricFilterAutoConfiguration;
|
||||
import org.springframework.boot.actuate.autoconfigure.MetricRepositoryAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.context.embedded.EmbeddedServletContainerFactory;
|
||||
import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.core.env.SimpleCommandLinePropertySource;
|
||||
import com.google.common.base.Joiner;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.inject.Inject;
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
|
||||
@ComponentScan
|
||||
@EnableAutoConfiguration(exclude = {MetricFilterAutoConfiguration.class, MetricRepositoryAutoConfiguration.class})
|
||||
public class Application {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(Application.class);
|
||||
|
||||
@Inject
|
||||
private Environment env;
|
||||
|
||||
@Value("${tomcatAjp.protocol}")
|
||||
String ajpProtocol;
|
||||
|
||||
@Value("${tomcatAjp.port}")
|
||||
String ajpPort;
|
||||
|
||||
@Value("${tomcatAjp.enabled}")
|
||||
String ajpEnabled;
|
||||
|
||||
@Value("${tomcatAjp.scheme}")
|
||||
String ajpScheme;
|
||||
|
||||
/**
|
||||
* Initializes openAjpPort.
|
||||
* <p/>
|
||||
* Spring profiles can be configured with a program arguments --spring.profiles.active=your-active-profile
|
||||
* <p/>
|
||||
* <p>
|
||||
* You can find more information on how profiles work with JHipster on <a href="http://jhipster.github.io/profiles.html">http://jhipster.github.io/profiles.html</a>.
|
||||
* </p>
|
||||
*/
|
||||
@PostConstruct
|
||||
public void initApplication() throws IOException {
|
||||
if (env.getActiveProfiles().length == 0) {
|
||||
log.warn("No Spring profile configured, running with default configuration");
|
||||
} else {
|
||||
log.info("Running with Spring profile(s) : {}", Arrays.toString(env.getActiveProfiles()));
|
||||
Collection<String> activeProfiles = Arrays.asList(env.getActiveProfiles());
|
||||
if (activeProfiles.contains("dev") && activeProfiles.contains("prod")) {
|
||||
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("prod") && activeProfiles.contains("fast")) {
|
||||
log.error("You have misconfigured your application! " +
|
||||
"It should not run with both the 'prod' and 'fast' profiles at the same time.");
|
||||
}
|
||||
if (activeProfiles.contains("dev") && activeProfiles.contains("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.
|
||||
*/
|
||||
public static void main(String[] args) throws UnknownHostException {
|
||||
SpringApplication app = new SpringApplication(Application.class);
|
||||
app.setShowBanner(false);
|
||||
SimpleCommandLinePropertySource source = new SimpleCommandLinePropertySource(args);
|
||||
addDefaultProfile(app, source);
|
||||
addLiquibaseScanPackages();
|
||||
Environment env = app.run(args).getEnvironment();
|
||||
log.info("Access URLs:\n----------------------------------------------------------\n\t" +
|
||||
"Local: \t\thttp://127.0.0.1:{}\n\t" +
|
||||
"External: \thttp://{}:{}\n----------------------------------------------------------",
|
||||
env.getProperty("server.port"),
|
||||
InetAddress.getLocalHost().getHostAddress(),
|
||||
env.getProperty("server.port"));
|
||||
|
||||
}
|
||||
|
||||
@Bean
|
||||
public EmbeddedServletContainerFactory servletContainer() {
|
||||
|
||||
Integer ajpPortInt = Integer.parseInt(ajpPort);
|
||||
Boolean ajpEnabledBool = Boolean.valueOf(ajpEnabled);
|
||||
|
||||
TomcatEmbeddedServletContainerFactory tomcat = new TomcatEmbeddedServletContainerFactory();
|
||||
if (ajpEnabledBool)
|
||||
{
|
||||
Connector ajpConnector = new Connector(ajpProtocol);
|
||||
ajpConnector.setProtocol(ajpProtocol);
|
||||
ajpConnector.setPort(ajpPortInt);
|
||||
ajpConnector.setSecure(false);
|
||||
ajpConnector.setAllowTrace(false);
|
||||
ajpConnector.setScheme(ajpScheme);
|
||||
tomcat.addAdditionalTomcatConnectors(ajpConnector);
|
||||
}
|
||||
|
||||
return tomcat;
|
||||
}
|
||||
|
||||
/**
|
||||
* If no profile has been configured, set by default the "dev" profile.
|
||||
*/
|
||||
private static void addDefaultProfile(SpringApplication app, SimpleCommandLinePropertySource source) {
|
||||
if (!source.containsProperty("spring.profiles.active") &&
|
||||
!System.getenv().containsKey("SPRING_PROFILES_ACTIVE")) {
|
||||
|
||||
app.setAdditionalProfiles(Constants.SPRING_PROFILE_DEVELOPMENT);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the liquibases.scan.packages to avoid an exception from ServiceLocator.
|
||||
*/
|
||||
private static void addLiquibaseScanPackages() {
|
||||
System.setProperty("liquibase.scan.packages", Joiner.on(",").join(
|
||||
"liquibase.change", "liquibase.database", "liquibase.parser",
|
||||
"liquibase.precondition", "liquibase.datatype",
|
||||
"liquibase.serializer", "liquibase.sqlgenerator", "liquibase.executor",
|
||||
"liquibase.snapshot", "liquibase.logging", "liquibase.diff",
|
||||
"liquibase.structure", "liquibase.structurecompare", "liquibase.lockservice",
|
||||
"liquibase.ext", "liquibase.changelog"));
|
||||
}
|
||||
}
|
||||
40
src/main/java/mark/quinn/ApplicationWebXml.java
Normal file
40
src/main/java/mark/quinn/ApplicationWebXml.java
Normal file
@@ -0,0 +1,40 @@
|
||||
package mark.quinn;
|
||||
|
||||
import mark.quinn.config.Constants;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.boot.builder.SpringApplicationBuilder;
|
||||
import org.springframework.boot.context.web.SpringBootServletInitializer;
|
||||
|
||||
/**
|
||||
* This is a helper Java class that provides an alternative to creating a web.xml.
|
||||
*/
|
||||
public class ApplicationWebXml extends SpringBootServletInitializer {
|
||||
|
||||
private final Logger log = LoggerFactory.getLogger(ApplicationWebXml.class);
|
||||
|
||||
@Override
|
||||
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
|
||||
return application.profiles(addDefaultProfile())
|
||||
.showBanner(false)
|
||||
.sources(Application.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a default profile if it has not been set.
|
||||
* <p/>
|
||||
* <p>
|
||||
* Please use -Dspring.profiles.active=dev
|
||||
* </p>
|
||||
*/
|
||||
private String addDefaultProfile() {
|
||||
String profile = System.getProperty("spring.profiles.active");
|
||||
if (profile != null) {
|
||||
log.info("Running with Spring profile(s) : {}", profile);
|
||||
return profile;
|
||||
}
|
||||
|
||||
log.warn("No Spring profile configured, running with default configuration");
|
||||
return Constants.SPRING_PROFILE_DEVELOPMENT;
|
||||
}
|
||||
}
|
||||
62
src/main/java/mark/quinn/aop/logging/LoggingAspect.java
Normal file
62
src/main/java/mark/quinn/aop/logging/LoggingAspect.java
Normal file
@@ -0,0 +1,62 @@
|
||||
package mark.quinn.aop.logging;
|
||||
|
||||
import mark.quinn.config.Constants;
|
||||
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 javax.inject.Inject;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Aspect for logging execution of service and repository Spring components.
|
||||
*/
|
||||
@Aspect
|
||||
public class LoggingAspect {
|
||||
|
||||
private final Logger log = LoggerFactory.getLogger(this.getClass());
|
||||
|
||||
@Inject
|
||||
private Environment env;
|
||||
|
||||
@Pointcut("within(mark.quinn.repository..*) || within(mark.quinn.service..*) || within(mark.quinn.web.rest..*)")
|
||||
public void loggingPointcut() {}
|
||||
|
||||
@AfterThrowing(pointcut = "loggingPointcut()", throwing = "e")
|
||||
public void logAfterThrowing(JoinPoint joinPoint, Throwable e) {
|
||||
if (env.acceptsProfiles(Constants.SPRING_PROFILE_DEVELOPMENT)) {
|
||||
log.error("Exception in {}.{}() with cause = {}", joinPoint.getSignature().getDeclaringTypeName(),
|
||||
joinPoint.getSignature().getName(), e.getCause(), e);
|
||||
} else {
|
||||
log.error("Exception in {}.{}() with cause = {}", joinPoint.getSignature().getDeclaringTypeName(),
|
||||
joinPoint.getSignature().getName(), e.getCause());
|
||||
}
|
||||
}
|
||||
|
||||
@Around("loggingPointcut()")
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package mark.quinn.async;
|
||||
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.DisposableBean;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.core.task.AsyncTaskExecutor;
|
||||
|
||||
public class ExceptionHandlingAsyncTaskExecutor implements AsyncTaskExecutor,
|
||||
InitializingBean, DisposableBean {
|
||||
|
||||
private final Logger log = LoggerFactory.getLogger(ExceptionHandlingAsyncTaskExecutor.class);
|
||||
|
||||
private final AsyncTaskExecutor executor;
|
||||
|
||||
public ExceptionHandlingAsyncTaskExecutor(AsyncTaskExecutor executor) {
|
||||
this.executor = executor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(Runnable task) {
|
||||
executor.execute(task);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(Runnable task, long startTimeout) {
|
||||
executor.execute(createWrappedRunnable(task), startTimeout);
|
||||
}
|
||||
|
||||
private <T> Callable<T> createCallable(final Callable<T> task) {
|
||||
return () -> {
|
||||
try {
|
||||
return task.call();
|
||||
} catch (Exception e) {
|
||||
handle(e);
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private Runnable createWrappedRunnable(final Runnable task) {
|
||||
return () -> {
|
||||
try {
|
||||
task.run();
|
||||
} catch (Exception e) {
|
||||
handle(e);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
protected void handle(Exception e) {
|
||||
log.error("Caught async exception", e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Future<?> submit(Runnable task) {
|
||||
return executor.submit(createWrappedRunnable(task));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Future<T> submit(Callable<T> task) {
|
||||
return executor.submit(createCallable(task));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() throws Exception {
|
||||
if (executor instanceof DisposableBean) {
|
||||
DisposableBean bean = (DisposableBean) executor;
|
||||
bean.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
if (executor instanceof InitializingBean) {
|
||||
InitializingBean bean = (InitializingBean) executor;
|
||||
bean.afterPropertiesSet();
|
||||
}
|
||||
}
|
||||
}
|
||||
4
src/main/java/mark/quinn/async/package-info.java
Normal file
4
src/main/java/mark/quinn/async/package-info.java
Normal file
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* Async helpers.
|
||||
*/
|
||||
package mark.quinn.async;
|
||||
53
src/main/java/mark/quinn/config/AsyncConfiguration.java
Normal file
53
src/main/java/mark/quinn/config/AsyncConfiguration.java
Normal file
@@ -0,0 +1,53 @@
|
||||
package mark.quinn.config;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
|
||||
import org.springframework.aop.interceptor.SimpleAsyncUncaughtExceptionHandler;
|
||||
import org.springframework.boot.bind.RelaxedPropertyResolver;
|
||||
import org.springframework.context.EnvironmentAware;
|
||||
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.scheduling.annotation.AsyncConfigurer;
|
||||
import org.springframework.scheduling.annotation.EnableAsync;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import mark.quinn.async.ExceptionHandlingAsyncTaskExecutor;
|
||||
|
||||
@Configuration
|
||||
@EnableAsync
|
||||
@EnableScheduling
|
||||
@Profile("!" + Constants.SPRING_PROFILE_FAST)
|
||||
public class AsyncConfiguration implements AsyncConfigurer, EnvironmentAware {
|
||||
|
||||
private final Logger log = LoggerFactory.getLogger(AsyncConfiguration.class);
|
||||
|
||||
private RelaxedPropertyResolver propertyResolver;
|
||||
|
||||
@Override
|
||||
public void setEnvironment(Environment environment) {
|
||||
this.propertyResolver = new RelaxedPropertyResolver(environment, "async.");
|
||||
}
|
||||
|
||||
@Override
|
||||
@Bean
|
||||
public Executor getAsyncExecutor() {
|
||||
log.debug("Creating Async Task Executor");
|
||||
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
|
||||
executor.setCorePoolSize(propertyResolver.getProperty("corePoolSize", Integer.class, 2));
|
||||
executor.setMaxPoolSize(propertyResolver.getProperty("maxPoolSize", Integer.class, 50));
|
||||
executor.setQueueCapacity(propertyResolver.getProperty("queueCapacity", Integer.class, 10000));
|
||||
executor.setThreadNamePrefix("openajpport-Executor-");
|
||||
return new ExceptionHandlingAsyncTaskExecutor(executor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
|
||||
return new SimpleAsyncUncaughtExceptionHandler();
|
||||
}
|
||||
}
|
||||
37
src/main/java/mark/quinn/config/CacheConfiguration.java
Normal file
37
src/main/java/mark/quinn/config/CacheConfiguration.java
Normal file
@@ -0,0 +1,37 @@
|
||||
package mark.quinn.config;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
|
||||
import org.springframework.cache.CacheManager;
|
||||
import org.springframework.cache.annotation.EnableCaching;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
import org.springframework.cache.support.NoOpCacheManager;
|
||||
|
||||
import javax.annotation.PreDestroy;
|
||||
import javax.inject.Inject;
|
||||
|
||||
@Configuration
|
||||
@EnableCaching
|
||||
@AutoConfigureAfter(value = {MetricsConfiguration.class, DatabaseConfiguration.class})
|
||||
@Profile("!" + Constants.SPRING_PROFILE_FAST)
|
||||
public class CacheConfiguration {
|
||||
|
||||
private final Logger log = LoggerFactory.getLogger(CacheConfiguration.class);
|
||||
|
||||
private CacheManager cacheManager;
|
||||
|
||||
@PreDestroy
|
||||
public void destroy() {
|
||||
log.info("Closing Cache Manager");
|
||||
}
|
||||
|
||||
@Bean
|
||||
public CacheManager cacheManager() {
|
||||
log.debug("No cache");
|
||||
cacheManager = new NoOpCacheManager();
|
||||
return cacheManager;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package mark.quinn.config;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.cloud.config.java.AbstractCloudConfig;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
@Configuration
|
||||
@Profile(Constants.SPRING_PROFILE_CLOUD)
|
||||
public class CloudDatabaseConfiguration extends AbstractCloudConfig {
|
||||
|
||||
private final Logger log = LoggerFactory.getLogger(CloudDatabaseConfiguration.class);
|
||||
|
||||
@Bean
|
||||
public DataSource dataSource() {
|
||||
log.info("Configuring JDBC datasource from a cloud provider");
|
||||
return connectionFactory().dataSource();
|
||||
}
|
||||
}
|
||||
21
src/main/java/mark/quinn/config/Constants.java
Normal file
21
src/main/java/mark/quinn/config/Constants.java
Normal file
@@ -0,0 +1,21 @@
|
||||
package mark.quinn.config;
|
||||
|
||||
/**
|
||||
* Application constants.
|
||||
*/
|
||||
public final class Constants {
|
||||
|
||||
// Spring profile for development, production and "fast", see http://jhipster.github.io/profiles.html
|
||||
public static final String SPRING_PROFILE_DEVELOPMENT = "dev";
|
||||
public static final String SPRING_PROFILE_PRODUCTION = "prod";
|
||||
public static final String SPRING_PROFILE_FAST = "fast";
|
||||
// Spring profile used when deploying with Spring Cloud (used when deploying to CloudFoundry)
|
||||
public static final String SPRING_PROFILE_CLOUD = "cloud";
|
||||
// Spring profile used when deploying to Heroku
|
||||
public static final String SPRING_PROFILE_HEROKU = "heroku";
|
||||
|
||||
public static final String SYSTEM_ACCOUNT = "system";
|
||||
|
||||
private Constants() {
|
||||
}
|
||||
}
|
||||
109
src/main/java/mark/quinn/config/DatabaseConfiguration.java
Normal file
109
src/main/java/mark/quinn/config/DatabaseConfiguration.java
Normal file
@@ -0,0 +1,109 @@
|
||||
package mark.quinn.config;
|
||||
|
||||
import com.codahale.metrics.MetricRegistry;
|
||||
import com.fasterxml.jackson.datatype.hibernate4.Hibernate4Module;
|
||||
import com.zaxxer.hikari.HikariConfig;
|
||||
import com.zaxxer.hikari.HikariDataSource;
|
||||
import liquibase.integration.spring.SpringLiquibase;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
|
||||
import org.springframework.boot.bind.RelaxedPropertyResolver;
|
||||
import org.springframework.context.ApplicationContextException;
|
||||
import org.springframework.context.EnvironmentAware;
|
||||
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 org.springframework.util.StringUtils;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.util.Arrays;
|
||||
|
||||
@Configuration
|
||||
@EnableJpaRepositories("mark.quinn.repository")
|
||||
@EnableJpaAuditing(auditorAwareRef = "springSecurityAuditorAware")
|
||||
@EnableTransactionManagement
|
||||
public class DatabaseConfiguration implements EnvironmentAware {
|
||||
|
||||
private final Logger log = LoggerFactory.getLogger(DatabaseConfiguration.class);
|
||||
|
||||
private RelaxedPropertyResolver dataSourcePropertyResolver;
|
||||
|
||||
private RelaxedPropertyResolver liquiBasePropertyResolver;
|
||||
|
||||
private Environment env;
|
||||
|
||||
@Autowired(required = false)
|
||||
private MetricRegistry metricRegistry;
|
||||
|
||||
@Override
|
||||
public void setEnvironment(Environment env) {
|
||||
this.env = env;
|
||||
this.dataSourcePropertyResolver = new RelaxedPropertyResolver(env, "spring.datasource.");
|
||||
this.liquiBasePropertyResolver = new RelaxedPropertyResolver(env, "liquiBase.");
|
||||
}
|
||||
|
||||
@Bean(destroyMethod = "shutdown")
|
||||
@ConditionalOnExpression("#{!environment.acceptsProfiles('cloud') && !environment.acceptsProfiles('heroku')}")
|
||||
public DataSource dataSource() {
|
||||
log.debug("Configuring Datasource");
|
||||
if (dataSourcePropertyResolver.getProperty("url") == null && dataSourcePropertyResolver.getProperty("databaseName") == null) {
|
||||
log.error("Your database connection pool configuration is incorrect! The application" +
|
||||
" cannot start. Please check your Spring profile, current profiles are: {}",
|
||||
Arrays.toString(env.getActiveProfiles()));
|
||||
|
||||
throw new ApplicationContextException("Database connection pool is not configured correctly");
|
||||
}
|
||||
HikariConfig config = new HikariConfig();
|
||||
config.setDataSourceClassName(dataSourcePropertyResolver.getProperty("dataSourceClassName"));
|
||||
if(StringUtils.isEmpty(dataSourcePropertyResolver.getProperty("url"))) {
|
||||
config.addDataSourceProperty("databaseName", dataSourcePropertyResolver.getProperty("databaseName"));
|
||||
config.addDataSourceProperty("serverName", dataSourcePropertyResolver.getProperty("serverName"));
|
||||
} else {
|
||||
config.addDataSourceProperty("url", dataSourcePropertyResolver.getProperty("url"));
|
||||
}
|
||||
config.addDataSourceProperty("user", dataSourcePropertyResolver.getProperty("username"));
|
||||
config.addDataSourceProperty("password", dataSourcePropertyResolver.getProperty("password"));
|
||||
|
||||
//MySQL optimizations, see https://github.com/brettwooldridge/HikariCP/wiki/MySQL-Configuration
|
||||
if ("com.mysql.jdbc.jdbc2.optional.MysqlDataSource".equals(dataSourcePropertyResolver.getProperty("dataSourceClassName"))) {
|
||||
config.addDataSourceProperty("cachePrepStmts", dataSourcePropertyResolver.getProperty("cachePrepStmts", "true"));
|
||||
config.addDataSourceProperty("prepStmtCacheSize", dataSourcePropertyResolver.getProperty("prepStmtCacheSize", "250"));
|
||||
config.addDataSourceProperty("prepStmtCacheSqlLimit", dataSourcePropertyResolver.getProperty("prepStmtCacheSqlLimit", "2048"));
|
||||
}
|
||||
if (metricRegistry != null) {
|
||||
config.setMetricRegistry(metricRegistry);
|
||||
}
|
||||
return new HikariDataSource(config);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public SpringLiquibase liquibase(DataSource dataSource) {
|
||||
SpringLiquibase liquibase = new SpringLiquibase();
|
||||
liquibase.setDataSource(dataSource);
|
||||
liquibase.setChangeLog("classpath:config/liquibase/master.xml");
|
||||
liquibase.setContexts(liquiBasePropertyResolver.getProperty("context"));
|
||||
if (env.acceptsProfiles(Constants.SPRING_PROFILE_FAST)) {
|
||||
if ("org.h2.jdbcx.JdbcDataSource".equals(dataSourcePropertyResolver.getProperty("dataSourceClassName"))) {
|
||||
liquibase.setShouldRun(true);
|
||||
log.warn("Using '{}' profile with H2 database in memory is not optimal, you should consider switching to" +
|
||||
" MySQL or Postgresql to avoid rebuilding your database upon each start.", Constants.SPRING_PROFILE_FAST);
|
||||
} else {
|
||||
liquibase.setShouldRun(false);
|
||||
}
|
||||
} else {
|
||||
log.debug("Configuring Liquibase");
|
||||
}
|
||||
return liquibase;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Hibernate4Module hibernate4Module() {
|
||||
return new Hibernate4Module();
|
||||
}
|
||||
}
|
||||
26
src/main/java/mark/quinn/config/JacksonConfiguration.java
Normal file
26
src/main/java/mark/quinn/config/JacksonConfiguration.java
Normal file
@@ -0,0 +1,26 @@
|
||||
package mark.quinn.config;
|
||||
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.LocalDate;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import mark.quinn.domain.util.CustomDateTimeDeserializer;
|
||||
import mark.quinn.domain.util.CustomDateTimeSerializer;
|
||||
import mark.quinn.domain.util.CustomLocalDateSerializer;
|
||||
import mark.quinn.domain.util.ISO8601LocalDateDeserializer;
|
||||
import com.fasterxml.jackson.datatype.joda.JodaModule;
|
||||
|
||||
@Configuration
|
||||
public class JacksonConfiguration {
|
||||
|
||||
@Bean
|
||||
public JodaModule jacksonJodaModule() {
|
||||
JodaModule module = new JodaModule();
|
||||
module.addSerializer(DateTime.class, new CustomDateTimeSerializer());
|
||||
module.addDeserializer(DateTime.class, new CustomDateTimeDeserializer());
|
||||
module.addSerializer(LocalDate.class, new CustomLocalDateSerializer());
|
||||
module.addDeserializer(LocalDate.class, new ISO8601LocalDateDeserializer());
|
||||
return module;
|
||||
}
|
||||
}
|
||||
48
src/main/java/mark/quinn/config/LocaleConfiguration.java
Normal file
48
src/main/java/mark/quinn/config/LocaleConfiguration.java
Normal file
@@ -0,0 +1,48 @@
|
||||
package mark.quinn.config;
|
||||
|
||||
import mark.quinn.config.locale.AngularCookieLocaleResolver;
|
||||
import org.springframework.boot.bind.RelaxedPropertyResolver;
|
||||
import org.springframework.context.EnvironmentAware;
|
||||
import org.springframework.context.MessageSource;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.support.ReloadableResourceBundleMessageSource;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.web.servlet.LocaleResolver;
|
||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
|
||||
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
|
||||
|
||||
@Configuration
|
||||
public class LocaleConfiguration extends WebMvcConfigurerAdapter implements EnvironmentAware {
|
||||
|
||||
private RelaxedPropertyResolver propertyResolver;
|
||||
|
||||
@Override
|
||||
public void setEnvironment(Environment environment) {
|
||||
this.propertyResolver = new RelaxedPropertyResolver(environment, "spring.messageSource.");
|
||||
}
|
||||
|
||||
@Bean(name = "localeResolver")
|
||||
public LocaleResolver localeResolver() {
|
||||
AngularCookieLocaleResolver cookieLocaleResolver = new AngularCookieLocaleResolver();
|
||||
cookieLocaleResolver.setCookieName("NG_TRANSLATE_LANG_KEY");
|
||||
return cookieLocaleResolver;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public MessageSource messageSource() {
|
||||
ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
|
||||
messageSource.setBasename("classpath:/i18n/messages");
|
||||
messageSource.setDefaultEncoding("UTF-8");
|
||||
messageSource.setCacheSeconds(propertyResolver.getProperty("cacheSeconds", Integer.class, 1));
|
||||
return messageSource;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addInterceptors(InterceptorRegistry registry) {
|
||||
LocaleChangeInterceptor localeChangeInterceptor = new LocaleChangeInterceptor();
|
||||
localeChangeInterceptor.setParamName("language");
|
||||
registry.addInterceptor(localeChangeInterceptor);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package mark.quinn.config;
|
||||
|
||||
import mark.quinn.aop.logging.LoggingAspect;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.EnableAspectJAutoProxy;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
|
||||
@Configuration
|
||||
@EnableAspectJAutoProxy
|
||||
public class LoggingAspectConfiguration {
|
||||
|
||||
@Bean
|
||||
@Profile(Constants.SPRING_PROFILE_DEVELOPMENT)
|
||||
public LoggingAspect loggingAspect() {
|
||||
return new LoggingAspect();
|
||||
}
|
||||
}
|
||||
70
src/main/java/mark/quinn/config/MailConfiguration.java
Normal file
70
src/main/java/mark/quinn/config/MailConfiguration.java
Normal file
@@ -0,0 +1,70 @@
|
||||
package mark.quinn.config;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.boot.bind.RelaxedPropertyResolver;
|
||||
import org.springframework.context.EnvironmentAware;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.mail.javamail.JavaMailSenderImpl;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
@Configuration
|
||||
public class MailConfiguration implements EnvironmentAware {
|
||||
|
||||
private static final String ENV_SPRING_MAIL = "mail.";
|
||||
private static final String DEFAULT_HOST = "127.0.0.1";
|
||||
private static final String PROP_HOST = "host";
|
||||
private static final String DEFAULT_PROP_HOST = "localhost";
|
||||
private static final String PROP_PORT = "port";
|
||||
private static final String PROP_USER = "username";
|
||||
private static final String PROP_PASSWORD = "password";
|
||||
private static final String PROP_PROTO = "protocol";
|
||||
private static final String PROP_TLS = "tls";
|
||||
private static final String PROP_AUTH = "auth";
|
||||
private static final String PROP_SMTP_AUTH = "mail.smtp.auth";
|
||||
private static final String PROP_STARTTLS = "mail.smtp.starttls.enable";
|
||||
private static final String PROP_TRANSPORT_PROTO = "mail.transport.protocol";
|
||||
|
||||
private final Logger log = LoggerFactory.getLogger(MailConfiguration.class);
|
||||
|
||||
private RelaxedPropertyResolver propertyResolver;
|
||||
|
||||
@Override
|
||||
public void setEnvironment(Environment environment) {
|
||||
this.propertyResolver = new RelaxedPropertyResolver(environment, ENV_SPRING_MAIL);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public JavaMailSenderImpl javaMailSender() {
|
||||
log.debug("Configuring mail server");
|
||||
String host = propertyResolver.getProperty(PROP_HOST, DEFAULT_PROP_HOST);
|
||||
int port = propertyResolver.getProperty(PROP_PORT, Integer.class, 0);
|
||||
String user = propertyResolver.getProperty(PROP_USER);
|
||||
String password = propertyResolver.getProperty(PROP_PASSWORD);
|
||||
String protocol = propertyResolver.getProperty(PROP_PROTO);
|
||||
Boolean tls = propertyResolver.getProperty(PROP_TLS, Boolean.class, false);
|
||||
Boolean auth = propertyResolver.getProperty(PROP_AUTH, Boolean.class, false);
|
||||
|
||||
JavaMailSenderImpl sender = new JavaMailSenderImpl();
|
||||
if (host != null && !host.isEmpty()) {
|
||||
sender.setHost(host);
|
||||
} else {
|
||||
log.warn("Warning! Your SMTP server is not configured. We will try to use one on localhost.");
|
||||
log.debug("Did you configure your SMTP settings in your application.yml?");
|
||||
sender.setHost(DEFAULT_HOST);
|
||||
}
|
||||
sender.setPort(port);
|
||||
sender.setUsername(user);
|
||||
sender.setPassword(password);
|
||||
|
||||
Properties sendProperties = new Properties();
|
||||
sendProperties.setProperty(PROP_SMTP_AUTH, auth.toString());
|
||||
sendProperties.setProperty(PROP_STARTTLS, tls.toString());
|
||||
sendProperties.setProperty(PROP_TRANSPORT_PROTO, protocol);
|
||||
sender.setJavaMailProperties(sendProperties);
|
||||
return sender;
|
||||
}
|
||||
}
|
||||
159
src/main/java/mark/quinn/config/MetricsConfiguration.java
Normal file
159
src/main/java/mark/quinn/config/MetricsConfiguration.java
Normal file
@@ -0,0 +1,159 @@
|
||||
package mark.quinn.config;
|
||||
|
||||
import com.codahale.metrics.JmxReporter;
|
||||
import com.codahale.metrics.MetricRegistry;
|
||||
import com.codahale.metrics.graphite.Graphite;
|
||||
import com.codahale.metrics.graphite.GraphiteReporter;
|
||||
import com.codahale.metrics.health.HealthCheckRegistry;
|
||||
import com.codahale.metrics.jvm.*;
|
||||
import com.ryantenney.metrics.spring.config.annotation.EnableMetrics;
|
||||
import com.ryantenney.metrics.spring.config.annotation.MetricsConfigurerAdapter;
|
||||
import fr.ippon.spark.metrics.SparkReporter;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.bind.RelaxedPropertyResolver;
|
||||
import org.springframework.context.EnvironmentAware;
|
||||
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 javax.annotation.PostConstruct;
|
||||
import javax.inject.Inject;
|
||||
import java.lang.management.ManagementFactory;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@Configuration
|
||||
@EnableMetrics(proxyTargetClass = true)
|
||||
@Profile("!" + Constants.SPRING_PROFILE_FAST)
|
||||
public class MetricsConfiguration extends MetricsConfigurerAdapter implements EnvironmentAware {
|
||||
|
||||
private static final String ENV_METRICS = "metrics.";
|
||||
private static final String ENV_METRICS_GRAPHITE = "metrics.graphite.";
|
||||
private static final String ENV_METRICS_SPARK = "metrics.spark.";
|
||||
private static final String PROP_JMX_ENABLED = "jmx.enabled";
|
||||
private static final String PROP_GRAPHITE_ENABLED = "enabled";
|
||||
private static final String PROP_SPARK_ENABLED = "enabled";
|
||||
private static final String PROP_GRAPHITE_PREFIX = "";
|
||||
|
||||
private static final String PROP_PORT = "port";
|
||||
private static final String PROP_HOST = "host";
|
||||
private static final String PROP_METRIC_REG_JVM_MEMORY = "jvm.memory";
|
||||
private static final String PROP_METRIC_REG_JVM_GARBAGE = "jvm.garbage";
|
||||
private static final String PROP_METRIC_REG_JVM_THREADS = "jvm.threads";
|
||||
private static final String PROP_METRIC_REG_JVM_FILES = "jvm.files";
|
||||
private static final String PROP_METRIC_REG_JVM_BUFFERS = "jvm.buffers";
|
||||
|
||||
private final Logger log = LoggerFactory.getLogger(MetricsConfiguration.class);
|
||||
|
||||
private MetricRegistry metricRegistry = new MetricRegistry();
|
||||
|
||||
private HealthCheckRegistry healthCheckRegistry = new HealthCheckRegistry();
|
||||
|
||||
private RelaxedPropertyResolver propertyResolver;
|
||||
|
||||
@Override
|
||||
public void setEnvironment(Environment environment) {
|
||||
this.propertyResolver = new RelaxedPropertyResolver(environment, ENV_METRICS);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Bean
|
||||
public MetricRegistry getMetricRegistry() {
|
||||
return metricRegistry;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Bean
|
||||
public HealthCheckRegistry getHealthCheckRegistry() {
|
||||
return healthCheckRegistry;
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
log.debug("Registering JVM gauges");
|
||||
metricRegistry.register(PROP_METRIC_REG_JVM_MEMORY, new MemoryUsageGaugeSet());
|
||||
metricRegistry.register(PROP_METRIC_REG_JVM_GARBAGE, new GarbageCollectorMetricSet());
|
||||
metricRegistry.register(PROP_METRIC_REG_JVM_THREADS, new ThreadStatesGaugeSet());
|
||||
metricRegistry.register(PROP_METRIC_REG_JVM_FILES, new FileDescriptorRatioGauge());
|
||||
metricRegistry.register(PROP_METRIC_REG_JVM_BUFFERS, new BufferPoolMetricSet(ManagementFactory.getPlatformMBeanServer()));
|
||||
if (propertyResolver.getProperty(PROP_JMX_ENABLED, Boolean.class, false)) {
|
||||
log.info("Initializing Metrics JMX reporting");
|
||||
JmxReporter jmxReporter = JmxReporter.forRegistry(metricRegistry).build();
|
||||
jmxReporter.start();
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@ConditionalOnClass(Graphite.class)
|
||||
@Profile("!" + Constants.SPRING_PROFILE_FAST)
|
||||
public static class GraphiteRegistry implements EnvironmentAware {
|
||||
|
||||
private final Logger log = LoggerFactory.getLogger(GraphiteRegistry.class);
|
||||
|
||||
@Inject
|
||||
private MetricRegistry metricRegistry;
|
||||
|
||||
private RelaxedPropertyResolver propertyResolver;
|
||||
|
||||
@Override
|
||||
public void setEnvironment(Environment environment) {
|
||||
this.propertyResolver = new RelaxedPropertyResolver(environment, ENV_METRICS_GRAPHITE);
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
private void init() {
|
||||
Boolean graphiteEnabled = propertyResolver.getProperty(PROP_GRAPHITE_ENABLED, Boolean.class, false);
|
||||
if (graphiteEnabled) {
|
||||
log.info("Initializing Metrics Graphite reporting");
|
||||
String graphiteHost = propertyResolver.getRequiredProperty(PROP_HOST);
|
||||
Integer graphitePort = propertyResolver.getRequiredProperty(PROP_PORT, Integer.class);
|
||||
String graphitePrefix = propertyResolver.getProperty(PROP_GRAPHITE_PREFIX, String.class, "");
|
||||
|
||||
Graphite graphite = new Graphite(new InetSocketAddress(graphiteHost, graphitePort));
|
||||
GraphiteReporter graphiteReporter = GraphiteReporter.forRegistry(metricRegistry)
|
||||
.convertRatesTo(TimeUnit.SECONDS)
|
||||
.convertDurationsTo(TimeUnit.MILLISECONDS)
|
||||
.prefixedWith(graphitePrefix)
|
||||
.build(graphite);
|
||||
graphiteReporter.start(1, TimeUnit.MINUTES);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@ConditionalOnClass(SparkReporter.class)
|
||||
@Profile("!" + Constants.SPRING_PROFILE_FAST)
|
||||
public static class SparkRegistry implements EnvironmentAware {
|
||||
|
||||
private final Logger log = LoggerFactory.getLogger(SparkRegistry.class);
|
||||
|
||||
@Inject
|
||||
private MetricRegistry metricRegistry;
|
||||
|
||||
private RelaxedPropertyResolver propertyResolver;
|
||||
|
||||
@Override
|
||||
public void setEnvironment(Environment environment) {
|
||||
this.propertyResolver = new RelaxedPropertyResolver(environment, ENV_METRICS_SPARK);
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
private void init() {
|
||||
Boolean sparkEnabled = propertyResolver.getProperty(PROP_SPARK_ENABLED, Boolean.class, false);
|
||||
if (sparkEnabled) {
|
||||
log.info("Initializing Metrics Spark reporting");
|
||||
String sparkHost = propertyResolver.getRequiredProperty(PROP_HOST);
|
||||
Integer sparkPort = propertyResolver.getRequiredProperty(PROP_PORT, Integer.class);
|
||||
|
||||
SparkReporter sparkReporter = SparkReporter.forRegistry(metricRegistry)
|
||||
.convertRatesTo(TimeUnit.SECONDS)
|
||||
.convertDurationsTo(TimeUnit.MILLISECONDS)
|
||||
.build(sparkHost, sparkPort);
|
||||
sparkReporter.start(1, TimeUnit.MINUTES);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
146
src/main/java/mark/quinn/config/SecurityConfiguration.java
Normal file
146
src/main/java/mark/quinn/config/SecurityConfiguration.java
Normal file
@@ -0,0 +1,146 @@
|
||||
package mark.quinn.config;
|
||||
|
||||
import mark.quinn.security.*;
|
||||
import mark.quinn.web.filter.CsrfCookieGeneratorFilter;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.env.Environment;
|
||||
|
||||
import org.springframework.data.repository.query.spi.EvaluationContextExtension;
|
||||
import org.springframework.data.repository.query.spi.EvaluationContextExtensionSupport;
|
||||
import org.springframework.security.access.expression.SecurityExpressionRoot;
|
||||
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
|
||||
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
|
||||
import org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.builders.WebSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.security.data.repository.query.SecurityEvaluationContextExtension;
|
||||
|
||||
import org.springframework.security.web.authentication.RememberMeServices;
|
||||
import org.springframework.security.web.csrf.CsrfFilter;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
|
||||
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
|
||||
|
||||
@Inject
|
||||
private Environment env;
|
||||
|
||||
@Inject
|
||||
private AjaxAuthenticationSuccessHandler ajaxAuthenticationSuccessHandler;
|
||||
|
||||
@Inject
|
||||
private AjaxAuthenticationFailureHandler ajaxAuthenticationFailureHandler;
|
||||
|
||||
@Inject
|
||||
private AjaxLogoutSuccessHandler ajaxLogoutSuccessHandler;
|
||||
|
||||
@Inject
|
||||
private Http401UnauthorizedEntryPoint authenticationEntryPoint;
|
||||
|
||||
@Inject
|
||||
private UserDetailsService userDetailsService;
|
||||
|
||||
@Inject
|
||||
private RememberMeServices rememberMeServices;
|
||||
|
||||
@Bean
|
||||
public PasswordEncoder passwordEncoder() {
|
||||
return new BCryptPasswordEncoder();
|
||||
}
|
||||
|
||||
@Inject
|
||||
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
|
||||
auth
|
||||
.userDetailsService(userDetailsService)
|
||||
.passwordEncoder(passwordEncoder());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(WebSecurity web) throws Exception {
|
||||
web.ignoring()
|
||||
.antMatchers("/scripts/**/*.{js,html}")
|
||||
.antMatchers("/bower_components/**")
|
||||
.antMatchers("/i18n/**")
|
||||
.antMatchers("/assets/**")
|
||||
.antMatchers("/swagger-ui.html")
|
||||
.antMatchers("/test/**")
|
||||
.antMatchers("/console/**");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.csrf()
|
||||
.ignoringAntMatchers("/websocket/**")
|
||||
.and()
|
||||
.addFilterAfter(new CsrfCookieGeneratorFilter(), CsrfFilter.class)
|
||||
.exceptionHandling()
|
||||
.authenticationEntryPoint(authenticationEntryPoint)
|
||||
.and()
|
||||
.rememberMe()
|
||||
.rememberMeServices(rememberMeServices)
|
||||
.rememberMeParameter("remember-me")
|
||||
.key(env.getProperty("jhipster.security.rememberme.key"))
|
||||
.and()
|
||||
.formLogin()
|
||||
.loginProcessingUrl("/api/authentication")
|
||||
.successHandler(ajaxAuthenticationSuccessHandler)
|
||||
.failureHandler(ajaxAuthenticationFailureHandler)
|
||||
.usernameParameter("j_username")
|
||||
.passwordParameter("j_password")
|
||||
.permitAll()
|
||||
.and()
|
||||
.logout()
|
||||
.logoutUrl("/api/logout")
|
||||
.logoutSuccessHandler(ajaxLogoutSuccessHandler)
|
||||
.deleteCookies("JSESSIONID")
|
||||
.permitAll()
|
||||
.and()
|
||||
.headers()
|
||||
.frameOptions()
|
||||
.disable()
|
||||
.and()
|
||||
.authorizeRequests()
|
||||
.antMatchers("/api/register").permitAll()
|
||||
.antMatchers("/api/activate").permitAll()
|
||||
.antMatchers("/api/authenticate").permitAll()
|
||||
.antMatchers("/api/account/reset_password/init").permitAll()
|
||||
.antMatchers("/api/account/reset_password/finish").permitAll()
|
||||
.antMatchers("/api/logs/**").hasAuthority(AuthoritiesConstants.ADMIN)
|
||||
.antMatchers("/api/audits/**").hasAuthority(AuthoritiesConstants.ADMIN)
|
||||
.antMatchers("/api/**").authenticated()
|
||||
.antMatchers("/webjars/**").permitAll()
|
||||
.antMatchers("/metrics/**").hasAuthority(AuthoritiesConstants.ADMIN)
|
||||
.antMatchers("/health/**").hasAuthority(AuthoritiesConstants.ADMIN)
|
||||
.antMatchers("/trace/**").hasAuthority(AuthoritiesConstants.ADMIN)
|
||||
.antMatchers("/dump/**").hasAuthority(AuthoritiesConstants.ADMIN)
|
||||
.antMatchers("/shutdown/**").hasAuthority(AuthoritiesConstants.ADMIN)
|
||||
.antMatchers("/beans/**").hasAuthority(AuthoritiesConstants.ADMIN)
|
||||
.antMatchers("/configprops/**").hasAuthority(AuthoritiesConstants.ADMIN)
|
||||
.antMatchers("/info/**").hasAuthority(AuthoritiesConstants.ADMIN)
|
||||
.antMatchers("/autoconfig/**").hasAuthority(AuthoritiesConstants.ADMIN)
|
||||
.antMatchers("/env/**").hasAuthority(AuthoritiesConstants.ADMIN)
|
||||
.antMatchers("/trace/**").hasAuthority(AuthoritiesConstants.ADMIN)
|
||||
.antMatchers("/v2/api-docs/**").hasAuthority(AuthoritiesConstants.ADMIN)
|
||||
.antMatchers("/configuration/security").hasAuthority(AuthoritiesConstants.ADMIN)
|
||||
.antMatchers("/configuration/ui").hasAuthority(AuthoritiesConstants.ADMIN)
|
||||
.antMatchers("/swagger-ui.html").hasAuthority(AuthoritiesConstants.ADMIN)
|
||||
.antMatchers("/protected/**").authenticated();
|
||||
|
||||
}
|
||||
|
||||
@Bean
|
||||
public SecurityEvaluationContextExtension securityEvaluationContextExtension() {
|
||||
return new SecurityEvaluationContextExtension();
|
||||
}
|
||||
}
|
||||
39
src/main/java/mark/quinn/config/ThymeleafConfiguration.java
Normal file
39
src/main/java/mark/quinn/config/ThymeleafConfiguration.java
Normal file
@@ -0,0 +1,39 @@
|
||||
package mark.quinn.config;
|
||||
|
||||
import org.apache.commons.lang.CharEncoding;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.context.MessageSource;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Description;
|
||||
import org.springframework.context.support.ReloadableResourceBundleMessageSource;
|
||||
import org.thymeleaf.templateresolver.ClassLoaderTemplateResolver;
|
||||
|
||||
@Configuration
|
||||
public class ThymeleafConfiguration {
|
||||
|
||||
private final Logger log = LoggerFactory.getLogger(ThymeleafConfiguration.class);
|
||||
|
||||
@Bean
|
||||
@Description("Thymeleaf template resolver serving HTML 5 emails")
|
||||
public ClassLoaderTemplateResolver emailTemplateResolver() {
|
||||
ClassLoaderTemplateResolver emailTemplateResolver = new ClassLoaderTemplateResolver();
|
||||
emailTemplateResolver.setPrefix("mails/");
|
||||
emailTemplateResolver.setSuffix(".html");
|
||||
emailTemplateResolver.setTemplateMode("HTML5");
|
||||
emailTemplateResolver.setCharacterEncoding(CharEncoding.UTF_8);
|
||||
emailTemplateResolver.setOrder(1);
|
||||
return emailTemplateResolver;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Description("Spring mail message resolver")
|
||||
public MessageSource emailMessageSource() {
|
||||
log.info("loading non-reloadable mail messages resources");
|
||||
ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
|
||||
messageSource.setBasename("classpath:/mails/messages/messages");
|
||||
messageSource.setDefaultEncoding(CharEncoding.UTF_8);
|
||||
return messageSource;
|
||||
}
|
||||
}
|
||||
161
src/main/java/mark/quinn/config/WebConfigurer.java
Normal file
161
src/main/java/mark/quinn/config/WebConfigurer.java
Normal file
@@ -0,0 +1,161 @@
|
||||
package mark.quinn.config;
|
||||
|
||||
import com.codahale.metrics.MetricRegistry;
|
||||
import com.codahale.metrics.servlet.InstrumentedFilter;
|
||||
import com.codahale.metrics.servlets.MetricsServlet;
|
||||
import mark.quinn.web.filter.CachingHttpHeadersFilter;
|
||||
import mark.quinn.web.filter.StaticResourcesProductionFilter;
|
||||
import mark.quinn.web.filter.gzip.GZipServletFilter;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
|
||||
import org.springframework.boot.context.embedded.ConfigurableEmbeddedServletContainer;
|
||||
import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizer;
|
||||
import org.springframework.boot.context.embedded.MimeMappings;
|
||||
import org.springframework.boot.context.embedded.ServletContextInitializer;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.env.Environment;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.servlet.*;
|
||||
import java.util.Arrays;
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Configuration of web application with Servlet 3.0 APIs.
|
||||
*/
|
||||
@Configuration
|
||||
@AutoConfigureAfter(CacheConfiguration.class)
|
||||
public class WebConfigurer implements ServletContextInitializer, EmbeddedServletContainerCustomizer {
|
||||
|
||||
private final Logger log = LoggerFactory.getLogger(WebConfigurer.class);
|
||||
|
||||
@Inject
|
||||
private Environment env;
|
||||
|
||||
@Autowired(required = false)
|
||||
private MetricRegistry metricRegistry;
|
||||
|
||||
@Override
|
||||
public void onStartup(ServletContext servletContext) throws ServletException {
|
||||
log.info("Web application configuration, using profiles: {}", Arrays.toString(env.getActiveProfiles()));
|
||||
EnumSet<DispatcherType> disps = EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD, DispatcherType.ASYNC);
|
||||
if (!env.acceptsProfiles(Constants.SPRING_PROFILE_FAST)) {
|
||||
initMetrics(servletContext, disps);
|
||||
}
|
||||
if (env.acceptsProfiles(Constants.SPRING_PROFILE_PRODUCTION)) {
|
||||
initCachingHttpHeadersFilter(servletContext, disps);
|
||||
initStaticResourcesProductionFilter(servletContext, disps);
|
||||
initGzipFilter(servletContext, disps);
|
||||
}
|
||||
if (env.acceptsProfiles(Constants.SPRING_PROFILE_DEVELOPMENT)) {
|
||||
initH2Console(servletContext);
|
||||
}
|
||||
log.info("Web application fully configured");
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up Mime types.
|
||||
*/
|
||||
@Override
|
||||
public void customize(ConfigurableEmbeddedServletContainer container) {
|
||||
MimeMappings mappings = new MimeMappings(MimeMappings.DEFAULT);
|
||||
// IE issue, see https://github.com/jhipster/generator-jhipster/pull/711
|
||||
mappings.add("html", "text/html;charset=utf-8");
|
||||
// CloudFoundry issue, see https://github.com/cloudfoundry/gorouter/issues/64
|
||||
mappings.add("json", "text/html;charset=utf-8");
|
||||
container.setMimeMappings(mappings);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the GZip filter.
|
||||
*/
|
||||
private void initGzipFilter(ServletContext servletContext, EnumSet<DispatcherType> disps) {
|
||||
log.debug("Registering GZip Filter");
|
||||
FilterRegistration.Dynamic compressingFilter = servletContext.addFilter("gzipFilter", new GZipServletFilter());
|
||||
Map<String, String> parameters = new HashMap<>();
|
||||
compressingFilter.setInitParameters(parameters);
|
||||
compressingFilter.addMappingForUrlPatterns(disps, true, "*.css");
|
||||
compressingFilter.addMappingForUrlPatterns(disps, true, "*.json");
|
||||
compressingFilter.addMappingForUrlPatterns(disps, true, "*.html");
|
||||
compressingFilter.addMappingForUrlPatterns(disps, true, "*.js");
|
||||
compressingFilter.addMappingForUrlPatterns(disps, true, "*.svg");
|
||||
compressingFilter.addMappingForUrlPatterns(disps, true, "*.ttf");
|
||||
compressingFilter.addMappingForUrlPatterns(disps, true, "/api/*");
|
||||
compressingFilter.addMappingForUrlPatterns(disps, true, "/metrics/*");
|
||||
compressingFilter.setAsyncSupported(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the static resources production Filter.
|
||||
*/
|
||||
private void initStaticResourcesProductionFilter(ServletContext servletContext,
|
||||
EnumSet<DispatcherType> disps) {
|
||||
|
||||
log.debug("Registering static resources production Filter");
|
||||
FilterRegistration.Dynamic staticResourcesProductionFilter =
|
||||
servletContext.addFilter("staticResourcesProductionFilter",
|
||||
new StaticResourcesProductionFilter());
|
||||
|
||||
staticResourcesProductionFilter.addMappingForUrlPatterns(disps, true, "/");
|
||||
staticResourcesProductionFilter.addMappingForUrlPatterns(disps, true, "/index.html");
|
||||
staticResourcesProductionFilter.addMappingForUrlPatterns(disps, true, "/assets/*");
|
||||
staticResourcesProductionFilter.addMappingForUrlPatterns(disps, true, "/scripts/*");
|
||||
staticResourcesProductionFilter.setAsyncSupported(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the caching HTTP Headers Filter.
|
||||
*/
|
||||
private void initCachingHttpHeadersFilter(ServletContext servletContext,
|
||||
EnumSet<DispatcherType> disps) {
|
||||
log.debug("Registering Caching HTTP Headers Filter");
|
||||
FilterRegistration.Dynamic cachingHttpHeadersFilter =
|
||||
servletContext.addFilter("cachingHttpHeadersFilter",
|
||||
new CachingHttpHeadersFilter(env));
|
||||
|
||||
cachingHttpHeadersFilter.addMappingForUrlPatterns(disps, true, "/assets/*");
|
||||
cachingHttpHeadersFilter.addMappingForUrlPatterns(disps, true, "/scripts/*");
|
||||
cachingHttpHeadersFilter.setAsyncSupported(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes Metrics.
|
||||
*/
|
||||
private void initMetrics(ServletContext servletContext, EnumSet<DispatcherType> disps) {
|
||||
log.debug("Initializing Metrics registries");
|
||||
servletContext.setAttribute(InstrumentedFilter.REGISTRY_ATTRIBUTE,
|
||||
metricRegistry);
|
||||
servletContext.setAttribute(MetricsServlet.METRICS_REGISTRY,
|
||||
metricRegistry);
|
||||
|
||||
log.debug("Registering Metrics Filter");
|
||||
FilterRegistration.Dynamic metricsFilter = servletContext.addFilter("webappMetricsFilter",
|
||||
new InstrumentedFilter());
|
||||
|
||||
metricsFilter.addMappingForUrlPatterns(disps, true, "/*");
|
||||
metricsFilter.setAsyncSupported(true);
|
||||
|
||||
log.debug("Registering Metrics Servlet");
|
||||
ServletRegistration.Dynamic metricsAdminServlet =
|
||||
servletContext.addServlet("metricsServlet", new MetricsServlet());
|
||||
|
||||
metricsAdminServlet.addMapping("/metrics/metrics/*");
|
||||
metricsAdminServlet.setAsyncSupported(true);
|
||||
metricsAdminServlet.setLoadOnStartup(2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes H2 console
|
||||
*/
|
||||
private void initH2Console(ServletContext servletContext) {
|
||||
log.debug("Initialize H2 console");
|
||||
ServletRegistration.Dynamic h2ConsoleServlet = servletContext.addServlet("H2Console", new org.h2.server.web.WebServlet());
|
||||
h2ConsoleServlet.addMapping("/console/*");
|
||||
h2ConsoleServlet.setInitParameter("-properties", "src/main/resources");
|
||||
h2ConsoleServlet.setLoadOnStartup(1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
package mark.quinn.config.apidoc;
|
||||
|
||||
import mark.quinn.config.Constants;
|
||||
import java.util.Date;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.boot.bind.RelaxedPropertyResolver;
|
||||
import org.springframework.context.EnvironmentAware;
|
||||
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.http.ResponseEntity;
|
||||
import org.springframework.util.StopWatch;
|
||||
import springfox.documentation.service.ApiInfo;
|
||||
import springfox.documentation.spi.DocumentationType;
|
||||
import springfox.documentation.spring.web.plugins.Docket;
|
||||
import springfox.documentation.swagger2.annotations.EnableSwagger2;
|
||||
|
||||
import static springfox.documentation.builders.PathSelectors.regex;
|
||||
|
||||
/**
|
||||
* Springfox Swagger configuration.
|
||||
*
|
||||
* Warning! When having a lot of REST endpoints, Springfox can become a performance issue. In that
|
||||
* case, you can use a specific Spring profile for this class, so that only front-end developers
|
||||
* have access to the Swagger view.
|
||||
*/
|
||||
@Configuration
|
||||
@EnableSwagger2
|
||||
@Profile("!"+Constants.SPRING_PROFILE_PRODUCTION)
|
||||
public class SwaggerConfiguration implements EnvironmentAware {
|
||||
|
||||
private final Logger log = LoggerFactory.getLogger(SwaggerConfiguration.class);
|
||||
|
||||
public static final String DEFAULT_INCLUDE_PATTERN = "/api/.*";
|
||||
|
||||
private RelaxedPropertyResolver propertyResolver;
|
||||
|
||||
@Override
|
||||
public void setEnvironment(Environment environment) {
|
||||
this.propertyResolver = new RelaxedPropertyResolver(environment, "swagger.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Swagger Springfox configuration.
|
||||
*/
|
||||
@Bean
|
||||
public Docket swaggerSpringfoxDocket() {
|
||||
log.debug("Starting Swagger");
|
||||
StopWatch watch = new StopWatch();
|
||||
watch.start();
|
||||
Docket docket = new Docket(DocumentationType.SWAGGER_2)
|
||||
.apiInfo(apiInfo())
|
||||
.genericModelSubstitutes(ResponseEntity.class)
|
||||
.forCodeGeneration(true)
|
||||
.genericModelSubstitutes(ResponseEntity.class)
|
||||
.directModelSubstitute(org.joda.time.LocalDate.class, String.class)
|
||||
.directModelSubstitute(org.joda.time.LocalDateTime.class, Date.class)
|
||||
.directModelSubstitute(org.joda.time.DateTime.class, Date.class)
|
||||
.directModelSubstitute(java.time.LocalDate.class, String.class)
|
||||
.directModelSubstitute(java.time.ZonedDateTime.class, Date.class)
|
||||
.directModelSubstitute(java.time.LocalDateTime.class, Date.class)
|
||||
.select()
|
||||
.paths(regex(DEFAULT_INCLUDE_PATTERN))
|
||||
.build();
|
||||
watch.stop();
|
||||
log.debug("Started Swagger in {} ms", watch.getTotalTimeMillis());
|
||||
return docket;
|
||||
}
|
||||
|
||||
/**
|
||||
* API Info as it appears on the swagger-ui page.
|
||||
*/
|
||||
private ApiInfo apiInfo() {
|
||||
return new ApiInfo(
|
||||
propertyResolver.getProperty("title"),
|
||||
propertyResolver.getProperty("description"),
|
||||
propertyResolver.getProperty("version"),
|
||||
propertyResolver.getProperty("termsOfServiceUrl"),
|
||||
propertyResolver.getProperty("contact"),
|
||||
propertyResolver.getProperty("license"),
|
||||
propertyResolver.getProperty("licenseUrl"));
|
||||
}
|
||||
}
|
||||
4
src/main/java/mark/quinn/config/apidoc/package-info.java
Normal file
4
src/main/java/mark/quinn/config/apidoc/package-info.java
Normal file
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* Swagger api specific code.
|
||||
*/
|
||||
package mark.quinn.config.apidoc;
|
||||
@@ -0,0 +1,79 @@
|
||||
package mark.quinn.config.audit;
|
||||
|
||||
import mark.quinn.domain.PersistentAuditEvent;
|
||||
import org.springframework.boot.actuate.audit.AuditEvent;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.web.authentication.WebAuthenticationDetails;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
@Configuration
|
||||
public class AuditEventConverter {
|
||||
|
||||
/**
|
||||
* Convert a list of PersistentAuditEvent to a list of AuditEvent
|
||||
* @param persistentAuditEvents the list to convert
|
||||
* @return the converted list.
|
||||
*/
|
||||
public List<AuditEvent> convertToAuditEvent(Iterable<PersistentAuditEvent> persistentAuditEvents) {
|
||||
if (persistentAuditEvents == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
List<AuditEvent> auditEvents = new ArrayList<>();
|
||||
|
||||
for (PersistentAuditEvent persistentAuditEvent : persistentAuditEvents) {
|
||||
AuditEvent auditEvent = new AuditEvent(persistentAuditEvent.getAuditEventDate().toDate(), persistentAuditEvent.getPrincipal(),
|
||||
persistentAuditEvent.getAuditEventType(), convertDataToObjects(persistentAuditEvent.getData()));
|
||||
auditEvents.add(auditEvent);
|
||||
}
|
||||
|
||||
return auditEvents;
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal conversion. This is needed to support the current SpringBoot actuator AuditEventRepository interface
|
||||
*
|
||||
* @param data the data to convert
|
||||
* @return a map of String, Object
|
||||
*/
|
||||
public Map<String, Object> convertDataToObjects(Map<String, String> data) {
|
||||
Map<String, Object> results = new HashMap<>();
|
||||
|
||||
if (data != null) {
|
||||
for (String key : data.keySet()) {
|
||||
results.put(key, data.get(key));
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal conversion. This method will allow to save additional data.
|
||||
* By default, it will save the object as string
|
||||
*
|
||||
* @param data the data to convert
|
||||
* @return a map of String, String
|
||||
*/
|
||||
public Map<String, String> convertDataToStrings(Map<String, Object> data) {
|
||||
Map<String, String> results = new HashMap<>();
|
||||
|
||||
if (data != null) {
|
||||
for (String key : data.keySet()) {
|
||||
Object object = data.get(key);
|
||||
|
||||
// Extract the data that will be saved.
|
||||
if (object instanceof WebAuthenticationDetails) {
|
||||
WebAuthenticationDetails authenticationDetails = (WebAuthenticationDetails) object;
|
||||
results.put("remoteAddress", authenticationDetails.getRemoteAddress());
|
||||
results.put("sessionId", authenticationDetails.getSessionId());
|
||||
} else {
|
||||
results.put(key, object.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
}
|
||||
4
src/main/java/mark/quinn/config/audit/package-info.java
Normal file
4
src/main/java/mark/quinn/config/audit/package-info.java
Normal file
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* Audit specific code.
|
||||
*/
|
||||
package mark.quinn.config.audit;
|
||||
@@ -0,0 +1,87 @@
|
||||
package mark.quinn.config.locale;
|
||||
|
||||
import org.springframework.context.i18n.LocaleContext;
|
||||
import org.springframework.context.i18n.TimeZoneAwareLocaleContext;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.servlet.i18n.CookieLocaleResolver;
|
||||
import org.springframework.web.util.WebUtils;
|
||||
|
||||
import javax.servlet.http.Cookie;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.util.Locale;
|
||||
import java.util.TimeZone;
|
||||
|
||||
/**
|
||||
* Angular cookie saved the locale with a double quote (%22en%22).
|
||||
* So the default CookieLocaleResolver#StringUtils.parseLocaleString(localePart)
|
||||
* is not able to parse the locale.
|
||||
*
|
||||
* This class will check if a double quote has been added, if so it will remove it.
|
||||
*/
|
||||
public class AngularCookieLocaleResolver extends CookieLocaleResolver {
|
||||
|
||||
@Override
|
||||
public Locale resolveLocale(HttpServletRequest request) {
|
||||
parseLocaleCookieIfNecessary(request);
|
||||
return (Locale) request.getAttribute(LOCALE_REQUEST_ATTRIBUTE_NAME);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocaleContext resolveLocaleContext(final HttpServletRequest request) {
|
||||
parseLocaleCookieIfNecessary(request);
|
||||
return new TimeZoneAwareLocaleContext() {
|
||||
@Override
|
||||
public Locale getLocale() {
|
||||
return (Locale) request.getAttribute(LOCALE_REQUEST_ATTRIBUTE_NAME);
|
||||
}
|
||||
@Override
|
||||
public TimeZone getTimeZone() {
|
||||
return (TimeZone) request.getAttribute(TIME_ZONE_REQUEST_ATTRIBUTE_NAME);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addCookie(HttpServletResponse response, String cookieValue) {
|
||||
// Mandatory cookie modification for angular to support the locale switching on the server side.
|
||||
cookieValue = "%22" + cookieValue + "%22";
|
||||
super.addCookie(response, cookieValue);
|
||||
}
|
||||
|
||||
private void parseLocaleCookieIfNecessary(HttpServletRequest request) {
|
||||
if (request.getAttribute(LOCALE_REQUEST_ATTRIBUTE_NAME) == null) {
|
||||
// Retrieve and parse cookie value.
|
||||
Cookie cookie = WebUtils.getCookie(request, getCookieName());
|
||||
Locale locale = null;
|
||||
TimeZone timeZone = null;
|
||||
if (cookie != null) {
|
||||
String value = cookie.getValue();
|
||||
|
||||
// Remove the double quote
|
||||
value = StringUtils.replace(value, "%22", "");
|
||||
|
||||
String localePart = value;
|
||||
String timeZonePart = null;
|
||||
int spaceIndex = localePart.indexOf(' ');
|
||||
if (spaceIndex != -1) {
|
||||
localePart = value.substring(0, spaceIndex);
|
||||
timeZonePart = value.substring(spaceIndex + 1);
|
||||
}
|
||||
locale = (!"-".equals(localePart) ? StringUtils.parseLocaleString(localePart.replace('-', '_')) : null);
|
||||
if (timeZonePart != null) {
|
||||
timeZone = StringUtils.parseTimeZoneString(timeZonePart);
|
||||
}
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Parsed cookie value [" + cookie.getValue() + "] into locale '" + locale +
|
||||
"'" + (timeZone != null ? " and time zone '" + timeZone.getID() + "'" : ""));
|
||||
}
|
||||
}
|
||||
request.setAttribute(LOCALE_REQUEST_ATTRIBUTE_NAME,
|
||||
(locale != null ? locale: determineDefaultLocale(request)));
|
||||
|
||||
request.setAttribute(TIME_ZONE_REQUEST_ATTRIBUTE_NAME,
|
||||
(timeZone != null ? timeZone : determineDefaultTimeZone(request)));
|
||||
}
|
||||
}
|
||||
}
|
||||
4
src/main/java/mark/quinn/config/locale/package-info.java
Normal file
4
src/main/java/mark/quinn/config/locale/package-info.java
Normal file
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* Locale specific code.
|
||||
*/
|
||||
package mark.quinn.config.locale;
|
||||
@@ -0,0 +1,73 @@
|
||||
package mark.quinn.config.metrics;
|
||||
|
||||
import org.springframework.boot.actuate.health.AbstractHealthIndicator;
|
||||
import org.springframework.boot.actuate.health.Health;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.sql.Connection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* SpringBoot Actuator HealthIndicator check for the Database.
|
||||
*/
|
||||
public class DatabaseHealthIndicator extends AbstractHealthIndicator {
|
||||
|
||||
private JdbcTemplate jdbcTemplate;
|
||||
|
||||
private static Map<String, String> queries = new HashMap<>();
|
||||
|
||||
static {
|
||||
queries.put("HSQL Database Engine",
|
||||
"SELECT COUNT(*) FROM INFORMATION_SCHEMA.SYSTEM_USERS");
|
||||
queries.put("Oracle", "SELECT 'Hello' from DUAL");
|
||||
queries.put("Apache Derby", "SELECT 1 FROM SYSIBM.SYSDUMMY1");
|
||||
queries.put("MySQL", "SELECT 1");
|
||||
queries.put("PostgreSQL", "SELECT 1");
|
||||
queries.put("Microsoft SQL Server", "SELECT 1");
|
||||
}
|
||||
|
||||
private static String DEFAULT_QUERY = "SELECT 1";
|
||||
|
||||
private String query = null;
|
||||
|
||||
public DatabaseHealthIndicator(DataSource dataSource) {
|
||||
this.jdbcTemplate = new JdbcTemplate(dataSource);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doHealthCheck(Health.Builder builder) throws Exception {
|
||||
String product = getProduct();
|
||||
builder.up().withDetail("database", product);
|
||||
String query = detectQuery(product);
|
||||
if (StringUtils.hasText(query)) {
|
||||
try {
|
||||
builder.withDetail("hello",
|
||||
this.jdbcTemplate.queryForObject(query, Object.class));
|
||||
} catch (Exception ex) {
|
||||
builder.down(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String getProduct() {
|
||||
return this.jdbcTemplate.execute((Connection connection) -> connection.getMetaData().getDatabaseProductName());
|
||||
}
|
||||
|
||||
protected String detectQuery(String product) {
|
||||
String query = this.query;
|
||||
if (!StringUtils.hasText(query)) {
|
||||
query = queries.get(product);
|
||||
}
|
||||
if (!StringUtils.hasText(query)) {
|
||||
query = DEFAULT_QUERY;
|
||||
}
|
||||
return query;
|
||||
}
|
||||
|
||||
public void setQuery(String query) {
|
||||
this.query = query;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package mark.quinn.config.metrics;
|
||||
|
||||
import org.springframework.boot.actuate.health.HealthIndicator;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.mail.javamail.JavaMailSenderImpl;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.sql.DataSource;
|
||||
|
||||
@Configuration
|
||||
public class JHipsterHealthIndicatorConfiguration {
|
||||
|
||||
@Inject
|
||||
private JavaMailSenderImpl javaMailSender;
|
||||
|
||||
@Inject
|
||||
private DataSource dataSource;
|
||||
|
||||
@Bean
|
||||
public HealthIndicator dbHealthIndicator() {
|
||||
return new DatabaseHealthIndicator(dataSource);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public HealthIndicator mailHealthIndicator() {
|
||||
return new JavaMailHealthIndicator(javaMailSender);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package mark.quinn.config.metrics;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.boot.actuate.health.AbstractHealthIndicator;
|
||||
import org.springframework.boot.actuate.health.Health;
|
||||
import org.springframework.mail.javamail.JavaMailSenderImpl;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import javax.mail.MessagingException;
|
||||
|
||||
/**
|
||||
* SpringBoot Actuator HealthIndicator check for JavaMail.
|
||||
*/
|
||||
public class JavaMailHealthIndicator extends AbstractHealthIndicator {
|
||||
|
||||
private final Logger log = LoggerFactory.getLogger(JavaMailHealthIndicator.class);
|
||||
|
||||
private JavaMailSenderImpl javaMailSender;
|
||||
|
||||
public JavaMailHealthIndicator(JavaMailSenderImpl javaMailSender) {
|
||||
Assert.notNull(javaMailSender, "javaMailSender must not be null");
|
||||
this.javaMailSender = javaMailSender;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doHealthCheck(Health.Builder builder) throws Exception {
|
||||
log.debug("Initializing JavaMail health indicator");
|
||||
try {
|
||||
javaMailSender.getSession().getTransport().connect(javaMailSender.getHost(),
|
||||
javaMailSender.getPort(),
|
||||
javaMailSender.getUsername(),
|
||||
javaMailSender.getPassword());
|
||||
|
||||
builder.up();
|
||||
|
||||
} catch (MessagingException e) {
|
||||
log.debug("Cannot connect to e-mail server. Error: {}", e.getMessage());
|
||||
builder.down(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* Health and Metrics specific code.
|
||||
*/
|
||||
package mark.quinn.config.metrics;
|
||||
4
src/main/java/mark/quinn/config/package-info.java
Normal file
4
src/main/java/mark/quinn/config/package-info.java
Normal file
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* Spring Framework configuration files.
|
||||
*/
|
||||
package mark.quinn.config;
|
||||
82
src/main/java/mark/quinn/domain/AbstractAuditingEntity.java
Normal file
82
src/main/java/mark/quinn/domain/AbstractAuditingEntity.java
Normal file
@@ -0,0 +1,82 @@
|
||||
package mark.quinn.domain;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import org.hibernate.annotations.Type;
|
||||
import org.hibernate.envers.Audited;
|
||||
import org.joda.time.DateTime;
|
||||
import org.springframework.data.annotation.CreatedBy;
|
||||
import org.springframework.data.annotation.CreatedDate;
|
||||
import org.springframework.data.annotation.LastModifiedBy;
|
||||
import org.springframework.data.annotation.LastModifiedDate;
|
||||
|
||||
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.EntityListeners;
|
||||
import javax.persistence.MappedSuperclass;
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
/**
|
||||
* Base abstract class for entities which will hold definitions for created, last modified by and created,
|
||||
* last modified by date.
|
||||
*/
|
||||
@MappedSuperclass
|
||||
@Audited
|
||||
@EntityListeners(AuditingEntityListener.class)
|
||||
public abstract class AbstractAuditingEntity {
|
||||
|
||||
@CreatedBy
|
||||
@NotNull
|
||||
@Column(name = "created_by", nullable = false, length = 50, updatable = false)
|
||||
@JsonIgnore
|
||||
private String createdBy;
|
||||
|
||||
@CreatedDate
|
||||
@NotNull
|
||||
@Type(type = "org.jadira.usertype.dateandtime.joda.PersistentDateTime")
|
||||
@Column(name = "created_date", nullable = false)
|
||||
@JsonIgnore
|
||||
private DateTime createdDate = DateTime.now();
|
||||
|
||||
@LastModifiedBy
|
||||
@Column(name = "last_modified_by", length = 50)
|
||||
@JsonIgnore
|
||||
private String lastModifiedBy;
|
||||
|
||||
@LastModifiedDate
|
||||
@Type(type = "org.jadira.usertype.dateandtime.joda.PersistentDateTime")
|
||||
@Column(name = "last_modified_date")
|
||||
@JsonIgnore
|
||||
private DateTime lastModifiedDate = DateTime.now();
|
||||
|
||||
public String getCreatedBy() {
|
||||
return createdBy;
|
||||
}
|
||||
|
||||
public void setCreatedBy(String createdBy) {
|
||||
this.createdBy = createdBy;
|
||||
}
|
||||
|
||||
public DateTime getCreatedDate() {
|
||||
return createdDate;
|
||||
}
|
||||
|
||||
public void setCreatedDate(DateTime createdDate) {
|
||||
this.createdDate = createdDate;
|
||||
}
|
||||
|
||||
public String getLastModifiedBy() {
|
||||
return lastModifiedBy;
|
||||
}
|
||||
|
||||
public void setLastModifiedBy(String lastModifiedBy) {
|
||||
this.lastModifiedBy = lastModifiedBy;
|
||||
}
|
||||
|
||||
public DateTime getLastModifiedDate() {
|
||||
return lastModifiedDate;
|
||||
}
|
||||
|
||||
public void setLastModifiedDate(DateTime lastModifiedDate) {
|
||||
this.lastModifiedDate = lastModifiedDate;
|
||||
}
|
||||
}
|
||||
62
src/main/java/mark/quinn/domain/Authority.java
Normal file
62
src/main/java/mark/quinn/domain/Authority.java
Normal file
@@ -0,0 +1,62 @@
|
||||
package mark.quinn.domain;
|
||||
|
||||
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.Table;
|
||||
import javax.persistence.Column;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import javax.validation.constraints.Size;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* An authority (a security role) used by Spring Security.
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "JHI_AUTHORITY")
|
||||
public class Authority implements Serializable {
|
||||
|
||||
@NotNull
|
||||
@Size(min = 0, max = 50)
|
||||
@Id
|
||||
@Column(length = 50)
|
||||
private String name;
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Authority authority = (Authority) o;
|
||||
|
||||
if (name != null ? !name.equals(authority.name) : authority.name != null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return name != null ? name.hashCode() : 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Authority{" +
|
||||
"name='" + name + '\'' +
|
||||
"}";
|
||||
}
|
||||
}
|
||||
78
src/main/java/mark/quinn/domain/PersistentAuditEvent.java
Normal file
78
src/main/java/mark/quinn/domain/PersistentAuditEvent.java
Normal file
@@ -0,0 +1,78 @@
|
||||
package mark.quinn.domain;
|
||||
|
||||
import org.hibernate.annotations.Type;
|
||||
import org.joda.time.LocalDateTime;
|
||||
import javax.persistence.*;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Persist AuditEvent managed by the Spring Boot actuator
|
||||
* @see org.springframework.boot.actuate.audit.AuditEvent
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "JHI_PERSISTENT_AUDIT_EVENT")
|
||||
public class PersistentAuditEvent {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.AUTO)
|
||||
@Column(name = "event_id")
|
||||
private Long id;
|
||||
|
||||
@NotNull
|
||||
@Column(nullable = false)
|
||||
private String principal;
|
||||
|
||||
@Column(name = "event_date")
|
||||
@Type(type = "org.jadira.usertype.dateandtime.joda.PersistentLocalDateTime")
|
||||
private LocalDateTime auditEventDate;
|
||||
@Column(name = "event_type")
|
||||
private String auditEventType;
|
||||
|
||||
@ElementCollection
|
||||
@MapKeyColumn(name="name")
|
||||
@Column(name="value")
|
||||
@CollectionTable(name="JHI_PERSISTENT_AUDIT_EVT_DATA", joinColumns=@JoinColumn(name="event_id"))
|
||||
private Map<String, String> data = new HashMap<>();
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getPrincipal() {
|
||||
return principal;
|
||||
}
|
||||
|
||||
public void setPrincipal(String principal) {
|
||||
this.principal = principal;
|
||||
}
|
||||
|
||||
public LocalDateTime getAuditEventDate() {
|
||||
return auditEventDate;
|
||||
}
|
||||
|
||||
public void setAuditEventDate(LocalDateTime auditEventDate) {
|
||||
this.auditEventDate = auditEventDate;
|
||||
}
|
||||
|
||||
public String getAuditEventType() {
|
||||
return auditEventType;
|
||||
}
|
||||
|
||||
public void setAuditEventType(String auditEventType) {
|
||||
this.auditEventType = auditEventType;
|
||||
}
|
||||
|
||||
public Map<String, String> getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
public void setData(Map<String, String> data) {
|
||||
this.data = data;
|
||||
}
|
||||
}
|
||||
143
src/main/java/mark/quinn/domain/PersistentToken.java
Normal file
143
src/main/java/mark/quinn/domain/PersistentToken.java
Normal file
@@ -0,0 +1,143 @@
|
||||
package mark.quinn.domain;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonGetter;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import org.hibernate.annotations.Type;
|
||||
import org.joda.time.LocalDate;
|
||||
import org.joda.time.format.DateTimeFormat;
|
||||
import org.joda.time.format.DateTimeFormatter;
|
||||
|
||||
import javax.persistence.*;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import javax.validation.constraints.Size;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* Persistent tokens are used by Spring Security to automatically log in users.
|
||||
*
|
||||
* @see mark.quinn.security.CustomPersistentRememberMeServices
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "JHI_PERSISTENT_TOKEN")
|
||||
public class PersistentToken implements Serializable {
|
||||
|
||||
private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormat.forPattern("d MMMM yyyy");
|
||||
|
||||
private static final int MAX_USER_AGENT_LEN = 255;
|
||||
|
||||
@Id
|
||||
private String series;
|
||||
|
||||
@JsonIgnore
|
||||
@NotNull
|
||||
@Column(name = "token_value", nullable = false)
|
||||
private String tokenValue;
|
||||
|
||||
@JsonIgnore
|
||||
@Column(name = "token_date")
|
||||
@Type(type = "org.jadira.usertype.dateandtime.joda.PersistentLocalDate")
|
||||
private LocalDate tokenDate;
|
||||
|
||||
//an IPV6 address max length is 39 characters
|
||||
@Size(min = 0, max = 39)
|
||||
@Column(name = "ip_address", length = 39)
|
||||
private String ipAddress;
|
||||
|
||||
@Column(name = "user_agent")
|
||||
private String userAgent;
|
||||
|
||||
@JsonIgnore
|
||||
@ManyToOne
|
||||
private User user;
|
||||
|
||||
public String getSeries() {
|
||||
return series;
|
||||
}
|
||||
|
||||
public void setSeries(String series) {
|
||||
this.series = series;
|
||||
}
|
||||
|
||||
public String getTokenValue() {
|
||||
return tokenValue;
|
||||
}
|
||||
|
||||
public void setTokenValue(String tokenValue) {
|
||||
this.tokenValue = tokenValue;
|
||||
}
|
||||
|
||||
public LocalDate getTokenDate() {
|
||||
return tokenDate;
|
||||
}
|
||||
|
||||
public void setTokenDate(LocalDate tokenDate) {
|
||||
this.tokenDate = tokenDate;
|
||||
}
|
||||
|
||||
@JsonGetter
|
||||
public String getFormattedTokenDate() {
|
||||
return DATE_TIME_FORMATTER.print(this.tokenDate);
|
||||
}
|
||||
|
||||
public String getIpAddress() {
|
||||
return ipAddress;
|
||||
}
|
||||
|
||||
public void setIpAddress(String ipAddress) {
|
||||
this.ipAddress = ipAddress;
|
||||
}
|
||||
|
||||
public String getUserAgent() {
|
||||
return userAgent;
|
||||
}
|
||||
|
||||
public void setUserAgent(String userAgent) {
|
||||
if (userAgent.length() >= MAX_USER_AGENT_LEN) {
|
||||
this.userAgent = userAgent.substring(0, MAX_USER_AGENT_LEN - 1);
|
||||
} else {
|
||||
this.userAgent = userAgent;
|
||||
}
|
||||
}
|
||||
|
||||
public User getUser() {
|
||||
return user;
|
||||
}
|
||||
|
||||
public void setUser(User user) {
|
||||
this.user = user;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
PersistentToken that = (PersistentToken) o;
|
||||
|
||||
if (!series.equals(that.series)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return series.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "PersistentToken{" +
|
||||
"series='" + series + '\'' +
|
||||
", tokenValue='" + tokenValue + '\'' +
|
||||
", tokenDate=" + tokenDate +
|
||||
", ipAddress='" + ipAddress + '\'' +
|
||||
", userAgent='" + userAgent + '\'' +
|
||||
"}";
|
||||
}
|
||||
}
|
||||
225
src/main/java/mark/quinn/domain/User.java
Normal file
225
src/main/java/mark/quinn/domain/User.java
Normal file
@@ -0,0 +1,225 @@
|
||||
package mark.quinn.domain;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import org.hibernate.validator.constraints.Email;
|
||||
|
||||
import javax.persistence.*;
|
||||
import org.hibernate.annotations.Type;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import javax.validation.constraints.Pattern;
|
||||
import javax.validation.constraints.Size;
|
||||
import java.io.Serializable;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/**
|
||||
* A user.
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "JHI_USER")
|
||||
public class User extends AbstractAuditingEntity implements Serializable {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.AUTO)
|
||||
private Long id;
|
||||
|
||||
@NotNull
|
||||
@Pattern(regexp = "^[a-z0-9]*$")
|
||||
@Size(min = 1, max = 50)
|
||||
@Column(length = 50, unique = true, nullable = false)
|
||||
private String login;
|
||||
|
||||
@JsonIgnore
|
||||
@NotNull
|
||||
@Size(min = 60, max = 60)
|
||||
@Column(length = 60)
|
||||
private String password;
|
||||
|
||||
@Size(max = 50)
|
||||
@Column(name = "first_name", length = 50)
|
||||
private String firstName;
|
||||
|
||||
@Size(max = 50)
|
||||
@Column(name = "last_name", length = 50)
|
||||
private String lastName;
|
||||
|
||||
@Email
|
||||
@Size(max = 100)
|
||||
@Column(length = 100, unique = true)
|
||||
private String email;
|
||||
|
||||
@Column(nullable = false)
|
||||
private boolean activated = false;
|
||||
|
||||
@Size(min = 2, max = 5)
|
||||
@Column(name = "lang_key", length = 5)
|
||||
private String langKey;
|
||||
|
||||
@Size(max = 20)
|
||||
@Column(name = "activation_key", length = 20)
|
||||
@JsonIgnore
|
||||
private String activationKey;
|
||||
|
||||
@Size(max = 20)
|
||||
@Column(name = "reset_key", length = 20)
|
||||
private String resetKey;
|
||||
|
||||
@Type(type = "org.jadira.usertype.dateandtime.joda.PersistentDateTime")
|
||||
@Column(name = "reset_date", nullable = true)
|
||||
private DateTime resetDate = null;
|
||||
|
||||
@JsonIgnore
|
||||
@ManyToMany
|
||||
@JoinTable(
|
||||
name = "JHI_USER_AUTHORITY",
|
||||
joinColumns = {@JoinColumn(name = "user_id", referencedColumnName = "id")},
|
||||
inverseJoinColumns = {@JoinColumn(name = "authority_name", referencedColumnName = "name")})
|
||||
private Set<Authority> authorities = new HashSet<>();
|
||||
|
||||
@JsonIgnore
|
||||
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "user")
|
||||
private Set<PersistentToken> persistentTokens = new HashSet<>();
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getLogin() {
|
||||
return login;
|
||||
}
|
||||
|
||||
public void setLogin(String login) {
|
||||
this.login = login;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
public void setPassword(String password) {
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
public String getFirstName() {
|
||||
return firstName;
|
||||
}
|
||||
|
||||
public void setFirstName(String firstName) {
|
||||
this.firstName = firstName;
|
||||
}
|
||||
|
||||
public String getLastName() {
|
||||
return lastName;
|
||||
}
|
||||
|
||||
public void setLastName(String lastName) {
|
||||
this.lastName = lastName;
|
||||
}
|
||||
|
||||
public String getEmail() {
|
||||
return email;
|
||||
}
|
||||
|
||||
public void setEmail(String email) {
|
||||
this.email = email;
|
||||
}
|
||||
|
||||
public boolean getActivated() {
|
||||
return activated;
|
||||
}
|
||||
|
||||
public void setActivated(boolean activated) {
|
||||
this.activated = activated;
|
||||
}
|
||||
|
||||
public String getActivationKey() {
|
||||
return activationKey;
|
||||
}
|
||||
|
||||
public void setActivationKey(String activationKey) {
|
||||
this.activationKey = activationKey;
|
||||
}
|
||||
|
||||
public String getResetKey() {
|
||||
return resetKey;
|
||||
}
|
||||
|
||||
public void setResetKey(String resetKey) {
|
||||
this.resetKey = resetKey;
|
||||
}
|
||||
|
||||
public DateTime getResetDate() {
|
||||
return resetDate;
|
||||
}
|
||||
|
||||
public void setResetDate(DateTime resetDate) {
|
||||
this.resetDate = resetDate;
|
||||
}
|
||||
|
||||
public String getLangKey() {
|
||||
return langKey;
|
||||
}
|
||||
|
||||
public void setLangKey(String langKey) {
|
||||
this.langKey = langKey;
|
||||
}
|
||||
|
||||
public Set<Authority> getAuthorities() {
|
||||
return authorities;
|
||||
}
|
||||
|
||||
public void setAuthorities(Set<Authority> authorities) {
|
||||
this.authorities = authorities;
|
||||
}
|
||||
|
||||
public Set<PersistentToken> getPersistentTokens() {
|
||||
return persistentTokens;
|
||||
}
|
||||
|
||||
public void setPersistentTokens(Set<PersistentToken> persistentTokens) {
|
||||
this.persistentTokens = persistentTokens;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
User user = (User) o;
|
||||
|
||||
if (!login.equals(user.login)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return login.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "User{" +
|
||||
"login='" + login + '\'' +
|
||||
", password='" + password + '\'' +
|
||||
", firstName='" + firstName + '\'' +
|
||||
", lastName='" + lastName + '\'' +
|
||||
", email='" + email + '\'' +
|
||||
", activated='" + activated + '\'' +
|
||||
", langKey='" + langKey + '\'' +
|
||||
", activationKey='" + activationKey + '\'' +
|
||||
"}";
|
||||
}
|
||||
}
|
||||
4
src/main/java/mark/quinn/domain/package-info.java
Normal file
4
src/main/java/mark/quinn/domain/package-info.java
Normal file
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* JPA domain objects.
|
||||
*/
|
||||
package mark.quinn.domain;
|
||||
@@ -0,0 +1,31 @@
|
||||
package mark.quinn.domain.util;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.format.ISODateTimeFormat;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.core.JsonToken;
|
||||
import com.fasterxml.jackson.databind.DeserializationContext;
|
||||
import com.fasterxml.jackson.databind.JsonDeserializer;
|
||||
|
||||
/**
|
||||
* Custom Jackson deserializer for transforming a JSON object to a Joda DateTime object.
|
||||
*/
|
||||
public class CustomDateTimeDeserializer extends JsonDeserializer<DateTime> {
|
||||
|
||||
@Override
|
||||
public DateTime deserialize(JsonParser jp, DeserializationContext ctxt)
|
||||
throws IOException {
|
||||
JsonToken t = jp.getCurrentToken();
|
||||
if (t == JsonToken.VALUE_STRING) {
|
||||
String str = jp.getText().trim();
|
||||
return ISODateTimeFormat.dateTimeParser().parseDateTime(str);
|
||||
}
|
||||
if (t == JsonToken.VALUE_NUMBER_INT) {
|
||||
return new DateTime(jp.getLongValue());
|
||||
}
|
||||
throw ctxt.mappingException(handledType());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package mark.quinn.domain.util;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.DateTimeZone;
|
||||
import org.joda.time.format.DateTimeFormat;
|
||||
import org.joda.time.format.DateTimeFormatter;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.databind.JsonSerializer;
|
||||
import com.fasterxml.jackson.databind.SerializerProvider;
|
||||
|
||||
/**
|
||||
* Custom Jackson serializer for transforming a Joda DateTime object to JSON.
|
||||
*/
|
||||
public class CustomDateTimeSerializer extends JsonSerializer<DateTime> {
|
||||
|
||||
private static DateTimeFormatter formatter = DateTimeFormat
|
||||
.forPattern("yyyy-MM-dd'T'HH:mm:ss'Z'");
|
||||
|
||||
@Override
|
||||
public void serialize(DateTime value, JsonGenerator generator,
|
||||
SerializerProvider serializerProvider)
|
||||
throws IOException {
|
||||
generator.writeString(formatter.print(value.toDateTime(DateTimeZone.UTC)));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package mark.quinn.domain.util;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.databind.JsonSerializer;
|
||||
import com.fasterxml.jackson.databind.SerializerProvider;
|
||||
import org.joda.time.LocalDate;
|
||||
import org.joda.time.format.DateTimeFormat;
|
||||
import org.joda.time.format.DateTimeFormatter;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Custom Jackson serializer for transforming a Joda LocalDate object to JSON.
|
||||
*/
|
||||
public class CustomLocalDateSerializer extends JsonSerializer<LocalDate> {
|
||||
|
||||
private static DateTimeFormatter formatter = DateTimeFormat
|
||||
.forPattern("yyyy-MM-dd");
|
||||
|
||||
@Override
|
||||
public void serialize(LocalDate value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
|
||||
jgen.writeString(formatter.print(value));
|
||||
}
|
||||
}
|
||||
12
src/main/java/mark/quinn/domain/util/FixedH2Dialect.java
Normal file
12
src/main/java/mark/quinn/domain/util/FixedH2Dialect.java
Normal file
@@ -0,0 +1,12 @@
|
||||
package mark.quinn.domain.util;
|
||||
|
||||
import java.sql.Types;
|
||||
|
||||
import org.hibernate.dialect.H2Dialect;
|
||||
|
||||
public class FixedH2Dialect extends H2Dialect {
|
||||
public FixedH2Dialect() {
|
||||
super();
|
||||
registerColumnType( Types.FLOAT, "real" );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package mark.quinn.domain.util;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.core.JsonToken;
|
||||
import com.fasterxml.jackson.databind.DeserializationContext;
|
||||
import com.fasterxml.jackson.databind.JsonDeserializer;
|
||||
import org.joda.time.LocalDate;
|
||||
import org.joda.time.format.ISODateTimeFormat;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Custom Jackson deserializer for transforming a JSON object (using the ISO 8601 date format)
|
||||
* to a Joda LocalDate object.
|
||||
*/
|
||||
public class ISO8601LocalDateDeserializer extends JsonDeserializer<LocalDate> {
|
||||
|
||||
@Override
|
||||
public LocalDate deserialize(JsonParser jp, DeserializationContext ctxt)
|
||||
throws IOException {
|
||||
JsonToken t = jp.getCurrentToken();
|
||||
if (t == JsonToken.VALUE_STRING) {
|
||||
String str = jp.getText().trim();
|
||||
return ISODateTimeFormat.dateTimeParser().parseDateTime(str).toLocalDate();
|
||||
}
|
||||
if (t == JsonToken.VALUE_NUMBER_INT) {
|
||||
return new LocalDate(jp.getLongValue());
|
||||
}
|
||||
throw ctxt.mappingException(handledType());
|
||||
}
|
||||
}
|
||||
10
src/main/java/mark/quinn/repository/AuthorityRepository.java
Normal file
10
src/main/java/mark/quinn/repository/AuthorityRepository.java
Normal file
@@ -0,0 +1,10 @@
|
||||
package mark.quinn.repository;
|
||||
|
||||
import mark.quinn.domain.Authority;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
/**
|
||||
* Spring Data JPA repository for the Authority entity.
|
||||
*/
|
||||
public interface AuthorityRepository extends JpaRepository<Authority, String> {
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package mark.quinn.repository;
|
||||
|
||||
import mark.quinn.config.audit.AuditEventConverter;
|
||||
import mark.quinn.domain.PersistentAuditEvent;
|
||||
import org.joda.time.LocalDateTime;
|
||||
import org.springframework.boot.actuate.audit.AuditEvent;
|
||||
import org.springframework.boot.actuate.audit.AuditEventRepository;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.stereotype.Repository;
|
||||
import org.springframework.transaction.annotation.Propagation;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Wraps an implementation of Spring Boot's AuditEventRepository.
|
||||
*/
|
||||
@Repository
|
||||
public class CustomAuditEventRepository {
|
||||
|
||||
@Inject
|
||||
private PersistenceAuditEventRepository persistenceAuditEventRepository;
|
||||
|
||||
@Bean
|
||||
public AuditEventRepository auditEventRepository() {
|
||||
return new AuditEventRepository() {
|
||||
|
||||
@Inject
|
||||
private AuditEventConverter auditEventConverter;
|
||||
|
||||
@Override
|
||||
public List<AuditEvent> find(String principal, Date after) {
|
||||
Iterable<PersistentAuditEvent> persistentAuditEvents;
|
||||
if (principal == null && after == null) {
|
||||
persistentAuditEvents = persistenceAuditEventRepository.findAll();
|
||||
} else if (after == null) {
|
||||
persistentAuditEvents = persistenceAuditEventRepository.findByPrincipal(principal);
|
||||
} else {
|
||||
persistentAuditEvents =
|
||||
persistenceAuditEventRepository.findByPrincipalAndAuditEventDateAfter(principal, new LocalDateTime(after));
|
||||
}
|
||||
return auditEventConverter.convertToAuditEvent(persistentAuditEvents);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(propagation = Propagation.REQUIRES_NEW)
|
||||
public void add(AuditEvent event) {
|
||||
PersistentAuditEvent persistentAuditEvent = new PersistentAuditEvent();
|
||||
persistentAuditEvent.setPrincipal(event.getPrincipal());
|
||||
persistentAuditEvent.setAuditEventType(event.getType());
|
||||
persistentAuditEvent.setAuditEventDate(new LocalDateTime(event.getTimestamp()));
|
||||
persistentAuditEvent.setData(auditEventConverter.convertDataToStrings(event.getData()));
|
||||
|
||||
persistenceAuditEventRepository.save(persistentAuditEvent);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package mark.quinn.repository;
|
||||
|
||||
import mark.quinn.domain.PersistentAuditEvent;
|
||||
import org.joda.time.LocalDateTime;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Spring Data JPA repository for the PersistentAuditEvent entity.
|
||||
*/
|
||||
public interface PersistenceAuditEventRepository extends JpaRepository<PersistentAuditEvent, String> {
|
||||
|
||||
List<PersistentAuditEvent> findByPrincipal(String principal);
|
||||
|
||||
List<PersistentAuditEvent> findByPrincipalAndAuditEventDateAfter(String principal, LocalDateTime after);
|
||||
|
||||
List<PersistentAuditEvent> findAllByAuditEventDateBetween(LocalDateTime fromDate, LocalDateTime toDate);
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package mark.quinn.repository;
|
||||
|
||||
import mark.quinn.domain.PersistentToken;
|
||||
import mark.quinn.domain.User;
|
||||
import org.joda.time.LocalDate;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Spring Data JPA repository for the PersistentToken entity.
|
||||
*/
|
||||
public interface PersistentTokenRepository extends JpaRepository<PersistentToken, String> {
|
||||
|
||||
List<PersistentToken> findByUser(User user);
|
||||
|
||||
List<PersistentToken> findByTokenDateBefore(LocalDate localDate);
|
||||
|
||||
}
|
||||
30
src/main/java/mark/quinn/repository/UserRepository.java
Normal file
30
src/main/java/mark/quinn/repository/UserRepository.java
Normal file
@@ -0,0 +1,30 @@
|
||||
package mark.quinn.repository;
|
||||
|
||||
import mark.quinn.domain.User;
|
||||
|
||||
import org.joda.time.DateTime;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Spring Data JPA repository for the User entity.
|
||||
*/
|
||||
public interface UserRepository extends JpaRepository<User, Long> {
|
||||
|
||||
Optional<User> findOneByActivationKey(String activationKey);
|
||||
|
||||
List<User> findAllByActivatedIsFalseAndCreatedDateBefore(DateTime dateTime);
|
||||
|
||||
Optional<User> findOneByResetKey(String resetKey);
|
||||
|
||||
Optional<User> findOneByEmail(String email);
|
||||
|
||||
Optional<User> findOneByLogin(String login);
|
||||
|
||||
@Override
|
||||
void delete(User t);
|
||||
|
||||
}
|
||||
4
src/main/java/mark/quinn/repository/package-info.java
Normal file
4
src/main/java/mark/quinn/repository/package-info.java
Normal file
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* Spring Data JPA repositories.
|
||||
*/
|
||||
package mark.quinn.repository;
|
||||
@@ -0,0 +1,24 @@
|
||||
package mark.quinn.security;
|
||||
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Returns a 401 error code (Unauthorized) to the client, when Ajax authentication fails.
|
||||
*/
|
||||
@Component
|
||||
public class AjaxAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
|
||||
|
||||
@Override
|
||||
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
|
||||
AuthenticationException exception) throws IOException, ServletException {
|
||||
|
||||
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Authentication failed");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package mark.quinn.security;
|
||||
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Spring Security success handler, specialized for Ajax requests.
|
||||
*/
|
||||
@Component
|
||||
public class AjaxAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
|
||||
|
||||
@Override
|
||||
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
|
||||
Authentication authentication)
|
||||
throws IOException, ServletException {
|
||||
|
||||
response.setStatus(HttpServletResponse.SC_OK);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package mark.quinn.security;
|
||||
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.web.authentication.AbstractAuthenticationTargetUrlRequestHandler;
|
||||
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Spring Security logout handler, specialized for Ajax requests.
|
||||
*/
|
||||
@Component
|
||||
public class AjaxLogoutSuccessHandler extends AbstractAuthenticationTargetUrlRequestHandler
|
||||
implements LogoutSuccessHandler {
|
||||
|
||||
@Override
|
||||
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response,
|
||||
Authentication authentication)
|
||||
throws IOException, ServletException {
|
||||
response.setStatus(HttpServletResponse.SC_OK);
|
||||
}
|
||||
}
|
||||
16
src/main/java/mark/quinn/security/AuthoritiesConstants.java
Normal file
16
src/main/java/mark/quinn/security/AuthoritiesConstants.java
Normal file
@@ -0,0 +1,16 @@
|
||||
package mark.quinn.security;
|
||||
|
||||
/**
|
||||
* Constants for Spring Security authorities.
|
||||
*/
|
||||
public final class AuthoritiesConstants {
|
||||
|
||||
private AuthoritiesConstants() {
|
||||
}
|
||||
|
||||
public static final String ADMIN = "ROLE_ADMIN";
|
||||
|
||||
public static final String USER = "ROLE_USER";
|
||||
|
||||
public static final String ANONYMOUS = "ROLE_ANONYMOUS";
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
package mark.quinn.security;
|
||||
|
||||
import mark.quinn.domain.PersistentToken;
|
||||
import mark.quinn.domain.User;
|
||||
import mark.quinn.repository.PersistentTokenRepository;
|
||||
import mark.quinn.repository.UserRepository;
|
||||
import org.joda.time.LocalDate;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.dao.DataAccessException;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
import org.springframework.security.crypto.codec.Base64;
|
||||
import org.springframework.security.web.authentication.rememberme.AbstractRememberMeServices;
|
||||
import org.springframework.security.web.authentication.rememberme.CookieTheftException;
|
||||
import org.springframework.security.web.authentication.rememberme.InvalidCookieException;
|
||||
import org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationException;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Custom implementation of Spring Security's RememberMeServices.
|
||||
* <p/>
|
||||
* Persistent tokens are used by Spring Security to automatically log in users.
|
||||
* <p/>
|
||||
* This is a specific implementation of Spring Security's remember-me authentication, but it is much
|
||||
* more powerful than the standard implementations:
|
||||
* <ul>
|
||||
* <li>It allows a user to see the list of his currently opened sessions, and invalidate them</li>
|
||||
* <li>It stores more information, such as the IP address and the user agent, for audit purposes<li>
|
||||
* <li>When a user logs out, only his current session is invalidated, and not all of his sessions</li>
|
||||
* </ul>
|
||||
* <p/>
|
||||
* This is inspired by:
|
||||
* <ul>
|
||||
* <li><a href="http://jaspan.com/improved_persistent_login_cookie_best_practice">Improved Persistent Login Cookie
|
||||
* Best Practice</a></li>
|
||||
* <li><a href="https://github.com/blog/1661-modeling-your-app-s-user-session">Github's "Modeling your App's User Session"</a></li></li>
|
||||
* </ul>
|
||||
* <p/>
|
||||
* The main algorithm comes from Spring Security's PersistentTokenBasedRememberMeServices, but this class
|
||||
* couldn't be cleanly extended.
|
||||
* <p/>
|
||||
*/
|
||||
@Service
|
||||
public class CustomPersistentRememberMeServices extends
|
||||
AbstractRememberMeServices {
|
||||
|
||||
private final Logger log = LoggerFactory.getLogger(CustomPersistentRememberMeServices.class);
|
||||
|
||||
// Token is valid for one month
|
||||
private static final int TOKEN_VALIDITY_DAYS = 31;
|
||||
|
||||
private static final int TOKEN_VALIDITY_SECONDS = 60 * 60 * 24 * TOKEN_VALIDITY_DAYS;
|
||||
|
||||
private static final int DEFAULT_SERIES_LENGTH = 16;
|
||||
|
||||
private static final int DEFAULT_TOKEN_LENGTH = 16;
|
||||
|
||||
private SecureRandom random;
|
||||
|
||||
@Inject
|
||||
private PersistentTokenRepository persistentTokenRepository;
|
||||
|
||||
@Inject
|
||||
private UserRepository userRepository;
|
||||
|
||||
@Inject
|
||||
public CustomPersistentRememberMeServices(Environment env, org.springframework.security.core.userdetails.UserDetailsService userDetailsService) {
|
||||
super(env.getProperty("jhipster.security.rememberme.key"), userDetailsService);
|
||||
random = new SecureRandom();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
protected UserDetails processAutoLoginCookie(String[] cookieTokens, HttpServletRequest request, HttpServletResponse response) {
|
||||
|
||||
PersistentToken token = getPersistentToken(cookieTokens);
|
||||
String login = token.getUser().getLogin();
|
||||
|
||||
// Token also matches, so login is valid. Update the token value, keeping the *same* series number.
|
||||
log.debug("Refreshing persistent login token for user '{}', series '{}'", login, token.getSeries());
|
||||
token.setTokenDate(new LocalDate());
|
||||
token.setTokenValue(generateTokenData());
|
||||
token.setIpAddress(request.getRemoteAddr());
|
||||
token.setUserAgent(request.getHeader("User-Agent"));
|
||||
try {
|
||||
persistentTokenRepository.saveAndFlush(token);
|
||||
addCookie(token, request, response);
|
||||
} catch (DataAccessException e) {
|
||||
log.error("Failed to update token: ", e);
|
||||
throw new RememberMeAuthenticationException("Autologin failed due to data access problem", e);
|
||||
}
|
||||
return getUserDetailsService().loadUserByUsername(login);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLoginSuccess(HttpServletRequest request, HttpServletResponse response, Authentication successfulAuthentication) {
|
||||
String login = successfulAuthentication.getName();
|
||||
|
||||
log.debug("Creating new persistent login for user {}", login);
|
||||
PersistentToken token = userRepository.findOneByLogin(login).map(u -> {
|
||||
PersistentToken t = new PersistentToken();
|
||||
t.setSeries(generateSeriesData());
|
||||
t.setUser(u);
|
||||
t.setTokenValue(generateTokenData());
|
||||
t.setTokenDate(new LocalDate());
|
||||
t.setIpAddress(request.getRemoteAddr());
|
||||
t.setUserAgent(request.getHeader("User-Agent"));
|
||||
return t;
|
||||
}).orElseThrow(() -> new UsernameNotFoundException("User " + login + " was not found in the database"));
|
||||
try {
|
||||
persistentTokenRepository.saveAndFlush(token);
|
||||
addCookie(token, request, response);
|
||||
} catch (DataAccessException e) {
|
||||
log.error("Failed to save persistent token ", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* When logout occurs, only invalidate the current token, and not all user sessions.
|
||||
* <p/>
|
||||
* The standard Spring Security implementations are too basic: they invalidate all tokens for the
|
||||
* current user, so when he logs out from one browser, all his other sessions are destroyed.
|
||||
*/
|
||||
@Override
|
||||
@Transactional
|
||||
public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
|
||||
String rememberMeCookie = extractRememberMeCookie(request);
|
||||
if (rememberMeCookie != null && rememberMeCookie.length() != 0) {
|
||||
try {
|
||||
String[] cookieTokens = decodeCookie(rememberMeCookie);
|
||||
PersistentToken token = getPersistentToken(cookieTokens);
|
||||
persistentTokenRepository.delete(token);
|
||||
} catch (InvalidCookieException ice) {
|
||||
log.info("Invalid cookie, no persistent token could be deleted");
|
||||
} catch (RememberMeAuthenticationException rmae) {
|
||||
log.debug("No persistent token found, so no token could be deleted");
|
||||
}
|
||||
}
|
||||
super.logout(request, response, authentication);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the token and return it.
|
||||
*/
|
||||
private PersistentToken getPersistentToken(String[] cookieTokens) {
|
||||
if (cookieTokens.length != 2) {
|
||||
throw new InvalidCookieException("Cookie token did not contain " + 2 +
|
||||
" tokens, but contained '" + Arrays.asList(cookieTokens) + "'");
|
||||
}
|
||||
String presentedSeries = cookieTokens[0];
|
||||
String presentedToken = cookieTokens[1];
|
||||
PersistentToken token = persistentTokenRepository.findOne(presentedSeries);
|
||||
|
||||
if (token == null) {
|
||||
// No series match, so we can't authenticate using this cookie
|
||||
throw new RememberMeAuthenticationException("No persistent token found for series id: " + presentedSeries);
|
||||
}
|
||||
|
||||
// We have a match for this user/series combination
|
||||
log.info("presentedToken={} / tokenValue={}", presentedToken, token.getTokenValue());
|
||||
if (!presentedToken.equals(token.getTokenValue())) {
|
||||
// Token doesn't match series value. Delete this session and throw an exception.
|
||||
persistentTokenRepository.delete(token);
|
||||
throw new CookieTheftException("Invalid remember-me token (Series/token) mismatch. Implies previous cookie theft attack.");
|
||||
}
|
||||
|
||||
if (token.getTokenDate().plusDays(TOKEN_VALIDITY_DAYS).isBefore(LocalDate.now())) {
|
||||
persistentTokenRepository.delete(token);
|
||||
throw new RememberMeAuthenticationException("Remember-me login has expired");
|
||||
}
|
||||
return token;
|
||||
}
|
||||
|
||||
private String generateSeriesData() {
|
||||
byte[] newSeries = new byte[DEFAULT_SERIES_LENGTH];
|
||||
random.nextBytes(newSeries);
|
||||
return new String(Base64.encode(newSeries));
|
||||
}
|
||||
|
||||
private String generateTokenData() {
|
||||
byte[] newToken = new byte[DEFAULT_TOKEN_LENGTH];
|
||||
random.nextBytes(newToken);
|
||||
return new String(Base64.encode(newToken));
|
||||
}
|
||||
|
||||
private void addCookie(PersistentToken token, HttpServletRequest request, HttpServletResponse response) {
|
||||
setCookie(
|
||||
new String[]{token.getSeries(), token.getTokenValue()},
|
||||
TOKEN_VALIDITY_SECONDS, request, response);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package mark.quinn.security;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.web.AuthenticationEntryPoint;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Returns a 401 error code (Unauthorized) to the client.
|
||||
*/
|
||||
@Component
|
||||
public class Http401UnauthorizedEntryPoint implements AuthenticationEntryPoint {
|
||||
|
||||
private final Logger log = LoggerFactory.getLogger(Http401UnauthorizedEntryPoint.class);
|
||||
|
||||
/**
|
||||
* Always returns a 401 error code to the client.
|
||||
*/
|
||||
@Override
|
||||
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException arg2) throws IOException,
|
||||
ServletException {
|
||||
|
||||
log.debug("Pre-authenticated entry point called. Rejecting access");
|
||||
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Access Denied");
|
||||
}
|
||||
}
|
||||
72
src/main/java/mark/quinn/security/SecurityUtils.java
Normal file
72
src/main/java/mark/quinn/security/SecurityUtils.java
Normal file
@@ -0,0 +1,72 @@
|
||||
package mark.quinn.security;
|
||||
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.security.core.context.SecurityContext;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* Utility class for Spring Security.
|
||||
*/
|
||||
public final class SecurityUtils {
|
||||
|
||||
private SecurityUtils() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the login of the current user.
|
||||
*/
|
||||
public static String getCurrentLogin() {
|
||||
SecurityContext securityContext = SecurityContextHolder.getContext();
|
||||
Authentication authentication = securityContext.getAuthentication();
|
||||
UserDetails springSecurityUser = null;
|
||||
String userName = null;
|
||||
if(authentication != null) {
|
||||
if (authentication.getPrincipal() instanceof UserDetails) {
|
||||
springSecurityUser = (UserDetails) authentication.getPrincipal();
|
||||
userName = springSecurityUser.getUsername();
|
||||
} else if (authentication.getPrincipal() instanceof String) {
|
||||
userName = (String) authentication.getPrincipal();
|
||||
}
|
||||
}
|
||||
return userName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a user is authenticated.
|
||||
*
|
||||
* @return true if the user is authenticated, false otherwise
|
||||
*/
|
||||
public static boolean isAuthenticated() {
|
||||
SecurityContext securityContext = SecurityContextHolder.getContext();
|
||||
Collection<? extends GrantedAuthority> authorities = securityContext.getAuthentication().getAuthorities();
|
||||
if (authorities != null) {
|
||||
for (GrantedAuthority authority : authorities) {
|
||||
if (authority.getAuthority().equals(AuthoritiesConstants.ANONYMOUS)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* If the current user has a specific security role.
|
||||
*/
|
||||
public static boolean isUserInRole(String role) {
|
||||
SecurityContext securityContext = SecurityContextHolder.getContext();
|
||||
Authentication authentication = securityContext.getAuthentication();
|
||||
if(authentication != null) {
|
||||
if (authentication.getPrincipal() instanceof UserDetails) {
|
||||
UserDetails springSecurityUser = (UserDetails) authentication.getPrincipal();
|
||||
return springSecurityUser.getAuthorities().contains(new SimpleGrantedAuthority(role));
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package mark.quinn.security;
|
||||
|
||||
import mark.quinn.config.Constants;
|
||||
import org.springframework.data.domain.AuditorAware;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* Implementation of AuditorAware based on Spring Security.
|
||||
*/
|
||||
@Component
|
||||
public class SpringSecurityAuditorAware implements AuditorAware<String> {
|
||||
|
||||
@Override
|
||||
public String getCurrentAuditor() {
|
||||
String userName = SecurityUtils.getCurrentLogin();
|
||||
return (userName != null ? userName : Constants.SYSTEM_ACCOUNT);
|
||||
}
|
||||
}
|
||||
52
src/main/java/mark/quinn/security/UserDetailsService.java
Normal file
52
src/main/java/mark/quinn/security/UserDetailsService.java
Normal file
@@ -0,0 +1,52 @@
|
||||
package mark.quinn.security;
|
||||
|
||||
import mark.quinn.domain.Authority;
|
||||
import mark.quinn.domain.User;
|
||||
import mark.quinn.repository.UserRepository;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Authenticate a user from the database.
|
||||
*/
|
||||
@Component("userDetailsService")
|
||||
public class UserDetailsService implements org.springframework.security.core.userdetails.UserDetailsService {
|
||||
|
||||
private final Logger log = LoggerFactory.getLogger(UserDetailsService.class);
|
||||
|
||||
@Inject
|
||||
private UserRepository userRepository;
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public UserDetails loadUserByUsername(final String login) {
|
||||
log.debug("Authenticating {}", login);
|
||||
String lowercaseLogin = login.toLowerCase();
|
||||
Optional<User> userFromDatabase = userRepository.findOneByLogin(lowercaseLogin);
|
||||
return userFromDatabase.map(user -> {
|
||||
if (!user.getActivated()) {
|
||||
throw new UserNotActivatedException("User " + lowercaseLogin + " was not activated");
|
||||
}
|
||||
List<GrantedAuthority> grantedAuthorities = user.getAuthorities().stream()
|
||||
.map(authority -> new SimpleGrantedAuthority(authority.getName()))
|
||||
.collect(Collectors.toList());
|
||||
return new org.springframework.security.core.userdetails.User(lowercaseLogin,
|
||||
user.getPassword(),
|
||||
grantedAuthorities);
|
||||
}).orElseThrow(() -> new UsernameNotFoundException("User " + lowercaseLogin + " was not found in the database"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package mark.quinn.security;
|
||||
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
|
||||
/**
|
||||
* This exception is throw in case of a not activated user trying to authenticate.
|
||||
*/
|
||||
public class UserNotActivatedException extends AuthenticationException {
|
||||
|
||||
public UserNotActivatedException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public UserNotActivatedException(String message, Throwable t) {
|
||||
super(message, t);
|
||||
}
|
||||
}
|
||||
4
src/main/java/mark/quinn/security/package-info.java
Normal file
4
src/main/java/mark/quinn/security/package-info.java
Normal file
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* Spring Security configuration.
|
||||
*/
|
||||
package mark.quinn.security;
|
||||
41
src/main/java/mark/quinn/service/AuditEventService.java
Normal file
41
src/main/java/mark/quinn/service/AuditEventService.java
Normal file
@@ -0,0 +1,41 @@
|
||||
package mark.quinn.service;
|
||||
|
||||
import mark.quinn.config.audit.AuditEventConverter;
|
||||
import mark.quinn.domain.PersistentAuditEvent;
|
||||
import mark.quinn.repository.PersistenceAuditEventRepository;
|
||||
import org.joda.time.LocalDateTime;
|
||||
import org.springframework.boot.actuate.audit.AuditEvent;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Service for managing audit events.
|
||||
* <p/>
|
||||
* <p>
|
||||
* This is the default implementation to support SpringBoot Actuator AuditEventRepository
|
||||
* </p>
|
||||
*/
|
||||
@Service
|
||||
@Transactional
|
||||
public class AuditEventService {
|
||||
|
||||
@Inject
|
||||
private PersistenceAuditEventRepository persistenceAuditEventRepository;
|
||||
|
||||
@Inject
|
||||
private AuditEventConverter auditEventConverter;
|
||||
|
||||
public List<AuditEvent> findAll() {
|
||||
return auditEventConverter.convertToAuditEvent(persistenceAuditEventRepository.findAll());
|
||||
}
|
||||
|
||||
public List<AuditEvent> findByDates(LocalDateTime fromDate, LocalDateTime toDate) {
|
||||
List<PersistentAuditEvent> persistentAuditEvents =
|
||||
persistenceAuditEventRepository.findAllByAuditEventDateBetween(fromDate, toDate);
|
||||
|
||||
return auditEventConverter.convertToAuditEvent(persistentAuditEvents);
|
||||
}
|
||||
}
|
||||
98
src/main/java/mark/quinn/service/MailService.java
Normal file
98
src/main/java/mark/quinn/service/MailService.java
Normal file
@@ -0,0 +1,98 @@
|
||||
package mark.quinn.service;
|
||||
|
||||
import mark.quinn.domain.User;
|
||||
import org.apache.commons.lang.CharEncoding;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.context.MessageSource;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.mail.javamail.JavaMailSenderImpl;
|
||||
import org.springframework.mail.javamail.MimeMessageHelper;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.thymeleaf.context.Context;
|
||||
import org.thymeleaf.spring4.SpringTemplateEngine;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.inject.Inject;
|
||||
import javax.mail.internet.MimeMessage;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Service for sending e-mails.
|
||||
* <p/>
|
||||
* <p>
|
||||
* We use the @Async annotation to send e-mails asynchronously.
|
||||
* </p>
|
||||
*/
|
||||
@Service
|
||||
public class MailService {
|
||||
|
||||
private final Logger log = LoggerFactory.getLogger(MailService.class);
|
||||
|
||||
@Inject
|
||||
private Environment env;
|
||||
|
||||
@Inject
|
||||
private JavaMailSenderImpl javaMailSender;
|
||||
|
||||
@Inject
|
||||
private MessageSource messageSource;
|
||||
|
||||
@Inject
|
||||
private SpringTemplateEngine templateEngine;
|
||||
|
||||
/**
|
||||
* System default email address that sends the e-mails.
|
||||
*/
|
||||
private String from;
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
this.from = env.getProperty("mail.from");
|
||||
}
|
||||
|
||||
@Async
|
||||
public void sendEmail(String to, String subject, String content, boolean isMultipart, boolean isHtml) {
|
||||
log.debug("Send e-mail[multipart '{}' and html '{}'] to '{}' with subject '{}' and content={}",
|
||||
isMultipart, isHtml, to, subject, content);
|
||||
|
||||
// Prepare message using a Spring helper
|
||||
MimeMessage mimeMessage = javaMailSender.createMimeMessage();
|
||||
try {
|
||||
MimeMessageHelper message = new MimeMessageHelper(mimeMessage, isMultipart, CharEncoding.UTF_8);
|
||||
message.setTo(to);
|
||||
message.setFrom(from);
|
||||
message.setSubject(subject);
|
||||
message.setText(content, isHtml);
|
||||
javaMailSender.send(mimeMessage);
|
||||
log.debug("Sent e-mail to User '{}'", to);
|
||||
} catch (Exception e) {
|
||||
log.warn("E-mail could not be sent to user '{}', exception is: {}", to, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Async
|
||||
public void sendActivationEmail(User user, String baseUrl) {
|
||||
log.debug("Sending activation e-mail to '{}'", user.getEmail());
|
||||
Locale locale = Locale.forLanguageTag(user.getLangKey());
|
||||
Context context = new Context(locale);
|
||||
context.setVariable("user", user);
|
||||
context.setVariable("baseUrl", baseUrl);
|
||||
String content = templateEngine.process("activationEmail", context);
|
||||
String subject = messageSource.getMessage("email.activation.title", null, locale);
|
||||
sendEmail(user.getEmail(), subject, content, false, true);
|
||||
}
|
||||
|
||||
@Async
|
||||
public void sendPasswordResetMail(User user, String baseUrl) {
|
||||
log.debug("Sending password reset e-mail to '{}'", user.getEmail());
|
||||
Locale locale = Locale.forLanguageTag(user.getLangKey());
|
||||
Context context = new Context(locale);
|
||||
context.setVariable("user", user);
|
||||
context.setVariable("baseUrl", baseUrl);
|
||||
String content = templateEngine.process("passwordResetEmail", context);
|
||||
String subject = messageSource.getMessage("email.reset.title", null, locale);
|
||||
sendEmail(user.getEmail(), subject, content, false, true);
|
||||
}
|
||||
}
|
||||
176
src/main/java/mark/quinn/service/UserService.java
Normal file
176
src/main/java/mark/quinn/service/UserService.java
Normal file
@@ -0,0 +1,176 @@
|
||||
package mark.quinn.service;
|
||||
|
||||
import mark.quinn.domain.Authority;
|
||||
import mark.quinn.domain.PersistentToken;
|
||||
import mark.quinn.domain.User;
|
||||
import mark.quinn.repository.AuthorityRepository;
|
||||
import mark.quinn.repository.PersistentTokenRepository;
|
||||
import mark.quinn.repository.UserRepository;
|
||||
import mark.quinn.security.SecurityUtils;
|
||||
import mark.quinn.service.util.RandomUtil;
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.LocalDate;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Service class for managing users.
|
||||
*/
|
||||
@Service
|
||||
@Transactional
|
||||
public class UserService {
|
||||
|
||||
private final Logger log = LoggerFactory.getLogger(UserService.class);
|
||||
|
||||
@Inject
|
||||
private PasswordEncoder passwordEncoder;
|
||||
|
||||
@Inject
|
||||
private UserRepository userRepository;
|
||||
|
||||
@Inject
|
||||
private PersistentTokenRepository persistentTokenRepository;
|
||||
|
||||
@Inject
|
||||
private AuthorityRepository authorityRepository;
|
||||
|
||||
public Optional<User> activateRegistration(String key) {
|
||||
log.debug("Activating user for activation key {}", key);
|
||||
userRepository.findOneByActivationKey(key)
|
||||
.map(user -> {
|
||||
// activate given user for the registration key.
|
||||
user.setActivated(true);
|
||||
user.setActivationKey(null);
|
||||
userRepository.save(user);
|
||||
log.debug("Activated user: {}", user);
|
||||
return user;
|
||||
});
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
public Optional<User> completePasswordReset(String newPassword, String key) {
|
||||
log.debug("Reset user password for reset key {}", key);
|
||||
|
||||
return userRepository.findOneByResetKey(key)
|
||||
.filter(user -> {
|
||||
DateTime oneDayAgo = DateTime.now().minusHours(24);
|
||||
return user.getResetDate().isAfter(oneDayAgo.toInstant().getMillis());
|
||||
})
|
||||
.map(user -> {
|
||||
user.setPassword(passwordEncoder.encode(newPassword));
|
||||
user.setResetKey(null);
|
||||
user.setResetDate(null);
|
||||
userRepository.save(user);
|
||||
return user;
|
||||
});
|
||||
}
|
||||
|
||||
public Optional<User> requestPasswordReset(String mail) {
|
||||
return userRepository.findOneByEmail(mail)
|
||||
.filter(user -> user.getActivated() == true)
|
||||
.map(user -> {
|
||||
user.setResetKey(RandomUtil.generateResetKey());
|
||||
user.setResetDate(DateTime.now());
|
||||
userRepository.save(user);
|
||||
return user;
|
||||
});
|
||||
}
|
||||
|
||||
public User createUserInformation(String login, String password, String firstName, String lastName, String email,
|
||||
String langKey) {
|
||||
|
||||
User newUser = new User();
|
||||
Authority authority = authorityRepository.findOne("ROLE_USER");
|
||||
Set<Authority> authorities = new HashSet<>();
|
||||
String encryptedPassword = passwordEncoder.encode(password);
|
||||
newUser.setLogin(login);
|
||||
// new user gets initially a generated password
|
||||
newUser.setPassword(encryptedPassword);
|
||||
newUser.setFirstName(firstName);
|
||||
newUser.setLastName(lastName);
|
||||
newUser.setEmail(email);
|
||||
newUser.setLangKey(langKey);
|
||||
// new user is not active
|
||||
newUser.setActivated(false);
|
||||
// new user gets registration key
|
||||
newUser.setActivationKey(RandomUtil.generateActivationKey());
|
||||
authorities.add(authority);
|
||||
newUser.setAuthorities(authorities);
|
||||
userRepository.save(newUser);
|
||||
log.debug("Created Information for User: {}", newUser);
|
||||
return newUser;
|
||||
}
|
||||
|
||||
public void updateUserInformation(String firstName, String lastName, String email, String langKey) {
|
||||
userRepository.findOneByLogin(SecurityUtils.getCurrentLogin()).ifPresent(u -> {
|
||||
u.setFirstName(firstName);
|
||||
u.setLastName(lastName);
|
||||
u.setEmail(email);
|
||||
u.setLangKey(langKey);
|
||||
userRepository.save(u);
|
||||
log.debug("Changed Information for User: {}", u);
|
||||
});
|
||||
}
|
||||
|
||||
public void changePassword(String password) {
|
||||
userRepository.findOneByLogin(SecurityUtils.getCurrentLogin()).ifPresent(u-> {
|
||||
String encryptedPassword = passwordEncoder.encode(password);
|
||||
u.setPassword(encryptedPassword);
|
||||
userRepository.save(u);
|
||||
log.debug("Changed password for User: {}", u);
|
||||
});
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public User getUserWithAuthorities() {
|
||||
User currentUser = userRepository.findOneByLogin(SecurityUtils.getCurrentLogin()).get();
|
||||
currentUser.getAuthorities().size(); // eagerly load the association
|
||||
return currentUser;
|
||||
}
|
||||
|
||||
/**
|
||||
* Persistent Token are used for providing automatic authentication, they should be automatically deleted after
|
||||
* 30 days.
|
||||
* <p/>
|
||||
* <p>
|
||||
* This is scheduled to get fired everyday, at midnight.
|
||||
* </p>
|
||||
*/
|
||||
@Scheduled(cron = "0 0 0 * * ?")
|
||||
public void removeOldPersistentTokens() {
|
||||
LocalDate now = new LocalDate();
|
||||
persistentTokenRepository.findByTokenDateBefore(now.minusMonths(1)).stream().forEach(token ->{
|
||||
log.debug("Deleting token {}", token.getSeries());
|
||||
User user = token.getUser();
|
||||
user.getPersistentTokens().remove(token);
|
||||
persistentTokenRepository.delete(token);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Not activated users should be automatically deleted after 3 days.
|
||||
* <p/>
|
||||
* <p>
|
||||
* This is scheduled to get fired everyday, at 01:00 (am).
|
||||
* </p>
|
||||
*/
|
||||
@Scheduled(cron = "0 0 1 * * ?")
|
||||
public void removeNotActivatedUsers() {
|
||||
DateTime now = new DateTime();
|
||||
List<User> users = userRepository.findAllByActivatedIsFalseAndCreatedDateBefore(now.minusDays(3));
|
||||
for (User user : users) {
|
||||
log.debug("Deleting not activated user {}", user.getLogin());
|
||||
userRepository.delete(user);
|
||||
}
|
||||
}
|
||||
}
|
||||
4
src/main/java/mark/quinn/service/package-info.java
Normal file
4
src/main/java/mark/quinn/service/package-info.java
Normal file
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* Service layer beans.
|
||||
*/
|
||||
package mark.quinn.service;
|
||||
41
src/main/java/mark/quinn/service/util/RandomUtil.java
Normal file
41
src/main/java/mark/quinn/service/util/RandomUtil.java
Normal file
@@ -0,0 +1,41 @@
|
||||
package mark.quinn.service.util;
|
||||
|
||||
import org.apache.commons.lang.RandomStringUtils;
|
||||
|
||||
/**
|
||||
* Utility class for generating random Strings.
|
||||
*/
|
||||
public final class RandomUtil {
|
||||
|
||||
private static final int DEF_COUNT = 20;
|
||||
|
||||
private RandomUtil() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a password.
|
||||
*
|
||||
* @return the generated password
|
||||
*/
|
||||
public static String generatePassword() {
|
||||
return RandomStringUtils.randomAlphanumeric(DEF_COUNT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an activation key.
|
||||
*
|
||||
* @return the generated activation key
|
||||
*/
|
||||
public static String generateActivationKey() {
|
||||
return RandomStringUtils.randomNumeric(DEF_COUNT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a reset key.
|
||||
*
|
||||
* @return the generated reset key
|
||||
*/
|
||||
public static String generateResetKey() {
|
||||
return RandomStringUtils.randomNumeric(DEF_COUNT);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package mark.quinn.web.filter;
|
||||
|
||||
import org.springframework.core.env.Environment;
|
||||
|
||||
import javax.servlet.*;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* This filter is used in production, to put HTTP cache headers with a long (1 month) expiration time.
|
||||
* </p>
|
||||
*/
|
||||
public class CachingHttpHeadersFilter implements Filter {
|
||||
|
||||
// We consider the last modified date is the start up time of the server
|
||||
private final static long LAST_MODIFIED = System.currentTimeMillis();
|
||||
|
||||
private long CACHE_TIME_TO_LIVE = TimeUnit.DAYS.toMillis(31L);
|
||||
|
||||
private Environment env;
|
||||
|
||||
public CachingHttpHeadersFilter(Environment env) {
|
||||
this.env = env;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(FilterConfig filterConfig) throws ServletException {
|
||||
CACHE_TIME_TO_LIVE = TimeUnit.DAYS.toMillis(env.getProperty("http.cache.timeToLiveInDays", Long.class, 31L));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
// Nothing to destroy
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
|
||||
HttpServletResponse httpResponse = (HttpServletResponse) response;
|
||||
|
||||
httpResponse.setHeader("Cache-Control", "max-age=" + CACHE_TIME_TO_LIVE + ", public");
|
||||
httpResponse.setHeader("Pragma", "cache");
|
||||
|
||||
// Setting Expires header, for proxy caching
|
||||
httpResponse.setDateHeader("Expires", CACHE_TIME_TO_LIVE + System.currentTimeMillis());
|
||||
|
||||
// Setting the Last-Modified header, for browser caching
|
||||
httpResponse.setDateHeader("Last-Modified", LAST_MODIFIED);
|
||||
|
||||
chain.doFilter(request, response);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package mark.quinn.web.filter;
|
||||
|
||||
import org.springframework.security.web.csrf.CsrfToken;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.Cookie;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Filter used to put the CSRF token generated by Spring Security in a cookie for use by AngularJS.
|
||||
*/
|
||||
public class CsrfCookieGeneratorFilter extends OncePerRequestFilter {
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
|
||||
// Spring put the CSRF token in session attribute "_csrf"
|
||||
CsrfToken csrfToken = (CsrfToken) request.getAttribute("_csrf");
|
||||
|
||||
// Send the cookie only if the token has changed
|
||||
String actualToken = request.getHeader("X-CSRF-TOKEN");
|
||||
if (actualToken == null || !actualToken.equals(csrfToken.getToken())) {
|
||||
// Session cookie that will be used by AngularJS
|
||||
String pCookieName = "CSRF-TOKEN";
|
||||
Cookie cookie = new Cookie(pCookieName, csrfToken.getToken());
|
||||
cookie.setMaxAge(-1);
|
||||
cookie.setHttpOnly(false);
|
||||
cookie.setPath("/");
|
||||
response.addCookie(cookie);
|
||||
}
|
||||
filterChain.doFilter(request, response);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package mark.quinn.web.filter;
|
||||
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
|
||||
import javax.servlet.*;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* This filter is used in production, to serve static resources generated by "grunt build".
|
||||
* <p/>
|
||||
* <p>
|
||||
* It is configured to serve resources from the "dist" directory, which is the Grunt
|
||||
* destination directory.
|
||||
* </p>
|
||||
*/
|
||||
public class StaticResourcesProductionFilter implements Filter {
|
||||
|
||||
@Override
|
||||
public void init(FilterConfig filterConfig) throws ServletException {
|
||||
// Nothing to initialize
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
// Nothing to destroy
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
|
||||
HttpServletRequest httpRequest = (HttpServletRequest) request;
|
||||
String contextPath = ((HttpServletRequest) request).getContextPath();
|
||||
String requestURI = httpRequest.getRequestURI();
|
||||
requestURI = StringUtils.substringAfter(requestURI, contextPath);
|
||||
if (StringUtils.equals("/", requestURI)) {
|
||||
requestURI = "/index.html";
|
||||
}
|
||||
String newURI = "/dist" + requestURI;
|
||||
request.getRequestDispatcher(newURI).forward(request, response);
|
||||
}
|
||||
}
|
||||
107
src/main/java/mark/quinn/web/filter/gzip/GZipResponseUtil.java
Normal file
107
src/main/java/mark/quinn/web/filter/gzip/GZipResponseUtil.java
Normal file
@@ -0,0 +1,107 @@
|
||||
package mark.quinn.web.filter.gzip;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
public final class GZipResponseUtil {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(GZipResponseUtil.class);
|
||||
|
||||
/**
|
||||
* Gzipping an empty file or stream always results in a 20 byte output
|
||||
* This is in java or elsewhere.
|
||||
* <p/>
|
||||
* On a unix system to reproduce do <code>gzip -n empty_file</code>. -n tells gzip to not
|
||||
* include the file name. The resulting file size is 20 bytes.
|
||||
* <p/>
|
||||
* Therefore 20 bytes can be used indicate that the gzip byte[] will be empty when ungzipped.
|
||||
*/
|
||||
private static final int EMPTY_GZIPPED_CONTENT_SIZE = 20;
|
||||
|
||||
/**
|
||||
* Utility class. No public constructor.
|
||||
*/
|
||||
private GZipResponseUtil() {
|
||||
// noop
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a gzipped body is actually empty and should just be zero.
|
||||
* When the compressedBytes is {@link #EMPTY_GZIPPED_CONTENT_SIZE} it should be zero.
|
||||
*
|
||||
* @param compressedBytes the gzipped response body
|
||||
* @param request the client HTTP request
|
||||
* @return true if the response should be 0, even if it is isn't.
|
||||
*/
|
||||
public static boolean shouldGzippedBodyBeZero(byte[] compressedBytes, HttpServletRequest request) {
|
||||
|
||||
//Check for 0 length body
|
||||
if (compressedBytes.length == EMPTY_GZIPPED_CONTENT_SIZE) {
|
||||
if (LOG.isTraceEnabled()) {
|
||||
LOG.trace("{} resulted in an empty response.", request.getRequestURL());
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a number of checks to ensure response saneness according to the rules of RFC2616:
|
||||
* <ol>
|
||||
* <li>If the response code is {@link javax.servlet.http.HttpServletResponse#SC_NO_CONTENT} then it is illegal for the body
|
||||
* to contain anything. See http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.2.5
|
||||
* <li>If the response code is {@link javax.servlet.http.HttpServletResponse#SC_NOT_MODIFIED} then it is illegal for the body
|
||||
* to contain anything. See http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.5
|
||||
* </ol>
|
||||
*
|
||||
* @param request the client HTTP request
|
||||
* @param responseStatus the responseStatus
|
||||
* @return true if the response should be 0, even if it is isn't.
|
||||
*/
|
||||
public static boolean shouldBodyBeZero(HttpServletRequest request, int responseStatus) {
|
||||
|
||||
//Check for NO_CONTENT
|
||||
if (responseStatus == HttpServletResponse.SC_NO_CONTENT) {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("{} resulted in a {} response. Removing message body in accordance with RFC2616.",
|
||||
request.getRequestURL(), HttpServletResponse.SC_NO_CONTENT);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
//Check for NOT_MODIFIED
|
||||
if (responseStatus == HttpServletResponse.SC_NOT_MODIFIED) {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("{} resulted in a {} response. Removing message body in accordance with RFC2616.",
|
||||
request.getRequestURL(), HttpServletResponse.SC_NOT_MODIFIED);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the gzip HTTP header to the response.
|
||||
* <p/>
|
||||
* <p>
|
||||
* This is need when a gzipped body is returned so that browsers can properly decompress it.
|
||||
* </p>
|
||||
*
|
||||
* @param response the response which will have a header added to it. I.e this method changes its parameter
|
||||
* @throws GzipResponseHeadersNotModifiableException Either the response is committed or we were called using the include method
|
||||
* from a {@link javax.servlet.RequestDispatcher#include(javax.servlet.ServletRequest, javax.servlet.ServletResponse)}
|
||||
* method and the set header is ignored.
|
||||
*/
|
||||
public static void addGzipHeader(HttpServletResponse response) throws GzipResponseHeadersNotModifiableException {
|
||||
response.setHeader("Content-Encoding", "gzip");
|
||||
boolean containsEncoding = response.containsHeader("Content-Encoding");
|
||||
if (!containsEncoding) {
|
||||
throw new GzipResponseHeadersNotModifiableException("Failure when attempting to set "
|
||||
+ "Content-Encoding: gzip");
|
||||
}
|
||||
}
|
||||
}
|
||||
111
src/main/java/mark/quinn/web/filter/gzip/GZipServletFilter.java
Normal file
111
src/main/java/mark/quinn/web/filter/gzip/GZipServletFilter.java
Normal file
@@ -0,0 +1,111 @@
|
||||
package mark.quinn.web.filter.gzip;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.servlet.*;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.zip.GZIPOutputStream;
|
||||
|
||||
public class GZipServletFilter implements Filter {
|
||||
|
||||
private Logger log = LoggerFactory.getLogger(GZipServletFilter.class);
|
||||
|
||||
@Override
|
||||
public void init(FilterConfig filterConfig) throws ServletException {
|
||||
// Nothing to initialize
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
// Nothing to destroy
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
|
||||
|
||||
HttpServletRequest httpRequest = (HttpServletRequest) request;
|
||||
HttpServletResponse httpResponse = (HttpServletResponse) response;
|
||||
|
||||
if (!isIncluded(httpRequest) && acceptsGZipEncoding(httpRequest) && !response.isCommitted()) {
|
||||
// Client accepts zipped content
|
||||
if (log.isTraceEnabled()) {
|
||||
log.trace("{} Written with gzip compression", httpRequest.getRequestURL());
|
||||
}
|
||||
|
||||
// Create a gzip stream
|
||||
final ByteArrayOutputStream compressed = new ByteArrayOutputStream();
|
||||
final GZIPOutputStream gzout = new GZIPOutputStream(compressed);
|
||||
|
||||
// Handle the request
|
||||
final GZipServletResponseWrapper wrapper = new GZipServletResponseWrapper(httpResponse, gzout);
|
||||
wrapper.setDisableFlushBuffer(true);
|
||||
chain.doFilter(request, wrapper);
|
||||
wrapper.flush();
|
||||
|
||||
gzout.close();
|
||||
|
||||
// double check one more time before writing out
|
||||
// repsonse might have been committed due to error
|
||||
if (response.isCommitted()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// return on these special cases when content is empty or unchanged
|
||||
switch (wrapper.getStatus()) {
|
||||
case HttpServletResponse.SC_NO_CONTENT:
|
||||
case HttpServletResponse.SC_RESET_CONTENT:
|
||||
case HttpServletResponse.SC_NOT_MODIFIED:
|
||||
return;
|
||||
default:
|
||||
}
|
||||
|
||||
// Saneness checks
|
||||
byte[] compressedBytes = compressed.toByteArray();
|
||||
boolean shouldGzippedBodyBeZero = GZipResponseUtil.shouldGzippedBodyBeZero(compressedBytes, httpRequest);
|
||||
boolean shouldBodyBeZero = GZipResponseUtil.shouldBodyBeZero(httpRequest, wrapper.getStatus());
|
||||
if (shouldGzippedBodyBeZero || shouldBodyBeZero) {
|
||||
// No reason to add GZIP headers or write body if no content was written or status code specifies no
|
||||
// content
|
||||
response.setContentLength(0);
|
||||
return;
|
||||
}
|
||||
|
||||
// Write the zipped body
|
||||
GZipResponseUtil.addGzipHeader(httpResponse);
|
||||
|
||||
response.setContentLength(compressedBytes.length);
|
||||
|
||||
response.getOutputStream().write(compressedBytes);
|
||||
|
||||
} else {
|
||||
// Client does not accept zipped content - don't bother zipping
|
||||
if (log.isTraceEnabled()) {
|
||||
log.trace("{} Written without gzip compression because the request does not accept gzip", httpRequest.getRequestURL());
|
||||
}
|
||||
chain.doFilter(request, response);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the request uri is an include. These cannot be gzipped.
|
||||
*/
|
||||
private boolean isIncluded(final HttpServletRequest request) {
|
||||
String uri = (String) request.getAttribute("javax.servlet.include.request_uri");
|
||||
boolean includeRequest = !(uri == null);
|
||||
if (includeRequest && log.isDebugEnabled()) {
|
||||
log.debug("{} resulted in an include request. This is unusable, because"
|
||||
+ "the response will be assembled into the overrall response. Not gzipping.",
|
||||
request.getRequestURL());
|
||||
}
|
||||
return includeRequest;
|
||||
}
|
||||
|
||||
private boolean acceptsGZipEncoding(HttpServletRequest httpRequest) {
|
||||
String acceptEncoding = httpRequest.getHeader("Accept-Encoding");
|
||||
return acceptEncoding != null && acceptEncoding.contains("gzip");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package mark.quinn.web.filter.gzip;
|
||||
|
||||
import javax.servlet.ServletOutputStream;
|
||||
import javax.servlet.WriteListener;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
class GZipServletOutputStream extends ServletOutputStream {
|
||||
private OutputStream stream;
|
||||
|
||||
public GZipServletOutputStream(OutputStream output)
|
||||
throws IOException {
|
||||
super();
|
||||
this.stream = output;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
this.stream.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flush() throws IOException {
|
||||
this.stream.flush();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(byte b[]) throws IOException {
|
||||
this.stream.write(b);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(byte b[], int off, int len) throws IOException {
|
||||
this.stream.write(b, off, len);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(int b) throws IOException {
|
||||
this.stream.write(b);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isReady() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setWriteListener(WriteListener listener) {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
package mark.quinn.web.filter.gzip;
|
||||
|
||||
import javax.servlet.ServletOutputStream;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.http.HttpServletResponseWrapper;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.zip.GZIPOutputStream;
|
||||
|
||||
class GZipServletResponseWrapper extends HttpServletResponseWrapper {
|
||||
|
||||
private GZipServletOutputStream gzipOutputStream = null;
|
||||
private PrintWriter printWriter = null;
|
||||
private boolean disableFlushBuffer = false;
|
||||
|
||||
public GZipServletResponseWrapper(HttpServletResponse response, GZIPOutputStream gzout)
|
||||
throws IOException {
|
||||
super(response);
|
||||
gzipOutputStream = new GZipServletOutputStream(gzout);
|
||||
}
|
||||
|
||||
public void close() throws IOException {
|
||||
|
||||
//PrintWriter.close does not throw exceptions. Thus, the call does not need
|
||||
//be inside a try-catch block.
|
||||
if (this.printWriter != null) {
|
||||
this.printWriter.close();
|
||||
}
|
||||
|
||||
if (this.gzipOutputStream != null) {
|
||||
this.gzipOutputStream.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Flush OutputStream or PrintWriter
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
@Override
|
||||
public void flushBuffer() throws IOException {
|
||||
|
||||
//PrintWriter.flush() does not throw exception
|
||||
if (this.printWriter != null) {
|
||||
this.printWriter.flush();
|
||||
}
|
||||
|
||||
if (this.gzipOutputStream != null) {
|
||||
this.gzipOutputStream.flush();
|
||||
}
|
||||
|
||||
// doing this might leads to response already committed exception
|
||||
// when the PageInfo has not yet built but the buffer already flushed
|
||||
// Happens in Weblogic when a servlet forward to a JSP page and the forward
|
||||
// method trigger a flush before it forwarded to the JSP
|
||||
// disableFlushBuffer for that purpose is 'true' by default
|
||||
if (!disableFlushBuffer) {
|
||||
super.flushBuffer();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServletOutputStream getOutputStream() throws IOException {
|
||||
if (this.printWriter != null) {
|
||||
throw new IllegalStateException(
|
||||
"PrintWriter obtained already - cannot get OutputStream");
|
||||
}
|
||||
|
||||
return this.gzipOutputStream;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PrintWriter getWriter() throws IOException {
|
||||
if (this.printWriter == null) {
|
||||
this.gzipOutputStream = new GZipServletOutputStream(
|
||||
getResponse().getOutputStream());
|
||||
|
||||
this.printWriter = new PrintWriter(new OutputStreamWriter(
|
||||
this.gzipOutputStream, getResponse().getCharacterEncoding()), true);
|
||||
}
|
||||
|
||||
return this.printWriter;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void setContentLength(int length) {
|
||||
//ignore, since content length of zipped content
|
||||
//does not match content length of unzipped content.
|
||||
}
|
||||
|
||||
/**
|
||||
* Flushes all the streams for this response.
|
||||
*/
|
||||
public void flush() throws IOException {
|
||||
if (printWriter != null) {
|
||||
printWriter.flush();
|
||||
}
|
||||
|
||||
if (gzipOutputStream != null) {
|
||||
gzipOutputStream.flush();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set if the wrapped reponse's buffer flushing should be disabled.
|
||||
*
|
||||
* @param disableFlushBuffer true if the wrapped reponse's buffer flushing should be disabled
|
||||
*/
|
||||
public void setDisableFlushBuffer(boolean disableFlushBuffer) {
|
||||
this.disableFlushBuffer = disableFlushBuffer;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package mark.quinn.web.filter.gzip;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
|
||||
public class GzipResponseHeadersNotModifiableException extends ServletException {
|
||||
|
||||
public GzipResponseHeadersNotModifiableException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* GZipping servlet filter.
|
||||
*/
|
||||
package mark.quinn.web.filter.gzip;
|
||||
4
src/main/java/mark/quinn/web/filter/package-info.java
Normal file
4
src/main/java/mark/quinn/web/filter/package-info.java
Normal file
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* Servlet filters.
|
||||
*/
|
||||
package mark.quinn.web.filter;
|
||||
@@ -0,0 +1,63 @@
|
||||
package mark.quinn.web.propertyeditors;
|
||||
|
||||
import org.joda.time.LocalDateTime;
|
||||
import org.joda.time.format.DateTimeFormat;
|
||||
import org.joda.time.format.DateTimeFormatter;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.beans.PropertyEditorSupport;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* Custom PropertyEditorSupport to convert from String to
|
||||
* Date using JodaTime (http://www.joda.org/joda-time/).
|
||||
*/
|
||||
public class LocaleDateTimeEditor extends PropertyEditorSupport {
|
||||
|
||||
private final DateTimeFormatter formatter;
|
||||
|
||||
private final boolean allowEmpty;
|
||||
|
||||
/**
|
||||
* Create a new LocaleDateTimeEditor instance, using the given format for
|
||||
* parsing and rendering.
|
||||
* <p/>
|
||||
* The "allowEmpty" parameter states if an empty String should be allowed
|
||||
* for parsing, i.e. get interpreted as null value. Otherwise, an
|
||||
* IllegalArgumentException gets thrown.
|
||||
*
|
||||
* @param dateFormat DateFormat to use for parsing and rendering
|
||||
* @param allowEmpty if empty strings should be allowed
|
||||
*/
|
||||
public LocaleDateTimeEditor(String dateFormat, boolean allowEmpty) {
|
||||
this.formatter = DateTimeFormat.forPattern(dateFormat);
|
||||
this.allowEmpty = allowEmpty;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format the YearMonthDay as String, using the specified format.
|
||||
*
|
||||
* @return DateTime formatted string
|
||||
*/
|
||||
@Override
|
||||
public String getAsText() {
|
||||
Date value = (Date) getValue();
|
||||
return value != null ? new LocalDateTime(value).toString(formatter) : "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the value from the given text, using the specified format.
|
||||
*
|
||||
* @param text the text to format
|
||||
* @throws IllegalArgumentException
|
||||
*/
|
||||
@Override
|
||||
public void setAsText( String text ) throws IllegalArgumentException {
|
||||
if ( allowEmpty && !StringUtils.hasText(text) ) {
|
||||
// Treat empty String as null value.
|
||||
setValue(null);
|
||||
} else {
|
||||
setValue(new LocalDateTime(formatter.parseDateTime(text)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* Property Editors.
|
||||
*/
|
||||
package mark.quinn.web.propertyeditors;
|
||||
236
src/main/java/mark/quinn/web/rest/AccountResource.java
Normal file
236
src/main/java/mark/quinn/web/rest/AccountResource.java
Normal file
@@ -0,0 +1,236 @@
|
||||
package mark.quinn.web.rest;
|
||||
|
||||
import com.codahale.metrics.annotation.Timed;
|
||||
import mark.quinn.domain.Authority;
|
||||
import mark.quinn.domain.PersistentToken;
|
||||
import mark.quinn.domain.User;
|
||||
import mark.quinn.repository.PersistentTokenRepository;
|
||||
import mark.quinn.repository.UserRepository;
|
||||
import mark.quinn.security.SecurityUtils;
|
||||
import mark.quinn.service.MailService;
|
||||
import mark.quinn.service.UserService;
|
||||
import mark.quinn.web.rest.dto.UserDTO;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.validation.Valid;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLDecoder;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* REST controller for managing the current user's account.
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api")
|
||||
public class AccountResource {
|
||||
|
||||
private final Logger log = LoggerFactory.getLogger(AccountResource.class);
|
||||
|
||||
@Inject
|
||||
private UserRepository userRepository;
|
||||
|
||||
@Inject
|
||||
private UserService userService;
|
||||
|
||||
@Inject
|
||||
private PersistentTokenRepository persistentTokenRepository;
|
||||
|
||||
@Inject
|
||||
private MailService mailService;
|
||||
|
||||
/**
|
||||
* POST /register -> register the user.
|
||||
*/
|
||||
@RequestMapping(value = "/register",
|
||||
method = RequestMethod.POST,
|
||||
produces = MediaType.TEXT_PLAIN_VALUE)
|
||||
@Timed
|
||||
public ResponseEntity<?> registerAccount(@Valid @RequestBody UserDTO userDTO, HttpServletRequest request) {
|
||||
return userRepository.findOneByLogin(userDTO.getLogin())
|
||||
.map(user -> new ResponseEntity<>("login already in use", HttpStatus.BAD_REQUEST))
|
||||
.orElseGet(() -> userRepository.findOneByEmail(userDTO.getEmail())
|
||||
.map(user -> new ResponseEntity<>("e-mail address already in use", HttpStatus.BAD_REQUEST))
|
||||
.orElseGet(() -> {
|
||||
User user = userService.createUserInformation(userDTO.getLogin(), userDTO.getPassword(),
|
||||
userDTO.getFirstName(), userDTO.getLastName(), userDTO.getEmail().toLowerCase(),
|
||||
userDTO.getLangKey());
|
||||
String baseUrl = request.getScheme() + // "http"
|
||||
"://" + // "://"
|
||||
request.getServerName() + // "myhost"
|
||||
":" + // ":"
|
||||
request.getServerPort(); // "80"
|
||||
|
||||
mailService.sendActivationEmail(user, baseUrl);
|
||||
return new ResponseEntity<>(HttpStatus.CREATED);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /activate -> activate the registered user.
|
||||
*/
|
||||
@RequestMapping(value = "/activate",
|
||||
method = RequestMethod.GET,
|
||||
produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
@Timed
|
||||
public ResponseEntity<String> activateAccount(@RequestParam(value = "key") String key) {
|
||||
return Optional.ofNullable(userService.activateRegistration(key))
|
||||
.map(user -> new ResponseEntity<String>(HttpStatus.OK))
|
||||
.orElse(new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR));
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /authenticate -> check if the user is authenticated, and return its login.
|
||||
*/
|
||||
@RequestMapping(value = "/authenticate",
|
||||
method = RequestMethod.GET,
|
||||
produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
@Timed
|
||||
public String isAuthenticated(HttpServletRequest request) {
|
||||
log.debug("REST request to check if the current user is authenticated");
|
||||
return request.getRemoteUser();
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /account -> get the current user.
|
||||
*/
|
||||
@RequestMapping(value = "/account",
|
||||
method = RequestMethod.GET,
|
||||
produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
@Timed
|
||||
public ResponseEntity<UserDTO> getAccount() {
|
||||
return Optional.ofNullable(userService.getUserWithAuthorities())
|
||||
.map(user -> {
|
||||
return new ResponseEntity<>(
|
||||
new UserDTO(
|
||||
user.getLogin(),
|
||||
null,
|
||||
user.getFirstName(),
|
||||
user.getLastName(),
|
||||
user.getEmail(),
|
||||
user.getLangKey(),
|
||||
user.getAuthorities().stream().map(Authority::getName)
|
||||
.collect(Collectors.toList())),
|
||||
HttpStatus.OK);
|
||||
})
|
||||
.orElse(new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR));
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /account -> update the current user information.
|
||||
*/
|
||||
@RequestMapping(value = "/account",
|
||||
method = RequestMethod.POST,
|
||||
produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
@Timed
|
||||
public ResponseEntity<String> saveAccount(@RequestBody UserDTO userDTO) {
|
||||
return userRepository
|
||||
.findOneByLogin(userDTO.getLogin())
|
||||
.filter(u -> u.getLogin().equals(SecurityUtils.getCurrentLogin()))
|
||||
.map(u -> {
|
||||
userService.updateUserInformation(userDTO.getFirstName(), userDTO.getLastName(), userDTO.getEmail(),
|
||||
userDTO.getLangKey());
|
||||
return new ResponseEntity<String>(HttpStatus.OK);
|
||||
})
|
||||
.orElseGet(() -> new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR));
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /change_password -> changes the current user's password
|
||||
*/
|
||||
@RequestMapping(value = "/account/change_password",
|
||||
method = RequestMethod.POST,
|
||||
produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
@Timed
|
||||
public ResponseEntity<?> changePassword(@RequestBody String password) {
|
||||
if (!checkPasswordLength(password)) {
|
||||
return new ResponseEntity<>("Incorrect password", HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
userService.changePassword(password);
|
||||
return new ResponseEntity<>(HttpStatus.OK);
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /account/sessions -> get the current open sessions.
|
||||
*/
|
||||
@RequestMapping(value = "/account/sessions",
|
||||
method = RequestMethod.GET,
|
||||
produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
@Timed
|
||||
public ResponseEntity<List<PersistentToken>> getCurrentSessions() {
|
||||
return userRepository.findOneByLogin(SecurityUtils.getCurrentLogin())
|
||||
.map(user -> new ResponseEntity<>(
|
||||
persistentTokenRepository.findByUser(user),
|
||||
HttpStatus.OK))
|
||||
.orElse(new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR));
|
||||
}
|
||||
|
||||
/**
|
||||
* DELETE /account/sessions?series={series} -> invalidate an existing session.
|
||||
*
|
||||
* - You can only delete your own sessions, not any other user's session
|
||||
* - If you delete one of your existing sessions, and that you are currently logged in on that session, you will
|
||||
* still be able to use that session, until you quit your browser: it does not work in real time (there is
|
||||
* no API for that), it only removes the "remember me" cookie
|
||||
* - This is also true if you invalidate your current session: you will still be able to use it until you close
|
||||
* your browser or that the session times out. But automatic login (the "remember me" cookie) will not work
|
||||
* anymore.
|
||||
* There is an API to invalidate the current session, but there is no API to check which session uses which
|
||||
* cookie.
|
||||
*/
|
||||
@RequestMapping(value = "/account/sessions/{series}",
|
||||
method = RequestMethod.DELETE)
|
||||
@Timed
|
||||
public void invalidateSession(@PathVariable String series) throws UnsupportedEncodingException {
|
||||
String decodedSeries = URLDecoder.decode(series, "UTF-8");
|
||||
userRepository.findOneByLogin(SecurityUtils.getCurrentLogin()).ifPresent(u -> {
|
||||
persistentTokenRepository.findByUser(u).stream()
|
||||
.filter(persistentToken -> StringUtils.equals(persistentToken.getSeries(), decodedSeries))
|
||||
.findAny().ifPresent(t -> persistentTokenRepository.delete(decodedSeries));
|
||||
});
|
||||
}
|
||||
|
||||
@RequestMapping(value = "/account/reset_password/init",
|
||||
method = RequestMethod.POST,
|
||||
produces = MediaType.TEXT_PLAIN_VALUE)
|
||||
@Timed
|
||||
public ResponseEntity<?> requestPasswordReset(@RequestBody String mail, HttpServletRequest request) {
|
||||
|
||||
return userService.requestPasswordReset(mail)
|
||||
.map(user -> {
|
||||
String baseUrl = request.getScheme() +
|
||||
"://" +
|
||||
request.getServerName() +
|
||||
":" +
|
||||
request.getServerPort();
|
||||
mailService.sendPasswordResetMail(user, baseUrl);
|
||||
return new ResponseEntity<>("e-mail was sent", HttpStatus.OK);
|
||||
}).orElse(new ResponseEntity<>("e-mail address not registered", HttpStatus.BAD_REQUEST));
|
||||
|
||||
}
|
||||
|
||||
@RequestMapping(value = "/account/reset_password/finish",
|
||||
method = RequestMethod.POST,
|
||||
produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
@Timed
|
||||
public ResponseEntity<String> finishPasswordReset(@RequestParam(value = "key") String key, @RequestParam(value = "newPassword") String newPassword) {
|
||||
if (!checkPasswordLength(newPassword)) {
|
||||
return new ResponseEntity<>("Incorrect password", HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
return userService.completePasswordReset(newPassword, key)
|
||||
.map(user -> new ResponseEntity<String>(HttpStatus.OK)).orElse(new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR));
|
||||
}
|
||||
|
||||
private boolean checkPasswordLength(String password) {
|
||||
return (!StringUtils.isEmpty(password) && password.length() >= UserDTO.PASSWORD_MIN_LENGTH && password.length() <= UserDTO.PASSWORD_MAX_LENGTH);
|
||||
}
|
||||
}
|
||||
44
src/main/java/mark/quinn/web/rest/AuditResource.java
Normal file
44
src/main/java/mark/quinn/web/rest/AuditResource.java
Normal file
@@ -0,0 +1,44 @@
|
||||
package mark.quinn.web.rest;
|
||||
|
||||
import mark.quinn.security.AuthoritiesConstants;
|
||||
import mark.quinn.service.AuditEventService;
|
||||
import mark.quinn.web.propertyeditors.LocaleDateTimeEditor;
|
||||
import org.joda.time.LocalDateTime;
|
||||
import org.springframework.boot.actuate.audit.AuditEvent;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.bind.WebDataBinder;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* REST controller for getting the audit events.
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api")
|
||||
public class AuditResource {
|
||||
|
||||
@Inject
|
||||
private AuditEventService auditEventService;
|
||||
|
||||
@InitBinder
|
||||
public void initBinder(WebDataBinder binder) {
|
||||
binder.registerCustomEditor(LocalDateTime.class, new LocaleDateTimeEditor("yyyy-MM-dd", false));
|
||||
}
|
||||
|
||||
@RequestMapping(value = "/audits/all",
|
||||
method = RequestMethod.GET,
|
||||
produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public List<AuditEvent> findAll() {
|
||||
return auditEventService.findAll();
|
||||
}
|
||||
|
||||
@RequestMapping(value = "/audits/byDates",
|
||||
method = RequestMethod.GET,
|
||||
produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public List<AuditEvent> findByDates(@RequestParam(value = "fromDate") LocalDateTime fromDate,
|
||||
@RequestParam(value = "toDate") LocalDateTime toDate) {
|
||||
return auditEventService.findByDates(fromDate, toDate);
|
||||
}
|
||||
}
|
||||
43
src/main/java/mark/quinn/web/rest/LogsResource.java
Normal file
43
src/main/java/mark/quinn/web/rest/LogsResource.java
Normal file
@@ -0,0 +1,43 @@
|
||||
package mark.quinn.web.rest;
|
||||
|
||||
import ch.qos.logback.classic.Level;
|
||||
import ch.qos.logback.classic.LoggerContext;
|
||||
import com.codahale.metrics.annotation.Timed;
|
||||
import mark.quinn.web.rest.dto.LoggerDTO;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Controller for view and managing Log Level at runtime.
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api")
|
||||
public class LogsResource {
|
||||
|
||||
@RequestMapping(value = "/logs",
|
||||
method = RequestMethod.GET,
|
||||
produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
@Timed
|
||||
public List<LoggerDTO> getList() {
|
||||
LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();
|
||||
return context.getLoggerList()
|
||||
.stream()
|
||||
.map(LoggerDTO::new)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
}
|
||||
|
||||
@RequestMapping(value = "/logs",
|
||||
method = RequestMethod.PUT)
|
||||
@ResponseStatus(HttpStatus.NO_CONTENT)
|
||||
@Timed
|
||||
public void changeLevel(@RequestBody LoggerDTO jsonLogger) {
|
||||
LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();
|
||||
context.getLogger(jsonLogger.getName()).setLevel(Level.valueOf(jsonLogger.getLevel()));
|
||||
}
|
||||
}
|
||||
57
src/main/java/mark/quinn/web/rest/UserResource.java
Normal file
57
src/main/java/mark/quinn/web/rest/UserResource.java
Normal file
@@ -0,0 +1,57 @@
|
||||
package mark.quinn.web.rest;
|
||||
|
||||
import com.codahale.metrics.annotation.Timed;
|
||||
import mark.quinn.domain.User;
|
||||
import mark.quinn.repository.UserRepository;
|
||||
import mark.quinn.security.AuthoritiesConstants;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* REST controller for managing users.
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api")
|
||||
public class UserResource {
|
||||
|
||||
private final Logger log = LoggerFactory.getLogger(UserResource.class);
|
||||
|
||||
@Inject
|
||||
private UserRepository userRepository;
|
||||
|
||||
/**
|
||||
* GET /users -> get all users.
|
||||
*/
|
||||
@RequestMapping(value = "/users",
|
||||
method = RequestMethod.GET,
|
||||
produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
@Timed
|
||||
public List<User> getAll() {
|
||||
log.debug("REST request to get all Users");
|
||||
return userRepository.findAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /users/:login -> get the "login" user.
|
||||
*/
|
||||
@RequestMapping(value = "/users/{login}",
|
||||
method = RequestMethod.GET,
|
||||
produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
@Timed
|
||||
ResponseEntity<User> getUser(@PathVariable String login) {
|
||||
log.debug("REST request to get User : {}", login);
|
||||
return userRepository.findOneByLogin(login)
|
||||
.map(user -> new ResponseEntity<>(user, HttpStatus.OK))
|
||||
.orElse(new ResponseEntity<>(HttpStatus.NOT_FOUND));
|
||||
}
|
||||
}
|
||||
44
src/main/java/mark/quinn/web/rest/dto/LoggerDTO.java
Normal file
44
src/main/java/mark/quinn/web/rest/dto/LoggerDTO.java
Normal file
@@ -0,0 +1,44 @@
|
||||
package mark.quinn.web.rest.dto;
|
||||
|
||||
import ch.qos.logback.classic.Logger;
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
|
||||
public class LoggerDTO {
|
||||
|
||||
private String name;
|
||||
|
||||
private String level;
|
||||
|
||||
public LoggerDTO(Logger logger) {
|
||||
this.name = logger.getName();
|
||||
this.level = logger.getEffectiveLevel().toString();
|
||||
}
|
||||
|
||||
@JsonCreator
|
||||
public LoggerDTO() {
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getLevel() {
|
||||
return level;
|
||||
}
|
||||
|
||||
public void setLevel(String level) {
|
||||
this.level = level;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "LoggerDTO{" +
|
||||
"name='" + name + '\'' +
|
||||
", level='" + level + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
93
src/main/java/mark/quinn/web/rest/dto/UserDTO.java
Normal file
93
src/main/java/mark/quinn/web/rest/dto/UserDTO.java
Normal file
@@ -0,0 +1,93 @@
|
||||
package mark.quinn.web.rest.dto;
|
||||
|
||||
import org.hibernate.validator.constraints.Email;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
import javax.validation.constraints.Pattern;
|
||||
import javax.validation.constraints.Size;
|
||||
import java.util.List;
|
||||
|
||||
public class UserDTO {
|
||||
|
||||
public static final int PASSWORD_MIN_LENGTH = 5;
|
||||
public static final int PASSWORD_MAX_LENGTH = 100;
|
||||
|
||||
@Pattern(regexp = "^[a-z0-9]*$")
|
||||
@NotNull
|
||||
@Size(min = 1, max = 50)
|
||||
private String login;
|
||||
|
||||
@NotNull
|
||||
@Size(min = PASSWORD_MIN_LENGTH, max = PASSWORD_MAX_LENGTH)
|
||||
private String password;
|
||||
|
||||
@Size(max = 50)
|
||||
private String firstName;
|
||||
|
||||
@Size(max = 50)
|
||||
private String lastName;
|
||||
|
||||
@Email
|
||||
@Size(min = 5, max = 100)
|
||||
private String email;
|
||||
|
||||
@Size(min = 2, max = 5)
|
||||
private String langKey;
|
||||
|
||||
private List<String> roles;
|
||||
|
||||
public UserDTO() {
|
||||
}
|
||||
|
||||
public UserDTO(String login, String password, String firstName, String lastName, String email, String langKey,
|
||||
List<String> roles) {
|
||||
this.login = login;
|
||||
this.password = password;
|
||||
this.firstName = firstName;
|
||||
this.lastName = lastName;
|
||||
this.email = email;
|
||||
this.langKey = langKey;
|
||||
this.roles = roles;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
public String getLogin() {
|
||||
return login;
|
||||
}
|
||||
|
||||
public String getFirstName() {
|
||||
return firstName;
|
||||
}
|
||||
|
||||
public String getLastName() {
|
||||
return lastName;
|
||||
}
|
||||
|
||||
public String getEmail() {
|
||||
return email;
|
||||
}
|
||||
|
||||
public String getLangKey() {
|
||||
return langKey;
|
||||
}
|
||||
|
||||
public List<String> getRoles() {
|
||||
return roles;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "UserDTO{" +
|
||||
"login='" + login + '\'' +
|
||||
", password='" + password + '\'' +
|
||||
", firstName='" + firstName + '\'' +
|
||||
", lastName='" + lastName + '\'' +
|
||||
", email='" + email + '\'' +
|
||||
", langKey='" + langKey + '\'' +
|
||||
", roles=" + roles +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
4
src/main/java/mark/quinn/web/rest/dto/package-info.java
Normal file
4
src/main/java/mark/quinn/web/rest/dto/package-info.java
Normal file
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* Data Transfer Objects used by Spring MVC REST controllers.
|
||||
*/
|
||||
package mark.quinn.web.rest.dto;
|
||||
@@ -0,0 +1,34 @@
|
||||
package mark.quinn.web.rest.errors;
|
||||
|
||||
/**
|
||||
* Custom, parameterized exception, which can be translated on the client side.
|
||||
* For example:
|
||||
*
|
||||
* <pre>
|
||||
* throw new CustomParameterizedException("myCustomError", "hello", "world");
|
||||
* </pre>
|
||||
*
|
||||
* Can be translated with:
|
||||
*
|
||||
* <pre>
|
||||
* "error.myCustomError" : "The server says {{params[0]}} to {{params[1]}}"
|
||||
* </pre>
|
||||
*/
|
||||
public class CustomParameterizedException extends RuntimeException {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final String message;
|
||||
private final String[] params;
|
||||
|
||||
public CustomParameterizedException(String message, String... params) {
|
||||
super(message);
|
||||
this.message = message;
|
||||
this.params = params;
|
||||
}
|
||||
|
||||
public ParameterizedErrorDTO getErrorDTO() {
|
||||
return new ParameterizedErrorDTO(message, params);
|
||||
}
|
||||
|
||||
}
|
||||
13
src/main/java/mark/quinn/web/rest/errors/ErrorConstants.java
Normal file
13
src/main/java/mark/quinn/web/rest/errors/ErrorConstants.java
Normal file
@@ -0,0 +1,13 @@
|
||||
package mark.quinn.web.rest.errors;
|
||||
|
||||
public final class ErrorConstants {
|
||||
|
||||
public static final String ERR_CONCURRENCY_FAILURE = "error.concurrencyFailure";
|
||||
public static final String ERR_ACCESS_DENIED = "error.accessDenied";
|
||||
public static final String ERR_VALIDATION = "error.validation";
|
||||
public static final String ERR_METHOD_NOT_SUPPORTED = "error.methodNotSupported";
|
||||
|
||||
private ErrorConstants() {
|
||||
}
|
||||
|
||||
}
|
||||
51
src/main/java/mark/quinn/web/rest/errors/ErrorDTO.java
Normal file
51
src/main/java/mark/quinn/web/rest/errors/ErrorDTO.java
Normal file
@@ -0,0 +1,51 @@
|
||||
package mark.quinn.web.rest.errors;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* DTO for transfering error message with a list of field errors.
|
||||
*/
|
||||
public class ErrorDTO implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final String message;
|
||||
private final String description;
|
||||
|
||||
private List<FieldErrorDTO> fieldErrors;
|
||||
|
||||
ErrorDTO(String message) {
|
||||
this(message, null);
|
||||
}
|
||||
|
||||
ErrorDTO(String message, String description) {
|
||||
this.message = message;
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
ErrorDTO(String message, String description, List<FieldErrorDTO> fieldErrors) {
|
||||
this.message = message;
|
||||
this.description = description;
|
||||
this.fieldErrors = fieldErrors;
|
||||
}
|
||||
|
||||
public void add(String objectName, String field, String message) {
|
||||
if (fieldErrors == null) {
|
||||
fieldErrors = new ArrayList<>();
|
||||
}
|
||||
fieldErrors.add(new FieldErrorDTO(objectName, field, message));
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public List<FieldErrorDTO> getFieldErrors() {
|
||||
return fieldErrors;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package mark.quinn.web.rest.errors;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.dao.ConcurrencyFailureException;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.security.access.AccessDeniedException;
|
||||
import org.springframework.validation.BindingResult;
|
||||
import org.springframework.validation.FieldError;
|
||||
import org.springframework.web.HttpRequestMethodNotSupportedException;
|
||||
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||
import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
|
||||
/**
|
||||
* Controller advice to translate the server side exceptions to client-friendly json structures.
|
||||
*/
|
||||
@ControllerAdvice
|
||||
public class ExceptionTranslator {
|
||||
|
||||
@ExceptionHandler(ConcurrencyFailureException.class)
|
||||
@ResponseStatus(HttpStatus.CONFLICT)
|
||||
@ResponseBody
|
||||
public ErrorDTO processConcurencyError(ConcurrencyFailureException ex) {
|
||||
return new ErrorDTO(ErrorConstants.ERR_CONCURRENCY_FAILURE);
|
||||
}
|
||||
|
||||
@ExceptionHandler(MethodArgumentNotValidException.class)
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
@ResponseBody
|
||||
public ErrorDTO processValidationError(MethodArgumentNotValidException ex) {
|
||||
BindingResult result = ex.getBindingResult();
|
||||
List<FieldError> fieldErrors = result.getFieldErrors();
|
||||
|
||||
return processFieldErrors(fieldErrors);
|
||||
}
|
||||
|
||||
@ExceptionHandler(CustomParameterizedException.class)
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
@ResponseBody
|
||||
public ParameterizedErrorDTO processParameterizedValidationError(CustomParameterizedException ex) {
|
||||
return ex.getErrorDTO();
|
||||
}
|
||||
|
||||
@ExceptionHandler(AccessDeniedException.class)
|
||||
@ResponseStatus(HttpStatus.FORBIDDEN)
|
||||
@ResponseBody
|
||||
public ErrorDTO processAccessDeniedExcpetion(AccessDeniedException e) {
|
||||
return new ErrorDTO(ErrorConstants.ERR_ACCESS_DENIED, e.getMessage());
|
||||
}
|
||||
|
||||
private ErrorDTO processFieldErrors(List<FieldError> fieldErrors) {
|
||||
ErrorDTO dto = new ErrorDTO(ErrorConstants.ERR_VALIDATION);
|
||||
|
||||
for (FieldError fieldError : fieldErrors) {
|
||||
dto.add(fieldError.getObjectName(), fieldError.getField(), fieldError.getCode());
|
||||
}
|
||||
|
||||
return dto;
|
||||
}
|
||||
|
||||
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
|
||||
@ResponseBody
|
||||
@ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED)
|
||||
public ErrorDTO processMethodNotSupportedException(HttpRequestMethodNotSupportedException exception) {
|
||||
return new ErrorDTO(ErrorConstants.ERR_METHOD_NOT_SUPPORTED, exception.getMessage());
|
||||
}
|
||||
}
|
||||
33
src/main/java/mark/quinn/web/rest/errors/FieldErrorDTO.java
Normal file
33
src/main/java/mark/quinn/web/rest/errors/FieldErrorDTO.java
Normal file
@@ -0,0 +1,33 @@
|
||||
package mark.quinn.web.rest.errors;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
public class FieldErrorDTO implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final String objectName;
|
||||
|
||||
private final String field;
|
||||
|
||||
private final String message;
|
||||
|
||||
FieldErrorDTO(String dto, String field, String message) {
|
||||
this.objectName = dto;
|
||||
this.field = field;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public String getObjectName() {
|
||||
return objectName;
|
||||
}
|
||||
|
||||
public String getField() {
|
||||
return field;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package mark.quinn.web.rest.errors;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* DTO for sending a parameterized error message.
|
||||
*/
|
||||
public class ParameterizedErrorDTO implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
private final String message;
|
||||
private final String[] params;
|
||||
|
||||
public ParameterizedErrorDTO(String message, String... params) {
|
||||
this.message = message;
|
||||
this.params = params;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public String[] getParams() {
|
||||
return params;
|
||||
}
|
||||
|
||||
}
|
||||
4
src/main/java/mark/quinn/web/rest/package-info.java
Normal file
4
src/main/java/mark/quinn/web/rest/package-info.java
Normal file
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* Spring MVC REST controllers.
|
||||
*/
|
||||
package mark.quinn.web.rest;
|
||||
30
src/main/java/mark/quinn/web/rest/util/HeaderUtil.java
Normal file
30
src/main/java/mark/quinn/web/rest/util/HeaderUtil.java
Normal file
@@ -0,0 +1,30 @@
|
||||
package mark.quinn.web.rest.util;
|
||||
|
||||
import org.springframework.http.HttpHeaders;
|
||||
|
||||
/**
|
||||
* Utility class for http header creation.
|
||||
*
|
||||
*/
|
||||
public class HeaderUtil {
|
||||
|
||||
public static HttpHeaders createAlert(String message, String param) {
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.add("X-openajpportApp-alert", message);
|
||||
headers.add("X-openajpportApp-params", param);
|
||||
return headers;
|
||||
}
|
||||
|
||||
public static HttpHeaders createEntityCreationAlert(String entityName, String param) {
|
||||
return createAlert("openajpportApp." + entityName + ".created", param);
|
||||
}
|
||||
|
||||
public static HttpHeaders createEntityUpdateAlert(String entityName, String param) {
|
||||
return createAlert("openajpportApp." + entityName + ".updated", param);
|
||||
}
|
||||
|
||||
public static HttpHeaders createEntityDeletionAlert(String entityName, String param) {
|
||||
return createAlert("openajpportApp." + entityName + ".deleted", param);
|
||||
}
|
||||
|
||||
}
|
||||
66
src/main/java/mark/quinn/web/rest/util/PaginationUtil.java
Normal file
66
src/main/java/mark/quinn/web/rest/util/PaginationUtil.java
Normal file
@@ -0,0 +1,66 @@
|
||||
package mark.quinn.web.rest.util;
|
||||
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
|
||||
/**
|
||||
* Utility class for handling pagination.
|
||||
*
|
||||
* <p>
|
||||
* Pagination uses the same principles as the <a href="https://developer.github.com/v3/#pagination">Github API</api>,
|
||||
* and follow <a href="http://tools.ietf.org/html/rfc5988">RFC 5988 (Link header)</a>.
|
||||
* </p>
|
||||
*/
|
||||
public class PaginationUtil {
|
||||
|
||||
public static final int DEFAULT_OFFSET = 1;
|
||||
|
||||
public static final int MIN_OFFSET = 1;
|
||||
|
||||
public static final int DEFAULT_LIMIT = 20;
|
||||
|
||||
public static final int MAX_LIMIT = 100;
|
||||
|
||||
public static Pageable generatePageRequest(Integer offset, Integer limit) {
|
||||
if (offset == null || offset < MIN_OFFSET) {
|
||||
offset = DEFAULT_OFFSET;
|
||||
}
|
||||
if (limit == null || limit > MAX_LIMIT) {
|
||||
limit = DEFAULT_LIMIT;
|
||||
}
|
||||
return new PageRequest(offset - 1, limit);
|
||||
}
|
||||
|
||||
public static HttpHeaders generatePaginationHttpHeaders(Page<?> page, String baseUrl, Integer offset, Integer limit)
|
||||
throws URISyntaxException {
|
||||
|
||||
if (offset == null || offset < MIN_OFFSET) {
|
||||
offset = DEFAULT_OFFSET;
|
||||
}
|
||||
if (limit == null || limit > MAX_LIMIT) {
|
||||
limit = DEFAULT_LIMIT;
|
||||
}
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.add("X-Total-Count", "" + page.getTotalElements());
|
||||
String link = "";
|
||||
if (offset < page.getTotalPages()) {
|
||||
link = "<" + (new URI(baseUrl +"?page=" + (offset + 1) + "&per_page=" + limit)).toString()
|
||||
+ ">; rel=\"next\",";
|
||||
}
|
||||
if (offset > 1) {
|
||||
link += "<" + (new URI(baseUrl +"?page=" + (offset - 1) + "&per_page=" + limit)).toString()
|
||||
+ ">; rel=\"prev\",";
|
||||
}
|
||||
link += "<" + (new URI(baseUrl +"?page=" + page.getTotalPages() + "&per_page=" + limit)).toString()
|
||||
+ ">; rel=\"last\"," +
|
||||
"<" + (new URI(baseUrl +"?page=" + 1 + "&per_page=" + limit)).toString()
|
||||
+ ">; rel=\"first\"";
|
||||
headers.add(HttpHeaders.LINK, link);
|
||||
return headers;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user