Implement YAML config, start SQLite implementation

A config.yml file has been added, to allow configuring server settings.
It will be expanded with new settings in the future.

Also, SQLite support has been added, with a "database.sqlite" file.
A basic table with user/pass/userid columns has been added for testing
purposes.
This commit is contained in:
Bea 2020-08-21 16:11:32 +02:00
parent 4ab01c3787
commit ce172c3dc4
17 changed files with 469 additions and 5 deletions

2
.gitignore vendored
View File

@ -1,2 +1,4 @@
.idea/
target/
run/
*.ignore

19
pom.xml
View File

@ -6,7 +6,7 @@
<groupId>net.mindoverflow.webmarker</groupId>
<artifactId>WebMarker</artifactId>
<version>1.0-SNAPSHOT</version>
<version>0.0.1-alpha</version>
<packaging>jar</packaging>
<dependencies>
@ -16,12 +16,27 @@
<version>1.13.1</version>
<type>pom</type>
</dependency>
<dependency>
<groupId>ro.pippo</groupId>
<artifactId>pippo-controller</artifactId>
<version>1.13.1</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>2.0.0-alpha1</version>
</dependency>
<dependency>
<groupId>org.xerial</groupId>
<artifactId>sqlite-jdbc</artifactId>
<version>3.32.3.2</version>
</dependency>
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<version>1.21</version>
</dependency>
</dependencies>
<build>

View File

@ -1,13 +1,39 @@
package net.mindoverflow.webmarker;
import net.mindoverflow.webmarker.server.WebApplication;
import net.mindoverflow.webmarker.runnables.StatsRunnable;
import net.mindoverflow.webmarker.utils.Cached;
import net.mindoverflow.webmarker.utils.sql.SQLiteManager;
import net.mindoverflow.webmarker.webserver.WebApplication;
import net.mindoverflow.webmarker.utils.config.ConfigEntries;
import net.mindoverflow.webmarker.utils.config.ConfigManager;
import net.mindoverflow.webmarker.utils.messaging.Messenger;
import ro.pippo.core.Pippo;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class WebMarker {
private static final Messenger msg = new Messenger();
public static void main(String[] args)
{
ConfigManager.checkFiles();
ConfigManager.loadFiles();
Cached.sqlManager = new SQLiteManager();
Cached.sqlManager.initialize();
msg.info("Loading Pippo framework...");
final Pippo pippo = new Pippo(new WebApplication());
pippo.start();
msg.info("Loaded Pippo framework.");
msg.info("Starting webserver...");
int port = (int) ConfigEntries.WEBSERVER_PORT.getValue();
pippo.start(port);
msg.info("Started webserver.");
ScheduledExecutorService exec = Executors.newSingleThreadScheduledExecutor();
exec.scheduleAtFixedRate(new StatsRunnable(), 0, 5, TimeUnit.SECONDS);
}
}

View File

@ -0,0 +1,16 @@
package net.mindoverflow.webmarker.runnables;
import net.mindoverflow.webmarker.utils.messaging.Messenger;
public class StatsRunnable implements Runnable {
Messenger msg = new Messenger();
@Override
public void run()
{
double usedRam = (Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()) / 1000000.0;
msg.info("Used RAM: " + usedRam + "MB");
}
}

View File

@ -0,0 +1,8 @@
package net.mindoverflow.webmarker.utils;
import net.mindoverflow.webmarker.utils.sql.SQLiteManager;
public class Cached {
public static SQLiteManager sqlManager;
}

View File

@ -0,0 +1,27 @@
package net.mindoverflow.webmarker.utils.config;
public enum ConfigEntries
{
WEBSERVER_PORT("port", 7344);
private final String path;
private Object value;
ConfigEntries(String path, Object value)
{
this.path = path;
this.value = value;
}
public String getPath() {
return path;
}
public Object getValue() {
return value;
}
public void setValue(Object value) {
this.value = value;
}
}

View File

@ -0,0 +1,90 @@
package net.mindoverflow.webmarker.utils.config;
import net.mindoverflow.webmarker.utils.messaging.MessageLevel;
import net.mindoverflow.webmarker.utils.messaging.Messenger;
import org.yaml.snakeyaml.Yaml;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
public class ConfigManager
{
private static final Messenger msg = new Messenger();
public static void checkFiles()
{
msg.send(MessageLevel.INFO, "Checking configuration files: ");
File configFile = FileType.CONFIG_YAML.file;
if(!configFile.exists())
{
try {
InputStream stream = ConfigManager.class.getResourceAsStream("/" + configFile.getName());
Files.copy(stream, Paths.get(configFile.getAbsolutePath()));
stream.close();
}
catch (IOException e)
{
msg.sendLine(MessageLevel.NONE, "FATAL");
e.printStackTrace();
msg.critical("Error creating missing file!");
System.exit(1);
}
}
msg.sendLine(MessageLevel.NONE, "OK");
}
public static void loadFiles()
{
msg.send(MessageLevel.INFO, "Loading configuration files: ");
try {
InputStream stream = new FileInputStream(FileType.CONFIG_YAML.file);
Yaml yaml = new Yaml();
FileType.CONFIG_YAML.yaml = yaml.load(stream);
} catch (FileNotFoundException e) {
e.printStackTrace();
msg.critical("Error loading config file!");
}
for(ConfigEntries entry : ConfigEntries.values())
{
String path = entry.getPath();
Object value = FileType.CONFIG_YAML.yaml.get(path);
entry.setValue(value);
}
msg.sendLine(MessageLevel.NONE, "OK");
for(ConfigEntries entry : ConfigEntries.values())
{ msg.info(entry.name() + ": " + entry.getValue()); }
}
public static String getJarAbsolutePath()
{
Path currentPath = Paths.get("");
return currentPath.toAbsolutePath().toString();
}
public enum FileType
{
DATABASE_FILE(new File(getJarAbsolutePath() + File.separator + "database.sqlite"), null),
CONFIG_YAML(new File(getJarAbsolutePath() + File.separator + "config.yml"), new HashMap<>());
public File file;
public HashMap<String, Object> yaml;
FileType(File givenFile, HashMap<String, Object> yamlConfig)
{
file = givenFile;
yaml = yamlConfig;
}
}
}

View File

@ -0,0 +1,21 @@
package net.mindoverflow.webmarker.utils.messaging;
public enum MessageLevel {
INFO("[INFO] "),
WARNING("[WARNING] "),
CRITICAL("[CRITICAL] "),
NONE("");
private String prefix;
MessageLevel(String prefix)
{
this.prefix = prefix;
}
public String getPrefix()
{
return prefix;
}
}

View File

@ -0,0 +1,19 @@
package net.mindoverflow.webmarker.utils.messaging;
public class Messenger {
public void info(String message)
{ sendLine(MessageLevel.INFO, message); }
public void warn(String message)
{ sendLine(MessageLevel.WARNING, message); }
public void critical(String message)
{ sendLine(MessageLevel.CRITICAL, message); }
public void sendLine(MessageLevel level, String message)
{ System.out.println(level.getPrefix() + message); }
public void send(MessageLevel level, String message)
{ System.out.print(level.getPrefix() + message); }
}

View File

@ -0,0 +1,32 @@
package net.mindoverflow.webmarker.utils.sql;
import net.mindoverflow.webmarker.utils.sql.primitives.SQLColumn;
import net.mindoverflow.webmarker.utils.sql.primitives.SQLDataType;
public enum FDatabaseColumn
{
ALL(new SQLColumn("*"), null),
USERNAME(new SQLColumn("username"), SQLDataType.VARCHAR_128),
PASSWORD(new SQLColumn("password"), SQLDataType.VARCHAR_128),
USERID(new SQLColumn("userid"), SQLDataType.VARCHAR_128),
;
private final SQLColumn column;
private final SQLDataType type;
FDatabaseColumn(SQLColumn column, SQLDataType type)
{
this.column = column;
this.type = type;
}
public SQLColumn getColumn()
{ return column; }
public SQLDataType getDataType()
{ return type; }
}

View File

@ -0,0 +1,26 @@
package net.mindoverflow.webmarker.utils.sql;
import net.mindoverflow.webmarker.utils.sql.primitives.SQLTable;
import java.util.ArrayList;
public enum FDatabaseTable
{
USERS(new SQLTable("users", // table name
new ArrayList<>(){{ // columns
add(FDatabaseColumn.USERID);
add(FDatabaseColumn.USERNAME);
add(FDatabaseColumn.PASSWORD);
}})),
;
private final SQLTable table;
FDatabaseTable(SQLTable table)
{ this.table = table; }
public SQLTable getTable()
{ return table; }
}

View File

@ -0,0 +1,113 @@
package net.mindoverflow.webmarker.utils.sql;
import net.mindoverflow.webmarker.utils.config.ConfigManager;
import net.mindoverflow.webmarker.utils.messaging.MessageLevel;
import net.mindoverflow.webmarker.utils.messaging.Messenger;
import net.mindoverflow.webmarker.utils.sql.primitives.SQLDataType;
import net.mindoverflow.webmarker.utils.sql.primitives.SQLTable;
import java.sql.*;
import java.util.List;
public class SQLiteManager {
private final Messenger msg = new Messenger();
private Connection connection;
public void initialize()
{
msg.send(MessageLevel.INFO, "Connecting to SQLite database: ");
String url = "jdbc:sqlite:" + ConfigManager.FileType.DATABASE_FILE.file.getAbsolutePath();
try {
connection = DriverManager.getConnection(url);
if(connection != null && !connection.isClosed())
{
msg.sendLine(MessageLevel.NONE, "OK");
doInitialSetup();
}
} catch (SQLException throwables) {
msg.sendLine(MessageLevel.NONE, "FATAL");
throwables.printStackTrace();
msg.critical("Error connecting to SQLite database!");
System.exit(1);
}
}
private void doInitialSetup()
{
for(FDatabaseTable currentTable : FDatabaseTable.values())
{
if(!tableExists(currentTable))
{
msg.info("Creating SQLite table `" + currentTable.getTable().getTableSQLName() + "`");
createTable(currentTable);
}
}
}
private boolean tableExists(FDatabaseTable tableEnum)
{
String name = tableEnum.getTable().getTableSQLName();
try
{
DatabaseMetaData meta = connection.getMetaData();
ResultSet result = meta.getTables(null, null, name, null);
while(result.next())
{
String tableName = result.getString("TABLE_NAME");
if(tableName != null && tableName.equals(name.toLowerCase())) return true;
}
} catch (SQLException e)
{
e.printStackTrace();
msg.critical("Error checking SQLite table " + name + "!");
System.exit(1);
}
return false;
}
private void createTable(FDatabaseTable tableEnum)
{
SQLTable table = tableEnum.getTable();
List<FDatabaseColumn> columns = table.getColumns();
List<SQLDataType> dataTypes = table.getDataTypes();
StringBuilder query = new StringBuilder("CREATE TABLE IF NOT EXISTS ").append(table.getTableSQLName()).append(" (");
int pos = 0;
for(FDatabaseColumn column : columns)
{
query.append(column.getColumn().getColumnSQLName()).append(" ").append(dataTypes.get(pos).getSQLName());
pos++;
if(pos < columns.size()) // we don't want to append a colon to the last entry
{ query.append(", "); }
}
query.append(");");
executeUpdate(query.toString());
}
private void executeUpdate(String query)
{
try
{
if(connection == null || connection.isClosed())
{
msg.critical("Lost connection to SQLite database!");
System.exit(1);
}
Statement statement = connection.createStatement();
statement.executeUpdate(query);
} catch (SQLException e)
{
e.printStackTrace();
msg.critical("Error executing SQLite update!");
System.exit(1);
}
}
}

View File

@ -0,0 +1,12 @@
package net.mindoverflow.webmarker.utils.sql.primitives;
public class SQLColumn {
private final String columnName;
public SQLColumn(String name)
{ columnName = name; }
public String getColumnSQLName()
{ return columnName; }
}

View File

@ -0,0 +1,18 @@
package net.mindoverflow.webmarker.utils.sql.primitives;
public enum SQLDataType
{
INTEGER("INTEGER NOT NULL"),
VARCHAR_128("VARCHAR(128)"),
TEXT("TEXT"),
DATETIME("DATETIME"),
;
private String sqlName;
SQLDataType(String sqlName)
{ this.sqlName = sqlName; }
public String getSQLName()
{ return this.sqlName; }
}

View File

@ -0,0 +1,31 @@
package net.mindoverflow.webmarker.utils.sql.primitives;
import net.mindoverflow.webmarker.utils.sql.FDatabaseColumn;
import java.util.ArrayList;
import java.util.List;
public class SQLTable
{
private final String tableName;
private final List<FDatabaseColumn> columns;
private final List<SQLDataType> columnTypes = new ArrayList<>();
public SQLTable(String tableName, List<FDatabaseColumn> columns)
{
this.tableName = tableName;
this.columns = columns;
for(FDatabaseColumn column : columns)
{ columnTypes.add(column.getDataType()); }
}
public String getTableSQLName()
{ return tableName; }
public List<FDatabaseColumn> getColumns()
{ return columns; }
public List<SQLDataType> getDataTypes()
{ return columnTypes; }
}

View File

@ -1,4 +1,4 @@
package net.mindoverflow.webmarker.server;
package net.mindoverflow.webmarker.webserver;
import net.mindoverflow.webmarker.utils.URLMap;
import ro.pippo.controller.ControllerApplication;
@ -46,6 +46,13 @@ public class WebApplication extends ControllerApplication {
});
POST("/post", routeContext -> {
int userId = routeContext.getParameter("id").toInt();
String url = routeContext.getParameter("url").toString();
routeContext.send("AAAAAAAAAAAAA " + url);
});
ANY("/.*", new TrailingSlashHandler(false)); // remove trailing slash
}
}

View File

@ -0,0 +1 @@
port: 7344