diff --git a/pom.xml b/pom.xml index d33cf797c..231d7bfc8 100644 --- a/pom.xml +++ b/pom.xml @@ -120,6 +120,12 @@ 4.5 + + io.ebean + ebean-types + 1.1 + + io.ebean ebean-datasource diff --git a/src/main/java/io/ebean/config/PlatformConfig.java b/src/main/java/io/ebean/config/PlatformConfig.java index 9a0ada54b..298b25d23 100644 --- a/src/main/java/io/ebean/config/PlatformConfig.java +++ b/src/main/java/io/ebean/config/PlatformConfig.java @@ -47,6 +47,11 @@ public class PlatformConfig { */ private DbUuid dbUuid = DbUuid.AUTO_VARCHAR; + /** + * Set to true to force InetAddress to map to Varchar (for Postgres rather than INET) + */ + private boolean databaseInetAddressVarchar; + /** * Modify the default mapping of standard types such as default precision for DECIMAL etc. */ @@ -181,6 +186,20 @@ public class PlatformConfig { this.idType = idType; } + /** + * Return true if InetAddress should map to varchar column (rather than Postgres INET). + */ + public boolean isDatabaseInetAddressVarchar() { + return databaseInetAddressVarchar; + } + + /** + * Set to true to force InetAddress to map to varchar column. + */ + public void setDatabaseInetAddressVarchar(boolean databaseInetAddressVarchar) { + this.databaseInetAddressVarchar = databaseInetAddressVarchar; + } + /** * Add a custom type mapping. *

@@ -235,6 +254,7 @@ public class PlatformConfig { databaseSequenceBatchSize = p.getInt("databaseSequenceBatchSize", databaseSequenceBatchSize); databaseBooleanTrue = p.get("databaseBooleanTrue", databaseBooleanTrue); databaseBooleanFalse = p.get("databaseBooleanFalse", databaseBooleanFalse); + databaseInetAddressVarchar = p.getBoolean("databaseInetAddressVarchar", databaseInetAddressVarchar); DbUuid dbUuid = p.getEnum(DbUuid.class, "dbuuid", null); if (dbUuid != null) { diff --git a/src/main/java/io/ebean/config/dbplatform/DbPlatformTypeMapping.java b/src/main/java/io/ebean/config/dbplatform/DbPlatformTypeMapping.java index 3376f617c..84fa3ba46 100644 --- a/src/main/java/io/ebean/config/dbplatform/DbPlatformTypeMapping.java +++ b/src/main/java/io/ebean/config/dbplatform/DbPlatformTypeMapping.java @@ -27,6 +27,9 @@ public class DbPlatformTypeMapping { private static final DbPlatformType BOOLEAN_LOGICAL = new BooleanLogicalType(); + private static final DbPlatformType INET_NATIVE = new DbPlatformType("inet", false); + private static final DbPlatformType INET_VARCHAR = new DbPlatformType("varchar", 50); + private static final DbPlatformType UUID_NATIVE = new DbPlatformType("uuid", false); @SuppressWarnings("unused") private static final DbPlatformType UUID_PLACEHOLDER = new DbPlatformType("uuidPlaceholder"); @@ -106,6 +109,7 @@ public class DbPlatformTypeMapping { put(DbType.JSONBLOB, new DbPlatformType("jsonblob")); put(DbType.JSONVARCHAR, new DbPlatformType("jsonvarchar", 1000)); put(DbType.UUID, UUID_NATIVE); + put(DbType.INET, INET_NATIVE); } else { put(DbType.VARCHAR, new DbPlatformType("varchar", 255)); @@ -121,6 +125,7 @@ public class DbPlatformTypeMapping { put(DbType.JSONVARCHAR, JSON_VARCHAR_PLACEHOLDER); // default to native UUID and override on platform configure() put(DbType.UUID, UUID_NATIVE); + put(DbType.INET, INET_VARCHAR); } } diff --git a/src/main/java/io/ebean/config/dbplatform/DbType.java b/src/main/java/io/ebean/config/dbplatform/DbType.java index f967db9ca..6f7705d65 100644 --- a/src/main/java/io/ebean/config/dbplatform/DbType.java +++ b/src/main/java/io/ebean/config/dbplatform/DbType.java @@ -35,6 +35,8 @@ public enum DbType { ARRAY(Types.ARRAY), UUID(ExtraDbTypes.UUID), + INET(ExtraDbTypes.INET), + CDIR(ExtraDbTypes.CDIR), POINT(ExtraDbTypes.POINT), POLYGON(ExtraDbTypes.POLYGON), diff --git a/src/main/java/io/ebean/config/dbplatform/ExtraDbTypes.java b/src/main/java/io/ebean/config/dbplatform/ExtraDbTypes.java index f05ce865c..d7b0f9339 100644 --- a/src/main/java/io/ebean/config/dbplatform/ExtraDbTypes.java +++ b/src/main/java/io/ebean/config/dbplatform/ExtraDbTypes.java @@ -40,6 +40,9 @@ public interface ExtraDbTypes { */ int JSONBlob = 5005; + int INET = 5020; + int CDIR = 5021; + /** * Geo Point */ diff --git a/src/main/java/io/ebean/config/dbplatform/postgres/PostgresPlatform.java b/src/main/java/io/ebean/config/dbplatform/postgres/PostgresPlatform.java index 80a9c40e9..c8266fbec 100644 --- a/src/main/java/io/ebean/config/dbplatform/postgres/PostgresPlatform.java +++ b/src/main/java/io/ebean/config/dbplatform/postgres/PostgresPlatform.java @@ -60,6 +60,7 @@ public class PostgresPlatform extends DatabasePlatform { DbPlatformType dbBytea = new DbPlatformType("bytea", false); dbTypeMap.put(DbType.UUID, new DbPlatformType("uuid", false)); + dbTypeMap.put(DbType.INET, new DbPlatformType("inet", false)); dbTypeMap.put(DbType.HSTORE, new DbPlatformType("hstore", false)); dbTypeMap.put(DbType.JSON, new DbPlatformType("json", false)); dbTypeMap.put(DbType.JSONB, new DbPlatformType("jsonb", false)); diff --git a/src/main/java/io/ebeaninternal/server/type/ConvertInetAddresses.java b/src/main/java/io/ebeaninternal/server/type/ConvertInetAddresses.java index cf028c81a..03357004a 100644 --- a/src/main/java/io/ebeaninternal/server/type/ConvertInetAddresses.java +++ b/src/main/java/io/ebeaninternal/server/type/ConvertInetAddresses.java @@ -334,6 +334,24 @@ public final class ConvertInetAddresses { return ip.getHostAddress(); } + /** + * Return the host address without the square brackets around IPv6 addresses. + */ + public static String toHostAddress(InetAddress ip) { + return ip.getHostAddress(); + } + + /** + * Parse the IPv4 or IPv6 address without quare brackets around IPv6 addresses. + */ + public static InetAddress fromHost(String hostAddr) { + if (hostAddr.startsWith("[")) { + // IPv6 address + hostAddr = hostAddr.substring(1, hostAddr.length() - 1); + } + return forString(hostAddr); + } + /** * Returns an InetAddress representing the literal IPv4 or IPv6 host * portion of a URL, encoded in the format specified by RFC 3986 section 3.2.2. diff --git a/src/main/java/io/ebeaninternal/server/type/DefaultTypeManager.java b/src/main/java/io/ebeaninternal/server/type/DefaultTypeManager.java index c29afeaad..710265bd4 100644 --- a/src/main/java/io/ebeaninternal/server/type/DefaultTypeManager.java +++ b/src/main/java/io/ebeaninternal/server/type/DefaultTypeManager.java @@ -14,6 +14,8 @@ import io.ebean.config.ScalarTypeConverter; import io.ebean.config.ServerConfig; import io.ebean.config.dbplatform.DatabasePlatform; import io.ebean.config.dbplatform.DbPlatformType; +import io.ebean.types.Cdir; +import io.ebean.types.Inet; import io.ebean.util.AnnotationUtil; import io.ebeaninternal.api.ExtraTypeFactory; import io.ebeaninternal.dbmigration.DbOffline; @@ -128,7 +130,6 @@ public final class DefaultTypeManager implements TypeManager { private final ScalarType dateType = new ScalarTypeDate(); - private final ScalarType inetAddressType = new ScalarTypeInetAddress(); private final ScalarType urlType = new ScalarTypeURL(); private final ScalarType uriType = new ScalarTypeURI(); private final ScalarType localeType = new ScalarTypeLocale(); @@ -975,8 +976,21 @@ public final class DefaultTypeManager implements TypeManager { addType(UUID.class, uuidType); } + if (offlineMigrationGeneration || (postgres && !config.getPlatformConfig().isDatabaseInetAddressVarchar())) { + addType(InetAddress.class, new ScalarTypeInetAddressPostgres()); + } else { + addType(InetAddress.class, new ScalarTypeInetAddress()); + } + + if (offlineMigrationGeneration || postgres) { + addType(Cdir.class, new ScalarTypeCdir.Postgres()); + addType(Inet.class, new ScalarTypeInet.Postgres()); + } else { + addType(Cdir.class, new ScalarTypeCdir.Varchar()); + addType(Inet.class, new ScalarTypeInet.Varchar()); + } + addType(File.class, fileType); - addType(InetAddress.class, inetAddressType); addType(Locale.class, localeType); addType(Currency.class, currencyType); addType(TimeZone.class, timeZoneType); diff --git a/src/main/java/io/ebeaninternal/server/type/PostgresHelper.java b/src/main/java/io/ebeaninternal/server/type/PostgresHelper.java index b79a8d483..d422b493f 100644 --- a/src/main/java/io/ebeaninternal/server/type/PostgresHelper.java +++ b/src/main/java/io/ebeaninternal/server/type/PostgresHelper.java @@ -16,6 +16,12 @@ public class PostgresHelper { */ public static final String JSONB_TYPE = "jsonb"; + public static final String INET_TYPE = "inet"; + + public static Object asInet(String value) throws SQLException { + return asObject(INET_TYPE, value); + } + /** * Construct and return Postgres specific PG object. */ diff --git a/src/main/java/io/ebeaninternal/server/type/ScalarTypeCdir.java b/src/main/java/io/ebeaninternal/server/type/ScalarTypeCdir.java new file mode 100644 index 000000000..cef625c29 --- /dev/null +++ b/src/main/java/io/ebeaninternal/server/type/ScalarTypeCdir.java @@ -0,0 +1,71 @@ +package io.ebeaninternal.server.type; + +import io.ebean.config.dbplatform.ExtraDbTypes; +import io.ebean.types.Cdir; + +import java.sql.SQLException; +import java.sql.Types; + +/** + * ScalarType for Cdir to Varchar or Postgres CDIR. + */ +public abstract class ScalarTypeCdir extends ScalarTypeBaseVarchar { + + ScalarTypeCdir() { + super(Cdir.class, false, ExtraDbTypes.INET); + } + + @Override + public abstract void bind(DataBind b, Cdir value) throws SQLException; + + @Override + public Cdir convertFromDbString(String dbValue) { + return parse(dbValue); + } + + @Override + public String convertToDbString(Cdir beanValue) { + return formatValue(beanValue); + } + + @Override + public String formatValue(Cdir value) { + return value.getAddress(); + } + + @Override + public Cdir parse(String value) { + return new Cdir(value); + } + + /** + * Cdir to Varchar. + */ + public static class Varchar extends ScalarTypeCdir { + + @Override + public void bind(DataBind b, Cdir value) throws SQLException { + if (value == null) { + b.setNull(Types.VARCHAR); + } else { + b.setString(convertToDbString(value)); + } + } + } + + /** + * Cdir to Postgres CDIR. + */ + public static class Postgres extends ScalarTypeCdir { + + @Override + public void bind(DataBind b, Cdir value) throws SQLException { + if (value == null) { + b.setNull(Types.OTHER); + } else { + String strValue = convertToDbString(value); + b.setObject(PostgresHelper.asInet(strValue)); + } + } + } +} diff --git a/src/main/java/io/ebeaninternal/server/type/ScalarTypeInet.java b/src/main/java/io/ebeaninternal/server/type/ScalarTypeInet.java new file mode 100644 index 000000000..4168e0c89 --- /dev/null +++ b/src/main/java/io/ebeaninternal/server/type/ScalarTypeInet.java @@ -0,0 +1,71 @@ +package io.ebeaninternal.server.type; + +import io.ebean.config.dbplatform.ExtraDbTypes; +import io.ebean.types.Inet; + +import java.sql.SQLException; +import java.sql.Types; + +/** + * ScalarType for Inet to Varchar or Postgres INET. + */ +public abstract class ScalarTypeInet extends ScalarTypeBaseVarchar { + + ScalarTypeInet() { + super(Inet.class, false, ExtraDbTypes.INET); + } + + @Override + public abstract void bind(DataBind b, Inet value) throws SQLException; + + @Override + public Inet convertFromDbString(String dbValue) { + return parse(dbValue); + } + + @Override + public String convertToDbString(Inet beanValue) { + return formatValue(beanValue); + } + + @Override + public String formatValue(Inet value) { + return value.getAddress(); + } + + @Override + public Inet parse(String value) { + return new Inet(value); + } + + /** + * Inet to Varchar. + */ + public static class Varchar extends ScalarTypeInet { + + @Override + public void bind(DataBind b, Inet value) throws SQLException { + if (value == null) { + b.setNull(Types.VARCHAR); + } else { + b.setString(convertToDbString(value)); + } + } + } + + /** + * Inet to Postgres INET. + */ + public static class Postgres extends ScalarTypeInet { + + @Override + public void bind(DataBind b, Inet value) throws SQLException { + if (value == null) { + b.setNull(Types.OTHER); + } else { + String strValue = convertToDbString(value); + b.setObject(PostgresHelper.asInet(strValue)); + } + } + } +} diff --git a/src/main/java/io/ebeaninternal/server/type/ScalarTypeInetAddressPostgres.java b/src/main/java/io/ebeaninternal/server/type/ScalarTypeInetAddressPostgres.java new file mode 100644 index 000000000..cf20624c5 --- /dev/null +++ b/src/main/java/io/ebeaninternal/server/type/ScalarTypeInetAddressPostgres.java @@ -0,0 +1,57 @@ +package io.ebeaninternal.server.type; + +import io.ebean.config.dbplatform.ExtraDbTypes; +import io.ebean.text.TextException; + +import java.net.InetAddress; +import java.sql.SQLException; +import java.sql.Types; + +/** + * ScalarType for InetAddress to Postgres INET. + */ +public class ScalarTypeInetAddressPostgres extends ScalarTypeBaseVarchar { + + public ScalarTypeInetAddressPostgres() { + super(InetAddress.class, false, ExtraDbTypes.INET); + } + + @Override + public void bind(DataBind b, InetAddress value) throws SQLException { + if (value == null) { + b.setNull(Types.OTHER); + } else { + String strValue = convertToDbString(value); + b.setObject(PostgresHelper.asInet(strValue)); + } + } + + @Override + public InetAddress convertFromDbString(String dbValue) { + try { + return parse(dbValue); + } catch (IllegalArgumentException e) { + throw new RuntimeException("Error with InetAddresses [" + dbValue + "] " + e); + } + } + + @Override + public String convertToDbString(InetAddress beanValue) { + return formatValue(beanValue); + } + + @Override + public String formatValue(InetAddress v) { + return ConvertInetAddresses.toHostAddress(v); + } + + @Override + public InetAddress parse(String value) { + try { + return ConvertInetAddresses.fromHost(value); + } catch (IllegalArgumentException e) { + throw new TextException("Error with InetAddresses [{}]", value, e); + } + } + +} diff --git a/src/test/java/io/ebeaninternal/server/type/ConvertInetAddressTest.java b/src/test/java/io/ebeaninternal/server/type/ConvertInetAddressTest.java index 96243a427..83c7ad3e5 100644 --- a/src/test/java/io/ebeaninternal/server/type/ConvertInetAddressTest.java +++ b/src/test/java/io/ebeaninternal/server/type/ConvertInetAddressTest.java @@ -1,34 +1,46 @@ package io.ebeaninternal.server.type; -import org.junit.Assert; import org.junit.Test; import java.net.InetAddress; +import static org.junit.Assert.assertEquals; + public class ConvertInetAddressTest { @Test public void forString() { InetAddress addr = ConvertInetAddresses.forString("128.1.10.23"); - Assert.assertNotNull(addr); - Assert.assertEquals("128.1.10.23", addr.getHostAddress()); + assertEquals("128.1.10.23", addr.getHostAddress()); String ip6addr = "2001:db8:85a3:0:0:8a2e:370:7334"; InetAddress addr6 = ConvertInetAddresses.forString(ip6addr); String uriAddr6 = ConvertInetAddresses.toUriString(addr6); - Assert.assertEquals("[" + ip6addr + "]", uriAddr6); - Assert.assertEquals(ip6addr, addr6.getHostAddress()); + assertEquals("[" + ip6addr + "]", uriAddr6); + assertEquals(ip6addr, addr6.getHostAddress()); + } + + @Test + public void ipv6_fromHost_getHostAddress() { + InetAddress addr2 = ConvertInetAddresses.fromHost("2001:4f8:3:ba:2e0:81ff:fe22:d1f1"); + assertEquals("2001:4f8:3:ba:2e0:81ff:fe22:d1f1", addr2.getHostAddress()); + } + + @Test + public void ipv6_fromHost_getHostAddress_2() { + InetAddress addr2 = ConvertInetAddresses.fromHost("2001:db8:85a3:0:0:8a2e:370:7334"); + assertEquals("2001:db8:85a3:0:0:8a2e:370:7334", addr2.getHostAddress()); } @Test public void toUriString() { InetAddress addr = ConvertInetAddresses.forString("128.1.10.23"); - Assert.assertEquals("128.1.10.23", addr.getHostAddress()); + assertEquals("128.1.10.23", addr.getHostAddress()); String uriAddr = ConvertInetAddresses.toUriString(addr); - Assert.assertEquals("128.1.10.23", uriAddr); + assertEquals("128.1.10.23", uriAddr); } } diff --git a/src/test/java/org/tests/basic/type/TestInetAddressType.java b/src/test/java/org/tests/basic/type/TestInetAddressType.java index dda0e0ae8..3aba6b688 100644 --- a/src/test/java/org/tests/basic/type/TestInetAddressType.java +++ b/src/test/java/org/tests/basic/type/TestInetAddressType.java @@ -1,61 +1,100 @@ package org.tests.basic.type; -import io.ebean.Ebean; +import io.ebean.DB; import io.ebean.TransactionalTestCase; - -import org.tests.model.basic.EWithInetAddr; -import org.junit.Assert; +import io.ebean.types.Cdir; +import io.ebean.types.Inet; import org.junit.Test; +import org.tests.model.basic.EWithInetAddr; import java.net.InetAddress; import java.net.UnknownHostException; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + public class TestInetAddressType extends TransactionalTestCase { @Test public void testIp4() throws UnknownHostException { - insertUpdateDeleteFind("120.12.12.56"); + insertUpdateDeleteFind("120.12.12.56", "120.12.12.56"); } @Test public void testIp6() throws UnknownHostException { - insertUpdateDeleteFind("2001:db8:85a3:0:0:8a2e:370:7334"); + if (isPostgres()) { + insertUpdateDeleteFind("2001:db8:85a3:0:0:8a2e:370:7334", "2001:db8:85a3::8a2e:370:7334"); + } else { + insertUpdateDeleteFind("2001:db8:85a3:0:0:8a2e:370:7334", "2001:db8:85a3:0:0:8a2e:370:7334"); + } } - private void insertUpdateDeleteFind(String ipAddress) throws UnknownHostException { + private void insertUpdateDeleteFind(String ipAddress, String expected) throws UnknownHostException { EWithInetAddr bean1 = new EWithInetAddr(); bean1.setName("jim"); InetAddress address1 = InetAddress.getByName(ipAddress); bean1.setInetAddress(address1); + bean1.setInet2(new Inet(ipAddress)); + bean1.setCdir(new Cdir(ipAddress)); - Ebean.save(bean1); - EWithInetAddr bean2 = Ebean.find(EWithInetAddr.class, bean1.getId()); + DB.save(bean1); + + EWithInetAddr bean2 = DB.find(EWithInetAddr.class, bean1.getId()); InetAddress address2 = bean2.getInetAddress(); - Assert.assertNotNull(address2.getHostAddress()); - Assert.assertEquals(address1.getHostAddress(), address2.getHostAddress()); + assertNotNull(address2.getHostAddress()); + assertThat(address1.getHostAddress()).isEqualTo(address2.getHostAddress()); + assertThat(bean2.getInet2().getAddress()).isEqualTo(expected); + assertThat(bean2.getCdir().getAddress()).isEqualTo(expected); bean2.setName("modJim"); bean2.setInetAddress(InetAddress.getByName("120.12.20.80")); - Ebean.save(bean2); - Ebean.delete(bean2); + bean1.setInet2(new Inet("120.12.20.80")); + bean1.setCdir(new Cdir("120.12.20.80")); + DB.save(bean2); + DB.delete(bean2); } @Test - public void use_null() throws UnknownHostException { + public void testIp6_ranges() { + + insertFindDeleteRange("2001:4f8:3:ba::/64"); + insertFindDeleteRange("2001:4f8:3:ba:2e0:81ff:fe22:d1f1/64"); + } + + private void insertFindDeleteRange(String ipAddressRange) { + + EWithInetAddr bean1 = new EWithInetAddr(); + bean1.setName("withRange"); + bean1.setInet2(new Inet(ipAddressRange)); + bean1.setCdir(new Cdir(ipAddressRange)); + + DB.save(bean1); + + EWithInetAddr bean2 = DB.find(EWithInetAddr.class, bean1.getId()); + assertNotNull(bean2.getInet2()); + assertThat(bean2.getInet2().getAddress()).isEqualTo(ipAddressRange); + assertThat(bean2.getCdir().getAddress()).isEqualTo(ipAddressRange); + + DB.delete(bean2); + } + + @Test + public void use_null() { EWithInetAddr bean1 = new EWithInetAddr(); bean1.setName("jim"); - Ebean.save(bean1); + DB.save(bean1); - EWithInetAddr bean2 = Ebean.find(EWithInetAddr.class, bean1.getId()); - InetAddress address2 = bean2.getInetAddress(); - Assert.assertNull(address2); + EWithInetAddr bean2 = DB.find(EWithInetAddr.class, bean1.getId()); + assertNull(bean2.getInetAddress()); + assertNull(bean2.getInet2()); } } diff --git a/src/test/java/org/tests/model/basic/EWithInetAddr.java b/src/test/java/org/tests/model/basic/EWithInetAddr.java index 3e823dc15..87ca89342 100644 --- a/src/test/java/org/tests/model/basic/EWithInetAddr.java +++ b/src/test/java/org/tests/model/basic/EWithInetAddr.java @@ -1,5 +1,8 @@ package org.tests.model.basic; +import io.ebean.types.Cdir; +import io.ebean.types.Inet; + import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.Table; @@ -20,6 +23,10 @@ public class EWithInetAddr { InetAddress inetAddress; + Inet inet2; + + Cdir cdir; + public Long getId() { return id; } @@ -52,4 +59,19 @@ public class EWithInetAddr { this.inetAddress = inetAddress; } + public Inet getInet2() { + return inet2; + } + + public void setInet2(Inet inet2) { + this.inet2 = inet2; + } + + public Cdir getCdir() { + return cdir; + } + + public void setCdir(Cdir cdir) { + this.cdir = cdir; + } }