Compare commits
12 Commits
master
...
feature/op
| Author | SHA1 | Date |
|---|---|---|
|
|
381fda5252 | |
|
|
3ad2f762e0 | |
|
|
8a2d52b9d2 | |
|
|
7e52c4735f | |
|
|
898a170d7b | |
|
|
0d202b5574 | |
|
|
dcb15ebdc1 | |
|
|
2d7c7fea9c | |
|
|
b264b38fe4 | |
|
|
f50fd2a474 | |
|
|
8e2412f36a | |
|
|
e29286cce3 |
|
|
@ -38,3 +38,5 @@ build/
|
|||
.DS_Store
|
||||
/.idea/
|
||||
/.mvn/
|
||||
xcp.conf
|
||||
xcpdata.mv.db
|
||||
|
|
|
|||
|
|
@ -0,0 +1,198 @@
|
|||
package ru.kirillius.XCP.ApiGenerator;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class JavascriptClientGenerator {
|
||||
|
||||
private static final String SERVICES_PACKAGE = "ru.kirillius.XCP.RPC.Services";
|
||||
private static final String OUTPUT_DIR = "target/generated-sources/api.js";
|
||||
|
||||
public static void main(String[] args) {
|
||||
try {
|
||||
generateJavascriptClient();
|
||||
System.out.println("JavaScript API client generated successfully at: " + OUTPUT_DIR);
|
||||
} catch (Exception e) {
|
||||
System.err.println("Failed to generate JavaScript client: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public static void generateJavascriptClient() throws IOException, ClassNotFoundException {
|
||||
StringBuilder jsBuilder = new StringBuilder();
|
||||
jsBuilder.append("API = {\n");
|
||||
|
||||
List<Class<?>> serviceClasses = findServiceClasses();
|
||||
boolean first = true;
|
||||
|
||||
for (Class<?> serviceClass : serviceClasses) {
|
||||
if (!first) {
|
||||
jsBuilder.append(",\n");
|
||||
}
|
||||
first = false;
|
||||
|
||||
String className = serviceClass.getSimpleName();
|
||||
jsBuilder.append(" ").append(className).append(" : {\n");
|
||||
|
||||
Method[] methods = serviceClass.getDeclaredMethods();
|
||||
List<Method> rpcMethods = new ArrayList<>();
|
||||
|
||||
for (Method method : methods) {
|
||||
Annotation[] annotations = method.getAnnotations();
|
||||
for (Annotation annotation : annotations) {
|
||||
if (annotation.annotationType().getSimpleName().equals("JsonRpcMethod")) {
|
||||
rpcMethods.add(method);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < rpcMethods.size(); i++) {
|
||||
Method method = rpcMethods.get(i);
|
||||
Annotation jsonRpcMethod = null;
|
||||
|
||||
for (Annotation annotation : method.getAnnotations()) {
|
||||
if (annotation.annotationType().getSimpleName().equals("JsonRpcMethod")) {
|
||||
jsonRpcMethod = annotation;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
generateMethod(jsBuilder, method, jsonRpcMethod, className);
|
||||
|
||||
if (i < rpcMethods.size() - 1) {
|
||||
jsBuilder.append(",\n");
|
||||
} else {
|
||||
jsBuilder.append("\n");
|
||||
}
|
||||
}
|
||||
|
||||
jsBuilder.append(" }");
|
||||
}
|
||||
|
||||
jsBuilder.append("\n}\n");
|
||||
|
||||
File outputFile = new File(OUTPUT_DIR);
|
||||
outputFile.getParentFile().mkdirs();
|
||||
|
||||
try (FileWriter writer = new FileWriter(outputFile)) {
|
||||
writer.write(jsBuilder.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private static List<Class<?>> findServiceClasses() throws ClassNotFoundException {
|
||||
List<Class<?>> serviceClasses = new ArrayList<>();
|
||||
|
||||
serviceClasses.add(Class.forName(SERVICES_PACKAGE + ".Auth"));
|
||||
serviceClasses.add(Class.forName(SERVICES_PACKAGE + ".Profile"));
|
||||
serviceClasses.add(Class.forName(SERVICES_PACKAGE + ".UserManagement"));
|
||||
|
||||
return serviceClasses;
|
||||
}
|
||||
|
||||
private static void generateMethod(StringBuilder builder, Method method, Annotation jsonRpcMethod, String className) {
|
||||
String methodName = method.getName();
|
||||
|
||||
builder.append(" /**\n");
|
||||
|
||||
try {
|
||||
Object description = jsonRpcMethod.annotationType().getMethod("description").invoke(jsonRpcMethod);
|
||||
builder.append(" * ").append(description).append("\n");
|
||||
|
||||
Object parameters = jsonRpcMethod.annotationType().getMethod("parameters").invoke(jsonRpcMethod);
|
||||
if (parameters instanceof Object[]) {
|
||||
Object[] params = (Object[]) parameters;
|
||||
for (Object param : params) {
|
||||
Class<?> paramClass = param.getClass();
|
||||
Object name = paramClass.getMethod("name").invoke(param);
|
||||
Object optional = paramClass.getMethod("optional").invoke(param);
|
||||
|
||||
builder.append(" * @param ").append(name);
|
||||
if (!(Boolean) optional) {
|
||||
builder.append(" (required)");
|
||||
} else {
|
||||
builder.append(" (optional)");
|
||||
}
|
||||
builder.append("\n");
|
||||
}
|
||||
}
|
||||
|
||||
Object returnType = jsonRpcMethod.annotationType().getMethod("returnType").invoke(jsonRpcMethod);
|
||||
String returnTypeName = getSimpleTypeName((Class<?>) returnType);
|
||||
builder.append(" * @returns ").append(returnTypeName).append("\n");
|
||||
} catch (Exception e) {
|
||||
builder.append(" * Method: ").append(methodName).append("\n");
|
||||
builder.append(" * @returns unknown\n");
|
||||
}
|
||||
|
||||
builder.append(" */\n");
|
||||
|
||||
builder.append(" ").append(methodName).append(" : async function(");
|
||||
|
||||
List<String> paramNames = new ArrayList<>();
|
||||
try {
|
||||
Object parameters = jsonRpcMethod.annotationType().getMethod("parameters").invoke(jsonRpcMethod);
|
||||
if (parameters instanceof Object[]) {
|
||||
Object[] params = (Object[]) parameters;
|
||||
for (Object param : params) {
|
||||
Object name = param.getClass().getMethod("name").invoke(param);
|
||||
Object optional = param.getClass().getMethod("optional").invoke(param);
|
||||
if (!(Boolean) optional) {
|
||||
paramNames.add(name.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// No parameters available
|
||||
}
|
||||
|
||||
builder.append(String.join(", ", paramNames));
|
||||
builder.append(") {\n");
|
||||
builder.append(" return await JSONRPC.invoke(\"").append(className).append(".").append(methodName).append("\", {");
|
||||
|
||||
List<String> paramPairs = new ArrayList<>();
|
||||
try {
|
||||
Object parameters = jsonRpcMethod.annotationType().getMethod("parameters").invoke(jsonRpcMethod);
|
||||
if (parameters instanceof Object[]) {
|
||||
Object[] params = (Object[]) parameters;
|
||||
for (Object param : params) {
|
||||
Object name = param.getClass().getMethod("name").invoke(param);
|
||||
Object optional = param.getClass().getMethod("optional").invoke(param);
|
||||
if (!(Boolean) optional) {
|
||||
paramPairs.add(name.toString() + ":" + name.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// No parameters available
|
||||
}
|
||||
|
||||
builder.append(String.join(", ", paramPairs));
|
||||
builder.append("});\n");
|
||||
builder.append(" }");
|
||||
}
|
||||
|
||||
private static String getSimpleTypeName(Class<?> type) {
|
||||
if (type == null) return "unknown";
|
||||
if (type == boolean.class) return "boolean";
|
||||
if (type == int.class || type == long.class) return "number";
|
||||
if (type == String.class) return "string";
|
||||
if (type == void.class || type == Void.class) return "void";
|
||||
|
||||
String typeName = type.getSimpleName();
|
||||
if (type.isArray()) {
|
||||
return "Array";
|
||||
}
|
||||
|
||||
if (typeName.contains("Node") || typeName.contains("Object")) {
|
||||
return "Object";
|
||||
}
|
||||
|
||||
return typeName;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
<?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>target/generated-sources/api.spec.json</argument>
|
||||
</arguments>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
|
|
@ -0,0 +1,125 @@
|
|||
package ru.kirillius.XCP.ApiGenerator;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
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.*;
|
||||
import java.util.Collection;
|
||||
import java.util.Objects;
|
||||
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();
|
||||
|
||||
@SneakyThrows
|
||||
public void generate() {
|
||||
specs.removeAll();
|
||||
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 = specs.putObject(cls.getSimpleName());
|
||||
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());
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
methodSpecs.put("return", getTypeName(descriptor.returnType()));
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void writeSpecs() throws IOException {
|
||||
outputFile.getParentFile().mkdirs();
|
||||
try (var stream = new FileOutputStream(outputFile)) {
|
||||
var mapper = new ObjectMapper();
|
||||
mapper.writerWithDefaultPrettyPrinter().writeValue(stream, specs);
|
||||
}
|
||||
}
|
||||
|
||||
private static String getTypeName(Class<?> type) {
|
||||
if (type == null) return "unknown";
|
||||
if (type == boolean.class) return "boolean";
|
||||
if (type == int.class || type == long.class) return "number";
|
||||
if (type == String.class) return "string";
|
||||
if (type == void.class || type == Void.class) return "void";
|
||||
|
||||
if (type.isArray() || Collection.class.isAssignableFrom(type) || type == ArrayNode.class) {
|
||||
return "Array";
|
||||
}
|
||||
|
||||
if (JsonNode.class.isAssignableFrom(type)) {
|
||||
return "Object";
|
||||
}
|
||||
|
||||
return type.getSimpleName();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
<?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>
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
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);
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
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;
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
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();
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
package ru.kirillius.XCP.Commons;
|
||||
|
||||
import java.io.Closeable;
|
||||
|
||||
public interface Initializable extends Closeable {
|
||||
void initialize(Context context);
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
package ru.kirillius.XCP.Commons;
|
||||
|
||||
import java.io.Closeable;
|
||||
|
||||
public interface ResourceHandler<T> extends Closeable {
|
||||
T get();
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
package ru.kirillius.XCP.Commons;
|
||||
|
||||
public interface Service extends Initializable {
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
package ru.kirillius.XCP.Commons;
|
||||
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public interface StreamHandler<T> extends ResourceHandler<Stream<T>> {
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
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);
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
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();
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
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<>();
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
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;
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
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> {
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
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 "";
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
package ru.kirillius.XCP.Logging;
|
||||
|
||||
public enum LogLevel {
|
||||
INFO,
|
||||
ERROR,
|
||||
WARNING
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package ru.kirillius.XCP.Logging;
|
||||
|
||||
import lombok.Builder;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
@Builder
|
||||
public record LogMessage(LogLevel level, Instant date, String message) {
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
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);
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
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();
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
package ru.kirillius.XCP.Persistence.Entities;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import ru.kirillius.XCP.Persistence.PersistenceEntity;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Date;
|
||||
|
||||
public interface ApiToken extends PersistenceEntity {
|
||||
|
||||
User getUser();
|
||||
|
||||
void setUser(User user);
|
||||
|
||||
String getName();
|
||||
|
||||
void setName(String name);
|
||||
|
||||
Date getExpirationDate();
|
||||
|
||||
void setExpirationDate(Date expirationDate);
|
||||
|
||||
@JsonIgnore
|
||||
default boolean isExpired() {
|
||||
return getExpirationDate().toInstant().isBefore(Instant.now());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package ru.kirillius.XCP.Persistence.Entities;
|
||||
|
||||
import ru.kirillius.XCP.Persistence.NodeEntity;
|
||||
|
||||
public interface Group extends NodeEntity {
|
||||
String getIcon();
|
||||
|
||||
void setIcon(String icon);
|
||||
|
||||
boolean isPrototype();
|
||||
|
||||
void setPrototype(boolean prototype);
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package ru.kirillius.XCP.Persistence.Entities;
|
||||
|
||||
import ru.kirillius.XCP.Data.PollSettings;
|
||||
import ru.kirillius.XCP.Persistence.IOEntity;
|
||||
import ru.kirillius.XCP.Persistence.NodeEntity;
|
||||
|
||||
public interface Input extends IOEntity, NodeEntity {
|
||||
|
||||
PollSettings getPollSettings();
|
||||
|
||||
void setPollSettings(PollSettings pollSettings);
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
package ru.kirillius.XCP.Persistence.Entities;
|
||||
|
||||
import ru.kirillius.XCP.Persistence.IOEntity;
|
||||
import ru.kirillius.XCP.Persistence.NodeEntity;
|
||||
|
||||
public interface Output extends IOEntity, NodeEntity {
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package ru.kirillius.XCP.Persistence.Entities;
|
||||
|
||||
import ru.kirillius.XCP.Persistence.PersistenceEntity;
|
||||
|
||||
public interface Tag extends PersistenceEntity {
|
||||
String getName();
|
||||
|
||||
void setName(String name);
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
package ru.kirillius.XCP.Persistence.Entities;
|
||||
|
||||
import ru.kirillius.XCP.Persistence.PersistenceEntity;
|
||||
import ru.kirillius.XCP.Security.UserRole;
|
||||
import tools.jackson.databind.node.ObjectNode;
|
||||
|
||||
public interface User extends PersistenceEntity {
|
||||
void setPassword(String password);
|
||||
|
||||
boolean verifyPassword(String password);
|
||||
|
||||
String getLogin();
|
||||
|
||||
void setLogin(String login);
|
||||
|
||||
UserRole getRole();
|
||||
|
||||
void setRole(UserRole role);
|
||||
|
||||
ObjectNode getValues();
|
||||
|
||||
void setValues(ObjectNode values);
|
||||
|
||||
String getName();
|
||||
|
||||
void setName(String name);
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
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);
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
package ru.kirillius.XCP.Persistence;
|
||||
|
||||
import ru.kirillius.XCP.Persistence.Entities.Group;
|
||||
import tools.jackson.databind.node.ObjectNode;
|
||||
|
||||
public interface NodeEntity extends PersistenceEntity {
|
||||
String getName();
|
||||
|
||||
void setName(String name);
|
||||
|
||||
boolean isProtectedEntity();
|
||||
|
||||
void setProtectedEntity(boolean essential);
|
||||
|
||||
boolean isEnabled();
|
||||
|
||||
void setEnabled(boolean enabled);
|
||||
|
||||
Group getParent();
|
||||
|
||||
void setParent(Group parent);
|
||||
|
||||
ObjectNode getProperties();
|
||||
|
||||
void setProperties(ObjectNode properties);
|
||||
|
||||
TagCollection getTags();
|
||||
|
||||
void setTags(TagCollection tags);
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
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);
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package ru.kirillius.XCP.Persistence;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public interface PersistenceEntity {
|
||||
long getId();
|
||||
|
||||
UUID getUuid();
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
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);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
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);
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
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();
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
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> {
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
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> {
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
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);
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
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);
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
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();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package ru.kirillius.XCP.Persistence;
|
||||
|
||||
import ru.kirillius.XCP.Persistence.Entities.Tag;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
public interface TagCollection extends Collection<Tag> {
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package ru.kirillius.XCP.Security;
|
||||
|
||||
public interface HashUtility {
|
||||
String hash(String password);
|
||||
|
||||
default boolean validate(String password, String hash) {
|
||||
return hash(password).equals(hash);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
package ru.kirillius.XCP.Security;
|
||||
|
||||
public interface SecurityManager {
|
||||
HashUtility getHashUtility();
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
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();
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
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;
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
package ru.kirillius.XCP.Services;
|
||||
|
||||
import ru.kirillius.XCP.Commons.Service;
|
||||
|
||||
public interface WebService extends Service {
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
<?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>
|
||||
|
|
@ -0,0 +1,165 @@
|
|||
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.RepositoryServiceImpl;
|
||||
import ru.kirillius.XCP.Security.ConfigManagerImpl;
|
||||
import ru.kirillius.XCP.Security.SecurityManager;
|
||||
import ru.kirillius.XCP.Security.SecurityManagerImpl;
|
||||
import ru.kirillius.XCP.Services.ServiceLoadPriority;
|
||||
import ru.kirillius.XCP.Services.WebService;
|
||||
import ru.kirillius.XCP.web.WebServiceImpl;
|
||||
|
||||
import java.io.File;
|
||||
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");
|
||||
}
|
||||
Runtime.getRuntime().addShutdownHook(new Thread(this::shutdown));
|
||||
|
||||
((WebServiceImpl) getService(WebService.class)).join();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
<?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>
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
package ru.kirillius.XCP.Security;
|
||||
|
||||
import de.mkammerer.argon2.Argon2;
|
||||
import de.mkammerer.argon2.Argon2Factory;
|
||||
import lombok.Getter;
|
||||
|
||||
public class Argon2HashUtility implements HashUtility {
|
||||
|
||||
@Getter
|
||||
private final static HashUtility instance = new Argon2HashUtility();
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,85 @@
|
|||
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;
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package ru.kirillius.XCP.Security;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
public class SecurityManagerImpl implements SecurityManager {
|
||||
@Getter
|
||||
private final HashUtility hashUtility = new Argon2HashUtility();
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
<?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>
|
||||
|
|
@ -0,0 +1,269 @@
|
|||
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();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package ru.kirillius.XCP.Persistence;
|
||||
|
||||
import ru.kirillius.XCP.Commons.Context;
|
||||
|
||||
public interface ContextReferencedEntity {
|
||||
void setContext(Context context);
|
||||
Context getContext();
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
package ru.kirillius.XCP.Persistence;
|
||||
|
||||
public interface DatabaseConfiguration {
|
||||
String getConnectionUrl();
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
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();
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package ru.kirillius.XCP.Persistence;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
public class EntityReference extends AtomicReference<PersistenceEntity> {
|
||||
public EntityReference(PersistenceEntity initialValue) {
|
||||
super(initialValue);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
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();
|
||||
|
||||
try {
|
||||
@SuppressWarnings("unchecked")
|
||||
var repository = repositoryService.getRepositoryForEntity((Class<? extends PersistenceEntity>) Class.forName(type));
|
||||
if (uuid != null) {
|
||||
return new EntityReference(repository.get(UUID.fromString(uuid)));
|
||||
}
|
||||
return new EntityReference(repository.get(id));
|
||||
} catch (ClassNotFoundException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
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.getName());
|
||||
gen.writeNumberProperty("id", value.getId());
|
||||
gen.writeStringProperty("uuid", value.getUuid().toString());
|
||||
gen.writeEndObject();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
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"), "");
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
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)
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
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;
|
||||
}
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
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);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
package ru.kirillius.XCP.Persistence.Repositories;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import jakarta.persistence.*;
|
||||
import lombok.*;
|
||||
import org.hibernate.annotations.UuidGenerator;
|
||||
import ru.kirillius.XCP.Commons.StreamHandler;
|
||||
import ru.kirillius.XCP.Persistence.AbstractRepository;
|
||||
import ru.kirillius.XCP.Persistence.Entities.ApiToken;
|
||||
import ru.kirillius.XCP.Persistence.Entities.User;
|
||||
import ru.kirillius.XCP.Persistence.EntityImplementation;
|
||||
import ru.kirillius.XCP.Persistence.EntityReference;
|
||||
import ru.kirillius.XCP.Persistence.RepositoryServiceImpl;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
||||
@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 implements ApiToken {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
@JsonProperty
|
||||
private long id = 0;
|
||||
|
||||
@JsonProperty
|
||||
@Column(unique = true, nullable = false)
|
||||
@UuidGenerator
|
||||
private UUID uuid;
|
||||
|
||||
@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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (!(o instanceof TokenEntity that)) return false;
|
||||
return Objects.equals(uuid, that.uuid);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hashCode(uuid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,177 @@
|
|||
package ru.kirillius.XCP.Persistence.Repositories;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import jakarta.persistence.*;
|
||||
import lombok.*;
|
||||
import org.hibernate.annotations.UuidGenerator;
|
||||
import ru.kirillius.XCP.Commons.StreamHandler;
|
||||
import ru.kirillius.XCP.Persistence.Entities.Group;
|
||||
import ru.kirillius.XCP.Persistence.*;
|
||||
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 implements Group {
|
||||
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
@JsonProperty
|
||||
private long id = 0;
|
||||
|
||||
@JsonProperty
|
||||
@Column(unique = true, nullable = false)
|
||||
@UuidGenerator
|
||||
private UUID uuid;
|
||||
|
||||
@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<>();
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (!(o instanceof GroupEntity that)) return false;
|
||||
return Objects.equals(uuid, that.uuid);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hashCode(uuid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,160 @@
|
|||
package ru.kirillius.XCP.Persistence.Repositories;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import jakarta.persistence.*;
|
||||
import lombok.*;
|
||||
import org.hibernate.annotations.UuidGenerator;
|
||||
import ru.kirillius.XCP.Data.PollSettings;
|
||||
import ru.kirillius.XCP.Data.ValueTransformationChain;
|
||||
import ru.kirillius.XCP.Persistence.Entities.Group;
|
||||
import ru.kirillius.XCP.Persistence.Entities.Input;
|
||||
import ru.kirillius.XCP.Persistence.*;
|
||||
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.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
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 implements Input {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
@JsonProperty
|
||||
private long id = 0;
|
||||
|
||||
@JsonProperty
|
||||
@Column(unique = true, nullable = false)
|
||||
@UuidGenerator
|
||||
private UUID uuid;
|
||||
|
||||
@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 boolean equals(Object o) {
|
||||
if (!(o instanceof InputEntity that)) return false;
|
||||
return Objects.equals(uuid, that.uuid);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hashCode(uuid);
|
||||
}
|
||||
|
||||
@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();
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,141 @@
|
|||
package ru.kirillius.XCP.Persistence.Repositories;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import jakarta.persistence.*;
|
||||
import lombok.*;
|
||||
import org.hibernate.annotations.UuidGenerator;
|
||||
import ru.kirillius.XCP.Data.ValueTransformationChain;
|
||||
import ru.kirillius.XCP.Persistence.Entities.Group;
|
||||
import ru.kirillius.XCP.Persistence.Entities.Output;
|
||||
import ru.kirillius.XCP.Persistence.*;
|
||||
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.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
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 implements Output {
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
@JsonProperty
|
||||
private long id = 0;
|
||||
|
||||
@JsonProperty
|
||||
@Column(unique = true, nullable = false)
|
||||
@UuidGenerator
|
||||
private UUID uuid;
|
||||
|
||||
@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 boolean equals(Object o) {
|
||||
if (!(o instanceof OutputEntity that)) return false;
|
||||
return Objects.equals(uuid, that.uuid);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hashCode(uuid);
|
||||
}
|
||||
|
||||
|
||||
@Column(nullable = false)
|
||||
@JsonProperty
|
||||
@Getter
|
||||
@Setter
|
||||
private String adapterId = "";
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@JsonProperty
|
||||
@Convert(converter = ValueTransformationChainConverter.class)
|
||||
@Column(nullable = false)
|
||||
private ValueTransformationChain transformationChain = new ValueTransformationChain();
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,141 @@
|
|||
package ru.kirillius.XCP.Persistence.Repositories;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import jakarta.persistence.*;
|
||||
import lombok.*;
|
||||
import org.hibernate.annotations.UuidGenerator;
|
||||
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.Objects;
|
||||
import java.util.UUID;
|
||||
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 implements Tag {
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
@JsonProperty
|
||||
private long id = 0;
|
||||
|
||||
@JsonProperty
|
||||
@Column(unique = true, nullable = false)
|
||||
@UuidGenerator
|
||||
private UUID uuid;
|
||||
|
||||
@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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (!(o instanceof TagEntity tagEntity)) return false;
|
||||
return Objects.equals(name, tagEntity.name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hashCode(name);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,106 @@
|
|||
package ru.kirillius.XCP.Persistence.Repositories;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import jakarta.persistence.*;
|
||||
import lombok.*;
|
||||
import org.hibernate.annotations.UuidGenerator;
|
||||
import ru.kirillius.XCP.Commons.Context;
|
||||
import ru.kirillius.XCP.Persistence.AbstractRepository;
|
||||
import ru.kirillius.XCP.Persistence.ContextReferencedEntity;
|
||||
import ru.kirillius.XCP.Persistence.Entities.User;
|
||||
import ru.kirillius.XCP.Persistence.EntityImplementation;
|
||||
import ru.kirillius.XCP.Persistence.RepositoryServiceImpl;
|
||||
import ru.kirillius.XCP.Security.UserRole;
|
||||
import tools.jackson.databind.node.JsonNodeFactory;
|
||||
import tools.jackson.databind.node.ObjectNode;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
||||
@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 implements User, ContextReferencedEntity {
|
||||
@Transient
|
||||
@JsonIgnore
|
||||
@Getter
|
||||
@Setter
|
||||
private Context context;
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
@JsonProperty
|
||||
private long id = 0;
|
||||
|
||||
@JsonProperty
|
||||
@Column(unique = true, nullable = false)
|
||||
@UuidGenerator
|
||||
private UUID uuid;
|
||||
|
||||
@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
|
||||
public void setPassword(String password) {
|
||||
passwordHash = context.getSecurityManager().getHashUtility().hash(password);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean verifyPassword(String password) {
|
||||
return context.getSecurityManager().getHashUtility().validate(password, passwordHash);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (!(o instanceof UserEntity that)) return false;
|
||||
return Objects.equals(uuid, that.uuid);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hashCode(uuid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,223 @@
|
|||
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
|
||||
);
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
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() {
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
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);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
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();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
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);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package ru.kirillius.XCP.Serialization;
|
||||
|
||||
import tools.jackson.databind.ObjectMapper;
|
||||
import tools.jackson.databind.node.ArrayNode;
|
||||
import tools.jackson.databind.node.ObjectNode;
|
||||
|
||||
public final class SerializationUtils {
|
||||
public final static ObjectMapper mapper = new ObjectMapper();
|
||||
|
||||
public static ObjectNode EmptyObject() {
|
||||
return mapper.createObjectNode();
|
||||
}
|
||||
|
||||
public static ArrayNode EmptyArray() {
|
||||
return mapper.createArrayNode();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
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);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
<!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>
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
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);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
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);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,115 @@
|
|||
package ru.kirillius.XCP.Persistence.Repositories;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import ru.kirillius.XCP.Persistence.NodeEntity;
|
||||
import ru.kirillius.XCP.Persistence.NodeRepository;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
abstract class GenericNodeRepositoryTest<E extends NodeEntity, R extends AbstractNodeRepository<E>> extends GenericRepositoryTest<E, R> {
|
||||
|
||||
@Test
|
||||
void getByGroup() throws IOException {
|
||||
try (var service = spawnRepositoryService()) {
|
||||
@SuppressWarnings("unchecked")
|
||||
var repository = (NodeRepository<E>) service.getRepository(service.getRepositoryBaseType(repositoryClass));
|
||||
|
||||
var groupRepository = service.getRepository(GroupRepository.class);
|
||||
|
||||
var root = groupRepository.create();
|
||||
groupRepository.save(root);
|
||||
|
||||
var firstChildGroup = groupRepository.create();
|
||||
firstChildGroup.setParent(root);
|
||||
groupRepository.save(firstChildGroup);
|
||||
|
||||
var secondChildGroup = groupRepository.create();
|
||||
secondChildGroup.setParent(root);
|
||||
groupRepository.save(secondChildGroup);
|
||||
|
||||
var firstChild = repository.create();
|
||||
modify(firstChild, service);
|
||||
firstChild.setParent(firstChildGroup);
|
||||
repository.save(firstChild);
|
||||
|
||||
var secondChild = repository.create();
|
||||
modify(secondChild, service);
|
||||
secondChild.setParent(secondChildGroup);
|
||||
repository.save(secondChild);
|
||||
|
||||
try (var handler = repository.getByGroup(firstChildGroup)) {
|
||||
var found = handler.get().findFirst().orElse(null);
|
||||
assertThat(found).isNotNull().isEqualTo(firstChild);
|
||||
}
|
||||
|
||||
try (var handler = repository.getByGroup(secondChildGroup)) {
|
||||
var found = handler.get().findFirst().orElse(null);
|
||||
assertThat(found).isNotNull().isEqualTo(secondChild);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testByTags() throws IOException {
|
||||
try (var service = spawnRepositoryService()) {
|
||||
var tagRepository = service.getRepository(TagRepository.class);
|
||||
@SuppressWarnings("unchecked")
|
||||
var repository = (NodeRepository<E>) service.getRepository(service.getRepositoryBaseType(repositoryClass));
|
||||
|
||||
var first = repository.create();
|
||||
modify(first, service);
|
||||
first.setTags(tagRepository.getByNamesOrCreate(List.of(
|
||||
"first",
|
||||
"foo",
|
||||
"bar"
|
||||
)));
|
||||
|
||||
repository.save(first);
|
||||
|
||||
var second = repository.create();
|
||||
modify(second, service);
|
||||
second.setTags(tagRepository.getByNamesOrCreate(List.of(
|
||||
"second",
|
||||
"foo",
|
||||
"third"
|
||||
)));
|
||||
repository.save(second);
|
||||
|
||||
var third = repository.create();
|
||||
modify(third, service);
|
||||
third.setTags(tagRepository.getByNamesOrCreate(List.of(
|
||||
"omg",
|
||||
"third",
|
||||
"yabba"
|
||||
)));
|
||||
repository.save(third);
|
||||
|
||||
try (var handler = repository.getByAllTags(tagRepository.getByNamesOrCreate(List.of("first")))) {
|
||||
assertThat(handler.get().toList()).containsExactlyInAnyOrder(first).doesNotContain(second, third);
|
||||
}
|
||||
|
||||
try (var handler = repository.getByAllTags(tagRepository.getByNamesOrCreate(List.of("foo")))) {
|
||||
assertThat(handler.get().toList()).containsExactlyInAnyOrder(first, second).doesNotContain(third);
|
||||
}
|
||||
|
||||
try (var handler = repository.getByAllTags(tagRepository.getByNamesOrCreate(List.of("third")))) {
|
||||
assertThat(handler.get().toList()).containsExactlyInAnyOrder(third, second).doesNotContain(first);
|
||||
}
|
||||
|
||||
try (var handler = repository.getByAnyTag(tagRepository.getByNamesOrCreate(List.of("first", "second", "third")))) {
|
||||
assertThat(handler.get().toList()).containsExactlyInAnyOrder(first, second, third);
|
||||
}
|
||||
|
||||
try (var handler = repository.getByAnyTag(tagRepository.getByNamesOrCreate(List.of("omg", "yabba")))) {
|
||||
assertThat(handler.get().toList()).containsExactlyInAnyOrder(third).doesNotContain(second, first);
|
||||
}
|
||||
|
||||
try (var handler = repository.getByAllTags(tagRepository.getByNamesOrCreate(List.of("shrash")))) {
|
||||
assertThat(handler.get().toList()).isEmpty();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,129 @@
|
|||
package ru.kirillius.XCP.Persistence.Repositories;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import ru.kirillius.XCP.Persistence.AbstractRepository;
|
||||
import ru.kirillius.XCP.Persistence.PersistenceEntity;
|
||||
import ru.kirillius.XCP.Services.RepositoryService;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static ru.kirillius.XCP.Persistence.TestEnvironment.instantiateTestService;
|
||||
|
||||
abstract class GenericRepositoryTest<E extends PersistenceEntity, R extends AbstractRepository<E>> {
|
||||
|
||||
protected Class<E> entityClass;
|
||||
protected Class<R> repositoryClass;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public GenericRepositoryTest() {
|
||||
try {
|
||||
var parameterizedType = (ParameterizedType) getClass().getGenericSuperclass();
|
||||
var typeArguments = parameterizedType.getActualTypeArguments();
|
||||
|
||||
if (typeArguments.length != 2) {
|
||||
throw new IllegalStateException("Generic parameters count is unsupported");
|
||||
}
|
||||
entityClass = (Class<E>) typeArguments[0];
|
||||
repositoryClass = (Class<R>) typeArguments[1];
|
||||
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to determine service generic parameters for Service: " + this.getClass().getName(), e);
|
||||
}
|
||||
}
|
||||
|
||||
protected RepositoryService spawnRepositoryService() {
|
||||
return instantiateTestService(List.of(repositoryClass));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCreate() throws IOException {
|
||||
try (var service = spawnRepositoryService()) {
|
||||
var repository = service.getRepositoryForEntity(entityClass);
|
||||
var entity = repository.create();
|
||||
assertThat(entity).isNotNull();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSave() throws IOException {
|
||||
try (var service = spawnRepositoryService()) {
|
||||
var repository = service.getRepositoryForEntity(entityClass);
|
||||
var entity = repository.create();
|
||||
modify(entity, service);
|
||||
repository.save(entity);
|
||||
assertThat(entity.getId()).isNotZero();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGet() throws IOException {
|
||||
try (var service = spawnRepositoryService()) {
|
||||
var repository = service.getRepositoryForEntity(entityClass);
|
||||
var entity = repository.create();
|
||||
modify(entity, service);
|
||||
repository.save(entity);
|
||||
var loaded = repository.get(entity.getId());
|
||||
assertThat(loaded).isNotNull().isEqualTo(entity);
|
||||
|
||||
var loadedByUUID = repository.get(entity.getUuid());
|
||||
assertThat(loadedByUUID).isNotNull().isEqualTo(entity);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testModify() throws IOException {
|
||||
try (var service = spawnRepositoryService()) {
|
||||
var repository = service.getRepositoryForEntity(entityClass);
|
||||
var entity = repository.create();
|
||||
modify(entity, service);
|
||||
repository.save(entity);
|
||||
modify(entity, service);
|
||||
repository.save(entity);
|
||||
var loaded = repository.get(entity.getId());
|
||||
assertThat(loaded).isNotNull().isEqualTo(entity);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRemove() throws IOException {
|
||||
try (var service = spawnRepositoryService()) {
|
||||
var repository = service.getRepositoryForEntity(entityClass);
|
||||
var entity = repository.create();
|
||||
modify(entity, service);
|
||||
repository.save(entity);
|
||||
assertThat(repository.getCount()).isEqualTo(1);
|
||||
repository.remove(entity);
|
||||
assertThat(repository.getCount()).isZero();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSerialize() throws IOException {
|
||||
try (var service = spawnRepositoryService()) {
|
||||
var repository = service.getRepositoryForEntity(entityClass);
|
||||
var entity = repository.create();
|
||||
modify(entity, service);
|
||||
repository.save(entity);
|
||||
|
||||
var serialized = repository.serialize(entity);
|
||||
var deserialized = repository.deserialize(serialized);
|
||||
|
||||
assertThat(deserialized).isNotNull().isEqualTo(entity);
|
||||
|
||||
var anotherEntity = repository.create();
|
||||
modify(anotherEntity, service);
|
||||
repository.save(anotherEntity);
|
||||
|
||||
var anotherSerialized = repository.serialize(anotherEntity);
|
||||
|
||||
assertThat(anotherSerialized.toString()).isNotEqualTo(serialized.toString());
|
||||
var anotherDeserialized = repository.deserialize(anotherSerialized);
|
||||
assertThat(anotherDeserialized).isNotNull().isEqualTo(anotherEntity).isNotEqualTo(entity);
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract void modify(E entity, RepositoryService service);
|
||||
}
|
||||
|
|
@ -0,0 +1,146 @@
|
|||
package ru.kirillius.XCP.Persistence.Repositories;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import ru.kirillius.XCP.Persistence.Entities.Group;
|
||||
import ru.kirillius.XCP.Persistence.Entities.Tag;
|
||||
import ru.kirillius.XCP.Services.RepositoryService;
|
||||
import ru.kirillius.XCP.Persistence.TagCollectionImpl;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static ru.kirillius.XCP.Persistence.TestEnvironment.instantiateTestService;
|
||||
|
||||
class GroupRepositoryImplTest extends GenericNodeRepositoryTest<Group, GroupRepositoryImpl> {
|
||||
@Override
|
||||
protected RepositoryService spawnRepositoryService() {
|
||||
var service = instantiateTestService(List.of(repositoryClass, TagRepositoryImpl.class));
|
||||
for (var i = 0; i < 5; i++) {
|
||||
var tagRepository = service.getRepositoryForEntity(Tag.class);
|
||||
var tag = tagRepository.create();
|
||||
tag.setName("tag" + i);
|
||||
tagRepository.save(tag);
|
||||
}
|
||||
return service;
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void getChildrenOf() throws IOException {
|
||||
try (var service = spawnRepositoryService()) {
|
||||
var repository = service.getRepository(GroupRepository.class);
|
||||
assertThat(repository.getRoot()).isNull();
|
||||
var root = repository.create();
|
||||
repository.save(root);
|
||||
|
||||
var anotherParent = repository.create();
|
||||
anotherParent.setParent(root);
|
||||
repository.save(anotherParent);
|
||||
|
||||
var children = new ArrayList<Group>();
|
||||
for (var i = 0; i < 20; i++) {
|
||||
var child = repository.create();
|
||||
child.setParent(anotherParent);
|
||||
children.add(child);
|
||||
repository.save(child);
|
||||
}
|
||||
|
||||
try (var handler = repository.getChildrenOf(root)) {
|
||||
assertThat(handler.get().toList()).containsExactly(anotherParent);
|
||||
}
|
||||
|
||||
try (var handler = repository.getChildrenOf(anotherParent)) {
|
||||
assertThat(handler.get().toList()).containsExactlyElementsOf(children);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void getAllChildrenInHierarchy() throws IOException {
|
||||
try (var service = spawnRepositoryService()) {
|
||||
var repository = service.getRepository(GroupRepository.class);
|
||||
assertThat(repository.getRoot()).isNull();
|
||||
var root = repository.create();
|
||||
repository.save(root);
|
||||
|
||||
var anotherParent = repository.create();
|
||||
anotherParent.setParent(root);
|
||||
repository.save(anotherParent);
|
||||
|
||||
var children = new ArrayList<Group>();
|
||||
for (var i = 0; i < 10; i++) {
|
||||
var child = repository.create();
|
||||
child.setParent(anotherParent);
|
||||
children.add(child);
|
||||
repository.save(child);
|
||||
|
||||
var subchild = repository.create();
|
||||
subchild.setParent(child);
|
||||
repository.save(subchild);
|
||||
children.add(subchild);
|
||||
}
|
||||
|
||||
try (var handler = repository.getAllChildrenInHierarchy(anotherParent)) {
|
||||
assertThat(handler.get().toList()).containsExactlyInAnyOrderElementsOf(children);
|
||||
}
|
||||
|
||||
try (var handler = repository.getAllChildrenInHierarchy(root)) {
|
||||
children.add(anotherParent);
|
||||
assertThat(handler.get().toList()).containsExactlyInAnyOrderElementsOf(children);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void getRoot() throws IOException {
|
||||
try (var service = spawnRepositoryService()) {
|
||||
var repository = service.getRepository(GroupRepository.class);
|
||||
assertThat(repository.getRoot()).isNull();
|
||||
var root = repository.create();
|
||||
repository.save(root);
|
||||
var notARoot = repository.create();
|
||||
notARoot.setParent(root);
|
||||
repository.save(notARoot);
|
||||
assertThat(repository.getRoot()).isNotNull().isEqualTo(root);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("CatchMayIgnoreException")
|
||||
@Test
|
||||
void testManyRoots() throws IOException {
|
||||
try (var service = spawnRepositoryService()) {
|
||||
var repository = service.getRepository(GroupRepository.class);
|
||||
var root = repository.create();
|
||||
var secondaryRoot = repository.create();
|
||||
|
||||
try {
|
||||
repository.save(List.of(root, secondaryRoot));
|
||||
throw new Exception("Nothing is thrown");
|
||||
} catch (Throwable e) {
|
||||
assertThat(e).isInstanceOf(IllegalStateException.class);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void modify(Group entity, RepositoryService service) {
|
||||
entity.setName(UUID.randomUUID().toString());
|
||||
var groupRepository = service.getRepository(GroupRepository.class);
|
||||
|
||||
var root = groupRepository.getRoot();
|
||||
|
||||
if (root != null && !root.equals(entity)) {
|
||||
entity.setParent(root);
|
||||
}
|
||||
|
||||
try (var handler = service.getRepositoryForEntity(Tag.class).getAll()) {
|
||||
entity.setTags(new TagCollectionImpl(handler.get().toList()));
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
package ru.kirillius.XCP.Persistence.Repositories;
|
||||
|
||||
import ru.kirillius.XCP.Persistence.Entities.Input;
|
||||
import ru.kirillius.XCP.Persistence.Entities.Tag;
|
||||
import ru.kirillius.XCP.Services.RepositoryService;
|
||||
import ru.kirillius.XCP.Persistence.TagCollectionImpl;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import static ru.kirillius.XCP.Persistence.TestEnvironment.instantiateTestService;
|
||||
|
||||
class InputRepositoryImplTest extends GenericNodeRepositoryTest<Input, InputRepositoryImpl> {
|
||||
@Override
|
||||
protected RepositoryService spawnRepositoryService() {
|
||||
var service = instantiateTestService(List.of(repositoryClass, TagRepositoryImpl.class, GroupRepositoryImpl.class));
|
||||
for (var i = 0; i < 5; i++) {
|
||||
var tagRepository = service.getRepositoryForEntity(Tag.class);
|
||||
var tag = tagRepository.create();
|
||||
tag.setName("tag" + i);
|
||||
tagRepository.save(tag);
|
||||
}
|
||||
return service;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void modify(Input entity, RepositoryService service) {
|
||||
var groupRepository = service.getRepository(GroupRepository.class);
|
||||
|
||||
var root = groupRepository.getRoot();
|
||||
if (root == null) {
|
||||
root = groupRepository.create();
|
||||
groupRepository.save(root);
|
||||
}
|
||||
|
||||
entity.setParent(root);
|
||||
entity.setName(UUID.randomUUID().toString());
|
||||
//var inputRepository = service.getRepository(InputRepository.class);
|
||||
|
||||
try (var handler = service.getRepositoryForEntity(Tag.class).getAll()) {
|
||||
entity.setTags(new TagCollectionImpl(handler.get().toList()));
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
package ru.kirillius.XCP.Persistence.Repositories;
|
||||
|
||||
import ru.kirillius.XCP.Persistence.Entities.Output;
|
||||
import ru.kirillius.XCP.Persistence.Entities.Tag;
|
||||
import ru.kirillius.XCP.Services.RepositoryService;
|
||||
import ru.kirillius.XCP.Persistence.TagCollectionImpl;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import static ru.kirillius.XCP.Persistence.TestEnvironment.instantiateTestService;
|
||||
|
||||
class OutputRepositoryImplTest extends GenericNodeRepositoryTest<Output, OutputRepositoryImpl> {
|
||||
@Override
|
||||
protected RepositoryService spawnRepositoryService() {
|
||||
var service = instantiateTestService(List.of(repositoryClass, TagRepositoryImpl.class, GroupRepositoryImpl.class));
|
||||
for (var i = 0; i < 5; i++) {
|
||||
var tagRepository = service.getRepositoryForEntity(Tag.class);
|
||||
var tag = tagRepository.create();
|
||||
tag.setName("tag" + i);
|
||||
tagRepository.save(tag);
|
||||
}
|
||||
return service;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void modify(Output entity, RepositoryService service) {
|
||||
var groupRepository = service.getRepository(GroupRepository.class);
|
||||
|
||||
var root = groupRepository.getRoot();
|
||||
if (root == null) {
|
||||
root = groupRepository.create();
|
||||
groupRepository.save(root);
|
||||
}
|
||||
|
||||
entity.setParent(root);
|
||||
entity.setName(UUID.randomUUID().toString());
|
||||
//var inputRepository = service.getRepository(InputRepository.class);
|
||||
|
||||
try (var handler = service.getRepositoryForEntity(Tag.class).getAll()) {
|
||||
entity.setTags(new TagCollectionImpl(handler.get().toList()));
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
package ru.kirillius.XCP.Persistence.Repositories;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import ru.kirillius.XCP.Persistence.Entities.Tag;
|
||||
import ru.kirillius.XCP.Services.RepositoryService;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class TagRepositoryImplTest extends GenericRepositoryTest<Tag, TagRepositoryImpl> {
|
||||
@Test
|
||||
void testGetByNameOrCreate() throws IOException {
|
||||
try (var service = spawnRepositoryService()) {
|
||||
var repository = service.getRepository(TagRepository.class);
|
||||
var tag = repository.create();
|
||||
tag.setName("test");
|
||||
repository.save(tag);
|
||||
var found = repository.getByNameOrCreate(tag.getName());
|
||||
assertThat(found).isEqualTo(tag);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetByNamesOrCreateAsCollectionOrCreate() throws IOException {
|
||||
try (var service = spawnRepositoryService()) {
|
||||
var repository = service.getRepository(TagRepository.class);
|
||||
|
||||
var tags = new ArrayList<Tag>();
|
||||
|
||||
for (var i = 0; i < 10; i++) {
|
||||
var tag = repository.create();
|
||||
tag.setName("test" + i);
|
||||
repository.save(tag);
|
||||
tags.add(tag);
|
||||
}
|
||||
|
||||
var tagNames = tags.stream().map(Tag::getName).toList();
|
||||
|
||||
assertThat(repository.getByNamesOrCreate(tagNames)).containsExactlyInAnyOrderElementsOf(tags);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateCollection() throws IOException {
|
||||
try (var service = spawnRepositoryService()) {
|
||||
var repository = service.getRepository(TagRepository.class);
|
||||
|
||||
var tags = new ArrayList<Tag>();
|
||||
|
||||
for (var i = 0; i < 10; i++) {
|
||||
var tag = repository.create();
|
||||
tag.setName("test" + i);
|
||||
repository.save(tag);
|
||||
tags.add(tag);
|
||||
}
|
||||
|
||||
var emptyCollection = repository.createCollection();
|
||||
assertThat(emptyCollection).isNotNull().isEmpty();
|
||||
|
||||
try (var handler = repository.getAll()) {
|
||||
var collection = repository.createCollection(handler);
|
||||
assertThat(collection).containsExactlyInAnyOrderElementsOf(tags);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void modify(Tag entity, RepositoryService service) {
|
||||
entity.setName("test" + Math.random());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
package ru.kirillius.XCP.Persistence.Repositories;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import ru.kirillius.XCP.Persistence.Entities.User;
|
||||
import ru.kirillius.XCP.Persistence.PersistenceException;
|
||||
import ru.kirillius.XCP.Services.RepositoryService;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class UserRepositoryImplTest extends GenericRepositoryTest<User, UserRepositoryImpl> {
|
||||
|
||||
@Test
|
||||
void saveSameLogin() throws IOException {
|
||||
|
||||
try (var service = spawnRepositoryService()) {
|
||||
var repository = service.getRepository(UserRepository.class);
|
||||
|
||||
var user = repository.create();
|
||||
user.setLogin("test");
|
||||
repository.save(user);
|
||||
|
||||
user = repository.create();
|
||||
user.setLogin("test");
|
||||
try {
|
||||
repository.save(user);
|
||||
throw new AssertionError("Should have thrown an exception");
|
||||
} catch (Throwable t) {
|
||||
assertThat(t).isInstanceOf(PersistenceException.class);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void getByLogin() throws IOException {
|
||||
var correct = "correctlogin";
|
||||
try (var service = spawnRepositoryService()) {
|
||||
var repository = service.getRepository(UserRepository.class);
|
||||
for (var i = 0; i < 10; i++) {
|
||||
var user = repository.create();
|
||||
user.setLogin("incorrect" + UUID.randomUUID());
|
||||
repository.save(user);
|
||||
}
|
||||
var correctUser = repository.create();
|
||||
correctUser.setLogin(correct);
|
||||
repository.save(correctUser);
|
||||
for (var i = 0; i < 10; i++) {
|
||||
var user = repository.create();
|
||||
user.setLogin("incorrect" + UUID.randomUUID());
|
||||
repository.save(user);
|
||||
}
|
||||
|
||||
var loaded = repository.getByLogin(correct);
|
||||
assertThat(loaded).isNotNull().isEqualTo(correctUser);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPasswords() throws IOException {
|
||||
try (var service = spawnRepositoryService()) {
|
||||
var repository = service.getRepository(UserRepository.class);
|
||||
var user = repository.create();
|
||||
var randPass = UUID.randomUUID().toString();
|
||||
user.setPassword(randPass);
|
||||
|
||||
assertThat(user.verifyPassword(randPass)).isTrue();
|
||||
assertThat(((UserRepositoryImpl.UserEntity) user).getPasswordHash()).doesNotContain(randPass);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void modify(User entity, RepositoryService service) {
|
||||
entity.setName("test" + UUID.randomUUID());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,190 @@
|
|||
package ru.kirillius.XCP.Persistence;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import jakarta.persistence.*;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.hibernate.annotations.UuidGenerator;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static ru.kirillius.XCP.Persistence.TestEnvironment.instantiateTestService;
|
||||
|
||||
class RepositoryServiceImplTest {
|
||||
|
||||
public interface TestEntity extends PersistenceEntity {
|
||||
String getTestField();
|
||||
|
||||
void setTestField(String data);
|
||||
}
|
||||
|
||||
public interface TestRepository extends Repository<TestEntity> {
|
||||
}
|
||||
|
||||
@EntityImplementation(RepoImpl.EntityImpl.class)
|
||||
public static class RepoImpl extends AbstractRepository<TestEntity> implements TestRepository {
|
||||
|
||||
public RepoImpl(RepositoryServiceImpl repositoryService) {
|
||||
super(repositoryService);
|
||||
}
|
||||
|
||||
@Entity
|
||||
@Table
|
||||
public static class EntityImpl implements TestEntity {
|
||||
@Getter
|
||||
@Setter
|
||||
@JsonProperty
|
||||
private String testField = "empty";
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
@JsonProperty
|
||||
@Getter
|
||||
@Setter
|
||||
private long id = 0;
|
||||
|
||||
@JsonProperty
|
||||
@Column(unique = true, nullable = false)
|
||||
@UuidGenerator
|
||||
@Getter
|
||||
@Setter
|
||||
private UUID uuid;
|
||||
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (!(o instanceof EntityImpl entity)) return false;
|
||||
return id == entity.id && Objects.equals(testField, entity.testField) && Objects.equals(uuid, entity.uuid);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(testField, id, uuid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void TestERepositoryInstantiate() {
|
||||
try (var service = instantiateTestService(List.of(RepoImpl.class))) {
|
||||
var repository = service.getRepository(TestRepository.class);
|
||||
assertThat(repository).isNotNull().isInstanceOf(TestRepository.class);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void TestEntityCreate() {
|
||||
try (var service = instantiateTestService(List.of(RepoImpl.class))) {
|
||||
var repository = service.getRepository(TestRepository.class);
|
||||
var testEntity = repository.create();
|
||||
assertThat(testEntity).isNotNull().isInstanceOf(TestEntity.class);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void TestSerialization() {
|
||||
try (var service = instantiateTestService(List.of(RepoImpl.class))) {
|
||||
var repository = service.getRepository(TestRepository.class);
|
||||
var testEntity = repository.create();
|
||||
repository.save(testEntity);
|
||||
var serialized = repository.serialize(testEntity);
|
||||
var deserialized = repository.deserialize(serialized);
|
||||
assertThat(deserialized).isEqualTo(testEntity);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void TestEntitySave() {
|
||||
try (var service = instantiateTestService(List.of(RepoImpl.class))) {
|
||||
var repository = service.getRepository(TestRepository.class);
|
||||
var testEntity = repository.create();
|
||||
testEntity.setTestField("new");
|
||||
repository.save(List.of(testEntity));
|
||||
assertThat(testEntity.getId()).isNotZero();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void TestEntityGet() {
|
||||
try (var service = instantiateTestService(List.of(RepoImpl.class))) {
|
||||
var repository = service.getRepository(TestRepository.class);
|
||||
|
||||
assertThat(repository.get(1)).isNull();
|
||||
|
||||
var testEntity = repository.create();
|
||||
testEntity.setTestField("new");
|
||||
repository.save(List.of(testEntity));
|
||||
|
||||
var receivedEntity = repository.get(testEntity.getId());
|
||||
assertThat(receivedEntity).isNotNull().isEqualTo(testEntity);
|
||||
assertThat(testEntity == receivedEntity).isFalse();//not exact instance
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void TestEntityGetMultiple() throws IOException {
|
||||
try (var service = instantiateTestService(List.of(RepoImpl.class))) {
|
||||
var repository = service.getRepository(TestRepository.class);
|
||||
try (var bundle = repository.getAll()) {
|
||||
assertThat(bundle.get().toList()).isEmpty();
|
||||
}
|
||||
|
||||
var entitiesToSave = new ArrayList<TestEntity>();
|
||||
|
||||
for (var i = 0; i < 10; i++) {
|
||||
var entity = repository.create();
|
||||
entity.setTestField("instance " + i);
|
||||
entitiesToSave.add(entity);
|
||||
}
|
||||
|
||||
repository.save(entitiesToSave);
|
||||
|
||||
try (var bundle = repository.get(entitiesToSave.stream().map(PersistenceEntity::getId).toList())) {
|
||||
var loaded = bundle.get().toList();
|
||||
assertThat(loaded).containsExactlyInAnyOrderElementsOf(entitiesToSave);
|
||||
}
|
||||
|
||||
try (var bundle = repository.getAll()) {
|
||||
var loaded = bundle.get().toList();
|
||||
assertThat(loaded).containsExactlyInAnyOrderElementsOf(entitiesToSave);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void TestEntityUpdate() {
|
||||
try (var service = instantiateTestService(List.of(RepoImpl.class))) {
|
||||
var repository = service.getRepository(TestRepository.class);
|
||||
var testEntity = repository.create();
|
||||
testEntity.setTestField("new");
|
||||
repository.save(List.of(testEntity));
|
||||
|
||||
testEntity.setTestField("updated");
|
||||
repository.save(testEntity);
|
||||
|
||||
var receivedEntity = repository.get(testEntity.getId());
|
||||
assertThat(receivedEntity).isNotNull().isEqualTo(testEntity);
|
||||
assertThat(testEntity == receivedEntity).isFalse();//not exact instance
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void TestEntityRemove() {
|
||||
try (var service = instantiateTestService(List.of(RepoImpl.class))) {
|
||||
var repository = service.getRepository(TestRepository.class);
|
||||
var testEntity = repository.create();
|
||||
repository.save(List.of(testEntity));
|
||||
assertThat(repository.getCount()).isEqualTo(1);
|
||||
repository.remove(List.of(testEntity));
|
||||
assertThat(repository.get(testEntity.getId())).isNull();
|
||||
assertThat(repository.getCount()).isZero();
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
package ru.kirillius.XCP.Persistence;
|
||||
|
||||
import ru.kirillius.XCP.Commons.Context;
|
||||
import ru.kirillius.XCP.Security.SecurityManager;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class TestEnvironment {
|
||||
public static RepositoryServiceImpl instantiateTestService(Collection<Class<? extends AbstractRepository<?>>> classes) {
|
||||
var service = new RepositoryServiceImpl(new H2InMemoryConfiguration(), classes);
|
||||
var context = mock(Context.class);
|
||||
var securityManager = mock(SecurityManager.class);
|
||||
|
||||
when(context.getSecurityManager()).thenReturn(securityManager);
|
||||
when(securityManager.getHashUtility()).thenReturn(new TestHashUtil());
|
||||
|
||||
service.initialize(context);
|
||||
return service;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package ru.kirillius.XCP.Persistence;
|
||||
|
||||
import ru.kirillius.XCP.Security.HashUtility;
|
||||
|
||||
public class TestHashUtil implements HashUtility {
|
||||
|
||||
@Override
|
||||
public String hash(String password) {
|
||||
return "hashed(" + password.hashCode() + ")";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean validate(String password, String hash) {
|
||||
return HashUtility.super.validate(password, hash);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
<?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>logging</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>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
<version>2.0.9</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-jdk14</artifactId>
|
||||
<version>2.0.9</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
|
|
@ -0,0 +1,97 @@
|
|||
package ru.kirillius.XCP.Logging;
|
||||
|
||||
import ru.kirillius.XCP.Commons.Context;
|
||||
import ru.kirillius.java.utils.events.EventHandler;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
import java.util.logging.Handler;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.LogRecord;
|
||||
|
||||
public class LogHandlerImpl extends Handler {
|
||||
private final EventHandler<LogMessage> eventHandler;
|
||||
private final boolean debugging;
|
||||
|
||||
public LogHandlerImpl(LoggingSystem loggingSystem, Context context) {
|
||||
eventHandler = loggingSystem.getEventHandler();
|
||||
debugging = context.getLaunchArgs().contains("--debug");
|
||||
}
|
||||
|
||||
private final static SimpleDateFormat dateFormat = new SimpleDateFormat("dd.MM.yyyy HH:mm:ss", Locale.US);
|
||||
|
||||
private String format(LogRecord logRecord) {
|
||||
var date = new Date(logRecord.getMillis());
|
||||
var builder = new StringBuilder();
|
||||
builder.append("[");
|
||||
builder.append(dateFormat.format(date));
|
||||
builder.append("][");
|
||||
builder.append(convertLevel(logRecord.getLevel()));
|
||||
builder.append("] ");
|
||||
builder.append(logRecord.getMessage().trim());
|
||||
var thrown = logRecord.getThrown();
|
||||
if (thrown != null) {
|
||||
builder.append("\n\tThrown ").append(thrown.getClass().getSimpleName()).append(": ").append(thrown.getMessage());
|
||||
if (debugging) {
|
||||
builder.append("\nStack trace:\n");
|
||||
|
||||
try (var writer = new StringWriter()) {
|
||||
try (var printWriter = new PrintWriter(writer)) {
|
||||
thrown.printStackTrace(printWriter);
|
||||
builder.append(writer);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
} else {
|
||||
var cause = thrown.getCause();
|
||||
while (cause != null) {
|
||||
builder.append("\n\t\tCaused by ").append(cause.getClass().getSimpleName()).append(": ").append(cause.getMessage());
|
||||
cause = cause.getCause();
|
||||
}
|
||||
builder.append("\n\t(Stack trace is hidden due to --debug flag)");
|
||||
}
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void publish(LogRecord logRecord) {
|
||||
//TODO сделать асинхронным чтобы не лочить треды
|
||||
print(logRecord);
|
||||
var message = LogMessage.builder().message(logRecord.getMessage()).level(convertLevel(logRecord.getLevel())).date(logRecord.getInstant()).build();
|
||||
try {
|
||||
eventHandler.invoke(message);
|
||||
} catch (Exception e) {
|
||||
print(new LogRecord(Level.SEVERE, "Unhandled error in logger event listener: " + e.getClass().getSimpleName() + ": " + e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
private void print(LogRecord logRecord) {
|
||||
System.out.println(format(logRecord));
|
||||
}
|
||||
|
||||
public LogLevel convertLevel(Level level) {
|
||||
if (level == Level.SEVERE) {
|
||||
return LogLevel.ERROR;
|
||||
}
|
||||
if (level == Level.WARNING) {
|
||||
return LogLevel.WARNING;
|
||||
}
|
||||
return LogLevel.INFO;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flush() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws SecurityException {
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
package ru.kirillius.XCP.Logging;
|
||||
|
||||
import java.util.logging.Level;
|
||||
|
||||
public class LoggerImpl extends java.util.logging.Logger implements Logger {
|
||||
private final static String SEPARATOR = ": ";
|
||||
|
||||
public LoggerImpl(String name) {
|
||||
super(name, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void info(String message) {
|
||||
super.info(getName() + SEPARATOR + message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void warning(String message) {
|
||||
super.warning(getName() + SEPARATOR + message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void error(String message) {
|
||||
severe(getName() + SEPARATOR + message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void error(String message, Throwable error) {
|
||||
log(Level.SEVERE, getName() + SEPARATOR + message, error);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void error(Throwable error) {
|
||||
log(Level.SEVERE, getName() + SEPARATOR + "Thrown unhandled exception", error);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
package ru.kirillius.XCP.Logging;
|
||||
|
||||
import lombok.Getter;
|
||||
import ru.kirillius.XCP.Commons.Context;
|
||||
import ru.kirillius.java.utils.events.ConcurrentEventHandler;
|
||||
import ru.kirillius.java.utils.events.EventHandler;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.logging.Handler;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.LogManager;
|
||||
|
||||
public class LoggingSystemImpl implements LoggingSystem {
|
||||
private final java.util.logging.Logger rootLogger;
|
||||
private final Handler handler;
|
||||
|
||||
@Override
|
||||
public Logger createLogger(Class<?> cls) {
|
||||
var logger = new LoggerImpl(cls.getSimpleName());
|
||||
LogManager.getLogManager().addLogger(logger);
|
||||
return logger;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Logger createLogger(String name) {
|
||||
var logger = new LoggerImpl(name);
|
||||
LogManager.getLogManager().addLogger(logger);
|
||||
return logger;
|
||||
}
|
||||
|
||||
public LoggingSystemImpl(Context context) {
|
||||
eventHandler = new ConcurrentEventHandler<>();
|
||||
var logManager = LogManager.getLogManager();
|
||||
rootLogger = logManager.getLogger("");
|
||||
rootLogger.setLevel(Level.INFO);
|
||||
handler = new LogHandlerImpl(this, context);
|
||||
Arrays.stream(rootLogger.getHandlers()).forEach(rootLogger::removeHandler);
|
||||
rootLogger.addHandler(handler);
|
||||
}
|
||||
|
||||
@Getter
|
||||
private final EventHandler<LogMessage> eventHandler;
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
if (handler != null) {
|
||||
rootLogger.removeHandler(handler);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,135 @@
|
|||
<?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>
|
||||
|
||||
<groupId>ru.kirillius</groupId>
|
||||
<artifactId>XCP</artifactId>
|
||||
<version>1.0.0.0</version>
|
||||
<packaging>pom</packaging>
|
||||
<modules>
|
||||
<module>api</module>
|
||||
<module>api-spec-generator</module>
|
||||
<module>database</module>
|
||||
<module>core</module>
|
||||
<module>rpc</module>
|
||||
<module>web-server</module>
|
||||
<module>app</module>
|
||||
<module>logging</module>
|
||||
<module>web-ui</module>
|
||||
</modules>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>21</maven.compiler.source>
|
||||
<maven.compiler.target>21</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<lombok.version>1.18.40</lombok.version>
|
||||
</properties>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.8.1</version>
|
||||
<configuration>
|
||||
<source>${maven.compiler.source}</source>
|
||||
<target>${maven.compiler.target}</target>
|
||||
<annotationProcessorPaths>
|
||||
<path>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>${lombok.version}</version>
|
||||
</path>
|
||||
</annotationProcessorPaths>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>kirillius</id>
|
||||
<name>kirillius</name>
|
||||
<url>https://repo.kirillius.ru/maven</url>
|
||||
<releases>
|
||||
<enabled>true</enabled>
|
||||
<updatePolicy>always</updatePolicy>
|
||||
<checksumPolicy>fail</checksumPolicy>
|
||||
</releases>
|
||||
<layout>default</layout>
|
||||
</repository>
|
||||
</repositories>
|
||||
<dependencies>
|
||||
<!-- <dependency>-->
|
||||
<!-- <groupId>ru.kirillius</groupId>-->
|
||||
<!-- <artifactId>json-convert</artifactId>-->
|
||||
<!-- <version>2.2.0.0</version>-->
|
||||
<!-- </dependency>-->
|
||||
|
||||
<!-- https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-api -->
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-api</artifactId>
|
||||
<version>5.13.0-M2</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- https://mvnrepository.com/artifact/org.mockito/mockito-core -->
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-core</artifactId>
|
||||
<version>5.21.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- https://mvnrepository.com/artifact/de.mkammerer/argon2-jvm -->
|
||||
<dependency>
|
||||
<groupId>de.mkammerer</groupId>
|
||||
<artifactId>argon2-jvm</artifactId>
|
||||
<version>2.12</version>
|
||||
</dependency>
|
||||
|
||||
<!-- https://mvnrepository.com/artifact/org.assertj/assertj-core -->
|
||||
<dependency>
|
||||
<groupId>org.assertj</groupId>
|
||||
<artifactId>assertj-core</artifactId>
|
||||
<version>4.0.0-M1</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.javatuples</groupId>
|
||||
<artifactId>javatuples</artifactId>
|
||||
<version>1.2</version>
|
||||
</dependency>
|
||||
|
||||
|
||||
<dependency>
|
||||
<groupId>ru.kirillius.utils</groupId>
|
||||
<artifactId>common-logging</artifactId>
|
||||
<version>1.3.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>tools.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
<version>3.0.3</version>
|
||||
</dependency>
|
||||
|
||||
|
||||
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>${lombok.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
|
||||
</project>
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
<?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>rpc</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>
|
||||
|
||||
<!-- Не используем т.к. есть уязвимости и не совместимо с текущей версией JSON-->
|
||||
<!-- <!– https://mvnrepository.com/artifact/com.github.briandilley.jsonrpc4j/jsonrpc4j –>-->
|
||||
<!-- <dependency>-->
|
||||
<!-- <groupId>com.github.briandilley.jsonrpc4j</groupId>-->
|
||||
<!-- <artifactId>jsonrpc4j</artifactId>-->
|
||||
<!-- <version>1.7</version>-->
|
||||
<!-- </dependency>-->
|
||||
|
||||
|
||||
|
||||
<!-- https://mvnrepository.com/artifact/org.eclipse.jetty/jetty-server -->
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-server</artifactId>
|
||||
<version>12.1.5</version>
|
||||
</dependency>
|
||||
|
||||
<!-- https://mvnrepository.com/artifact/org.eclipse.jetty.ee10/jetty-ee10-servlet -->
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.ee10</groupId>
|
||||
<artifactId>jetty-ee10-servlet</artifactId>
|
||||
<version>12.1.5</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.ee10</groupId>
|
||||
<artifactId>jetty-ee10-webapp</artifactId>
|
||||
<version>12.1.5</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>ru.kirillius</groupId>
|
||||
<artifactId>api</artifactId>
|
||||
<version>1.0.0.0</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
package ru.kirillius.XCP.RPC.JSONRPC;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import ru.kirillius.XCP.Commons.Context;
|
||||
import ru.kirillius.XCP.Persistence.Entities.User;
|
||||
import tools.jackson.databind.node.ObjectNode;
|
||||
|
||||
public interface CallContext {
|
||||
Context getContext();
|
||||
|
||||
HttpServletRequest getRequest();
|
||||
|
||||
HttpServletResponse getResponse();
|
||||
|
||||
ObjectNode getParams();
|
||||
|
||||
User getCurrentUser();
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
package ru.kirillius.XCP.RPC.JSONRPC;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
public class JsonRpcError {
|
||||
public JsonRpcError(JsonRpcErrorCode code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public JsonRpcError(JsonRpcErrorCode code, String message) {
|
||||
this.code = code;
|
||||
this.messageInternal = message;
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
|
||||
private final JsonRpcErrorCode code;
|
||||
|
||||
@JsonProperty(value = "error")
|
||||
public int getError() {
|
||||
return code.getCode();
|
||||
}
|
||||
|
||||
@JsonProperty(value = "message")
|
||||
public String getMessage() {
|
||||
return messageInternal == null ? code.getMessage() : messageInternal;
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
private String messageInternal;
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue