aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/main/java/org/traccar/storage/DatabaseStorage.java164
-rw-r--r--src/main/java/org/traccar/storage/MemoryStorage.java27
-rw-r--r--src/main/java/org/traccar/storage/QueryBuilder.java26
-rw-r--r--src/main/java/org/traccar/storage/Storage.java25
-rw-r--r--src/main/java/org/traccar/storage/StorageException.java17
-rw-r--r--src/main/java/org/traccar/storage/StorageName.java12
-rw-r--r--src/main/java/org/traccar/storage/query/Columns.java63
-rw-r--r--src/main/java/org/traccar/storage/query/Condition.java83
-rw-r--r--src/main/java/org/traccar/storage/query/Order.java25
-rw-r--r--src/main/java/org/traccar/storage/query/Request.java27
10 files changed, 464 insertions, 5 deletions
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 <T> List<T> getObjects(Class<T> 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<String, Object> variable : getConditionVariables(request.getCondition()).entrySet()) {
+ builder.setValue(variable.getKey(), variable.getValue());
+ }
+ return builder.executeQuery(clazz);
+ } catch (SQLException e) {
+ throw new StorageException(e);
+ }
+ }
+
+ @Override
+ public <T> 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 <T> 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<String, Object> 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<String, Object> 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<String, Object> getConditionVariables(Condition genericCondition) {
+ Map<String, Object> 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<String, String> 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 <T> List<T> getObjects(Class<T> clazz, Request request) throws StorageException {
+ return null;
+ }
+
+ @Override
+ public <T> long addObject(T entity, Request request) throws StorageException {
+ return 0;
+ }
+
+ @Override
+ public <T> 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> T executeQuerySingle(Class<T> clazz) throws SQLException {
- Collection<T> result = executeQuery(clazz);
+ List<T> result = executeQuery(clazz);
if (!result.isEmpty()) {
return result.iterator().next();
} else {
@@ -380,7 +396,7 @@ public final class QueryBuilder {
}
}
- public <T> Collection<T> executeQuery(Class<T> clazz) throws SQLException {
+ public <T> List<T> executeQuery(Class<T> clazz) throws SQLException {
List<T> result = new LinkedList<>();
if (query != null) {
@@ -458,7 +474,7 @@ public final class QueryBuilder {
return 0;
}
- public Collection<Permission> executePermissionsQuery() throws SQLException, ClassNotFoundException {
+ public List<Permission> executePermissionsQuery() throws SQLException, ClassNotFoundException {
List<Permission> 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 <T> List<T> getObjects(Class<T> clazz, Request request) throws StorageException;
+
+ abstract <T> long addObject(T entity, Request request) throws StorageException;
+
+ abstract <T> void updateObject(T entity, Request request) throws StorageException;
+
+ abstract void removeObject(Class<?> clazz, Request request) throws StorageException;
+
+ <T> T getObject(Class<T> 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<String> getColumns(Class<?> clazz);
+
+ protected List<String> getAllColumns(Class<?> clazz) {
+ List<String> 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<String> getColumns(Class<?> clazz) {
+ return getAllColumns(clazz);
+ }
+ }
+
+ public static class Include extends Columns {
+ private final List<String> columns;
+
+ public Include(String... columns) {
+ this.columns = Arrays.stream(columns).collect(Collectors.toList());
+ }
+
+ @Override
+ public List<String> getColumns(Class<?> clazz) {
+ return columns;
+ }
+ }
+
+ public static class Exclude extends Columns {
+ private final Set<String> columns;
+
+ public Exclude(String... columns) {
+ this.columns = Arrays.stream(columns).collect(Collectors.toSet());
+ }
+
+ @Override
+ public List<String> 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;
+ }
+
+}