Compare commits
No commits in common. "develop" and "master" have entirely different histories.
|
|
@ -38,15 +38,3 @@ build/
|
||||||
.DS_Store
|
.DS_Store
|
||||||
/.idea/
|
/.idea/
|
||||||
/.mvn/
|
/.mvn/
|
||||||
xcp.conf
|
|
||||||
xcpdata.mv.db
|
|
||||||
/web-ui/api.spec.json
|
|
||||||
api-sandbox/app/node_modules
|
|
||||||
api-sandbox/app/api.spec.json
|
|
||||||
/xcpdata.trace.db
|
|
||||||
web-ui/vue-app/TODO.md
|
|
||||||
web-ui/vue-app/src/generated/*
|
|
||||||
/api-sandbox/src/main/resources/htdocs/api-sandbox/
|
|
||||||
api-sandbox/app/dist/api.spec.json
|
|
||||||
api-sandbox/app/node/
|
|
||||||
api.spec.json
|
|
||||||
|
|
|
||||||
|
|
@ -1,124 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="ru">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>API Debug Console</title>
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
font-family: 'Courier New', monospace;
|
|
||||||
margin: 0;
|
|
||||||
background-color: #1a1a1a;
|
|
||||||
color: #ffffff;
|
|
||||||
height: 100vh;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
.container {
|
|
||||||
display: flex;
|
|
||||||
height: 100vh;
|
|
||||||
}
|
|
||||||
.left-panel {
|
|
||||||
flex: 1;
|
|
||||||
padding: 20px;
|
|
||||||
border-right: 1px solid #ffffff;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
.right-panel {
|
|
||||||
flex: 1;
|
|
||||||
padding: 20px;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
h1 { color: #ffffff; border-bottom: 1px solid #ffffff; padding-bottom: 10px; margin-top: 0; }
|
|
||||||
.form-group { margin: 10px 0; }
|
|
||||||
label { display: block; margin-bottom: 5px; color: #ffffff; }
|
|
||||||
select, input, textarea {
|
|
||||||
padding: 8px;
|
|
||||||
width: 100%;
|
|
||||||
max-width: 100%;
|
|
||||||
background: transparent;
|
|
||||||
border: 1px solid #ffffff;
|
|
||||||
color: #ffffff;
|
|
||||||
outline: none;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
select option { background: #1a1a1a; color: #ffffff; }
|
|
||||||
button {
|
|
||||||
padding: 10px 20px;
|
|
||||||
background: transparent;
|
|
||||||
color: #ffffff;
|
|
||||||
border: 1px solid #ffffff;
|
|
||||||
cursor: pointer;
|
|
||||||
outline: none;
|
|
||||||
margin-top: 10px;
|
|
||||||
}
|
|
||||||
button:hover {
|
|
||||||
background: #ffffff;
|
|
||||||
color: #1a1a1a;
|
|
||||||
}
|
|
||||||
#result, #request {
|
|
||||||
margin-top: 20px;
|
|
||||||
white-space: pre-wrap;
|
|
||||||
background: transparent;
|
|
||||||
padding: 10px;
|
|
||||||
font-family: 'Courier New', monospace;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
.params { margin-top: 10px; }
|
|
||||||
.param { margin-bottom: 15px; }
|
|
||||||
.param-header { display: flex; align-items: center; gap: 10px; margin-bottom: 5px; }
|
|
||||||
.checkbox-wrapper { display: flex; align-items: center; gap: 5px; }
|
|
||||||
.checkbox-wrapper input[type="checkbox"] { width: auto; margin: 0; accent-color: #ffffff; }
|
|
||||||
.checkbox-wrapper label { margin: 0; font-weight: normal; white-space: nowrap; color: #ffffff; }
|
|
||||||
input:disabled, textarea:disabled {
|
|
||||||
background: transparent;
|
|
||||||
color: #666;
|
|
||||||
border-color: #666;
|
|
||||||
}
|
|
||||||
input:focus, select:focus, textarea:focus {
|
|
||||||
border-color: #ffffff;
|
|
||||||
box-shadow: 0 0 2px #ffffff;
|
|
||||||
}
|
|
||||||
textarea {
|
|
||||||
resize: vertical;
|
|
||||||
min-height: 80px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="container">
|
|
||||||
<div class="left-panel">
|
|
||||||
<div id="app">
|
|
||||||
<h1>API Debug Console</h1>
|
|
||||||
|
|
||||||
<div id="user-info" class="form-group" style="margin-bottom: 20px;">
|
|
||||||
<label>Пользователь:</label>
|
|
||||||
<span id="username" style="color: #ccc;">не авторизован</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="service">Сервис:</label>
|
|
||||||
<select id="service"></select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="method">Метод:</label>
|
|
||||||
<select id="method"></select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="params-container" class="params"></div>
|
|
||||||
|
|
||||||
<button id="send-btn">Отправить</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="right-panel">
|
|
||||||
<h1>Result</h1>
|
|
||||||
<div id="result"></div>
|
|
||||||
|
|
||||||
<h1 style="margin-top: 30px;">Request</h1>
|
|
||||||
<div id="request"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<script type="module" src="/main.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
@ -1,267 +0,0 @@
|
||||||
import $ from 'jquery'
|
|
||||||
|
|
||||||
let apiSpec = null
|
|
||||||
let currentUser = null
|
|
||||||
const apiEndpoint = "/api";
|
|
||||||
|
|
||||||
async function loadUserProfile() {
|
|
||||||
try {
|
|
||||||
const requestData = {
|
|
||||||
jsonrpc: '2.0',
|
|
||||||
method: 'Profile.get',
|
|
||||||
params: {},
|
|
||||||
id: Date.now()
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = await fetch(apiEndpoint, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
},
|
|
||||||
credentials: 'include',
|
|
||||||
body: JSON.stringify(requestData)
|
|
||||||
})
|
|
||||||
|
|
||||||
const result = await response.json()
|
|
||||||
if (result.result) {
|
|
||||||
currentUser = result.result
|
|
||||||
$('#username').text(currentUser.name || 'неизвестный пользователь')
|
|
||||||
} else {
|
|
||||||
currentUser = null
|
|
||||||
$('#username').text('не авторизован')
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Ошибка загрузки профиля:', error)
|
|
||||||
currentUser = null
|
|
||||||
$('#username').text('не авторизован')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function loadApiSpec() {
|
|
||||||
try {
|
|
||||||
const response = await fetch('/api.spec.json', {
|
|
||||||
method: 'GET',
|
|
||||||
headers: {
|
|
||||||
'Cache-Control': 'no-cache',
|
|
||||||
'Accept': 'application/json'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(`HTTP ${response.status}: ${response.statusText}`)
|
|
||||||
}
|
|
||||||
const data = await response.json()
|
|
||||||
apiSpec = data.modules
|
|
||||||
console.log('API Spec loaded:', data)
|
|
||||||
return apiSpec
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Ошибка загрузки API спецификации:', error)
|
|
||||||
$('#result').text('Ошибка загрузки API спецификации: ' + error.message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function populateServices() {
|
|
||||||
const $serviceSelect = $('#service')
|
|
||||||
$serviceSelect.empty()
|
|
||||||
$serviceSelect.append('<option value="">Выберите сервис</option>')
|
|
||||||
|
|
||||||
apiSpec.forEach(service => {
|
|
||||||
$serviceSelect.append(`<option value="${service.name}">${service.name}</option>`)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function populateMethods(serviceName) {
|
|
||||||
const $methodSelect = $('#method')
|
|
||||||
$methodSelect.empty()
|
|
||||||
$methodSelect.append('<option value="">Выберите метод</option>')
|
|
||||||
|
|
||||||
if (serviceName) {
|
|
||||||
const service = apiSpec.find(s => s.name === serviceName)
|
|
||||||
if (service && service.methods) {
|
|
||||||
service.methods.forEach(method => {
|
|
||||||
$methodSelect.append(`<option value="${method.name}">${method.name}</option>`)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function createParamInputs(methodName, serviceName) {
|
|
||||||
const $paramsContainer = $('#params-container')
|
|
||||||
$paramsContainer.empty()
|
|
||||||
|
|
||||||
if (!serviceName || !methodName) return
|
|
||||||
|
|
||||||
const service = apiSpec.find(s => s.name === serviceName)
|
|
||||||
if (!service || !service.methods) return
|
|
||||||
|
|
||||||
const method = service.methods.find(m => m.name === methodName)
|
|
||||||
if (!method || !method.params) return
|
|
||||||
|
|
||||||
method.params.forEach(param => {
|
|
||||||
const required = !param.optional ? ' (обязательно)' : ' (необязательно)'
|
|
||||||
const isOptional = param.optional
|
|
||||||
|
|
||||||
const isObjectOrArray = param.type === 'object' || param.type === 'array'
|
|
||||||
const defaultValue = param.type === 'object' ? '{}' : (param.type === 'array' ? '[]' : '')
|
|
||||||
const inputElement = isObjectOrArray
|
|
||||||
? `<textarea id="param-${param.name}" data-param="${param.name}" placeholder="${param.description}" rows="4" style="font-family: 'Courier New', monospace;">${defaultValue}</textarea>`
|
|
||||||
: `<input type="text" id="param-${param.name}" data-param="${param.name}" placeholder="${param.description}">`
|
|
||||||
|
|
||||||
if (isOptional) {
|
|
||||||
const $paramDiv = $(`
|
|
||||||
<div class="param">
|
|
||||||
<div class="param-header">
|
|
||||||
<label for="param-${param.name}">${param.name}${required} (${param.type}):</label>
|
|
||||||
<div class="checkbox-wrapper">
|
|
||||||
<input type="checkbox" id="defined-${param.name}" data-param="${param.name}" class="defined-checkbox">
|
|
||||||
<label for="defined-${param.name}">is defined</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
${inputElement}
|
|
||||||
</div>
|
|
||||||
`)
|
|
||||||
$paramsContainer.append($paramDiv)
|
|
||||||
|
|
||||||
$(`#defined-${param.name}`).on('change', function () {
|
|
||||||
const $input = $(`#param-${param.name}`)
|
|
||||||
const isChecked = $(this).is(':checked')
|
|
||||||
$input.prop('disabled', !isChecked)
|
|
||||||
if (!isChecked) {
|
|
||||||
$input.val(isObjectOrArray ? defaultValue : '')
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// Initially disable optional fields
|
|
||||||
$(`#param-${param.name}`).prop('disabled', true)
|
|
||||||
} else {
|
|
||||||
const $paramDiv = $(`
|
|
||||||
<div class="param">
|
|
||||||
<label for="param-${param.name}">${param.name}${required} (${param.type}):</label>
|
|
||||||
${inputElement}
|
|
||||||
</div>
|
|
||||||
`)
|
|
||||||
$paramsContainer.append($paramDiv)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async function sendRequest() {
|
|
||||||
const serviceName = $('#service').val()
|
|
||||||
const methodName = $('#method').val()
|
|
||||||
|
|
||||||
if (!serviceName || !methodName) {
|
|
||||||
$('#result').text('Выберите сервис и метод')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const params = {}
|
|
||||||
let parseError = null
|
|
||||||
|
|
||||||
$('.param').each(function () {
|
|
||||||
const $checkbox = $(this).find('.defined-checkbox')
|
|
||||||
const $input = $(this).find('input[type="text"], textarea')
|
|
||||||
const paramName = $input.data('param')
|
|
||||||
const paramType = $input.attr('id').replace('param-', '')
|
|
||||||
|
|
||||||
// Find parameter type from method definition
|
|
||||||
const service = apiSpec.find(s => s.name === serviceName)
|
|
||||||
const method = service.methods.find(m => m.name === methodName)
|
|
||||||
const paramDef = method.params.find(p => p.name === paramName)
|
|
||||||
const isObjectOrArray = paramDef && (paramDef.type === 'object' || paramDef.type === 'array')
|
|
||||||
|
|
||||||
if ($checkbox.length > 0) {
|
|
||||||
// Optional parameter
|
|
||||||
if ($checkbox.is(':checked') && $input.val().trim() !== '') {
|
|
||||||
const value = $input.val().trim()
|
|
||||||
if (isObjectOrArray) {
|
|
||||||
try {
|
|
||||||
params[paramName] = JSON.parse(value)
|
|
||||||
} catch (e) {
|
|
||||||
parseError = `Ошибка парсинга JSON для поля "${paramName}": ${e.message}`
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
params[paramName] = value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Required parameter
|
|
||||||
const value = $input.val().trim()
|
|
||||||
if (value !== '') {
|
|
||||||
if (isObjectOrArray) {
|
|
||||||
try {
|
|
||||||
params[paramName] = JSON.parse(value)
|
|
||||||
} catch (e) {
|
|
||||||
parseError = `Ошибка парсинга JSON для поля "${paramName}": ${e.message}`
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
params[paramName] = value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
if (parseError) {
|
|
||||||
$('#result').text(parseError)
|
|
||||||
$('#request').text(JSON.stringify(requestData, null, 2))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const requestData = {
|
|
||||||
jsonrpc: '2.0',
|
|
||||||
method: `${serviceName}.${methodName}`,
|
|
||||||
params: params,
|
|
||||||
id: Date.now()
|
|
||||||
}
|
|
||||||
|
|
||||||
$('#result').text('Отправка запроса...')
|
|
||||||
$('#request').text('')
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch(apiEndpoint, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
},
|
|
||||||
credentials: 'include',
|
|
||||||
body: JSON.stringify(requestData)
|
|
||||||
})
|
|
||||||
|
|
||||||
const result = await response.json()
|
|
||||||
$('#result').text(JSON.stringify(result, null, 2))
|
|
||||||
$('#request').text(JSON.stringify(requestData, null, 2))
|
|
||||||
|
|
||||||
// Проверяем, нужно ли обновить профиль пользователя
|
|
||||||
if (result.result && (
|
|
||||||
methodName.startsWith('authenticateBy') ||
|
|
||||||
methodName === 'logout'
|
|
||||||
)) {
|
|
||||||
await loadUserProfile()
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
$('#result').text(`Ошибка: ${error.message}`)
|
|
||||||
$('#request').text(JSON.stringify(requestData, null, 2))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$(document).ready(async () => {
|
|
||||||
apiSpec = await loadApiSpec()
|
|
||||||
if (apiSpec) {
|
|
||||||
populateServices()
|
|
||||||
|
|
||||||
// Автоматически загружаем профиль пользователя
|
|
||||||
await loadUserProfile()
|
|
||||||
|
|
||||||
$('#service').on('change', function () {
|
|
||||||
const serviceName = $(this).val()
|
|
||||||
populateMethods(serviceName)
|
|
||||||
$('#params-container').empty()
|
|
||||||
})
|
|
||||||
|
|
||||||
$('#method').on('change', function () {
|
|
||||||
const serviceName = $('#service').val()
|
|
||||||
const methodName = $(this).val()
|
|
||||||
createParamInputs(methodName, serviceName)
|
|
||||||
})
|
|
||||||
|
|
||||||
$('#send-btn').on('click', sendRequest)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,22 +0,0 @@
|
||||||
{
|
|
||||||
"name": "app",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"description": "",
|
|
||||||
"main": "index.js",
|
|
||||||
"scripts": {
|
|
||||||
"dev": "vite",
|
|
||||||
"predev": "node -e \"const fs=require('fs'); try{fs.copyFileSync('../../api.spec.json','./api.spec.json');console.log('Copied api.spec.json');}catch(e){console.log('api.spec.json not found');}\"",
|
|
||||||
"build": "vite build",
|
|
||||||
"preview": "vite preview",
|
|
||||||
"test": "echo \"Error: no test specified\" && exit 1"
|
|
||||||
},
|
|
||||||
"keywords": [],
|
|
||||||
"author": "",
|
|
||||||
"license": "ISC",
|
|
||||||
"type": "commonjs",
|
|
||||||
"devDependencies": {
|
|
||||||
"jquery": "^3.7.1",
|
|
||||||
"rollup-plugin-copy": "^3.5.0",
|
|
||||||
"vite": "^7.3.1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,96 +0,0 @@
|
||||||
import { defineConfig } from 'vite'
|
|
||||||
import { resolve } from 'path'
|
|
||||||
import copy from 'rollup-plugin-copy'
|
|
||||||
|
|
||||||
export default defineConfig({
|
|
||||||
root: '.',
|
|
||||||
build: {
|
|
||||||
outDir: '../src/main/resources/htdocs/api-sandbox',
|
|
||||||
rollupOptions: {
|
|
||||||
plugins: [
|
|
||||||
copy({
|
|
||||||
targets: [
|
|
||||||
{
|
|
||||||
src: resolve(__dirname, '../../api.spec.json'),
|
|
||||||
dest: 'dist'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
hook: 'writeBundle'
|
|
||||||
})
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
resolve: {
|
|
||||||
alias: {
|
|
||||||
'@': resolve(__dirname, './')
|
|
||||||
}
|
|
||||||
},
|
|
||||||
server: {
|
|
||||||
port: 3000,
|
|
||||||
fs: {
|
|
||||||
allow: ['..', '../..']
|
|
||||||
},
|
|
||||||
historyApiFallback: false
|
|
||||||
},
|
|
||||||
publicDir: false,
|
|
||||||
configureServer(server) {
|
|
||||||
server.middlewares.use('/', (req, res, next) => {
|
|
||||||
if (req.url === '/api.spec.json' && req.method === 'GET') {
|
|
||||||
const fs = require('fs')
|
|
||||||
const path = require('path')
|
|
||||||
const apiSpecPath = path.resolve(__dirname, './api.spec.json')
|
|
||||||
|
|
||||||
console.log('Serving api.spec.json from:', apiSpecPath)
|
|
||||||
|
|
||||||
try {
|
|
||||||
const content = fs.readFileSync(apiSpecPath, 'utf-8')
|
|
||||||
res.setHeader('Content-Type', 'application/json')
|
|
||||||
res.setHeader('Cache-Control', 'no-cache')
|
|
||||||
res.end(content)
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error reading api.spec.json:', error)
|
|
||||||
res.statusCode = 404
|
|
||||||
res.setHeader('Content-Type', 'text/plain')
|
|
||||||
res.end('File not found')
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
next()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// Mock API endpoint for testing
|
|
||||||
server.middlewares.use('/api', (req, res, next) => {
|
|
||||||
if (req.method === 'POST') {
|
|
||||||
let body = ''
|
|
||||||
req.on('data', chunk => {
|
|
||||||
body += chunk.toString()
|
|
||||||
})
|
|
||||||
req.on('end', () => {
|
|
||||||
try {
|
|
||||||
const requestData = JSON.parse(body)
|
|
||||||
const response = {
|
|
||||||
jsonrpc: '2.0',
|
|
||||||
result: {
|
|
||||||
success: true,
|
|
||||||
message: `Method ${requestData.method} called with params: ${JSON.stringify(requestData.params)}`,
|
|
||||||
timestamp: new Date().toISOString()
|
|
||||||
},
|
|
||||||
id: requestData.id
|
|
||||||
}
|
|
||||||
res.setHeader('Content-Type', 'application/json')
|
|
||||||
res.end(JSON.stringify(response))
|
|
||||||
} catch (error) {
|
|
||||||
res.statusCode = 400
|
|
||||||
res.end(JSON.stringify({
|
|
||||||
jsonrpc: '2.0',
|
|
||||||
error: { code: -32700, message: 'Parse error' },
|
|
||||||
id: null
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
next()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
@ -1,64 +0,0 @@
|
||||||
<?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/xsd/maven-4.0.0.xsd">
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
|
||||||
<parent>
|
|
||||||
<groupId>ru.kirillius</groupId>
|
|
||||||
<artifactId>XCP</artifactId>
|
|
||||||
<version>1.0.0.0</version>
|
|
||||||
</parent>
|
|
||||||
|
|
||||||
<artifactId>api-sandbox</artifactId>
|
|
||||||
|
|
||||||
<properties>
|
|
||||||
<maven.compiler.source>21</maven.compiler.source>
|
|
||||||
<maven.compiler.target>21</maven.compiler.target>
|
|
||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
|
||||||
</properties>
|
|
||||||
|
|
||||||
<build>
|
|
||||||
<plugins>
|
|
||||||
<plugin>
|
|
||||||
<groupId>com.github.eirslett</groupId>
|
|
||||||
<artifactId>frontend-maven-plugin</artifactId>
|
|
||||||
<version>1.15.0</version>
|
|
||||||
<configuration>
|
|
||||||
<workingDirectory>app</workingDirectory>
|
|
||||||
<installDirectory>app</installDirectory>
|
|
||||||
</configuration>
|
|
||||||
<executions>
|
|
||||||
<execution>
|
|
||||||
<id>install node and npm</id>
|
|
||||||
<goals>
|
|
||||||
<goal>install-node-and-npm</goal>
|
|
||||||
</goals>
|
|
||||||
<configuration>
|
|
||||||
<nodeVersion>v22.12.0</nodeVersion>
|
|
||||||
<npmVersion>10.8.2</npmVersion>
|
|
||||||
</configuration>
|
|
||||||
</execution>
|
|
||||||
<execution>
|
|
||||||
<id>npm install</id>
|
|
||||||
<goals>
|
|
||||||
<goal>npm</goal>
|
|
||||||
</goals>
|
|
||||||
<configuration>
|
|
||||||
<arguments>install</arguments>
|
|
||||||
</configuration>
|
|
||||||
</execution>
|
|
||||||
<execution>
|
|
||||||
<id>npm build</id>
|
|
||||||
<goals>
|
|
||||||
<goal>npm</goal>
|
|
||||||
</goals>
|
|
||||||
<configuration>
|
|
||||||
<arguments>run build</arguments>
|
|
||||||
</configuration>
|
|
||||||
</execution>
|
|
||||||
</executions>
|
|
||||||
</plugin>
|
|
||||||
</plugins>
|
|
||||||
</build>
|
|
||||||
|
|
||||||
</project>
|
|
||||||
|
|
@ -1,48 +0,0 @@
|
||||||
<?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/xsd/maven-4.0.0.xsd">
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
|
||||||
<parent>
|
|
||||||
<groupId>ru.kirillius</groupId>
|
|
||||||
<artifactId>XCP</artifactId>
|
|
||||||
<version>1.0.0.0</version>
|
|
||||||
</parent>
|
|
||||||
|
|
||||||
<artifactId>api-spec-generator</artifactId>
|
|
||||||
|
|
||||||
<dependencies>
|
|
||||||
<dependency>
|
|
||||||
<groupId>ru.kirillius</groupId>
|
|
||||||
<artifactId>rpc</artifactId>
|
|
||||||
<version>1.0.0.0</version>
|
|
||||||
</dependency>
|
|
||||||
</dependencies>
|
|
||||||
|
|
||||||
<build>
|
|
||||||
<plugins>
|
|
||||||
<plugin>
|
|
||||||
<groupId>org.codehaus.mojo</groupId>
|
|
||||||
<artifactId>exec-maven-plugin</artifactId>
|
|
||||||
<version>3.1.0</version>
|
|
||||||
<executions>
|
|
||||||
<execution>
|
|
||||||
<id>generate-spec</id>
|
|
||||||
<phase>compile</phase>
|
|
||||||
<goals>
|
|
||||||
<goal>java</goal>
|
|
||||||
</goals>
|
|
||||||
<configuration>
|
|
||||||
<mainClass>ru.kirillius.XCP.ApiGenerator.SpecGenerator</mainClass>
|
|
||||||
<arguments>
|
|
||||||
<argument>rpc/src/main/java</argument>
|
|
||||||
<argument>ru.kirillius.XCP.RPC.Services</argument>
|
|
||||||
<argument>api.spec.json</argument>
|
|
||||||
</arguments>
|
|
||||||
</configuration>
|
|
||||||
</execution>
|
|
||||||
</executions>
|
|
||||||
</plugin>
|
|
||||||
</plugins>
|
|
||||||
</build>
|
|
||||||
</project>
|
|
||||||
|
|
@ -1,255 +0,0 @@
|
||||||
package ru.kirillius.XCP.ApiGenerator;
|
|
||||||
|
|
||||||
import lombok.SneakyThrows;
|
|
||||||
import ru.kirillius.XCP.Commons.GenerateApiSpec;
|
|
||||||
import ru.kirillius.XCP.RPC.JSONRPC.JsonRpcMethod;
|
|
||||||
import ru.kirillius.XCP.RPC.JSONRPC.JsonRpcService;
|
|
||||||
import tools.jackson.databind.JsonNode;
|
|
||||||
import tools.jackson.databind.ObjectMapper;
|
|
||||||
import tools.jackson.databind.node.ArrayNode;
|
|
||||||
import tools.jackson.databind.node.JsonNodeFactory;
|
|
||||||
import tools.jackson.databind.node.ObjectNode;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.concurrent.LinkedBlockingQueue;
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
|
|
||||||
public class SpecGenerator {
|
|
||||||
/**
|
|
||||||
* Args:
|
|
||||||
* 1 - path to find classes
|
|
||||||
* 2 - search in package
|
|
||||||
* 3 - output spec file path
|
|
||||||
*
|
|
||||||
* @param args
|
|
||||||
* @throws IOException
|
|
||||||
*/
|
|
||||||
public static void main(String[] args) throws IOException {
|
|
||||||
var generator = new SpecGenerator(
|
|
||||||
new File(args[0]),
|
|
||||||
args[1],
|
|
||||||
new File(args[2])
|
|
||||||
);
|
|
||||||
|
|
||||||
generator.generate();
|
|
||||||
generator.writeSpecs();
|
|
||||||
}
|
|
||||||
|
|
||||||
private final File scanDir;
|
|
||||||
private final String packageName;
|
|
||||||
private final File outputFile;
|
|
||||||
|
|
||||||
public SpecGenerator(File scanDir, String packageName, File outputFile) {
|
|
||||||
this.scanDir = scanDir;
|
|
||||||
this.packageName = packageName;
|
|
||||||
this.outputFile = outputFile;
|
|
||||||
}
|
|
||||||
|
|
||||||
private final ObjectNode specs = JsonNodeFactory.instance.objectNode();
|
|
||||||
private final Set<Class<?>> types = new HashSet<>();
|
|
||||||
private ObjectMapper mapper = new ObjectMapper();
|
|
||||||
|
|
||||||
@SneakyThrows
|
|
||||||
public void generate() {
|
|
||||||
specs.removeAll();
|
|
||||||
var modules = specs.putArray("modules");
|
|
||||||
var typesArray = specs.putArray("types");
|
|
||||||
var scanPath = new File(scanDir, packageName.replaceAll(Pattern.quote("."), "/"));
|
|
||||||
for (var file : Objects.requireNonNull(scanPath.listFiles())) {
|
|
||||||
if (!file.getName().endsWith(".java") || file.getName().contains("$")) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
var className = packageName + '.' + file.getName();
|
|
||||||
className = className.substring(0, className.length() - ".java".length());
|
|
||||||
|
|
||||||
var cls = Class.forName(className);
|
|
||||||
|
|
||||||
if (!JsonRpcService.class.isAssignableFrom(cls)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var classSpecs = modules.addObject();
|
|
||||||
classSpecs.put("name", cls.getSimpleName());
|
|
||||||
var methods = classSpecs.putArray("methods");
|
|
||||||
|
|
||||||
for (var method : cls.getDeclaredMethods()) {
|
|
||||||
var descriptor = method.getAnnotation(JsonRpcMethod.class);
|
|
||||||
if (descriptor == null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
var methodSpecs = methods.addObject();
|
|
||||||
methodSpecs.put("name", method.getName());
|
|
||||||
methodSpecs.put("description", descriptor.description());
|
|
||||||
methodSpecs.put("return", getTypeName(descriptor.returnType()));
|
|
||||||
methodSpecs.put("accessLevel", descriptor.accessLevel().name());
|
|
||||||
registerType(descriptor.returnType());
|
|
||||||
var params = methodSpecs.putArray("params");
|
|
||||||
|
|
||||||
for (var parameter : descriptor.parameters()) {
|
|
||||||
var paramSpecs = params.addObject();
|
|
||||||
paramSpecs.put("name", parameter.name());
|
|
||||||
paramSpecs.put("type", getTypeName(parameter.type()));
|
|
||||||
paramSpecs.put("description", parameter.description());
|
|
||||||
paramSpecs.put("optional", parameter.optional());
|
|
||||||
registerType(parameter.type());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
var typesQueue = new LinkedBlockingQueue<>(types);
|
|
||||||
Class<?> type;
|
|
||||||
while (!typesQueue.isEmpty()) {
|
|
||||||
type = typesQueue.poll();
|
|
||||||
if (type.isEnum()) {
|
|
||||||
var typeDescriptor = typesArray.addObject();
|
|
||||||
typeDescriptor.put("name", type.getSimpleName());
|
|
||||||
typeDescriptor.put("type", "enum");
|
|
||||||
var values = typeDescriptor.putArray("values");
|
|
||||||
for (var value : type.getEnumConstants()) {
|
|
||||||
values.add(value.toString());
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
var classAnnotation = type.getAnnotation(GenerateApiSpec.class);
|
|
||||||
if (classAnnotation == null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var aClass : classAnnotation.directInheritors()) {
|
|
||||||
if (!types.contains(aClass)) {
|
|
||||||
typesQueue.add(registerType(aClass));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var typeDescriptor = typesArray.addObject();
|
|
||||||
typeDescriptor.put("name", classAnnotation.alias().isEmpty() ? type.getSimpleName() : classAnnotation.alias());
|
|
||||||
typeDescriptor.put("type", "class");
|
|
||||||
var parents = typeDescriptor.putArray("parents");
|
|
||||||
for (var aClass : type.getInterfaces()) {
|
|
||||||
if (!types.contains(aClass)) {
|
|
||||||
|
|
||||||
typesQueue.add(registerType(aClass));
|
|
||||||
}
|
|
||||||
if (!aClass.isAnnotationPresent(GenerateApiSpec.class)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
parents.add(aClass.getSimpleName());
|
|
||||||
}
|
|
||||||
|
|
||||||
var fields = typeDescriptor.putArray("fields");
|
|
||||||
if (!type.isInterface()) {
|
|
||||||
for (var field : type.getDeclaredFields()) {
|
|
||||||
var annotation = field.getAnnotation(GenerateApiSpec.class);
|
|
||||||
if (annotation == null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
var returnType = annotation.type();
|
|
||||||
if (returnType == void.class) {
|
|
||||||
returnType = field.getType();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!types.contains(returnType)) {
|
|
||||||
typesQueue.put(registerType(returnType));
|
|
||||||
}
|
|
||||||
|
|
||||||
var fieldDescriptor = fields.addObject();
|
|
||||||
fieldDescriptor.put("name", annotation.alias().isEmpty() ? field.getName() : annotation.alias());
|
|
||||||
fieldDescriptor.put("type", getTypeName(returnType));
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var method : type.getMethods()) {
|
|
||||||
var methodAnnotation = method.getAnnotation(GenerateApiSpec.class);
|
|
||||||
if (methodAnnotation == null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
var returnType = methodAnnotation.type();
|
|
||||||
if (returnType == void.class) {
|
|
||||||
|
|
||||||
returnType = method.getReturnType();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!types.contains(returnType)) {
|
|
||||||
typesQueue.put(registerType(returnType));
|
|
||||||
}
|
|
||||||
|
|
||||||
var methodName = method.getName();
|
|
||||||
if (!methodName.startsWith("get") && !methodName.startsWith("is")) {
|
|
||||||
throw new RuntimeException("Invalid @" + GenerateApiSpec.class.getSimpleName() + " usage on " + type.getSimpleName() + "::" + methodName + ": Only getters are supported");
|
|
||||||
}
|
|
||||||
|
|
||||||
var fieldDescriptor = fields.addObject();
|
|
||||||
fieldDescriptor.put("name", methodAnnotation.alias().isEmpty() ? getVariableName(methodName) : methodAnnotation.alias());
|
|
||||||
fieldDescriptor.put("type", getTypeName(returnType));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getVariableName(String methodName) {
|
|
||||||
if (methodName.startsWith("get")) {
|
|
||||||
return methodName.substring(3, 4).toLowerCase() + methodName.substring(4);
|
|
||||||
} else if (methodName.startsWith("is")) {
|
|
||||||
return methodName.substring(2, 3).toLowerCase() + methodName.substring(3);
|
|
||||||
}
|
|
||||||
throw new RuntimeException("Invalid method name: " + methodName);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void writeSpecs() throws IOException {
|
|
||||||
var parentFile = outputFile.getParentFile();
|
|
||||||
if (parentFile != null) {
|
|
||||||
parentFile.mkdirs();
|
|
||||||
}
|
|
||||||
try (var stream = new FileOutputStream(outputFile)) {
|
|
||||||
var mapper = new ObjectMapper();
|
|
||||||
mapper.writerWithDefaultPrettyPrinter().writeValue(stream, specs);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Class<?> registerType(Class<?> type) {
|
|
||||||
if (type.isArray()) {
|
|
||||||
types.add(type.getComponentType());
|
|
||||||
return type.getComponentType();
|
|
||||||
} else {
|
|
||||||
types.add(type);
|
|
||||||
return type;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getTypeName(Class<?> type) {
|
|
||||||
if (type == null) return "unknown";
|
|
||||||
if (type == boolean.class) return "boolean";
|
|
||||||
if (type == int.class || type == long.class || type == double.class || type == float.class) return "number";
|
|
||||||
if (type == String.class) return "string";
|
|
||||||
if (type == void.class || type == Void.class) return "void";
|
|
||||||
|
|
||||||
if (type.isArray()) {
|
|
||||||
var componentType = type.getComponentType();
|
|
||||||
if (componentType != null) {
|
|
||||||
return componentType.getSimpleName() + "[]";
|
|
||||||
} else {
|
|
||||||
return "[]";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Collection.class.isAssignableFrom(type) || type == ArrayNode.class) {
|
|
||||||
return "array";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (JsonNode.class.isAssignableFrom(type)) {
|
|
||||||
return "object";
|
|
||||||
}
|
|
||||||
|
|
||||||
return type.getSimpleName();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
21
api/pom.xml
21
api/pom.xml
|
|
@ -1,21 +0,0 @@
|
||||||
<?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/xsd/maven-4.0.0.xsd">
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
|
||||||
<parent>
|
|
||||||
<groupId>ru.kirillius</groupId>
|
|
||||||
<artifactId>XCP</artifactId>
|
|
||||||
<version>1.0.0.0</version>
|
|
||||||
</parent>
|
|
||||||
|
|
||||||
<artifactId>api</artifactId>
|
|
||||||
|
|
||||||
<dependencies>
|
|
||||||
<dependency>
|
|
||||||
<groupId>ru.kirillius</groupId>
|
|
||||||
<artifactId>java-events</artifactId>
|
|
||||||
<version>1.1.0.0</version>
|
|
||||||
</dependency>
|
|
||||||
</dependencies>
|
|
||||||
</project>
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
package ru.kirillius.XCP.Commons;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
|
|
||||||
public interface
|
|
||||||
Config {
|
|
||||||
|
|
||||||
File getLoadedConfigFile();
|
|
||||||
|
|
||||||
String getHost();
|
|
||||||
|
|
||||||
void setHost(String host);
|
|
||||||
|
|
||||||
File getDatabaseFile();
|
|
||||||
|
|
||||||
void setDatabaseFile(File databaseFile);
|
|
||||||
|
|
||||||
int getHttpPort();
|
|
||||||
|
|
||||||
void setHttpPort(int httpPort);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
package ru.kirillius.XCP.Commons;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
public interface ConfigManager {
|
|
||||||
File getConfigFile();
|
|
||||||
|
|
||||||
boolean isExist();
|
|
||||||
|
|
||||||
Config load() throws IOException;
|
|
||||||
|
|
||||||
Config create();
|
|
||||||
|
|
||||||
void save(Config config) throws IOException;
|
|
||||||
}
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
package ru.kirillius.XCP.Commons;
|
|
||||||
|
|
||||||
import ru.kirillius.XCP.Logging.LoggingSystem;
|
|
||||||
import ru.kirillius.XCP.Security.SecurityManager;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public interface Context {
|
|
||||||
Config getConfig();
|
|
||||||
|
|
||||||
ConfigManager getConfigManager();
|
|
||||||
|
|
||||||
<S extends Service> S getService(Class<S> serviceClass);
|
|
||||||
|
|
||||||
void shutdown();
|
|
||||||
|
|
||||||
SecurityManager getSecurityManager();
|
|
||||||
|
|
||||||
LoggingSystem getLoggingSystem();
|
|
||||||
|
|
||||||
List<String> getLaunchArgs();
|
|
||||||
|
|
||||||
boolean isDebuggingEnabled();
|
|
||||||
}
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
package ru.kirillius.XCP.Commons;
|
|
||||||
|
|
||||||
import java.lang.annotation.ElementType;
|
|
||||||
import java.lang.annotation.Retention;
|
|
||||||
import java.lang.annotation.RetentionPolicy;
|
|
||||||
import java.lang.annotation.Target;
|
|
||||||
|
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
|
||||||
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
|
|
||||||
public @interface GenerateApiSpec {
|
|
||||||
String alias() default "";
|
|
||||||
|
|
||||||
Class<?> type() default void.class;
|
|
||||||
|
|
||||||
Class<?>[] directInheritors() default {};
|
|
||||||
}
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
package ru.kirillius.XCP.Commons;
|
|
||||||
|
|
||||||
import java.io.Closeable;
|
|
||||||
|
|
||||||
public interface Initializable extends Closeable {
|
|
||||||
void initialize(Context context);
|
|
||||||
}
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
package ru.kirillius.XCP.Commons;
|
|
||||||
|
|
||||||
import java.io.Closeable;
|
|
||||||
|
|
||||||
public interface ResourceHandler<T> extends Closeable {
|
|
||||||
T get();
|
|
||||||
}
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
package ru.kirillius.XCP.Commons;
|
|
||||||
|
|
||||||
public interface Service extends Initializable {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
package ru.kirillius.XCP.Commons;
|
|
||||||
|
|
||||||
import java.util.stream.Stream;
|
|
||||||
|
|
||||||
public interface StreamHandler<T> extends ResourceHandler<Stream<T>> {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
package ru.kirillius.XCP.Data;
|
|
||||||
|
|
||||||
import ru.kirillius.XCP.Commons.Initializable;
|
|
||||||
import ru.kirillius.java.utils.events.EventHandler;
|
|
||||||
import tools.jackson.databind.node.ObjectNode;
|
|
||||||
|
|
||||||
|
|
||||||
public interface DataAdapter extends Initializable {
|
|
||||||
Object send(Object value, ObjectNode properties);
|
|
||||||
|
|
||||||
Object receive(ObjectNode properties);
|
|
||||||
|
|
||||||
EventHandler<Object> subscribe();
|
|
||||||
|
|
||||||
void unsubscribe(EventHandler<Object> subscription);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
package ru.kirillius.XCP.Data;
|
|
||||||
|
|
||||||
public interface PollSettings {
|
|
||||||
long getPollInterval();
|
|
||||||
|
|
||||||
void setPollInterval(long pollInterval);
|
|
||||||
|
|
||||||
boolean isInterruptable();
|
|
||||||
|
|
||||||
void setInterruptable(boolean interruptable);
|
|
||||||
|
|
||||||
boolean isRateMeasurement();
|
|
||||||
|
|
||||||
void setRateMeasurement(boolean rateMeasurement);
|
|
||||||
|
|
||||||
void setEnabled(boolean enabled);
|
|
||||||
|
|
||||||
boolean isEnabled();
|
|
||||||
}
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
package ru.kirillius.XCP.Data;
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
|
||||||
import com.fasterxml.jackson.annotation.JsonSetter;
|
|
||||||
import com.fasterxml.jackson.annotation.Nulls;
|
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.NoArgsConstructor;
|
|
||||||
import lombok.Setter;
|
|
||||||
import tools.jackson.databind.annotation.JsonDeserialize;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
@NoArgsConstructor
|
|
||||||
@AllArgsConstructor
|
|
||||||
public final class ValueTransformationChain {
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
@JsonSetter(nulls = Nulls.SKIP)
|
|
||||||
@JsonProperty
|
|
||||||
@JsonDeserialize(contentAs = ValueTransformationStep.class)
|
|
||||||
private List<ValueTransformationStep> steps = new ArrayList<>();
|
|
||||||
}
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
package ru.kirillius.XCP.Data;
|
|
||||||
|
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.NoArgsConstructor;
|
|
||||||
import lombok.Setter;
|
|
||||||
import tools.jackson.databind.node.ObjectNode;
|
|
||||||
|
|
||||||
@NoArgsConstructor
|
|
||||||
@AllArgsConstructor
|
|
||||||
public final class ValueTransformationStep {
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
private String transformerId;
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
ObjectNode configuration;
|
|
||||||
}
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
package ru.kirillius.XCP.Data;
|
|
||||||
|
|
||||||
import org.javatuples.Pair;
|
|
||||||
import tools.jackson.databind.node.ObjectNode;
|
|
||||||
|
|
||||||
import java.util.function.Function;
|
|
||||||
|
|
||||||
public interface ValueTransformer extends Function<Pair<Object, ObjectNode>, Object> {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
package ru.kirillius.XCP.Data;
|
|
||||||
|
|
||||||
import java.lang.annotation.ElementType;
|
|
||||||
import java.lang.annotation.Retention;
|
|
||||||
import java.lang.annotation.RetentionPolicy;
|
|
||||||
import java.lang.annotation.Target;
|
|
||||||
|
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
|
||||||
@Target(ElementType.TYPE)
|
|
||||||
public @interface ValueTransformerDescriptor {
|
|
||||||
String transformerId() default "";
|
|
||||||
}
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
package ru.kirillius.XCP.Logging;
|
|
||||||
|
|
||||||
public enum LogLevel {
|
|
||||||
INFO,
|
|
||||||
ERROR,
|
|
||||||
WARNING
|
|
||||||
}
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
package ru.kirillius.XCP.Logging;
|
|
||||||
|
|
||||||
import lombok.Builder;
|
|
||||||
|
|
||||||
import java.time.Instant;
|
|
||||||
|
|
||||||
@Builder
|
|
||||||
public record LogMessage(LogLevel level, Instant date, String message) {
|
|
||||||
}
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
package ru.kirillius.XCP.Logging;
|
|
||||||
|
|
||||||
public interface Logger {
|
|
||||||
|
|
||||||
|
|
||||||
void info(String message);
|
|
||||||
|
|
||||||
void warning(String message);
|
|
||||||
|
|
||||||
void error(String message);
|
|
||||||
|
|
||||||
void error(String message, Throwable error);
|
|
||||||
|
|
||||||
void error(Throwable error);
|
|
||||||
}
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
package ru.kirillius.XCP.Logging;
|
|
||||||
|
|
||||||
import ru.kirillius.java.utils.events.EventHandler;
|
|
||||||
|
|
||||||
import java.io.Closeable;
|
|
||||||
|
|
||||||
public interface LoggingSystem extends Closeable {
|
|
||||||
|
|
||||||
Logger createLogger(Class<?> clazz);
|
|
||||||
|
|
||||||
Logger createLogger(String name);
|
|
||||||
|
|
||||||
EventHandler<LogMessage> getEventHandler();
|
|
||||||
}
|
|
||||||
|
|
@ -1,36 +0,0 @@
|
||||||
package ru.kirillius.XCP.Persistence.Entities;
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
|
||||||
import ru.kirillius.XCP.Commons.GenerateApiSpec;
|
|
||||||
import ru.kirillius.XCP.Persistence.PersistenceEntity;
|
|
||||||
|
|
||||||
import java.time.Instant;
|
|
||||||
import java.util.Date;
|
|
||||||
|
|
||||||
@GenerateApiSpec
|
|
||||||
public interface ApiToken extends PersistenceEntity {
|
|
||||||
|
|
||||||
@GenerateApiSpec(type = PersistenceEntity.class)
|
|
||||||
User getUser();
|
|
||||||
|
|
||||||
void setUser(User user);
|
|
||||||
|
|
||||||
@GenerateApiSpec
|
|
||||||
String getName();
|
|
||||||
|
|
||||||
void setName(String name);
|
|
||||||
|
|
||||||
@GenerateApiSpec
|
|
||||||
Date getExpirationDate();
|
|
||||||
|
|
||||||
void setExpirationDate(Date expirationDate);
|
|
||||||
|
|
||||||
@JsonIgnore
|
|
||||||
default boolean isExpired() {
|
|
||||||
var expirationDate = getExpirationDate();
|
|
||||||
if (expirationDate == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return expirationDate.toInstant().isBefore(Instant.now());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
package ru.kirillius.XCP.Persistence.Entities;
|
|
||||||
|
|
||||||
import ru.kirillius.XCP.Commons.GenerateApiSpec;
|
|
||||||
import ru.kirillius.XCP.Persistence.NodeEntity;
|
|
||||||
|
|
||||||
@GenerateApiSpec
|
|
||||||
public interface Group extends NodeEntity {
|
|
||||||
String getIcon();
|
|
||||||
|
|
||||||
void setIcon(String icon);
|
|
||||||
|
|
||||||
@GenerateApiSpec
|
|
||||||
boolean isPrototype();
|
|
||||||
|
|
||||||
void setPrototype(boolean prototype);
|
|
||||||
}
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
package ru.kirillius.XCP.Persistence.Entities;
|
|
||||||
|
|
||||||
import ru.kirillius.XCP.Commons.GenerateApiSpec;
|
|
||||||
import ru.kirillius.XCP.Data.PollSettings;
|
|
||||||
import ru.kirillius.XCP.Persistence.IOEntity;
|
|
||||||
import ru.kirillius.XCP.Persistence.NodeEntity;
|
|
||||||
|
|
||||||
@GenerateApiSpec
|
|
||||||
public interface Input extends IOEntity, NodeEntity {
|
|
||||||
@GenerateApiSpec
|
|
||||||
PollSettings getPollSettings();
|
|
||||||
|
|
||||||
void setPollSettings(PollSettings pollSettings);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
package ru.kirillius.XCP.Persistence.Entities;
|
|
||||||
|
|
||||||
import ru.kirillius.XCP.Commons.GenerateApiSpec;
|
|
||||||
import ru.kirillius.XCP.Persistence.IOEntity;
|
|
||||||
import ru.kirillius.XCP.Persistence.NodeEntity;
|
|
||||||
@GenerateApiSpec
|
|
||||||
public interface Output extends IOEntity, NodeEntity {
|
|
||||||
}
|
|
||||||
|
|
@ -1,39 +0,0 @@
|
||||||
package ru.kirillius.XCP.Persistence.Entities;
|
|
||||||
|
|
||||||
import ru.kirillius.XCP.Commons.GenerateApiSpec;
|
|
||||||
import ru.kirillius.XCP.Persistence.PersistenceEntity;
|
|
||||||
import ru.kirillius.XCP.Properties.Constraint;
|
|
||||||
import ru.kirillius.XCP.Properties.Constraints;
|
|
||||||
import ru.kirillius.XCP.Properties.PropertyTarget;
|
|
||||||
import ru.kirillius.XCP.Properties.PropertyType;
|
|
||||||
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
@GenerateApiSpec
|
|
||||||
public interface PropertyDescriptor extends PersistenceEntity {
|
|
||||||
|
|
||||||
@GenerateApiSpec(type = PropertyTarget[].class, alias = "targets")
|
|
||||||
Set<PropertyTarget> getPropertyTargets();
|
|
||||||
|
|
||||||
void setPropertyTargets(Set<PropertyTarget> propertyTargets);
|
|
||||||
|
|
||||||
@GenerateApiSpec(type = Constraint[].class)
|
|
||||||
Constraints getConstraints();
|
|
||||||
|
|
||||||
void setConstraints(Constraints constraints);
|
|
||||||
|
|
||||||
@GenerateApiSpec
|
|
||||||
boolean isArray();
|
|
||||||
|
|
||||||
void setArray(boolean isArray);
|
|
||||||
|
|
||||||
@GenerateApiSpec
|
|
||||||
PropertyType getPropertyType();
|
|
||||||
|
|
||||||
void setPropertyType(PropertyType propertyType);
|
|
||||||
|
|
||||||
@GenerateApiSpec
|
|
||||||
String getName();
|
|
||||||
|
|
||||||
void setName(String name);
|
|
||||||
}
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
package ru.kirillius.XCP.Persistence.Entities;
|
|
||||||
|
|
||||||
import ru.kirillius.XCP.Commons.GenerateApiSpec;
|
|
||||||
import ru.kirillius.XCP.Persistence.PersistenceEntity;
|
|
||||||
|
|
||||||
@GenerateApiSpec
|
|
||||||
public interface Tag extends PersistenceEntity {
|
|
||||||
@GenerateApiSpec
|
|
||||||
String getName();
|
|
||||||
|
|
||||||
void setName(String name);
|
|
||||||
}
|
|
||||||
|
|
@ -1,33 +0,0 @@
|
||||||
package ru.kirillius.XCP.Persistence.Entities;
|
|
||||||
|
|
||||||
import ru.kirillius.XCP.Commons.GenerateApiSpec;
|
|
||||||
import ru.kirillius.XCP.Persistence.PersistenceEntity;
|
|
||||||
import ru.kirillius.XCP.Security.UserRole;
|
|
||||||
import tools.jackson.databind.node.ObjectNode;
|
|
||||||
|
|
||||||
@GenerateApiSpec
|
|
||||||
public interface User extends PersistenceEntity {
|
|
||||||
void setPassword(String password);
|
|
||||||
|
|
||||||
boolean verifyPassword(String password);
|
|
||||||
|
|
||||||
@GenerateApiSpec
|
|
||||||
String getLogin();
|
|
||||||
|
|
||||||
void setLogin(String login);
|
|
||||||
|
|
||||||
@GenerateApiSpec
|
|
||||||
UserRole getRole();
|
|
||||||
|
|
||||||
void setRole(UserRole role);
|
|
||||||
|
|
||||||
@GenerateApiSpec
|
|
||||||
ObjectNode getValues();
|
|
||||||
|
|
||||||
void setValues(ObjectNode values);
|
|
||||||
|
|
||||||
@GenerateApiSpec
|
|
||||||
String getName();
|
|
||||||
|
|
||||||
void setName(String name);
|
|
||||||
}
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
package ru.kirillius.XCP.Persistence;
|
|
||||||
|
|
||||||
import ru.kirillius.XCP.Data.ValueTransformationChain;
|
|
||||||
|
|
||||||
public interface IOEntity extends NodeEntity {
|
|
||||||
ValueTransformationChain getTransformationChain();
|
|
||||||
|
|
||||||
void setTransformationChain(ValueTransformationChain transformationChain);
|
|
||||||
|
|
||||||
String getAdapterId();
|
|
||||||
|
|
||||||
void setAdapterId(String adapterId);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,37 +0,0 @@
|
||||||
package ru.kirillius.XCP.Persistence;
|
|
||||||
|
|
||||||
import ru.kirillius.XCP.Commons.GenerateApiSpec;
|
|
||||||
import ru.kirillius.XCP.Persistence.Entities.Group;
|
|
||||||
import tools.jackson.databind.node.ObjectNode;
|
|
||||||
|
|
||||||
public interface NodeEntity extends PersistenceEntity {
|
|
||||||
@GenerateApiSpec
|
|
||||||
String getName();
|
|
||||||
|
|
||||||
void setName(String name);
|
|
||||||
|
|
||||||
@GenerateApiSpec
|
|
||||||
boolean isProtectedEntity();
|
|
||||||
|
|
||||||
void setProtectedEntity(boolean essential);
|
|
||||||
|
|
||||||
@GenerateApiSpec
|
|
||||||
boolean isEnabled();
|
|
||||||
|
|
||||||
void setEnabled(boolean enabled);
|
|
||||||
|
|
||||||
@GenerateApiSpec(type = PersistenceEntity.class)
|
|
||||||
Group getParent();
|
|
||||||
|
|
||||||
void setParent(Group parent);
|
|
||||||
|
|
||||||
@GenerateApiSpec
|
|
||||||
ObjectNode getProperties();
|
|
||||||
|
|
||||||
void setProperties(ObjectNode properties);
|
|
||||||
|
|
||||||
@GenerateApiSpec
|
|
||||||
TagCollection getTags();
|
|
||||||
|
|
||||||
void setTags(TagCollection tags);
|
|
||||||
}
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
package ru.kirillius.XCP.Persistence;
|
|
||||||
|
|
||||||
import ru.kirillius.XCP.Commons.StreamHandler;
|
|
||||||
import ru.kirillius.XCP.Persistence.Entities.Group;
|
|
||||||
import ru.kirillius.XCP.Persistence.Entities.Tag;
|
|
||||||
|
|
||||||
import java.util.Collection;
|
|
||||||
|
|
||||||
public interface NodeRepository<E extends NodeEntity> extends Repository<E> {
|
|
||||||
StreamHandler<E> getByGroup(Group group);
|
|
||||||
|
|
||||||
StreamHandler<E> getByAllTags(Collection<Tag> tags);
|
|
||||||
|
|
||||||
StreamHandler<E> getByAnyTag(Collection<Tag> tags);
|
|
||||||
}
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
package ru.kirillius.XCP.Persistence;
|
|
||||||
|
|
||||||
import ru.kirillius.XCP.Commons.GenerateApiSpec;
|
|
||||||
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
@GenerateApiSpec
|
|
||||||
public interface PersistenceEntity {
|
|
||||||
@GenerateApiSpec
|
|
||||||
long getId();
|
|
||||||
|
|
||||||
@GenerateApiSpec(type = String.class)
|
|
||||||
UUID getUuid();
|
|
||||||
}
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
package ru.kirillius.XCP.Persistence;
|
|
||||||
|
|
||||||
public class PersistenceException extends RuntimeException {
|
|
||||||
public PersistenceException(String message) {
|
|
||||||
super(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
public PersistenceException(String message, Throwable cause) {
|
|
||||||
super(message, cause);
|
|
||||||
}
|
|
||||||
|
|
||||||
public PersistenceException(Throwable cause) {
|
|
||||||
super(cause);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
package ru.kirillius.XCP.Persistence.Repositories;
|
|
||||||
|
|
||||||
import ru.kirillius.XCP.Commons.StreamHandler;
|
|
||||||
import ru.kirillius.XCP.Persistence.Entities.ApiToken;
|
|
||||||
import ru.kirillius.XCP.Persistence.Entities.User;
|
|
||||||
import ru.kirillius.XCP.Persistence.Repository;
|
|
||||||
|
|
||||||
public interface ApiTokenRepository extends Repository<ApiToken> {
|
|
||||||
StreamHandler<ApiToken> getByUser(User user);
|
|
||||||
}
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
package ru.kirillius.XCP.Persistence.Repositories;
|
|
||||||
|
|
||||||
import ru.kirillius.XCP.Commons.StreamHandler;
|
|
||||||
import ru.kirillius.XCP.Persistence.Entities.Group;
|
|
||||||
import ru.kirillius.XCP.Persistence.NodeRepository;
|
|
||||||
|
|
||||||
public interface GroupRepository extends NodeRepository<Group> {
|
|
||||||
StreamHandler<Group> getChildrenOf(Group group);
|
|
||||||
|
|
||||||
StreamHandler<Group> getAllChildrenInHierarchy(Group group);
|
|
||||||
|
|
||||||
Group getRoot();
|
|
||||||
}
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
package ru.kirillius.XCP.Persistence.Repositories;
|
|
||||||
|
|
||||||
import ru.kirillius.XCP.Persistence.Entities.Input;
|
|
||||||
import ru.kirillius.XCP.Persistence.NodeRepository;
|
|
||||||
|
|
||||||
public interface InputRepository extends NodeRepository<Input> {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
package ru.kirillius.XCP.Persistence.Repositories;
|
|
||||||
|
|
||||||
import ru.kirillius.XCP.Persistence.Entities.Output;
|
|
||||||
import ru.kirillius.XCP.Persistence.NodeRepository;
|
|
||||||
|
|
||||||
public interface OutputRepository extends NodeRepository<Output> {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
package ru.kirillius.XCP.Persistence.Repositories;
|
|
||||||
|
|
||||||
import ru.kirillius.XCP.Persistence.Entities.PropertyDescriptor;
|
|
||||||
import ru.kirillius.XCP.Persistence.Repository;
|
|
||||||
|
|
||||||
public interface PropertyDescriptorRepository extends Repository<PropertyDescriptor> {
|
|
||||||
PropertyDescriptor getByName(String name);
|
|
||||||
}
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
package ru.kirillius.XCP.Persistence.Repositories;
|
|
||||||
|
|
||||||
import ru.kirillius.XCP.Commons.ResourceHandler;
|
|
||||||
import ru.kirillius.XCP.Persistence.Entities.Tag;
|
|
||||||
import ru.kirillius.XCP.Persistence.Repository;
|
|
||||||
import ru.kirillius.XCP.Persistence.TagCollection;
|
|
||||||
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.stream.Stream;
|
|
||||||
|
|
||||||
public interface TagRepository extends Repository<Tag> {
|
|
||||||
Tag getByNameOrCreate(String name);
|
|
||||||
|
|
||||||
TagCollection getByNamesOrCreate(Collection<String> names);
|
|
||||||
|
|
||||||
TagCollection createCollection();
|
|
||||||
|
|
||||||
TagCollection createCollection(ResourceHandler<Stream<Tag>> handler);
|
|
||||||
}
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
package ru.kirillius.XCP.Persistence.Repositories;
|
|
||||||
|
|
||||||
import ru.kirillius.XCP.Persistence.Entities.User;
|
|
||||||
import ru.kirillius.XCP.Persistence.Repository;
|
|
||||||
|
|
||||||
public interface UserRepository extends Repository<User> {
|
|
||||||
|
|
||||||
User getByLogin(String login);
|
|
||||||
}
|
|
||||||
|
|
@ -1,49 +0,0 @@
|
||||||
package ru.kirillius.XCP.Persistence;
|
|
||||||
|
|
||||||
import ru.kirillius.XCP.Commons.StreamHandler;
|
|
||||||
import ru.kirillius.java.utils.events.EventHandler;
|
|
||||||
import tools.jackson.databind.node.ArrayNode;
|
|
||||||
import tools.jackson.databind.node.ObjectNode;
|
|
||||||
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
public interface Repository<E extends PersistenceEntity> {
|
|
||||||
E create();
|
|
||||||
|
|
||||||
E get(long id);
|
|
||||||
|
|
||||||
E get(UUID uuid);
|
|
||||||
|
|
||||||
StreamHandler<E> get(Collection<Long> ids);
|
|
||||||
|
|
||||||
StreamHandler<E> search(String query, Collection<Object> queryParameters);
|
|
||||||
|
|
||||||
EventBindings<E> events();
|
|
||||||
|
|
||||||
StreamHandler<E> getAll();
|
|
||||||
|
|
||||||
long getCount();
|
|
||||||
|
|
||||||
void save(E entity);
|
|
||||||
|
|
||||||
void save(Collection<E> entities);
|
|
||||||
|
|
||||||
void remove(E entity);
|
|
||||||
|
|
||||||
void remove(Collection<E> entities);
|
|
||||||
|
|
||||||
ObjectNode serialize(E entity);
|
|
||||||
|
|
||||||
ArrayNode serialize(Collection<E> entities);
|
|
||||||
|
|
||||||
E deserialize(ObjectNode object);
|
|
||||||
|
|
||||||
Collection<E> deserialize(ArrayNode array);
|
|
||||||
|
|
||||||
interface EventBindings<E> {
|
|
||||||
EventHandler<E> entityStored();
|
|
||||||
|
|
||||||
EventHandler<E> entityRemoved();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
package ru.kirillius.XCP.Persistence;
|
|
||||||
|
|
||||||
import ru.kirillius.XCP.Persistence.Entities.Tag;
|
|
||||||
|
|
||||||
import java.util.Collection;
|
|
||||||
|
|
||||||
public interface TagCollection extends Collection<Tag> {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
package ru.kirillius.XCP.Properties;
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
|
||||||
import ru.kirillius.XCP.Commons.GenerateApiSpec;
|
|
||||||
|
|
||||||
@GenerateApiSpec(directInheritors = {
|
|
||||||
NumberConstraint.class,
|
|
||||||
ValueListConstraint.class,
|
|
||||||
StringConstraint.class
|
|
||||||
})
|
|
||||||
public interface Constraint {
|
|
||||||
@JsonProperty(value = "type")
|
|
||||||
@GenerateApiSpec
|
|
||||||
default String getType() {
|
|
||||||
return getClass().getSimpleName();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
package ru.kirillius.XCP.Properties;
|
|
||||||
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
public interface Constraints extends Set<Constraint> {
|
|
||||||
}
|
|
||||||
|
|
@ -1,26 +0,0 @@
|
||||||
package ru.kirillius.XCP.Properties;
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
|
||||||
import lombok.*;
|
|
||||||
import ru.kirillius.XCP.Commons.GenerateApiSpec;
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
@Builder
|
|
||||||
@AllArgsConstructor
|
|
||||||
@GenerateApiSpec
|
|
||||||
@NoArgsConstructor
|
|
||||||
public final class NumberConstraint implements Constraint {
|
|
||||||
@JsonProperty
|
|
||||||
@GenerateApiSpec
|
|
||||||
private double min;
|
|
||||||
@JsonProperty
|
|
||||||
@GenerateApiSpec
|
|
||||||
private double max;
|
|
||||||
@JsonProperty
|
|
||||||
@GenerateApiSpec
|
|
||||||
private double step;
|
|
||||||
@JsonProperty
|
|
||||||
@GenerateApiSpec
|
|
||||||
private boolean integer;
|
|
||||||
}
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
package ru.kirillius.XCP.Properties;
|
|
||||||
|
|
||||||
public enum PropertyTarget {
|
|
||||||
Users,
|
|
||||||
Data,
|
|
||||||
Any
|
|
||||||
}
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
package ru.kirillius.XCP.Properties;
|
|
||||||
|
|
||||||
public enum PropertyType {
|
|
||||||
Number,
|
|
||||||
Boolean,
|
|
||||||
Text,
|
|
||||||
ValueList,
|
|
||||||
JSON
|
|
||||||
}
|
|
||||||
|
|
@ -1,25 +0,0 @@
|
||||||
package ru.kirillius.XCP.Properties;
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
|
||||||
import lombok.*;
|
|
||||||
import ru.kirillius.XCP.Commons.GenerateApiSpec;
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
@Builder
|
|
||||||
@AllArgsConstructor
|
|
||||||
@GenerateApiSpec
|
|
||||||
@NoArgsConstructor
|
|
||||||
public final class StringConstraint implements Constraint {
|
|
||||||
@JsonProperty
|
|
||||||
@GenerateApiSpec
|
|
||||||
private int maxLength;
|
|
||||||
|
|
||||||
@JsonProperty
|
|
||||||
@GenerateApiSpec
|
|
||||||
private boolean multiline;
|
|
||||||
|
|
||||||
@JsonProperty
|
|
||||||
@GenerateApiSpec
|
|
||||||
private String regexp;
|
|
||||||
}
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
package ru.kirillius.XCP.Properties;
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
|
||||||
import lombok.*;
|
|
||||||
import ru.kirillius.XCP.Commons.GenerateApiSpec;
|
|
||||||
import tools.jackson.databind.node.ArrayNode;
|
|
||||||
|
|
||||||
import java.util.Collection;
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
@Builder
|
|
||||||
@AllArgsConstructor
|
|
||||||
@GenerateApiSpec
|
|
||||||
@NoArgsConstructor
|
|
||||||
public final class ValueListConstraint implements Constraint {
|
|
||||||
@JsonProperty
|
|
||||||
@GenerateApiSpec(type = ArrayNode.class)
|
|
||||||
private Collection<String> values;
|
|
||||||
}
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
package ru.kirillius.XCP.Security;
|
|
||||||
|
|
||||||
public interface HashUtility {
|
|
||||||
String hash(String password);
|
|
||||||
|
|
||||||
default boolean validate(String password, String hash) {
|
|
||||||
return hash(password).equals(hash);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
package ru.kirillius.XCP.Security;
|
|
||||||
|
|
||||||
public interface SecurityManager {
|
|
||||||
HashUtility getHashUtility();
|
|
||||||
}
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
package ru.kirillius.XCP.Security;
|
|
||||||
|
|
||||||
import lombok.Getter;
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
public enum UserRole {
|
|
||||||
Guest(0),
|
|
||||||
User(1),
|
|
||||||
Operator(2),
|
|
||||||
Admin(3);
|
|
||||||
|
|
||||||
private final int level;
|
|
||||||
|
|
||||||
UserRole(int level) {
|
|
||||||
this.level = level;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
package ru.kirillius.XCP.Services;
|
|
||||||
|
|
||||||
import ru.kirillius.XCP.Commons.Service;
|
|
||||||
import ru.kirillius.XCP.Persistence.PersistenceEntity;
|
|
||||||
import ru.kirillius.XCP.Persistence.Repository;
|
|
||||||
import tools.jackson.databind.ObjectMapper;
|
|
||||||
|
|
||||||
public interface RepositoryService extends Service {
|
|
||||||
<E extends PersistenceEntity> Repository<E> getRepositoryForEntity(Class<E> entityType);
|
|
||||||
|
|
||||||
<R extends Repository<?>> R getRepository(Class<R> repositoryType);
|
|
||||||
|
|
||||||
Class<? extends PersistenceEntity> getEntityBaseType(Class<? extends PersistenceEntity> entityClass);
|
|
||||||
|
|
||||||
Class<? extends Repository<?>> getRepositoryBaseType(Class<? extends Repository<?>> repositoryClass);
|
|
||||||
|
|
||||||
Class<? extends PersistenceEntity> getRepositoryEntityType(Class<? extends Repository<?>> repositoryClass);
|
|
||||||
|
|
||||||
ObjectMapper getMapper();
|
|
||||||
|
|
||||||
Class<? extends PersistenceEntity> getEntityTypeByName(String entityName);
|
|
||||||
}
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
package ru.kirillius.XCP.Services;
|
|
||||||
|
|
||||||
import java.lang.annotation.ElementType;
|
|
||||||
import java.lang.annotation.Retention;
|
|
||||||
import java.lang.annotation.RetentionPolicy;
|
|
||||||
import java.lang.annotation.Target;
|
|
||||||
|
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
|
||||||
@Target(ElementType.TYPE)
|
|
||||||
public @interface ServiceLoadPriority {
|
|
||||||
int value() default 1000;
|
|
||||||
}
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
package ru.kirillius.XCP.Services;
|
|
||||||
|
|
||||||
import ru.kirillius.XCP.Commons.Service;
|
|
||||||
|
|
||||||
public interface WebService extends Service {
|
|
||||||
}
|
|
||||||
52
app/pom.xml
52
app/pom.xml
|
|
@ -1,52 +0,0 @@
|
||||||
<?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/xsd/maven-4.0.0.xsd">
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
|
||||||
<parent>
|
|
||||||
<groupId>ru.kirillius</groupId>
|
|
||||||
<artifactId>XCP</artifactId>
|
|
||||||
<version>1.0.0.0</version>
|
|
||||||
</parent>
|
|
||||||
|
|
||||||
<artifactId>app</artifactId>
|
|
||||||
|
|
||||||
<properties>
|
|
||||||
<maven.compiler.source>21</maven.compiler.source>
|
|
||||||
<maven.compiler.target>21</maven.compiler.target>
|
|
||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
|
||||||
</properties>
|
|
||||||
<dependencies>
|
|
||||||
<dependency>
|
|
||||||
<groupId>ru.kirillius</groupId>
|
|
||||||
<artifactId>api</artifactId>
|
|
||||||
<version>1.0.0.0</version>
|
|
||||||
<scope>compile</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>ru.kirillius</groupId>
|
|
||||||
<artifactId>core</artifactId>
|
|
||||||
<version>1.0.0.0</version>
|
|
||||||
<scope>compile</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>ru.kirillius</groupId>
|
|
||||||
<artifactId>logging</artifactId>
|
|
||||||
<version>1.0.0.0</version>
|
|
||||||
<scope>compile</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>ru.kirillius</groupId>
|
|
||||||
<artifactId>database</artifactId>
|
|
||||||
<version>1.0.0.0</version>
|
|
||||||
<scope>compile</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>ru.kirillius</groupId>
|
|
||||||
<artifactId>web-server</artifactId>
|
|
||||||
<version>1.0.0.0</version>
|
|
||||||
<scope>compile</scope>
|
|
||||||
</dependency>
|
|
||||||
</dependencies>
|
|
||||||
|
|
||||||
</project>
|
|
||||||
|
|
@ -1,201 +0,0 @@
|
||||||
package ru.kirillius.XCP;
|
|
||||||
|
|
||||||
import lombok.Getter;
|
|
||||||
import ru.kirillius.XCP.Commons.Config;
|
|
||||||
import ru.kirillius.XCP.Commons.ConfigManager;
|
|
||||||
import ru.kirillius.XCP.Commons.Context;
|
|
||||||
import ru.kirillius.XCP.Commons.Service;
|
|
||||||
import ru.kirillius.XCP.Logging.Logger;
|
|
||||||
import ru.kirillius.XCP.Logging.LoggingSystem;
|
|
||||||
import ru.kirillius.XCP.Logging.LoggingSystemImpl;
|
|
||||||
import ru.kirillius.XCP.Persistence.PersistenceEntity;
|
|
||||||
import ru.kirillius.XCP.Persistence.Repository;
|
|
||||||
import ru.kirillius.XCP.Persistence.RepositoryServiceImpl;
|
|
||||||
import ru.kirillius.XCP.Security.ConfigManagerImpl;
|
|
||||||
import ru.kirillius.XCP.Security.SecurityManager;
|
|
||||||
import ru.kirillius.XCP.Security.SecurityManagerImpl;
|
|
||||||
import ru.kirillius.XCP.Services.RepositoryService;
|
|
||||||
import ru.kirillius.XCP.Services.ServiceLoadPriority;
|
|
||||||
import ru.kirillius.XCP.Services.WebService;
|
|
||||||
import ru.kirillius.XCP.web.WebServiceImpl;
|
|
||||||
import tools.jackson.databind.node.ArrayNode;
|
|
||||||
import tools.jackson.databind.node.ObjectNode;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.lang.reflect.InvocationTargetException;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
public class Application implements Context {
|
|
||||||
@Getter
|
|
||||||
private final SecurityManager securityManager;
|
|
||||||
@Getter
|
|
||||||
private final LoggingSystem loggingSystem;
|
|
||||||
private final Logger log;
|
|
||||||
@Getter
|
|
||||||
private final List<String> launchArgs;
|
|
||||||
@Getter
|
|
||||||
private final Config config;
|
|
||||||
@Getter
|
|
||||||
private final ConfigManager configManager;
|
|
||||||
private final Map<Class<? extends Service>, Service> services = new ConcurrentHashMap<>();
|
|
||||||
|
|
||||||
private Config loadConfig() {
|
|
||||||
var configFile = configManager.getConfigFile().getAbsolutePath();
|
|
||||||
|
|
||||||
try {
|
|
||||||
configFile = configManager.getConfigFile().getCanonicalPath();
|
|
||||||
} catch (IOException e) {
|
|
||||||
log.warning("Unable to determine real path of file " + configFile);
|
|
||||||
}
|
|
||||||
|
|
||||||
Config config;
|
|
||||||
if (configManager.isExist()) {
|
|
||||||
try {
|
|
||||||
config = configManager.load();
|
|
||||||
log.info("Loaded config file: " + configFile);
|
|
||||||
} catch (IOException e) {
|
|
||||||
log.error("Error loading config file " + configFile, e);
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
log.warning("Unable to find config file " + configFile + ". Using default values.");
|
|
||||||
config = configManager.create();
|
|
||||||
log.info("Saving default config file to " + configFile);
|
|
||||||
try {
|
|
||||||
configManager.save(config);
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException("Unable to save config file", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return config;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Application(String[] args) {
|
|
||||||
launchArgs = Arrays.stream(args).toList();
|
|
||||||
loggingSystem = new LoggingSystemImpl(this);
|
|
||||||
log = loggingSystem.createLogger(Application.class);
|
|
||||||
configManager = new ConfigManagerImpl(this);
|
|
||||||
config = loadConfig();
|
|
||||||
|
|
||||||
securityManager = new SecurityManagerImpl();
|
|
||||||
try {
|
|
||||||
loadServices();
|
|
||||||
} catch (Throwable throwable) {
|
|
||||||
log.error(throwable);
|
|
||||||
shutdown();
|
|
||||||
throw new RuntimeException("Error loading services");
|
|
||||||
}
|
|
||||||
|
|
||||||
checkDefaultInstall();
|
|
||||||
|
|
||||||
Runtime.getRuntime().addShutdownHook(new Thread(this::shutdown));
|
|
||||||
|
|
||||||
((WebServiceImpl) getService(WebService.class)).join();
|
|
||||||
}
|
|
||||||
|
|
||||||
private final static String DEFAULT_INSTALLER_FILE = "defaultEntities.json";
|
|
||||||
|
|
||||||
private void checkDefaultInstall() {
|
|
||||||
try (var stream = getClass().getClassLoader().getResourceAsStream(DEFAULT_INSTALLER_FILE)) {
|
|
||||||
var repositoryService = getService(RepositoryService.class);
|
|
||||||
var mapper = repositoryService.getMapper();
|
|
||||||
var array = (ArrayNode) mapper.readTree(stream);
|
|
||||||
for (var node : array) {
|
|
||||||
var entry = (ObjectNode) node;
|
|
||||||
var typeName = entry.get("type").asString();
|
|
||||||
var entityType = repositoryService.getEntityTypeByName(typeName);
|
|
||||||
@SuppressWarnings("unchecked") var repository = (Repository<PersistenceEntity>) repositoryService.getRepositoryForEntity(entityType);
|
|
||||||
var deserialized = repository.deserialize((ObjectNode) entry.get("entity"));
|
|
||||||
if (repository.get(deserialized.getUuid()) == null) {
|
|
||||||
log.warning("Installing default entity " + typeName + " (" + deserialized + ").");
|
|
||||||
repository.save(deserialized);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException("Unable to load file " + DEFAULT_INSTALLER_FILE, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void loadServices() {
|
|
||||||
var servicesToLoad = List.of(RepositoryServiceImpl.class, WebServiceImpl.class);
|
|
||||||
|
|
||||||
servicesToLoad.stream().sorted(Comparator.comparingInt(aClass -> {
|
|
||||||
var order = aClass.getAnnotation(ServiceLoadPriority.class);
|
|
||||||
return order == null ? 100000 : order.value();
|
|
||||||
})).forEach(aClass -> {
|
|
||||||
@SuppressWarnings("unchecked") var facade = (Class<? extends Service>) Arrays.stream(aClass.getInterfaces())
|
|
||||||
.filter(Service.class::isAssignableFrom)
|
|
||||||
.findFirst().
|
|
||||||
orElseThrow(() -> new ClassCastException("Unable to get service interface from class " + aClass.getSimpleName()));
|
|
||||||
log.info("Loading service " + facade.getSimpleName());
|
|
||||||
try {
|
|
||||||
var constructor = aClass.getConstructor();
|
|
||||||
try {
|
|
||||||
var service = constructor.newInstance();
|
|
||||||
try {
|
|
||||||
service.initialize(this);
|
|
||||||
|
|
||||||
services.put(facade, service);
|
|
||||||
} catch (Throwable e) {
|
|
||||||
try {
|
|
||||||
service.close();
|
|
||||||
} catch (IOException ex) {
|
|
||||||
e.addSuppressed(ex);
|
|
||||||
}
|
|
||||||
throw new RuntimeException("Failed to start " + facade.getSimpleName(), e);
|
|
||||||
}
|
|
||||||
} catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
|
|
||||||
throw new RuntimeException("Failed to instantiate " + facade.getSimpleName(), e);
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (NoSuchMethodException e) {
|
|
||||||
throw new RuntimeException("Failed to find default constructor of " + facade.getSimpleName(), e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
@Override
|
|
||||||
public <S extends Service> S getService(Class<S> serviceClass) {
|
|
||||||
return (S) services.get(serviceClass);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void shutdown() {
|
|
||||||
try {
|
|
||||||
services.forEach((serviceClass, service) -> {
|
|
||||||
try {
|
|
||||||
log.info("Shutting down service " + serviceClass.getSimpleName());
|
|
||||||
service.close();
|
|
||||||
} catch (IOException e) {
|
|
||||||
log.error("Error shutting down service " + serviceClass.getSimpleName(), e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
loggingSystem.close();
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isDebuggingEnabled() {
|
|
||||||
return launchArgs.contains("--debug");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void main(String[] args) {
|
|
||||||
try {
|
|
||||||
new Application(args);
|
|
||||||
} catch (Throwable e) {
|
|
||||||
System.err.println("Error starting application");
|
|
||||||
e.printStackTrace(System.err);
|
|
||||||
System.exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,47 +0,0 @@
|
||||||
[
|
|
||||||
{
|
|
||||||
"type": "User",
|
|
||||||
"entity": {
|
|
||||||
"login": "admin",
|
|
||||||
"name": "Administrator",
|
|
||||||
"role": "Admin",
|
|
||||||
"values": {},
|
|
||||||
"uuid": "00000000-0000-0000-0000-000000000000",
|
|
||||||
"passwordHash": "$argon2id$v=19$m=65536,t=3,p=1$SBqQtx5adxoG53V0TgqmDw$zIy0Wiq53m9r/SOldtCXWXLWbvZuS0F3HHILxpUsLhQ"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "PropertyDescriptor",
|
|
||||||
"entity": {
|
|
||||||
"constraints": [
|
|
||||||
{
|
|
||||||
"type": "StringConstraint",
|
|
||||||
"maxLength": 15,
|
|
||||||
"multiline": false,
|
|
||||||
"regexp": "^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"targets": [
|
|
||||||
"Data"
|
|
||||||
],
|
|
||||||
"array": false,
|
|
||||||
"propertyType": "Text",
|
|
||||||
"name": "host.ip",
|
|
||||||
"uuid": "00bd0f8a-e9d8-4789-83e5-d2f2eb94f876"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "PropertyDescriptor",
|
|
||||||
"entity": {
|
|
||||||
"constraints": [
|
|
||||||
],
|
|
||||||
"targets": [
|
|
||||||
"Users"
|
|
||||||
],
|
|
||||||
"array": true,
|
|
||||||
"propertyType": "Number",
|
|
||||||
"name": "user.test.prop",
|
|
||||||
"uuid": "00bd0f8a-e9d8-4789-83e5-d2f2eb94f877"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
28
core/pom.xml
28
core/pom.xml
|
|
@ -1,28 +0,0 @@
|
||||||
<?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/xsd/maven-4.0.0.xsd">
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
|
||||||
<parent>
|
|
||||||
<groupId>ru.kirillius</groupId>
|
|
||||||
<artifactId>XCP</artifactId>
|
|
||||||
<version>1.0.0.0</version>
|
|
||||||
</parent>
|
|
||||||
|
|
||||||
<artifactId>core</artifactId>
|
|
||||||
|
|
||||||
<properties>
|
|
||||||
<maven.compiler.source>21</maven.compiler.source>
|
|
||||||
<maven.compiler.target>21</maven.compiler.target>
|
|
||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
|
||||||
</properties>
|
|
||||||
<dependencies>
|
|
||||||
<dependency>
|
|
||||||
<groupId>ru.kirillius</groupId>
|
|
||||||
<artifactId>api</artifactId>
|
|
||||||
<version>1.0.0.0</version>
|
|
||||||
<scope>compile</scope>
|
|
||||||
</dependency>
|
|
||||||
</dependencies>
|
|
||||||
|
|
||||||
</project>
|
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
package ru.kirillius.XCP.Security;
|
|
||||||
|
|
||||||
import de.mkammerer.argon2.Argon2;
|
|
||||||
import de.mkammerer.argon2.Argon2Factory;
|
|
||||||
|
|
||||||
public class Argon2HashUtility implements HashUtility {
|
|
||||||
|
|
||||||
|
|
||||||
private final Argon2 argon2;
|
|
||||||
|
|
||||||
public Argon2HashUtility() {
|
|
||||||
this.argon2 = Argon2Factory.create(
|
|
||||||
Argon2Factory.Argon2Types.ARGON2id,
|
|
||||||
16,
|
|
||||||
32
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String hash(String password) {
|
|
||||||
try {
|
|
||||||
return argon2.hash(3, 65536, 1, password.toCharArray());
|
|
||||||
} finally {
|
|
||||||
argon2.wipeArray(password.toCharArray());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean validate(String password, String hash) {
|
|
||||||
return argon2.verify(hash, password.toCharArray());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,85 +0,0 @@
|
||||||
package ru.kirillius.XCP.Security;
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.NoArgsConstructor;
|
|
||||||
import lombok.Setter;
|
|
||||||
import ru.kirillius.XCP.Commons.Config;
|
|
||||||
import ru.kirillius.XCP.Commons.ConfigManager;
|
|
||||||
import ru.kirillius.XCP.Commons.Context;
|
|
||||||
import tools.jackson.databind.ObjectMapper;
|
|
||||||
|
|
||||||
import java.io.*;
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
|
|
||||||
public final class ConfigManagerImpl implements ConfigManager {
|
|
||||||
private final Context context;
|
|
||||||
private final static String DEFAULT_CONFIG_PATH = "./xcp.conf";
|
|
||||||
private final static String DEFAULT_DB_PATH = "./xcpdata";
|
|
||||||
private final ObjectMapper mapper = new ObjectMapper();
|
|
||||||
|
|
||||||
public ConfigManagerImpl(Context context) {
|
|
||||||
this.context = context;
|
|
||||||
configFile = findConfigFile();
|
|
||||||
}
|
|
||||||
|
|
||||||
private File findConfigFile() {
|
|
||||||
return new File(context.getLaunchArgs().stream()
|
|
||||||
.filter(a -> a.startsWith("--config="))
|
|
||||||
.findFirst()
|
|
||||||
.map(a -> a.split(Pattern.quote("="))[1])
|
|
||||||
.orElse(DEFAULT_CONFIG_PATH));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
private final File configFile;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isExist() {
|
|
||||||
return configFile.exists();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Config load() throws IOException {
|
|
||||||
try (var stream = new FileInputStream(configFile)) {
|
|
||||||
return mapper.readValue(stream, ConfigImpl.class);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Config create() {
|
|
||||||
var config = new ConfigImpl();
|
|
||||||
config.loadedConfigFile = configFile;
|
|
||||||
return config;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void save(Config config) throws IOException {
|
|
||||||
try (var stream = new FileOutputStream(configFile)) {
|
|
||||||
mapper.writerWithDefaultPrettyPrinter().writeValue(stream, config);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@NoArgsConstructor
|
|
||||||
private final static class ConfigImpl implements Config {
|
|
||||||
@Getter
|
|
||||||
@JsonIgnore
|
|
||||||
private File loadedConfigFile;
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
@JsonProperty
|
|
||||||
private String host = "127.0.0.1";
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
@JsonProperty
|
|
||||||
private File databaseFile = new File(DEFAULT_DB_PATH);
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
@JsonProperty
|
|
||||||
private int httpPort = 8080;
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
package ru.kirillius.XCP.Security;
|
|
||||||
|
|
||||||
import lombok.Getter;
|
|
||||||
|
|
||||||
public class SecurityManagerImpl implements SecurityManager {
|
|
||||||
@Getter
|
|
||||||
private final HashUtility hashUtility = new Argon2HashUtility();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
package ru.kirillius.XCP.Security;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
|
||||||
|
|
||||||
class Argon2HashUtilityTest {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void hash() {
|
|
||||||
var util = new Argon2HashUtility();
|
|
||||||
var hashed = util.hash("admin");
|
|
||||||
assertThat(hashed).isNotNull().isNotEmpty().doesNotContain("admin").isEqualTo("$argon2id$v=19$m=65536,t=3,p=1$SBqQtx5adxoG53V0TgqmDw$zIy0Wiq53m9r/SOldtCXWXLWbvZuS0F3HHILxpUsLhQ");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void validate() {
|
|
||||||
var util = new Argon2HashUtility();
|
|
||||||
var hashed = util.hash("admin");
|
|
||||||
assertThat(util.validate("admin", hashed)).isTrue();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,42 +0,0 @@
|
||||||
<?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/xsd/maven-4.0.0.xsd">
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
|
||||||
<parent>
|
|
||||||
<groupId>ru.kirillius</groupId>
|
|
||||||
<artifactId>XCP</artifactId>
|
|
||||||
<version>1.0.0.0</version>
|
|
||||||
</parent>
|
|
||||||
|
|
||||||
<artifactId>database</artifactId>
|
|
||||||
|
|
||||||
<dependencies>
|
|
||||||
<!-- Source: https://mvnrepository.com/artifact/com.h2database/h2 -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>com.h2database</groupId>
|
|
||||||
<artifactId>h2</artifactId>
|
|
||||||
<version>2.4.240</version>
|
|
||||||
</dependency>
|
|
||||||
<!-- https://mvnrepository.com/artifact/org.hibernate.orm/hibernate-core -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.hibernate.orm</groupId>
|
|
||||||
<artifactId>hibernate-core</artifactId>
|
|
||||||
<version>7.1.10.Final</version>
|
|
||||||
</dependency>
|
|
||||||
<!-- https://mvnrepository.com/artifact/org.hibernate.orm/hibernate-c3p0 -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.hibernate.orm</groupId>
|
|
||||||
<artifactId>hibernate-c3p0</artifactId>
|
|
||||||
<version>7.1.10.Final</version>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<dependency>
|
|
||||||
<groupId>ru.kirillius</groupId>
|
|
||||||
<artifactId>api</artifactId>
|
|
||||||
<version>1.0.0.0</version>
|
|
||||||
<scope>compile</scope>
|
|
||||||
</dependency>
|
|
||||||
</dependencies>
|
|
||||||
|
|
||||||
</project>
|
|
||||||
|
|
@ -1,55 +0,0 @@
|
||||||
package ru.kirillius.XCP.Persistence;
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
|
||||||
import jakarta.persistence.*;
|
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.Setter;
|
|
||||||
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
@MappedSuperclass
|
|
||||||
public abstract class AbstractEntity implements PersistenceEntity {
|
|
||||||
|
|
||||||
@Id
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
|
||||||
@JsonProperty
|
|
||||||
private long id = 0;
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
@JsonProperty
|
|
||||||
@Column(unique = true, nullable = false, updatable = false)
|
|
||||||
private UUID uuid;
|
|
||||||
|
|
||||||
@PrePersist
|
|
||||||
protected void prePersist() {
|
|
||||||
if (uuid == null) {
|
|
||||||
uuid = UUID.randomUUID();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if (o == this) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (o == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (o.getClass() != getClass()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var that = (AbstractEntity) o;
|
|
||||||
return Objects.equals(uuid, that.uuid);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return Objects.hashCode(uuid);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,269 +0,0 @@
|
||||||
package ru.kirillius.XCP.Persistence;
|
|
||||||
|
|
||||||
import org.hibernate.Session;
|
|
||||||
import org.hibernate.Transaction;
|
|
||||||
import org.hibernate.query.Query;
|
|
||||||
import ru.kirillius.XCP.Commons.StreamHandler;
|
|
||||||
import ru.kirillius.java.utils.events.ConcurrentEventHandler;
|
|
||||||
import ru.kirillius.java.utils.events.EventHandler;
|
|
||||||
import tools.jackson.databind.node.ArrayNode;
|
|
||||||
import tools.jackson.databind.node.ObjectNode;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.lang.reflect.InvocationTargetException;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.StringJoiner;
|
|
||||||
import java.util.UUID;
|
|
||||||
import java.util.stream.Stream;
|
|
||||||
|
|
||||||
public abstract class AbstractRepository<E extends PersistenceEntity> implements Repository<E> {
|
|
||||||
|
|
||||||
private final Repository.EventBindings<E> eventBindings = new EventBindingsImpl<>();
|
|
||||||
protected final Class<? extends E> entityImplementationClass;
|
|
||||||
protected RepositoryServiceImpl repositoryService;
|
|
||||||
protected String tableName;
|
|
||||||
|
|
||||||
public AbstractRepository(RepositoryServiceImpl repositoryService) {
|
|
||||||
var thisClass = getClass();
|
|
||||||
this.repositoryService = repositoryService;
|
|
||||||
|
|
||||||
//noinspection unchecked
|
|
||||||
entityImplementationClass = (Class<? extends E>) thisClass.getAnnotation(EntityImplementation.class).value();
|
|
||||||
tableName = entityImplementationClass.getName();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public E create() {
|
|
||||||
try {
|
|
||||||
var constructor = entityImplementationClass.getConstructor();
|
|
||||||
var instance = constructor.newInstance();
|
|
||||||
if (instance instanceof ContextReferencedEntity referencedEntity) {
|
|
||||||
referencedEntity.setContext(repositoryService.getContext());
|
|
||||||
}
|
|
||||||
return instance;
|
|
||||||
} catch (NoSuchMethodException | InvocationTargetException | InstantiationException |
|
|
||||||
IllegalAccessException e) {
|
|
||||||
throw new PersistenceException("Unable to instantiate entity", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public StreamHandler<E> search(String query, Collection<Object> queryParameters) {
|
|
||||||
return buildQuery(query, queryParameters.toArray());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public E get(UUID uuid) {
|
|
||||||
try (var query = buildQueryParametrized("where uuid = ?1", uuid)) {
|
|
||||||
return query.get().findFirst().orElse(null);
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new PersistenceException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public E get(long id) {
|
|
||||||
try (var query = buildQueryParametrized("where id = ?1", id)) {
|
|
||||||
return query.get().findFirst().orElse(null);
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new PersistenceException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public StreamHandler<E> get(Collection<Long> ids) {
|
|
||||||
if (ids != null && !ids.isEmpty()) {
|
|
||||||
return buildQueryParametrized("where id IN (" + joinIdentifiers(ids) + ")");
|
|
||||||
} else {
|
|
||||||
return new EmptyRequest<>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Repository.EventBindings<E> events() {
|
|
||||||
return eventBindings;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public StreamHandler<E> getAll() {
|
|
||||||
return buildQueryParametrized("order by id");
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings({"JpaQlInspection", "SqlSourceToSinkFlow"})
|
|
||||||
@Override
|
|
||||||
public long getCount() {
|
|
||||||
var session = repositoryService.openSession();
|
|
||||||
var transaction = session.beginTransaction();
|
|
||||||
try {
|
|
||||||
return session.createQuery("select count(id) as c from " + tableName, Long.class).uniqueResult();
|
|
||||||
} finally {
|
|
||||||
transaction.commit();
|
|
||||||
session.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void save(E entity) {
|
|
||||||
var session = repositoryService.openSession();
|
|
||||||
var transaction = session.beginTransaction();
|
|
||||||
try {
|
|
||||||
if (entity.getId() == 0L) {
|
|
||||||
session.persist(entity);
|
|
||||||
} else {
|
|
||||||
session.merge(entity);
|
|
||||||
}
|
|
||||||
transaction.commit();
|
|
||||||
} catch (Exception e) {
|
|
||||||
transaction.rollback();
|
|
||||||
throw new PersistenceException("Unable to save entity", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
eventBindings.entityStored().invoke(entity);
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new PersistenceException("Something went wrong on entity save event", e);
|
|
||||||
} finally {
|
|
||||||
session.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void save(Collection<E> entities) {
|
|
||||||
entities.forEach(this::save);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void remove(E entity) {
|
|
||||||
var session = repositoryService.openSession();
|
|
||||||
var transaction = session.beginTransaction();
|
|
||||||
try {
|
|
||||||
session.remove(entity);
|
|
||||||
transaction.commit();
|
|
||||||
} catch (Exception e) {
|
|
||||||
transaction.rollback();
|
|
||||||
throw new PersistenceException("Unable to remove entity", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
eventBindings.entityRemoved().invoke(entity);
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new PersistenceException("Something went wrong on entity deletion", e);
|
|
||||||
} finally {
|
|
||||||
session.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void remove(Collection<E> entities) {
|
|
||||||
entities.forEach(this::remove);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ObjectNode serialize(E entity) {
|
|
||||||
return repositoryService.getMapper().valueToTree(entity);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ArrayNode serialize(Collection<E> entities) {
|
|
||||||
var array = repositoryService.getMapper().createArrayNode();
|
|
||||||
for (E entity : entities) {
|
|
||||||
array.add(repositoryService.getMapper().valueToTree(entity));
|
|
||||||
}
|
|
||||||
return array;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public E deserialize(ObjectNode object) {
|
|
||||||
return repositoryService.getMapper().convertValue(object, entityImplementationClass);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Collection<E> deserialize(ArrayNode array) {
|
|
||||||
return repositoryService.getMapper().treeToValue(array, repositoryService.getMapper().getTypeFactory().constructCollectionType(List.class, entityImplementationClass));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected String joinIdentifiers(Collection<Long> ids) {
|
|
||||||
var joiner = new StringJoiner(", ");
|
|
||||||
|
|
||||||
for (var id : ids) {
|
|
||||||
joiner.add(id.toString());
|
|
||||||
}
|
|
||||||
return joiner.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings({"unchecked", "SqlSourceToSinkFlow"})
|
|
||||||
protected StreamHandler<E> buildQuery(String condition, Object[] parameters) {
|
|
||||||
var session = repositoryService.openSession();
|
|
||||||
var transaction = session.beginTransaction();
|
|
||||||
var queryString = "from " + tableName + " " + condition;
|
|
||||||
var query = (Query<E>) session.createQuery(queryString, entityImplementationClass);
|
|
||||||
for (var key = 0; key < parameters.length; ++key) {
|
|
||||||
query.setParameter(key + 1, parameters[key]);
|
|
||||||
}
|
|
||||||
return new ResourceHandlerImpl<>(query, transaction, session);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected StreamHandler<E> buildQueryParametrized(String condition, Object... parameters) {
|
|
||||||
return buildQuery(condition, parameters);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class EventBindingsImpl<T> implements Repository.EventBindings<T> {
|
|
||||||
private final EventHandler<T> stored = new ConcurrentEventHandler<>();
|
|
||||||
private final EventHandler<T> removed = new ConcurrentEventHandler<>();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public EventHandler<T> entityStored() {
|
|
||||||
return stored;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public EventHandler<T> entityRemoved() {
|
|
||||||
return removed;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static class EmptyRequest<T> implements StreamHandler<T> {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Stream<T> get() {
|
|
||||||
return Stream.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void close() throws IOException {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static class ResourceHandlerImpl<T> implements StreamHandler<T> {
|
|
||||||
|
|
||||||
private final Query<T> query;
|
|
||||||
private final Transaction transaction;
|
|
||||||
private final Session session;
|
|
||||||
|
|
||||||
public ResourceHandlerImpl(Query<T> query, Transaction transaction, Session session) {
|
|
||||||
this.query = query;
|
|
||||||
this.transaction = transaction;
|
|
||||||
this.session = session;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Stream<T> get() {
|
|
||||||
return query.getResultStream();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void close() {
|
|
||||||
if (transaction != null && transaction.isActive()) {
|
|
||||||
transaction.commit();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (session.isOpen()) {
|
|
||||||
session.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
package ru.kirillius.XCP.Persistence;
|
|
||||||
|
|
||||||
import ru.kirillius.XCP.Commons.Context;
|
|
||||||
|
|
||||||
public interface ContextReferencedEntity {
|
|
||||||
void setContext(Context context);
|
|
||||||
Context getContext();
|
|
||||||
}
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
package ru.kirillius.XCP.Persistence;
|
|
||||||
|
|
||||||
public interface DatabaseConfiguration {
|
|
||||||
String getConnectionUrl();
|
|
||||||
}
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
package ru.kirillius.XCP.Persistence;
|
|
||||||
|
|
||||||
import java.lang.annotation.ElementType;
|
|
||||||
import java.lang.annotation.Retention;
|
|
||||||
import java.lang.annotation.RetentionPolicy;
|
|
||||||
import java.lang.annotation.Target;
|
|
||||||
|
|
||||||
@Target(ElementType.TYPE)
|
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
|
||||||
public @interface EntityImplementation {
|
|
||||||
Class<? extends PersistenceEntity> value();
|
|
||||||
}
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
package ru.kirillius.XCP.Persistence;
|
|
||||||
|
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
|
||||||
|
|
||||||
public class EntityReference extends AtomicReference<PersistenceEntity> {
|
|
||||||
public EntityReference(PersistenceEntity initialValue) {
|
|
||||||
super(initialValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,33 +0,0 @@
|
||||||
package ru.kirillius.XCP.Persistence;
|
|
||||||
|
|
||||||
import tools.jackson.core.JacksonException;
|
|
||||||
import tools.jackson.core.JsonParser;
|
|
||||||
import tools.jackson.databind.DeserializationContext;
|
|
||||||
import tools.jackson.databind.deser.std.StdDeserializer;
|
|
||||||
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
public class EntityReferenceDeserializer extends StdDeserializer<EntityReference> {
|
|
||||||
|
|
||||||
private final RepositoryServiceImpl repositoryService;
|
|
||||||
|
|
||||||
public EntityReferenceDeserializer(RepositoryServiceImpl repositoryService) {
|
|
||||||
super(EntityReference.class);
|
|
||||||
this.repositoryService = repositoryService;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public EntityReference deserialize(JsonParser p, DeserializationContext ctxt) throws JacksonException {
|
|
||||||
var node = ctxt.readTree(p);
|
|
||||||
var type = node.get("type").asString();
|
|
||||||
var id = node.get("id").asLong();
|
|
||||||
var uuid = node.get("uuid").asString();
|
|
||||||
|
|
||||||
var repository = repositoryService.getRepositoryForEntity((Class<? extends PersistenceEntity>) repositoryService.getEntityTypeByName(type));
|
|
||||||
if (uuid != null) {
|
|
||||||
return new EntityReference(repository.get(UUID.fromString(uuid)));
|
|
||||||
}
|
|
||||||
return new EntityReference(repository.get(id));
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
package ru.kirillius.XCP.Persistence;
|
|
||||||
|
|
||||||
import tools.jackson.core.JacksonException;
|
|
||||||
import tools.jackson.core.JsonGenerator;
|
|
||||||
import tools.jackson.databind.SerializationContext;
|
|
||||||
import tools.jackson.databind.ser.std.StdSerializer;
|
|
||||||
|
|
||||||
public class EntityReferenceSerializer extends StdSerializer<EntityReference> {
|
|
||||||
private final RepositoryServiceImpl repositoryService;
|
|
||||||
|
|
||||||
public EntityReferenceSerializer(RepositoryServiceImpl repositoryService) {
|
|
||||||
super(EntityReference.class);
|
|
||||||
this.repositoryService = repositoryService;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void serialize(EntityReference reference, JsonGenerator gen, SerializationContext provider) throws JacksonException {
|
|
||||||
var value = reference.get();
|
|
||||||
if(value == null) {
|
|
||||||
gen.writeNull();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
gen.writeStartObject();
|
|
||||||
var baseType = repositoryService.getEntityBaseType(value.getClass());
|
|
||||||
gen.writeStringProperty("type", baseType.getSimpleName());
|
|
||||||
gen.writeNumberProperty("id", value.getId());
|
|
||||||
gen.writeStringProperty("uuid", value.getUuid().toString());
|
|
||||||
gen.writeEndObject();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
package ru.kirillius.XCP.Persistence;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
|
|
||||||
public class H2DatabaseInFileConfiguration implements DatabaseConfiguration {
|
|
||||||
private final File databaseFile;
|
|
||||||
|
|
||||||
public H2DatabaseInFileConfiguration(File databaseFile) {
|
|
||||||
this.databaseFile = databaseFile;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getConnectionUrl() {
|
|
||||||
return "jdbc:h2:file:" + databaseFile.getPath().replaceAll(Pattern.quote(".mv.db"), "");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
package ru.kirillius.XCP.Persistence;
|
|
||||||
|
|
||||||
import lombok.Getter;
|
|
||||||
import tools.jackson.core.Version;
|
|
||||||
import tools.jackson.databind.JacksonModule;
|
|
||||||
import tools.jackson.databind.module.SimpleDeserializers;
|
|
||||||
import tools.jackson.databind.module.SimpleSerializers;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
class PersistenceSerializationModule extends JacksonModule {
|
|
||||||
public PersistenceSerializationModule(RepositoryServiceImpl repositoryService) {
|
|
||||||
this.repositoryService = repositoryService;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
private final RepositoryServiceImpl repositoryService;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getModuleName() {
|
|
||||||
return getClass().getSimpleName();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Version version() {
|
|
||||||
return new Version(1, 0, 0, null, null, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setupModule(SetupContext context) {
|
|
||||||
context.addSerializers(new SimpleSerializers(List.of(
|
|
||||||
new EntityReferenceSerializer(repositoryService),
|
|
||||||
new TagCollectionSerializer()
|
|
||||||
)));
|
|
||||||
context.addDeserializers(new SimpleDeserializers(Map.of(
|
|
||||||
EntityReference.class, new EntityReferenceDeserializer(repositoryService),
|
|
||||||
TagCollection.class, new TagCollectionDeserializer(repositoryService)
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
package ru.kirillius.XCP.Persistence;
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
|
||||||
import lombok.*;
|
|
||||||
import ru.kirillius.XCP.Data.PollSettings;
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
@Builder
|
|
||||||
@AllArgsConstructor
|
|
||||||
@NoArgsConstructor
|
|
||||||
public class PollSettingsImpl implements PollSettings {
|
|
||||||
@JsonProperty
|
|
||||||
private long pollInterval = 300;
|
|
||||||
@JsonProperty
|
|
||||||
private boolean interruptable = true;
|
|
||||||
@JsonProperty
|
|
||||||
private boolean rateMeasurement = false;
|
|
||||||
@JsonProperty
|
|
||||||
private boolean enabled = true;
|
|
||||||
}
|
|
||||||
|
|
@ -1,56 +0,0 @@
|
||||||
package ru.kirillius.XCP.Persistence.Repositories;
|
|
||||||
|
|
||||||
import org.hibernate.query.Query;
|
|
||||||
import ru.kirillius.XCP.Commons.StreamHandler;
|
|
||||||
import ru.kirillius.XCP.Persistence.AbstractRepository;
|
|
||||||
import ru.kirillius.XCP.Persistence.Entities.Group;
|
|
||||||
import ru.kirillius.XCP.Persistence.Entities.Tag;
|
|
||||||
import ru.kirillius.XCP.Persistence.NodeEntity;
|
|
||||||
import ru.kirillius.XCP.Persistence.NodeRepository;
|
|
||||||
import ru.kirillius.XCP.Persistence.RepositoryServiceImpl;
|
|
||||||
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public abstract class AbstractNodeRepository<E extends NodeEntity> extends AbstractRepository<E> implements NodeRepository<E> {
|
|
||||||
|
|
||||||
public AbstractNodeRepository(RepositoryServiceImpl repositoryService) {
|
|
||||||
super(repositoryService);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public StreamHandler<E> getByGroup(Group group) {
|
|
||||||
return search("WHERE parent = ?1", List.of(group));
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
@Override
|
|
||||||
public StreamHandler<E> getByAllTags(Collection<Tag> tags) {
|
|
||||||
|
|
||||||
var hql = "SELECT n FROM " + tableName + " n JOIN n.tags t " +
|
|
||||||
"WHERE t IN :tags " +
|
|
||||||
"GROUP BY n.id " +
|
|
||||||
"HAVING COUNT(DISTINCT t.id) = :tagCount";
|
|
||||||
|
|
||||||
var session = repositoryService.openSession();
|
|
||||||
var transaction = session.beginTransaction();
|
|
||||||
var query = (Query<E>) session.createQuery(hql, entityImplementationClass);
|
|
||||||
query.setParameter("tags", tags);
|
|
||||||
query.setParameter("tagCount", (long) tags.size());
|
|
||||||
return new ResourceHandlerImpl<>(query, transaction, session);
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
@Override
|
|
||||||
public StreamHandler<E> getByAnyTag(Collection<Tag> tags) {
|
|
||||||
var hql = "SELECT DISTINCT n FROM " + tableName + " n " +
|
|
||||||
"JOIN n.tags t " +
|
|
||||||
"WHERE t.name IN :tagNames";
|
|
||||||
|
|
||||||
var session = repositoryService.openSession();
|
|
||||||
var transaction = session.beginTransaction();
|
|
||||||
var query = (Query<E>) session.createQuery(hql, entityImplementationClass);
|
|
||||||
query.setParameter("tagNames", tags.stream().map(Tag::getName).toList());
|
|
||||||
return new ResourceHandlerImpl<>(query, transaction, session);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,67 +0,0 @@
|
||||||
package ru.kirillius.XCP.Persistence.Repositories;
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
|
||||||
import jakarta.persistence.*;
|
|
||||||
import lombok.*;
|
|
||||||
import ru.kirillius.XCP.Commons.StreamHandler;
|
|
||||||
import ru.kirillius.XCP.Persistence.*;
|
|
||||||
import ru.kirillius.XCP.Persistence.Entities.ApiToken;
|
|
||||||
import ru.kirillius.XCP.Persistence.Entities.User;
|
|
||||||
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
@EntityImplementation(ApiTokenRepositoryImpl.TokenEntity.class)
|
|
||||||
public class ApiTokenRepositoryImpl extends AbstractRepository<ApiToken> implements ApiTokenRepository {
|
|
||||||
|
|
||||||
public ApiTokenRepositoryImpl(RepositoryServiceImpl repositoryService) {
|
|
||||||
super(repositoryService);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public StreamHandler<ApiToken> getByUser(User user) {
|
|
||||||
return search("WHERE user = ?1", List.of(user));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Entity
|
|
||||||
@Table(name = "Tokens")
|
|
||||||
@Builder
|
|
||||||
@AllArgsConstructor
|
|
||||||
@NoArgsConstructor
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
public static class TokenEntity extends AbstractEntity implements ApiToken {
|
|
||||||
|
|
||||||
@JsonProperty
|
|
||||||
@Column
|
|
||||||
private Date expirationDate;
|
|
||||||
|
|
||||||
@JsonProperty
|
|
||||||
@Column(nullable = false)
|
|
||||||
private String name;
|
|
||||||
|
|
||||||
@ManyToOne(fetch = FetchType.EAGER)
|
|
||||||
@JsonIgnore
|
|
||||||
private UserRepositoryImpl.UserEntity user;
|
|
||||||
|
|
||||||
@JsonProperty("user")
|
|
||||||
EntityReference getUserReference() {
|
|
||||||
return new EntityReference(getUser());
|
|
||||||
}
|
|
||||||
|
|
||||||
@JsonProperty("user")
|
|
||||||
void setUserReference(EntityReference entityReference) {
|
|
||||||
user = entityReference == null ? null : (UserRepositoryImpl.UserEntity) entityReference.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
public User getUser() {
|
|
||||||
return user;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setUser(User parent) {
|
|
||||||
this.user = (UserRepositoryImpl.UserEntity) parent;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,149 +0,0 @@
|
||||||
package ru.kirillius.XCP.Persistence.Repositories;
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
|
||||||
import jakarta.persistence.*;
|
|
||||||
import lombok.*;
|
|
||||||
import ru.kirillius.XCP.Commons.StreamHandler;
|
|
||||||
import ru.kirillius.XCP.Persistence.*;
|
|
||||||
import ru.kirillius.XCP.Persistence.Entities.Group;
|
|
||||||
import tools.jackson.databind.node.JsonNodeFactory;
|
|
||||||
import tools.jackson.databind.node.ObjectNode;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.*;
|
|
||||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
@EntityImplementation(GroupRepositoryImpl.GroupEntity.class)
|
|
||||||
public class GroupRepositoryImpl extends AbstractNodeRepository<Group> implements GroupRepository {
|
|
||||||
|
|
||||||
public GroupRepositoryImpl(RepositoryServiceImpl repositoryService) {
|
|
||||||
super(repositoryService);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public StreamHandler<Group> getChildrenOf(Group group) {
|
|
||||||
return search("WHERE parent = ?1", List.of(group));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public StreamHandler<Group> getAllChildrenInHierarchy(Group group) {
|
|
||||||
var children = new ArrayList<Group>();
|
|
||||||
var pendingGroups = new ConcurrentLinkedQueue<Group>();
|
|
||||||
pendingGroups.add(group);
|
|
||||||
while (!pendingGroups.isEmpty()) {
|
|
||||||
var child = pendingGroups.remove();
|
|
||||||
try (var handler = getChildrenOf(child)) {
|
|
||||||
handler.get().forEach(item -> {
|
|
||||||
children.add(item);
|
|
||||||
pendingGroups.add(item);
|
|
||||||
});
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return new SimpleStreamHandler<>(children.stream());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Group getRoot() {
|
|
||||||
try (var handler = search("WHERE parent is null", Collections.emptyList())) {
|
|
||||||
return handler.get().findFirst().orElse(null);
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException("Unable to get root group", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void save(Group entity) {
|
|
||||||
if (entity != null && entity.getParent() == null) {
|
|
||||||
var root = getRoot();
|
|
||||||
if (root != null && !root.equals(entity)) {
|
|
||||||
throw new IllegalStateException("Root group already exists");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
super.save(entity);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Entity
|
|
||||||
@Table(name = "Groups")
|
|
||||||
@Builder
|
|
||||||
@AllArgsConstructor
|
|
||||||
@NoArgsConstructor
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
public static class GroupEntity extends AbstractEntity implements Group {
|
|
||||||
|
|
||||||
@Column(nullable = false)
|
|
||||||
@JsonProperty
|
|
||||||
private String name = "";
|
|
||||||
|
|
||||||
@Column(nullable = false)
|
|
||||||
@JsonProperty
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
private String icon = "";
|
|
||||||
|
|
||||||
@Column(nullable = false)
|
|
||||||
@JsonProperty
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
private boolean prototype;
|
|
||||||
|
|
||||||
@Column(nullable = false)
|
|
||||||
@JsonProperty
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
private boolean protectedEntity;
|
|
||||||
|
|
||||||
@Column(nullable = false)
|
|
||||||
@JsonProperty
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
private boolean enabled;
|
|
||||||
|
|
||||||
@ManyToOne(fetch = FetchType.EAGER)
|
|
||||||
@JsonIgnore
|
|
||||||
private GroupEntity parent;
|
|
||||||
|
|
||||||
@JsonProperty("parent")
|
|
||||||
EntityReference getParentReference() {
|
|
||||||
return new EntityReference(getParent());
|
|
||||||
}
|
|
||||||
|
|
||||||
@JsonProperty("parent")
|
|
||||||
void setParentReference(EntityReference entityReference) {
|
|
||||||
parent = entityReference == null ? null : (GroupEntity) entityReference.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Group getParent() {
|
|
||||||
return parent;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setParent(Group parent) {
|
|
||||||
this.parent = (GroupEntity) parent;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setTags(TagCollection tags) {
|
|
||||||
this.tags = tags.stream().map(t -> (TagRepositoryImpl.TagEntity) t).collect(Collectors.toSet());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public TagCollection getTags() {
|
|
||||||
return new TagCollectionImpl(tags);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Column(nullable = false)
|
|
||||||
@JsonProperty
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
private ObjectNode properties = JsonNodeFactory.instance.objectNode();
|
|
||||||
|
|
||||||
@JsonIgnore
|
|
||||||
@ManyToMany(fetch = FetchType.EAGER)
|
|
||||||
private Set<TagRepositoryImpl.TagEntity> tags = new HashSet<>();
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,134 +0,0 @@
|
||||||
package ru.kirillius.XCP.Persistence.Repositories;
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
|
||||||
import jakarta.persistence.*;
|
|
||||||
import lombok.*;
|
|
||||||
import ru.kirillius.XCP.Data.PollSettings;
|
|
||||||
import ru.kirillius.XCP.Data.ValueTransformationChain;
|
|
||||||
import ru.kirillius.XCP.Persistence.*;
|
|
||||||
import ru.kirillius.XCP.Persistence.Entities.Group;
|
|
||||||
import ru.kirillius.XCP.Persistence.Entities.Input;
|
|
||||||
import ru.kirillius.XCP.Serialization.PollSettingsConverter;
|
|
||||||
import ru.kirillius.XCP.Serialization.ValueTransformationChainConverter;
|
|
||||||
import tools.jackson.databind.annotation.JsonDeserialize;
|
|
||||||
import tools.jackson.databind.node.JsonNodeFactory;
|
|
||||||
import tools.jackson.databind.node.ObjectNode;
|
|
||||||
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
@EntityImplementation(InputRepositoryImpl.InputEntity.class)
|
|
||||||
public class InputRepositoryImpl extends AbstractNodeRepository<Input> implements InputRepository {
|
|
||||||
|
|
||||||
public InputRepositoryImpl(RepositoryServiceImpl repositoryService) {
|
|
||||||
super(repositoryService);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void save(Input entity) {
|
|
||||||
if (entity != null && entity.getParent() == null) {
|
|
||||||
throw new IllegalStateException("Saving inputs without group is prohibited");
|
|
||||||
}
|
|
||||||
super.save(entity);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Entity
|
|
||||||
@Table(name = "Inputs")
|
|
||||||
@Builder
|
|
||||||
@AllArgsConstructor
|
|
||||||
@NoArgsConstructor
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
public static class InputEntity extends AbstractEntity implements Input {
|
|
||||||
|
|
||||||
@Column(nullable = false)
|
|
||||||
@JsonProperty
|
|
||||||
private String name = "";
|
|
||||||
|
|
||||||
@Column(nullable = false)
|
|
||||||
@JsonProperty
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
private boolean protectedEntity;
|
|
||||||
|
|
||||||
@Column(nullable = false)
|
|
||||||
@JsonProperty
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
private boolean enabled;
|
|
||||||
|
|
||||||
@ManyToOne(fetch = FetchType.EAGER)
|
|
||||||
@JsonIgnore
|
|
||||||
private GroupRepositoryImpl.GroupEntity parent;
|
|
||||||
|
|
||||||
@JsonProperty("parent")
|
|
||||||
EntityReference getParentReference() {
|
|
||||||
return new EntityReference(getParent());
|
|
||||||
}
|
|
||||||
|
|
||||||
@JsonProperty("parent")
|
|
||||||
void setParentReference(EntityReference entityReference) {
|
|
||||||
parent = entityReference == null ? null : (GroupRepositoryImpl.GroupEntity) entityReference.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Group getParent() {
|
|
||||||
return parent;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setParent(Group parent) {
|
|
||||||
this.parent = (GroupRepositoryImpl.GroupEntity) parent;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setTags(TagCollection tags) {
|
|
||||||
this.tags = tags.stream().map(t -> (TagRepositoryImpl.TagEntity) t).collect(Collectors.toSet());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public TagCollection getTags() {
|
|
||||||
return new TagCollectionImpl(tags);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Column(nullable = false)
|
|
||||||
@JsonProperty
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
private ObjectNode properties = JsonNodeFactory.instance.objectNode();
|
|
||||||
|
|
||||||
@JsonIgnore
|
|
||||||
@ManyToMany(fetch = FetchType.EAGER)
|
|
||||||
private Set<TagRepositoryImpl.TagEntity> tags = new HashSet<>();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public PollSettings getPollSettings() {
|
|
||||||
return pollSettings;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setPollSettings(PollSettings pollSettings) {
|
|
||||||
this.pollSettings = (PollSettingsImpl) pollSettings;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Column(nullable = false)
|
|
||||||
@JsonProperty
|
|
||||||
@Convert(converter = PollSettingsConverter.class)
|
|
||||||
@JsonDeserialize(as = PollSettingsImpl.class)
|
|
||||||
private PollSettingsImpl pollSettings = new PollSettingsImpl();
|
|
||||||
|
|
||||||
@Column(nullable = false)
|
|
||||||
@JsonProperty
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
private String adapterId = "";
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
@JsonProperty
|
|
||||||
@Convert(converter = ValueTransformationChainConverter.class)
|
|
||||||
@Column(nullable = false)
|
|
||||||
private ValueTransformationChain transformationChain = new ValueTransformationChain();
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,114 +0,0 @@
|
||||||
package ru.kirillius.XCP.Persistence.Repositories;
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
|
||||||
import jakarta.persistence.*;
|
|
||||||
import lombok.*;
|
|
||||||
import ru.kirillius.XCP.Data.ValueTransformationChain;
|
|
||||||
import ru.kirillius.XCP.Persistence.*;
|
|
||||||
import ru.kirillius.XCP.Persistence.Entities.Group;
|
|
||||||
import ru.kirillius.XCP.Persistence.Entities.Output;
|
|
||||||
import ru.kirillius.XCP.Serialization.ValueTransformationChainConverter;
|
|
||||||
import tools.jackson.databind.node.JsonNodeFactory;
|
|
||||||
import tools.jackson.databind.node.ObjectNode;
|
|
||||||
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
@EntityImplementation(OutputRepositoryImpl.OutputEntity.class)
|
|
||||||
public class OutputRepositoryImpl extends AbstractNodeRepository<Output> implements OutputRepository {
|
|
||||||
|
|
||||||
public OutputRepositoryImpl(RepositoryServiceImpl repositoryService) {
|
|
||||||
super(repositoryService);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void save(Output entity) {
|
|
||||||
if (entity != null && entity.getParent() == null) {
|
|
||||||
throw new IllegalStateException("Saving outputs without group is prohibited");
|
|
||||||
}
|
|
||||||
super.save(entity);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Entity
|
|
||||||
@Table(name = "Outputs")
|
|
||||||
@Builder
|
|
||||||
@AllArgsConstructor
|
|
||||||
@NoArgsConstructor
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
public static class OutputEntity extends AbstractEntity implements Output {
|
|
||||||
|
|
||||||
@Column(nullable = false)
|
|
||||||
@JsonProperty
|
|
||||||
private String name = "";
|
|
||||||
|
|
||||||
@Column(nullable = false)
|
|
||||||
@JsonProperty
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
private boolean protectedEntity;
|
|
||||||
|
|
||||||
@Column(nullable = false)
|
|
||||||
@JsonProperty
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
private boolean enabled;
|
|
||||||
|
|
||||||
@ManyToOne(fetch = FetchType.EAGER)
|
|
||||||
@JsonIgnore
|
|
||||||
private GroupRepositoryImpl.GroupEntity parent;
|
|
||||||
|
|
||||||
@JsonProperty("parent")
|
|
||||||
EntityReference getParentReference() {
|
|
||||||
return new EntityReference(getParent());
|
|
||||||
}
|
|
||||||
|
|
||||||
@JsonProperty("parent")
|
|
||||||
void setParentReference(EntityReference entityReference) {
|
|
||||||
parent = entityReference == null ? null : (GroupRepositoryImpl.GroupEntity) entityReference.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Group getParent() {
|
|
||||||
return parent;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setParent(Group parent) {
|
|
||||||
this.parent = (GroupRepositoryImpl.GroupEntity) parent;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setTags(TagCollection tags) {
|
|
||||||
this.tags = tags.stream().map(t -> (TagRepositoryImpl.TagEntity) t).collect(Collectors.toSet());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public TagCollection getTags() {
|
|
||||||
return new TagCollectionImpl(tags);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Column(nullable = false)
|
|
||||||
@JsonProperty
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
private ObjectNode properties = JsonNodeFactory.instance.objectNode();
|
|
||||||
|
|
||||||
@JsonIgnore
|
|
||||||
@ManyToMany(fetch = FetchType.EAGER)
|
|
||||||
private Set<TagRepositoryImpl.TagEntity> tags = new HashSet<>();
|
|
||||||
|
|
||||||
@Column(nullable = false)
|
|
||||||
@JsonProperty
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
private String adapterId = "";
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
@JsonProperty
|
|
||||||
@Convert(converter = ValueTransformationChainConverter.class)
|
|
||||||
@Column(nullable = false)
|
|
||||||
private ValueTransformationChain transformationChain = new ValueTransformationChain();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,129 +0,0 @@
|
||||||
package ru.kirillius.XCP.Persistence.Repositories;
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
|
||||||
import jakarta.persistence.*;
|
|
||||||
import lombok.*;
|
|
||||||
import ru.kirillius.XCP.Commons.GenerateApiSpec;
|
|
||||||
import ru.kirillius.XCP.Persistence.AbstractEntity;
|
|
||||||
import ru.kirillius.XCP.Persistence.AbstractRepository;
|
|
||||||
import ru.kirillius.XCP.Persistence.Entities.PropertyDescriptor;
|
|
||||||
import ru.kirillius.XCP.Persistence.EntityImplementation;
|
|
||||||
import ru.kirillius.XCP.Persistence.RepositoryServiceImpl;
|
|
||||||
import ru.kirillius.XCP.Properties.Constraint;
|
|
||||||
import ru.kirillius.XCP.Properties.Constraints;
|
|
||||||
import ru.kirillius.XCP.Properties.PropertyTarget;
|
|
||||||
import ru.kirillius.XCP.Properties.PropertyType;
|
|
||||||
import tools.jackson.databind.ObjectMapper;
|
|
||||||
import tools.jackson.databind.node.ArrayNode;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
@EntityImplementation(PropertyDescriptorRepositoryImpl.PropertyDescriptorEntity.class)
|
|
||||||
public class PropertyDescriptorRepositoryImpl extends AbstractRepository<PropertyDescriptor> implements PropertyDescriptorRepository {
|
|
||||||
|
|
||||||
public PropertyDescriptorRepositoryImpl(RepositoryServiceImpl repositoryService) {
|
|
||||||
super(repositoryService);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public PropertyDescriptor getByName(String name) {
|
|
||||||
try (var request = buildQueryParametrized("WHERE name = ?1", name)) {
|
|
||||||
return request.get().findFirst().orElse(null);
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Entity
|
|
||||||
@Table(name = "PropertyDescriptors")
|
|
||||||
@Builder
|
|
||||||
@AllArgsConstructor
|
|
||||||
@NoArgsConstructor
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
public static class PropertyDescriptorEntity extends AbstractEntity implements PropertyDescriptor {
|
|
||||||
|
|
||||||
@Column(nullable = false, unique = true)
|
|
||||||
@JsonProperty
|
|
||||||
private String name = "";
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
@Column(nullable = false)
|
|
||||||
@JsonProperty("targets")
|
|
||||||
private Set<PropertyTarget> propertyTargets = Collections.emptySet();
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
@JsonIgnore
|
|
||||||
@Column(nullable = false, name = "constraints_set", columnDefinition = "TEXT")
|
|
||||||
@Convert(converter = ConstraintsConverter.class)
|
|
||||||
private Constraints constraints = new ConstraintsImpl();
|
|
||||||
|
|
||||||
@Transient
|
|
||||||
@JsonProperty("constraints")
|
|
||||||
ArrayNode getConstraintsSerialized() {
|
|
||||||
return ConstraintsImpl.serialize(constraints);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Transient
|
|
||||||
@JsonProperty("constraints")
|
|
||||||
void setConstraintsSerialized(ArrayNode array) {
|
|
||||||
constraints = ConstraintsImpl.deserialize(array);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
@JsonProperty
|
|
||||||
@Column(name = "is_array")
|
|
||||||
private boolean array;
|
|
||||||
|
|
||||||
@Enumerated(EnumType.STRING)
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
@JsonProperty
|
|
||||||
@Column(nullable = false)
|
|
||||||
private PropertyType propertyType = PropertyType.Text;
|
|
||||||
}
|
|
||||||
|
|
||||||
private final static ObjectMapper mapper = new ObjectMapper();
|
|
||||||
|
|
||||||
private static final class ConstraintsImpl extends HashSet<Constraint> implements Constraints {
|
|
||||||
|
|
||||||
//TODO перенести в отдельный класс сериализации
|
|
||||||
static ArrayNode serialize(Constraints constraints) {
|
|
||||||
var array = mapper.createArrayNode();
|
|
||||||
constraints.forEach(c -> array.add(mapper.valueToTree(c)));
|
|
||||||
return array;
|
|
||||||
}
|
|
||||||
|
|
||||||
static Constraints deserialize(ArrayNode array) {
|
|
||||||
var inheritors = Constraint.class.getAnnotation(GenerateApiSpec.class).directInheritors();
|
|
||||||
var constraints = new ConstraintsImpl();
|
|
||||||
array.forEach(node -> {
|
|
||||||
var typeName = node.get("type").asString();
|
|
||||||
var type = Arrays.stream(inheritors).filter(cls -> cls.getSimpleName().equalsIgnoreCase(typeName)).findFirst().orElseThrow(() -> new RuntimeException("Invalid type: " + typeName));
|
|
||||||
constraints.add((Constraint) mapper.convertValue(node, type));
|
|
||||||
});
|
|
||||||
return constraints;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public final static class ConstraintsConverter implements AttributeConverter<Constraints, String> {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String convertToDatabaseColumn(Constraints constraints) {
|
|
||||||
return mapper.writeValueAsString(ConstraintsImpl.serialize(constraints));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Constraints convertToEntityAttribute(String s) {
|
|
||||||
return ConstraintsImpl.deserialize((ArrayNode) mapper.readTree(s));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,118 +0,0 @@
|
||||||
package ru.kirillius.XCP.Persistence.Repositories;
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
|
||||||
import jakarta.persistence.Column;
|
|
||||||
import jakarta.persistence.Entity;
|
|
||||||
import jakarta.persistence.Table;
|
|
||||||
import lombok.*;
|
|
||||||
import ru.kirillius.XCP.Commons.ResourceHandler;
|
|
||||||
import ru.kirillius.XCP.Persistence.*;
|
|
||||||
import ru.kirillius.XCP.Persistence.Entities.Tag;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
import java.util.stream.Stream;
|
|
||||||
|
|
||||||
@EntityImplementation(TagRepositoryImpl.TagEntity.class)
|
|
||||||
public class TagRepositoryImpl extends AbstractRepository<Tag> implements TagRepository {
|
|
||||||
|
|
||||||
public TagRepositoryImpl(RepositoryServiceImpl repositoryService) {
|
|
||||||
super(repositoryService);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Tag getByNameOrCreate(String name) {
|
|
||||||
try (var handler = buildQueryParametrized("WHERE name = ?1", name)) {
|
|
||||||
var result = handler.get().findFirst();
|
|
||||||
if (result.isPresent()) {
|
|
||||||
return result.get();
|
|
||||||
} else {
|
|
||||||
var tag = create();
|
|
||||||
tag.setName(name);
|
|
||||||
save(tag);
|
|
||||||
return tag;
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public TagCollection getByNamesOrCreate(Collection<String> names) {
|
|
||||||
var tags = new TagCollectionImpl();
|
|
||||||
try (var handler = search("where name IN (?1)", List.of(names))) {
|
|
||||||
tags.addAll(handler.get().toList());
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException("Unable to find tags by names " + names, e);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tags.size() != names.size()) {
|
|
||||||
var foundNames = tags.stream().map(Tag::getName).toList();
|
|
||||||
names.forEach(tagName -> {
|
|
||||||
if (!foundNames.contains(tagName)) {
|
|
||||||
var tag = create();
|
|
||||||
tag.setName(tagName);
|
|
||||||
save(tag);
|
|
||||||
tags.add(tag);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return tags;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public TagCollection createCollection() {
|
|
||||||
return new TagCollectionImpl();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public TagCollection createCollection(ResourceHandler<Stream<Tag>> handler) {
|
|
||||||
return new TagCollectionImpl(handler.get().toList());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Entity
|
|
||||||
@Table(name = "Tags")
|
|
||||||
@Builder
|
|
||||||
@AllArgsConstructor
|
|
||||||
@NoArgsConstructor
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
public static class TagEntity extends AbstractEntity implements Tag {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Column(nullable = false, unique = true)
|
|
||||||
@JsonProperty
|
|
||||||
private String name = "";
|
|
||||||
private static final Pattern NAME_PATTERN =
|
|
||||||
Pattern.compile("^[a-z0-9]+(\\.[a-z0-9]+)*$");
|
|
||||||
|
|
||||||
public void setName(String name) {
|
|
||||||
if (name == null || name.isEmpty()) {
|
|
||||||
throw new IllegalArgumentException("Name cannot be null or empty");
|
|
||||||
}
|
|
||||||
|
|
||||||
name = name.trim().toLowerCase();
|
|
||||||
|
|
||||||
if (!NAME_PATTERN.matcher(name).matches()) {
|
|
||||||
throw new IllegalArgumentException(
|
|
||||||
String.format(
|
|
||||||
"Invalid name: '%s'. " +
|
|
||||||
"Name must contain only lowercase letters a-z, digits 0-9, and dots. " +
|
|
||||||
"Cannot start or end with dot, and dots cannot be consecutive.",
|
|
||||||
name
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
this.name = name;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,87 +0,0 @@
|
||||||
package ru.kirillius.XCP.Persistence.Repositories;
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
|
||||||
import jakarta.persistence.*;
|
|
||||||
import lombok.*;
|
|
||||||
import ru.kirillius.XCP.Commons.Context;
|
|
||||||
import ru.kirillius.XCP.Persistence.*;
|
|
||||||
import ru.kirillius.XCP.Persistence.Entities.User;
|
|
||||||
import ru.kirillius.XCP.Security.UserRole;
|
|
||||||
import tools.jackson.databind.node.JsonNodeFactory;
|
|
||||||
import tools.jackson.databind.node.ObjectNode;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
@EntityImplementation(UserRepositoryImpl.UserEntity.class)
|
|
||||||
public class UserRepositoryImpl extends AbstractRepository<User> implements UserRepository {
|
|
||||||
|
|
||||||
public UserRepositoryImpl(RepositoryServiceImpl repositoryService) {
|
|
||||||
super(repositoryService);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public User getByLogin(String login) {
|
|
||||||
try (var request = buildQueryParametrized("WHERE login = ?1", login)) {
|
|
||||||
return request.get().findFirst().orElse(null);
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Entity
|
|
||||||
@Table(name = "Users")
|
|
||||||
@Builder
|
|
||||||
@AllArgsConstructor
|
|
||||||
@NoArgsConstructor
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
public static class UserEntity extends AbstractEntity implements User, ContextReferencedEntity {
|
|
||||||
@Transient
|
|
||||||
@JsonIgnore
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
private Context context;
|
|
||||||
|
|
||||||
@Column(nullable = false, unique = true)
|
|
||||||
@JsonProperty
|
|
||||||
private String login = "";
|
|
||||||
|
|
||||||
@Column(nullable = false)
|
|
||||||
@JsonProperty
|
|
||||||
private String name = "";
|
|
||||||
|
|
||||||
@Column(nullable = false)
|
|
||||||
@JsonProperty
|
|
||||||
private String passwordHash = "";
|
|
||||||
|
|
||||||
@Column(nullable = false)
|
|
||||||
@JsonProperty
|
|
||||||
@Enumerated(EnumType.STRING)
|
|
||||||
private UserRole role = UserRole.User;
|
|
||||||
|
|
||||||
@Column(name = "custom_values", nullable = false)
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
@JsonProperty
|
|
||||||
private ObjectNode values = JsonNodeFactory.instance.objectNode();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void prePersist() {
|
|
||||||
super.prePersist();
|
|
||||||
if (login == null || login.isEmpty()) {
|
|
||||||
login = "user" + System.currentTimeMillis();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setPassword(String password) {
|
|
||||||
passwordHash = context.getSecurityManager().getHashUtility().hash(password);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean verifyPassword(String password) {
|
|
||||||
return context.getSecurityManager().getHashUtility().validate(password, passwordHash);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,229 +0,0 @@
|
||||||
package ru.kirillius.XCP.Persistence;
|
|
||||||
|
|
||||||
import lombok.Getter;
|
|
||||||
import org.hibernate.Interceptor;
|
|
||||||
import org.hibernate.Session;
|
|
||||||
import org.hibernate.SessionFactory;
|
|
||||||
import org.hibernate.cfg.Configuration;
|
|
||||||
import org.hibernate.type.Type;
|
|
||||||
import ru.kirillius.XCP.Commons.Context;
|
|
||||||
import ru.kirillius.XCP.Persistence.Repositories.*;
|
|
||||||
import ru.kirillius.XCP.Services.RepositoryService;
|
|
||||||
import ru.kirillius.XCP.Services.ServiceLoadPriority;
|
|
||||||
import tools.jackson.databind.ObjectMapper;
|
|
||||||
import tools.jackson.databind.json.JsonMapper;
|
|
||||||
|
|
||||||
import java.lang.reflect.InvocationTargetException;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
|
|
||||||
@ServiceLoadPriority(0)
|
|
||||||
public final class RepositoryServiceImpl implements RepositoryService {
|
|
||||||
@Getter
|
|
||||||
private ObjectMapper mapper = new ObjectMapper();
|
|
||||||
private final Configuration configuration;
|
|
||||||
private SessionFactory sessionFactory;
|
|
||||||
private DatabaseConfiguration databaseConfiguration;
|
|
||||||
private final Map<Class<? extends PersistenceEntity>, Class<? extends PersistenceEntity>> entityBaseBindings = new ConcurrentHashMap<>();
|
|
||||||
private final Map<Class<? extends Repository<?>>, Class<? extends PersistenceEntity>> repositoryEntityBindings = new ConcurrentHashMap<>();
|
|
||||||
private final Map<Class<? extends Repository<?>>, Repository<?>> repositoryBindings = new ConcurrentHashMap<>();
|
|
||||||
private final Map<Class<? extends Repository<?>>, Class<? extends Repository<?>>> repositoryBaseBindings = new ConcurrentHashMap<>();
|
|
||||||
private final Map<Class<? extends PersistenceEntity>, Class<? extends Repository<?>>> entityBindings = new ConcurrentHashMap<>();
|
|
||||||
private final Collection<Class<? extends AbstractRepository<?>>> managedRepositoryClasses;
|
|
||||||
@Getter
|
|
||||||
private Context context;
|
|
||||||
|
|
||||||
public RepositoryServiceImpl(DatabaseConfiguration databaseConfiguration, Collection<Class<? extends AbstractRepository<?>>> repositoryImplClasses) {
|
|
||||||
managedRepositoryClasses = repositoryImplClasses;
|
|
||||||
configuration = new Configuration();
|
|
||||||
configuration.configure();
|
|
||||||
registerClasses();
|
|
||||||
loadDatabaseConfig(databaseConfiguration);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void loadDatabaseConfig(DatabaseConfiguration databaseConfiguration) {
|
|
||||||
this.databaseConfiguration = databaseConfiguration;
|
|
||||||
configuration.getProperties().setProperty("hibernate.connection.url", databaseConfiguration.getConnectionUrl());
|
|
||||||
}
|
|
||||||
|
|
||||||
public RepositoryServiceImpl(Collection<Class<? extends AbstractRepository<?>>> repositoryImplClasses) {
|
|
||||||
managedRepositoryClasses = repositoryImplClasses;
|
|
||||||
configuration = new Configuration();
|
|
||||||
configuration.configure();
|
|
||||||
registerClasses();
|
|
||||||
}
|
|
||||||
|
|
||||||
public RepositoryServiceImpl() {
|
|
||||||
managedRepositoryClasses = List.of(
|
|
||||||
ApiTokenRepositoryImpl.class,
|
|
||||||
GroupRepositoryImpl.class,
|
|
||||||
InputRepositoryImpl.class,
|
|
||||||
OutputRepositoryImpl.class,
|
|
||||||
TagRepositoryImpl.class,
|
|
||||||
UserRepositoryImpl.class,
|
|
||||||
PropertyDescriptorRepositoryImpl.class
|
|
||||||
);
|
|
||||||
configuration = new Configuration();
|
|
||||||
configuration.configure();
|
|
||||||
registerClasses();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void registerClasses() {
|
|
||||||
managedRepositoryClasses.forEach(aClass -> {
|
|
||||||
var implementation = aClass.getAnnotation(EntityImplementation.class);
|
|
||||||
if (implementation == null) {
|
|
||||||
throw new IllegalStateException("@" + EntityImplementation.class.getSimpleName() + " is not present in class " + aClass.getSimpleName());
|
|
||||||
}
|
|
||||||
|
|
||||||
configuration.addAnnotatedClass(implementation.value());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private AbstractRepository<?> instantiateRepository(Class<? extends AbstractRepository<?>> sCls) {
|
|
||||||
try {
|
|
||||||
var constructor = sCls.getDeclaredConstructor(RepositoryServiceImpl.class);
|
|
||||||
constructor.setAccessible(true);
|
|
||||||
return constructor.newInstance(this);
|
|
||||||
} catch (InvocationTargetException | InstantiationException | IllegalAccessException |
|
|
||||||
NoSuchMethodException e) {
|
|
||||||
throw new RuntimeException("Failed to instantiate Service", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Session openSession() {
|
|
||||||
return sessionFactory.openSession();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void close() {
|
|
||||||
repositoryBindings.clear();
|
|
||||||
entityBindings.clear();
|
|
||||||
if (sessionFactory != null) {
|
|
||||||
sessionFactory.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private volatile boolean initialized;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void initialize(Context context) {
|
|
||||||
if (initialized) {
|
|
||||||
throw new IllegalStateException("Initialized already");
|
|
||||||
}
|
|
||||||
initialized = true;
|
|
||||||
this.context = context;
|
|
||||||
if (databaseConfiguration == null) {
|
|
||||||
loadDatabaseConfig(new H2DatabaseInFileConfiguration(context.getConfig().getDatabaseFile()));
|
|
||||||
}
|
|
||||||
managedRepositoryClasses.forEach(aClass -> {
|
|
||||||
var instance = instantiateRepository(aClass);
|
|
||||||
var baseClass = getRepositoryBaseType(aClass);
|
|
||||||
repositoryBindings.put(baseClass, instance);
|
|
||||||
|
|
||||||
var entityClass = getEntityBaseType(getRepositoryEntityType(aClass));
|
|
||||||
entityBindings.put(entityClass, baseClass);
|
|
||||||
});
|
|
||||||
mapper = JsonMapper.builder().addModule(new PersistenceSerializationModule(this)).build();
|
|
||||||
this.configuration.setInterceptor(new EntityInterceptor(context));
|
|
||||||
sessionFactory = this.configuration.buildSessionFactory();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class EntityInterceptor implements Interceptor {
|
|
||||||
private final Context context;
|
|
||||||
|
|
||||||
private EntityInterceptor(Context context) {
|
|
||||||
this.context = context;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onLoad(Object entity, Object id, Object[] state, String[] propertyNames, Type[] types) {
|
|
||||||
if (entity instanceof ContextReferencedEntity referencedEntity) {
|
|
||||||
if (referencedEntity.getContext() == null) {
|
|
||||||
referencedEntity.setContext(context);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <E extends PersistenceEntity> Repository<E> getRepositoryForEntity(Class<E> entityType) {
|
|
||||||
//noinspection unchecked
|
|
||||||
return (Repository<E>) getRepository(entityBindings.get(entityType));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <R extends Repository<?>> R getRepository(Class<R> repositoryType) {
|
|
||||||
//noinspection unchecked
|
|
||||||
return (R) repositoryBindings.get(repositoryType);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns entity base interface Class<E> from Class<? extends E>
|
|
||||||
*
|
|
||||||
* @param entityClass
|
|
||||||
* @return Class<E>
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public Class<? extends PersistenceEntity> getEntityBaseType(Class<? extends PersistenceEntity> entityClass) {
|
|
||||||
if (!entityBaseBindings.containsKey(entityClass)) {
|
|
||||||
var foundClass = Arrays.stream(entityClass.getInterfaces()).filter(PersistenceEntity.class::isAssignableFrom).findFirst();
|
|
||||||
if (foundClass.isPresent()) {
|
|
||||||
entityBaseBindings.put(entityClass, (Class<? extends PersistenceEntity>) foundClass.get());
|
|
||||||
} else {
|
|
||||||
throw new RuntimeException("Unable to determine base interface Class<? extends PersistenceEntity> of " + entityClass.getName());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return entityBaseBindings.get(entityClass);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns repository base interface type Class<E> from Class<? extends E>
|
|
||||||
*
|
|
||||||
* @param repositoryClass
|
|
||||||
* @return Class<E>
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public Class<? extends Repository<?>> getRepositoryBaseType(Class<? extends Repository<?>> repositoryClass) {
|
|
||||||
if (!repositoryBaseBindings.containsKey(repositoryClass)) {
|
|
||||||
var foundClass = Arrays.stream(repositoryClass.getInterfaces()).filter(Repository.class::isAssignableFrom).findFirst();
|
|
||||||
if (foundClass.isPresent()) {
|
|
||||||
repositoryBaseBindings.put(repositoryClass, (Class<? extends Repository<?>>) foundClass.get());
|
|
||||||
} else {
|
|
||||||
throw new RuntimeException("Unable to determine base interface Class<? extends Repository> of " + repositoryClass.getName());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return repositoryBaseBindings.get(repositoryClass);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns Entity implementation class that implements E from Class<? extends Repository<E>>
|
|
||||||
*
|
|
||||||
* @param repositoryImplClass
|
|
||||||
* @return Class<? extends E>
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public Class<? extends PersistenceEntity> getRepositoryEntityType(Class<? extends Repository<?>> repositoryImplClass) {
|
|
||||||
if (!repositoryEntityBindings.containsKey(repositoryImplClass)) {
|
|
||||||
var annotation = repositoryImplClass.getAnnotation(EntityImplementation.class);
|
|
||||||
if (annotation != null) {
|
|
||||||
repositoryEntityBindings.put(repositoryImplClass, annotation.value());
|
|
||||||
} else {
|
|
||||||
throw new RuntimeException("Unable to get @" + EntityImplementation.class.getSimpleName() + " from class " + repositoryImplClass.getName());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return repositoryEntityBindings.get(repositoryImplClass);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Class<? extends PersistenceEntity> getEntityTypeByName(String entityName) {
|
|
||||||
return entityBaseBindings.values().stream().filter(e -> e.getSimpleName().equals(entityName)).findFirst().orElse(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
package ru.kirillius.XCP.Persistence;
|
|
||||||
|
|
||||||
import ru.kirillius.XCP.Commons.StreamHandler;
|
|
||||||
|
|
||||||
import java.util.stream.Stream;
|
|
||||||
|
|
||||||
public class SimpleStreamHandler<T> implements StreamHandler<T> {
|
|
||||||
private final Stream<T> stream;
|
|
||||||
|
|
||||||
public SimpleStreamHandler(Stream<T> stream) {
|
|
||||||
this.stream = stream;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Stream<T> get() {
|
|
||||||
return stream;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void close() {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,31 +0,0 @@
|
||||||
package ru.kirillius.XCP.Persistence;
|
|
||||||
|
|
||||||
import ru.kirillius.XCP.Persistence.Repositories.TagRepository;
|
|
||||||
import tools.jackson.core.JacksonException;
|
|
||||||
import tools.jackson.core.JsonParser;
|
|
||||||
import tools.jackson.databind.DeserializationContext;
|
|
||||||
import tools.jackson.databind.deser.std.StdDeserializer;
|
|
||||||
|
|
||||||
public class TagCollectionDeserializer extends StdDeserializer<TagCollection> {
|
|
||||||
private final RepositoryServiceImpl repositoryService;
|
|
||||||
|
|
||||||
public TagCollectionDeserializer(RepositoryServiceImpl repositoryService) {
|
|
||||||
super(TagCollection.class);
|
|
||||||
this.repositoryService = repositoryService;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public TagCollection deserialize(JsonParser p, DeserializationContext ctxt) throws JacksonException {
|
|
||||||
var result = new TagCollectionImpl();
|
|
||||||
|
|
||||||
var tagNames = p.readValueAs(String[].class);
|
|
||||||
var repository = repositoryService.getRepository(TagRepository.class);
|
|
||||||
|
|
||||||
for (var name : tagNames) {
|
|
||||||
var tag = repository.getByNameOrCreate(name);
|
|
||||||
result.add(tag);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
package ru.kirillius.XCP.Persistence;
|
|
||||||
|
|
||||||
import ru.kirillius.XCP.Persistence.Entities.Tag;
|
|
||||||
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.HashSet;
|
|
||||||
|
|
||||||
public class TagCollectionImpl extends HashSet<Tag> implements TagCollection {
|
|
||||||
public TagCollectionImpl() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public TagCollectionImpl(Collection<? extends Tag> c) {
|
|
||||||
super(c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
package ru.kirillius.XCP.Persistence;
|
|
||||||
|
|
||||||
import tools.jackson.core.JacksonException;
|
|
||||||
import tools.jackson.core.JsonGenerator;
|
|
||||||
import tools.jackson.databind.SerializationContext;
|
|
||||||
import tools.jackson.databind.ser.std.StdSerializer;
|
|
||||||
|
|
||||||
public class TagCollectionSerializer extends StdSerializer<TagCollection> {
|
|
||||||
|
|
||||||
public TagCollectionSerializer() {
|
|
||||||
super(TagCollection.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void serialize(TagCollection set, JsonGenerator gen, SerializationContext provider) throws JacksonException {
|
|
||||||
if(set == null) {
|
|
||||||
gen.writeNull();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
gen.writeStartArray();
|
|
||||||
set.forEach(tag -> gen.writeString(tag.getName()));
|
|
||||||
gen.writeEndArray();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
package ru.kirillius.XCP.Serialization;
|
|
||||||
|
|
||||||
import jakarta.persistence.AttributeConverter;
|
|
||||||
import ru.kirillius.XCP.Persistence.PollSettingsImpl;
|
|
||||||
import tools.jackson.databind.ObjectMapper;
|
|
||||||
|
|
||||||
public class PollSettingsConverter implements AttributeConverter<PollSettingsImpl, String> {
|
|
||||||
private final static ObjectMapper mapper = new ObjectMapper();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String convertToDatabaseColumn(PollSettingsImpl pollSettings) {
|
|
||||||
return mapper.writeValueAsString(pollSettings);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public PollSettingsImpl convertToEntityAttribute(String s) {
|
|
||||||
return mapper.readValue(s, PollSettingsImpl.class);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
package ru.kirillius.XCP.Serialization;
|
|
||||||
|
|
||||||
import jakarta.persistence.AttributeConverter;
|
|
||||||
import ru.kirillius.XCP.Data.ValueTransformationChain;
|
|
||||||
import tools.jackson.databind.ObjectMapper;
|
|
||||||
|
|
||||||
public class ValueTransformationChainConverter implements AttributeConverter<ValueTransformationChain, String> {
|
|
||||||
private final static ObjectMapper mapper = new ObjectMapper();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String convertToDatabaseColumn(ValueTransformationChain pollSettings) {
|
|
||||||
return mapper.writeValueAsString(pollSettings);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ValueTransformationChain convertToEntityAttribute(String s) {
|
|
||||||
return mapper.readValue(s, ValueTransformationChain.class);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,39 +0,0 @@
|
||||||
<!DOCTYPE hibernate-configuration PUBLIC
|
|
||||||
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
|
|
||||||
"http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
|
|
||||||
<hibernate-configuration>
|
|
||||||
<session-factory>
|
|
||||||
<!-- JDBC Database connection settings -->
|
|
||||||
<property name="connection.driver_class">org.h2.Driver</property>
|
|
||||||
<property name="connection.url"></property>
|
|
||||||
<property name="connection.username">sa</property>
|
|
||||||
<property name="connection.password">sa</property>
|
|
||||||
<!-- JDBC connection pool settings ... using built-in test pool -->
|
|
||||||
<property name="connection.pool_size">1</property>
|
|
||||||
<!-- Select our SQL dialect -->
|
|
||||||
|
|
||||||
<!-- Echo the SQL to stdout -->
|
|
||||||
<property name="show_sql">true</property>
|
|
||||||
<!-- Set the current session context -->
|
|
||||||
<property name="current_session_context_class">thread</property>
|
|
||||||
<!-- Drop and re-create the database schema on startup -->
|
|
||||||
<!-- <property name="hbm2ddl.auto">create-drop</property> <!– TODO cahnge to UPDATE ! –>-->
|
|
||||||
<property name="hbm2ddl.auto">update</property>
|
|
||||||
<!-- dbcp connection pool configuration -->
|
|
||||||
<property name="hibernate.dbcp.initialSize">5</property>
|
|
||||||
<property name="hibernate.dbcp.maxTotal">20</property>
|
|
||||||
<property name="hibernate.dbcp.maxIdle">10</property>
|
|
||||||
<property name="hibernate.dbcp.minIdle">5</property>
|
|
||||||
<property name="hibernate.dbcp.maxWaitMillis">-1</property>
|
|
||||||
<!-- c3p0 config http://www.hibernate.org/214.html -->
|
|
||||||
<property name="connection.provider_class">org.hibernate.connection.C3P0ConnectionProvider</property>
|
|
||||||
<property name="hibernate.c3p0.acquire_increment">1</property>
|
|
||||||
<property name="hibernate.c3p0.idle_test_period">60</property>
|
|
||||||
<property name="hibernate.c3p0.min_size">1</property>
|
|
||||||
<property name="hibernate.c3p0.max_size">2</property>
|
|
||||||
<property name="hibernate.c3p0.max_statements">50</property>
|
|
||||||
<property name="hibernate.c3p0.timeout">0</property>
|
|
||||||
<property name="hibernate.c3p0.acquireRetryAttempts">1</property>
|
|
||||||
<property name="hibernate.c3p0.acquireRetryDelay">250</property>
|
|
||||||
</session-factory>
|
|
||||||
</hibernate-configuration>
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
package ru.kirillius.XCP.Persistence;
|
|
||||||
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
public class H2InMemoryConfiguration implements DatabaseConfiguration {
|
|
||||||
@Override
|
|
||||||
public String getConnectionUrl() {
|
|
||||||
return "jdbc:h2:mem:" + "testdb_" + UUID.randomUUID().toString().substring(0, 8);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
package ru.kirillius.XCP.Persistence.Repositories;
|
|
||||||
|
|
||||||
import ru.kirillius.XCP.Persistence.Entities.ApiToken;
|
|
||||||
import ru.kirillius.XCP.Services.RepositoryService;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
import static ru.kirillius.XCP.Persistence.TestEnvironment.instantiateTestService;
|
|
||||||
|
|
||||||
class ApiTokenRepositoryImplTest extends GenericRepositoryTest<ApiToken, ApiTokenRepositoryImpl> {
|
|
||||||
@Override
|
|
||||||
protected RepositoryService spawnRepositoryService() {
|
|
||||||
var service = instantiateTestService(List.of(repositoryClass, UserRepositoryImpl.class));
|
|
||||||
var userRepository = service.getRepository(UserRepository.class);
|
|
||||||
var user = userRepository.create();
|
|
||||||
userRepository.save(user);
|
|
||||||
return service;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void modify(ApiToken entity, RepositoryService service) {
|
|
||||||
entity.setName("test" + UUID.randomUUID());
|
|
||||||
var user = service.getRepository(UserRepository.class).get(1);
|
|
||||||
entity.setUser(user);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue