Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Bind validation #3102

Closed
wants to merge 11 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions ebean-api/src/main/java/io/ebean/DataBindException.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package io.ebean;

import javax.persistence.PersistenceException;

/**
* Thrown when a bind validator detects a data bind error.
*/
public class DataBindException extends PersistenceException {
private static final long serialVersionUID = -1755215106960660645L;

/**
* Create with a message.
*/
public DataBindException(String message) {
super(message);
}

/**
* Create with a message and cause.
*/
public DataBindException(String message, Throwable cause) {
super(message, cause);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package io.ebean.config.dbplatform;

import io.ebean.DataBindException;

import java.io.File;
import java.lang.reflect.Array;
import java.sql.Types;

/**
* @author Roland Praml, FOCONIS AG
*/
public class BasicBindValidatorFactory implements BindValidatorFactory {

protected boolean isLengthBased(int jdbcType) {
switch (jdbcType) {
case Types.BLOB:
case Types.CLOB:
case Types.LONGVARBINARY:
case Types.LONGVARCHAR:
case Types.VARBINARY:
case Types.BINARY:
case Types.CHAR:
case Types.VARCHAR:
case Types.NCHAR:
case Types.NVARCHAR:
case Types.NCLOB:
case Types.LONGNVARCHAR:
case Types.SQLXML:
case ExtraDbTypes.JSON:
case ExtraDbTypes.JSONB:
case ExtraDbTypes.JSONClob:
case ExtraDbTypes.JSONBlob:
case ExtraDbTypes.JSONVarchar:
return true;
default:
return false;
}
}

@Override
public BindValidator create(PropertyDefinition property) {
if (property.getDbLength() > 0 && isLengthBased(property.getJdbcType())) {
return value -> validate(value, property.getDbLength(), property.getTable(), property.getColumn());
} else {
return null;
}
}

/**
* Default validator, that handles length check for String, Arrays, and Files
*/
protected void validate(Object value, int dbLength, String table, String column) {
int valueLength = 0;
if (value instanceof String) {
valueLength = ((String) value).length();
} else if (value instanceof File) {
valueLength = (int) ((File) value).length();
} else if (value != null && value.getClass().isArray()) {
valueLength = Array.getLength(value);
}
if (valueLength > dbLength) {
throw new DataBindException("Value of length " + valueLength + " exceeds limit of " + dbLength + " for " + table + "." + column);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package io.ebean.config.dbplatform;

import io.ebean.DataBindException;

/**
* Validates a value at bind level. See BindValidatorFactory for details.
*
* @author Roland Praml, FOCONIS AG
*/
@FunctionalInterface
public interface BindValidator {
/**
* The validate method should throw a DataBindException, if the value is invalid.
*/
void validate(Object value) throws DataBindException;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package io.ebean.config.dbplatform;

/**
* The database platforms treat values that will exceed length or precision differently.
* Some will silently truncate varchars, if they are too long. This is fatal, when saving JSONs,
* because the JSON mostly breaks by this truncation.
* <p>
* BindValidators can be implemented depending on the database platform needs.
* E.g. they can perform length checks of varchars/blobs/clobs/...
* It would be also possible to implement precision and range checks for timestamps or numeric values.
* or 'confidental checks' - e.g. detect if all password hashes are SHA256
*
* @author Roland Praml, FOCONIS AG
*/
@FunctionalInterface
public interface BindValidatorFactory {
/**
* Creates a bindValidator. The <code>propertyDefinition</code> provides information like JDBC-type or DbLength.
*/
BindValidator create(PropertyDefinition propertyDefinition);
}
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,8 @@ public class DatabasePlatform {

protected boolean supportsNativeIlike;

protected BindValidatorFactory bindValidatorFactory = new BasicBindValidatorFactory();

protected SqlExceptionTranslator exceptionTranslator = new SqlCodeTranslator();

/**
Expand Down Expand Up @@ -301,6 +303,17 @@ public boolean supportsNativeIlike() {
return supportsNativeIlike;
}

/**
* Return the bindValidatorFactory for this platform.
*/
public BindValidatorFactory getBindValidatorFactory() {
return bindValidatorFactory;
}

public void setBindValidatorFactory(BindValidatorFactory bindValidatorFactory) {
this.bindValidatorFactory = bindValidatorFactory;
}

/**
* Return true if the platform supports delete statements with table alias.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,9 +139,7 @@ public String renderType(int deployLength, int deployScale, boolean strict) {
protected void renderLengthScale(int deployLength, int deployScale, StringBuilder sb) {
// see if there is a precision/scale to add (or not)
int len = deployLength != 0 ? deployLength : defaultLength;
if (len == Integer.MAX_VALUE) {
sb.append("(max)"); // TODO: this is sqlserver specific
} else if (len > 0) {
if (len > 0) {
sb.append("(");
sb.append(len);
int scale = deployScale != 0 ? deployScale : defaultScale;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ private void loadDefaults(boolean logicalTypes) {
/**
* Lookup the platform specific DbType given the standard sql type name.
*/
public DbPlatformType lookup(String name, boolean withScale) {
public DbPlatformType lookup(String name, int scale) {
DbType type = lookup.byName(name);
if (type == null) {
throw new IllegalArgumentException("Unknown type [" + name + "] - not standard sql type");
Expand All @@ -148,19 +148,19 @@ public DbPlatformType lookup(String name, boolean withScale) {
case JSONVARCHAR:
return get(DbType.VARCHAR);
case JSON:
return getJsonType(DbType.JSON, withScale);
return getJsonType(DbType.JSON, scale);
case JSONB:
return getJsonType(DbType.JSONB, withScale);
return getJsonType(DbType.JSONB, scale);
default:
return get(type);
}
}

private DbPlatformType getJsonType(DbType type, boolean withScale) {
private DbPlatformType getJsonType(DbType type, int scale) {
DbPlatformType dbType = get(type);
if (dbType == JSON_CLOB_PLACEHOLDER) {
// if we have scale that implies this maps to varchar
return withScale ? get(DbType.VARCHAR) : get(DbType.CLOB);
return scale <= 8000 && scale != 0 ? get(DbType.VARCHAR) : get(DbType.CLOB);
}
if (dbType == JSON_BLOB_PLACEHOLDER) {
return get(DbType.BLOB);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package io.ebean.config.dbplatform;

import java.io.InputStream;

/**
* InputStreamInfo for BindValidation.
*
* @author Roland Praml, FOCONIS AG
*/
public class InputStreamInfo {
private final InputStream stream;

private final long length;

public InputStreamInfo(InputStream stream, long length) {
this.stream = stream;
this.length = length;
}

public InputStream stream() {
return stream;
}

public long length() {
return length;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package io.ebean.config.dbplatform;

/**
* Information about the property, that should be bind-validated.l
*
* @author Roland Praml, FOCONIS AG
*/
public class PropertyDefinition {
private final int jdbcType;
private final int dbLength;
private final String table;
private final String column;
private final String columnDefn;

public PropertyDefinition(int jdbcType, int dbLength, String baseTable, String column, String columnDefn) {
this.jdbcType = jdbcType;
this.dbLength = dbLength;
this.table = baseTable;
this.column = column;
this.columnDefn = columnDefn;
}

public int getJdbcType() {
return jdbcType;
}

public int getDbLength() {
return dbLength;
}

public String getTable() {
return table;
}

public String getColumn() {
return column;
}

public String getColumnDefn() {
return columnDefn;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -174,4 +174,9 @@ public interface DataBinder {
*/
String popJson();

/**
* Returns the last bound object (e.g. for BindValidation)
*/
Object popLastObject();

}
Loading