From 79730c0bf5d5fbe83204e52e73b4bf47964c5a2d Mon Sep 17 00:00:00 2001 From: Anton Tananaev Date: Sun, 13 Feb 2022 17:01:56 -0800 Subject: Implement new storage framework --- .../java/org/traccar/storage/DatabaseStorage.java | 164 +++++++++++++++++++++ .../java/org/traccar/storage/MemoryStorage.java | 27 ++++ .../java/org/traccar/storage/QueryBuilder.java | 26 +++- src/main/java/org/traccar/storage/Storage.java | 25 ++++ .../java/org/traccar/storage/StorageException.java | 17 +++ src/main/java/org/traccar/storage/StorageName.java | 12 ++ .../java/org/traccar/storage/query/Columns.java | 63 ++++++++ .../java/org/traccar/storage/query/Condition.java | 83 +++++++++++ src/main/java/org/traccar/storage/query/Order.java | 25 ++++ .../java/org/traccar/storage/query/Request.java | 27 ++++ 10 files changed, 464 insertions(+), 5 deletions(-) create mode 100644 src/main/java/org/traccar/storage/DatabaseStorage.java create mode 100644 src/main/java/org/traccar/storage/MemoryStorage.java create mode 100644 src/main/java/org/traccar/storage/Storage.java create mode 100644 src/main/java/org/traccar/storage/StorageException.java create mode 100644 src/main/java/org/traccar/storage/StorageName.java create mode 100644 src/main/java/org/traccar/storage/query/Columns.java create mode 100644 src/main/java/org/traccar/storage/query/Condition.java create mode 100644 src/main/java/org/traccar/storage/query/Order.java create mode 100644 src/main/java/org/traccar/storage/query/Request.java diff --git a/src/main/java/org/traccar/storage/DatabaseStorage.java b/src/main/java/org/traccar/storage/DatabaseStorage.java new file mode 100644 index 000000000..0bcab7d20 --- /dev/null +++ b/src/main/java/org/traccar/storage/DatabaseStorage.java @@ -0,0 +1,164 @@ +package org.traccar.storage; + +import org.traccar.storage.query.Columns; +import org.traccar.storage.query.Condition; +import org.traccar.storage.query.Order; +import org.traccar.storage.query.Request; + +import javax.sql.DataSource; +import java.sql.SQLException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +public class DatabaseStorage extends Storage { + + private final DataSource dataSource; + + public DatabaseStorage(DataSource dataSource) { + this.dataSource = dataSource; + } + + @Override + public List getObjects(Class clazz, Request request) throws StorageException { + StringBuilder query = new StringBuilder("SELECT "); + query.append(formatColumns(request.getColumns(), clazz, c -> c)); + query.append(" FROM ").append(getTableName(clazz)); + query.append(formatCondition(request.getCondition())); + query.append(formatOrder(request.getOrder())); + try { + QueryBuilder builder = QueryBuilder.create(dataSource, query.toString()); + for (Map.Entry variable : getConditionVariables(request.getCondition()).entrySet()) { + builder.setValue(variable.getKey(), variable.getValue()); + } + return builder.executeQuery(clazz); + } catch (SQLException e) { + throw new StorageException(e); + } + } + + @Override + public long addObject(T entity, Request request) throws StorageException { + StringBuilder query = new StringBuilder("INSERT INTO "); + query.append(getTableName(entity.getClass())); + query.append("("); + query.append(formatColumns(request.getColumns(), entity.getClass(), c -> c)); + query.append(") VALUES ("); + query.append(formatColumns(request.getColumns(), entity.getClass(), c -> ':' + c)); + query.append(")"); + try { + QueryBuilder builder = QueryBuilder.create(dataSource, query.toString(), true); + builder.setObject(entity); + return builder.executeUpdate(); + } catch (SQLException e) { + throw new StorageException(e); + } + } + + @Override + public void updateObject(T entity, Request request) throws StorageException { + StringBuilder query = new StringBuilder("UPDATE "); + query.append(getTableName(entity.getClass())); + query.append(" SET "); + query.append(formatColumns(request.getColumns(), entity.getClass(), c -> c + " = :" + c)); + query.append(formatCondition(request.getCondition())); + try { + QueryBuilder builder = QueryBuilder.create(dataSource, query.toString()); + builder.setObject(entity); + for (Map.Entry variable : getConditionVariables(request.getCondition()).entrySet()) { + builder.setValue(variable.getKey(), variable.getValue()); + } + builder.executeUpdate(); + } catch (SQLException e) { + throw new StorageException(e); + } + } + + @Override + public void removeObject(Class clazz, Request request) throws StorageException { + StringBuilder query = new StringBuilder("DELETE FROM "); + query.append(getTableName(clazz)); + query.append(formatCondition(request.getCondition())); + try { + QueryBuilder builder = QueryBuilder.create(dataSource, query.toString()); + for (Map.Entry variable : getConditionVariables(request.getCondition()).entrySet()) { + builder.setValue(variable.getKey(), variable.getValue()); + } + builder.executeUpdate(); + } catch (SQLException e) { + throw new StorageException(e); + } + } + + private String getTableName(Class clazz) throws StorageException { + StorageName storageName = clazz.getAnnotation(StorageName.class); + if (storageName == null) { + throw new StorageException("StorageName annotation is missing"); + } + return storageName.value(); + } + + private Map getConditionVariables(Condition genericCondition) { + Map result = new HashMap<>(); + if (genericCondition instanceof Condition.Equals) { + Condition.Equals condition = (Condition.Equals) genericCondition; + result.put(condition.getVariable(), condition.getValue()); + } else if (genericCondition instanceof Condition.Between) { + Condition.Between condition = (Condition.Between) genericCondition; + result.put(condition.getFromVariable(), condition.getFromValue()); + result.put(condition.getToVariable(), condition.getToValue()); + } + return result; + } + + private String formatColumns(Columns columns, Class clazz, Function mapper) { + return columns.getColumns(clazz).stream().map(mapper).collect(Collectors.joining(", ")); + } + + private String formatCondition(Condition genericCondition) { + StringBuilder result = new StringBuilder(); + if (genericCondition != null) { + result.append(" WHERE "); + if (genericCondition instanceof Condition.Equals) { + + Condition.Equals condition = (Condition.Equals) genericCondition; + result.append(condition.getColumn()); + result.append(" == :"); + result.append(condition.getVariable()); + + } else if (genericCondition instanceof Condition.Between) { + + Condition.Between condition = (Condition.Between) genericCondition; + result.append(condition.getColumn()); + result.append(" BETWEEN :"); + result.append(condition.getFromVariable()); + result.append(" AND :"); + result.append(condition.getToVariable()); + + } else if (genericCondition instanceof Condition.And) { + + Condition.And condition = (Condition.And) genericCondition; + result.append(formatCondition(condition.getFirst())); + result.append(" AND "); + result.append(formatCondition(condition.getSecond())); + + } + } + return result.toString(); + } + + private String formatOrder(Order order) { + StringBuilder result = new StringBuilder(); + if (order != null) { + result.append(" ORDER BY "); + result.append(order.getColumn()); + if (order.getDescending()) { + result.append(" DESC"); + } + } + return result.toString(); + } + +} diff --git a/src/main/java/org/traccar/storage/MemoryStorage.java b/src/main/java/org/traccar/storage/MemoryStorage.java new file mode 100644 index 000000000..0ebd3b87e --- /dev/null +++ b/src/main/java/org/traccar/storage/MemoryStorage.java @@ -0,0 +1,27 @@ +package org.traccar.storage; + +import org.traccar.storage.query.Request; + +import java.util.List; + +public class MemoryStorage extends Storage { + + @Override + public List getObjects(Class clazz, Request request) throws StorageException { + return null; + } + + @Override + public long addObject(T entity, Request request) throws StorageException { + return 0; + } + + @Override + public void updateObject(T entity, Request request) throws StorageException { + } + + @Override + public void removeObject(Class clazz, Request request) throws StorageException { + } + +} diff --git a/src/main/java/org/traccar/storage/QueryBuilder.java b/src/main/java/org/traccar/storage/QueryBuilder.java index c7800f19b..838472c8b 100644 --- a/src/main/java/org/traccar/storage/QueryBuilder.java +++ b/src/main/java/org/traccar/storage/QueryBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 - 2020 Anton Tananaev (anton@traccar.org) + * Copyright 2015 - 2022 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -33,7 +33,6 @@ import java.sql.SQLException; import java.sql.Statement; import java.sql.Timestamp; import java.sql.Types; -import java.util.Collection; import java.util.Date; import java.util.HashMap; import java.util.LinkedHashMap; @@ -255,6 +254,23 @@ public final class QueryBuilder { return this; } + public QueryBuilder setValue(String name, Object value) throws SQLException { + if (value instanceof Boolean) { + setBoolean(name, (Boolean) value); + } else if (value instanceof Integer) { + setInteger(name, (Integer) value); + } else if (value instanceof Long) { + setLong(name, (Long) value); + } else if (value instanceof Double) { + setDouble(name, (Double) value); + } else if (value instanceof String) { + setString(name, (String) value); + } else if (value instanceof Date) { + setDate(name, (Date) value); + } + return this; + } + public QueryBuilder setObject(Object object) throws SQLException { Method[] methods = object.getClass().getMethods(); @@ -295,7 +311,7 @@ public final class QueryBuilder { } public T executeQuerySingle(Class clazz) throws SQLException { - Collection result = executeQuery(clazz); + List result = executeQuery(clazz); if (!result.isEmpty()) { return result.iterator().next(); } else { @@ -380,7 +396,7 @@ public final class QueryBuilder { } } - public Collection executeQuery(Class clazz) throws SQLException { + public List executeQuery(Class clazz) throws SQLException { List result = new LinkedList<>(); if (query != null) { @@ -458,7 +474,7 @@ public final class QueryBuilder { return 0; } - public Collection executePermissionsQuery() throws SQLException, ClassNotFoundException { + public List executePermissionsQuery() throws SQLException, ClassNotFoundException { List result = new LinkedList<>(); if (query != null) { try { diff --git a/src/main/java/org/traccar/storage/Storage.java b/src/main/java/org/traccar/storage/Storage.java new file mode 100644 index 000000000..b95ce9505 --- /dev/null +++ b/src/main/java/org/traccar/storage/Storage.java @@ -0,0 +1,25 @@ +package org.traccar.storage; + +import org.traccar.storage.query.Request; + +import java.util.List; + +public abstract class Storage { + + abstract List getObjects(Class clazz, Request request) throws StorageException; + + abstract long addObject(T entity, Request request) throws StorageException; + + abstract void updateObject(T entity, Request request) throws StorageException; + + abstract void removeObject(Class clazz, Request request) throws StorageException; + + T getObject(Class clazz, Request request) throws StorageException { + var objects = getObjects(clazz, request); + if (objects.isEmpty()) { + throw new StorageException("No objects found"); + } + return objects.get(0); + } + +} diff --git a/src/main/java/org/traccar/storage/StorageException.java b/src/main/java/org/traccar/storage/StorageException.java new file mode 100644 index 000000000..6c1e9c2ff --- /dev/null +++ b/src/main/java/org/traccar/storage/StorageException.java @@ -0,0 +1,17 @@ +package org.traccar.storage; + +public class StorageException extends Exception { + + public StorageException(String message) { + super(message); + } + + public StorageException(Throwable cause) { + super(cause); + } + + public StorageException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/src/main/java/org/traccar/storage/StorageName.java b/src/main/java/org/traccar/storage/StorageName.java new file mode 100644 index 000000000..b6fa55e02 --- /dev/null +++ b/src/main/java/org/traccar/storage/StorageName.java @@ -0,0 +1,12 @@ +package org.traccar.storage; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface StorageName { + String value(); +} diff --git a/src/main/java/org/traccar/storage/query/Columns.java b/src/main/java/org/traccar/storage/query/Columns.java new file mode 100644 index 000000000..2053c556f --- /dev/null +++ b/src/main/java/org/traccar/storage/query/Columns.java @@ -0,0 +1,63 @@ +package org.traccar.storage.query; + +import org.traccar.storage.QueryIgnore; + +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +abstract public class Columns { + + abstract public List getColumns(Class clazz); + + protected List getAllColumns(Class clazz) { + List columns = new LinkedList<>(); + Method[] methods = clazz.getMethods(); + for (Method method : methods) { + if (method.getName().startsWith("set") && method.getParameterTypes().length == 1 + && !method.isAnnotationPresent(QueryIgnore.class)) { + columns.add(method.getName().substring(3).toLowerCase()); + } + } + return columns; + } + + public static class All extends Columns { + @Override + public List getColumns(Class clazz) { + return getAllColumns(clazz); + } + } + + public static class Include extends Columns { + private final List columns; + + public Include(String... columns) { + this.columns = Arrays.stream(columns).collect(Collectors.toList()); + } + + @Override + public List getColumns(Class clazz) { + return columns; + } + } + + public static class Exclude extends Columns { + private final Set columns; + + public Exclude(String... columns) { + this.columns = Arrays.stream(columns).collect(Collectors.toSet()); + } + + @Override + public List getColumns(Class clazz) { + return getAllColumns(clazz).stream() + .filter(column -> !columns.contains(column)) + .collect(Collectors.toList()); + } + } + +} diff --git a/src/main/java/org/traccar/storage/query/Condition.java b/src/main/java/org/traccar/storage/query/Condition.java new file mode 100644 index 000000000..50d520bc7 --- /dev/null +++ b/src/main/java/org/traccar/storage/query/Condition.java @@ -0,0 +1,83 @@ +package org.traccar.storage.query; + +public interface Condition { + + class Equals implements Condition { + private final String column; + private final String variable; + private final Object value; + + public Equals(String column, String variable, Object value) { + this.column = column; + this.variable = variable; + this.value = value; + } + + public String getColumn() { + return column; + } + + public String getVariable() { + return variable; + } + + public Object getValue() { + return value; + } + } + + class Between implements Condition { + private final String column; + private final String fromVariable; + private final Object fromValue; + private final String toVariable; + private final Object toValue; + + public Between(String column, String fromVariable, Object fromValue, String toVariable, Object toValue) { + this.column = column; + this.fromVariable = fromVariable; + this.fromValue = fromValue; + this.toVariable = toVariable; + this.toValue = toValue; + } + + public String getColumn() { + return column; + } + + public String getFromVariable() { + return fromVariable; + } + + public Object getFromValue() { + return fromValue; + } + + public String getToVariable() { + return toVariable; + } + + public Object getToValue() { + return toValue; + } + } + + class And implements Condition { + private final Condition first; + private final Condition second; + + public And(Condition first, Condition second) { + this.first = first; + this.second = second; + } + + public Condition getFirst() { + return first; + } + + public Condition getSecond() { + return second; + } + } + +} diff --git a/src/main/java/org/traccar/storage/query/Order.java b/src/main/java/org/traccar/storage/query/Order.java new file mode 100644 index 000000000..6852c5ba8 --- /dev/null +++ b/src/main/java/org/traccar/storage/query/Order.java @@ -0,0 +1,25 @@ +package org.traccar.storage.query; + +public class Order { + + private final String column; + private final boolean descending; + + public Order(String column) { + this(false, column); + } + + public Order(boolean descending, String column) { + this.column = column; + this.descending = descending; + } + + public String getColumn() { + return column; + } + + public boolean getDescending() { + return descending; + } + +} diff --git a/src/main/java/org/traccar/storage/query/Request.java b/src/main/java/org/traccar/storage/query/Request.java new file mode 100644 index 000000000..8536cafd0 --- /dev/null +++ b/src/main/java/org/traccar/storage/query/Request.java @@ -0,0 +1,27 @@ +package org.traccar.storage.query; + +public class Request { + + private final Columns columns; + private final Condition condition; + private final Order order; + + public Request(Columns columns, Condition condition, Order order) { + this.columns = columns; + this.condition = condition; + this.order = order; + } + + public Columns getColumns() { + return columns; + } + + public Condition getCondition() { + return condition; + } + + public Order getOrder() { + return order; + } + +} -- cgit v1.2.3