From 0391b7a9a0fe3e0b79dbe842dcdb7fe7aa5379b1 Mon Sep 17 00:00:00 2001 From: Marc Baloup Date: Wed, 13 Sep 2017 01:05:10 +0200 Subject: [PATCH] Improve Java/SQL ORM - Support for custom Java types and conversion for database storage - Enums are considered custom types - SQLField.name is now encapsulated as encouraged by OOP principles - SQLField.name is now auto deducted from the Java field's name in the SQLElement subclass (no more field name in the constructor) - No need to precise the SQLType of an SQLFKField (auto deducted from the target SQLField's type) via static method SQLFKField.customFK() - Support of Java type UUID (stored as CHAR(36) in database. No need to use custom getter and setters for SQLElements using UUID fields --- .../java/fr/pandacube/java/util/orm/ORM.java | 497 +++++------ .../java/util/orm/SQLCustomType.java | 32 + .../pandacube/java/util/orm/SQLElement.java | 825 +++++++++--------- .../java/util/orm/SQLElementList.java | 414 ++++----- .../pandacube/java/util/orm/SQLFKField.java | 127 ++- .../fr/pandacube/java/util/orm/SQLField.java | 167 ++-- .../pandacube/java/util/orm/SQLOrderBy.java | 136 +-- .../fr/pandacube/java/util/orm/SQLType.java | 185 ++-- .../fr/pandacube/java/util/orm/SQLWhere.java | 39 +- .../java/util/orm/SQLWhereChain.java | 108 +-- .../pandacube/java/util/orm/SQLWhereComp.java | 111 +-- .../pandacube/java/util/orm/SQLWhereLike.java | 66 +- .../pandacube/java/util/orm/SQLWhereNull.java | 72 +- 13 files changed, 1421 insertions(+), 1358 deletions(-) create mode 100644 src/main/java/fr/pandacube/java/util/orm/SQLCustomType.java diff --git a/src/main/java/fr/pandacube/java/util/orm/ORM.java b/src/main/java/fr/pandacube/java/util/orm/ORM.java index 9208adc..0bc4479 100644 --- a/src/main/java/fr/pandacube/java/util/orm/ORM.java +++ b/src/main/java/fr/pandacube/java/util/orm/ORM.java @@ -1,246 +1,251 @@ -package fr.pandacube.java.util.orm; - -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; - -import fr.pandacube.java.util.EnumUtil; -import fr.pandacube.java.util.Log; -import fr.pandacube.java.util.orm.SQLWhereChain.SQLBoolOp; -import fr.pandacube.java.util.orm.SQLWhereComp.SQLComparator; - -import org.javatuples.Pair; - -/** - * ORM = Object-Relational Mapping - * - * @author Marc Baloup - * - */ -public final class ORM { - - private static List>> tables = new ArrayList<>(); - - private static DBConnection connection; - - public static DBConnection getConnection() { - return connection; - } - - public synchronized static > void init(DBConnection conn) { - - connection = conn; - - - } - - public static synchronized > void initTable(Class elemClass) throws ORMInitTableException { - if (tables.contains(elemClass)) return; - try { - tables.add(elemClass); - //Log.info("Start Init SQL table "+elemClass.getSimpleName()); - E instance = elemClass.newInstance(); - String tableName = instance.tableName(); - if (!tableExist(tableName)) createTable(instance); - //Log.info("End init SQL table "+elemClass.getSimpleName()); - } catch (Exception|ExceptionInInitializerError e) { - throw new ORMInitTableException(elemClass, e); - } - } - - private static > void createTable(E elem) throws SQLException { - - String sql = "CREATE TABLE IF NOT EXISTS " + elem.tableName() + " ("; - List params = new ArrayList<>(); - - Collection> tableFields = elem.getFields().values(); - boolean first = true; - for (SQLField f : tableFields) { - Pair> statementPart = f.forSQLPreparedStatement(); - params.addAll(statementPart.getValue1()); - - if (!first) sql += ", "; - first = false; - sql += statementPart.getValue0(); - } - - sql += ", PRIMARY KEY id(id))"; - PreparedStatement ps = connection.getNativeConnection().prepareStatement(sql); - int i = 1; - for (Object val : params) - ps.setObject(i++, val); - try { - Log.info("Creating table " + elem.tableName() + ":\n" + ps.toString()); - ps.executeUpdate(); - } finally { - ps.close(); - } - } - - private static boolean tableExist(String tableName) throws SQLException { - ResultSet set = null; - boolean exist = false; - try { - set = connection.getNativeConnection().getMetaData().getTables(null, null, tableName, null); - exist = set.next(); - } finally { - if (set != null) set.close(); - } - return exist; - } - - @SuppressWarnings("unchecked") - public static > SQLField getSQLIdField(Class elemClass) - throws ORMInitTableException { - initTable(elemClass); - return (SQLField) SQLElement.fieldsCache.get(elemClass).get("id"); - } - - public static > List getByIds(Class elemClass, Collection ids) - throws ORMException { - return getByIds(elemClass, ids.toArray(new Integer[ids.size()])); - } - - public static > List getByIds(Class elemClass, Integer... ids) throws ORMException { - SQLField idField = getSQLIdField(elemClass); - SQLWhereChain where = new SQLWhereChain(SQLBoolOp.OR); - for (Integer id : ids) - if (id != null) where.add(new SQLWhereComp(idField, SQLComparator.EQ, id)); - return getAll(elemClass, where, new SQLOrderBy().addField(idField), 1, null); - } - - public static > E getById(Class elemClass, int id) throws ORMException { - return getFirst(elemClass, new SQLWhereComp(getSQLIdField(elemClass), SQLComparator.EQ, id), null); - } - - public static > E getFirst(Class elemClass, SQLWhere where, SQLOrderBy orderBy) - throws ORMException { - SQLElementList elts = getAll(elemClass, where, orderBy, 1, null); - return (elts.size() == 0) ? null : elts.get(0); - } - - public static > SQLElementList getAll(Class elemClass) throws ORMException { - return getAll(elemClass, null, null, null, null); - } - - public static > SQLElementList getAll(Class elemClass, SQLWhere where, - SQLOrderBy orderBy, Integer limit, Integer offset) throws ORMException { - initTable(elemClass); - - try { - String sql = "SELECT * FROM " + elemClass.newInstance().tableName(); - - List params = new ArrayList<>(); - - if (where != null) { - Pair> ret = where.toSQL(); - sql += " WHERE " + ret.getValue0(); - params.addAll(ret.getValue1()); - } - if (orderBy != null) sql += " ORDER BY " + orderBy.toSQL(); - if (limit != null) sql += " LIMIT " + limit; - if (offset != null) sql += " OFFSET " + offset; - sql += ";"; - - SQLElementList elmts = new SQLElementList<>(); - - PreparedStatement ps = connection.getNativeConnection().prepareStatement(sql); - - try { - - int i = 1; - for (Object val : params) { - if (val instanceof Enum) val = ((Enum) val).name(); - ps.setObject(i++, val); - } - Log.debug(ps.toString()); - ResultSet set = ps.executeQuery(); - - try { - while (set.next()) - elmts.add(getElementInstance(set, elemClass)); - } finally { - set.close(); - } - } finally { - ps.close(); - } - - return elmts; - } catch (ReflectiveOperationException | SQLException e) { - throw new ORMException(e); - } - - } - - private static > E getElementInstance(ResultSet set, Class elemClass) throws ORMException { - try { - E instance = elemClass.getConstructor(int.class).newInstance(set.getInt("id")); - - int fieldCount = set.getMetaData().getColumnCount(); - - for (int c = 1; c <= fieldCount; c++) { - String fieldName = set.getMetaData().getColumnLabel(c); - if (!instance.getFields().containsKey(fieldName)) continue; - // ignore when field is present in database but not handled by SQLElement instance - @SuppressWarnings("unchecked") - SQLField sqlField = (SQLField) instance.getFields().get(fieldName); - if (sqlField.type.getJavaType().isEnum()) { - // JDBC ne supporte pas les enums - String enumStrValue = set.getString(c); - if (enumStrValue == null || set.wasNull()) instance.set(sqlField, null, false); - else { - Enum enumValue = EnumUtil.searchUncheckedEnum(sqlField.type.getJavaType(), enumStrValue); - if (enumValue == null) throw new ORMException("The enum constant '" + enumStrValue - + "' is not found in enum class " + sqlField.type.getJavaType().getName()); - instance.set(sqlField, enumValue, false); - } - } - else { - Object val = set.getObject(c, sqlField.type.getJavaType()); - if (val == null || set.wasNull()) instance.set(sqlField, null, false); - else - instance.set(sqlField, val, false); - } - - // la valeur venant de la BDD est marqué comme "non modifié" - // dans l'instance - // car le constructeur de l'instance met tout les champs comme - // modifiés - instance.modifiedSinceLastSave.remove(sqlField.name); - } - - if (!instance.isValidForSave()) throw new ORMException( - "This SQLElement representing a database entry is not valid for save : " + instance.toString()); - - return instance; - } catch (ReflectiveOperationException | IllegalArgumentException | SecurityException | SQLException e) { - throw new ORMException("Can't instanciate " + elemClass.getName(), e); - } - } - - private ORM() {} // rend la classe non instanciable - - /* - * public static void main(String[] args) throws Throwable { - * ORM.init(new DBConnection("localhost", 3306, "pandacube", "pandacube", - * "pandacube")); - * List players = ORM.getAll(SQLPlayer.class, - * new SQLWhereChain(SQLBoolOp.AND) - * .add(new SQLWhereNull(SQLPlayer.banTimeout, true)) - * .add(new SQLWhereChain(SQLBoolOp.OR) - * .add(new SQLWhereComp(SQLPlayer.bambou, SQLComparator.EQ, 0L)) - * .add(new SQLWhereComp(SQLPlayer.grade, SQLComparator.EQ, "default")) - * ), - * new SQLOrderBy().addField(SQLPlayer.playerDisplayName), null, null); - * for(SQLPlayer p : players) { - * System.out.println(p.get(SQLPlayer.playerDisplayName)); - * } - * // TODO mise à jour relative d'un champ (incrément / décrément) - * } - */ - -} +package fr.pandacube.java.util.orm; + +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import org.javatuples.Pair; + +import fr.pandacube.java.util.Log; +import fr.pandacube.java.util.orm.SQLWhereChain.SQLBoolOp; +import fr.pandacube.java.util.orm.SQLWhereComp.SQLComparator; + +/** + * ORM = Object-Relational Mapping + * + * @author Marc Baloup + * + */ +public final class ORM { + + private static List>> tables = new ArrayList<>(); + + private static DBConnection connection; + + public static DBConnection getConnection() { + return connection; + } + + public synchronized static > void init(DBConnection conn) { + + connection = conn; + + + } + + public static synchronized > void initTable(Class elemClass) throws ORMInitTableException { + if (tables.contains(elemClass)) return; + try { + tables.add(elemClass); + //Log.info("Start Init SQL table "+elemClass.getSimpleName()); + E instance = elemClass.newInstance(); + String tableName = instance.tableName(); + if (!tableExist(tableName)) createTable(instance); + //Log.info("End init SQL table "+elemClass.getSimpleName()); + } catch (Exception|ExceptionInInitializerError e) { + throw new ORMInitTableException(elemClass, e); + } + } + + private static > void createTable(E elem) throws SQLException { + + String sql = "CREATE TABLE IF NOT EXISTS " + elem.tableName() + " ("; + List params = new ArrayList<>(); + + Collection> tableFields = elem.getFields().values(); + boolean first = true; + for (SQLField f : tableFields) { + Pair> statementPart = f.forSQLPreparedStatement(); + params.addAll(statementPart.getValue1()); + + if (!first) sql += ", "; + first = false; + sql += statementPart.getValue0(); + } + + sql += ", PRIMARY KEY id(id))"; + PreparedStatement ps = connection.getNativeConnection().prepareStatement(sql); + int i = 1; + for (Object val : params) + ps.setObject(i++, val); + try { + Log.info("Creating table " + elem.tableName() + ":\n" + ps.toString()); + ps.executeUpdate(); + } finally { + ps.close(); + } + } + + private static boolean tableExist(String tableName) throws SQLException { + ResultSet set = null; + boolean exist = false; + try { + set = connection.getNativeConnection().getMetaData().getTables(null, null, tableName, null); + exist = set.next(); + } finally { + if (set != null) set.close(); + } + return exist; + } + + @SuppressWarnings("unchecked") + public static > SQLField getSQLIdField(Class elemClass) + throws ORMInitTableException { + initTable(elemClass); + return (SQLField) SQLElement.fieldsCache.get(elemClass).get("id"); + } + + public static > SQLElementList getByIds(Class elemClass, Collection ids) + throws ORMException { + return getByIds(elemClass, ids.toArray(new Integer[ids.size()])); + } + + public static > SQLElementList getByIds(Class elemClass, Integer... ids) throws ORMException { + SQLField idField = getSQLIdField(elemClass); + SQLWhereChain where = new SQLWhereChain(SQLBoolOp.OR); + for (Integer id : ids) + if (id != null) where.add(new SQLWhereComp(idField, SQLComparator.EQ, id)); + return getAll(elemClass, where, new SQLOrderBy().addField(idField), 1, null); + } + + public static > E getById(Class elemClass, int id) throws ORMException { + return getFirst(elemClass, new SQLWhereComp(getSQLIdField(elemClass), SQLComparator.EQ, id), null); + } + + public static > E getFirst(Class elemClass, SQLWhere where, SQLOrderBy orderBy) + throws ORMException { + SQLElementList elts = getAll(elemClass, where, orderBy, 1, null); + return (elts.size() == 0) ? null : elts.get(0); + } + + public static > SQLElementList getAll(Class elemClass) throws ORMException { + return getAll(elemClass, null, null, null, null); + } + + public static > SQLElementList getAll(Class elemClass, SQLWhere where, + SQLOrderBy orderBy, Integer limit, Integer offset) throws ORMException { + initTable(elemClass); + + try { + String sql = "SELECT * FROM " + elemClass.newInstance().tableName(); + + List params = new ArrayList<>(); + + if (where != null) { + Pair> ret = where.toSQL(); + sql += " WHERE " + ret.getValue0(); + params.addAll(ret.getValue1()); + } + if (orderBy != null) sql += " ORDER BY " + orderBy.toSQL(); + if (limit != null) sql += " LIMIT " + limit; + if (offset != null) sql += " OFFSET " + offset; + sql += ";"; + + SQLElementList elmts = new SQLElementList<>(); + + PreparedStatement ps = connection.getNativeConnection().prepareStatement(sql); + + try { + + int i = 1; + for (Object val : params) { + if (val instanceof Enum) val = ((Enum) val).name(); + ps.setObject(i++, val); + } + Log.debug(ps.toString()); + ResultSet set = ps.executeQuery(); + + try { + while (set.next()) + elmts.add(getElementInstance(set, elemClass)); + } finally { + set.close(); + } + } finally { + ps.close(); + } + + return elmts; + } catch (ReflectiveOperationException | SQLException e) { + throw new ORMException(e); + } + + } + + @SuppressWarnings("unchecked") + private static > E getElementInstance(ResultSet set, Class elemClass) throws ORMException { + try { + E instance = elemClass.getConstructor(int.class).newInstance(set.getInt("id")); + + int fieldCount = set.getMetaData().getColumnCount(); + + for (int c = 1; c <= fieldCount; c++) { + String fieldName = set.getMetaData().getColumnLabel(c); + + // ignore when field is present in database but not handled by SQLElement instance + if (!instance.getFields().containsKey(fieldName)) continue; + + SQLField sqlField = (SQLField) instance.getFields().get(fieldName); + + boolean customType = sqlField.type instanceof SQLCustomType; + + Object val = set.getObject(c, + (Class)(customType ? ((SQLCustomType)sqlField.type).intermediateJavaType + : sqlField.type.getJavaType())); + + if (val == null || set.wasNull()) { + instance.set(sqlField, null, false); + } + else { + if (customType) { + try { + val = ((SQLCustomType)sqlField.type).dbToJavaConv.apply(val); + } catch (Exception e) { + throw new ORMException("Error while converting value of field '"+sqlField.getName()+"' with SQLCustomType from "+((SQLCustomType)sqlField.type).intermediateJavaType + +"(jdbc source) to "+sqlField.type.getJavaType()+"(java destination). The original value is '"+val.toString()+"'", e); + } + } + + instance.set(sqlField, val, false); + // la valeur venant de la BDD est marqué comme "non modifié" + // dans l'instance car le constructeur de l'instance met + // tout les champs comme modifiés + instance.modifiedSinceLastSave.remove(sqlField.getName()); + + } + } + + if (!instance.isValidForSave()) throw new ORMException( + "This SQLElement representing a database entry is not valid for save : " + instance.toString()); + + return instance; + } catch (ReflectiveOperationException | IllegalArgumentException | SecurityException | SQLException e) { + throw new ORMException("Can't instanciate " + elemClass.getName(), e); + } + } + + private ORM() {} // rend la classe non instanciable + + /* + * public static void main(String[] args) throws Throwable { + * ORM.init(new DBConnection("localhost", 3306, "pandacube", "pandacube", + * "pandacube")); + * List players = ORM.getAll(SQLPlayer.class, + * new SQLWhereChain(SQLBoolOp.AND) + * .add(new SQLWhereNull(SQLPlayer.banTimeout, true)) + * .add(new SQLWhereChain(SQLBoolOp.OR) + * .add(new SQLWhereComp(SQLPlayer.bambou, SQLComparator.EQ, 0L)) + * .add(new SQLWhereComp(SQLPlayer.grade, SQLComparator.EQ, "default")) + * ), + * new SQLOrderBy().addField(SQLPlayer.playerDisplayName), null, null); + * for(SQLPlayer p : players) { + * System.out.println(p.get(SQLPlayer.playerDisplayName)); + * } + * // TODO mise à jour relative d'un champ (incrément / décrément) + * } + */ + +} diff --git a/src/main/java/fr/pandacube/java/util/orm/SQLCustomType.java b/src/main/java/fr/pandacube/java/util/orm/SQLCustomType.java new file mode 100644 index 0000000..f3e2589 --- /dev/null +++ b/src/main/java/fr/pandacube/java/util/orm/SQLCustomType.java @@ -0,0 +1,32 @@ +package fr.pandacube.java.util.orm; + +import java.util.function.Function; + +/** + * + * @author Marc + * + * @param intermediate type, the type of the value transmitted to the JDBC + * @param Java type + */ +public class SQLCustomType extends SQLType { + + public final Class intermediateJavaType; + public final Function dbToJavaConv; + public final Function javaToDbConv; + + protected SQLCustomType(SQLType type, Class javaT, Function dbToJava, Function javaToDb) { + this(type.sqlDeclaration, type.getJavaType(), javaT, dbToJava, javaToDb); + } + + protected SQLCustomType(String sqlD, Class intermediateJavaT, Class javaT, Function dbToJava, Function javaToDb) { + super(sqlD, javaT); + intermediateJavaType = intermediateJavaT; + dbToJavaConv = dbToJava; + javaToDbConv = javaToDb; + } + + + // tester en local + +} diff --git a/src/main/java/fr/pandacube/java/util/orm/SQLElement.java b/src/main/java/fr/pandacube/java/util/orm/SQLElement.java index 7ef42fb..cf05f66 100644 --- a/src/main/java/fr/pandacube/java/util/orm/SQLElement.java +++ b/src/main/java/fr/pandacube/java/util/orm/SQLElement.java @@ -1,410 +1,415 @@ -package fr.pandacube.java.util.orm; - -import java.sql.Connection; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.Statement; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Set; - -import org.apache.commons.lang.builder.ToStringBuilder; - -import com.google.gson.Gson; -import com.google.gson.JsonObject; - -import fr.pandacube.java.util.Log; -import fr.pandacube.java.util.orm.SQLWhereComp.SQLComparator; - -public abstract class SQLElement> { - /** cache for fields for each subclass of SQLElement */ - /* package */ static final Map>, SQLFieldMap>> fieldsCache = new HashMap<>(); - - DBConnection db = ORM.getConnection(); - - private boolean stored = false; - private int id; - - private final String tableName; - private final SQLFieldMap fields; - - private final Map, Object> values; - /* package */ final Set modifiedSinceLastSave; - - @SuppressWarnings("unchecked") - public SQLElement() { - tableName = tableName(); - - try { - ORM.initTable((Class)getClass()); - } catch (ORMInitTableException e) { - throw new RuntimeException(e); - } - - if (fieldsCache.get(getClass()) == null) { - fields = new SQLFieldMap<>((Class)getClass()); - - // le champ id commun à toutes les tables - fields.addField(new SQLField<>("id", SQLType.INT, false, true, 0)); - - generateFields(fields); - fieldsCache.put((Class)getClass(), fields); - } - else - fields = (SQLFieldMap) fieldsCache.get(getClass()); - - values = new LinkedHashMap<>(fields.size()); - modifiedSinceLastSave = new HashSet<>(fields.size()); - - initDefaultValues(); - - } - - protected SQLElement(int id) { - this(); - @SuppressWarnings("unchecked") - SQLField idField = (SQLField) fields.get("id"); - set(idField, id, false); - this.id = id; - stored = true; - } - - /** - * @return The name of the table in the database. - */ - protected abstract String tableName(); - - @SuppressWarnings("unchecked") - private void initDefaultValues() { - // remplissage des données par défaut (si peut être null ou si valeur - // par défaut existe) - for (@SuppressWarnings("rawtypes") - SQLField f : fields.values()) - if (f.defaultValue != null) set(f, f.defaultValue); - else if (f.canBeNull || (f.autoIncrement && !stored)) set(f, null); - } - - protected void generateFields(SQLFieldMap listToFill) { - - java.lang.reflect.Field[] declaredFields = getClass().getDeclaredFields(); - for (java.lang.reflect.Field field : declaredFields) { - if (!java.lang.reflect.Modifier.isStatic(field.getModifiers())) continue; - - try { - Object val = field.get(null); - if (val == null || !(val instanceof SQLField)) continue; - - listToFill.addField((SQLField) val); - } catch (IllegalArgumentException | IllegalAccessException e) { - Log.severe("Can't get value of static field " + field.toString(), e); - } - } - - } - - /* package */ Map> getFields() { - return Collections.unmodifiableMap(fields); - } - - public Map, Object> getValues() { - return Collections.unmodifiableMap(values); - } - - public void set(SQLField field, T value) { - set(field, value, true); - } - - /* package */ void set(SQLField sqlField, T value, boolean setModified) { - if (sqlField == null) throw new IllegalArgumentException("sqlField can't be null"); - if (!fields.containsValue(sqlField)) - throw new IllegalArgumentException(sqlField.getSQLElementType().getName()+sqlField.name + " is not a SQLField of " + getClass().getName()); - - boolean modify = false; - if (value == null) { - if (sqlField.canBeNull || (sqlField.autoIncrement && !stored)) modify = true; - else - throw new IllegalArgumentException( - "SQLField '" + sqlField.name + "' of " + getClass().getName() + " is a NOT NULL field"); - } - else if (sqlField.type.isAssignableFrom(value)) modify = true; - else - throw new IllegalArgumentException("SQLField '" + sqlField.name + "' of " + getClass().getName() - + " type is '" + sqlField.type.toString() + "' and can't accept values of type " - + value.getClass().getName()); - - if (modify) if (!values.containsKey(sqlField)) { - values.put(sqlField, value); - if (setModified) modifiedSinceLastSave.add(sqlField.name); - } - else { - Object oldVal = values.get(sqlField); - if (!Objects.equals(oldVal, value)) { - values.put(sqlField, value); - if (setModified) modifiedSinceLastSave.add(sqlField.name); - } - // sinon, rien n'est modifié - } - - } - - public T get(SQLField field) { - if (field == null) throw new IllegalArgumentException("field can't be null"); - if (values.containsKey(field)) { - @SuppressWarnings("unchecked") - T val = (T) values.get(field); - return val; - } - throw new IllegalArgumentException("The field '" + field.name + "' in this instance of " + getClass().getName() - + " does not exist or is not set"); - } - - public > F getForeign(SQLFKField field) throws ORMException { - T fkValue = get(field); - if (fkValue == null) return null; - return ORM.getFirst(field.getForeignElementClass(), - new SQLWhereComp(field.getForeignField(), SQLComparator.EQ, fkValue), null); - } - - public boolean isValidForSave() { - return values.keySet().containsAll(fields.values()); - } - - private Map, Object> getOnlyModifiedValues() { - Map, Object> modifiedValues = new LinkedHashMap<>(); - values.forEach((k, v) -> { - if (modifiedSinceLastSave.contains(k.name)) modifiedValues.put(k, v); - }); - return modifiedValues; - } - - public boolean isModified(SQLField field) { - return modifiedSinceLastSave.contains(field.name); - } - - @SuppressWarnings("unchecked") - public void save() throws ORMException { - if (!isValidForSave()) - throw new IllegalStateException(toString() + " has at least one undefined value and can't be saved."); - - ORM.initTable((Class)getClass()); - String toStringStatement = ""; - try { - - Connection conn = db.getNativeConnection(); - - if (stored) { // mettre à jour les valeurs dans la base - - // restaurer l'ID au cas il aurait été changé à la main dans - // values - SQLField idField = (SQLField) fields.get("id"); - values.put(idField, id); - modifiedSinceLastSave.remove("id"); - Map, Object> modifiedValues = getOnlyModifiedValues(); - - if (modifiedValues.isEmpty()) return; - - String sql = ""; - List psValues = new ArrayList<>(); - - for (Map.Entry, Object> entry : modifiedValues.entrySet()) { - sql += entry.getKey().name + " = ? ,"; - if (entry.getKey().type.getJavaType().isEnum()) // prise en - // charge - // enum (non - // prise en - // charge - // par JDBC) - psValues.add(((Enum) entry.getValue()).name()); - else - psValues.add(entry.getValue()); - } - - if (sql.length() > 0) sql = sql.substring(0, sql.length() - 1); - - PreparedStatement ps = conn.prepareStatement("UPDATE " + tableName + " SET " + sql + " WHERE id=" + id); - - try { - - int i = 1; - for (Object val : psValues) - ps.setObject(i++, val); - - toStringStatement = ps.toString(); - ps.executeUpdate(); - } finally { - ps.close(); - } - } - else { // ajouter dans la base - - // restaurer l'ID au cas il aurait été changé à la main dans - // values - values.put(fields.get("id"), null); - - String concat_vals = ""; - String concat_fields = ""; - List psValues = new ArrayList<>(); - - boolean first = true; - for (Map.Entry, Object> entry : values.entrySet()) { - if (!first) { - concat_vals += ","; - concat_fields += ","; - } - first = false; - concat_vals += " ? "; - concat_fields += entry.getKey().name; - if (entry.getKey().type.getJavaType().isEnum()) // prise en - // charge - // enum (non - // prise en - // charge - // par JDBC) - psValues.add(((Enum) entry.getValue()).name()); - else - psValues.add(entry.getValue()); - } - - PreparedStatement ps = conn.prepareStatement( - "INSERT INTO " + tableName + " (" + concat_fields + ") VALUES (" + concat_vals + ")", - Statement.RETURN_GENERATED_KEYS); - try { - - int i = 1; - for (Object val : psValues) - ps.setObject(i++, val); - - toStringStatement = ps.toString(); - ps.executeUpdate(); - - ResultSet rs = ps.getGeneratedKeys(); - try { - if (rs.next()) id = rs.getInt(1); - - stored = true; - } finally { - rs.close(); - } - } finally { - ps.close(); - } - - } - - modifiedSinceLastSave.clear(); - } catch (SQLException e) { - throw new ORMException("Error while executing SQL statement " + toStringStatement, e); - } - Log.debug(toStringStatement); - } - - public boolean isStored() { - return stored; - } - - public Integer getId() { - return (stored) ? id : null; - } - - @SuppressWarnings("unchecked") - public SQLField getFieldId() { - return (SQLField) fields.get("id"); - } - - public void delete() throws ORMException { - - try { - if (stored) { // supprimer la ligne de la base - PreparedStatement st = db.getNativeConnection() - .prepareStatement("DELETE FROM " + tableName + " WHERE id=" + id); - try { - Log.debug(st.toString()); - st.executeUpdate(); - markAsNotStored(); - } finally { - st.close(); - } - } - } catch (SQLException e) { - throw new ORMException(e); - } - - } - - /** - * Méthode appelée quand l'élément courant est retirée de la base de données - * via une requête externe - */ - /* package */ void markAsNotStored() { - stored = false; - id = 0; - modifiedSinceLastSave.clear(); - values.forEach((k, v) -> modifiedSinceLastSave.add(k.name)); - } - - protected static class SQLFieldMap> extends LinkedHashMap> { - private static final long serialVersionUID = 1L; - - private final Class sqlElemClass; - - private SQLFieldMap(Class elemClass) { - sqlElemClass = elemClass; - } - - private void addField(SQLField f) { - if (f == null) return; - if (containsKey(f.name)) throw new IllegalArgumentException( - "SQLField " + f.name + " already exist in " + sqlElemClass.getName()); - @SuppressWarnings("unchecked") - SQLField checkedF = (SQLField) f; - checkedF.setSQLElementType(sqlElemClass); - put(checkedF.name, checkedF); - } - - } - - @Override - public String toString() { - ToStringBuilder b = new ToStringBuilder(this); - - for (SQLField f : fields.values()) - try { - b.append(f.name, get(f)); - } catch (IllegalArgumentException e) { - b.append(f.name, "(Undefined)"); - } - - return b.toString(); - } - - @Override - public boolean equals(Object o) { - if (o == null || !(getClass().isInstance(o))) return false; - SQLElement oEl = (SQLElement) o; - if (oEl.getId() == null) return false; - return oEl.getId().equals(getId()); - } - - @Override - public int hashCode() { - return super.hashCode(); - } - - - - public JsonObject asJsonObject() { - JsonObject json = new JsonObject(); - for (SQLField f : getFields().values()) { - json.add(f.name, new Gson().toJsonTree(get(f))); - } - return json; - } - -} +package fr.pandacube.java.util.orm; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +import org.apache.commons.lang.builder.ToStringBuilder; + +import com.google.gson.Gson; +import com.google.gson.JsonObject; + +import fr.pandacube.java.util.Log; +import fr.pandacube.java.util.orm.SQLWhereComp.SQLComparator; + +public abstract class SQLElement> { + /** cache for fields for each subclass of SQLElement */ + /* package */ static final Map>, SQLFieldMap>> fieldsCache = new HashMap<>(); + + DBConnection db = ORM.getConnection(); + + private boolean stored = false; + private int id; + + private final String tableName; + private final SQLFieldMap fields; + + private final Map, Object> values; + /* package */ final Set modifiedSinceLastSave; + + @SuppressWarnings("unchecked") + public SQLElement() { + tableName = tableName(); + + try { + ORM.initTable((Class)getClass()); + } catch (ORMInitTableException e) { + throw new RuntimeException(e); + } + + if (fieldsCache.get(getClass()) == null) { + fields = new SQLFieldMap<>((Class)getClass()); + + // le champ id commun à toutes les tables + SQLField idF = new SQLField<>(SQLType.INT, false, true, 0); + idF.setName("id"); + fields.addField(idF); + + generateFields(fields); + fieldsCache.put((Class)getClass(), fields); + } + else + fields = (SQLFieldMap) fieldsCache.get(getClass()); + + values = new LinkedHashMap<>(fields.size()); + modifiedSinceLastSave = new HashSet<>(fields.size()); + + initDefaultValues(); + + } + + protected SQLElement(int id) { + this(); + @SuppressWarnings("unchecked") + SQLField idField = (SQLField) fields.get("id"); + set(idField, id, false); + this.id = id; + stored = true; + } + + /** + * @return The name of the table in the database. + */ + protected abstract String tableName(); + + @SuppressWarnings("unchecked") + private void initDefaultValues() { + // remplissage des données par défaut (si peut être null ou si valeur + // par défaut existe) + for (@SuppressWarnings("rawtypes") + SQLField f : fields.values()) + if (f.defaultValue != null) set(f, f.defaultValue); + else if (f.canBeNull || (f.autoIncrement && !stored)) set(f, null); + } + + @SuppressWarnings("unchecked") + protected void generateFields(SQLFieldMap listToFill) { + + java.lang.reflect.Field[] declaredFields = getClass().getDeclaredFields(); + for (java.lang.reflect.Field field : declaredFields) { + if (!java.lang.reflect.Modifier.isStatic(field.getModifiers())) continue; + + try { + Object val = field.get(null); + if (val == null || !(val instanceof SQLField)) continue; + SQLField checkedF = (SQLField) val; + checkedF.setName(field.getName()); + if (listToFill.containsKey(checkedF.getName())) throw new IllegalArgumentException( + "SQLField " + checkedF.getName() + " already exist in " + getClass().getName()); + checkedF.setSQLElementType((Class) getClass()); + listToFill.addField((SQLField) val); + } catch (IllegalArgumentException | IllegalAccessException e) { + Log.severe("Can't get value of static field " + field.toString(), e); + } + } + + } + + /* package */ Map> getFields() { + return Collections.unmodifiableMap(fields); + } + + public Map, Object> getValues() { + return Collections.unmodifiableMap(values); + } + + public void set(SQLField field, T value) { + set(field, value, true); + } + + /* package */ void set(SQLField sqlField, T value, boolean setModified) { + if (sqlField == null) throw new IllegalArgumentException("sqlField can't be null"); + if (!fields.containsValue(sqlField)) + throw new IllegalArgumentException(sqlField.getSQLElementType().getName()+sqlField.getName() + " is not a SQLField of " + getClass().getName()); + + boolean modify = false; + if (value == null) { + if (sqlField.canBeNull || (sqlField.autoIncrement && !stored)) modify = true; + else + throw new IllegalArgumentException( + "SQLField '" + sqlField.getName() + "' of " + getClass().getName() + " is a NOT NULL field"); + } + else if (sqlField.type.isAssignableFrom(value)) modify = true; + else + throw new IllegalArgumentException("SQLField '" + sqlField.getName() + "' of " + getClass().getName() + + " type is '" + sqlField.type.toString() + "' and can't accept values of type " + + value.getClass().getName()); + + if (modify) if (!values.containsKey(sqlField)) { + values.put(sqlField, value); + if (setModified) modifiedSinceLastSave.add(sqlField.getName()); + } + else { + Object oldVal = values.get(sqlField); + if (!Objects.equals(oldVal, value)) { + values.put(sqlField, value); + if (setModified) modifiedSinceLastSave.add(sqlField.getName()); + } + // sinon, rien n'est modifié + } + + } + + public T get(SQLField field) { + if (field == null) throw new IllegalArgumentException("field can't be null"); + if (values.containsKey(field)) { + @SuppressWarnings("unchecked") + T val = (T) values.get(field); + return val; + } + throw new IllegalArgumentException("The field '" + field.getName() + "' in this instance of " + getClass().getName() + + " does not exist or is not set"); + } + + public > F getForeign(SQLFKField field) throws ORMException { + T fkValue = get(field); + if (fkValue == null) return null; + return ORM.getFirst(field.getForeignElementClass(), + new SQLWhereComp(field.getForeignField(), SQLComparator.EQ, fkValue), null); + } + + public boolean isValidForSave() { + return values.keySet().containsAll(fields.values()); + } + + private Map, Object> getOnlyModifiedValues() { + Map, Object> modifiedValues = new LinkedHashMap<>(); + values.forEach((k, v) -> { + if (modifiedSinceLastSave.contains(k.getName())) modifiedValues.put(k, v); + }); + return modifiedValues; + } + + public boolean isModified(SQLField field) { + return modifiedSinceLastSave.contains(field.getName()); + } + + @SuppressWarnings("unchecked") + public void save() throws ORMException { + if (!isValidForSave()) + throw new IllegalStateException(toString() + " has at least one undefined value and can't be saved."); + + ORM.initTable((Class)getClass()); + String toStringStatement = ""; + try { + + Connection conn = db.getNativeConnection(); + + if (stored) { // mettre à jour les valeurs dans la base + + // restaurer l'ID au cas il aurait été changé à la main dans + // values + SQLField idField = (SQLField) fields.get("id"); + values.put(idField, id); + modifiedSinceLastSave.remove("id"); + Map, Object> modifiedValues = getOnlyModifiedValues(); + + if (modifiedValues.isEmpty()) return; + + String sql = ""; + List psValues = new ArrayList<>(); + + for (Map.Entry, Object> entry : modifiedValues.entrySet()) { + sql += entry.getKey().getName() + " = ? ,"; + addValueToSQLObjectList(psValues, entry.getKey(), entry.getValue()); + } + + if (sql.length() > 0) sql = sql.substring(0, sql.length() - 1); + + PreparedStatement ps = conn.prepareStatement("UPDATE " + tableName + " SET " + sql + " WHERE id=" + id); + + try { + + int i = 1; + for (Object val : psValues) + ps.setObject(i++, val); + + toStringStatement = ps.toString(); + ps.executeUpdate(); + } finally { + ps.close(); + } + } + else { // ajouter dans la base + + // restaurer l'ID au cas il aurait été changé à la main dans + // values + values.put(fields.get("id"), null); + + String concat_vals = ""; + String concat_fields = ""; + List psValues = new ArrayList<>(); + + boolean first = true; + for (Map.Entry, Object> entry : values.entrySet()) { + if (!first) { + concat_vals += ","; + concat_fields += ","; + } + first = false; + concat_vals += " ? "; + concat_fields += entry.getKey().getName(); + addValueToSQLObjectList(psValues, entry.getKey(), entry.getValue()); + } + + PreparedStatement ps = conn.prepareStatement( + "INSERT INTO " + tableName + " (" + concat_fields + ") VALUES (" + concat_vals + ")", + Statement.RETURN_GENERATED_KEYS); + try { + + int i = 1; + for (Object val : psValues) + ps.setObject(i++, val); + + toStringStatement = ps.toString(); + ps.executeUpdate(); + + ResultSet rs = ps.getGeneratedKeys(); + try { + if (rs.next()) id = rs.getInt(1); + + stored = true; + } finally { + rs.close(); + } + } finally { + ps.close(); + } + + } + + modifiedSinceLastSave.clear(); + } catch (SQLException e) { + throw new ORMException("Error while executing SQL statement " + toStringStatement, e); + } + Log.debug(toStringStatement); + } + + + @SuppressWarnings("rawtypes") + protected static > void addValueToSQLObjectList(List list, SQLField field, Object jValue) throws ORMException { + if (jValue != null && field.type instanceof SQLCustomType) { + try { + jValue = ((SQLCustomType)field.type).javaToDbConv.apply(jValue); + } catch (Exception e) { + throw new ORMException("Error while converting value of field '"+field.getName()+"' with SQLCustomType from "+field.type.getJavaType() + +"(java source) to "+((SQLCustomType)field.type).intermediateJavaType+"(jdbc destination). The original value is '"+jValue.toString()+"'", e); + } + } + list.add(jValue); + } + + public boolean isStored() { + return stored; + } + + public Integer getId() { + return (stored) ? id : null; + } + + @SuppressWarnings("unchecked") + public SQLField getFieldId() { + return (SQLField) fields.get("id"); + } + + public void delete() throws ORMException { + + try { + if (stored) { // supprimer la ligne de la base + PreparedStatement st = db.getNativeConnection() + .prepareStatement("DELETE FROM " + tableName + " WHERE id=" + id); + try { + Log.debug(st.toString()); + st.executeUpdate(); + markAsNotStored(); + } finally { + st.close(); + } + } + } catch (SQLException e) { + throw new ORMException(e); + } + + } + + /** + * Méthode appelée quand l'élément courant est retirée de la base de données + * via une requête externe + */ + /* package */ void markAsNotStored() { + stored = false; + id = 0; + modifiedSinceLastSave.clear(); + values.forEach((k, v) -> modifiedSinceLastSave.add(k.getName())); + } + + protected static class SQLFieldMap> extends LinkedHashMap> { + private static final long serialVersionUID = 1L; + + private final Class sqlElemClass; + + private SQLFieldMap(Class elemClass) { + sqlElemClass = elemClass; + } + + private void addField(SQLField f) { + if (f == null) return; + if (containsKey(f.getName())) throw new IllegalArgumentException( + "SQLField " + f.getName() + " already exist in " + sqlElemClass.getName()); + @SuppressWarnings("unchecked") + SQLField checkedF = (SQLField) f; + checkedF.setSQLElementType(sqlElemClass); + put(checkedF.getName(), checkedF); + } + + } + + @Override + public String toString() { + ToStringBuilder b = new ToStringBuilder(this); + + for (SQLField f : fields.values()) + try { + b.append(f.getName(), get(f)); + } catch (IllegalArgumentException e) { + b.append(f.getName(), "(Undefined)"); + } + + return b.toString(); + } + + @Override + public boolean equals(Object o) { + if (o == null || !(getClass().isInstance(o))) return false; + SQLElement oEl = (SQLElement) o; + if (oEl.getId() == null) return false; + return oEl.getId().equals(getId()); + } + + @Override + public int hashCode() { + return super.hashCode(); + } + + + + public JsonObject asJsonObject() { + JsonObject json = new JsonObject(); + for (SQLField f : getFields().values()) { + json.add(f.getName(), new Gson().toJsonTree(get(f))); + } + return json; + } + +} diff --git a/src/main/java/fr/pandacube/java/util/orm/SQLElementList.java b/src/main/java/fr/pandacube/java/util/orm/SQLElementList.java index 2ad97d6..5f62fb8 100644 --- a/src/main/java/fr/pandacube/java/util/orm/SQLElementList.java +++ b/src/main/java/fr/pandacube/java/util/orm/SQLElementList.java @@ -1,207 +1,207 @@ -package fr.pandacube.java.util.orm; - -import java.sql.PreparedStatement; -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.stream.Collectors; - -import com.google.gson.JsonArray; - -import fr.pandacube.java.util.Log; -import fr.pandacube.java.util.orm.SQLWhereChain.SQLBoolOp; -import fr.pandacube.java.util.orm.SQLWhereComp.SQLComparator; - -/** - * - * @param - */ -public class SQLElementList> extends ArrayList { - private static final long serialVersionUID = 1L; - - private final Map, Object> modifiedValues = new LinkedHashMap<>(); - - @Override - public synchronized boolean add(E e) { - if (e == null || !e.isStored()) return false; - return super.add(e); - } - - /** - * Défini une valeur à un champ qui sera appliquée dans la base de données à - * tous les - * entrées présente dans cette liste lors de l'appel à {@link #saveCommon()} - * . - * Les valeurs stockés dans chaque élément de cette liste ne seront affectés - * que lors de - * l'appel à {@link #saveCommon()} - * - * @param - * @param field le champs à modifier - * @param value la valeur à lui appliquer - */ - public synchronized void setCommon(SQLField field, T value) { - if (field == null) - throw new IllegalArgumentException("field can't be null"); - if (field.name == "id") - throw new IllegalArgumentException("Can't modify id field in a SQLElementList"); - - Class elemClass = field.getSQLElementType(); - try { - E emptyElement = elemClass.newInstance(); - emptyElement.set(field, value, false); - } catch (Exception e) { - throw new IllegalArgumentException("Illegal field or value or can't instanciante an empty instance of " - + elemClass.getName() + ". (the instance is only created to test validity of field and value)", e); - } - - // ici, la valeur est bonne - modifiedValues.put(field, value); - - } - - /** - * Applique toutes les valeurs défini avec - * {@link #setCommon(SQLField, Object)} à toutes - * les entrées dans la base de données correspondants aux entrées de cette - * liste. Les nouvelles - * valeurs sont aussi mises à jour dans les objets contenus dans cette - * liste, si la valeur n'a pas été modifiée individuellement avec - * {@link SQLElement#set(SQLField, Object)}.
- * Les objets de cette liste qui n'ont pas leur données en base de données - * sont ignorées. - * - * @throws SQLException - */ - public synchronized void saveCommon() throws SQLException { - List storedEl = getStoredEl(); - if (storedEl.isEmpty()) return; - - String sqlSet = ""; - List psValues = new ArrayList<>(); - - for (Map.Entry, Object> entry : modifiedValues.entrySet()) { - sqlSet += entry.getKey().name + " = ? ,"; - if (entry.getKey().type.getJavaType().isEnum()) // prise en charge - // enum (non prise - // en charge par - // JDBC) - psValues.add(((Enum) entry.getValue()).name()); - else - psValues.add(entry.getValue()); - } - - if (sqlSet.length() > 0) sqlSet = sqlSet.substring(0, sqlSet.length() - 1); - - String sqlWhere = ""; - boolean first = true; - for (E el : storedEl) { - if (!first) sqlWhere += " OR "; - first = false; - sqlWhere += "id = " + el.getId(); - } - - PreparedStatement ps = ORM.getConnection().getNativeConnection() - .prepareStatement("UPDATE " + storedEl.get(0).tableName() + " SET " + sqlSet + " WHERE " + sqlWhere); - try { - - int i = 1; - for (Object val : psValues) - ps.setObject(i++, val); - - Log.debug(ps.toString()); - ps.executeUpdate(); - - applyNewValuesToElements(storedEl); - } finally { - ps.close(); - } - } - - @SuppressWarnings("unchecked") - private void applyNewValuesToElements(List storedEl) { - // applique les valeurs dans chaques objets de la liste - for (E el : storedEl) - for (@SuppressWarnings("rawtypes") - SQLField entry : modifiedValues.keySet()) - if (!el.isModified(entry)) el.set(entry, modifiedValues.get(entry), false); - } - - private List getStoredEl() { - return stream().filter(SQLElement::isStored).collect(Collectors.toCollection(() -> new ArrayList<>())); - } - - public synchronized void removeFromDB() { - List storedEl = getStoredEl(); - if (storedEl.isEmpty()) return; - - try { - - String sqlWhere = ""; - boolean first = true; - for (E el : storedEl) { - if (!first) sqlWhere += " OR "; - first = false; - sqlWhere += "id = " + el.getId(); - } - - PreparedStatement st = ORM.getConnection().getNativeConnection() - .prepareStatement("DELETE FROM " + storedEl.get(0).tableName() + " WHERE " + sqlWhere); - try { - Log.debug(st.toString()); - st.executeUpdate(); - - for (E el : storedEl) - el.markAsNotStored(); - - } finally { - st.close(); - } - - } catch (SQLException e) { - Log.severe(e); - } - - } - - - - public > Map getAllForeign(SQLFKField foreignKey) throws ORMException { - Set values = new HashSet<>(); - forEach(v -> { - T val = v.get(foreignKey); - if (val != null) - values.add(val); - }); - - if (values.isEmpty()) { - return new HashMap<>(); - } - - SQLWhereChain where = new SQLWhereChain(SQLBoolOp.OR); - values.forEach(v -> where.add(new SQLWhereComp(foreignKey.getForeignField(), SQLComparator.EQ, v))); - - - SQLElementList foreignElemts = ORM.getAll(foreignKey.getForeignElementClass(), where, null, null, null); - - Map ret = new HashMap<>(); - foreignElemts.forEach(foreignVal -> ret.put(foreignVal.get(foreignKey.getForeignField()), foreignVal)); - return ret; - } - - - - - - public JsonArray asJsonArray() { - JsonArray json = new JsonArray(); - forEach(el -> json.add(el.asJsonObject())); - return json; - } - -} +package fr.pandacube.java.util.orm; + +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import com.google.gson.JsonArray; + +import fr.pandacube.java.util.Log; +import fr.pandacube.java.util.orm.SQLWhereChain.SQLBoolOp; +import fr.pandacube.java.util.orm.SQLWhereComp.SQLComparator; + +/** + * + * @param + */ +public class SQLElementList> extends ArrayList { + private static final long serialVersionUID = 1L; + + private final Map, Object> modifiedValues = new LinkedHashMap<>(); + + @Override + public synchronized boolean add(E e) { + if (e == null || !e.isStored()) return false; + return super.add(e); + } + + /** + * Défini une valeur à un champ qui sera appliquée dans la base de données à + * tous les + * entrées présente dans cette liste lors de l'appel à {@link #saveCommon()} + * . + * Les valeurs stockés dans chaque élément de cette liste ne seront affectés + * que lors de + * l'appel à {@link #saveCommon()} + * + * @param + * @param field le champs à modifier + * @param value la valeur à lui appliquer + */ + public synchronized void setCommon(SQLField field, T value) { + if (field == null) + throw new IllegalArgumentException("field can't be null"); + if (field.getName() == "id") + throw new IllegalArgumentException("Can't modify id field in a SQLElementList"); + + Class elemClass = field.getSQLElementType(); + try { + E emptyElement = elemClass.newInstance(); + emptyElement.set(field, value, false); + } catch (Exception e) { + throw new IllegalArgumentException("Illegal field or value or can't instanciante an empty instance of " + + elemClass.getName() + ". (the instance is only created to test validity of field and value)", e); + } + + // ici, la valeur est bonne + modifiedValues.put(field, value); + + } + + /** + * Applique toutes les valeurs défini avec + * {@link #setCommon(SQLField, Object)} à toutes + * les entrées dans la base de données correspondants aux entrées de cette + * liste. Les nouvelles + * valeurs sont aussi mises à jour dans les objets contenus dans cette + * liste, si la valeur n'a pas été modifiée individuellement avec + * {@link SQLElement#set(SQLField, Object)}.
+ * Les objets de cette liste qui n'ont pas leur données en base de données + * sont ignorées. + * + * @throws SQLException + */ + public synchronized void saveCommon() throws ORMException { + List storedEl = getStoredEl(); + if (storedEl.isEmpty()) return; + + String sqlSet = ""; + List psValues = new ArrayList<>(); + + for (Map.Entry, Object> entry : modifiedValues.entrySet()) { + sqlSet += entry.getKey().getName() + " = ? ,"; + SQLElement.addValueToSQLObjectList(psValues, entry.getKey(), entry.getValue()); + if (entry.getKey().type.getJavaType().isEnum()) { + // prise en charge enum (non prise en charge par JDBC) + psValues.add(((Enum) entry.getValue()).name()); + } + else + psValues.add(entry.getValue()); + } + + if (sqlSet.length() > 0) sqlSet = sqlSet.substring(0, sqlSet.length() - 1); + + String sqlWhere = ""; + boolean first = true; + for (E el : storedEl) { + if (!first) sqlWhere += " OR "; + first = false; + sqlWhere += "id = " + el.getId(); + } + + try(PreparedStatement ps = ORM.getConnection().getNativeConnection() + .prepareStatement("UPDATE " + storedEl.get(0).tableName() + " SET " + sqlSet + " WHERE " + sqlWhere);) { + + + int i = 1; + for (Object val : psValues) + ps.setObject(i++, val); + + Log.debug(ps.toString()); + ps.executeUpdate(); + + applyNewValuesToElements(storedEl); + } catch (SQLException e) { + throw new ORMException(e); + } + } + + @SuppressWarnings("unchecked") + private void applyNewValuesToElements(List storedEl) { + // applique les valeurs dans chaques objets de la liste + for (E el : storedEl) + for (@SuppressWarnings("rawtypes") + SQLField entry : modifiedValues.keySet()) + if (!el.isModified(entry)) el.set(entry, modifiedValues.get(entry), false); + } + + private List getStoredEl() { + return stream().filter(SQLElement::isStored).collect(Collectors.toCollection(() -> new ArrayList<>())); + } + + public synchronized void removeFromDB() { + List storedEl = getStoredEl(); + if (storedEl.isEmpty()) return; + + try { + + String sqlWhere = ""; + boolean first = true; + for (E el : storedEl) { + if (!first) sqlWhere += " OR "; + first = false; + sqlWhere += "id = " + el.getId(); + } + + PreparedStatement st = ORM.getConnection().getNativeConnection() + .prepareStatement("DELETE FROM " + storedEl.get(0).tableName() + " WHERE " + sqlWhere); + try { + Log.debug(st.toString()); + st.executeUpdate(); + + for (E el : storedEl) + el.markAsNotStored(); + + } finally { + st.close(); + } + + } catch (SQLException e) { + Log.severe(e); + } + + } + + + + public > Map getAllForeign(SQLFKField foreignKey) throws ORMException { + Set values = new HashSet<>(); + forEach(v -> { + T val = v.get(foreignKey); + if (val != null) + values.add(val); + }); + + if (values.isEmpty()) { + return new HashMap<>(); + } + + SQLWhereChain where = new SQLWhereChain(SQLBoolOp.OR); + values.forEach(v -> where.add(new SQLWhereComp(foreignKey.getForeignField(), SQLComparator.EQ, v))); + + + SQLElementList foreignElemts = ORM.getAll(foreignKey.getForeignElementClass(), where, null, null, null); + + Map ret = new HashMap<>(); + foreignElemts.forEach(foreignVal -> ret.put(foreignVal.get(foreignKey.getForeignField()), foreignVal)); + return ret; + } + + + + + + public JsonArray asJsonArray() { + JsonArray json = new JsonArray(); + forEach(el -> json.add(el.asJsonObject())); + return json; + } + +} diff --git a/src/main/java/fr/pandacube/java/util/orm/SQLFKField.java b/src/main/java/fr/pandacube/java/util/orm/SQLFKField.java index df40212..25b39d3 100644 --- a/src/main/java/fr/pandacube/java/util/orm/SQLFKField.java +++ b/src/main/java/fr/pandacube/java/util/orm/SQLFKField.java @@ -1,65 +1,62 @@ -package fr.pandacube.java.util.orm; - -import fr.pandacube.java.util.Log; - -public class SQLFKField, T, F extends SQLElement> extends SQLField { - - private SQLField sqlForeignKeyField; - private Class sqlForeignKeyElemClass; - - public SQLFKField(String n, SQLType t, boolean nul, Class fkEl, SQLField fkF) { - super(n, t, nul); - construct(fkEl, fkF); - } - - public SQLFKField(String n, SQLType t, boolean nul, T deflt, Class fkEl, SQLField fkF) { - super(n, t, nul, deflt); - construct(fkEl, fkF); - } - - public static , F extends SQLElement> SQLFKField idFK(String n, SQLType t, boolean nul, - Class fkEl) { - if (fkEl == null) throw new IllegalArgumentException("foreignKeyElement can't be null"); - try { - return new SQLFKField<>(n, t, nul, fkEl, ORM.getSQLIdField(fkEl)); - } catch (ORMInitTableException e) { - Log.severe("Can't create Foreign key Field called '" + n + "'", e); - return null; - } - } - - public static , F extends SQLElement> SQLFKField idFKField(String n, SQLType t, boolean nul, - Integer deflt, Class fkEl) { - if (fkEl == null) throw new IllegalArgumentException("foreignKeyElement can't be null"); - try { - return new SQLFKField<>(n, t, nul, deflt, fkEl, ORM.getSQLIdField(fkEl)); - } catch (ORMInitTableException e) { - Log.severe("Can't create Foreign key Field called '" + n + "'", e); - return null; - } - } - - private void construct(Class fkEl, SQLField fkF) { - if (fkF == null) throw new IllegalArgumentException("foreignKeyField can't be null"); - try { - ORM.initTable(fkEl); - } catch (ORMInitTableException e) { - throw new RuntimeException(e); - } - if (!fkEl.equals(fkF.getSQLElementType())) - throw new IllegalArgumentException("foreignKeyField must be from supplied foreignKeyElement"); - if (!type.equals(fkF.type)) - throw new IllegalArgumentException("foreignKeyField and current Field must have the same SQLType"); - sqlForeignKeyField = fkF; - sqlForeignKeyElemClass = fkEl; - } - - public SQLField getForeignField() { - return sqlForeignKeyField; - } - - public Class getForeignElementClass() { - return sqlForeignKeyElemClass; - } - -} +package fr.pandacube.java.util.orm; + +import fr.pandacube.java.util.Log; + +public class SQLFKField, T, F extends SQLElement> extends SQLField { + + private SQLField sqlForeignKeyField; + private Class sqlForeignKeyElemClass; + + protected SQLFKField(SQLType t, boolean nul, T deflt, Class fkEl, SQLField fkF) { + super(t, nul, deflt); + construct(fkEl, fkF); + } + + public static , F extends SQLElement> SQLFKField idFK(boolean nul, Class fkEl) { + return idFK(nul, null, fkEl); + } + + public static , F extends SQLElement> SQLFKField idFK(boolean nul, Integer deflt, Class fkEl) { + if (fkEl == null) throw new IllegalArgumentException("foreignKeyElement can't be null"); + try { + SQLField f = ORM.getSQLIdField(fkEl); + return new SQLFKField<>(f.type, nul, deflt, fkEl, f); + } catch (ORMInitTableException e) { + Log.severe("Can't create Foreign key Field targetting id field of '"+fkEl+"'", e); + return null; + } + } + + public static , T, F extends SQLElement> SQLFKField customFK(boolean nul, Class fkEl, SQLField fkF) { + return customFK(nul, null, fkEl, fkF); + } + + public static , T, F extends SQLElement> SQLFKField customFK(boolean nul, T deflt, Class fkEl, SQLField fkF) { + if (fkEl == null) throw new IllegalArgumentException("foreignKeyElement can't be null"); + return new SQLFKField<>(fkF.type, nul, deflt, fkEl, fkF); + } + + private void construct(Class fkEl, SQLField fkF) { + if (fkF == null) throw new IllegalArgumentException("foreignKeyField can't be null"); + try { + ORM.initTable(fkEl); + } catch (ORMInitTableException e) { + throw new RuntimeException(e); + } + if (!fkEl.equals(fkF.getSQLElementType())) + throw new IllegalArgumentException("foreignKeyField must be from supplied foreignKeyElement"); + if (!type.equals(fkF.type)) + throw new IllegalArgumentException("foreignKeyField and current Field must have the same SQLType"); + sqlForeignKeyField = fkF; + sqlForeignKeyElemClass = fkEl; + } + + public SQLField getForeignField() { + return sqlForeignKeyField; + } + + public Class getForeignElementClass() { + return sqlForeignKeyElemClass; + } + +} diff --git a/src/main/java/fr/pandacube/java/util/orm/SQLField.java b/src/main/java/fr/pandacube/java/util/orm/SQLField.java index 9266457..8f7fb01 100644 --- a/src/main/java/fr/pandacube/java/util/orm/SQLField.java +++ b/src/main/java/fr/pandacube/java/util/orm/SQLField.java @@ -1,80 +1,87 @@ -package fr.pandacube.java.util.orm; - -import java.util.ArrayList; -import java.util.List; - -import org.javatuples.Pair; - -public class SQLField, T> { - - private Class sqlElemClass; - public final String name; - public final SQLType type; - public final boolean canBeNull; - public final boolean autoIncrement; - /* package */ final T defaultValue; - - public SQLField(String n, SQLType t, boolean nul, boolean autoIncr, T deflt) { - name = n; - type = t; - canBeNull = nul; - autoIncrement = autoIncr; - defaultValue = deflt; - } - - public SQLField(String n, SQLType t, boolean nul) { - this(n, t, nul, false, null); - } - - public SQLField(String n, SQLType t, boolean nul, boolean autoIncr) { - this(n, t, nul, autoIncr, null); - } - - public SQLField(String n, SQLType t, boolean nul, T deflt) { - this(n, t, nul, false, deflt); - } - - /* package */ Pair> forSQLPreparedStatement() { - List params = new ArrayList<>(1); - if (defaultValue != null && !autoIncrement) params.add(defaultValue); - return new Pair<>(name + " " + type.toString() + (canBeNull ? " NULL" : " NOT NULL") - + (autoIncrement ? " AUTO_INCREMENT" : "") - + ((defaultValue == null || autoIncrement) ? "" : " DEFAULT ?"), params); - } - - /* package */ void setSQLElementType(Class elemClass) { - sqlElemClass = elemClass; - } - - public Class getSQLElementType() { - return sqlElemClass; - } - - /** - * Don't use this {@link #toString()} method in a SQL query, because - * the default value is not escaped correctly - * - * @see java.lang.Object#toString() - */ - @Override - public String toString() { - return forSQLPreparedStatement().getValue0().replaceFirst("\\?", - (defaultValue != null && !autoIncrement) ? defaultValue.toString() : ""); - } - - @Override - public boolean equals(Object obj) { - if (obj == null) return false; - if (!(obj instanceof SQLField)) return false; - SQLField f = (SQLField) obj; - if (!f.name.equals(name)) return false; - if (!f.sqlElemClass.equals(sqlElemClass)) return false; - return true; - } - - @Override - public int hashCode() { - return name.hashCode() + sqlElemClass.hashCode(); - } - -} +package fr.pandacube.java.util.orm; + +import java.util.ArrayList; +import java.util.List; + +import org.javatuples.Pair; + +public class SQLField, T> { + + private Class sqlElemClass; + private String name = null; + public final SQLType type; + public final boolean canBeNull; + public final boolean autoIncrement; + /* package */ final T defaultValue; + + public SQLField(SQLType t, boolean nul, boolean autoIncr, T deflt) { + type = t; + canBeNull = nul; + autoIncrement = autoIncr; + defaultValue = deflt; + } + + public SQLField(SQLType t, boolean nul) { + this(t, nul, false, null); + } + + public SQLField(SQLType t, boolean nul, boolean autoIncr) { + this(t, nul, autoIncr, null); + } + + public SQLField(SQLType t, boolean nul, T deflt) { + this(t, nul, false, deflt); + } + + /* package */ Pair> forSQLPreparedStatement() { + List params = new ArrayList<>(1); + if (defaultValue != null && !autoIncrement) params.add(defaultValue); + return new Pair<>(getName() + " " + type.toString() + (canBeNull ? " NULL" : " NOT NULL") + + (autoIncrement ? " AUTO_INCREMENT" : "") + + ((defaultValue == null || autoIncrement) ? "" : " DEFAULT ?"), params); + } + + /* package */ void setSQLElementType(Class elemClass) { + sqlElemClass = elemClass; + } + + public Class getSQLElementType() { + return sqlElemClass; + } + + /* package */ void setName(String n) { + name = n; + } + + public String getName() { + return name; + } + + /** + * Don't use this {@link #toString()} method in a SQL query, because + * the default value is not escaped correctly + * + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return forSQLPreparedStatement().getValue0().replaceFirst("\\?", + (defaultValue != null && !autoIncrement) ? defaultValue.toString() : ""); + } + + @Override + public boolean equals(Object obj) { + if (obj == null) return false; + if (!(obj instanceof SQLField)) return false; + SQLField f = (SQLField) obj; + if (!f.getName().equals(getName())) return false; + if (!f.sqlElemClass.equals(sqlElemClass)) return false; + return true; + } + + @Override + public int hashCode() { + return getName().hashCode() + sqlElemClass.hashCode(); + } + +} diff --git a/src/main/java/fr/pandacube/java/util/orm/SQLOrderBy.java b/src/main/java/fr/pandacube/java/util/orm/SQLOrderBy.java index f78568d..f67d8c9 100644 --- a/src/main/java/fr/pandacube/java/util/orm/SQLOrderBy.java +++ b/src/main/java/fr/pandacube/java/util/orm/SQLOrderBy.java @@ -1,68 +1,68 @@ -package fr.pandacube.java.util.orm; - -import java.util.ArrayList; -import java.util.List; - -public class SQLOrderBy { - - private List orderByFields = new ArrayList<>(); - - /** - * Construit une nouvelle clause ORDER BY - */ - public SQLOrderBy() {} - - /** - * Ajoute un champ dans la clause ORDER BY en construction - * - * @param field le champ SQL à ordonner - * @param d le sens de tri (croissant ASC ou décroissant DESC) - * @return l'objet courant (permet de chainer les ajouts de champs) - */ - public SQLOrderBy addField(SQLField field, Direction d) { - orderByFields.add(new OBField(field, d)); - return this; - } - - /** - * Ajoute un champ dans la clause ORDER BY en construction, - * avec comme ordre de tri croissant ASC par défaut - * - * @param field le champ SQL à ordonner dans l'ordre croissant ASC - * @return l'objet courant (permet de chainer les ajouts de champs) - */ - public SQLOrderBy addField(SQLField field) { - return addField(field, Direction.ASC); - } - - /* package */ String toSQL() { - String ret = ""; - boolean first = true; - for (OBField f : orderByFields) { - if (!first) ret += ", "; - first = false; - ret += f.field.name + " " + f.direction.name(); - } - return ret; - } - - @Override - public String toString() { - return toSQL(); - } - - private class OBField { - public final SQLField field; - public final Direction direction; - - public OBField(SQLField f, Direction d) { - field = f; - direction = d; - } - - } - - public enum Direction { - ASC, DESC; - } -} +package fr.pandacube.java.util.orm; + +import java.util.ArrayList; +import java.util.List; + +public class SQLOrderBy { + + private List orderByFields = new ArrayList<>(); + + /** + * Construit une nouvelle clause ORDER BY + */ + public SQLOrderBy() {} + + /** + * Ajoute un champ dans la clause ORDER BY en construction + * + * @param field le champ SQL à ordonner + * @param d le sens de tri (croissant ASC ou décroissant DESC) + * @return l'objet courant (permet de chainer les ajouts de champs) + */ + public SQLOrderBy addField(SQLField field, Direction d) { + orderByFields.add(new OBField(field, d)); + return this; + } + + /** + * Ajoute un champ dans la clause ORDER BY en construction, + * avec comme ordre de tri croissant ASC par défaut + * + * @param field le champ SQL à ordonner dans l'ordre croissant ASC + * @return l'objet courant (permet de chainer les ajouts de champs) + */ + public SQLOrderBy addField(SQLField field) { + return addField(field, Direction.ASC); + } + + /* package */ String toSQL() { + String ret = ""; + boolean first = true; + for (OBField f : orderByFields) { + if (!first) ret += ", "; + first = false; + ret += f.field.getName() + " " + f.direction.name(); + } + return ret; + } + + @Override + public String toString() { + return toSQL(); + } + + private class OBField { + public final SQLField field; + public final Direction direction; + + public OBField(SQLField f, Direction d) { + field = f; + direction = d; + } + + } + + public enum Direction { + ASC, DESC; + } +} diff --git a/src/main/java/fr/pandacube/java/util/orm/SQLType.java b/src/main/java/fr/pandacube/java/util/orm/SQLType.java index d15b7a2..3ca3d08 100644 --- a/src/main/java/fr/pandacube/java/util/orm/SQLType.java +++ b/src/main/java/fr/pandacube/java/util/orm/SQLType.java @@ -1,90 +1,95 @@ -package fr.pandacube.java.util.orm; - -import java.sql.Date; - -public class SQLType { - - private final String sqlType; - private final String sqlTypeParam; - private final Class javaTypes; - - public SQLType(String sqlT, String sqlP, Class javaT) { - sqlType = sqlT; - sqlTypeParam = sqlP; - javaTypes = javaT; - } - - @Override - public String toString() { - return sqlType + sqlTypeParam; - } - - public boolean isAssignableFrom(Object val) { - if (javaTypes.isInstance(val)) return true; - return false; - } - - @Override - public int hashCode() { - return toString().hashCode(); - } - - @Override - public boolean equals(Object obj) { - if (obj == null || !(obj instanceof SQLType)) return false; - return toString().equals(((SQLType) obj).toString()); - } - - public Class getJavaType() { - return javaTypes; - } - - public static final SQLType BOOLEAN = new SQLType<>("BOOLEAN", "", Boolean.class); - - public static final SQLType TINYINT = new SQLType<>("TINYINT", "", Byte.class); - public static final SQLType BYTE = TINYINT; - - public static final SQLType SMALLINT = new SQLType<>("SMALLINT", "", Short.class); - public static final SQLType SHORT = SMALLINT; - - public static final SQLType INT = new SQLType<>("INT", "", Integer.class); - public static final SQLType INTEGER = INT; - - public static final SQLType BIGINT = new SQLType<>("BIGINT", "", Long.class); - public static final SQLType LONG = BIGINT; - - public static final SQLType DATE = new SQLType<>("DATE", "", Date.class); - - public static final SQLType FLOAT = new SQLType<>("FLOAT", "", Float.class); - - public static final SQLType DOUBLE = new SQLType<>("DOUBLE", "", Double.class); - - public static final SQLType CHAR(int charCount) { - if (charCount <= 0) throw new IllegalArgumentException("charCount must be positive."); - return new SQLType<>("CHAR", "(" + charCount + ")", String.class); - } - - public static final SQLType VARCHAR(int charCount) { - if (charCount <= 0) throw new IllegalArgumentException("charCount must be positive."); - return new SQLType<>("VARCHAR", "(" + charCount + ")", String.class); - } - - public static final SQLType TEXT = new SQLType<>("TEXT", "", String.class); - public static final SQLType STRING = TEXT; - - public static final > SQLType ENUM(Class enumType) { - if (enumType == null) throw new IllegalArgumentException("enumType can't be null."); - String enumStr = "'"; - boolean first = true; - for (T el : enumType.getEnumConstants()) { - if (!first) enumStr += "', '"; - first = false; - enumStr += el.name(); - - } - enumStr += "'"; - - return new SQLType<>("VARCHAR", "(" + enumStr + ")", enumType); - } - -} +package fr.pandacube.java.util.orm; + +import java.sql.Date; +import java.util.UUID; + +import fr.pandacube.java.util.EnumUtil; + +public class SQLType { + + protected final String sqlDeclaration; + private final Class javaTypes; + + protected SQLType(String sqlD, Class javaT) { + sqlDeclaration = sqlD; + javaTypes = javaT; + } + + @Override + public String toString() { + return sqlDeclaration; + } + + public boolean isAssignableFrom(Object val) { + if (javaTypes.isInstance(val)) return true; + return false; + } + + @Override + public int hashCode() { + return toString().hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (obj == null || !(obj instanceof SQLType)) return false; + return toString().equals(((SQLType) obj).toString()); + } + + public Class getJavaType() { + return javaTypes; + } + + public static final SQLType BOOLEAN = new SQLType<>("BOOLEAN", Boolean.class); + + public static final SQLType TINYINT = new SQLType<>("TINYINT", Byte.class); + public static final SQLType BYTE = TINYINT; + + public static final SQLType SMALLINT = new SQLType<>("SMALLINT", Short.class); + public static final SQLType SHORT = SMALLINT; + + public static final SQLType INT = new SQLType<>("INT", Integer.class); + public static final SQLType INTEGER = INT; + + public static final SQLType BIGINT = new SQLType<>("BIGINT", Long.class); + public static final SQLType LONG = BIGINT; + + public static final SQLType DATE = new SQLType<>("DATE", Date.class); + + public static final SQLType FLOAT = new SQLType<>("FLOAT", Float.class); + + public static final SQLType DOUBLE = new SQLType<>("DOUBLE", Double.class); + + @Deprecated + public static final SQLType CHAR(int charCount) { + if (charCount <= 0) throw new IllegalArgumentException("charCount must be positive."); + return new SQLType<>("CHAR(" + charCount + ")", String.class); + } + + public static final SQLType VARCHAR(int charCount) { + if (charCount <= 0) throw new IllegalArgumentException("charCount must be positive."); + return new SQLType<>("VARCHAR(" + charCount + ")", String.class); + } + + public static final SQLType TEXT = new SQLType<>("TEXT", String.class); + public static final SQLType STRING = TEXT; + + public static final > SQLType ENUM(Class enumType) { + if (enumType == null) throw new IllegalArgumentException("enumType can't be null."); + String enumStr = "'"; + boolean first = true; + for (T el : enumType.getEnumConstants()) { + if (!first) enumStr += "', '"; + first = false; + enumStr += el.name(); + + } + enumStr += "'"; + + return new SQLCustomType<>("VARCHAR(" + enumStr + ")", String.class, enumType, s -> EnumUtil.searchEnum(enumType, s), Enum::name); + } + + + public static final SQLType CHAR36_UUID = new SQLCustomType<>(SQLType.CHAR(36), UUID.class, UUID::fromString, UUID::toString); + +} diff --git a/src/main/java/fr/pandacube/java/util/orm/SQLWhere.java b/src/main/java/fr/pandacube/java/util/orm/SQLWhere.java index e8804aa..75e9fa5 100644 --- a/src/main/java/fr/pandacube/java/util/orm/SQLWhere.java +++ b/src/main/java/fr/pandacube/java/util/orm/SQLWhere.java @@ -1,16 +1,23 @@ -package fr.pandacube.java.util.orm; - -import java.util.List; - -import org.javatuples.Pair; - -public abstract class SQLWhere { - - public abstract Pair> toSQL(); - - @Override - public String toString() { - return toSQL().getValue0(); - } - -} +package fr.pandacube.java.util.orm; + +import java.util.List; + +import org.javatuples.Pair; + +import fr.pandacube.java.util.Log; + +public abstract class SQLWhere { + + public abstract Pair> toSQL() throws ORMException; + + @Override + public String toString() { + try { + return toSQL().getValue0(); + } catch (ORMException e) { + Log.warning(e); + return "[SQLWhere.toString() error (see logs)]"; + } + } + +} diff --git a/src/main/java/fr/pandacube/java/util/orm/SQLWhereChain.java b/src/main/java/fr/pandacube/java/util/orm/SQLWhereChain.java index 061512d..0b2991d 100644 --- a/src/main/java/fr/pandacube/java/util/orm/SQLWhereChain.java +++ b/src/main/java/fr/pandacube/java/util/orm/SQLWhereChain.java @@ -1,54 +1,54 @@ -package fr.pandacube.java.util.orm; - -import java.util.ArrayList; -import java.util.List; - -import org.javatuples.Pair; - -public class SQLWhereChain extends SQLWhere { - - private SQLBoolOp operator; - private List conditions = new ArrayList<>(); - - public SQLWhereChain(SQLBoolOp op) { - if (op == null) throw new IllegalArgumentException("op can't be null"); - operator = op; - } - - public SQLWhereChain add(SQLWhere sqlWhere) { - if (sqlWhere == null) throw new IllegalArgumentException("sqlWhere can't be null"); - conditions.add(sqlWhere); - return this; - } - - @Override - public Pair> toSQL() { - String sql = ""; - List params = new ArrayList<>(); - boolean first = true; - - for (SQLWhere w : conditions) { - if (!first) sql += " " + operator.sql + " "; - first = false; - - Pair> ret = w.toSQL(); - sql += "(" + ret.getValue0() + ")"; - params.addAll(ret.getValue1()); - } - - return new Pair<>(sql, params); - } - - public enum SQLBoolOp { - /** Equivalent to SQL "AND" */ - AND("AND"), /** Equivalent to SQL "OR" */ - OR("OR"); - public final String sql; - - private SQLBoolOp(String s) { - sql = s; - } - - } - -} +package fr.pandacube.java.util.orm; + +import java.util.ArrayList; +import java.util.List; + +import org.javatuples.Pair; + +public class SQLWhereChain extends SQLWhere { + + private SQLBoolOp operator; + private List conditions = new ArrayList<>(); + + public SQLWhereChain(SQLBoolOp op) { + if (op == null) throw new IllegalArgumentException("op can't be null"); + operator = op; + } + + public SQLWhereChain add(SQLWhere sqlWhere) { + if (sqlWhere == null) throw new IllegalArgumentException("sqlWhere can't be null"); + conditions.add(sqlWhere); + return this; + } + + @Override + public Pair> toSQL() throws ORMException { + String sql = ""; + List params = new ArrayList<>(); + boolean first = true; + + for (SQLWhere w : conditions) { + if (!first) sql += " " + operator.sql + " "; + first = false; + + Pair> ret = w.toSQL(); + sql += "(" + ret.getValue0() + ")"; + params.addAll(ret.getValue1()); + } + + return new Pair<>(sql, params); + } + + public enum SQLBoolOp { + /** Equivalent to SQL "AND" */ + AND("AND"), /** Equivalent to SQL "OR" */ + OR("OR"); + public final String sql; + + private SQLBoolOp(String s) { + sql = s; + } + + } + +} diff --git a/src/main/java/fr/pandacube/java/util/orm/SQLWhereComp.java b/src/main/java/fr/pandacube/java/util/orm/SQLWhereComp.java index cbdff6b..e20bbe8 100644 --- a/src/main/java/fr/pandacube/java/util/orm/SQLWhereComp.java +++ b/src/main/java/fr/pandacube/java/util/orm/SQLWhereComp.java @@ -1,53 +1,58 @@ -package fr.pandacube.java.util.orm; - -import java.util.ArrayList; -import java.util.List; - -import org.javatuples.Pair; - -public class SQLWhereComp extends SQLWhere { - - private SQLField left; - private SQLComparator comp; - private Object right; - - /** - * Compare a field with a value - * - * @param l the field at left of the comparison operator. Can't be null - * @param c the comparison operator, can't be null - * @param r the value at right of the comparison operator. Can't be null - */ - public SQLWhereComp(SQLField l, SQLComparator c, T r) { - if (l == null || r == null || c == null) - throw new IllegalArgumentException("All arguments for SQLWhereComp constructor can't be null"); - left = l; - comp = c; - right = r; - } - - @Override - public Pair> toSQL() { - List params = new ArrayList<>(); - params.add(right); - return new Pair<>(left.name + " " + comp.sql + " ? ", params); - } - - public enum SQLComparator { - /** Equivalent to SQL "=" */ - EQ("="), /** Equivalent to SQL ">" */ - GT(">"), /** Equivalent to SQL ">=" */ - GEQ(">="), /** Equivalent to SQL "<" */ - LT("<"), /** Equivalent to SQL "<=" */ - LEQ("<="), /** Equivalent to SQL "!=" */ - NEQ("!="); - - public final String sql; - - private SQLComparator(String s) { - sql = s; - } - - } - -} +package fr.pandacube.java.util.orm; + +import java.util.ArrayList; +import java.util.List; + +import org.javatuples.Pair; + +public class SQLWhereComp extends SQLWhere { + + private SQLField left; + private SQLComparator comp; + private Object right; + + /** + * Compare a field with a value + * + * @param l the field at left of the comparison operator. Can't be null + * @param c the comparison operator, can't be null + * @param r the value at right of the comparison operator. Can't be null + */ + public SQLWhereComp(SQLField l, SQLComparator c, T r) { + if (l == null || r == null || c == null) + throw new IllegalArgumentException("All arguments for SQLWhereComp constructor can't be null"); + left = l; + comp = c; + right = r; + } + + @Override + public Pair> toSQL() throws ORMException { + List params = new ArrayList<>(); + SQLElement.addValueToSQLObjectList(params, left, right); + return new Pair<>(left.getName() + " " + comp.sql + " ? ", params); + } + + public enum SQLComparator { + /** Equivalent to SQL "=" */ + EQ("="), + /** Equivalent to SQL ">" */ + GT(">"), + /** Equivalent to SQL ">=" */ + GEQ(">="), + /** Equivalent to SQL "<" */ + LT("<"), + /** Equivalent to SQL "<=" */ + LEQ("<="), + /** Equivalent to SQL "!=" */ + NEQ("!="); + + public final String sql; + + private SQLComparator(String s) { + sql = s; + } + + } + +} diff --git a/src/main/java/fr/pandacube/java/util/orm/SQLWhereLike.java b/src/main/java/fr/pandacube/java/util/orm/SQLWhereLike.java index 9777c72..015b6a8 100644 --- a/src/main/java/fr/pandacube/java/util/orm/SQLWhereLike.java +++ b/src/main/java/fr/pandacube/java/util/orm/SQLWhereLike.java @@ -1,33 +1,33 @@ -package fr.pandacube.java.util.orm; - -import java.util.ArrayList; -import java.util.List; - -import org.javatuples.Pair; - -public class SQLWhereLike extends SQLWhere { - - private SQLField field; - private String likeExpr; - - /** - * Compare a field with a value - * - * @param f the field at left of the LIKE keyword. Can't be null - * @param like the like expression. - */ - public SQLWhereLike(SQLField f, String like) { - if (f == null || like == null) - throw new IllegalArgumentException("All arguments for SQLWhereLike constructor can't be null"); - field = f; - likeExpr = like; - } - - @Override - public Pair> toSQL() { - ArrayList params = new ArrayList<>(); - params.add(likeExpr); - return new Pair<>(field.name + " LIKE ? ", params); - } - -} +package fr.pandacube.java.util.orm; + +import java.util.ArrayList; +import java.util.List; + +import org.javatuples.Pair; + +public class SQLWhereLike extends SQLWhere { + + private SQLField field; + private String likeExpr; + + /** + * Compare a field with a value + * + * @param f the field at left of the LIKE keyword. Can't be null + * @param like the like expression. + */ + public SQLWhereLike(SQLField f, String like) { + if (f == null || like == null) + throw new IllegalArgumentException("All arguments for SQLWhereLike constructor can't be null"); + field = f; + likeExpr = like; + } + + @Override + public Pair> toSQL() { + ArrayList params = new ArrayList<>(); + params.add(likeExpr); + return new Pair<>(field.getName() + " LIKE ? ", params); + } + +} diff --git a/src/main/java/fr/pandacube/java/util/orm/SQLWhereNull.java b/src/main/java/fr/pandacube/java/util/orm/SQLWhereNull.java index b752bc5..55e7088 100644 --- a/src/main/java/fr/pandacube/java/util/orm/SQLWhereNull.java +++ b/src/main/java/fr/pandacube/java/util/orm/SQLWhereNull.java @@ -1,36 +1,36 @@ -package fr.pandacube.java.util.orm; - -import java.util.ArrayList; -import java.util.List; -import java.util.logging.Level; - -import fr.pandacube.java.util.Log; -import org.javatuples.Pair; - -public class SQLWhereNull extends SQLWhere { - - private SQLField fild; - private boolean nulll; - - /** - * Init a IS NULL / IS NOT NULL expression for a SQL WHERE condition. - * - * @param field the field to check null / not null state - * @param isNull true if we want to ckeck if "IS NULL", or false to check if - * "IS NOT NULL" - */ - public SQLWhereNull(SQLField field, boolean isNull) { - if (field == null) throw new IllegalArgumentException("field can't be null"); - if (!field.canBeNull) Log.getLogger().log(Level.WARNING, - "Useless : Trying to check IS [NOT] NULL on the field " + field.getSQLElementType().getName() + "#" - + field.name + " which is declared in the ORM as 'can't be null'"); - fild = field; - nulll = isNull; - } - - @Override - public Pair> toSQL() { - return new Pair<>(fild.name + " IS" + ((nulll) ? " NULL" : " NOT NULL"), new ArrayList<>()); - } - -} +package fr.pandacube.java.util.orm; + +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Level; + +import fr.pandacube.java.util.Log; +import org.javatuples.Pair; + +public class SQLWhereNull extends SQLWhere { + + private SQLField fild; + private boolean nulll; + + /** + * Init a IS NULL / IS NOT NULL expression for a SQL WHERE condition. + * + * @param field the field to check null / not null state + * @param isNull true if we want to ckeck if "IS NULL", or false to check if + * "IS NOT NULL" + */ + public SQLWhereNull(SQLField field, boolean isNull) { + if (field == null) throw new IllegalArgumentException("field can't be null"); + if (!field.canBeNull) Log.getLogger().log(Level.WARNING, + "Useless : Trying to check IS [NOT] NULL on the field " + field.getSQLElementType().getName() + "#" + + field.getName() + " which is declared in the ORM as 'can't be null'"); + fild = field; + nulll = isNull; + } + + @Override + public Pair> toSQL() { + return new Pair<>(fild.getName() + ((nulll) ? " IS NULL" : " IS NOT NULL"), new ArrayList<>()); + } + +}