[DEV] continue refacto
This commit is contained in:
parent
d8c6de7bde
commit
e64c70cd86
@ -20,7 +20,6 @@
|
||||
</classpathentry>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER">
|
||||
<attributes>
|
||||
<attribute name="module" value="true"/>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
|
@ -58,7 +58,7 @@ public class AnnotationTools {
|
||||
}
|
||||
return tmp;
|
||||
}
|
||||
|
||||
|
||||
public static Integer getLimitSize(final Field element) throws Exception {
|
||||
final Annotation[] annotation = element.getDeclaredAnnotationsByType(Column.class);
|
||||
if (annotation.length == 0) {
|
||||
@ -69,7 +69,7 @@ public class AnnotationTools {
|
||||
}
|
||||
return ((Column) annotation[0]).length();
|
||||
}
|
||||
|
||||
|
||||
public static boolean isAnnotationGroup(final Field field, final Class<?> annotationType) {
|
||||
try {
|
||||
final Annotation[] anns = field.getAnnotations();
|
||||
@ -92,22 +92,37 @@ public class AnnotationTools {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean getNotNull(final Field element) throws Exception {
|
||||
|
||||
public static String getFieldName(final Field element) throws Exception {
|
||||
final Annotation[] annotation = element.getDeclaredAnnotationsByType(Column.class);
|
||||
if (annotation.length == 0) {
|
||||
return true;
|
||||
return element.getName();
|
||||
}
|
||||
if (annotation.length > 1) {
|
||||
throw new Exception("Must not have more than 1 element @Column on " + element.getClass().getCanonicalName());
|
||||
}
|
||||
return ((Column) annotation[0]).nullable();
|
||||
String name = ((Column) annotation[0]).name();
|
||||
if (name.isBlank()) {
|
||||
return element.getName();
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
public static boolean getNotNull(final Field element) throws Exception {
|
||||
final Annotation[] annotation = element.getDeclaredAnnotationsByType(Column.class);
|
||||
if (annotation.length == 0) {
|
||||
return false;
|
||||
}
|
||||
if (annotation.length > 1) {
|
||||
throw new Exception("Must not have more than 1 element @Column on " + element.getClass().getCanonicalName());
|
||||
}
|
||||
return !((Column) annotation[0]).nullable();
|
||||
}
|
||||
|
||||
public static boolean isPrimaryKey(final Field element) throws Exception {
|
||||
final Annotation[] annotation = element.getDeclaredAnnotationsByType(Column.class);
|
||||
if (annotation.length == 0) {
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
if (annotation.length > 1) {
|
||||
throw new Exception("Must not have more than 1 element @Column on " + element.getClass().getCanonicalName());
|
||||
@ -125,5 +140,5 @@ public class AnnotationTools {
|
||||
}
|
||||
return ((GeneratedValue) annotation[0]).strategy();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -7,6 +7,6 @@ import java.lang.annotation.Target;
|
||||
|
||||
@Target(ElementType.ANNOTATION_TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface SQLAddOn {
|
||||
public @interface DataAddOn {
|
||||
|
||||
}
|
@ -5,8 +5,8 @@ import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Target(ElementType.FIELD)
|
||||
@Target({ ElementType.TYPE })
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface SQLForeignKey {
|
||||
String value();
|
||||
public @interface SQLWhere {
|
||||
String clause();
|
||||
}
|
@ -5,11 +5,11 @@ import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.kar.archidata.annotation.SQLAddOn;
|
||||
import org.kar.archidata.annotation.DataAddOn;
|
||||
|
||||
@Target(ElementType.FIELD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@SQLAddOn
|
||||
@DataAddOn
|
||||
public @interface SQLTableExternalForeinKeyAsList {
|
||||
|
||||
}
|
||||
|
@ -1,28 +0,0 @@
|
||||
package org.kar.archidata.annotation.addOn;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.kar.archidata.annotation.SQLAddOn;
|
||||
|
||||
@Target(ElementType.FIELD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@SQLAddOn
|
||||
public @interface SQLTableExternalLink {
|
||||
public static String AUTOMATIC = "__auto__";
|
||||
|
||||
// If automatic table name, the table name is: parentTableName_externalTableName__link
|
||||
String tableName() default AUTOMATIC;
|
||||
|
||||
// If automatic table name, the name of the foreign table is manage with the variable name.
|
||||
String externalTableName() default AUTOMATIC;
|
||||
|
||||
// If the external link have a field to filter with a specific value (name of the field)
|
||||
String filterField() default AUTOMATIC;
|
||||
|
||||
// If the external link have a field to filter with a specific value (value of the field)
|
||||
String filterValue() default AUTOMATIC;
|
||||
|
||||
}
|
29
src/org/kar/archidata/backup/BackupEngine.java
Normal file
29
src/org/kar/archidata/backup/BackupEngine.java
Normal file
@ -0,0 +1,29 @@
|
||||
package org.kar.archidata.backup;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class BackupEngine {
|
||||
|
||||
public enum StoreMode {
|
||||
JSON, SQL
|
||||
}
|
||||
|
||||
private final String pathStore;
|
||||
private final StoreMode mode;
|
||||
private List<Class<?>> classes = new ArrayList<>();
|
||||
|
||||
public BackupEngine(String pathToStoreDB, StoreMode mode) {
|
||||
this.pathStore = pathToStoreDB;
|
||||
this.mode = mode;
|
||||
}
|
||||
|
||||
public void addClass(Class<?> clazz) {
|
||||
classes.add(clazz);
|
||||
}
|
||||
|
||||
public void store() {
|
||||
// TODO ...
|
||||
|
||||
}
|
||||
}
|
@ -7,6 +7,7 @@ import java.util.List;
|
||||
|
||||
import org.kar.archidata.db.DBConfig;
|
||||
import org.kar.archidata.db.DBEntry;
|
||||
import org.kar.archidata.sqlWrapper.QuerryOptions;
|
||||
import org.kar.archidata.sqlWrapper.SqlWrapper;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@ -61,7 +62,7 @@ public class MigrationEngine {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
List<MigrationModel> data = SqlWrapper.gets(MigrationModel.class, false);
|
||||
List<MigrationModel> data = SqlWrapper.gets(MigrationModel.class, new QuerryOptions("SQLNotRead_disable", true));
|
||||
if (data == null) {
|
||||
LOGGER.error("Can not collect the migration table in the DB:{}");
|
||||
return null;
|
||||
@ -72,7 +73,7 @@ public class MigrationEngine {
|
||||
}
|
||||
LOGGER.debug("List of migrations:");
|
||||
for (MigrationModel elem : data) {
|
||||
LOGGER.debug(" - date={} name={} end={}", elem.modify_date, elem.name, elem.terminated);
|
||||
LOGGER.debug(" - date={} name={} end={}", elem.updatedAt, elem.name, elem.terminated);
|
||||
}
|
||||
return data.get(data.size() - 1);
|
||||
} catch (Exception ex) {
|
||||
@ -111,6 +112,7 @@ public class MigrationEngine {
|
||||
LOGGER.info("Verify existance of migration table '{}'", "KAR_migration");
|
||||
exist = SqlWrapper.isTableExist("KAR_migration");
|
||||
if (!exist) {
|
||||
LOGGER.info("'{}' Does not exist create a new one...", "KAR_migration");
|
||||
// create the table:
|
||||
List<String> sqlQuery;
|
||||
try {
|
||||
|
@ -13,24 +13,24 @@ import org.slf4j.LoggerFactory;
|
||||
public class MigrationSqlStep implements MigrationInterface {
|
||||
final static Logger LOGGER = LoggerFactory.getLogger(MigrationSqlStep.class);
|
||||
private final List<String> actions = new ArrayList<>();
|
||||
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return getClass().getCanonicalName();
|
||||
}
|
||||
|
||||
|
||||
public void display() {
|
||||
for (int iii = 0; iii < this.actions.size(); iii++) {
|
||||
final String action = this.actions.get(iii);
|
||||
LOGGER.info(" >>>> SQL ACTION : {}/{} ==> \n{}", iii, this.actions.size(), action);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean applyMigration(final DBEntry entry, final StringBuilder log, final MigrationModel model) {
|
||||
for (int iii = 0; iii < this.actions.size(); iii++) {
|
||||
log.append("action [" + iii + "/" + this.actions.size() + "]\n");
|
||||
LOGGER.info(" >>>> SQL ACTION : {}/{}", iii, this.actions.size());
|
||||
log.append("action [" + (iii + 1) + "/" + this.actions.size() + "]\n");
|
||||
LOGGER.info(" >>>> SQL ACTION : {}/{}", iii + 1, this.actions.size());
|
||||
final String action = this.actions.get(iii);
|
||||
LOGGER.info("SQL request: ```{}```", action);
|
||||
log.append("SQL: " + action + "\n");
|
||||
@ -49,8 +49,8 @@ public class MigrationSqlStep implements MigrationInterface {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
log.append("action [" + iii + "/" + this.actions.size() + "] ==> DONE\n");
|
||||
LOGGER.info(" >>>> SQL ACTION : {}/{} ==> DONE", iii, this.actions.size());
|
||||
log.append("action [" + (iii + 1) + "/" + this.actions.size() + "] ==> DONE\n");
|
||||
LOGGER.info(" >>>> SQL ACTION : {}/{} ==> DONE", iii + 1, this.actions.size());
|
||||
model.stepId = iii + 1;
|
||||
model.log = log.toString();
|
||||
try {
|
||||
@ -68,24 +68,24 @@ public class MigrationSqlStep implements MigrationInterface {
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean revertMigration(final DBEntry entry, final StringBuilder log) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public void addAction(final String action) {
|
||||
this.actions.add(action);
|
||||
}
|
||||
|
||||
|
||||
public void addClass(final Class<?> clazz) throws Exception {
|
||||
final List<String> tmp = SqlWrapper.createTable(clazz, false);
|
||||
this.actions.addAll(tmp);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int getNumberOfStep() {
|
||||
return this.actions.size();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
|
||||
//@SQLWhere(clause = "deleted=false")
|
||||
public class GenericTable {
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
@ -30,12 +31,25 @@ public class GenericTable {
|
||||
@CreationTimestamp
|
||||
@Column(nullable = false)
|
||||
@SQLComment("Create time of the object")
|
||||
@SQLDefault("CURRENT_TIMESTAMP(3)")
|
||||
public Timestamp create_date = null;
|
||||
public Timestamp createdAt = null;
|
||||
@SQLNotRead
|
||||
@UpdateTimestamp
|
||||
@Column(nullable = false)
|
||||
@SQLComment("When update the object")
|
||||
@SQLDefault("CURRENT_TIMESTAMP(3)")
|
||||
public Timestamp modify_date = null;
|
||||
public Timestamp updatedAt = null;
|
||||
}
|
||||
/* TODO Later:
|
||||
@SQLNotRead
|
||||
@CreationTimestamp
|
||||
@Column(nullable = false)
|
||||
@Temporal(TemporalType.TIMESTAMP)
|
||||
@SQLComment("Create time of the object")
|
||||
public Date createdAt = null;
|
||||
|
||||
@SQLNotRead
|
||||
@UpdateTimestamp
|
||||
@Column(nullable = false)
|
||||
@Temporal(TemporalType.TIMESTAMP)
|
||||
@SQLComment("When update the object")
|
||||
public Date updatedAt = null;
|
||||
*/
|
||||
|
@ -19,13 +19,12 @@ import java.util.List;
|
||||
|
||||
import org.kar.archidata.annotation.SQLDefault;
|
||||
import org.kar.archidata.annotation.SQLIfNotExists;
|
||||
import org.kar.archidata.sqlWrapper.Foreign;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.FetchType;
|
||||
import jakarta.persistence.ManyToOne;
|
||||
import jakarta.persistence.ManyToMany;
|
||||
import jakarta.persistence.Table;
|
||||
|
||||
@Table(name = "user")
|
||||
@ -45,10 +44,10 @@ public class User extends GenericTable {
|
||||
@SQLDefault("'0'")
|
||||
@Column(nullable = false)
|
||||
public boolean removed = false;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
public List<Foreign<Data>> covers;
|
||||
|
||||
|
||||
@ManyToMany(fetch = FetchType.LAZY, targetEntity = Data.class)
|
||||
public List<Long> covers;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "User [login=" + this.login + ", last=" + this.lastConnection + ", admin=" + this.admin + "]";
|
||||
|
@ -1,5 +1,7 @@
|
||||
package org.kar.archidata.sqlWrapper;
|
||||
|
||||
// Mark as deprecated while the concept is not ready ...
|
||||
@Deprecated
|
||||
public class Foreign<T> {
|
||||
public final Long id;
|
||||
public final T data;
|
||||
|
@ -1,11 +0,0 @@
|
||||
package org.kar.archidata.sqlWrapper;
|
||||
|
||||
import org.kar.archidata.sqlWrapper.addOn.AddOnSQLTableExternalForeinKeyAsList;
|
||||
import org.kar.archidata.sqlWrapper.addOn.AddOnSQLTableExternalLink;
|
||||
|
||||
public class GenericAddOn {
|
||||
public static void addGenericAddOn() {
|
||||
SqlWrapper.addAddOn(new AddOnSQLTableExternalLink());
|
||||
SqlWrapper.addAddOn(new AddOnSQLTableExternalForeinKeyAsList());
|
||||
}
|
||||
}
|
@ -19,8 +19,10 @@ public class QuerryAnd implements QuerryItem {
|
||||
}
|
||||
|
||||
public void generateQuerry(StringBuilder querry, String tableName) {
|
||||
querry.append(" (");
|
||||
boolean first = false;
|
||||
if (this.childs.size() >= 1) {
|
||||
querry.append(" (");
|
||||
}
|
||||
boolean first = true;
|
||||
for (QuerryItem elem : this.childs) {
|
||||
if (first) {
|
||||
first = false;
|
||||
@ -29,7 +31,9 @@ public class QuerryAnd implements QuerryItem {
|
||||
}
|
||||
elem.generateQuerry(querry, tableName);
|
||||
}
|
||||
querry.append(")");
|
||||
if (this.childs.size() >= 1) {
|
||||
querry.append(")");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
35
src/org/kar/archidata/sqlWrapper/QuerryOptions.java
Normal file
35
src/org/kar/archidata/sqlWrapper/QuerryOptions.java
Normal file
@ -0,0 +1,35 @@
|
||||
package org.kar.archidata.sqlWrapper;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class QuerryOptions {
|
||||
private final Map<String, Object> options = new HashMap<>();
|
||||
|
||||
public QuerryOptions() {
|
||||
|
||||
}
|
||||
|
||||
public QuerryOptions(String key, Object value) {
|
||||
options.put(key, value);
|
||||
}
|
||||
|
||||
public QuerryOptions(String key, Object value, String key2, Object value2) {
|
||||
options.put(key, value);
|
||||
options.put(key2, value2);
|
||||
}
|
||||
|
||||
public QuerryOptions(String key, Object value, String key2, Object value2, String key3, Object value3) {
|
||||
options.put(key, value);
|
||||
options.put(key2, value2);
|
||||
options.put(key3, value3);
|
||||
}
|
||||
|
||||
public void put(String key, Object value) {
|
||||
options.put(key, value);
|
||||
}
|
||||
|
||||
public Object get(String value) {
|
||||
return options.get(value);
|
||||
}
|
||||
}
|
@ -11,8 +11,10 @@ public class QuerryOr implements QuerryItem {
|
||||
}
|
||||
|
||||
public void generateQuerry(StringBuilder querry, String tableName) {
|
||||
querry.append(" (");
|
||||
boolean first = false;
|
||||
if (this.childs.size() >= 1) {
|
||||
querry.append(" (");
|
||||
}
|
||||
boolean first = true;
|
||||
for (QuerryItem elem : this.childs) {
|
||||
if (first) {
|
||||
first = false;
|
||||
@ -21,7 +23,9 @@ public class QuerryOr implements QuerryItem {
|
||||
}
|
||||
elem.generateQuerry(querry, tableName);
|
||||
}
|
||||
querry.append(")");
|
||||
if (this.childs.size() >= 1) {
|
||||
querry.append(")");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -18,13 +18,16 @@ import java.util.List;
|
||||
import org.kar.archidata.GlobalConfiguration;
|
||||
import org.kar.archidata.annotation.AnnotationTools;
|
||||
import org.kar.archidata.annotation.CreationTimestamp;
|
||||
import org.kar.archidata.annotation.SQLAddOn;
|
||||
import org.kar.archidata.annotation.DataAddOn;
|
||||
import org.kar.archidata.annotation.SQLDefault;
|
||||
import org.kar.archidata.annotation.SQLDeleted;
|
||||
import org.kar.archidata.annotation.SQLIfNotExists;
|
||||
import org.kar.archidata.annotation.SQLNotRead;
|
||||
import org.kar.archidata.annotation.UpdateTimestamp;
|
||||
import org.kar.archidata.db.DBEntry;
|
||||
import org.kar.archidata.sqlWrapper.addOn.AddOnManyToMany;
|
||||
import org.kar.archidata.sqlWrapper.addOn.AddOnManyToOne;
|
||||
import org.kar.archidata.sqlWrapper.addOn.AddOnSQLTableExternalForeinKeyAsList;
|
||||
import org.kar.archidata.util.ConfigBaseVariable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@ -33,30 +36,38 @@ import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.ManyToMany;
|
||||
import jakarta.persistence.ManyToOne;
|
||||
import jakarta.ws.rs.InternalServerErrorException;
|
||||
|
||||
public class SqlWrapper {
|
||||
static final Logger LOGGER = LoggerFactory.getLogger(SqlWrapper.class);
|
||||
static final List<SqlWrapperAddOn> addOn = new ArrayList<>();
|
||||
|
||||
|
||||
static {
|
||||
addOn.add(new AddOnManyToMany());
|
||||
addOn.add(new AddOnManyToOne());
|
||||
addOn.add(new AddOnSQLTableExternalForeinKeyAsList());
|
||||
}
|
||||
|
||||
public static void addAddOn(final SqlWrapperAddOn addOn) {
|
||||
SqlWrapper.addOn.add(addOn);
|
||||
};
|
||||
|
||||
|
||||
public static class ExceptionDBInterface extends Exception {
|
||||
private static final long serialVersionUID = 1L;
|
||||
public int errorID;
|
||||
|
||||
|
||||
public ExceptionDBInterface(final int errorId, final String message) {
|
||||
super(message);
|
||||
this.errorID = errorId;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public SqlWrapper() {
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
public static boolean isDBExist(final String name) throws InternalServerErrorException {
|
||||
if (ConfigBaseVariable.getDBType().equals("sqlite")) {
|
||||
// no base manage in sqLite ...
|
||||
@ -98,7 +109,7 @@ public class SqlWrapper {
|
||||
}
|
||||
throw new InternalServerErrorException("Can Not manage the DB-access");
|
||||
}
|
||||
|
||||
|
||||
public static boolean createDB(final String name) {
|
||||
if (ConfigBaseVariable.getDBType().equals("sqlite")) {
|
||||
// no base manage in sqLite ...
|
||||
@ -117,7 +128,7 @@ public class SqlWrapper {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static boolean isTableExist(final String name) throws InternalServerErrorException {
|
||||
try {
|
||||
String request = "";
|
||||
@ -157,7 +168,7 @@ public class SqlWrapper {
|
||||
}
|
||||
throw new InternalServerErrorException("Can Not manage the DB-access");
|
||||
}
|
||||
|
||||
|
||||
public static String convertTypeInSQL(final Class<?> type) throws Exception {
|
||||
if (!ConfigBaseVariable.getDBType().equals("sqlite")) {
|
||||
if (type == Long.class || type == long.class) {
|
||||
@ -212,7 +223,7 @@ public class SqlWrapper {
|
||||
}
|
||||
throw new Exception("Imcompatible type of element in object for: " + type.getCanonicalName());
|
||||
}
|
||||
|
||||
|
||||
protected static <T> void setValuedb(final Class<?> type, final T data, int index, final Field field, final PreparedStatement ps)
|
||||
throws IllegalArgumentException, IllegalAccessException, SQLException {
|
||||
if (type == Long.class) {
|
||||
@ -283,7 +294,7 @@ public class SqlWrapper {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected static <T> void setValueFromDb(final Class<?> type, final T data, final int index, final Field field, final ResultSet rs)
|
||||
throws IllegalArgumentException, IllegalAccessException, SQLException {
|
||||
if (type == Long.class) {
|
||||
@ -380,11 +391,22 @@ public class SqlWrapper {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static boolean isAddOnField(final Field field) {
|
||||
return AnnotationTools.isAnnotationGroup(field, SQLAddOn.class);
|
||||
boolean ret = AnnotationTools.isAnnotationGroup(field, DataAddOn.class);
|
||||
if (ret == true) {
|
||||
return true;
|
||||
}
|
||||
// The specific element of the JPA manage fy generic add-on system:
|
||||
if (field.getDeclaredAnnotationsByType(ManyToMany.class).length != 0) {
|
||||
return true;
|
||||
}
|
||||
if (field.getDeclaredAnnotationsByType(ManyToOne.class).length != 0) {
|
||||
return true;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
public static SqlWrapperAddOn findAddOnforField(final Field field) {
|
||||
for (final SqlWrapperAddOn elem : addOn) {
|
||||
if (elem.isCompatibleField(field)) {
|
||||
@ -393,11 +415,11 @@ public class SqlWrapper {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public static <T> T insert(final T data) throws Exception {
|
||||
final Class<?> clazz = data.getClass();
|
||||
//public static NodeSmall createNode(String typeInNode, String name, String descrition, Long parentId) {
|
||||
|
||||
|
||||
DBEntry entry = DBEntry.createInterface(GlobalConfiguration.dbConfig);
|
||||
// real add in the BDD:
|
||||
try {
|
||||
@ -407,7 +429,7 @@ public class SqlWrapper {
|
||||
querry.append("INSERT INTO `");
|
||||
querry.append(tableName);
|
||||
querry.append("` (");
|
||||
|
||||
|
||||
boolean firstField = true;
|
||||
int count = 0;
|
||||
for (final Field elem : clazz.getFields()) {
|
||||
@ -437,7 +459,7 @@ public class SqlWrapper {
|
||||
}
|
||||
}
|
||||
count++;
|
||||
final String name = elem.getName();
|
||||
final String name = AnnotationTools.getFieldName(elem);
|
||||
if (firstField) {
|
||||
firstField = false;
|
||||
} else {
|
||||
@ -537,16 +559,16 @@ public class SqlWrapper {
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
|
||||
// seems a good idea, but very dangerous if we not filter input data... if set an id it can be complicated...
|
||||
public static <T> T insertWithJson(final Class<T> clazz, final String jsonData) throws Exception {
|
||||
final ObjectMapper mapper = new ObjectMapper();
|
||||
// parse the object to be sure the data are valid:
|
||||
final T data = mapper.readValue(jsonData, clazz);
|
||||
|
||||
|
||||
return insert(data);
|
||||
}
|
||||
|
||||
|
||||
public static <T> int update(final Class<T> clazz, final long id, final String jsonData) throws Exception {
|
||||
final ObjectMapper mapper = new ObjectMapper();
|
||||
// parse the object to be sure the data are valid:
|
||||
@ -558,7 +580,7 @@ public class SqlWrapper {
|
||||
iterator.forEachRemaining(e -> keys.add(e));
|
||||
return update(data, id, keys);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @param <T>
|
||||
@ -571,7 +593,7 @@ public class SqlWrapper {
|
||||
public static <T> int update(final T data, final long id, final List<String> filterValue) throws Exception {
|
||||
final Class<?> clazz = data.getClass();
|
||||
//public static NodeSmall createNode(String typeInNode, String name, String description, Long parentId) {
|
||||
|
||||
|
||||
DBEntry entry = DBEntry.createInterface(GlobalConfiguration.dbConfig);
|
||||
// real add in the BDD:
|
||||
try {
|
||||
@ -581,7 +603,7 @@ public class SqlWrapper {
|
||||
querry.append("UPDATE `");
|
||||
querry.append(tableName);
|
||||
querry.append("` SET ");
|
||||
|
||||
|
||||
boolean firstField = true;
|
||||
Field primaryKeyField = null;
|
||||
for (final Field elem : clazz.getFields()) {
|
||||
@ -601,7 +623,7 @@ public class SqlWrapper {
|
||||
if (createTime) {
|
||||
continue;
|
||||
}
|
||||
final String name = elem.getName();
|
||||
final String name = AnnotationTools.getFieldName(elem);
|
||||
final boolean updateTime = elem.getDeclaredAnnotationsByType(UpdateTimestamp.class).length != 0;
|
||||
if (!updateTime && !filterValue.contains(name)) {
|
||||
continue;
|
||||
@ -628,7 +650,7 @@ public class SqlWrapper {
|
||||
}
|
||||
}
|
||||
querry.append(" WHERE `");
|
||||
querry.append(primaryKeyField.getName());
|
||||
querry.append(AnnotationTools.getFieldName(primaryKeyField));
|
||||
querry.append("` = ?");
|
||||
firstField = true;
|
||||
// logger.debug("generate the querry: '{}'", querry.toString());
|
||||
@ -651,7 +673,7 @@ public class SqlWrapper {
|
||||
if (createTime) {
|
||||
continue;
|
||||
}
|
||||
final String name = elem.getName();
|
||||
final String name = AnnotationTools.getFieldName(elem);
|
||||
final boolean updateTime = elem.getDeclaredAnnotationsByType(UpdateTimestamp.class).length != 0;
|
||||
if (updateTime || !filterValue.contains(name)) {
|
||||
continue;
|
||||
@ -681,7 +703,7 @@ public class SqlWrapper {
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static void addElement(final PreparedStatement ps, final Object value, final int iii) throws Exception {
|
||||
if (value.getClass() == Long.class) {
|
||||
ps.setLong(iii, (Long) value);
|
||||
@ -709,32 +731,19 @@ public class SqlWrapper {
|
||||
throw new Exception("Not manage type ==> need to add it ...");
|
||||
}
|
||||
}
|
||||
|
||||
public static <T> T getWhere(final Class<T> clazz, final QuerryItem condition) throws Exception {
|
||||
return getWhere(clazz, condition, false);
|
||||
}
|
||||
|
||||
public static <T> T getWhere(final Class<T> clazz, final QuerryItem condition, final boolean full) throws Exception {
|
||||
final List<T> values = getsWhere(clazz, condition, full, 1);
|
||||
if (values.size() == 0) {
|
||||
return null;
|
||||
|
||||
public static void whereAppendQuery(final StringBuilder querry, final String tableName, final QuerryItem condition, final QuerryOptions options) throws ExceptionDBInterface {
|
||||
boolean exclude_deleted = true;
|
||||
if (options != null) {
|
||||
Object data = options.get("SQLDeleted_disable");
|
||||
if (data instanceof Boolean elem) {
|
||||
exclude_deleted = !(elem == true);
|
||||
} else {
|
||||
if (data != null) {
|
||||
LOGGER.error("'SQLDeleted_disable' ==> has not a boolean value: {}", data);
|
||||
}
|
||||
}
|
||||
}
|
||||
return values.get(0);
|
||||
}
|
||||
|
||||
public static <T> List<T> getsWhere(final Class<T> clazz, final QuerryItem condition) throws Exception {
|
||||
return getsWhere(clazz, condition, null, false, null);
|
||||
}
|
||||
|
||||
public static <T> List<T> getsWhere(final Class<T> clazz, final QuerryItem condition, final boolean full) throws Exception {
|
||||
return getsWhere(clazz, condition, null, full, null);
|
||||
}
|
||||
|
||||
public static <T> List<T> getsWhere(final Class<T> clazz, final QuerryItem condition, final boolean full, final Integer linit) throws Exception {
|
||||
return getsWhere(clazz, condition, null, full, linit);
|
||||
}
|
||||
|
||||
public static void whereAppendQuery(final StringBuilder querry, final String tableName, final QuerryItem condition, final boolean exclude_deleted) throws ExceptionDBInterface {
|
||||
// Check if we have a condition to generate
|
||||
if (condition == null) {
|
||||
if (exclude_deleted) {
|
||||
@ -746,7 +755,7 @@ public class SqlWrapper {
|
||||
}
|
||||
querry.append(" WHERE (");
|
||||
condition.generateQuerry(querry, tableName);
|
||||
|
||||
|
||||
querry.append(") ");
|
||||
if (exclude_deleted) {
|
||||
querry.append("AND ");
|
||||
@ -754,7 +763,7 @@ public class SqlWrapper {
|
||||
querry.append(".deleted = false ");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static void whereInjectValue(final PreparedStatement ps, final QuerryItem condition) throws Exception {
|
||||
// Check if we have a condition to generate
|
||||
if (condition == null) {
|
||||
@ -763,29 +772,67 @@ public class SqlWrapper {
|
||||
int iii = 1;
|
||||
iii = condition.injectQuerry(ps, iii);
|
||||
}
|
||||
|
||||
|
||||
public static int executeSimpleQuerry(final String querry, final boolean root) throws SQLException, IOException {
|
||||
final DBEntry entry = DBEntry.createInterface(GlobalConfiguration.dbConfig, root);
|
||||
final Statement stmt = entry.connection.createStatement();
|
||||
return stmt.executeUpdate(querry);
|
||||
}
|
||||
|
||||
|
||||
public static int executeSimpleQuerry(final String querry) throws SQLException, IOException {
|
||||
return executeSimpleQuerry(querry, false);
|
||||
}
|
||||
|
||||
|
||||
public static boolean executeQuerry(final String querry, final boolean root) throws SQLException, IOException {
|
||||
final DBEntry entry = DBEntry.createInterface(GlobalConfiguration.dbConfig, root);
|
||||
final Statement stmt = entry.connection.createStatement();
|
||||
return stmt.execute(querry);
|
||||
}
|
||||
|
||||
|
||||
public static boolean executeQuerry(final String querry) throws SQLException, IOException {
|
||||
return executeQuerry(querry, false);
|
||||
}
|
||||
|
||||
|
||||
public static <T> T getWhere(final Class<T> clazz, final QuerryItem condition) throws Exception {
|
||||
return getWhere(clazz, condition, null);
|
||||
}
|
||||
|
||||
public static <T> T getWhere(final Class<T> clazz, final QuerryItem condition, final QuerryOptions options) throws Exception {
|
||||
final List<T> values = getsWhere(clazz, condition, options, 1);
|
||||
if (values.size() == 0) {
|
||||
return null;
|
||||
}
|
||||
return values.get(0);
|
||||
}
|
||||
|
||||
public static <T> List<T> getsWhere(final Class<T> clazz, final QuerryItem condition) throws Exception {
|
||||
return getsWhere(clazz, condition, null, null, null);
|
||||
}
|
||||
|
||||
public static <T> List<T> getsWhere(final Class<T> clazz, final QuerryItem condition, final QuerryOptions options) throws Exception {
|
||||
return getsWhere(clazz, condition, null, options, null);
|
||||
}
|
||||
|
||||
public static <T> List<T> getsWhere(final Class<T> clazz, final QuerryItem condition, final QuerryOptions options, final Integer linit) throws Exception {
|
||||
return getsWhere(clazz, condition, null, options, linit);
|
||||
}
|
||||
|
||||
// TODO: set limit as an querry Option...
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> List<T> getsWhere(final Class<T> clazz, final QuerryItem condition, final String orderBy, final boolean full, final Integer linit) throws Exception {
|
||||
public static <T> List<T> getsWhere(final Class<T> clazz, final QuerryItem condition, final String orderBy, final QuerryOptions options, final Integer linit) throws Exception {
|
||||
|
||||
boolean readAllfields = false;
|
||||
if (options != null) {
|
||||
Object data = options.get("SQLNotRead_disable");
|
||||
if (data instanceof Boolean elem) {
|
||||
readAllfields = elem;
|
||||
} else {
|
||||
if (data != null) {
|
||||
LOGGER.error("'SQLNotRead_disable' ==> has not a boolean value: {}", data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DBEntry entry = DBEntry.createInterface(GlobalConfiguration.dbConfig);
|
||||
final List<T> outs = new ArrayList<>();
|
||||
// real add in the BDD:
|
||||
@ -796,7 +843,7 @@ public class SqlWrapper {
|
||||
querry.append("SELECT ");
|
||||
//querry.append(tableName);
|
||||
//querry.append(" SET ");
|
||||
|
||||
|
||||
boolean firstField = true;
|
||||
int count = 0;
|
||||
boolean hasDeleted = false;
|
||||
@ -809,18 +856,15 @@ public class SqlWrapper {
|
||||
if (addOn != null) {
|
||||
continue;
|
||||
}
|
||||
final boolean createTime = elem.getDeclaredAnnotationsByType(CreationTimestamp.class).length != 0;
|
||||
if (!full && createTime) {
|
||||
// TODO: Manage it with AddOn
|
||||
final boolean notRead = elem.getDeclaredAnnotationsByType(SQLNotRead.class).length != 0;
|
||||
if (!readAllfields && notRead) {
|
||||
continue;
|
||||
}
|
||||
if (!hasDeleted) {
|
||||
hasDeleted = elem.getDeclaredAnnotationsByType(SQLDeleted.class).length != 0;
|
||||
}
|
||||
final String name = elem.getName();
|
||||
final boolean updateTime = elem.getDeclaredAnnotationsByType(UpdateTimestamp.class).length != 0;
|
||||
if (!full && updateTime) {
|
||||
continue;
|
||||
}
|
||||
final String name = AnnotationTools.getFieldName(elem);
|
||||
count++;
|
||||
if (firstField) {
|
||||
firstField = false;
|
||||
@ -830,13 +874,13 @@ public class SqlWrapper {
|
||||
querry.append(" ");
|
||||
querry.append(tableName);
|
||||
querry.append(".");
|
||||
|
||||
|
||||
querry.append(name);
|
||||
}
|
||||
querry.append(" FROM `");
|
||||
querry.append(tableName);
|
||||
querry.append("` ");
|
||||
whereAppendQuery(querry, tableName, condition, firstField);
|
||||
whereAppendQuery(querry, tableName, condition, options);
|
||||
if (orderBy != null && orderBy.length() >= 1) {
|
||||
querry.append(" ORDER BY ");
|
||||
//querry.append(tableName);
|
||||
@ -865,13 +909,9 @@ public class SqlWrapper {
|
||||
if (addOn != null) {
|
||||
continue;
|
||||
}
|
||||
final boolean createTime = elem.getDeclaredAnnotationsByType(CreationTimestamp.class).length != 0;
|
||||
if (!full && createTime) {
|
||||
continue;
|
||||
}
|
||||
//String name = elem.getName();
|
||||
final boolean updateTime = elem.getDeclaredAnnotationsByType(UpdateTimestamp.class).length != 0;
|
||||
if (!full && updateTime) {
|
||||
// TODO: Manage it with AddOn
|
||||
final boolean notRead = elem.getDeclaredAnnotationsByType(SQLNotRead.class).length != 0;
|
||||
if (!readAllfields && notRead) {
|
||||
continue;
|
||||
}
|
||||
setValueFromDb(elem.getType(), data, count, elem, rs);
|
||||
@ -880,9 +920,10 @@ public class SqlWrapper {
|
||||
final T out = (T) data;
|
||||
outs.add(out);
|
||||
}
|
||||
|
||||
|
||||
} catch (final SQLException ex) {
|
||||
ex.printStackTrace();
|
||||
throw ex;
|
||||
} catch (final Exception ex) {
|
||||
ex.printStackTrace();
|
||||
} finally {
|
||||
@ -891,7 +932,8 @@ public class SqlWrapper {
|
||||
}
|
||||
return outs;
|
||||
}
|
||||
|
||||
|
||||
// TODO : detect the @Id
|
||||
public static <T> T get(final Class<T> clazz, final long id) throws Exception {
|
||||
Field primaryKeyField = null;
|
||||
for (final Field elem : clazz.getFields()) {
|
||||
@ -904,145 +946,66 @@ public class SqlWrapper {
|
||||
}
|
||||
}
|
||||
if (primaryKeyField != null) {
|
||||
return SqlWrapper.getWhere(clazz, new QuerryCondition(primaryKeyField.getName(), "=", id), false);
|
||||
return SqlWrapper.getWhere(clazz, new QuerryCondition(AnnotationTools.getFieldName(primaryKeyField), "=", id));
|
||||
}
|
||||
throw new Exception("Missing primary Key...");
|
||||
}
|
||||
|
||||
|
||||
public static String getCurrentTimeStamp() {
|
||||
return LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS"));
|
||||
}
|
||||
|
||||
public static <T> List<T> gets(final Class<T> clazz, final boolean full) throws Exception {
|
||||
LOGGER.debug("request get {} start @{}", clazz.getCanonicalName(), getCurrentTimeStamp());
|
||||
DBEntry entry = DBEntry.createInterface(GlobalConfiguration.dbConfig);
|
||||
final List<T> out = new ArrayList<>();
|
||||
// real add in the BDD:
|
||||
|
||||
public static <T> List<T> gets(final Class<T> clazz) throws Exception {
|
||||
return getsWhere(clazz, null);
|
||||
}
|
||||
|
||||
public static <T> List<T> gets(final Class<T> clazz, final QuerryOptions options) throws Exception {
|
||||
return getsWhere(clazz, null, options);
|
||||
}
|
||||
|
||||
public static boolean hasDeletedField(final Class<?> clazz) throws Exception {
|
||||
try {
|
||||
final String tableName = AnnotationTools.getTableName(clazz);
|
||||
//boolean createIfNotExist = clazz.getDeclaredAnnotationsByType(SQLIfNotExists.class).length != 0;
|
||||
final StringBuilder querry = new StringBuilder();
|
||||
querry.append("SELECT ");
|
||||
boolean firstField = true;
|
||||
int count = 0;
|
||||
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! A revoir, il faut faire une liste dynamique qui dépend des add_ons....
|
||||
//StateLoad[] autoClasify = new StateLoad[clazz.getFields().length];
|
||||
final List<StateLoad> autoClasify = new ArrayList<>();
|
||||
int indexAutoClasify = 0;
|
||||
boolean hasDeleted = false;
|
||||
for (final Field elem : clazz.getFields()) {
|
||||
// static field is only for internal global declaration ==> remove it ..
|
||||
if (java.lang.reflect.Modifier.isStatic(elem.getModifiers())) {
|
||||
continue;
|
||||
}
|
||||
final boolean notRead = elem.getDeclaredAnnotationsByType(SQLNotRead.class).length != 0;
|
||||
if (!full && notRead) {
|
||||
autoClasify.add(StateLoad.DISABLE);
|
||||
continue;
|
||||
}
|
||||
if (!hasDeleted) {
|
||||
hasDeleted = elem.getDeclaredAnnotationsByType(SQLDeleted.class).length != 0;
|
||||
}
|
||||
final String name = elem.getName();
|
||||
if (firstField) {
|
||||
firstField = false;
|
||||
} else {
|
||||
querry.append(",");
|
||||
}
|
||||
final SqlWrapperAddOn addOn = findAddOnforField(elem);
|
||||
if (addOn != null) {
|
||||
count += addOn.generateQuerry(tableName, elem, querry, name, autoClasify);
|
||||
} else {
|
||||
count++;
|
||||
autoClasify.add(StateLoad.NORMAL);
|
||||
querry.append(" ");
|
||||
querry.append(tableName);
|
||||
querry.append(".");
|
||||
querry.append(name);
|
||||
if (elem.getDeclaredAnnotationsByType(SQLDeleted.class).length != 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
querry.append(" FROM `");
|
||||
querry.append(tableName);
|
||||
querry.append("` ");
|
||||
if (hasDeleted) {
|
||||
querry.append(" WHERE ");
|
||||
//querry.append(tableName);
|
||||
//querry.append(".");
|
||||
//querry.append(primaryKeyField.getName());
|
||||
//querry.append(" = ?");
|
||||
//querry.append(" AND ");
|
||||
querry.append(tableName);
|
||||
querry.append(".deleted = false ");
|
||||
}
|
||||
firstField = true;
|
||||
LOGGER.debug("generate the querry: '{}'", querry.toString());
|
||||
LOGGER.debug("request get {} prepare @{}", clazz.getCanonicalName(), getCurrentTimeStamp());
|
||||
// prepare the request:
|
||||
final PreparedStatement ps = entry.connection.prepareStatement(querry.toString(), Statement.RETURN_GENERATED_KEYS);
|
||||
|
||||
LOGGER.debug("request get {} querry @{}", clazz.getCanonicalName(), getCurrentTimeStamp());
|
||||
// execute the request
|
||||
final ResultSet rs = ps.executeQuery();
|
||||
LOGGER.debug("request get {} transform @{}", clazz.getCanonicalName(), getCurrentTimeStamp());
|
||||
|
||||
while (rs.next()) {
|
||||
indexAutoClasify = 0;
|
||||
final Object data = clazz.getConstructors()[0].newInstance();
|
||||
count = 1;
|
||||
for (final Field elem : clazz.getFields()) {
|
||||
if (java.lang.reflect.Modifier.isStatic(elem.getModifiers())) {
|
||||
continue;
|
||||
}
|
||||
/*
|
||||
boolean notRead = elem.getDeclaredAnnotationsByType(SQLNotRead.class).length != 0;
|
||||
*/
|
||||
final boolean notRead = autoClasify.get(indexAutoClasify) == StateLoad.DISABLE;
|
||||
if (!full && notRead) {
|
||||
indexAutoClasify++;
|
||||
continue;
|
||||
}
|
||||
//String name = elem.getName();
|
||||
//boolean linkGeneric = elem.getDeclaredAnnotationsByType(SQLTableLinkGeneric.class).length != 0;
|
||||
|
||||
final SqlWrapperAddOn addOn = findAddOnforField(elem);
|
||||
if (addOn != null) {
|
||||
count += addOn.fillFromQuerry(rs, elem, data, count);
|
||||
} else {
|
||||
setValueFromDb(elem.getType(), data, count, elem, rs);
|
||||
count++;
|
||||
}
|
||||
indexAutoClasify++;
|
||||
}
|
||||
//logger.debug("Read: {}", (T)data);
|
||||
out.add((T) data);
|
||||
}
|
||||
|
||||
LOGGER.debug("request get {} ready @{}", clazz.getCanonicalName(), getCurrentTimeStamp());
|
||||
|
||||
} catch (final SQLException ex) {
|
||||
} catch (final Exception ex) {
|
||||
ex.printStackTrace();
|
||||
} finally {
|
||||
entry.close();
|
||||
entry = null;
|
||||
}
|
||||
return out;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// TODO : detect the @Id
|
||||
public static void delete(final Class<?> clazz, final long id) throws Exception {
|
||||
// TODO: I am not sure this is a real good idea.
|
||||
boolean hasDeleted = hasDeletedField(clazz);
|
||||
if (hasDeleted == true) {
|
||||
deleteSoft(clazz, id);
|
||||
} else {
|
||||
deleteHard(clazz, id);
|
||||
}
|
||||
}
|
||||
|
||||
public static int setDelete(final Class<?> clazz, final long id) throws Exception {
|
||||
|
||||
public static void deleteHard(final Class<?> clazz, final long id) throws Exception {
|
||||
throw new Exception("Not implemented delete hard ...");
|
||||
}
|
||||
|
||||
private static int deleteSoft(final Class<?> clazz, final long id) throws Exception {
|
||||
return setDeleteWhere(clazz, new QuerryCondition("id", "=", id));
|
||||
}
|
||||
|
||||
|
||||
public static String getDBNow() {
|
||||
if (!ConfigBaseVariable.getDBType().equals("sqlite")) {
|
||||
return "now(3)";
|
||||
}
|
||||
return "DATE()";
|
||||
}
|
||||
|
||||
|
||||
public static int setDeleteWhere(final Class<?> clazz, final QuerryItem condition) throws Exception {
|
||||
final String tableName = AnnotationTools.getTableName(clazz);
|
||||
DBEntry entry = DBEntry.createInterface(GlobalConfiguration.dbConfig);
|
||||
@ -1052,7 +1015,7 @@ public class SqlWrapper {
|
||||
querry.append("` SET `modify_date`=");
|
||||
querry.append(getDBNow());
|
||||
querry.append(", `deleted`=true ");
|
||||
whereAppendQuery(querry, tableName, condition, false);
|
||||
whereAppendQuery(querry, tableName, condition, null);
|
||||
try {
|
||||
final PreparedStatement ps = entry.connection.prepareStatement(querry.toString());
|
||||
whereInjectValue(ps, condition);
|
||||
@ -1063,21 +1026,28 @@ public class SqlWrapper {
|
||||
entry = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static int unsetDelete(final Class<?> clazz, final long id) throws Exception {
|
||||
return unsetDeleteWhere(clazz, new QuerryCondition("id", "=", id));
|
||||
}
|
||||
|
||||
|
||||
public static int unsetDeleteWhere(final Class<?> clazz, final QuerryItem condition) throws Exception {
|
||||
final String tableName = AnnotationTools.getTableName(clazz);
|
||||
DBEntry entry = DBEntry.createInterface(GlobalConfiguration.dbConfig);
|
||||
final StringBuilder querry = new StringBuilder();
|
||||
querry.append("UPDATE `");
|
||||
querry.append(tableName);
|
||||
querry.append("` SET `modify_date`=");
|
||||
querry.append("` SET ");
|
||||
/*
|
||||
* is is needed only for SQLite ???
|
||||
querry.append("`modify_date`=");
|
||||
querry.append(getDBNow());
|
||||
querry.append(", `deleted`=false ");
|
||||
whereAppendQuery(querry, tableName, condition, false);
|
||||
querry.append(", ");
|
||||
*/
|
||||
querry.append("`deleted`=false ");
|
||||
// need to disable the deleted false because the model must be unselected to be updated.
|
||||
QuerryOptions options = new QuerryOptions("SQLDeleted_disable", true);
|
||||
whereAppendQuery(querry, tableName, condition, options);
|
||||
try {
|
||||
final PreparedStatement ps = entry.connection.prepareStatement(querry.toString());
|
||||
whereInjectValue(ps, condition);
|
||||
@ -1088,25 +1058,25 @@ public class SqlWrapper {
|
||||
entry = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static List<String> createTable(final Class<?> clazz) throws Exception {
|
||||
return createTable(clazz, true);
|
||||
}
|
||||
|
||||
|
||||
public static void createTablesSpecificType(final String tableName, final Field elem, final StringBuilder mainTableBuilder, final List<String> ListOtherTables, final boolean createIfNotExist,
|
||||
final boolean createDrop, final int fieldId, final Class<?> classModel) throws Exception {
|
||||
final String name = elem.getName();
|
||||
final String name = AnnotationTools.getFieldName(elem);
|
||||
final Integer limitSize = AnnotationTools.getLimitSize(elem);
|
||||
final boolean notNull = AnnotationTools.getNotNull(elem);
|
||||
|
||||
final boolean primaryKey = AnnotationTools.isPrimaryKey(elem);
|
||||
final GenerationType strategy = AnnotationTools.getStrategy(elem);
|
||||
|
||||
|
||||
final boolean createTime = elem.getDeclaredAnnotationsByType(CreationTimestamp.class).length != 0;
|
||||
final boolean updateTime = elem.getDeclaredAnnotationsByType(UpdateTimestamp.class).length != 0;
|
||||
final String comment = AnnotationTools.getComment(elem);
|
||||
final String defaultValue = AnnotationTools.getDefault(elem);
|
||||
|
||||
|
||||
if (fieldId == 0) {
|
||||
mainTableBuilder.append("\n\t\t`");
|
||||
} else {
|
||||
@ -1168,11 +1138,13 @@ public class SqlWrapper {
|
||||
}
|
||||
} else if (defaultValue == null) {
|
||||
if (updateTime || createTime) {
|
||||
if (!ConfigBaseVariable.getDBType().equals("sqlite")) {
|
||||
if (ConfigBaseVariable.getDBType().equals("sqlite")) {
|
||||
mainTableBuilder.append("DEFAULT CURRENT_TIMESTAMP ");
|
||||
} else {
|
||||
mainTableBuilder.append("DEFAULT CURRENT_TIMESTAMP(3) ");
|
||||
}
|
||||
} else if (primaryKey) {
|
||||
mainTableBuilder.append("NOT NULL ");
|
||||
} else {
|
||||
mainTableBuilder.append("DEFAULT NULL ");
|
||||
}
|
||||
@ -1180,11 +1152,11 @@ public class SqlWrapper {
|
||||
mainTableBuilder.append("DEFAULT ");
|
||||
mainTableBuilder.append(defaultValue);
|
||||
mainTableBuilder.append(" ");
|
||||
|
||||
|
||||
}
|
||||
if (primaryKey && ConfigBaseVariable.getDBType().equals("sqlite")) {
|
||||
mainTableBuilder.append("PRIMARY KEY ");
|
||||
|
||||
|
||||
}
|
||||
if (strategy == GenerationType.IDENTITY) {
|
||||
if (!ConfigBaseVariable.getDBType().equals("sqlite")) {
|
||||
@ -1195,14 +1167,14 @@ public class SqlWrapper {
|
||||
} else if (strategy != null) {
|
||||
throw new Exception("Can not generate a stategy different of IDENTITY");
|
||||
}
|
||||
|
||||
|
||||
if (comment != null && !ConfigBaseVariable.getDBType().equals("sqlite")) {
|
||||
mainTableBuilder.append("COMMENT '");
|
||||
mainTableBuilder.append(comment.replace('\'', '\''));
|
||||
mainTableBuilder.append("' ");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static List<String> createTable(final Class<?> clazz, final boolean createDrop) throws Exception {
|
||||
final String tableName = AnnotationTools.getTableName(clazz);
|
||||
final boolean createIfNotExist = clazz.getDeclaredAnnotationsByType(SQLIfNotExists.class).length != 0;
|
||||
@ -1227,7 +1199,7 @@ public class SqlWrapper {
|
||||
for (final Field elem : clazz.getFields()) {
|
||||
// DEtect the primary key (support only one primary key right now...
|
||||
if (AnnotationTools.isPrimaryKey(elem)) {
|
||||
primaryKeys.add(elem.getName());
|
||||
primaryKeys.add(AnnotationTools.getFieldName(elem));
|
||||
}
|
||||
}
|
||||
|
||||
@ -1238,14 +1210,14 @@ public class SqlWrapper {
|
||||
}
|
||||
if (isAddOnField(elem)) {
|
||||
final SqlWrapperAddOn addOn = findAddOnforField(elem);
|
||||
LOGGER.info("Create type for: {} ==> {} (ADD-ON)", elem.getName(), elem.getType());
|
||||
LOGGER.info("Create type for: {} ==> {} (ADD-ON)", AnnotationTools.getFieldName(elem), elem.getType());
|
||||
if (addOn != null) {
|
||||
addOn.createTables(tableName, elem, out, outList, createIfNotExist, createDrop, fieldId);
|
||||
} else {
|
||||
throw new Exception("Element matked as add-on but add-on does not loaded: table:" + tableName + " field name=" + elem.getName() + " type=" + elem.getType());
|
||||
throw new Exception("Element matked as add-on but add-on does not loaded: table:" + tableName + " field name=" + AnnotationTools.getFieldName(elem) + " type=" + elem.getType());
|
||||
}
|
||||
} else {
|
||||
LOGGER.info("Create type for: {} ==> {}", elem.getName(), elem.getType());
|
||||
LOGGER.info("Create type for: {} ==> {}", AnnotationTools.getFieldName(elem), elem.getType());
|
||||
SqlWrapper.createTablesSpecificType(tableName, elem, out, outList, createIfNotExist, createDrop, fieldId, elem.getType());
|
||||
}
|
||||
fieldId++;
|
||||
@ -1268,5 +1240,5 @@ public class SqlWrapper {
|
||||
outList.add(out.toString());
|
||||
return outList;
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -32,9 +32,9 @@ public interface SqlWrapperAddOn {
|
||||
// External mean that the type of the object is absolutely not obvious...
|
||||
boolean isExternal();
|
||||
|
||||
int generateQuerry(String tableName, Field elem, StringBuilder querry, String name, List<StateLoad> autoClasify);
|
||||
int generateQuerry(String tableName, Field elem, StringBuilder querry, String name, List<StateLoad> autoClasify, QuerryOptions options);
|
||||
|
||||
int fillFromQuerry(ResultSet rs, Field elem, Object data, int count) throws SQLException, IllegalArgumentException, IllegalAccessException;
|
||||
int fillFromQuerry(ResultSet rs, Field elem, Object data, int count, QuerryOptions options) throws SQLException, IllegalArgumentException, IllegalAccessException;
|
||||
|
||||
boolean canUpdate();
|
||||
|
||||
|
@ -12,8 +12,8 @@ import java.util.stream.Collectors;
|
||||
|
||||
import org.kar.archidata.GlobalConfiguration;
|
||||
import org.kar.archidata.annotation.AnnotationTools;
|
||||
import org.kar.archidata.annotation.addOn.SQLTableExternalLink;
|
||||
import org.kar.archidata.db.DBEntry;
|
||||
import org.kar.archidata.sqlWrapper.QuerryOptions;
|
||||
import org.kar.archidata.sqlWrapper.SqlWrapper;
|
||||
import org.kar.archidata.sqlWrapper.SqlWrapper.ExceptionDBInterface;
|
||||
import org.kar.archidata.sqlWrapper.SqlWrapperAddOn;
|
||||
@ -22,8 +22,10 @@ import org.kar.archidata.util.ConfigBaseVariable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class AddOnSQLTableExternalLink implements SqlWrapperAddOn {
|
||||
static final Logger LOGGER = LoggerFactory.getLogger(AddOnSQLTableExternalLink.class);
|
||||
import jakarta.persistence.ManyToMany;
|
||||
|
||||
public class AddOnManyToMany implements SqlWrapperAddOn {
|
||||
static final Logger LOGGER = LoggerFactory.getLogger(AddOnManyToMany.class);
|
||||
|
||||
/**
|
||||
* Convert the list if external id in a string '-' separated
|
||||
@ -58,17 +60,17 @@ public class AddOnSQLTableExternalLink implements SqlWrapperAddOn {
|
||||
|
||||
@Override
|
||||
public Class<?> getAnnotationClass() {
|
||||
return SQLTableExternalLink.class;
|
||||
return ManyToMany.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSQLFieldType(final Field elem) {
|
||||
return "STRING";
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCompatibleField(final Field elem) {
|
||||
final SQLTableExternalLink decorators = elem.getDeclaredAnnotation(SQLTableExternalLink.class);
|
||||
final ManyToMany decorators = elem.getDeclaredAnnotation(ManyToMany.class);
|
||||
return decorators != null;
|
||||
}
|
||||
|
||||
@ -92,7 +94,7 @@ public class AddOnSQLTableExternalLink implements SqlWrapperAddOn {
|
||||
}
|
||||
|
||||
@Override
|
||||
public int generateQuerry(final String tableName, final Field elem, final StringBuilder querry, final String name, final List<StateLoad> autoClasify) {
|
||||
public int generateQuerry(final String tableName, final Field elem, final StringBuilder querry, final String name, final List<StateLoad> autoClasify, QuerryOptions options) {
|
||||
|
||||
autoClasify.add(StateLoad.ARRAY);
|
||||
String localName = name;
|
||||
@ -136,7 +138,7 @@ public class AddOnSQLTableExternalLink implements SqlWrapperAddOn {
|
||||
}
|
||||
|
||||
@Override
|
||||
public int fillFromQuerry(final ResultSet rs, final Field elem, final Object data, final int count) throws SQLException, IllegalArgumentException, IllegalAccessException {
|
||||
public int fillFromQuerry(final ResultSet rs, final Field elem, final Object data, final int count, QuerryOptions options) throws SQLException, IllegalArgumentException, IllegalAccessException {
|
||||
throw new IllegalAccessException("This Add-on has not the capability to insert data directly in DB");
|
||||
}
|
||||
|
||||
@ -204,10 +206,11 @@ public class AddOnSQLTableExternalLink implements SqlWrapperAddOn {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO : refacto this table to manage a generic table with dynamic name to be serializable with the default system
|
||||
@Override
|
||||
public void createTables(final String tableName, final Field elem, final StringBuilder mainTableBuilder, final List<String> ListOtherTables, final boolean createIfNotExist,
|
||||
final boolean createDrop, final int fieldId) throws Exception {
|
||||
final String name = elem.getName();
|
||||
final String name = AnnotationTools.getFieldName(elem);
|
||||
String localName = name;
|
||||
if (name.endsWith("s")) {
|
||||
localName = name.substring(0, name.length() - 1);
|
||||
@ -230,13 +233,13 @@ public class AddOnSQLTableExternalLink implements SqlWrapperAddOn {
|
||||
if (!ConfigBaseVariable.getDBType().equals("sqlite")) {
|
||||
otherTable.append("\t\t`id` bigint NOT NULL AUTO_INCREMENT,\n");
|
||||
otherTable.append("\t\t`deleted` tinyint(1) NOT NULL DEFAULT '0',\n");
|
||||
otherTable.append("\t\t`create_date` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),\n");
|
||||
otherTable.append("\t\t`modify_date` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),\n");
|
||||
otherTable.append("\t\t`createdAt` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),\n");
|
||||
otherTable.append("\t\t`updatedAt` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),\n");
|
||||
} else {
|
||||
otherTable.append("\t\t`id` INTEGER PRIMARY KEY AUTOINCREMENT,\n");
|
||||
otherTable.append("\t\t`deleted` INTEGER NOT NULL DEFAULT '0',\n");
|
||||
otherTable.append("\t\t`create_date` INTEGER NOT NULL DEFAULT CURRENT_TIMESTAMP,\n");
|
||||
otherTable.append("\t\t`modify_date` INTEGER NOT NULL DEFAULT CURRENT_TIMESTAMP,\n");
|
||||
otherTable.append("\t\t`createdAt` INTEGER NOT NULL DEFAULT CURRENT_TIMESTAMP,\n");
|
||||
otherTable.append("\t\t`updatedAt` INTEGER NOT NULL DEFAULT CURRENT_TIMESTAMP,\n");
|
||||
}
|
||||
otherTable.append("\t\t`");
|
||||
otherTable.append(tableName);
|
126
src/org/kar/archidata/sqlWrapper/addOn/AddOnManyToOne.java
Normal file
126
src/org/kar/archidata/sqlWrapper/addOn/AddOnManyToOne.java
Normal file
@ -0,0 +1,126 @@
|
||||
package org.kar.archidata.sqlWrapper.addOn;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Types;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.kar.archidata.sqlWrapper.QuerryOptions;
|
||||
import org.kar.archidata.sqlWrapper.SqlWrapper;
|
||||
import org.kar.archidata.sqlWrapper.SqlWrapperAddOn;
|
||||
import org.kar.archidata.sqlWrapper.StateLoad;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jakarta.persistence.ManyToOne;
|
||||
|
||||
public class AddOnManyToOne implements SqlWrapperAddOn {
|
||||
static final Logger LOGGER = LoggerFactory.getLogger(AddOnManyToMany.class);
|
||||
|
||||
/**
|
||||
* Convert the list if external id in a string '-' separated
|
||||
* @param ids List of value (null are removed)
|
||||
* @return '-' string separated
|
||||
*/
|
||||
protected static String getStringOfIds(final List<Long> ids) {
|
||||
final List<Long> tmp = new ArrayList<>(ids);
|
||||
return tmp.stream().map(String::valueOf).collect(Collectors.joining("-"));
|
||||
}
|
||||
|
||||
/**
|
||||
* extract a list of "-" separated element from a SQL input data.
|
||||
* @param rs Result Set of the BDD
|
||||
* @param iii Id in the result set
|
||||
* @return The list of Long value
|
||||
* @throws SQLException if an error is generated in the sql request.
|
||||
*/
|
||||
protected static List<Long> getListOfIds(final ResultSet rs, final int iii) throws SQLException {
|
||||
final String trackString = rs.getString(iii);
|
||||
if (rs.wasNull()) {
|
||||
return null;
|
||||
}
|
||||
final List<Long> out = new ArrayList<>();
|
||||
final String[] elements = trackString.split("-");
|
||||
for (final String elem : elements) {
|
||||
final Long tmp = Long.parseLong(elem);
|
||||
out.add(tmp);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getAnnotationClass() {
|
||||
return ManyToOne.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSQLFieldType(final Field elem) {
|
||||
try {
|
||||
return SqlWrapper.convertTypeInSQL(Long.class);
|
||||
} catch (Exception e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCompatibleField(final Field elem) {
|
||||
final ManyToOne decorators = elem.getDeclaredAnnotation(ManyToOne.class);
|
||||
return decorators != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int insertData(final PreparedStatement ps, final Object data, int iii) throws SQLException {
|
||||
if (data == null) {
|
||||
ps.setNull(iii++, Types.BIGINT);
|
||||
} else {
|
||||
@SuppressWarnings("unchecked")
|
||||
String dataTmp = getStringOfIds((List<Long>) data);
|
||||
ps.setString(iii++, dataTmp);
|
||||
}
|
||||
return iii++;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isExternal() {
|
||||
// TODO Auto-generated method stub
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int generateQuerry(final String tableName, final Field elem, final StringBuilder querry, final String name, final List<StateLoad> autoClasify, QuerryOptions options) {
|
||||
autoClasify.add(StateLoad.NORMAL);
|
||||
querry.append(" ");
|
||||
querry.append(tableName);
|
||||
querry.append(".");
|
||||
querry.append(name);
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int fillFromQuerry(final ResultSet rs, final Field elem, final Object data, final int count, QuerryOptions options) throws SQLException, IllegalArgumentException, IllegalAccessException {
|
||||
Long foreignKey = rs.getLong(count);
|
||||
if (rs.wasNull()) {
|
||||
return 0;
|
||||
}
|
||||
elem.set(data, foreignKey);
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canUpdate() {
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO : refacto this table to manage a generic table with dynamic name to be serializable with the default system
|
||||
@Override
|
||||
public void createTables(final String tableName, final Field elem, final StringBuilder mainTableBuilder, final List<String> ListOtherTables, final boolean createIfNotExist,
|
||||
final boolean createDrop, final int fieldId) throws Exception {
|
||||
SqlWrapper.createTablesSpecificType(tableName, elem, mainTableBuilder, ListOtherTables, createIfNotExist, createDrop, fieldId, Long.class);
|
||||
}
|
||||
}
|
@ -9,7 +9,8 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.kar.archidata.annotation.addOn.SQLTableExternalLink;
|
||||
import org.kar.archidata.annotation.addOn.SQLTableExternalForeinKeyAsList;
|
||||
import org.kar.archidata.sqlWrapper.QuerryOptions;
|
||||
import org.kar.archidata.sqlWrapper.SqlWrapper;
|
||||
import org.kar.archidata.sqlWrapper.SqlWrapperAddOn;
|
||||
import org.kar.archidata.sqlWrapper.StateLoad;
|
||||
@ -17,7 +18,7 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class AddOnSQLTableExternalForeinKeyAsList implements SqlWrapperAddOn {
|
||||
static final Logger LOGGER = LoggerFactory.getLogger(AddOnSQLTableExternalLink.class);
|
||||
static final Logger LOGGER = LoggerFactory.getLogger(AddOnManyToMany.class);
|
||||
|
||||
/**
|
||||
* Convert the list if external id in a string '-' separated
|
||||
@ -55,15 +56,21 @@ public class AddOnSQLTableExternalForeinKeyAsList implements SqlWrapperAddOn {
|
||||
|
||||
@Override
|
||||
public Class<?> getAnnotationClass() {
|
||||
return SQLTableExternalLink.class;
|
||||
return SQLTableExternalForeinKeyAsList.class;
|
||||
}
|
||||
|
||||
public String getSQLFieldType(Field elem) {
|
||||
return "STRING";
|
||||
try {
|
||||
return SqlWrapper.convertTypeInSQL(String.class);
|
||||
} catch (Exception e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean isCompatibleField(Field elem) {
|
||||
SQLTableExternalLink decorators = elem.getDeclaredAnnotation(SQLTableExternalLink.class);
|
||||
SQLTableExternalForeinKeyAsList decorators = elem.getDeclaredAnnotation(SQLTableExternalForeinKeyAsList.class);
|
||||
return decorators != null;
|
||||
}
|
||||
|
||||
@ -85,7 +92,7 @@ public class AddOnSQLTableExternalForeinKeyAsList implements SqlWrapperAddOn {
|
||||
}
|
||||
|
||||
@Override
|
||||
public int generateQuerry(String tableName, Field elem, StringBuilder querry, String name, List<StateLoad> autoClasify) {
|
||||
public int generateQuerry(String tableName, Field elem, StringBuilder querry, String name, List<StateLoad> autoClasify, QuerryOptions options) {
|
||||
autoClasify.add(StateLoad.ARRAY);
|
||||
querry.append(" ");
|
||||
querry.append(tableName);
|
||||
@ -95,7 +102,7 @@ public class AddOnSQLTableExternalForeinKeyAsList implements SqlWrapperAddOn {
|
||||
}
|
||||
|
||||
@Override
|
||||
public int fillFromQuerry(ResultSet rs, Field elem, Object data, int count) throws SQLException, IllegalArgumentException, IllegalAccessException {
|
||||
public int fillFromQuerry(ResultSet rs, Field elem, Object data, int count, QuerryOptions options) throws SQLException, IllegalArgumentException, IllegalAccessException {
|
||||
List<Long> idList = getListOfIds(rs, count);
|
||||
elem.set(data, idList);
|
||||
return 1;
|
||||
|
@ -18,7 +18,7 @@ import org.kar.archidata.model.Data;
|
||||
import org.kar.archidata.sqlWrapper.QuerryAnd;
|
||||
import org.kar.archidata.sqlWrapper.QuerryCondition;
|
||||
import org.kar.archidata.sqlWrapper.SqlWrapper;
|
||||
import org.kar.archidata.sqlWrapper.addOn.AddOnSQLTableExternalLink;
|
||||
import org.kar.archidata.sqlWrapper.addOn.AddOnManyToMany;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@ -282,7 +282,7 @@ public class DataTools {
|
||||
}
|
||||
// Fist step: retrieve all the Id of each parents:...
|
||||
LOGGER.info("Find typeNode");
|
||||
AddOnSQLTableExternalLink.addLink(clazz, id, "cover", data.id);
|
||||
AddOnManyToMany.addLink(clazz, id, "cover", data.id);
|
||||
return Response.ok(SqlWrapper.get(clazz, id)).build();
|
||||
} catch (Exception ex) {
|
||||
System.out.println("Cat ann unexpected error ... ");
|
||||
|
Loading…
Reference in New Issue
Block a user