Добавил ApiToken entity для авторизации, контекст для RPC, реализовал ConfigManager, логгирование и RPC
This commit is contained in:
parent
0d202b5574
commit
898a170d7b
|
|
@ -17,11 +17,5 @@
|
||||||
<artifactId>java-events</artifactId>
|
<artifactId>java-events</artifactId>
|
||||||
<version>1.1.0.0</version>
|
<version>1.1.0.0</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<!-- https://mvnrepository.com/artifact/tools.jackson.core/jackson-databind -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>tools.jackson.core</groupId>
|
|
||||||
<artifactId>jackson-databind</artifactId>
|
|
||||||
<version>3.0.3</version>
|
|
||||||
</dependency>
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</project>
|
</project>
|
||||||
|
|
@ -2,7 +2,8 @@ package ru.kirillius.XCP.Commons;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
|
||||||
public interface Config {
|
public interface
|
||||||
|
Config {
|
||||||
|
|
||||||
File getLoadedConfigFile();
|
File getLoadedConfigFile();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,14 @@
|
||||||
package ru.kirillius.XCP.Commons;
|
package ru.kirillius.XCP.Commons;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
public interface ConfigManager {
|
public interface ConfigManager {
|
||||||
|
File getConfigFile();
|
||||||
|
|
||||||
boolean isExist();
|
boolean isExist();
|
||||||
|
|
||||||
Config load();
|
Config load() throws IOException;
|
||||||
|
|
||||||
Config create();
|
Config create();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,10 @@
|
||||||
package ru.kirillius.XCP.Commons;
|
package ru.kirillius.XCP.Commons;
|
||||||
|
|
||||||
|
import ru.kirillius.XCP.Logging.LoggingSystem;
|
||||||
import ru.kirillius.XCP.Security.SecurityManager;
|
import ru.kirillius.XCP.Security.SecurityManager;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
public interface Context {
|
public interface Context {
|
||||||
Config getConfig();
|
Config getConfig();
|
||||||
|
|
||||||
|
|
@ -12,4 +15,8 @@ public interface Context {
|
||||||
void shutdown();
|
void shutdown();
|
||||||
|
|
||||||
SecurityManager getSecurityManager();
|
SecurityManager getSecurityManager();
|
||||||
|
|
||||||
|
LoggingSystem getLoggingSystem();
|
||||||
|
|
||||||
|
List<String> getLaunchArgs();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,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);
|
||||||
|
}
|
||||||
|
|
@ -11,23 +11,23 @@ import java.util.UUID;
|
||||||
public interface Repository<E extends PersistenceEntity> {
|
public interface Repository<E extends PersistenceEntity> {
|
||||||
E create();
|
E create();
|
||||||
|
|
||||||
E load(long id);
|
E get(long id);
|
||||||
|
|
||||||
E load(UUID uuid);
|
E get(UUID uuid);
|
||||||
|
|
||||||
StreamHandler<E> load(Collection<Long> ids);
|
StreamHandler<E> get(Collection<Long> ids);
|
||||||
|
|
||||||
StreamHandler<E> search(String query, Collection<Object> queryParameters);
|
StreamHandler<E> search(String query, Collection<Object> queryParameters);
|
||||||
|
|
||||||
EventBindings<E> events();
|
EventBindings<E> events();
|
||||||
|
|
||||||
StreamHandler<E> loadAll();
|
StreamHandler<E> getAll();
|
||||||
|
|
||||||
long getCount();
|
long getCount();
|
||||||
|
|
||||||
void store(E entity);
|
void save(E entity);
|
||||||
|
|
||||||
void store(Collection<E> entities);
|
void save(Collection<E> entities);
|
||||||
|
|
||||||
void remove(E entity);
|
void remove(E entity);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
package ru.kirillius.XCP.Persistence;
|
package ru.kirillius.XCP.Services;
|
||||||
|
|
||||||
import ru.kirillius.XCP.Commons.Service;
|
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 {
|
public interface RepositoryService extends Service {
|
||||||
<E extends PersistenceEntity> Repository<E> getRepositoryForEntity(Class<E> entityType);
|
<E extends PersistenceEntity> Repository<E> getRepositoryForEntity(Class<E> entityType);
|
||||||
|
|
@ -8,8 +11,11 @@ public interface RepositoryService extends Service {
|
||||||
<R extends Repository<?>> R getRepository(Class<R> repositoryType);
|
<R extends Repository<?>> R getRepository(Class<R> repositoryType);
|
||||||
|
|
||||||
Class<? extends PersistenceEntity> getEntityBaseType(Class<? extends PersistenceEntity> entityClass);
|
Class<? extends PersistenceEntity> getEntityBaseType(Class<? extends PersistenceEntity> entityClass);
|
||||||
|
|
||||||
Class<? extends Repository<?>> getRepositoryBaseType(Class<? extends Repository<?>> repositoryClass);
|
Class<? extends Repository<?>> getRepositoryBaseType(Class<? extends Repository<?>> repositoryClass);
|
||||||
|
|
||||||
Class<? extends PersistenceEntity> getRepositoryEntityType(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,142 @@
|
||||||
|
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.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<>();
|
||||||
|
|
||||||
|
public Application(String[] args) {
|
||||||
|
launchArgs = Arrays.stream(args).toList();
|
||||||
|
loggingSystem = new LoggingSystemImpl(this);
|
||||||
|
log = loggingSystem.createLogger(Application.class);
|
||||||
|
configManager = new ConfigManagerImpl(this);
|
||||||
|
if (configManager.isExist()) {
|
||||||
|
try {
|
||||||
|
config = configManager.load();
|
||||||
|
log.info("Loaded config file: " + configManager.getConfigFile().getAbsolutePath());
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.error("Error loading config file " + configManager.getConfigFile(), e);
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.warning("Unable to find config file " + configManager.getConfigFile().getAbsolutePath() + ". Using default values.");
|
||||||
|
config = configManager.create();
|
||||||
|
}
|
||||||
|
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 -> {
|
||||||
|
log.info("Loading service " + aClass.getSimpleName());
|
||||||
|
try {
|
||||||
|
var constructor = aClass.getConstructor();
|
||||||
|
try {
|
||||||
|
var service = constructor.newInstance();
|
||||||
|
try {
|
||||||
|
service.initialize(this);
|
||||||
|
@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()));
|
||||||
|
services.put(facade, service);
|
||||||
|
} catch (Throwable e) {
|
||||||
|
try {
|
||||||
|
service.close();
|
||||||
|
} catch (IOException ex) {
|
||||||
|
e.addSuppressed(ex);
|
||||||
|
}
|
||||||
|
throw new RuntimeException("Failed to start service " + aClass.getSimpleName(), e);
|
||||||
|
}
|
||||||
|
} catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
|
||||||
|
throw new RuntimeException("Failed to instantiate service " + aClass.getSimpleName(), e);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (NoSuchMethodException e) {
|
||||||
|
throw new RuntimeException("Failed to find default constructor of service " + aClass.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,80 @@
|
||||||
|
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.*;
|
||||||
|
|
||||||
|
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().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.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_CONFIG_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();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -30,11 +30,7 @@
|
||||||
<artifactId>hibernate-c3p0</artifactId>
|
<artifactId>hibernate-c3p0</artifactId>
|
||||||
<version>7.1.10.Final</version>
|
<version>7.1.10.Final</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
|
||||||
<groupId>tools.jackson.core</groupId>
|
|
||||||
<artifactId>jackson-databind</artifactId>
|
|
||||||
<version>3.0.3</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>ru.kirillius</groupId>
|
<groupId>ru.kirillius</groupId>
|
||||||
<artifactId>api</artifactId>
|
<artifactId>api</artifactId>
|
||||||
|
|
|
||||||
|
|
@ -37,14 +37,14 @@ public abstract class AbstractRepository<E extends PersistenceEntity> implements
|
||||||
public E create() {
|
public E create() {
|
||||||
try {
|
try {
|
||||||
var constructor = entityImplementationClass.getConstructor();
|
var constructor = entityImplementationClass.getConstructor();
|
||||||
var instance= constructor.newInstance();
|
var instance = constructor.newInstance();
|
||||||
if(instance instanceof ContextReferencedEntity referencedEntity) {
|
if (instance instanceof ContextReferencedEntity referencedEntity) {
|
||||||
referencedEntity.setContext(repositoryService.getContext());
|
referencedEntity.setContext(repositoryService.getContext());
|
||||||
}
|
}
|
||||||
return instance;
|
return instance;
|
||||||
} catch (NoSuchMethodException | InvocationTargetException | InstantiationException |
|
} catch (NoSuchMethodException | InvocationTargetException | InstantiationException |
|
||||||
IllegalAccessException e) {
|
IllegalAccessException e) {
|
||||||
throw new RuntimeException("Unable to instantiate entity", e);
|
throw new PersistenceException("Unable to instantiate entity", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -54,25 +54,25 @@ public abstract class AbstractRepository<E extends PersistenceEntity> implements
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public E load(UUID uuid) {
|
public E get(UUID uuid) {
|
||||||
try (var query = buildQueryParametrized("where uuid = ?1", uuid)) {
|
try (var query = buildQueryParametrized("where uuid = ?1", uuid)) {
|
||||||
return query.get().findFirst().orElse(null);
|
return query.get().findFirst().orElse(null);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new RuntimeException(e);
|
throw new PersistenceException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public E load(long id) {
|
public E get(long id) {
|
||||||
try (var query = buildQueryParametrized("where id = ?1", id)) {
|
try (var query = buildQueryParametrized("where id = ?1", id)) {
|
||||||
return query.get().findFirst().orElse(null);
|
return query.get().findFirst().orElse(null);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new RuntimeException(e);
|
throw new PersistenceException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public StreamHandler<E> load(Collection<Long> ids) {
|
public StreamHandler<E> get(Collection<Long> ids) {
|
||||||
if (ids != null && !ids.isEmpty()) {
|
if (ids != null && !ids.isEmpty()) {
|
||||||
return buildQueryParametrized("where id IN (" + joinIdentifiers(ids) + ")");
|
return buildQueryParametrized("where id IN (" + joinIdentifiers(ids) + ")");
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -86,7 +86,7 @@ public abstract class AbstractRepository<E extends PersistenceEntity> implements
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public StreamHandler<E> loadAll() {
|
public StreamHandler<E> getAll() {
|
||||||
return buildQueryParametrized("order by id");
|
return buildQueryParametrized("order by id");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -104,7 +104,7 @@ public abstract class AbstractRepository<E extends PersistenceEntity> implements
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void store(E entity) {
|
public void save(E entity) {
|
||||||
var session = repositoryService.openSession();
|
var session = repositoryService.openSession();
|
||||||
var transaction = session.beginTransaction();
|
var transaction = session.beginTransaction();
|
||||||
try {
|
try {
|
||||||
|
|
@ -116,21 +116,21 @@ public abstract class AbstractRepository<E extends PersistenceEntity> implements
|
||||||
transaction.commit();
|
transaction.commit();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
transaction.rollback();
|
transaction.rollback();
|
||||||
throw new RuntimeException(e);
|
throw new PersistenceException("Unable to save entity", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
eventBindings.entityStored().invoke(entity);
|
eventBindings.entityStored().invoke(entity);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new RuntimeException(e);
|
throw new PersistenceException("Something went wrong on entity save event", e);
|
||||||
} finally {
|
} finally {
|
||||||
session.close();
|
session.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void store(Collection<E> entities) {
|
public void save(Collection<E> entities) {
|
||||||
entities.forEach(this::store);
|
entities.forEach(this::save);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -142,13 +142,13 @@ public abstract class AbstractRepository<E extends PersistenceEntity> implements
|
||||||
transaction.commit();
|
transaction.commit();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
transaction.rollback();
|
transaction.rollback();
|
||||||
throw new RuntimeException(e);
|
throw new PersistenceException("Unable to remove entity", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
eventBindings.entityRemoved().invoke(entity);
|
eventBindings.entityRemoved().invoke(entity);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new RuntimeException(e);
|
throw new PersistenceException("Something went wrong on entity deletion", e);
|
||||||
} finally {
|
} finally {
|
||||||
session.close();
|
session.close();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,9 +27,9 @@ public class EntityReferenceDeserializer extends StdDeserializer<EntityReference
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
var repository = repositoryService.getRepositoryForEntity((Class<? extends PersistenceEntity>) Class.forName(type));
|
var repository = repositoryService.getRepositoryForEntity((Class<? extends PersistenceEntity>) Class.forName(type));
|
||||||
if (uuid != null) {
|
if (uuid != null) {
|
||||||
return new EntityReference(repository.load(UUID.fromString(uuid)));
|
return new EntityReference(repository.get(UUID.fromString(uuid)));
|
||||||
}
|
}
|
||||||
return new EntityReference(repository.load(id));
|
return new EntityReference(repository.get(id));
|
||||||
} catch (ClassNotFoundException e) {
|
} catch (ClassNotFoundException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -57,14 +57,14 @@ public class GroupRepositoryImpl extends AbstractNodeRepository<Group> implement
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void store(Group entity) {
|
public void save(Group entity) {
|
||||||
if (entity != null && entity.getParent() == null) {
|
if (entity != null && entity.getParent() == null) {
|
||||||
var root = getRoot();
|
var root = getRoot();
|
||||||
if (root != null && !root.equals(entity)) {
|
if (root != null && !root.equals(entity)) {
|
||||||
throw new IllegalStateException("Root group already exists");
|
throw new IllegalStateException("Root group already exists");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
super.store(entity);
|
super.save(entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -30,11 +30,11 @@ public class InputRepositoryImpl extends AbstractNodeRepository<Input> implement
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void store(Input entity) {
|
public void save(Input entity) {
|
||||||
if (entity != null && entity.getParent() == null) {
|
if (entity != null && entity.getParent() == null) {
|
||||||
throw new IllegalStateException("Saving inputs without group is prohibited");
|
throw new IllegalStateException("Saving inputs without group is prohibited");
|
||||||
}
|
}
|
||||||
super.store(entity);
|
super.save(entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -27,11 +27,11 @@ public class OutputRepositoryImpl extends AbstractNodeRepository<Output> impleme
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void store(Output entity) {
|
public void save(Output entity) {
|
||||||
if (entity != null && entity.getParent() == null) {
|
if (entity != null && entity.getParent() == null) {
|
||||||
throw new IllegalStateException("Saving outputs without group is prohibited");
|
throw new IllegalStateException("Saving outputs without group is prohibited");
|
||||||
}
|
}
|
||||||
super.store(entity);
|
super.save(entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ public class TagRepositoryImpl extends AbstractRepository<Tag> implements TagRep
|
||||||
} else {
|
} else {
|
||||||
var tag = create();
|
var tag = create();
|
||||||
tag.setName(name);
|
tag.setName(name);
|
||||||
store(tag);
|
save(tag);
|
||||||
return tag;
|
return tag;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -58,7 +58,7 @@ public class TagRepositoryImpl extends AbstractRepository<Tag> implements TagRep
|
||||||
if(!foundNames.contains(tagName)) {
|
if(!foundNames.contains(tagName)) {
|
||||||
var tag = create();
|
var tag = create();
|
||||||
tag.setName(tagName);
|
tag.setName(tagName);
|
||||||
store(tag);
|
save(tag);
|
||||||
tags.add(tag);
|
tags.add(tag);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -59,7 +59,7 @@ public class UserRepositoryImpl extends AbstractRepository<User> implements User
|
||||||
@UuidGenerator
|
@UuidGenerator
|
||||||
private UUID uuid;
|
private UUID uuid;
|
||||||
|
|
||||||
@Column(nullable = false)
|
@Column(nullable = false, unique = true)
|
||||||
@JsonProperty
|
@JsonProperty
|
||||||
private String login = "";
|
private String login = "";
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,15 +7,20 @@ import org.hibernate.SessionFactory;
|
||||||
import org.hibernate.cfg.Configuration;
|
import org.hibernate.cfg.Configuration;
|
||||||
import org.hibernate.type.Type;
|
import org.hibernate.type.Type;
|
||||||
import ru.kirillius.XCP.Commons.Context;
|
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.ObjectMapper;
|
||||||
import tools.jackson.databind.json.JsonMapper;
|
import tools.jackson.databind.json.JsonMapper;
|
||||||
|
|
||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
@ServiceLoadPriority(0)
|
||||||
public final class RepositoryServiceImpl implements RepositoryService {
|
public final class RepositoryServiceImpl implements RepositoryService {
|
||||||
@Getter
|
@Getter
|
||||||
private ObjectMapper mapper = new ObjectMapper();
|
private ObjectMapper mapper = new ObjectMapper();
|
||||||
|
|
@ -51,6 +56,20 @@ public final class RepositoryServiceImpl implements RepositoryService {
|
||||||
registerClasses();
|
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() {
|
private void registerClasses() {
|
||||||
managedRepositoryClasses.forEach(aClass -> {
|
managedRepositoryClasses.forEach(aClass -> {
|
||||||
var implementation = aClass.getAnnotation(EntityImplementation.class);
|
var implementation = aClass.getAnnotation(EntityImplementation.class);
|
||||||
|
|
@ -81,8 +100,10 @@ public final class RepositoryServiceImpl implements RepositoryService {
|
||||||
public void close() {
|
public void close() {
|
||||||
repositoryBindings.clear();
|
repositoryBindings.clear();
|
||||||
entityBindings.clear();
|
entityBindings.clear();
|
||||||
|
if (sessionFactory != null) {
|
||||||
sessionFactory.close();
|
sessionFactory.close();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private volatile boolean initialized;
|
private volatile boolean initialized;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -20,25 +20,25 @@ abstract class GenericNodeRepositoryTest<E extends NodeEntity, R extends Abstrac
|
||||||
var groupRepository = service.getRepository(GroupRepository.class);
|
var groupRepository = service.getRepository(GroupRepository.class);
|
||||||
|
|
||||||
var root = groupRepository.create();
|
var root = groupRepository.create();
|
||||||
groupRepository.store(root);
|
groupRepository.save(root);
|
||||||
|
|
||||||
var firstChildGroup = groupRepository.create();
|
var firstChildGroup = groupRepository.create();
|
||||||
firstChildGroup.setParent(root);
|
firstChildGroup.setParent(root);
|
||||||
groupRepository.store(firstChildGroup);
|
groupRepository.save(firstChildGroup);
|
||||||
|
|
||||||
var secondChildGroup = groupRepository.create();
|
var secondChildGroup = groupRepository.create();
|
||||||
secondChildGroup.setParent(root);
|
secondChildGroup.setParent(root);
|
||||||
groupRepository.store(secondChildGroup);
|
groupRepository.save(secondChildGroup);
|
||||||
|
|
||||||
var firstChild = repository.create();
|
var firstChild = repository.create();
|
||||||
modify(firstChild, service);
|
modify(firstChild, service);
|
||||||
firstChild.setParent(firstChildGroup);
|
firstChild.setParent(firstChildGroup);
|
||||||
repository.store(firstChild);
|
repository.save(firstChild);
|
||||||
|
|
||||||
var secondChild = repository.create();
|
var secondChild = repository.create();
|
||||||
modify(secondChild, service);
|
modify(secondChild, service);
|
||||||
secondChild.setParent(secondChildGroup);
|
secondChild.setParent(secondChildGroup);
|
||||||
repository.store(secondChild);
|
repository.save(secondChild);
|
||||||
|
|
||||||
try (var handler = repository.getByGroup(firstChildGroup)) {
|
try (var handler = repository.getByGroup(firstChildGroup)) {
|
||||||
var found = handler.get().findFirst().orElse(null);
|
var found = handler.get().findFirst().orElse(null);
|
||||||
|
|
@ -67,7 +67,7 @@ abstract class GenericNodeRepositoryTest<E extends NodeEntity, R extends Abstrac
|
||||||
"bar"
|
"bar"
|
||||||
)));
|
)));
|
||||||
|
|
||||||
repository.store(first);
|
repository.save(first);
|
||||||
|
|
||||||
var second = repository.create();
|
var second = repository.create();
|
||||||
modify(second, service);
|
modify(second, service);
|
||||||
|
|
@ -76,7 +76,7 @@ abstract class GenericNodeRepositoryTest<E extends NodeEntity, R extends Abstrac
|
||||||
"foo",
|
"foo",
|
||||||
"third"
|
"third"
|
||||||
)));
|
)));
|
||||||
repository.store(second);
|
repository.save(second);
|
||||||
|
|
||||||
var third = repository.create();
|
var third = repository.create();
|
||||||
modify(third, service);
|
modify(third, service);
|
||||||
|
|
@ -85,7 +85,7 @@ abstract class GenericNodeRepositoryTest<E extends NodeEntity, R extends Abstrac
|
||||||
"third",
|
"third",
|
||||||
"yabba"
|
"yabba"
|
||||||
)));
|
)));
|
||||||
repository.store(third);
|
repository.save(third);
|
||||||
|
|
||||||
try (var handler = repository.getByAllTags(tagRepository.getByNamesOrCreate(List.of("first")))) {
|
try (var handler = repository.getByAllTags(tagRepository.getByNamesOrCreate(List.of("first")))) {
|
||||||
assertThat(handler.get().toList()).containsExactlyInAnyOrder(first).doesNotContain(second, third);
|
assertThat(handler.get().toList()).containsExactlyInAnyOrder(first).doesNotContain(second, third);
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ package ru.kirillius.XCP.Persistence.Repositories;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import ru.kirillius.XCP.Persistence.AbstractRepository;
|
import ru.kirillius.XCP.Persistence.AbstractRepository;
|
||||||
import ru.kirillius.XCP.Persistence.PersistenceEntity;
|
import ru.kirillius.XCP.Persistence.PersistenceEntity;
|
||||||
import ru.kirillius.XCP.Persistence.RepositoryService;
|
import ru.kirillius.XCP.Services.RepositoryService;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.lang.reflect.ParameterizedType;
|
import java.lang.reflect.ParameterizedType;
|
||||||
|
|
@ -48,27 +48,27 @@ abstract class GenericRepositoryTest<E extends PersistenceEntity, R extends Abst
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testStore() throws IOException {
|
void testSave() throws IOException {
|
||||||
try (var service = spawnRepositoryService()) {
|
try (var service = spawnRepositoryService()) {
|
||||||
var repository = service.getRepositoryForEntity(entityClass);
|
var repository = service.getRepositoryForEntity(entityClass);
|
||||||
var entity = repository.create();
|
var entity = repository.create();
|
||||||
modify(entity, service);
|
modify(entity, service);
|
||||||
repository.store(entity);
|
repository.save(entity);
|
||||||
assertThat(entity.getId()).isNotZero();
|
assertThat(entity.getId()).isNotZero();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testLoad() throws IOException {
|
void testGet() throws IOException {
|
||||||
try (var service = spawnRepositoryService()) {
|
try (var service = spawnRepositoryService()) {
|
||||||
var repository = service.getRepositoryForEntity(entityClass);
|
var repository = service.getRepositoryForEntity(entityClass);
|
||||||
var entity = repository.create();
|
var entity = repository.create();
|
||||||
modify(entity, service);
|
modify(entity, service);
|
||||||
repository.store(entity);
|
repository.save(entity);
|
||||||
var loaded = repository.load(entity.getId());
|
var loaded = repository.get(entity.getId());
|
||||||
assertThat(loaded).isNotNull().isEqualTo(entity);
|
assertThat(loaded).isNotNull().isEqualTo(entity);
|
||||||
|
|
||||||
var loadedByUUID = repository.load(entity.getUuid());
|
var loadedByUUID = repository.get(entity.getUuid());
|
||||||
assertThat(loadedByUUID).isNotNull().isEqualTo(entity);
|
assertThat(loadedByUUID).isNotNull().isEqualTo(entity);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -79,10 +79,10 @@ abstract class GenericRepositoryTest<E extends PersistenceEntity, R extends Abst
|
||||||
var repository = service.getRepositoryForEntity(entityClass);
|
var repository = service.getRepositoryForEntity(entityClass);
|
||||||
var entity = repository.create();
|
var entity = repository.create();
|
||||||
modify(entity, service);
|
modify(entity, service);
|
||||||
repository.store(entity);
|
repository.save(entity);
|
||||||
modify(entity, service);
|
modify(entity, service);
|
||||||
repository.store(entity);
|
repository.save(entity);
|
||||||
var loaded = repository.load(entity.getId());
|
var loaded = repository.get(entity.getId());
|
||||||
assertThat(loaded).isNotNull().isEqualTo(entity);
|
assertThat(loaded).isNotNull().isEqualTo(entity);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -93,7 +93,7 @@ abstract class GenericRepositoryTest<E extends PersistenceEntity, R extends Abst
|
||||||
var repository = service.getRepositoryForEntity(entityClass);
|
var repository = service.getRepositoryForEntity(entityClass);
|
||||||
var entity = repository.create();
|
var entity = repository.create();
|
||||||
modify(entity, service);
|
modify(entity, service);
|
||||||
repository.store(entity);
|
repository.save(entity);
|
||||||
assertThat(repository.getCount()).isEqualTo(1);
|
assertThat(repository.getCount()).isEqualTo(1);
|
||||||
repository.remove(entity);
|
repository.remove(entity);
|
||||||
assertThat(repository.getCount()).isZero();
|
assertThat(repository.getCount()).isZero();
|
||||||
|
|
@ -106,7 +106,7 @@ abstract class GenericRepositoryTest<E extends PersistenceEntity, R extends Abst
|
||||||
var repository = service.getRepositoryForEntity(entityClass);
|
var repository = service.getRepositoryForEntity(entityClass);
|
||||||
var entity = repository.create();
|
var entity = repository.create();
|
||||||
modify(entity, service);
|
modify(entity, service);
|
||||||
repository.store(entity);
|
repository.save(entity);
|
||||||
|
|
||||||
var serialized = repository.serialize(entity);
|
var serialized = repository.serialize(entity);
|
||||||
var deserialized = repository.deserialize(serialized);
|
var deserialized = repository.deserialize(serialized);
|
||||||
|
|
@ -115,7 +115,7 @@ abstract class GenericRepositoryTest<E extends PersistenceEntity, R extends Abst
|
||||||
|
|
||||||
var anotherEntity = repository.create();
|
var anotherEntity = repository.create();
|
||||||
modify(anotherEntity, service);
|
modify(anotherEntity, service);
|
||||||
repository.store(anotherEntity);
|
repository.save(anotherEntity);
|
||||||
|
|
||||||
var anotherSerialized = repository.serialize(anotherEntity);
|
var anotherSerialized = repository.serialize(anotherEntity);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ package ru.kirillius.XCP.Persistence.Repositories;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import ru.kirillius.XCP.Persistence.Entities.Group;
|
import ru.kirillius.XCP.Persistence.Entities.Group;
|
||||||
import ru.kirillius.XCP.Persistence.Entities.Tag;
|
import ru.kirillius.XCP.Persistence.Entities.Tag;
|
||||||
import ru.kirillius.XCP.Persistence.RepositoryService;
|
import ru.kirillius.XCP.Services.RepositoryService;
|
||||||
import ru.kirillius.XCP.Persistence.TagCollectionImpl;
|
import ru.kirillius.XCP.Persistence.TagCollectionImpl;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
@ -22,7 +22,7 @@ class GroupRepositoryImplTest extends GenericNodeRepositoryTest<Group, GroupRepo
|
||||||
var tagRepository = service.getRepositoryForEntity(Tag.class);
|
var tagRepository = service.getRepositoryForEntity(Tag.class);
|
||||||
var tag = tagRepository.create();
|
var tag = tagRepository.create();
|
||||||
tag.setName("tag" + i);
|
tag.setName("tag" + i);
|
||||||
tagRepository.store(tag);
|
tagRepository.save(tag);
|
||||||
}
|
}
|
||||||
return service;
|
return service;
|
||||||
}
|
}
|
||||||
|
|
@ -34,18 +34,18 @@ class GroupRepositoryImplTest extends GenericNodeRepositoryTest<Group, GroupRepo
|
||||||
var repository = service.getRepository(GroupRepository.class);
|
var repository = service.getRepository(GroupRepository.class);
|
||||||
assertThat(repository.getRoot()).isNull();
|
assertThat(repository.getRoot()).isNull();
|
||||||
var root = repository.create();
|
var root = repository.create();
|
||||||
repository.store(root);
|
repository.save(root);
|
||||||
|
|
||||||
var anotherParent = repository.create();
|
var anotherParent = repository.create();
|
||||||
anotherParent.setParent(root);
|
anotherParent.setParent(root);
|
||||||
repository.store(anotherParent);
|
repository.save(anotherParent);
|
||||||
|
|
||||||
var children = new ArrayList<Group>();
|
var children = new ArrayList<Group>();
|
||||||
for (var i = 0; i < 20; i++) {
|
for (var i = 0; i < 20; i++) {
|
||||||
var child = repository.create();
|
var child = repository.create();
|
||||||
child.setParent(anotherParent);
|
child.setParent(anotherParent);
|
||||||
children.add(child);
|
children.add(child);
|
||||||
repository.store(child);
|
repository.save(child);
|
||||||
}
|
}
|
||||||
|
|
||||||
try (var handler = repository.getChildrenOf(root)) {
|
try (var handler = repository.getChildrenOf(root)) {
|
||||||
|
|
@ -65,22 +65,22 @@ class GroupRepositoryImplTest extends GenericNodeRepositoryTest<Group, GroupRepo
|
||||||
var repository = service.getRepository(GroupRepository.class);
|
var repository = service.getRepository(GroupRepository.class);
|
||||||
assertThat(repository.getRoot()).isNull();
|
assertThat(repository.getRoot()).isNull();
|
||||||
var root = repository.create();
|
var root = repository.create();
|
||||||
repository.store(root);
|
repository.save(root);
|
||||||
|
|
||||||
var anotherParent = repository.create();
|
var anotherParent = repository.create();
|
||||||
anotherParent.setParent(root);
|
anotherParent.setParent(root);
|
||||||
repository.store(anotherParent);
|
repository.save(anotherParent);
|
||||||
|
|
||||||
var children = new ArrayList<Group>();
|
var children = new ArrayList<Group>();
|
||||||
for (var i = 0; i < 10; i++) {
|
for (var i = 0; i < 10; i++) {
|
||||||
var child = repository.create();
|
var child = repository.create();
|
||||||
child.setParent(anotherParent);
|
child.setParent(anotherParent);
|
||||||
children.add(child);
|
children.add(child);
|
||||||
repository.store(child);
|
repository.save(child);
|
||||||
|
|
||||||
var subchild = repository.create();
|
var subchild = repository.create();
|
||||||
subchild.setParent(child);
|
subchild.setParent(child);
|
||||||
repository.store(subchild);
|
repository.save(subchild);
|
||||||
children.add(subchild);
|
children.add(subchild);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -101,10 +101,10 @@ class GroupRepositoryImplTest extends GenericNodeRepositoryTest<Group, GroupRepo
|
||||||
var repository = service.getRepository(GroupRepository.class);
|
var repository = service.getRepository(GroupRepository.class);
|
||||||
assertThat(repository.getRoot()).isNull();
|
assertThat(repository.getRoot()).isNull();
|
||||||
var root = repository.create();
|
var root = repository.create();
|
||||||
repository.store(root);
|
repository.save(root);
|
||||||
var notARoot = repository.create();
|
var notARoot = repository.create();
|
||||||
notARoot.setParent(root);
|
notARoot.setParent(root);
|
||||||
repository.store(notARoot);
|
repository.save(notARoot);
|
||||||
assertThat(repository.getRoot()).isNotNull().isEqualTo(root);
|
assertThat(repository.getRoot()).isNotNull().isEqualTo(root);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -118,7 +118,7 @@ class GroupRepositoryImplTest extends GenericNodeRepositoryTest<Group, GroupRepo
|
||||||
var secondaryRoot = repository.create();
|
var secondaryRoot = repository.create();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
repository.store(List.of(root, secondaryRoot));
|
repository.save(List.of(root, secondaryRoot));
|
||||||
throw new Exception("Nothing is thrown");
|
throw new Exception("Nothing is thrown");
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
assertThat(e).isInstanceOf(IllegalStateException.class);
|
assertThat(e).isInstanceOf(IllegalStateException.class);
|
||||||
|
|
@ -137,7 +137,7 @@ class GroupRepositoryImplTest extends GenericNodeRepositoryTest<Group, GroupRepo
|
||||||
entity.setParent(root);
|
entity.setParent(root);
|
||||||
}
|
}
|
||||||
|
|
||||||
try (var handler = service.getRepositoryForEntity(Tag.class).loadAll()) {
|
try (var handler = service.getRepositoryForEntity(Tag.class).getAll()) {
|
||||||
entity.setTags(new TagCollectionImpl(handler.get().toList()));
|
entity.setTags(new TagCollectionImpl(handler.get().toList()));
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ package ru.kirillius.XCP.Persistence.Repositories;
|
||||||
|
|
||||||
import ru.kirillius.XCP.Persistence.Entities.Input;
|
import ru.kirillius.XCP.Persistence.Entities.Input;
|
||||||
import ru.kirillius.XCP.Persistence.Entities.Tag;
|
import ru.kirillius.XCP.Persistence.Entities.Tag;
|
||||||
import ru.kirillius.XCP.Persistence.RepositoryService;
|
import ru.kirillius.XCP.Services.RepositoryService;
|
||||||
import ru.kirillius.XCP.Persistence.TagCollectionImpl;
|
import ru.kirillius.XCP.Persistence.TagCollectionImpl;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
@ -19,7 +19,7 @@ class InputRepositoryImplTest extends GenericNodeRepositoryTest<Input, InputRepo
|
||||||
var tagRepository = service.getRepositoryForEntity(Tag.class);
|
var tagRepository = service.getRepositoryForEntity(Tag.class);
|
||||||
var tag = tagRepository.create();
|
var tag = tagRepository.create();
|
||||||
tag.setName("tag" + i);
|
tag.setName("tag" + i);
|
||||||
tagRepository.store(tag);
|
tagRepository.save(tag);
|
||||||
}
|
}
|
||||||
return service;
|
return service;
|
||||||
}
|
}
|
||||||
|
|
@ -31,14 +31,14 @@ class InputRepositoryImplTest extends GenericNodeRepositoryTest<Input, InputRepo
|
||||||
var root = groupRepository.getRoot();
|
var root = groupRepository.getRoot();
|
||||||
if (root == null) {
|
if (root == null) {
|
||||||
root = groupRepository.create();
|
root = groupRepository.create();
|
||||||
groupRepository.store(root);
|
groupRepository.save(root);
|
||||||
}
|
}
|
||||||
|
|
||||||
entity.setParent(root);
|
entity.setParent(root);
|
||||||
entity.setName(UUID.randomUUID().toString());
|
entity.setName(UUID.randomUUID().toString());
|
||||||
//var inputRepository = service.getRepository(InputRepository.class);
|
//var inputRepository = service.getRepository(InputRepository.class);
|
||||||
|
|
||||||
try (var handler = service.getRepositoryForEntity(Tag.class).loadAll()) {
|
try (var handler = service.getRepositoryForEntity(Tag.class).getAll()) {
|
||||||
entity.setTags(new TagCollectionImpl(handler.get().toList()));
|
entity.setTags(new TagCollectionImpl(handler.get().toList()));
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ package ru.kirillius.XCP.Persistence.Repositories;
|
||||||
|
|
||||||
import ru.kirillius.XCP.Persistence.Entities.Output;
|
import ru.kirillius.XCP.Persistence.Entities.Output;
|
||||||
import ru.kirillius.XCP.Persistence.Entities.Tag;
|
import ru.kirillius.XCP.Persistence.Entities.Tag;
|
||||||
import ru.kirillius.XCP.Persistence.RepositoryService;
|
import ru.kirillius.XCP.Services.RepositoryService;
|
||||||
import ru.kirillius.XCP.Persistence.TagCollectionImpl;
|
import ru.kirillius.XCP.Persistence.TagCollectionImpl;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
@ -19,7 +19,7 @@ class OutputRepositoryImplTest extends GenericNodeRepositoryTest<Output, OutputR
|
||||||
var tagRepository = service.getRepositoryForEntity(Tag.class);
|
var tagRepository = service.getRepositoryForEntity(Tag.class);
|
||||||
var tag = tagRepository.create();
|
var tag = tagRepository.create();
|
||||||
tag.setName("tag" + i);
|
tag.setName("tag" + i);
|
||||||
tagRepository.store(tag);
|
tagRepository.save(tag);
|
||||||
}
|
}
|
||||||
return service;
|
return service;
|
||||||
}
|
}
|
||||||
|
|
@ -31,14 +31,14 @@ class OutputRepositoryImplTest extends GenericNodeRepositoryTest<Output, OutputR
|
||||||
var root = groupRepository.getRoot();
|
var root = groupRepository.getRoot();
|
||||||
if (root == null) {
|
if (root == null) {
|
||||||
root = groupRepository.create();
|
root = groupRepository.create();
|
||||||
groupRepository.store(root);
|
groupRepository.save(root);
|
||||||
}
|
}
|
||||||
|
|
||||||
entity.setParent(root);
|
entity.setParent(root);
|
||||||
entity.setName(UUID.randomUUID().toString());
|
entity.setName(UUID.randomUUID().toString());
|
||||||
//var inputRepository = service.getRepository(InputRepository.class);
|
//var inputRepository = service.getRepository(InputRepository.class);
|
||||||
|
|
||||||
try (var handler = service.getRepositoryForEntity(Tag.class).loadAll()) {
|
try (var handler = service.getRepositoryForEntity(Tag.class).getAll()) {
|
||||||
entity.setTags(new TagCollectionImpl(handler.get().toList()));
|
entity.setTags(new TagCollectionImpl(handler.get().toList()));
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ package ru.kirillius.XCP.Persistence.Repositories;
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import ru.kirillius.XCP.Persistence.Entities.Tag;
|
import ru.kirillius.XCP.Persistence.Entities.Tag;
|
||||||
import ru.kirillius.XCP.Persistence.RepositoryService;
|
import ru.kirillius.XCP.Services.RepositoryService;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
@ -16,7 +16,7 @@ class TagRepositoryImplTest extends GenericRepositoryTest<Tag, TagRepositoryImpl
|
||||||
var repository = service.getRepository(TagRepository.class);
|
var repository = service.getRepository(TagRepository.class);
|
||||||
var tag = repository.create();
|
var tag = repository.create();
|
||||||
tag.setName("test");
|
tag.setName("test");
|
||||||
repository.store(tag);
|
repository.save(tag);
|
||||||
var found = repository.getByNameOrCreate(tag.getName());
|
var found = repository.getByNameOrCreate(tag.getName());
|
||||||
assertThat(found).isEqualTo(tag);
|
assertThat(found).isEqualTo(tag);
|
||||||
}
|
}
|
||||||
|
|
@ -32,7 +32,7 @@ class TagRepositoryImplTest extends GenericRepositoryTest<Tag, TagRepositoryImpl
|
||||||
for (var i = 0; i < 10; i++) {
|
for (var i = 0; i < 10; i++) {
|
||||||
var tag = repository.create();
|
var tag = repository.create();
|
||||||
tag.setName("test" + i);
|
tag.setName("test" + i);
|
||||||
repository.store(tag);
|
repository.save(tag);
|
||||||
tags.add(tag);
|
tags.add(tag);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -52,14 +52,14 @@ class TagRepositoryImplTest extends GenericRepositoryTest<Tag, TagRepositoryImpl
|
||||||
for (var i = 0; i < 10; i++) {
|
for (var i = 0; i < 10; i++) {
|
||||||
var tag = repository.create();
|
var tag = repository.create();
|
||||||
tag.setName("test" + i);
|
tag.setName("test" + i);
|
||||||
repository.store(tag);
|
repository.save(tag);
|
||||||
tags.add(tag);
|
tags.add(tag);
|
||||||
}
|
}
|
||||||
|
|
||||||
var emptyCollection = repository.createCollection();
|
var emptyCollection = repository.createCollection();
|
||||||
assertThat(emptyCollection).isNotNull().isEmpty();
|
assertThat(emptyCollection).isNotNull().isEmpty();
|
||||||
|
|
||||||
try (var handler = repository.loadAll()) {
|
try (var handler = repository.getAll()) {
|
||||||
var collection = repository.createCollection(handler);
|
var collection = repository.createCollection(handler);
|
||||||
assertThat(collection).containsExactlyInAnyOrderElementsOf(tags);
|
assertThat(collection).containsExactlyInAnyOrderElementsOf(tags);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,8 @@ package ru.kirillius.XCP.Persistence.Repositories;
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import ru.kirillius.XCP.Persistence.Entities.User;
|
import ru.kirillius.XCP.Persistence.Entities.User;
|
||||||
import ru.kirillius.XCP.Persistence.RepositoryService;
|
import ru.kirillius.XCP.Persistence.PersistenceException;
|
||||||
|
import ru.kirillius.XCP.Services.RepositoryService;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
@ -11,6 +12,27 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
class UserRepositoryImplTest extends GenericRepositoryTest<User, UserRepositoryImpl> {
|
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
|
@Test
|
||||||
void getByLogin() throws IOException {
|
void getByLogin() throws IOException {
|
||||||
var correct = "correctlogin";
|
var correct = "correctlogin";
|
||||||
|
|
@ -19,15 +41,15 @@ class UserRepositoryImplTest extends GenericRepositoryTest<User, UserRepositoryI
|
||||||
for (var i = 0; i < 10; i++) {
|
for (var i = 0; i < 10; i++) {
|
||||||
var user = repository.create();
|
var user = repository.create();
|
||||||
user.setLogin("incorrect" + UUID.randomUUID());
|
user.setLogin("incorrect" + UUID.randomUUID());
|
||||||
repository.store(user);
|
repository.save(user);
|
||||||
}
|
}
|
||||||
var correctUser = repository.create();
|
var correctUser = repository.create();
|
||||||
correctUser.setLogin(correct);
|
correctUser.setLogin(correct);
|
||||||
repository.store(correctUser);
|
repository.save(correctUser);
|
||||||
for (var i = 0; i < 10; i++) {
|
for (var i = 0; i < 10; i++) {
|
||||||
var user = repository.create();
|
var user = repository.create();
|
||||||
user.setLogin("incorrect" + UUID.randomUUID());
|
user.setLogin("incorrect" + UUID.randomUUID());
|
||||||
repository.store(user);
|
repository.save(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
var loaded = repository.getByLogin(correct);
|
var loaded = repository.getByLogin(correct);
|
||||||
|
|
|
||||||
|
|
@ -92,7 +92,7 @@ class RepositoryServiceImplTest {
|
||||||
try (var service = instantiateTestService(List.of(RepoImpl.class))) {
|
try (var service = instantiateTestService(List.of(RepoImpl.class))) {
|
||||||
var repository = service.getRepository(TestRepository.class);
|
var repository = service.getRepository(TestRepository.class);
|
||||||
var testEntity = repository.create();
|
var testEntity = repository.create();
|
||||||
repository.store(testEntity);
|
repository.save(testEntity);
|
||||||
var serialized = repository.serialize(testEntity);
|
var serialized = repository.serialize(testEntity);
|
||||||
var deserialized = repository.deserialize(serialized);
|
var deserialized = repository.deserialize(serialized);
|
||||||
assertThat(deserialized).isEqualTo(testEntity);
|
assertThat(deserialized).isEqualTo(testEntity);
|
||||||
|
|
@ -100,38 +100,38 @@ class RepositoryServiceImplTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void TestEntityStore() {
|
public void TestEntitySave() {
|
||||||
try (var service = instantiateTestService(List.of(RepoImpl.class))) {
|
try (var service = instantiateTestService(List.of(RepoImpl.class))) {
|
||||||
var repository = service.getRepository(TestRepository.class);
|
var repository = service.getRepository(TestRepository.class);
|
||||||
var testEntity = repository.create();
|
var testEntity = repository.create();
|
||||||
testEntity.setTestField("new");
|
testEntity.setTestField("new");
|
||||||
repository.store(List.of(testEntity));
|
repository.save(List.of(testEntity));
|
||||||
assertThat(testEntity.getId()).isNotZero();
|
assertThat(testEntity.getId()).isNotZero();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void TestEntityLoad() {
|
public void TestEntityGet() {
|
||||||
try (var service = instantiateTestService(List.of(RepoImpl.class))) {
|
try (var service = instantiateTestService(List.of(RepoImpl.class))) {
|
||||||
var repository = service.getRepository(TestRepository.class);
|
var repository = service.getRepository(TestRepository.class);
|
||||||
|
|
||||||
assertThat(repository.load(1)).isNull();
|
assertThat(repository.get(1)).isNull();
|
||||||
|
|
||||||
var testEntity = repository.create();
|
var testEntity = repository.create();
|
||||||
testEntity.setTestField("new");
|
testEntity.setTestField("new");
|
||||||
repository.store(List.of(testEntity));
|
repository.save(List.of(testEntity));
|
||||||
|
|
||||||
var receivedEntity = repository.load(testEntity.getId());
|
var receivedEntity = repository.get(testEntity.getId());
|
||||||
assertThat(receivedEntity).isNotNull().isEqualTo(testEntity);
|
assertThat(receivedEntity).isNotNull().isEqualTo(testEntity);
|
||||||
assertThat(testEntity == receivedEntity).isFalse();//not exact instance
|
assertThat(testEntity == receivedEntity).isFalse();//not exact instance
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void TestEntityLoadMultiple() throws IOException {
|
public void TestEntityGetMultiple() throws IOException {
|
||||||
try (var service = instantiateTestService(List.of(RepoImpl.class))) {
|
try (var service = instantiateTestService(List.of(RepoImpl.class))) {
|
||||||
var repository = service.getRepository(TestRepository.class);
|
var repository = service.getRepository(TestRepository.class);
|
||||||
try (var bundle = repository.loadAll()) {
|
try (var bundle = repository.getAll()) {
|
||||||
assertThat(bundle.get().toList()).isEmpty();
|
assertThat(bundle.get().toList()).isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -143,14 +143,14 @@ class RepositoryServiceImplTest {
|
||||||
entitiesToSave.add(entity);
|
entitiesToSave.add(entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
repository.store(entitiesToSave);
|
repository.save(entitiesToSave);
|
||||||
|
|
||||||
try (var bundle = repository.load(entitiesToSave.stream().map(PersistenceEntity::getId).toList())) {
|
try (var bundle = repository.get(entitiesToSave.stream().map(PersistenceEntity::getId).toList())) {
|
||||||
var loaded = bundle.get().toList();
|
var loaded = bundle.get().toList();
|
||||||
assertThat(loaded).containsExactlyInAnyOrderElementsOf(entitiesToSave);
|
assertThat(loaded).containsExactlyInAnyOrderElementsOf(entitiesToSave);
|
||||||
}
|
}
|
||||||
|
|
||||||
try (var bundle = repository.loadAll()) {
|
try (var bundle = repository.getAll()) {
|
||||||
var loaded = bundle.get().toList();
|
var loaded = bundle.get().toList();
|
||||||
assertThat(loaded).containsExactlyInAnyOrderElementsOf(entitiesToSave);
|
assertThat(loaded).containsExactlyInAnyOrderElementsOf(entitiesToSave);
|
||||||
}
|
}
|
||||||
|
|
@ -163,12 +163,12 @@ class RepositoryServiceImplTest {
|
||||||
var repository = service.getRepository(TestRepository.class);
|
var repository = service.getRepository(TestRepository.class);
|
||||||
var testEntity = repository.create();
|
var testEntity = repository.create();
|
||||||
testEntity.setTestField("new");
|
testEntity.setTestField("new");
|
||||||
repository.store(List.of(testEntity));
|
repository.save(List.of(testEntity));
|
||||||
|
|
||||||
testEntity.setTestField("updated");
|
testEntity.setTestField("updated");
|
||||||
repository.store(testEntity);
|
repository.save(testEntity);
|
||||||
|
|
||||||
var receivedEntity = repository.load(testEntity.getId());
|
var receivedEntity = repository.get(testEntity.getId());
|
||||||
assertThat(receivedEntity).isNotNull().isEqualTo(testEntity);
|
assertThat(receivedEntity).isNotNull().isEqualTo(testEntity);
|
||||||
assertThat(testEntity == receivedEntity).isFalse();//not exact instance
|
assertThat(testEntity == receivedEntity).isFalse();//not exact instance
|
||||||
}
|
}
|
||||||
|
|
@ -179,10 +179,10 @@ class RepositoryServiceImplTest {
|
||||||
try (var service = instantiateTestService(List.of(RepoImpl.class))) {
|
try (var service = instantiateTestService(List.of(RepoImpl.class))) {
|
||||||
var repository = service.getRepository(TestRepository.class);
|
var repository = service.getRepository(TestRepository.class);
|
||||||
var testEntity = repository.create();
|
var testEntity = repository.create();
|
||||||
repository.store(List.of(testEntity));
|
repository.save(List.of(testEntity));
|
||||||
assertThat(repository.getCount()).isEqualTo(1);
|
assertThat(repository.getCount()).isEqualTo(1);
|
||||||
repository.remove(List.of(testEntity));
|
repository.remove(List.of(testEntity));
|
||||||
assertThat(repository.load(testEntity.getId())).isNull();
|
assertThat(repository.get(testEntity.getId())).isNull();
|
||||||
assertThat(repository.getCount()).isZero();
|
assertThat(repository.getCount()).isZero();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,90 @@
|
||||||
|
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("\nError thrown ").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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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 error", 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
11
pom.xml
11
pom.xml
|
|
@ -12,6 +12,10 @@
|
||||||
<module>api</module>
|
<module>api</module>
|
||||||
<module>database</module>
|
<module>database</module>
|
||||||
<module>core</module>
|
<module>core</module>
|
||||||
|
<module>rpc</module>
|
||||||
|
<module>web-server</module>
|
||||||
|
<module>app</module>
|
||||||
|
<module>logging</module>
|
||||||
</modules>
|
</modules>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
|
|
@ -108,11 +112,10 @@
|
||||||
<version>1.3.0.0</version>
|
<version>1.3.0.0</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- https://mvnrepository.com/artifact/org.javassist/javassist -->
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.javassist</groupId>
|
<groupId>tools.jackson.core</groupId>
|
||||||
<artifactId>javassist</artifactId>
|
<artifactId>jackson-databind</artifactId>
|
||||||
<version>3.29.2-GA</version>
|
<version>3.0.3</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
package ru.kirillius.XCP.RPC.JSONRPC;
|
||||||
|
|
||||||
|
public enum JsonRpcErrorCode {
|
||||||
|
|
||||||
|
// Standard JSON-RPC 2.0 errors
|
||||||
|
PARSE_ERROR(-32700, "Parse error: Invalid JSON was received by the server. An error occurred on the server while parsing the JSON text."),
|
||||||
|
INVALID_REQUEST(-32600, "Invalid Request: The JSON sent is not a valid Request object."),
|
||||||
|
METHOD_NOT_FOUND(-32601, "Method not found: The method does not exist / is not available."),
|
||||||
|
INVALID_PARAMS(-32602, "Invalid params: Invalid method parameter(s)."),
|
||||||
|
INTERNAL_ERROR(-32603, "Internal error: Internal JSON-RPC error."),
|
||||||
|
|
||||||
|
// Implementation-specific errors
|
||||||
|
INVOCATION_ERROR(-32000, "Invocation error: The target object threw an exception (CommonErrorData)."),
|
||||||
|
NO_MARSHALED_OBJECT_FOUND(-32001, "No marshaled object found: The request was made for a remotely marshaled object that does not exist or has been removed."),
|
||||||
|
RESPONSE_SERIALIZATION_FAILURE(-32003, "Response serialization failure: The response could not be serialized as intended."),
|
||||||
|
INVOCATION_ERROR_WITH_EXCEPTION(-32004, "Invocation error with exception: The target object threw an exception (ISerializable)."),
|
||||||
|
REQUEST_CANCELED(-32800, "Request canceled: The execution of the server method was aborted due to a cancellation request from the client.");
|
||||||
|
|
||||||
|
private final int code;
|
||||||
|
private final String message;
|
||||||
|
|
||||||
|
JsonRpcErrorCode(int code, String message) {
|
||||||
|
this.code = code;
|
||||||
|
this.message = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCode() {
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMessage() {
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static JsonRpcErrorCode fromCode(int code) {
|
||||||
|
for (var error : JsonRpcErrorCode.values()) {
|
||||||
|
if (error.code == code) {
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return INTERNAL_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
package ru.kirillius.XCP.RPC.JSONRPC;
|
||||||
|
|
||||||
|
import ru.kirillius.XCP.Security.UserRole;
|
||||||
|
|
||||||
|
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.METHOD)
|
||||||
|
public @interface JsonRpcMethod {
|
||||||
|
UserRole accessLevel() default UserRole.Admin;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
package ru.kirillius.XCP.RPC.JSONRPC;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
import tools.jackson.databind.node.ObjectNode;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
public class JsonRpcRequest {
|
||||||
|
@JsonProperty
|
||||||
|
private String jsonrpc = "2.0";
|
||||||
|
@JsonProperty
|
||||||
|
private String method;
|
||||||
|
@JsonProperty
|
||||||
|
private ObjectNode params;
|
||||||
|
@JsonProperty
|
||||||
|
private long id;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
package ru.kirillius.XCP.RPC.JSONRPC;
|
||||||
|
|
||||||
|
import lombok.*;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@Builder
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class JsonRpcResponse {
|
||||||
|
private String jsonrpc = "2.0";
|
||||||
|
private Object result;
|
||||||
|
private JsonRpcError error;
|
||||||
|
private long id;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
package ru.kirillius.XCP.RPC.JSONRPC;
|
||||||
|
|
||||||
|
import tools.jackson.databind.JsonNode;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
public abstract class JsonRpcService {
|
||||||
|
protected JsonNode requireParam(CallContext context, String paramName) {
|
||||||
|
var params = context.getParams();
|
||||||
|
if (params.has(paramName)) {
|
||||||
|
return params.get(paramName);
|
||||||
|
}
|
||||||
|
throw new IllegalArgumentException(String.format("Missing required parameter '%s'", paramName));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected <T> T requireParam(CallContext context, String paramName, Function<JsonNode, T> whenFound) {
|
||||||
|
return whenFound.apply(requireParam(context, paramName));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected <T> Optional<T> getParam(CallContext context, String paramName, Function<JsonNode, T> whenFound) {
|
||||||
|
var node = getParam(context, paramName);
|
||||||
|
return node.map(whenFound);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Optional<JsonNode> getParam(CallContext context, String paramName) {
|
||||||
|
var params = context.getParams();
|
||||||
|
if (params.has(paramName)) {
|
||||||
|
return Optional.of(params.get(paramName));
|
||||||
|
}
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,184 @@
|
||||||
|
package ru.kirillius.XCP.RPC.JSONRPC;
|
||||||
|
|
||||||
|
import jakarta.servlet.ServletException;
|
||||||
|
import jakarta.servlet.http.HttpServlet;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.SneakyThrows;
|
||||||
|
import ru.kirillius.XCP.Commons.Context;
|
||||||
|
import ru.kirillius.XCP.Logging.Logger;
|
||||||
|
import ru.kirillius.XCP.Persistence.Entities.User;
|
||||||
|
import ru.kirillius.XCP.Persistence.Repositories.ApiTokenRepository;
|
||||||
|
import ru.kirillius.XCP.Persistence.Repositories.UserRepository;
|
||||||
|
import ru.kirillius.XCP.Services.RepositoryService;
|
||||||
|
import ru.kirillius.XCP.Security.UserRole;
|
||||||
|
import tools.jackson.databind.ObjectMapper;
|
||||||
|
import tools.jackson.databind.node.ObjectNode;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.lang.reflect.Modifier;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
public class JsonRpcServlet extends HttpServlet {
|
||||||
|
private final Context context;
|
||||||
|
private final ObjectMapper mapper;
|
||||||
|
private final Logger log;
|
||||||
|
|
||||||
|
public JsonRpcServlet(Context context) {
|
||||||
|
this.context = context;
|
||||||
|
var repositoryService = context.getService(RepositoryService.class);
|
||||||
|
mapper = repositoryService.getMapper();
|
||||||
|
userRepository = repositoryService.getRepository(UserRepository.class);
|
||||||
|
log = context.getLoggingSystem().createLogger(JsonRpcServlet.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SafeVarargs
|
||||||
|
@SneakyThrows
|
||||||
|
public final void registerRpcService(Class<? extends JsonRpcService>... serviceClasses) {
|
||||||
|
|
||||||
|
for (var cls : serviceClasses) {
|
||||||
|
var constructor = cls.getConstructor();
|
||||||
|
var instance = (JsonRpcService) constructor.newInstance();
|
||||||
|
var methodBindings = new ConcurrentHashMap<String, MethodBinding>();
|
||||||
|
|
||||||
|
for (var method : cls.getDeclaredMethods()) {
|
||||||
|
if (Modifier.isPublic(method.getModifiers()) && method.isAnnotationPresent(JsonRpcMethod.class)) {
|
||||||
|
methodBindings.put(method.getName(), new MethodBinding(instance, method, method.getAnnotation(JsonRpcMethod.class).accessLevel()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bindings.put(cls.getSimpleName(), methodBindings);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Getter
|
||||||
|
private static final class MethodBinding {
|
||||||
|
private final JsonRpcService service;
|
||||||
|
private final Method method;
|
||||||
|
private UserRole accessLevel;
|
||||||
|
}
|
||||||
|
|
||||||
|
private final Map<String, Map<String, MethodBinding>> bindings = new ConcurrentHashMap<>();
|
||||||
|
public final static String SESSION_ATTR = "ActiveUser";
|
||||||
|
|
||||||
|
private final UserRepository userRepository;
|
||||||
|
|
||||||
|
private User authorize(HttpServletRequest httpServletRequest) {
|
||||||
|
User user = null;
|
||||||
|
|
||||||
|
var session = httpServletRequest.getSession(false);
|
||||||
|
|
||||||
|
//try auth by session
|
||||||
|
if (session != null) {
|
||||||
|
user = (User) session.getAttribute(SESSION_ATTR);
|
||||||
|
}
|
||||||
|
|
||||||
|
//try auth by token
|
||||||
|
if (user == null) {
|
||||||
|
var authHeader = httpServletRequest.getHeader("X-Auth-Token");
|
||||||
|
if (authHeader != null) {
|
||||||
|
var tokenRepository = context.getService(RepositoryService.class).getRepository(ApiTokenRepository.class);
|
||||||
|
var token = tokenRepository.get(UUID.fromString(authHeader));
|
||||||
|
if (token != null && token.getExpirationDate() != null && token.isExpired()) {
|
||||||
|
tokenRepository.remove(token);
|
||||||
|
} else if (token != null) {
|
||||||
|
user = token.getUser();
|
||||||
|
if (session == null) {
|
||||||
|
session = httpServletRequest.getSession();
|
||||||
|
}
|
||||||
|
session.setAttribute(SESSION_ATTR, user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (user == null) {
|
||||||
|
user = userRepository.create();
|
||||||
|
user.setName("Guest");
|
||||||
|
user.setRole(UserRole.Guest);
|
||||||
|
}
|
||||||
|
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doPost(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws ServletException, IOException {
|
||||||
|
JsonRpcRequest jsonRpcRequest = null;
|
||||||
|
JsonRpcResponse jsonRpcResponse = null;
|
||||||
|
|
||||||
|
httpServletResponse.setStatus(HttpServletResponse.SC_OK);
|
||||||
|
httpServletResponse.setContentType("application/json;charset=UTF-8");
|
||||||
|
httpServletResponse.setHeader("Access-Control-Allow-Origin", "*");
|
||||||
|
httpServletResponse.setCharacterEncoding("UTF-8");
|
||||||
|
|
||||||
|
var user = authorize(httpServletRequest);
|
||||||
|
|
||||||
|
try {
|
||||||
|
jsonRpcRequest = mapper.readValue(httpServletRequest.getReader(), JsonRpcRequest.class);
|
||||||
|
var callContext = new CallContextImpl(httpServletRequest, httpServletResponse, context, jsonRpcRequest.getParams(), user);
|
||||||
|
jsonRpcResponse = processRequest(jsonRpcRequest, callContext);
|
||||||
|
} catch (Exception e) {
|
||||||
|
httpServletResponse.setStatus(HttpServletResponse.SC_BAD_REQUEST);
|
||||||
|
jsonRpcResponse = createErrorResponse(jsonRpcRequest, JsonRpcErrorCode.PARSE_ERROR);
|
||||||
|
log.error("Failed to parse JRPC request", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
mapper.writeValue(httpServletResponse.getWriter(), jsonRpcResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
private JsonRpcResponse createErrorResponse(JsonRpcRequest request, JsonRpcErrorCode code) {
|
||||||
|
return JsonRpcResponse.builder()
|
||||||
|
.id(request == null ? -1L : request.getId())
|
||||||
|
.error(new JsonRpcError(code))
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private JsonRpcResponse processRequest(JsonRpcRequest request, CallContext callContext) {
|
||||||
|
var split = request.getMethod().split(Pattern.quote("."), 2);
|
||||||
|
if (split.length != 2) {
|
||||||
|
return createErrorResponse(request, JsonRpcErrorCode.METHOD_NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
var bindingMap = bindings.get(split[0]);
|
||||||
|
if (bindingMap == null) {
|
||||||
|
return createErrorResponse(request, JsonRpcErrorCode.METHOD_NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
var methodBinding = bindingMap.get(split[1]);
|
||||||
|
if (methodBinding == null) {
|
||||||
|
return createErrorResponse(request, JsonRpcErrorCode.METHOD_NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (callContext.getCurrentUser().getRole().getLevel() < methodBinding.getAccessLevel().getLevel()) {
|
||||||
|
callContext.getResponse().setStatus(HttpServletResponse.SC_FORBIDDEN);
|
||||||
|
return createErrorResponse(request, JsonRpcErrorCode.INTERNAL_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
var result = methodBinding.getMethod().invoke(methodBinding.getService(), callContext);
|
||||||
|
return JsonRpcResponse.builder().id(request.getId()).result(result).build();
|
||||||
|
} catch (IllegalAccessException | InvocationTargetException e) {
|
||||||
|
log.error("Failed to process JSON-RPC request: " + mapper.valueToTree(request).toString(), e);
|
||||||
|
return createErrorResponse(request, JsonRpcErrorCode.INVOCATION_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@AllArgsConstructor
|
||||||
|
private static final class CallContextImpl implements CallContext {
|
||||||
|
private final HttpServletRequest request;
|
||||||
|
private final HttpServletResponse response;
|
||||||
|
private final Context context;
|
||||||
|
private final ObjectNode params;
|
||||||
|
private final User currentUser;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,120 @@
|
||||||
|
package ru.kirillius.XCP.RPC.Services;
|
||||||
|
|
||||||
|
import ru.kirillius.XCP.Persistence.Repositories.ApiTokenRepository;
|
||||||
|
import ru.kirillius.XCP.Persistence.Repositories.UserRepository;
|
||||||
|
import ru.kirillius.XCP.Services.RepositoryService;
|
||||||
|
import ru.kirillius.XCP.RPC.JSONRPC.CallContext;
|
||||||
|
import ru.kirillius.XCP.RPC.JSONRPC.JsonRpcMethod;
|
||||||
|
import ru.kirillius.XCP.RPC.JSONRPC.JsonRpcService;
|
||||||
|
import ru.kirillius.XCP.RPC.JSONRPC.JsonRpcServlet;
|
||||||
|
import ru.kirillius.XCP.Security.UserRole;
|
||||||
|
import tools.jackson.databind.JsonNode;
|
||||||
|
import tools.jackson.databind.node.ArrayNode;
|
||||||
|
import tools.jackson.databind.node.JsonNodeFactory;
|
||||||
|
import tools.jackson.databind.node.ObjectNode;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.time.temporal.ChronoUnit;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public class Auth extends JsonRpcService {
|
||||||
|
|
||||||
|
@JsonRpcMethod(accessLevel = UserRole.Guest)
|
||||||
|
public boolean authenticateByPassword(CallContext call) {
|
||||||
|
var login = requireParam(call, "login", JsonNode::asString);
|
||||||
|
var passwd = requireParam(call, "password", JsonNode::asString);
|
||||||
|
var userRepository = call.getContext().getService(RepositoryService.class).getRepository(UserRepository.class);
|
||||||
|
|
||||||
|
var user = userRepository.getByLogin(login);
|
||||||
|
if (user == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!user.verifyPassword(passwd)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var session = call.getRequest().getSession();
|
||||||
|
session.setAttribute(JsonRpcServlet.SESSION_ATTR, user);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@JsonRpcMethod(accessLevel = UserRole.User)
|
||||||
|
public ArrayNode getTokens(CallContext call) throws IOException {
|
||||||
|
var tokenRepository = call.getContext().getService(RepositoryService.class).getRepository(ApiTokenRepository.class);
|
||||||
|
var tokens = JsonNodeFactory.instance.arrayNode();
|
||||||
|
try (var handler = tokenRepository.getByUser(call.getCurrentUser())) {
|
||||||
|
handler.get().map(token -> {
|
||||||
|
var json = tokenRepository.serialize(token);
|
||||||
|
json.remove("uuid");
|
||||||
|
var uuid = token.getUuid().toString();
|
||||||
|
json.put("token", uuid.substring(0, 4) + "..." + uuid.substring(uuid.length() - 4));
|
||||||
|
return json;
|
||||||
|
}).forEach(tokens::add);
|
||||||
|
}
|
||||||
|
return tokens;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonRpcMethod(accessLevel = UserRole.User)
|
||||||
|
public ObjectNode generateToken(CallContext call) {
|
||||||
|
var repositoryService = call.getContext().getService(RepositoryService.class);
|
||||||
|
var tokenRepository = repositoryService.getRepository(ApiTokenRepository.class);
|
||||||
|
var params = call.getParams();
|
||||||
|
var permanent = params.has("permanent") && params.get("permanent").asBoolean();
|
||||||
|
var token = tokenRepository.create();
|
||||||
|
var name = params.has("name") ? params.get("name").asString() : null;
|
||||||
|
|
||||||
|
if (name == null) {
|
||||||
|
var header = call.getRequest().getHeader("User-Agent");
|
||||||
|
name = header != null ? header : "Unnamed token";
|
||||||
|
}
|
||||||
|
|
||||||
|
token.setExpirationDate(permanent ? null : Date.from(Instant.now().plus(30, ChronoUnit.DAYS)));
|
||||||
|
token.setName(name);
|
||||||
|
|
||||||
|
tokenRepository.save(token);
|
||||||
|
|
||||||
|
return tokenRepository.serialize(token);
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonRpcMethod(accessLevel = UserRole.Guest)
|
||||||
|
public boolean authenticateByToken(CallContext call) throws IllegalAccessException {
|
||||||
|
var repositoryService = call.getContext().getService(RepositoryService.class);
|
||||||
|
var tokenRepository = repositoryService.getRepository(ApiTokenRepository.class);
|
||||||
|
var token = tokenRepository.get(UUID.fromString(
|
||||||
|
requireParam(call, "token", JsonNode::asString)
|
||||||
|
));
|
||||||
|
if (token == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (token.isExpired()) {
|
||||||
|
tokenRepository.remove(token);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var user = token.getUser();
|
||||||
|
if (user == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var session = call.getRequest().getSession();
|
||||||
|
session.setAttribute(JsonRpcServlet.SESSION_ATTR, user);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonRpcMethod(accessLevel = UserRole.User)
|
||||||
|
public boolean logout(CallContext call) {
|
||||||
|
ru.kirillius.XCP.Persistence.Entities.User user = null;
|
||||||
|
|
||||||
|
var session = call.getRequest().getSession(false);
|
||||||
|
if (session != null) {
|
||||||
|
user = (ru.kirillius.XCP.Persistence.Entities.User) session.getAttribute(JsonRpcServlet.SESSION_ATTR);
|
||||||
|
session.setAttribute(JsonRpcServlet.SESSION_ATTR, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
return session != null && user != null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,63 @@
|
||||||
|
package ru.kirillius.XCP.RPC.Services;
|
||||||
|
|
||||||
|
import ru.kirillius.XCP.Persistence.Repositories.UserRepository;
|
||||||
|
import ru.kirillius.XCP.Services.RepositoryService;
|
||||||
|
import ru.kirillius.XCP.RPC.JSONRPC.CallContext;
|
||||||
|
import ru.kirillius.XCP.RPC.JSONRPC.JsonRpcMethod;
|
||||||
|
import ru.kirillius.XCP.RPC.JSONRPC.JsonRpcService;
|
||||||
|
import ru.kirillius.XCP.Security.UserRole;
|
||||||
|
import tools.jackson.databind.JsonNode;
|
||||||
|
import tools.jackson.databind.node.ObjectNode;
|
||||||
|
|
||||||
|
public class Profile extends JsonRpcService {
|
||||||
|
|
||||||
|
@JsonRpcMethod(accessLevel = UserRole.User)
|
||||||
|
public boolean save(CallContext call) {
|
||||||
|
var userRepository = call.getContext().getService(RepositoryService.class).getRepository(UserRepository.class);
|
||||||
|
var user = call.getCurrentUser();
|
||||||
|
|
||||||
|
var login = requireParam(call, "login", JsonNode::asString);
|
||||||
|
var name = requireParam(call, "name", JsonNode::asString);
|
||||||
|
var passwordOptional = getParam(call, "password", JsonNode::asString);
|
||||||
|
var values = requireParam(call, "values", n -> (ObjectNode) n);
|
||||||
|
|
||||||
|
if (!user.getLogin().equals(login) && userRepository.getByLogin(login) != null) {
|
||||||
|
throw new RuntimeException("Login is already in use");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (login.isBlank()) {
|
||||||
|
throw new RuntimeException("Login is blank");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (name.isBlank()) {
|
||||||
|
throw new RuntimeException("Name is blank");
|
||||||
|
}
|
||||||
|
|
||||||
|
user.setLogin(login);
|
||||||
|
user.setName(name);
|
||||||
|
|
||||||
|
if (passwordOptional.isPresent()) {
|
||||||
|
var password = passwordOptional.get();
|
||||||
|
if (password.isBlank()) {
|
||||||
|
throw new RuntimeException("Password is blank");
|
||||||
|
}
|
||||||
|
user.setPassword(password);
|
||||||
|
}
|
||||||
|
|
||||||
|
user.setValues(values);
|
||||||
|
userRepository.save(user);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonRpcMethod(accessLevel = UserRole.User)
|
||||||
|
public ObjectNode get(CallContext call) {
|
||||||
|
var userRepository = call.getContext().getService(RepositoryService.class).getRepository(UserRepository.class);
|
||||||
|
var user = call.getCurrentUser();
|
||||||
|
|
||||||
|
var json = userRepository.serialize(user);
|
||||||
|
json.remove("passwordHash");
|
||||||
|
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
package ru.kirillius.XCP.RPC.Services;
|
||||||
|
|
||||||
|
import ru.kirillius.XCP.Persistence.Repositories.UserRepository;
|
||||||
|
import ru.kirillius.XCP.Services.RepositoryService;
|
||||||
|
import ru.kirillius.XCP.RPC.JSONRPC.CallContext;
|
||||||
|
import ru.kirillius.XCP.RPC.JSONRPC.JsonRpcMethod;
|
||||||
|
import ru.kirillius.XCP.RPC.JSONRPC.JsonRpcService;
|
||||||
|
import ru.kirillius.XCP.Security.UserRole;
|
||||||
|
import tools.jackson.databind.JsonNode;
|
||||||
|
import tools.jackson.databind.node.ArrayNode;
|
||||||
|
import tools.jackson.databind.node.ObjectNode;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public class UserManagement extends JsonRpcService {
|
||||||
|
@JsonRpcMethod(accessLevel = UserRole.Admin)
|
||||||
|
public ArrayNode getAll(CallContext call) throws IOException {
|
||||||
|
var userRepository = call.getContext().getService(RepositoryService.class).getRepository(UserRepository.class);
|
||||||
|
try (var handler = userRepository.getAll()) {
|
||||||
|
return userRepository.serialize(handler.get().toList());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonRpcMethod(accessLevel = UserRole.Admin)
|
||||||
|
public void save(CallContext call) {
|
||||||
|
var userRepository = call.getContext().getService(RepositoryService.class).getRepository(UserRepository.class);
|
||||||
|
var user = userRepository.deserialize((ObjectNode) requireParam(call, "user"));
|
||||||
|
userRepository.save(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonRpcMethod(accessLevel = UserRole.Admin)
|
||||||
|
public ObjectNode getById(CallContext call) {
|
||||||
|
var userId = requireParam(call, "id", JsonNode::asLong);
|
||||||
|
var userRepository = call.getContext().getService(RepositoryService.class).getRepository(UserRepository.class);
|
||||||
|
return userRepository.serialize(userRepository.get(userId));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
<?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>web-server</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>rpc</artifactId>
|
||||||
|
<version>1.0.0.0</version>
|
||||||
|
<scope>compile</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
</project>
|
||||||
|
|
@ -0,0 +1,72 @@
|
||||||
|
package ru.kirillius.XCP.web;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.ee10.servlet.DefaultServlet;
|
||||||
|
import org.eclipse.jetty.ee10.servlet.ServletContextHandler;
|
||||||
|
import org.eclipse.jetty.ee10.servlet.ServletHolder;
|
||||||
|
import org.eclipse.jetty.server.Server;
|
||||||
|
import org.eclipse.jetty.util.resource.ResourceFactory;
|
||||||
|
import ru.kirillius.XCP.Commons.Context;
|
||||||
|
import ru.kirillius.XCP.RPC.JSONRPC.JsonRpcServlet;
|
||||||
|
import ru.kirillius.XCP.RPC.Services.Auth;
|
||||||
|
import ru.kirillius.XCP.RPC.Services.Profile;
|
||||||
|
import ru.kirillius.XCP.RPC.Services.UserManagement;
|
||||||
|
import ru.kirillius.XCP.Services.ServiceLoadPriority;
|
||||||
|
import ru.kirillius.XCP.Services.WebService;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
@ServiceLoadPriority(100)
|
||||||
|
public class WebServiceImpl implements WebService {
|
||||||
|
private Server server;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initialize(Context context) {
|
||||||
|
if (server != null) {
|
||||||
|
throw new IllegalStateException("Server already started");
|
||||||
|
}
|
||||||
|
var jsonRpc = new JsonRpcServlet(context);
|
||||||
|
jsonRpc.registerRpcService(
|
||||||
|
UserManagement.class,
|
||||||
|
Auth.class,
|
||||||
|
Profile.class
|
||||||
|
);
|
||||||
|
var config = context.getConfig();
|
||||||
|
server = new Server(new InetSocketAddress(config.getHost(), config.getHttpPort()));
|
||||||
|
var servletContext = new ServletContextHandler("/", ServletContextHandler.SESSIONS);
|
||||||
|
servletContext.addServlet(new ServletHolder(jsonRpc), "/api/*");
|
||||||
|
servletContext.addServlet(DefaultServlet.class, "/");
|
||||||
|
var resourceFactory = ResourceFactory.root();
|
||||||
|
|
||||||
|
try {
|
||||||
|
var resource = resourceFactory.newResource(Objects.requireNonNull(getClass().getClassLoader().getResource("htdocs/")).toURI());
|
||||||
|
servletContext.setBaseResource(resource);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("Unable to determine path to htdocs directory", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
server.setHandler(servletContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
if (server != null) {
|
||||||
|
try {
|
||||||
|
server.stop();
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new IOException("Failed to stop web server", e);
|
||||||
|
} finally {
|
||||||
|
server = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void join() {
|
||||||
|
try {
|
||||||
|
server.join();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Title</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Loading…
Reference in New Issue