Commit 4c6bf1e4 authored by Tuttas's avatar Tuttas
Browse files

Merge branch 'main' into 'main'

Refactor Task Entity, Improve JSON Handling, and Enhance Testing

See merge request !19
1 merge request!19Refactor Task Entity, Improve JSON Handling, and Enhance Testing
Pipeline #5965 failed with stage
in 9 seconds
Showing with 581 additions and 47 deletions
+581 -47
......@@ -27,3 +27,10 @@ buildNumber.properties
Doku/
.vscode/launch.json
# Other
logs/
*.db
*.db-shm
*.db-wal
\ No newline at end of file
/*
Compile and Catalog Configuration
*/
{
// For UniVerse, catalog types can be: global, local, or normal.
// For UniData, catalog types can be: local or direct.
"catalog": "local",
//Catalog arguments
"arguments": "",
// For the catalog type "global" in UniVerse, the initial characters can be: Asterisk (*), Exclamation mark (!), Minus sign (-), or Dollar sign ($).
"initialCharacter": "",
// "initialCharacter": "*",
// "initialCharacter": "!",
// "initialCharacter": "-",
// "initialCharacter": "$"
// If the datasource is Unidata, provide the `ud_compile_flavor`.
// The default flavor is Unibasic.
"ud_compile_flavor": ""
// "ud_compile_flavor": "Pick",
// "ud_compile_flavor": "Unibasic",
// "ud_compile_flavor": "revelation",
// "ud_compile_flavor": "douglas"
}
\ No newline at end of file
/*
Database configuration
This is the example configuration file for the Rocket MV Basic for VS Code extension.
The configuration file should be at .rmv/config/db.mvbasic.json.
Configure the following items:
- db:{} This object defines the connection details to the U2 server.
- accounts: [] This array of objects defines paths on the local machine to other accounts.
- catalog: {} This object allows autocompletion for CALL statements and
enables "Peek" and "Go to Definition" to access the source code.
- includeMapping: [] This array allows autocompletion for INCLUDE files and
enables "Peek" and "Go to Definition" to access the source code for an INCLUDEd item.
*/
{
"version": "1.0",
// Define connection details to the U2 server.
// VS Code prompts for these values when left blank.
// For more information, please refer https://rocketsoftware.github.io/rocket-mvbasic/usage/Connection
"db": {
// Hostname or IP address of U2 server.
"host": "",
// "host": "localhost",
// "host": "my.u2.server.local",
// "host": "192.0.2.1",
// Username used to log in over Telnet or UniRPC. Use an escape character for domain log-in. (\\ instead of \)
"userName": "",
// "userName": "DOMAIN1\\myuser",
// "userName": "myuser",
// Password used to log in over Telnet or UniRPC. RECOMMENDED TO LEAVE THIS PASSWORD BLANK.
// VS Code will prompt for the password when connecting.
"password": "",
// U2 account to LOGIN to.
"account": "",
// "account": "XDEMO",
// "account": "MVTUTOR",
// Data source is either "UNIVERSE" or "UNIDATA".
// If this value is changed, VS Code MUST BE restarted for it to take effect.
"dataSource": "UNIVERSE",
// UniRPC TCP/IP port to connect to U2 server. 31438 is the default port of UniRPC.
// Match the port number here if the U2 server runs the UniRPC on another port.
"port": 31438
},
// The "accounts" array defines the local paths to the U2 server account directories.
// These accounts are referred to in the "catalog" and "includeMapping" items.
// The "db" section "account" does not need to be defined here since it would be the current directory.
// For more details, please refer https://rocketsoftware.github.io/rocket-mvbasic/usage/Accounts/
"accounts": [
{
// The account name. For example, "XDEMO" or "HS.SALES" or "MVTUTOR", etc.
// This is the "account" referred to in the "programDirs"."programMapping" and "includeMapping" arrays.
"name": "",
// The full path of the account's folder as referenced from this local machine.
// For example, "C:\\U2\\UV\\XDEMO" or "U:\\U2\\UV\\XDEMO" or "/mnt/u2server/U2/UV/XDEMO".
// NOTE : If this client is a windows box, you must specify two backslashes (\\) between directories.
"path": ""
}
],
// Map cataloged programs for autocompletion of CALL statements.
"catalog": {
// Enables the extension to scan all directories in the workspace (account).
// Affects performance when there are a large number of directories in the workspace.
// Best practice is to define specific directories in "programDirs" that are known to contain SUBROUTINE source code.
"isSearchAllDirs": false,
// This is the time interval (in seconds) when the extension will scan directory files in the account.
// The default for this setting is 5.
"readServerInterval": 5,
// The "programDirs" array defines the accounts and directories in which to search for source code for CATALOGed
// ("CALL " program) items. You can define multiple directories to search.
// The directories will be searched for source code in alphabetic order regardless of their order in the array.
// You can find examples of this setting at https://rocketsoftware.github.io/rocket-mvbasic/usage/Catalog/
"programDirs": [
{
// Account name. Account name should be defined in "accounts" array.
// This setting could be removed if cataloged programs are not in directories in other accounts.
"account": "",
// The name of source code directory which contains cataloged programs.
// When searching cataloged programs, this configured directory will be scanned.
"fileName": ""
}
],
// The "programMapping" array allows a one-to-one correlation for known SUBROUTINEs to be offered in the
// "CALL " auto-completion. If set in this section, those items defined as "catalogName" will appear in the
// pop-up auto-completion list and the source for "Peek" or "Go to Definition" can be explicitly defined here
// as "account", "fileName" and "program".
// You can find examples of these settings at https://rocketsoftware.github.io/rocket-mvbasic/usage/Catalog/
"programMapping": [
{
// The CATALOGed program name to be used in the CALL statement.
"catalogName": "",
// The Account name where the cataloged program source code exists. The Account name must appear in the
// "accounts" array with it's associated local path unless it is the current workspace account.
"account": "",
// The name of the actual program directory which contains the source code of cataloged program.
// NOTE: This may differ from the VOC entry for this U2 program file, since a Q or F point could
// exist that points to a different OS level directory.
"fileName": "",
// The item ID of the source code in the "fileName" directory defined above.
"program": ""
}
]
},
// The 'includeMapping" array allows the extension to populate the pop-up list for "INCLUDE file " and allows the
// "Peek" and "Go to Definition" to access the source code for the INCLUDEd item.
// Please refer more details at https://rocketsoftware.github.io/rocket-mvbasic/usage/Include/
"includeMapping": [
{
// The file name used in the "INCLUDE " statement. For example INCLUDE BP ABC would have "BP" as the
// "includeFile" name. This file name would be a VOC entry within the "account" and is not necessarily the
// actual OS directory name.
"includeFile": "",
// The account where the actual code directory exists. This "account" should be configured in "accounts" array.
"account": "",
// The actual OS directory within the account. This may vary from the VOC name in "includeFile" above.
// For example, UNIVERSE.INCLUDE in the VOC may refer to OS directory INCLUDE.
"fileName": ""
}
]
}
\ No newline at end of file
{
"version": "1.0",
"formatting": {
"trimNewlines": false,
"trimFinalNewlines": false,
"trimTrailingWhitespace": true,
"insertFinalNewline": true,
"multiStatementsOneLine": true,
"alignInlineComments": 48,
"spacing.operator": 1,
"spacing.semicolon": 1,
"spacing.comma": 1,
"spacing.assignment": 1,
"spacing.parentheses": 0,
"spacing.squareBrackets": 0,
"spacing.curlyBrackets": 0,
"spacing.angleBrackets": 0,
"style.operator": "keep",
"style.keyword": "keep",
"style.commentMark": "keep",
"style.keywordCase": "keep",
"indent.base": 0,
"indent.block": true,
"indent.commentLine": true,
"indent.directive": false,
"indent.clause": false,
"clause.useBlock": false,
"clause.addBlankLines": false,
"clause.atNewline": false,
"routine.labelPattern": ""
}
}
\ No newline at end of file
{
}
......@@ -4,6 +4,20 @@
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "java",
"name": "ServerLauncher",
"request": "launch",
"mainClass": "schueler.ServerLauncher",
"projectName": "todo"
},
{
"type": "java",
"name": "RestHandler",
"request": "launch",
"mainClass": "schueler.RestHandler",
"projectName": "todo"
},
{
"type": "java",
"name": "CodeLens (Launch) - App",
......
......@@ -3,4 +3,7 @@
"terminal.integrated.shell.windows": "C:\\WINDOWS\\System32\\cmd.exe",
"terminal.integrated.shellArgs.windows": [
],
"[rocket-mvbasic]": {
"editor.codeLens": false
},
}
\ No newline at end of file
FROM nginx:latest
COPY ./index.html /usr/share/nginx/html/index.html
File added
File added
File added
File added
......@@ -2,7 +2,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>todo</groupId>
<artifactId>todo</artifactId>
<version>todo</version>
<version>1.0.0</version>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
......@@ -43,6 +43,12 @@
<version>1.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>4.0.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
......@@ -59,6 +65,12 @@
<artifactId>json</artifactId>
<version>20190722</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>RELEASE</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<pluginManagement>
......@@ -107,7 +119,7 @@
<archive>
<manifest>
<mainClass>
schueler.MyHandler</mainClass>
schueler.ServerLauncher</mainClass>
</manifest>
</archive>
<descriptorRefs>
......@@ -159,4 +171,4 @@
</profile>
</profiles>
</project>
\ No newline at end of file
</project>
package persistence;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import org.sqlite.SQLiteConfig;
public class ConnectionPool {
private static final int INITIAL_POOL_SIZE = 10;
private static final String DB_URL = "jdbc:sqlite:todo_klein_org.db";
private final BlockingQueue<Connection> connectionQueue;
public ConnectionPool() throws SQLException {
connectionQueue = new LinkedBlockingQueue<>(INITIAL_POOL_SIZE);
for (int i = 0; i < INITIAL_POOL_SIZE; i++) {
connectionQueue.offer(createConnection());
}
}
private Connection createConnection() throws SQLException {
SQLiteConfig config = new SQLiteConfig();
config.enforceForeignKeys(true);
config.setBusyTimeout(5000); // 5 seconds timeout
Connection connection = DriverManager.getConnection(DB_URL, config.toProperties());
enableWALMode(connection);
connection.setAutoCommit(false);
return connection;
}
private void enableWALMode(Connection connection) throws SQLException {
try (java.sql.Statement statement = connection.createStatement()) {
statement.execute("PRAGMA journal_mode=WAL;");
}
}
public Connection getConnection() throws InterruptedException {
return connectionQueue.take(); // Waits for an available connection
}
public void releaseConnection(Connection connection) {
if (connection != null) {
connectionQueue.offer(connection);
}
}
public void closeAllConnections() {
while (!connectionQueue.isEmpty()) {
try {
connectionQueue.poll().close();
} catch (SQLException e) {
e.printStackTrace(); // Log the error properly in a real-world app
}
}
}
}
package persistence;
import java.sql.ResultSet;
import schueler.entity.Entity;
public interface IPersistence {
public ResultSet getResultSet(Entity entity, boolean isSingleEntity) throws Exception;
public ResultSet readResultSet(Entity entity);
public int executeSQL(String updateString, boolean getAffected) throws Exception;
}
package persistence;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import schueler.LoggerUtil;
import schueler.entity.Entity;
public class Persistence implements IPersistence {
private static ConnectionPool connectionPool;
static {
try {
connectionPool = new ConnectionPool();
} catch (SQLException e) {
e.printStackTrace();
}
}
public Persistence() {
}
@Override
public ResultSet readResultSet(Entity entity) {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'readResultSet'");
}
@Override
public ResultSet getResultSet(Entity entity, boolean isSingleEntity) throws SQLException, InterruptedException {
Connection connection = connectionPool.getConnection();
Statement statement = connection.createStatement();
ResultSet resultSet = isSingleEntity ? statement.executeQuery(entity.getReadStatement())
: statement.executeQuery(entity.getReadAllStatement());
connectionPool.releaseConnection(connection);
return resultSet;
}
public int executeSQL(String sql, boolean getAffected) throws SQLException, InterruptedException {
int result = -1;
Connection connection = connectionPool.getConnection();
try (Statement statement = connection.createStatement()) {
LoggerUtil.info("Executing SQL: " + sql);
if (getAffected) {
result = statement.executeUpdate(sql); // Return number of affected rows
} else {
statement.executeUpdate(sql, Statement.RETURN_GENERATED_KEYS);
try (ResultSet rs = statement.getGeneratedKeys()) {
if (rs.next()) {
result = rs.getInt(1); // Return the new ID
}
}
}
connection.commit();
} catch (SQLException e) {
if (e.getMessage().contains("FOREIGN KEY constraint failed")) {
LoggerUtil.severe("Foreign key constraint failed during SQL execution: " + sql);
throw new SQLException("Foreign key constraint failed: " + sql, e);
} else {
e.printStackTrace();
throw e;
}
} finally {
connectionPool.releaseConnection(connection);
}
return result;
}
}
package persistence;
import java.sql.ResultSet;
import schueler.entity.Entity;
public class PersistenceInjector {
static final private IPersistence persistance = new Persistence();
public ResultSet get(Entity entity, boolean isSingleEntity) throws Exception {
return persistance.getResultSet(entity, isSingleEntity);
}
public int executeSQL(String sql, boolean getAffected) throws Exception {
return persistance.executeSQL(sql, getAffected);
}
}
package schueler;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import org.sqlite.SQLiteConfig;
public class App {
public static void main(String[] args) {
try {
Class.forName("org.sqlite.JDBC");
SQLiteConfig config = new SQLiteConfig();
config.enforceForeignKeys(true);
Connection c = DriverManager.getConnection("jdbc:sqlite:todo_klein_org.db", config.toProperties());
insertPriority(10,"sehr unwichtig",c);
Statement st = c.createStatement();
ResultSet rs = st.executeQuery("select * from priority");
while (rs.next()) {
System.out.println(rs.getInt(1) + " " + rs.getInt(2) + " " + rs.getString(3));
}
} catch (ClassNotFoundException e) {
System.out.println("Treiber nicht gefunden");
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}
}
public static boolean insertPriority(int p, String description, Connection c){
try{
Statement st = c.createStatement();
return st.execute("insert into priority(value,description) values ("+p+",\""+description+"\");");
} catch (SQLException e) {
e.printStackTrace();
return false;
}
}
}
package schueler;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.logging.*;
public class LoggerUtil {
// Logger instance for the entire project
private static final Logger logger = Logger.getLogger(LoggerUtil.class.getName());
// Static block to initialize the logger and its handlers
static {
try {
// Create /logs directory if it doesn't exist
File logDirectory = new File("logs");
if (!logDirectory.exists()) {
logDirectory.mkdirs(); // Create the directory
}
// Set up the console handler to show INFO level and higher in console
ConsoleHandler consoleHandler = new ConsoleHandler();
consoleHandler.setLevel(Level.INFO);
// Set up the file handler to log ALL levels to a file inside the /logs directory
FileHandler fileHandler = new FileHandler("logs/app.log", true); // 'true' for append mode
fileHandler.setLevel(Level.ALL);
// Custom formatter to use a human-readable log format
CustomFormatter formatter = new CustomFormatter();
consoleHandler.setFormatter(formatter);
fileHandler.setFormatter(formatter);
// Add handlers to the logger
logger.addHandler(consoleHandler);
logger.addHandler(fileHandler);
// Set the default logging level for the logger
logger.setLevel(Level.ALL); // Log everything (INFO, WARNING, SEVERE, etc.)
} catch (IOException e) {
e.printStackTrace();
}
}
// Methods to log different levels of messages
public static void info(String message) {
logger.info(message);
}
public static void warning(String message) {
logger.warning(message);
}
public static void severe(String message) {
logger.severe(message);
}
public static void fine(String message) {
logger.fine(message);
}
public static void config(String message) {
logger.config(message);
}
// Custom formatter to add a human-readable timestamp
private static class CustomFormatter extends Formatter {
// Override format method to define custom log message format
@Override
public String format(LogRecord record) {
// SimpleDateFormat to format the date
SimpleDateFormat dateFormat = new SimpleDateFormat("dd.MM.yyyy HH:mm:ss");
String timestamp = dateFormat.format(new Date(record.getMillis()));
// Format the log message with the custom date, log level, and the message
return String.format("[%s] [%s] %s%n", timestamp, record.getLevel(), record.getMessage());
}
}
}
package schueler;
import java.io.File;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.Map;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.json.JSONObject;
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;
import com.sun.net.httpserver.HttpServer;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpExchange;
import schueler.handler.RestHandler;
import schueler.handler.RouteHandlerFactory;
import schueler.handler.response.ResponseHandler;
import schueler.handler.response.StatusCodes;
public class ServerLauncher {
private static final int LISTENER_PORT = 8000;
public static void main(String[] args) {
try {
HttpServer httpServer = createServer(LISTENER_PORT);
httpServer.createContext("/", new RequestHandler());
httpServer.setExecutor(null);
httpServer.start();
System.out.println("Server started on port " + LISTENER_PORT);
} catch (IOException e) {
e.printStackTrace();
}
}
private static HttpServer createServer(int port) throws IOException {
return HttpServer.create(new InetSocketAddress(port), 0);
}
private static class RequestHandler implements HttpHandler {
private final Map<String, RestHandler> routeHandlers;
public RequestHandler() {
this.routeHandlers = RouteHandlerFactory.createRouteHandlers();
}
@Override
public void handle(HttpExchange exchange) throws IOException {
String path = normalizePath(exchange.getRequestURI().getPath());
RestHandler handler = routeHandlers.get(path);
if (path.equals("")) {
String projectVersion = getVersion();
JSONObject response = new JSONObject();
response.put("version", projectVersion);
ResponseHandler.sendResponse(exchange, StatusCodes.HTTP_OK, "Server is running", response.toString());
} else if (handler != null) {
handler.handle(exchange);
} else {
ResponseHandler.sendResponse(exchange, StatusCodes.HTTP_BAD_REQUEST, "Invalid path: " + path);
}
}
private String normalizePath(String path) {
String[] parts = path.toLowerCase().split("/");
return parts.length > 1 ? parts[1] : "";
}
public static String getVersion() {
try {
File file = new File("pom.xml");
DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
Document doc = dBuilder.parse(file);
doc.getDocumentElement().normalize();
NodeList nodeList = doc.getElementsByTagName("version");
if (nodeList.getLength() > 0) {
return nodeList.item(0).getTextContent();
}
} catch (Exception e) {
e.printStackTrace();
}
return "Unknown";
}
}
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment