Compare commits
5 Commits
898a170d7b
...
3f022de862
| Author | SHA1 | Date |
|---|---|---|
|
|
3f022de862 | |
|
|
381fda5252 | |
|
|
3ad2f762e0 | |
|
|
8a2d52b9d2 | |
|
|
7e52c4735f |
|
|
@ -38,3 +38,5 @@ build/
|
|||
.DS_Store
|
||||
/.idea/
|
||||
/.mvn/
|
||||
xcp.conf
|
||||
xcpdata.mv.db
|
||||
|
|
|
|||
|
|
@ -0,0 +1,48 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>ru.kirillius</groupId>
|
||||
<artifactId>XCP</artifactId>
|
||||
<version>1.0.0.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>api-spec-generator</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>ru.kirillius</groupId>
|
||||
<artifactId>rpc</artifactId>
|
||||
<version>1.0.0.0</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>exec-maven-plugin</artifactId>
|
||||
<version>3.1.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>generate-spec</id>
|
||||
<phase>compile</phase>
|
||||
<goals>
|
||||
<goal>java</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<mainClass>ru.kirillius.XCP.ApiGenerator.SpecGenerator</mainClass>
|
||||
<arguments>
|
||||
<argument>rpc/src/main/java</argument>
|
||||
<argument>ru.kirillius.XCP.RPC.Services</argument>
|
||||
<argument>target/generated-sources/api.spec.json</argument>
|
||||
</arguments>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
|
|
@ -0,0 +1,125 @@
|
|||
package ru.kirillius.XCP.ApiGenerator;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
import ru.kirillius.XCP.RPC.JSONRPC.JsonRpcMethod;
|
||||
import ru.kirillius.XCP.RPC.JSONRPC.JsonRpcService;
|
||||
import tools.jackson.databind.JsonNode;
|
||||
import tools.jackson.databind.ObjectMapper;
|
||||
import tools.jackson.databind.node.ArrayNode;
|
||||
import tools.jackson.databind.node.JsonNodeFactory;
|
||||
import tools.jackson.databind.node.ObjectNode;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.Collection;
|
||||
import java.util.Objects;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class SpecGenerator {
|
||||
/**
|
||||
* Args:
|
||||
* 1 - path to find classes
|
||||
* 2 - search in package
|
||||
* 3 - output spec file path
|
||||
*
|
||||
* @param args
|
||||
* @throws IOException
|
||||
*/
|
||||
public static void main(String[] args) throws IOException {
|
||||
var generator = new SpecGenerator(
|
||||
new File(args[0]),
|
||||
args[1],
|
||||
new File(args[2])
|
||||
);
|
||||
|
||||
generator.generate();
|
||||
generator.writeSpecs();
|
||||
}
|
||||
|
||||
private final File scanDir;
|
||||
private final String packageName;
|
||||
private final File outputFile;
|
||||
|
||||
public SpecGenerator(File scanDir, String packageName, File outputFile) {
|
||||
this.scanDir = scanDir;
|
||||
this.packageName = packageName;
|
||||
this.outputFile = outputFile;
|
||||
}
|
||||
|
||||
private final ObjectNode specs = JsonNodeFactory.instance.objectNode();
|
||||
|
||||
@SneakyThrows
|
||||
public void generate() {
|
||||
specs.removeAll();
|
||||
var scanPath = new File(scanDir, packageName.replaceAll(Pattern.quote("."), "/"));
|
||||
for (var file : Objects.requireNonNull(scanPath.listFiles())) {
|
||||
if (!file.getName().endsWith(".java") || file.getName().contains("$")) {
|
||||
continue;
|
||||
}
|
||||
var className = packageName + '.' + file.getName();
|
||||
className = className.substring(0, className.length() - ".java".length());
|
||||
|
||||
var cls = Class.forName(className);
|
||||
|
||||
if (!JsonRpcService.class.isAssignableFrom(cls)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var classSpecs = specs.putObject(cls.getSimpleName());
|
||||
classSpecs.put("name", cls.getSimpleName());
|
||||
var methods = classSpecs.putArray("methods");
|
||||
|
||||
for (var method : cls.getDeclaredMethods()) {
|
||||
var descriptor = method.getAnnotation(JsonRpcMethod.class);
|
||||
if (descriptor == null) {
|
||||
continue;
|
||||
}
|
||||
var methodSpecs = methods.addObject();
|
||||
methodSpecs.put("name", method.getName());
|
||||
methodSpecs.put("description", descriptor.description());
|
||||
methodSpecs.put("return", getTypeName(descriptor.returnType()));
|
||||
methodSpecs.put("accessLevel", descriptor.accessLevel().name());
|
||||
|
||||
var params = methodSpecs.putArray("params");
|
||||
|
||||
for (var parameter : descriptor.parameters()) {
|
||||
var paramSpecs = params.addObject();
|
||||
paramSpecs.put("name", parameter.name());
|
||||
paramSpecs.put("type", getTypeName(parameter.type()));
|
||||
paramSpecs.put("description", parameter.description());
|
||||
paramSpecs.put("optional", parameter.optional());
|
||||
}
|
||||
|
||||
methodSpecs.put("return", getTypeName(descriptor.returnType()));
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void writeSpecs() throws IOException {
|
||||
outputFile.getParentFile().mkdirs();
|
||||
try (var stream = new FileOutputStream(outputFile)) {
|
||||
var mapper = new ObjectMapper();
|
||||
mapper.writerWithDefaultPrettyPrinter().writeValue(stream, specs);
|
||||
}
|
||||
}
|
||||
|
||||
private static String getTypeName(Class<?> type) {
|
||||
if (type == null) return "unknown";
|
||||
if (type == boolean.class) return "boolean";
|
||||
if (type == int.class || type == long.class) return "number";
|
||||
if (type == String.class) return "string";
|
||||
if (type == void.class || type == Void.class) return "void";
|
||||
|
||||
if (type.isArray() || Collection.class.isAssignableFrom(type) || type == ArrayNode.class) {
|
||||
return "Array";
|
||||
}
|
||||
|
||||
if (JsonNode.class.isAssignableFrom(type)) {
|
||||
return "Object";
|
||||
}
|
||||
|
||||
return type.getSimpleName();
|
||||
}
|
||||
}
|
||||
|
|
@ -16,6 +16,7 @@ import ru.kirillius.XCP.Services.ServiceLoadPriority;
|
|||
import ru.kirillius.XCP.Services.WebService;
|
||||
import ru.kirillius.XCP.web.WebServiceImpl;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.Arrays;
|
||||
|
|
@ -39,23 +40,44 @@ public class Application implements Context {
|
|||
private final ConfigManager configManager;
|
||||
private final Map<Class<? extends Service>, Service> services = new ConcurrentHashMap<>();
|
||||
|
||||
private Config loadConfig() {
|
||||
var configFile = configManager.getConfigFile().getAbsolutePath();
|
||||
|
||||
try {
|
||||
configFile = configManager.getConfigFile().getCanonicalPath();
|
||||
} catch (IOException e) {
|
||||
log.warning("Unable to determine real path of file " + configFile);
|
||||
}
|
||||
|
||||
Config config;
|
||||
if (configManager.isExist()) {
|
||||
try {
|
||||
config = configManager.load();
|
||||
log.info("Loaded config file: " + configFile);
|
||||
} catch (IOException e) {
|
||||
log.error("Error loading config file " + configFile, e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
} else {
|
||||
log.warning("Unable to find config file " + configFile + ". Using default values.");
|
||||
config = configManager.create();
|
||||
log.info("Saving default config file to " + configFile);
|
||||
try {
|
||||
configManager.save(config);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Unable to save config file", e);
|
||||
}
|
||||
}
|
||||
return config;
|
||||
}
|
||||
|
||||
public Application(String[] args) {
|
||||
launchArgs = Arrays.stream(args).toList();
|
||||
loggingSystem = new LoggingSystemImpl(this);
|
||||
log = loggingSystem.createLogger(Application.class);
|
||||
configManager = new ConfigManagerImpl(this);
|
||||
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();
|
||||
}
|
||||
config = loadConfig();
|
||||
|
||||
securityManager = new SecurityManagerImpl();
|
||||
try {
|
||||
loadServices();
|
||||
|
|
@ -76,17 +98,18 @@ public class Application implements Context {
|
|||
var order = aClass.getAnnotation(ServiceLoadPriority.class);
|
||||
return order == null ? 100000 : order.value();
|
||||
})).forEach(aClass -> {
|
||||
log.info("Loading service " + aClass.getSimpleName());
|
||||
@SuppressWarnings("unchecked") var facade = (Class<? extends Service>) Arrays.stream(aClass.getInterfaces())
|
||||
.filter(Service.class::isAssignableFrom)
|
||||
.findFirst().
|
||||
orElseThrow(() -> new ClassCastException("Unable to get service interface from class " + aClass.getSimpleName()));
|
||||
log.info("Loading service " + facade.getSimpleName());
|
||||
try {
|
||||
var constructor = aClass.getConstructor();
|
||||
try {
|
||||
var service = constructor.newInstance();
|
||||
try {
|
||||
service.initialize(this);
|
||||
@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 {
|
||||
|
|
@ -94,14 +117,14 @@ public class Application implements Context {
|
|||
} catch (IOException ex) {
|
||||
e.addSuppressed(ex);
|
||||
}
|
||||
throw new RuntimeException("Failed to start service " + aClass.getSimpleName(), e);
|
||||
throw new RuntimeException("Failed to start " + facade.getSimpleName(), e);
|
||||
}
|
||||
} catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
|
||||
throw new RuntimeException("Failed to instantiate service " + aClass.getSimpleName(), e);
|
||||
throw new RuntimeException("Failed to instantiate " + facade.getSimpleName(), e);
|
||||
}
|
||||
|
||||
} catch (NoSuchMethodException e) {
|
||||
throw new RuntimeException("Failed to find default constructor of service " + aClass.getSimpleName(), e);
|
||||
throw new RuntimeException("Failed to find default constructor of " + facade.getSimpleName(), e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import ru.kirillius.XCP.Commons.Context;
|
|||
import tools.jackson.databind.ObjectMapper;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public final class ConfigManagerImpl implements ConfigManager {
|
||||
private final Context context;
|
||||
|
|
@ -24,7 +25,11 @@ public final class ConfigManagerImpl implements ConfigManager {
|
|||
}
|
||||
|
||||
private File findConfigFile() {
|
||||
return new File(context.getLaunchArgs().stream().filter(a -> a.startsWith("--config=")).findFirst().orElse(DEFAULT_CONFIG_PATH));
|
||||
return new File(context.getLaunchArgs().stream()
|
||||
.filter(a -> a.startsWith("--config="))
|
||||
.findFirst()
|
||||
.map(a -> a.split(Pattern.quote("="))[1])
|
||||
.orElse(DEFAULT_CONFIG_PATH));
|
||||
}
|
||||
|
||||
@Getter
|
||||
|
|
@ -52,7 +57,7 @@ public final class ConfigManagerImpl implements ConfigManager {
|
|||
@Override
|
||||
public void save(Config config) throws IOException {
|
||||
try (var stream = new FileOutputStream(configFile)) {
|
||||
mapper.writeValue(stream, config);
|
||||
mapper.writerWithDefaultPrettyPrinter().writeValue(stream, config);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -70,7 +75,7 @@ public final class ConfigManagerImpl implements ConfigManager {
|
|||
@Getter
|
||||
@Setter
|
||||
@JsonProperty
|
||||
private File databaseFile = new File(DEFAULT_CONFIG_PATH);
|
||||
private File databaseFile = new File(DEFAULT_DB_PATH);
|
||||
@Getter
|
||||
@Setter
|
||||
@JsonProperty
|
||||
|
|
|
|||
|
|
@ -12,11 +12,11 @@
|
|||
<artifactId>database</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<!-- https://mvnrepository.com/artifact/com.h2database/h2 -->
|
||||
<!-- Source: https://mvnrepository.com/artifact/com.h2database/h2 -->
|
||||
<dependency>
|
||||
<groupId>com.h2database</groupId>
|
||||
<artifactId>h2</artifactId>
|
||||
<version>2.1.214</version>
|
||||
<version>2.4.240</version>
|
||||
</dependency>
|
||||
<!-- https://mvnrepository.com/artifact/org.hibernate.orm/hibernate-core -->
|
||||
<dependency>
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import org.hibernate.annotations.UuidGenerator;
|
|||
import ru.kirillius.XCP.Commons.StreamHandler;
|
||||
import ru.kirillius.XCP.Persistence.Entities.Group;
|
||||
import ru.kirillius.XCP.Persistence.*;
|
||||
import ru.kirillius.XCP.Serialization.SerializationUtils;
|
||||
import tools.jackson.databind.node.JsonNodeFactory;
|
||||
import tools.jackson.databind.node.ObjectNode;
|
||||
|
||||
import java.io.IOException;
|
||||
|
|
@ -157,7 +157,7 @@ public class GroupRepositoryImpl extends AbstractNodeRepository<Group> implement
|
|||
@JsonProperty
|
||||
@Getter
|
||||
@Setter
|
||||
private ObjectNode properties = SerializationUtils.EmptyObject();
|
||||
private ObjectNode properties = JsonNodeFactory.instance.objectNode();
|
||||
|
||||
@JsonIgnore
|
||||
@ManyToMany(fetch = FetchType.EAGER)
|
||||
|
|
|
|||
|
|
@ -11,9 +11,9 @@ import ru.kirillius.XCP.Persistence.Entities.Group;
|
|||
import ru.kirillius.XCP.Persistence.Entities.Input;
|
||||
import ru.kirillius.XCP.Persistence.*;
|
||||
import ru.kirillius.XCP.Serialization.PollSettingsConverter;
|
||||
import ru.kirillius.XCP.Serialization.SerializationUtils;
|
||||
import ru.kirillius.XCP.Serialization.ValueTransformationChainConverter;
|
||||
import tools.jackson.databind.annotation.JsonDeserialize;
|
||||
import tools.jackson.databind.node.JsonNodeFactory;
|
||||
import tools.jackson.databind.node.ObjectNode;
|
||||
|
||||
import java.util.HashSet;
|
||||
|
|
@ -110,7 +110,7 @@ public class InputRepositoryImpl extends AbstractNodeRepository<Input> implement
|
|||
@JsonProperty
|
||||
@Getter
|
||||
@Setter
|
||||
private ObjectNode properties = SerializationUtils.EmptyObject();
|
||||
private ObjectNode properties = JsonNodeFactory.instance.objectNode();
|
||||
|
||||
@JsonIgnore
|
||||
@ManyToMany(fetch = FetchType.EAGER)
|
||||
|
|
|
|||
|
|
@ -9,8 +9,8 @@ import ru.kirillius.XCP.Data.ValueTransformationChain;
|
|||
import ru.kirillius.XCP.Persistence.Entities.Group;
|
||||
import ru.kirillius.XCP.Persistence.Entities.Output;
|
||||
import ru.kirillius.XCP.Persistence.*;
|
||||
import ru.kirillius.XCP.Serialization.SerializationUtils;
|
||||
import ru.kirillius.XCP.Serialization.ValueTransformationChainConverter;
|
||||
import tools.jackson.databind.node.JsonNodeFactory;
|
||||
import tools.jackson.databind.node.ObjectNode;
|
||||
|
||||
import java.util.HashSet;
|
||||
|
|
@ -106,7 +106,7 @@ public class OutputRepositoryImpl extends AbstractNodeRepository<Output> impleme
|
|||
@JsonProperty
|
||||
@Getter
|
||||
@Setter
|
||||
private ObjectNode properties = SerializationUtils.EmptyObject();
|
||||
private ObjectNode properties = JsonNodeFactory.instance.objectNode();
|
||||
|
||||
@JsonIgnore
|
||||
@ManyToMany(fetch = FetchType.EAGER)
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import ru.kirillius.XCP.Persistence.Entities.User;
|
|||
import ru.kirillius.XCP.Persistence.EntityImplementation;
|
||||
import ru.kirillius.XCP.Persistence.RepositoryServiceImpl;
|
||||
import ru.kirillius.XCP.Security.UserRole;
|
||||
import ru.kirillius.XCP.Serialization.SerializationUtils;
|
||||
import tools.jackson.databind.node.JsonNodeFactory;
|
||||
import tools.jackson.databind.node.ObjectNode;
|
||||
|
||||
import java.io.IOException;
|
||||
|
|
@ -80,7 +80,7 @@ public class UserRepositoryImpl extends AbstractRepository<User> implements User
|
|||
@Getter
|
||||
@Setter
|
||||
@JsonProperty
|
||||
private ObjectNode values = SerializationUtils.EmptyObject();
|
||||
private ObjectNode values = JsonNodeFactory.instance.objectNode();
|
||||
|
||||
@Override
|
||||
public void setPassword(String password) {
|
||||
|
|
|
|||
|
|
@ -1,17 +0,0 @@
|
|||
package ru.kirillius.XCP.Serialization;
|
||||
|
||||
import tools.jackson.databind.ObjectMapper;
|
||||
import tools.jackson.databind.node.ArrayNode;
|
||||
import tools.jackson.databind.node.ObjectNode;
|
||||
|
||||
public final class SerializationUtils {
|
||||
public final static ObjectMapper mapper = new ObjectMapper();
|
||||
|
||||
public static ObjectNode EmptyObject() {
|
||||
return mapper.createObjectNode();
|
||||
}
|
||||
|
||||
public static ArrayNode EmptyArray() {
|
||||
return mapper.createArrayNode();
|
||||
}
|
||||
}
|
||||
|
|
@ -35,7 +35,7 @@ public class LogHandlerImpl extends Handler {
|
|||
builder.append(logRecord.getMessage().trim());
|
||||
var thrown = logRecord.getThrown();
|
||||
if (thrown != null) {
|
||||
builder.append("\nError thrown ").append(thrown.getClass().getSimpleName()).append(":").append(thrown.getMessage());
|
||||
builder.append("\n\tThrown ").append(thrown.getClass().getSimpleName()).append(": ").append(thrown.getMessage());
|
||||
if (debugging) {
|
||||
builder.append("\nStack trace:\n");
|
||||
|
||||
|
|
@ -47,6 +47,13 @@ public class LogHandlerImpl extends Handler {
|
|||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
} else {
|
||||
var cause = thrown.getCause();
|
||||
while (cause != null) {
|
||||
builder.append("\n\t\tCaused by ").append(cause.getClass().getSimpleName()).append(": ").append(cause.getMessage());
|
||||
cause = cause.getCause();
|
||||
}
|
||||
builder.append("\n\t(Stack trace is hidden due to --debug flag)");
|
||||
}
|
||||
}
|
||||
return builder.toString();
|
||||
|
|
|
|||
|
|
@ -31,6 +31,6 @@ public class LoggerImpl extends java.util.logging.Logger implements Logger {
|
|||
|
||||
@Override
|
||||
public void error(Throwable error) {
|
||||
log(Level.SEVERE, getName() + SEPARATOR + "Thrown error", error);
|
||||
log(Level.SEVERE, getName() + SEPARATOR + "Thrown unhandled exception", error);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
2
pom.xml
2
pom.xml
|
|
@ -10,12 +10,14 @@
|
|||
<packaging>pom</packaging>
|
||||
<modules>
|
||||
<module>api</module>
|
||||
<module>api-spec-generator</module>
|
||||
<module>database</module>
|
||||
<module>core</module>
|
||||
<module>rpc</module>
|
||||
<module>web-server</module>
|
||||
<module>app</module>
|
||||
<module>logging</module>
|
||||
<module>web-ui</module>
|
||||
</modules>
|
||||
|
||||
<properties>
|
||||
|
|
|
|||
|
|
@ -11,4 +11,23 @@ import java.lang.annotation.Target;
|
|||
@Target(ElementType.METHOD)
|
||||
public @interface JsonRpcMethod {
|
||||
UserRole accessLevel() default UserRole.Admin;
|
||||
|
||||
String description() default "";
|
||||
|
||||
Parameter[] parameters() default {};
|
||||
|
||||
Class<?> returnType() default Void.class;
|
||||
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.METHOD)
|
||||
@interface Parameter {
|
||||
String name();
|
||||
|
||||
String description() default "";
|
||||
|
||||
Class<?> type() default Void.class;
|
||||
|
||||
boolean optional() default false;
|
||||
}
|
||||
}
|
||||
|
|
@ -133,8 +133,38 @@ public class JsonRpcServlet extends HttpServlet {
|
|||
mapper.writeValue(httpServletResponse.getWriter(), jsonRpcResponse);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
|
||||
mapper.writeValue(resp.getWriter(), createErrorResponse(null, JsonRpcErrorCode.INVALID_REQUEST));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws IOException {
|
||||
mapper.writeValue(resp.getWriter(), createErrorResponse(null, JsonRpcErrorCode.INVALID_REQUEST));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doHead(HttpServletRequest req, HttpServletResponse resp) throws IOException {
|
||||
mapper.writeValue(resp.getWriter(), createErrorResponse(null, JsonRpcErrorCode.INVALID_REQUEST));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws IOException {
|
||||
mapper.writeValue(resp.getWriter(), createErrorResponse(null, JsonRpcErrorCode.INVALID_REQUEST));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doTrace(HttpServletRequest req, HttpServletResponse resp) throws IOException {
|
||||
mapper.writeValue(resp.getWriter(), createErrorResponse(null, JsonRpcErrorCode.INVALID_REQUEST));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doOptions(HttpServletRequest req, HttpServletResponse resp) throws IOException {
|
||||
mapper.writeValue(resp.getWriter(), createErrorResponse(null, JsonRpcErrorCode.INVALID_REQUEST));
|
||||
}
|
||||
|
||||
private JsonRpcResponse createErrorResponse(JsonRpcRequest request, JsonRpcErrorCode code) {
|
||||
return JsonRpcResponse.builder()
|
||||
return JsonRpcResponse.builder().jsonrpc("2.0")
|
||||
.id(request == null ? -1L : request.getId())
|
||||
.error(new JsonRpcError(code))
|
||||
.build();
|
||||
|
|
|
|||
|
|
@ -21,7 +21,23 @@ import java.util.UUID;
|
|||
|
||||
public class Auth extends JsonRpcService {
|
||||
|
||||
@JsonRpcMethod(accessLevel = UserRole.Guest)
|
||||
@JsonRpcMethod(
|
||||
accessLevel = UserRole.Guest,
|
||||
description = "Authenticates a user using login and password. Returns true if authentication is successful.",
|
||||
parameters = {
|
||||
@JsonRpcMethod.Parameter(
|
||||
name = "login",
|
||||
description = "User's login name",
|
||||
type = String.class
|
||||
),
|
||||
@JsonRpcMethod.Parameter(
|
||||
name = "password",
|
||||
description = "User's password",
|
||||
type = String.class
|
||||
)
|
||||
},
|
||||
returnType = boolean.class
|
||||
)
|
||||
public boolean authenticateByPassword(CallContext call) {
|
||||
var login = requireParam(call, "login", JsonNode::asString);
|
||||
var passwd = requireParam(call, "password", JsonNode::asString);
|
||||
|
|
@ -41,7 +57,11 @@ public class Auth extends JsonRpcService {
|
|||
}
|
||||
|
||||
|
||||
@JsonRpcMethod(accessLevel = UserRole.User)
|
||||
@JsonRpcMethod(
|
||||
accessLevel = UserRole.User,
|
||||
description = "Retrieves all API tokens associated with the current user",
|
||||
returnType = ArrayNode.class
|
||||
)
|
||||
public ArrayNode getTokens(CallContext call) throws IOException {
|
||||
var tokenRepository = call.getContext().getService(RepositoryService.class).getRepository(ApiTokenRepository.class);
|
||||
var tokens = JsonNodeFactory.instance.arrayNode();
|
||||
|
|
@ -57,7 +77,26 @@ public class Auth extends JsonRpcService {
|
|||
return tokens;
|
||||
}
|
||||
|
||||
@JsonRpcMethod(accessLevel = UserRole.User)
|
||||
|
||||
@JsonRpcMethod(
|
||||
accessLevel = UserRole.User,
|
||||
description = "Generates a new API token and returns its details",
|
||||
parameters = {
|
||||
@JsonRpcMethod.Parameter(
|
||||
name = "permanent",
|
||||
description = "If true, creates a token that never expires",
|
||||
type = boolean.class,
|
||||
optional = true
|
||||
),
|
||||
@JsonRpcMethod.Parameter(
|
||||
name = "name",
|
||||
description = "Display name for the token. If not provided, the User-Agent header will be used",
|
||||
type = String.class,
|
||||
optional = true
|
||||
)
|
||||
},
|
||||
returnType = ObjectNode.class
|
||||
)
|
||||
public ObjectNode generateToken(CallContext call) {
|
||||
var repositoryService = call.getContext().getService(RepositoryService.class);
|
||||
var tokenRepository = repositoryService.getRepository(ApiTokenRepository.class);
|
||||
|
|
@ -79,8 +118,19 @@ public class Auth extends JsonRpcService {
|
|||
return tokenRepository.serialize(token);
|
||||
}
|
||||
|
||||
@JsonRpcMethod(accessLevel = UserRole.Guest)
|
||||
public boolean authenticateByToken(CallContext call) throws IllegalAccessException {
|
||||
@JsonRpcMethod(
|
||||
accessLevel = UserRole.Guest,
|
||||
description = "Authenticates a user using an API token. Returns true if authentication is successful.",
|
||||
parameters = {
|
||||
@JsonRpcMethod.Parameter(
|
||||
name = "token",
|
||||
description = "API token string for authentication",
|
||||
type = String.class
|
||||
)
|
||||
},
|
||||
returnType = boolean.class
|
||||
)
|
||||
public boolean authenticateByToken(CallContext call) {
|
||||
var repositoryService = call.getContext().getService(RepositoryService.class);
|
||||
var tokenRepository = repositoryService.getRepository(ApiTokenRepository.class);
|
||||
var token = tokenRepository.get(UUID.fromString(
|
||||
|
|
@ -105,7 +155,11 @@ public class Auth extends JsonRpcService {
|
|||
return true;
|
||||
}
|
||||
|
||||
@JsonRpcMethod(accessLevel = UserRole.User)
|
||||
@JsonRpcMethod(
|
||||
accessLevel = UserRole.User,
|
||||
description = "Logs out the current user. Returns true if logout is successful, false if the user is not logged in",
|
||||
returnType = boolean.class
|
||||
)
|
||||
public boolean logout(CallContext call) {
|
||||
ru.kirillius.XCP.Persistence.Entities.User user = null;
|
||||
|
||||
|
|
@ -117,4 +171,4 @@ public class Auth extends JsonRpcService {
|
|||
|
||||
return session != null && user != null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -24,7 +24,7 @@ public class WebServiceImpl implements WebService {
|
|||
@Override
|
||||
public void initialize(Context context) {
|
||||
if (server != null) {
|
||||
throw new IllegalStateException("Server already started");
|
||||
throw new IllegalStateException("Server is started already");
|
||||
}
|
||||
var jsonRpc = new JsonRpcServlet(context);
|
||||
jsonRpc.registerRpcService(
|
||||
|
|
@ -47,6 +47,11 @@ public class WebServiceImpl implements WebService {
|
|||
}
|
||||
|
||||
server.setHandler(servletContext);
|
||||
try {
|
||||
server.start();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to start jetty web server", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
<?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-ui</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>
|
||||
|
||||
</project>
|
||||
Loading…
Reference in New Issue