- 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