Добавил ApiToken entity для авторизации, контекст для RPC, реализовал ConfigManager, логгирование и RPC
This commit is contained in:
parent
0d202b5574
commit
898a170d7b
|
|
@ -17,11 +17,5 @@
|
|||
<artifactId>java-events</artifactId>
|
||||
<version>1.1.0.0</version>
|
||||
</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>
|
||||
</project>
|
||||
|
|
@ -2,7 +2,8 @@ package ru.kirillius.XCP.Commons;
|
|||
|
||||
import java.io.File;
|
||||
|
||||
public interface Config {
|
||||
public interface
|
||||
Config {
|
||||
|
||||
File getLoadedConfigFile();
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +1,14 @@
|
|||
package ru.kirillius.XCP.Commons;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
public interface ConfigManager {
|
||||
File getConfigFile();
|
||||
|
||||
boolean isExist();
|
||||
|
||||
Config load();
|
||||
Config load() throws IOException;
|
||||
|
||||
Config create();
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
package ru.kirillius.XCP.Commons;
|
||||
|
||||
import ru.kirillius.XCP.Logging.LoggingSystem;
|
||||
import ru.kirillius.XCP.Security.SecurityManager;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface Context {
|
||||
Config getConfig();
|
||||
|
||||
|
|
@ -12,4 +15,8 @@ public interface Context {
|
|||
void shutdown();
|
||||
|
||||
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> {
|
||||
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);
|
||||
|
||||
EventBindings<E> events();
|
||||
|
||||
StreamHandler<E> loadAll();
|
||||
StreamHandler<E> getAll();
|
||||
|
||||
long getCount();
|
||||
|
||||
void store(E entity);
|
||||
void save(E entity);
|
||||
|
||||
void store(Collection<E> entities);
|
||||
void save(Collection<E> entities);
|
||||
|
||||
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.Persistence.PersistenceEntity;
|
||||
import ru.kirillius.XCP.Persistence.Repository;
|
||||
import tools.jackson.databind.ObjectMapper;
|
||||
|
||||
public interface RepositoryService extends Service {
|
||||
<E extends PersistenceEntity> Repository<E> getRepositoryForEntity(Class<E> entityType);
|
||||
|
|
@ -8,8 +11,11 @@ public interface RepositoryService extends Service {
|
|||
<R extends Repository<?>> R getRepository(Class<R> repositoryType);
|
||||
|
||||
Class<? extends PersistenceEntity> getEntityBaseType(Class<? extends PersistenceEntity> entityClass);
|
||||
|
||||
Class<? extends Repository<?>> getRepositoryBaseType(Class<? extends Repository<?>> repositoryClass);
|
||||
|
||||
Class<? extends PersistenceEntity> getRepositoryEntityType(Class<? extends Repository<?>> repositoryClass);
|
||||
|
||||
ObjectMapper getMapper();
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package ru.kirillius.XCP.Services;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.TYPE)
|
||||
public @interface ServiceLoadPriority {
|
||||
int value() default 1000;
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
package ru.kirillius.XCP.Services;
|
||||
|
||||
import ru.kirillius.XCP.Commons.Service;
|
||||
|
||||
public interface WebService extends Service {
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>ru.kirillius</groupId>
|
||||
<artifactId>XCP</artifactId>
|
||||
<version>1.0.0.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>app</artifactId>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>21</maven.compiler.source>
|
||||
<maven.compiler.target>21</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>ru.kirillius</groupId>
|
||||
<artifactId>api</artifactId>
|
||||
<version>1.0.0.0</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>ru.kirillius</groupId>
|
||||
<artifactId>core</artifactId>
|
||||
<version>1.0.0.0</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>ru.kirillius</groupId>
|
||||
<artifactId>logging</artifactId>
|
||||
<version>1.0.0.0</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>ru.kirillius</groupId>
|
||||
<artifactId>database</artifactId>
|
||||
<version>1.0.0.0</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>ru.kirillius</groupId>
|
||||
<artifactId>web-server</artifactId>
|
||||
<version>1.0.0.0</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
|
|
@ -0,0 +1,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>
|
||||
<version>7.1.10.Final</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>tools.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
<version>3.0.3</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>ru.kirillius</groupId>
|
||||
<artifactId>api</artifactId>
|
||||
|
|
|
|||
|
|
@ -37,14 +37,14 @@ public abstract class AbstractRepository<E extends PersistenceEntity> implements
|
|||
public E create() {
|
||||
try {
|
||||
var constructor = entityImplementationClass.getConstructor();
|
||||
var instance= constructor.newInstance();
|
||||
if(instance instanceof ContextReferencedEntity referencedEntity) {
|
||||
var instance = constructor.newInstance();
|
||||
if (instance instanceof ContextReferencedEntity referencedEntity) {
|
||||
referencedEntity.setContext(repositoryService.getContext());
|
||||
}
|
||||
return instance;
|
||||
} catch (NoSuchMethodException | InvocationTargetException | InstantiationException |
|
||||
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
|
||||
public E load(UUID uuid) {
|
||||
public E get(UUID uuid) {
|
||||
try (var query = buildQueryParametrized("where uuid = ?1", uuid)) {
|
||||
return query.get().findFirst().orElse(null);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
throw new PersistenceException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public E load(long id) {
|
||||
public E get(long id) {
|
||||
try (var query = buildQueryParametrized("where id = ?1", id)) {
|
||||
return query.get().findFirst().orElse(null);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
throw new PersistenceException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public StreamHandler<E> load(Collection<Long> ids) {
|
||||
public StreamHandler<E> get(Collection<Long> ids) {
|
||||
if (ids != null && !ids.isEmpty()) {
|
||||
return buildQueryParametrized("where id IN (" + joinIdentifiers(ids) + ")");
|
||||
} else {
|
||||
|
|
@ -86,7 +86,7 @@ public abstract class AbstractRepository<E extends PersistenceEntity> implements
|
|||
}
|
||||
|
||||
@Override
|
||||
public StreamHandler<E> loadAll() {
|
||||
public StreamHandler<E> getAll() {
|
||||
return buildQueryParametrized("order by id");
|
||||
}
|
||||
|
||||
|
|
@ -104,7 +104,7 @@ public abstract class AbstractRepository<E extends PersistenceEntity> implements
|
|||
}
|
||||
|
||||
@Override
|
||||
public void store(E entity) {
|
||||
public void save(E entity) {
|
||||
var session = repositoryService.openSession();
|
||||
var transaction = session.beginTransaction();
|
||||
try {
|
||||
|
|
@ -116,21 +116,21 @@ public abstract class AbstractRepository<E extends PersistenceEntity> implements
|
|||
transaction.commit();
|
||||
} catch (Exception e) {
|
||||
transaction.rollback();
|
||||
throw new RuntimeException(e);
|
||||
throw new PersistenceException("Unable to save entity", e);
|
||||
}
|
||||
|
||||
try {
|
||||
eventBindings.entityStored().invoke(entity);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
throw new PersistenceException("Something went wrong on entity save event", e);
|
||||
} finally {
|
||||
session.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void store(Collection<E> entities) {
|
||||
entities.forEach(this::store);
|
||||
public void save(Collection<E> entities) {
|
||||
entities.forEach(this::save);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -142,13 +142,13 @@ public abstract class AbstractRepository<E extends PersistenceEntity> implements
|
|||
transaction.commit();
|
||||
} catch (Exception e) {
|
||||
transaction.rollback();
|
||||
throw new RuntimeException(e);
|
||||
throw new PersistenceException("Unable to remove entity", e);
|
||||
}
|
||||
|
||||
try {
|
||||
eventBindings.entityRemoved().invoke(entity);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
throw new PersistenceException("Something went wrong on entity deletion", e);
|
||||
} finally {
|
||||
session.close();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,9 +27,9 @@ public class EntityReferenceDeserializer extends StdDeserializer<EntityReference
|
|||
@SuppressWarnings("unchecked")
|
||||
var repository = repositoryService.getRepositoryForEntity((Class<? extends PersistenceEntity>) Class.forName(type));
|
||||
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) {
|
||||
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
|
||||
public void store(Group entity) {
|
||||
public void save(Group entity) {
|
||||
if (entity != null && entity.getParent() == null) {
|
||||
var root = getRoot();
|
||||
if (root != null && !root.equals(entity)) {
|
||||
throw new IllegalStateException("Root group already exists");
|
||||
}
|
||||
}
|
||||
super.store(entity);
|
||||
super.save(entity);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -30,11 +30,11 @@ public class InputRepositoryImpl extends AbstractNodeRepository<Input> implement
|
|||
}
|
||||
|
||||
@Override
|
||||
public void store(Input entity) {
|
||||
public void save(Input entity) {
|
||||
if (entity != null && entity.getParent() == null) {
|
||||
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
|
||||
public void store(Output entity) {
|
||||
public void save(Output entity) {
|
||||
if (entity != null && entity.getParent() == null) {
|
||||
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 {
|
||||
var tag = create();
|
||||
tag.setName(name);
|
||||
store(tag);
|
||||
save(tag);
|
||||
return tag;
|
||||
}
|
||||
|
||||
|
|
@ -58,7 +58,7 @@ public class TagRepositoryImpl extends AbstractRepository<Tag> implements TagRep
|
|||
if(!foundNames.contains(tagName)) {
|
||||
var tag = create();
|
||||
tag.setName(tagName);
|
||||
store(tag);
|
||||
save(tag);
|
||||
tags.add(tag);
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ public class UserRepositoryImpl extends AbstractRepository<User> implements User
|
|||
@UuidGenerator
|
||||
private UUID uuid;
|
||||
|
||||
@Column(nullable = false)
|
||||
@Column(nullable = false, unique = true)
|
||||
@JsonProperty
|
||||
private String login = "";
|
||||
|
||||
|
|
|
|||
|
|
@ -7,15 +7,20 @@ import org.hibernate.SessionFactory;
|
|||
import org.hibernate.cfg.Configuration;
|
||||
import org.hibernate.type.Type;
|
||||
import ru.kirillius.XCP.Commons.Context;
|
||||
import ru.kirillius.XCP.Persistence.Repositories.*;
|
||||
import ru.kirillius.XCP.Services.RepositoryService;
|
||||
import ru.kirillius.XCP.Services.ServiceLoadPriority;
|
||||
import tools.jackson.databind.ObjectMapper;
|
||||
import tools.jackson.databind.json.JsonMapper;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
@ServiceLoadPriority(0)
|
||||
public final class RepositoryServiceImpl implements RepositoryService {
|
||||
@Getter
|
||||
private ObjectMapper mapper = new ObjectMapper();
|
||||
|
|
@ -51,6 +56,20 @@ public final class RepositoryServiceImpl implements RepositoryService {
|
|||
registerClasses();
|
||||
}
|
||||
|
||||
public RepositoryServiceImpl() {
|
||||
managedRepositoryClasses = List.of(
|
||||
ApiTokenRepositoryImpl.class,
|
||||
GroupRepositoryImpl.class,
|
||||
InputRepositoryImpl.class,
|
||||
OutputRepositoryImpl.class,
|
||||
TagRepositoryImpl.class,
|
||||
UserRepositoryImpl.class
|
||||
);
|
||||
configuration = new Configuration();
|
||||
configuration.configure();
|
||||
registerClasses();
|
||||
}
|
||||
|
||||
private void registerClasses() {
|
||||
managedRepositoryClasses.forEach(aClass -> {
|
||||
var implementation = aClass.getAnnotation(EntityImplementation.class);
|
||||
|
|
@ -81,7 +100,9 @@ public final class RepositoryServiceImpl implements RepositoryService {
|
|||
public void close() {
|
||||
repositoryBindings.clear();
|
||||
entityBindings.clear();
|
||||
sessionFactory.close();
|
||||
if (sessionFactory != null) {
|
||||
sessionFactory.close();
|
||||
}
|
||||
}
|
||||
|
||||
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 root = groupRepository.create();
|
||||
groupRepository.store(root);
|
||||
groupRepository.save(root);
|
||||
|
||||
var firstChildGroup = groupRepository.create();
|
||||
firstChildGroup.setParent(root);
|
||||
groupRepository.store(firstChildGroup);
|
||||
groupRepository.save(firstChildGroup);
|
||||
|
||||
var secondChildGroup = groupRepository.create();
|
||||
secondChildGroup.setParent(root);
|
||||
groupRepository.store(secondChildGroup);
|
||||
groupRepository.save(secondChildGroup);
|
||||
|
||||
var firstChild = repository.create();
|
||||
modify(firstChild, service);
|
||||
firstChild.setParent(firstChildGroup);
|
||||
repository.store(firstChild);
|
||||
repository.save(firstChild);
|
||||
|
||||
var secondChild = repository.create();
|
||||
modify(secondChild, service);
|
||||
secondChild.setParent(secondChildGroup);
|
||||
repository.store(secondChild);
|
||||
repository.save(secondChild);
|
||||
|
||||
try (var handler = repository.getByGroup(firstChildGroup)) {
|
||||
var found = handler.get().findFirst().orElse(null);
|
||||
|
|
@ -67,7 +67,7 @@ abstract class GenericNodeRepositoryTest<E extends NodeEntity, R extends Abstrac
|
|||
"bar"
|
||||
)));
|
||||
|
||||
repository.store(first);
|
||||
repository.save(first);
|
||||
|
||||
var second = repository.create();
|
||||
modify(second, service);
|
||||
|
|
@ -76,7 +76,7 @@ abstract class GenericNodeRepositoryTest<E extends NodeEntity, R extends Abstrac
|
|||
"foo",
|
||||
"third"
|
||||
)));
|
||||
repository.store(second);
|
||||
repository.save(second);
|
||||
|
||||
var third = repository.create();
|
||||
modify(third, service);
|
||||
|
|
@ -85,7 +85,7 @@ abstract class GenericNodeRepositoryTest<E extends NodeEntity, R extends Abstrac
|
|||
"third",
|
||||
"yabba"
|
||||
)));
|
||||
repository.store(third);
|
||||
repository.save(third);
|
||||
|
||||
try (var handler = repository.getByAllTags(tagRepository.getByNamesOrCreate(List.of("first")))) {
|
||||
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 ru.kirillius.XCP.Persistence.AbstractRepository;
|
||||
import ru.kirillius.XCP.Persistence.PersistenceEntity;
|
||||
import ru.kirillius.XCP.Persistence.RepositoryService;
|
||||
import ru.kirillius.XCP.Services.RepositoryService;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
|
|
@ -48,27 +48,27 @@ abstract class GenericRepositoryTest<E extends PersistenceEntity, R extends Abst
|
|||
}
|
||||
|
||||
@Test
|
||||
void testStore() throws IOException {
|
||||
void testSave() throws IOException {
|
||||
try (var service = spawnRepositoryService()) {
|
||||
var repository = service.getRepositoryForEntity(entityClass);
|
||||
var entity = repository.create();
|
||||
modify(entity, service);
|
||||
repository.store(entity);
|
||||
repository.save(entity);
|
||||
assertThat(entity.getId()).isNotZero();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLoad() throws IOException {
|
||||
void testGet() throws IOException {
|
||||
try (var service = spawnRepositoryService()) {
|
||||
var repository = service.getRepositoryForEntity(entityClass);
|
||||
var entity = repository.create();
|
||||
modify(entity, service);
|
||||
repository.store(entity);
|
||||
var loaded = repository.load(entity.getId());
|
||||
repository.save(entity);
|
||||
var loaded = repository.get(entity.getId());
|
||||
assertThat(loaded).isNotNull().isEqualTo(entity);
|
||||
|
||||
var loadedByUUID = repository.load(entity.getUuid());
|
||||
var loadedByUUID = repository.get(entity.getUuid());
|
||||
assertThat(loadedByUUID).isNotNull().isEqualTo(entity);
|
||||
}
|
||||
}
|
||||
|
|
@ -79,10 +79,10 @@ abstract class GenericRepositoryTest<E extends PersistenceEntity, R extends Abst
|
|||
var repository = service.getRepositoryForEntity(entityClass);
|
||||
var entity = repository.create();
|
||||
modify(entity, service);
|
||||
repository.store(entity);
|
||||
repository.save(entity);
|
||||
modify(entity, service);
|
||||
repository.store(entity);
|
||||
var loaded = repository.load(entity.getId());
|
||||
repository.save(entity);
|
||||
var loaded = repository.get(entity.getId());
|
||||
assertThat(loaded).isNotNull().isEqualTo(entity);
|
||||
}
|
||||
}
|
||||
|
|
@ -93,7 +93,7 @@ abstract class GenericRepositoryTest<E extends PersistenceEntity, R extends Abst
|
|||
var repository = service.getRepositoryForEntity(entityClass);
|
||||
var entity = repository.create();
|
||||
modify(entity, service);
|
||||
repository.store(entity);
|
||||
repository.save(entity);
|
||||
assertThat(repository.getCount()).isEqualTo(1);
|
||||
repository.remove(entity);
|
||||
assertThat(repository.getCount()).isZero();
|
||||
|
|
@ -106,7 +106,7 @@ abstract class GenericRepositoryTest<E extends PersistenceEntity, R extends Abst
|
|||
var repository = service.getRepositoryForEntity(entityClass);
|
||||
var entity = repository.create();
|
||||
modify(entity, service);
|
||||
repository.store(entity);
|
||||
repository.save(entity);
|
||||
|
||||
var serialized = repository.serialize(entity);
|
||||
var deserialized = repository.deserialize(serialized);
|
||||
|
|
@ -115,7 +115,7 @@ abstract class GenericRepositoryTest<E extends PersistenceEntity, R extends Abst
|
|||
|
||||
var anotherEntity = repository.create();
|
||||
modify(anotherEntity, service);
|
||||
repository.store(anotherEntity);
|
||||
repository.save(anotherEntity);
|
||||
|
||||
var anotherSerialized = repository.serialize(anotherEntity);
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ package ru.kirillius.XCP.Persistence.Repositories;
|
|||
import org.junit.jupiter.api.Test;
|
||||
import ru.kirillius.XCP.Persistence.Entities.Group;
|
||||
import ru.kirillius.XCP.Persistence.Entities.Tag;
|
||||
import ru.kirillius.XCP.Persistence.RepositoryService;
|
||||
import ru.kirillius.XCP.Services.RepositoryService;
|
||||
import ru.kirillius.XCP.Persistence.TagCollectionImpl;
|
||||
|
||||
import java.io.IOException;
|
||||
|
|
@ -22,7 +22,7 @@ class GroupRepositoryImplTest extends GenericNodeRepositoryTest<Group, GroupRepo
|
|||
var tagRepository = service.getRepositoryForEntity(Tag.class);
|
||||
var tag = tagRepository.create();
|
||||
tag.setName("tag" + i);
|
||||
tagRepository.store(tag);
|
||||
tagRepository.save(tag);
|
||||
}
|
||||
return service;
|
||||
}
|
||||
|
|
@ -34,18 +34,18 @@ class GroupRepositoryImplTest extends GenericNodeRepositoryTest<Group, GroupRepo
|
|||
var repository = service.getRepository(GroupRepository.class);
|
||||
assertThat(repository.getRoot()).isNull();
|
||||
var root = repository.create();
|
||||
repository.store(root);
|
||||
repository.save(root);
|
||||
|
||||
var anotherParent = repository.create();
|
||||
anotherParent.setParent(root);
|
||||
repository.store(anotherParent);
|
||||
repository.save(anotherParent);
|
||||
|
||||
var children = new ArrayList<Group>();
|
||||
for (var i = 0; i < 20; i++) {
|
||||
var child = repository.create();
|
||||
child.setParent(anotherParent);
|
||||
children.add(child);
|
||||
repository.store(child);
|
||||
repository.save(child);
|
||||
}
|
||||
|
||||
try (var handler = repository.getChildrenOf(root)) {
|
||||
|
|
@ -65,22 +65,22 @@ class GroupRepositoryImplTest extends GenericNodeRepositoryTest<Group, GroupRepo
|
|||
var repository = service.getRepository(GroupRepository.class);
|
||||
assertThat(repository.getRoot()).isNull();
|
||||
var root = repository.create();
|
||||
repository.store(root);
|
||||
repository.save(root);
|
||||
|
||||
var anotherParent = repository.create();
|
||||
anotherParent.setParent(root);
|
||||
repository.store(anotherParent);
|
||||
repository.save(anotherParent);
|
||||
|
||||
var children = new ArrayList<Group>();
|
||||
for (var i = 0; i < 10; i++) {
|
||||
var child = repository.create();
|
||||
child.setParent(anotherParent);
|
||||
children.add(child);
|
||||
repository.store(child);
|
||||
repository.save(child);
|
||||
|
||||
var subchild = repository.create();
|
||||
subchild.setParent(child);
|
||||
repository.store(subchild);
|
||||
repository.save(subchild);
|
||||
children.add(subchild);
|
||||
}
|
||||
|
||||
|
|
@ -101,10 +101,10 @@ class GroupRepositoryImplTest extends GenericNodeRepositoryTest<Group, GroupRepo
|
|||
var repository = service.getRepository(GroupRepository.class);
|
||||
assertThat(repository.getRoot()).isNull();
|
||||
var root = repository.create();
|
||||
repository.store(root);
|
||||
repository.save(root);
|
||||
var notARoot = repository.create();
|
||||
notARoot.setParent(root);
|
||||
repository.store(notARoot);
|
||||
repository.save(notARoot);
|
||||
assertThat(repository.getRoot()).isNotNull().isEqualTo(root);
|
||||
}
|
||||
}
|
||||
|
|
@ -118,7 +118,7 @@ class GroupRepositoryImplTest extends GenericNodeRepositoryTest<Group, GroupRepo
|
|||
var secondaryRoot = repository.create();
|
||||
|
||||
try {
|
||||
repository.store(List.of(root, secondaryRoot));
|
||||
repository.save(List.of(root, secondaryRoot));
|
||||
throw new Exception("Nothing is thrown");
|
||||
} catch (Throwable e) {
|
||||
assertThat(e).isInstanceOf(IllegalStateException.class);
|
||||
|
|
@ -137,7 +137,7 @@ class GroupRepositoryImplTest extends GenericNodeRepositoryTest<Group, GroupRepo
|
|||
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()));
|
||||
} catch (IOException 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.Tag;
|
||||
import ru.kirillius.XCP.Persistence.RepositoryService;
|
||||
import ru.kirillius.XCP.Services.RepositoryService;
|
||||
import ru.kirillius.XCP.Persistence.TagCollectionImpl;
|
||||
|
||||
import java.io.IOException;
|
||||
|
|
@ -19,7 +19,7 @@ class InputRepositoryImplTest extends GenericNodeRepositoryTest<Input, InputRepo
|
|||
var tagRepository = service.getRepositoryForEntity(Tag.class);
|
||||
var tag = tagRepository.create();
|
||||
tag.setName("tag" + i);
|
||||
tagRepository.store(tag);
|
||||
tagRepository.save(tag);
|
||||
}
|
||||
return service;
|
||||
}
|
||||
|
|
@ -31,14 +31,14 @@ class InputRepositoryImplTest extends GenericNodeRepositoryTest<Input, InputRepo
|
|||
var root = groupRepository.getRoot();
|
||||
if (root == null) {
|
||||
root = groupRepository.create();
|
||||
groupRepository.store(root);
|
||||
groupRepository.save(root);
|
||||
}
|
||||
|
||||
entity.setParent(root);
|
||||
entity.setName(UUID.randomUUID().toString());
|
||||
//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()));
|
||||
} catch (IOException 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.Tag;
|
||||
import ru.kirillius.XCP.Persistence.RepositoryService;
|
||||
import ru.kirillius.XCP.Services.RepositoryService;
|
||||
import ru.kirillius.XCP.Persistence.TagCollectionImpl;
|
||||
|
||||
import java.io.IOException;
|
||||
|
|
@ -19,7 +19,7 @@ class OutputRepositoryImplTest extends GenericNodeRepositoryTest<Output, OutputR
|
|||
var tagRepository = service.getRepositoryForEntity(Tag.class);
|
||||
var tag = tagRepository.create();
|
||||
tag.setName("tag" + i);
|
||||
tagRepository.store(tag);
|
||||
tagRepository.save(tag);
|
||||
}
|
||||
return service;
|
||||
}
|
||||
|
|
@ -31,14 +31,14 @@ class OutputRepositoryImplTest extends GenericNodeRepositoryTest<Output, OutputR
|
|||
var root = groupRepository.getRoot();
|
||||
if (root == null) {
|
||||
root = groupRepository.create();
|
||||
groupRepository.store(root);
|
||||
groupRepository.save(root);
|
||||
}
|
||||
|
||||
entity.setParent(root);
|
||||
entity.setName(UUID.randomUUID().toString());
|
||||
//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()));
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ package ru.kirillius.XCP.Persistence.Repositories;
|
|||
|
||||
import org.junit.jupiter.api.Test;
|
||||
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.util.ArrayList;
|
||||
|
|
@ -16,7 +16,7 @@ class TagRepositoryImplTest extends GenericRepositoryTest<Tag, TagRepositoryImpl
|
|||
var repository = service.getRepository(TagRepository.class);
|
||||
var tag = repository.create();
|
||||
tag.setName("test");
|
||||
repository.store(tag);
|
||||
repository.save(tag);
|
||||
var found = repository.getByNameOrCreate(tag.getName());
|
||||
assertThat(found).isEqualTo(tag);
|
||||
}
|
||||
|
|
@ -32,7 +32,7 @@ class TagRepositoryImplTest extends GenericRepositoryTest<Tag, TagRepositoryImpl
|
|||
for (var i = 0; i < 10; i++) {
|
||||
var tag = repository.create();
|
||||
tag.setName("test" + i);
|
||||
repository.store(tag);
|
||||
repository.save(tag);
|
||||
tags.add(tag);
|
||||
}
|
||||
|
||||
|
|
@ -52,14 +52,14 @@ class TagRepositoryImplTest extends GenericRepositoryTest<Tag, TagRepositoryImpl
|
|||
for (var i = 0; i < 10; i++) {
|
||||
var tag = repository.create();
|
||||
tag.setName("test" + i);
|
||||
repository.store(tag);
|
||||
repository.save(tag);
|
||||
tags.add(tag);
|
||||
}
|
||||
|
||||
var emptyCollection = repository.createCollection();
|
||||
assertThat(emptyCollection).isNotNull().isEmpty();
|
||||
|
||||
try (var handler = repository.loadAll()) {
|
||||
try (var handler = repository.getAll()) {
|
||||
var collection = repository.createCollection(handler);
|
||||
assertThat(collection).containsExactlyInAnyOrderElementsOf(tags);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,8 @@ package ru.kirillius.XCP.Persistence.Repositories;
|
|||
|
||||
import org.junit.jupiter.api.Test;
|
||||
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.util.UUID;
|
||||
|
|
@ -11,6 +12,27 @@ import static org.assertj.core.api.Assertions.assertThat;
|
|||
|
||||
class UserRepositoryImplTest extends GenericRepositoryTest<User, UserRepositoryImpl> {
|
||||
|
||||
@Test
|
||||
void saveSameLogin() throws IOException {
|
||||
|
||||
try (var service = spawnRepositoryService()) {
|
||||
var repository = service.getRepository(UserRepository.class);
|
||||
|
||||
var user = repository.create();
|
||||
user.setLogin("test");
|
||||
repository.save(user);
|
||||
|
||||
user = repository.create();
|
||||
user.setLogin("test");
|
||||
try {
|
||||
repository.save(user);
|
||||
throw new AssertionError("Should have thrown an exception");
|
||||
} catch (Throwable t) {
|
||||
assertThat(t).isInstanceOf(PersistenceException.class);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void getByLogin() throws IOException {
|
||||
var correct = "correctlogin";
|
||||
|
|
@ -19,15 +41,15 @@ class UserRepositoryImplTest extends GenericRepositoryTest<User, UserRepositoryI
|
|||
for (var i = 0; i < 10; i++) {
|
||||
var user = repository.create();
|
||||
user.setLogin("incorrect" + UUID.randomUUID());
|
||||
repository.store(user);
|
||||
repository.save(user);
|
||||
}
|
||||
var correctUser = repository.create();
|
||||
correctUser.setLogin(correct);
|
||||
repository.store(correctUser);
|
||||
repository.save(correctUser);
|
||||
for (var i = 0; i < 10; i++) {
|
||||
var user = repository.create();
|
||||
user.setLogin("incorrect" + UUID.randomUUID());
|
||||
repository.store(user);
|
||||
repository.save(user);
|
||||
}
|
||||
|
||||
var loaded = repository.getByLogin(correct);
|
||||
|
|
|
|||
|
|
@ -92,7 +92,7 @@ class RepositoryServiceImplTest {
|
|||
try (var service = instantiateTestService(List.of(RepoImpl.class))) {
|
||||
var repository = service.getRepository(TestRepository.class);
|
||||
var testEntity = repository.create();
|
||||
repository.store(testEntity);
|
||||
repository.save(testEntity);
|
||||
var serialized = repository.serialize(testEntity);
|
||||
var deserialized = repository.deserialize(serialized);
|
||||
assertThat(deserialized).isEqualTo(testEntity);
|
||||
|
|
@ -100,38 +100,38 @@ class RepositoryServiceImplTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void TestEntityStore() {
|
||||
public void TestEntitySave() {
|
||||
try (var service = instantiateTestService(List.of(RepoImpl.class))) {
|
||||
var repository = service.getRepository(TestRepository.class);
|
||||
var testEntity = repository.create();
|
||||
testEntity.setTestField("new");
|
||||
repository.store(List.of(testEntity));
|
||||
repository.save(List.of(testEntity));
|
||||
assertThat(testEntity.getId()).isNotZero();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void TestEntityLoad() {
|
||||
public void TestEntityGet() {
|
||||
try (var service = instantiateTestService(List.of(RepoImpl.class))) {
|
||||
var repository = service.getRepository(TestRepository.class);
|
||||
|
||||
assertThat(repository.load(1)).isNull();
|
||||
assertThat(repository.get(1)).isNull();
|
||||
|
||||
var testEntity = repository.create();
|
||||
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(testEntity == receivedEntity).isFalse();//not exact instance
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void TestEntityLoadMultiple() throws IOException {
|
||||
public void TestEntityGetMultiple() throws IOException {
|
||||
try (var service = instantiateTestService(List.of(RepoImpl.class))) {
|
||||
var repository = service.getRepository(TestRepository.class);
|
||||
try (var bundle = repository.loadAll()) {
|
||||
try (var bundle = repository.getAll()) {
|
||||
assertThat(bundle.get().toList()).isEmpty();
|
||||
}
|
||||
|
||||
|
|
@ -143,14 +143,14 @@ class RepositoryServiceImplTest {
|
|||
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();
|
||||
assertThat(loaded).containsExactlyInAnyOrderElementsOf(entitiesToSave);
|
||||
}
|
||||
|
||||
try (var bundle = repository.loadAll()) {
|
||||
try (var bundle = repository.getAll()) {
|
||||
var loaded = bundle.get().toList();
|
||||
assertThat(loaded).containsExactlyInAnyOrderElementsOf(entitiesToSave);
|
||||
}
|
||||
|
|
@ -163,12 +163,12 @@ class RepositoryServiceImplTest {
|
|||
var repository = service.getRepository(TestRepository.class);
|
||||
var testEntity = repository.create();
|
||||
testEntity.setTestField("new");
|
||||
repository.store(List.of(testEntity));
|
||||
repository.save(List.of(testEntity));
|
||||
|
||||
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(testEntity == receivedEntity).isFalse();//not exact instance
|
||||
}
|
||||
|
|
@ -179,10 +179,10 @@ class RepositoryServiceImplTest {
|
|||
try (var service = instantiateTestService(List.of(RepoImpl.class))) {
|
||||
var repository = service.getRepository(TestRepository.class);
|
||||
var testEntity = repository.create();
|
||||
repository.store(List.of(testEntity));
|
||||
repository.save(List.of(testEntity));
|
||||
assertThat(repository.getCount()).isEqualTo(1);
|
||||
repository.remove(List.of(testEntity));
|
||||
assertThat(repository.load(testEntity.getId())).isNull();
|
||||
assertThat(repository.get(testEntity.getId())).isNull();
|
||||
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>database</module>
|
||||
<module>core</module>
|
||||
<module>rpc</module>
|
||||
<module>web-server</module>
|
||||
<module>app</module>
|
||||
<module>logging</module>
|
||||
</modules>
|
||||
|
||||
<properties>
|
||||
|
|
@ -108,11 +112,10 @@
|
|||
<version>1.3.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- https://mvnrepository.com/artifact/org.javassist/javassist -->
|
||||
<dependency>
|
||||
<groupId>org.javassist</groupId>
|
||||
<artifactId>javassist</artifactId>
|
||||
<version>3.29.2-GA</version>
|
||||
<groupId>tools.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
<version>3.0.3</version>
|
||||
</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