Добавил ApiToken entity для авторизации, контекст для RPC, реализовал ConfigManager, логгирование и RPC

This commit is contained in:
kirillius 2026-01-12 10:14:17 +03:00
parent 0d202b5574
commit 898a170d7b
58 changed files with 1663 additions and 120 deletions

View File

@ -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>

View File

@ -2,7 +2,8 @@ package ru.kirillius.XCP.Commons;
import java.io.File;
public interface Config {
public interface
Config {
File getLoadedConfigFile();

View File

@ -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();

View File

@ -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();
}

View File

@ -0,0 +1,7 @@
package ru.kirillius.XCP.Logging;
public enum LogLevel {
INFO,
ERROR,
WARNING
}

View File

@ -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) {
}

View File

@ -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);
}

View File

@ -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();
}

View File

@ -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());
}
}

View File

@ -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);
}
}

View File

@ -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);
}

View File

@ -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);

View File

@ -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();
}

View File

@ -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;
}

View File

@ -0,0 +1,6 @@
package ru.kirillius.XCP.Services;
import ru.kirillius.XCP.Commons.Service;
public interface WebService extends Service {
}

52
app/pom.xml Normal file
View File

@ -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>

View File

@ -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);
}
}
}

View File

@ -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;
}
}

View File

@ -0,0 +1,9 @@
package ru.kirillius.XCP.Security;
import lombok.Getter;
public class SecurityManagerImpl implements SecurityManager {
@Getter
private final HashUtility hashUtility = new Argon2HashUtility();
}

View File

@ -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>

View File

@ -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();
}

View File

@ -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);
}

View File

@ -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);
}
}
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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);
}
});

View File

@ -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 = "";

View File

@ -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;

View File

@ -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);
}
}

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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);
}

View File

@ -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);

View File

@ -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();
}

39
logging/pom.xml Normal file
View File

@ -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>

View File

@ -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 {
}
}

View File

@ -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);
}
}

View File

@ -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
View File

@ -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>

60
rpc/pom.xml Normal file
View File

@ -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-->
<!-- &lt;!&ndash; https://mvnrepository.com/artifact/com.github.briandilley.jsonrpc4j/jsonrpc4j &ndash;&gt;-->
<!-- <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>

View File

@ -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();
}

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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));
}
}

34
web-server/pom.xml Normal file
View File

@ -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>

View File

@ -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);
}
}
}

View File

@ -0,0 +1,10 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
</body>
</html>