diff --git a/internal/build/pom.xml b/internal/build/pom.xml index a178113b0e..30459e796a 100644 --- a/internal/build/pom.xml +++ b/internal/build/pom.xml @@ -12,14 +12,15 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> - + 4.0.0 basepom-oss org.basepom 46 - + org.jdbi.internal @@ -58,6 +59,7 @@ 3.31.1-SNAPSHOT 21.0.1 2.9.9 + 1.19.0 5.8.2 1.7.10 4.2.0 @@ -69,6 +71,7 @@ 2.8.0 1.7.32 5.3.20 + 1.16.0 0.9.3 ${jdbi.check.fail-kotlin} ${basepom.check.fail-extended} @@ -226,6 +229,12 @@ + + org.locationtech.jts + jts-core + ${dep.jts.version} + + com.opentable.components otj-pg-embedded @@ -430,6 +439,27 @@ pom import + + + org.testcontainers + testcontainers + ${dep.testcontainers.version} + test + + + + org.testcontainers + junit-jupiter + ${dep.testcontainers.version} + test + + + + org.testcontainers + postgresql + ${dep.testcontainers.version} + test + @@ -638,7 +668,8 @@ policy/checkstyle.xml policy/checkstyle-suppress.xml - ${project.build.sourceDirectory},${project.build.testSourceDirectory} + ${project.build.sourceDirectory},${project.build.testSourceDirectory} + ${basepom.check.skip-extended} @@ -736,7 +767,7 @@ - + moduleName This module must set a moduleName. @@ -791,7 +822,7 @@ - + @@ -804,7 +835,7 @@ - + diff --git a/postgres/pom.xml b/postgres/pom.xml index 1e34981a98..68fba7c9b7 100644 --- a/postgres/pom.xml +++ b/postgres/pom.xml @@ -69,6 +69,30 @@ provided + + org.locationtech.jts + jts-core + provided + + + + org.testcontainers + testcontainers + test + + + + org.testcontainers + junit-jupiter + test + + + + org.testcontainers + postgresql + test + + org.immutables value diff --git a/postgres/src/main/java/org/jdbi/v3/postgres/PostgisPlugin.java b/postgres/src/main/java/org/jdbi/v3/postgres/PostgisPlugin.java new file mode 100644 index 0000000000..f6afc5f37c --- /dev/null +++ b/postgres/src/main/java/org/jdbi/v3/postgres/PostgisPlugin.java @@ -0,0 +1,218 @@ +/* + * Copyright (C) 2020 The Baremaps Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package org.jdbi.v3.postgres; + +import static org.locationtech.jts.io.WKBConstants.wkbNDR; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Types; +import org.jdbi.v3.core.Jdbi; +import org.jdbi.v3.core.argument.AbstractArgumentFactory; +import org.jdbi.v3.core.argument.Argument; +import org.jdbi.v3.core.config.ConfigRegistry; +import org.jdbi.v3.core.mapper.ColumnMapper; +import org.jdbi.v3.core.spi.JdbiPlugin; +import org.jdbi.v3.core.statement.StatementContext; +import org.locationtech.jts.geom.Geometry; +import org.locationtech.jts.geom.GeometryCollection; +import org.locationtech.jts.geom.GeometryFactory; +import org.locationtech.jts.geom.LineString; +import org.locationtech.jts.geom.LinearRing; +import org.locationtech.jts.geom.MultiLineString; +import org.locationtech.jts.geom.MultiPoint; +import org.locationtech.jts.geom.MultiPolygon; +import org.locationtech.jts.geom.Point; +import org.locationtech.jts.geom.Polygon; +import org.locationtech.jts.io.ParseException; +import org.locationtech.jts.io.WKBReader; +import org.locationtech.jts.io.WKBWriter; + +/** + * Postgis plugin. Adds support for binding and mapping the following data types: + * + *
    + *
  • {@link org.locationtech.jts.geom.Point}
  • + *
  • {@link org.locationtech.jts.geom.LineString}
  • + *
  • {@link org.locationtech.jts.geom.LinearRing}
  • + *
  • {@link org.locationtech.jts.geom.Polygon}
  • + *
  • {@link org.locationtech.jts.geom.MultiPoint}
  • + *
  • {@link org.locationtech.jts.geom.MultiLineString}
  • + *
  • {@link org.locationtech.jts.geom.MultiPolygon}
  • + *
  • {@link org.locationtech.jts.geom.GeometryCollection}
  • + *
  • {@link org.locationtech.jts.geom.Geometry}
  • + */ +public class PostgisPlugin extends JdbiPlugin.Singleton { + + @Override + public void customizeJdbi(Jdbi jdbi) { + // Register argument factories + jdbi.registerArgument(new PointArgumentFactory()); + jdbi.registerArgument(new LineStringArgumentFactory()); + jdbi.registerArgument(new LinearRingArgumentFactory()); + jdbi.registerArgument(new PolygonArgumentFactory()); + jdbi.registerArgument(new MultiPointArgumentFactory()); + jdbi.registerArgument(new MultiLineStringArgumentFactory()); + jdbi.registerArgument(new MultiPolygonArgumentFactory()); + jdbi.registerArgument(new GeometryCollectionArgumentFactory()); + jdbi.registerArgument(new GeometryArgumentFactory()); + + // Register column mappers + jdbi.registerColumnMapper(new PointColumnMapper()); + jdbi.registerColumnMapper(new LineStringColumnMapper()); + jdbi.registerColumnMapper(new LinearRingColumnMapper()); + jdbi.registerColumnMapper(new PolygonColumnMapper()); + jdbi.registerColumnMapper(new MultiPointColumnMapper()); + jdbi.registerColumnMapper(new MultiLineStringColumnMapper()); + jdbi.registerColumnMapper(new MultiPolygonColumnMapper()); + jdbi.registerColumnMapper(new GeometryCollectionColumnMapper()); + jdbi.registerColumnMapper(new GeometryColumnMapper()); + } + + /** + * Serializes a geometry in the WKB format. + * + * @param geometry + * @return + */ + public static byte[] serialize(Geometry geometry) { + if (geometry == null) { + return null; + } + WKBWriter writer = new WKBWriter(2, wkbNDR, true); + return writer.write(geometry); + } + + /** + * Deserializes a geometry in the WKB format. + * + * @param wkb + * @return + */ + public static Geometry deserialize(byte[] wkb) { + if (wkb == null) { + return null; + } + try { + WKBReader reader = new WKBReader(new GeometryFactory()); + return reader.read(wkb); + } catch (ParseException e) { + throw new IllegalArgumentException(e); + } + } + + abstract static class BaseArgumentFactory extends AbstractArgumentFactory { + + public BaseArgumentFactory() { + super(Types.OTHER); + } + + @Override + public Argument build(T value, ConfigRegistry config) { + return (position, statement, ctx) -> statement.setBytes(position, serialize(value)); + } + } + + abstract static class BaseColumnMapper implements ColumnMapper { + + @Override + public T map(ResultSet r, int columnNumber, StatementContext ctx) throws SQLException { + byte[] bytes = hexStringToByteArray(r.getString(columnNumber)); + return (T) deserialize(bytes); + } + + private static byte[] hexStringToByteArray(String s) { + int len = s.length(); + byte[] data = new byte[len / 2]; + for (int i = 0; i < len; i += 2) { + data[i / 2] = + (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character.digit(s.charAt(i + 1), 16)); + } + return data; + } + } + + static final class GeometryArgumentFactory extends BaseArgumentFactory { + + } + + static final class GeometryCollectionArgumentFactory extends BaseArgumentFactory { + + } + + static final class GeometryCollectionColumnMapper extends BaseColumnMapper { + + } + + static final class GeometryColumnMapper extends BaseColumnMapper { + + } + + static final class LinearRingArgumentFactory extends BaseArgumentFactory { + + } + + static final class LinearRingColumnMapper extends BaseColumnMapper { + + } + + static final class LineStringArgumentFactory extends BaseArgumentFactory { + + } + + static final class LineStringColumnMapper extends BaseColumnMapper { + + } + + static final class MultiLineStringArgumentFactory extends BaseArgumentFactory { + + } + + static final class MultiLineStringColumnMapper extends BaseColumnMapper { + + } + + static final class MultiPointArgumentFactory extends BaseArgumentFactory { + + } + + static final class MultiPointColumnMapper extends BaseColumnMapper { + + } + + static final class MultiPolygonArgumentFactory extends BaseArgumentFactory { + + } + + static final class MultiPolygonColumnMapper extends BaseColumnMapper { + + } + + static final class PointArgumentFactory extends BaseArgumentFactory { + + } + + static final class PointColumnMapper extends BaseColumnMapper { + + } + + static final class PolygonArgumentFactory extends BaseArgumentFactory { + + } + + static final class PolygonColumnMapper extends BaseColumnMapper { + + } +} diff --git a/postgres/src/test/java/org/jdbi/v3/postgres/PostgisPluginTest.java b/postgres/src/test/java/org/jdbi/v3/postgres/PostgisPluginTest.java new file mode 100644 index 0000000000..d78bb27d30 --- /dev/null +++ b/postgres/src/test/java/org/jdbi/v3/postgres/PostgisPluginTest.java @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2020 The Baremaps Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package org.jdbi.v3.postgres; + +import java.util.List; +import org.jdbi.v3.core.Jdbi; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.locationtech.jts.geom.Coordinate; +import org.locationtech.jts.geom.GeometryFactory; +import org.locationtech.jts.geom.LineString; +import org.locationtech.jts.geom.Point; +import org.locationtech.jts.geom.Polygon; + +class PostgisPluginTest { + + @Test + @Disabled + void test() { + Jdbi jdbi = Jdbi.create("jdbc:tc:postgis:13-3.1:///test") + .installPlugin(new PostgresPlugin()) + .installPlugin(new PostgisPlugin()); + + PostgisRecord record = new PostgisRecord(); + record.setId(1); + record.setPoint(new GeometryFactory().createPoint(new Coordinate(1, 1))); + record.setLineString( + new GeometryFactory() + .createLineString( + new Coordinate[] { + new Coordinate(1, 1), + new Coordinate(1, 2), + new Coordinate(2, 2), + new Coordinate(2, 1) + })); + record.setPolygon( + new GeometryFactory() + .createPolygon( + new Coordinate[] { + new Coordinate(1, 1), + new Coordinate(1, 2), + new Coordinate(2, 2), + new Coordinate(2, 1), + new Coordinate(1, 1), + })); + + List result = + jdbi.withHandle( + handle -> { + handle.execute("DROP TABLE IF EXISTS record"); + handle.execute( + "CREATE TABLE record (id INTEGER PRIMARY KEY, point geometry(point), linestring geometry(linestring), polygon geometry(polygon))"); + handle + .createUpdate( + "INSERT INTO record (id, point, linestring, polygon) VALUES (:id, :point, :lineString, :polygon)") + .bindBean(record) + .execute(); + return handle + .createQuery("SELECT * FROM record ORDER BY id") + .mapToBean(PostgisRecord.class) + .list(); + }); + + Assertions.assertEquals(record.getPoint(), result.get(0).getPoint()); + Assertions.assertEquals(record.getLineString(), result.get(0).getLineString()); + Assertions.assertEquals(record.getPolygon(), result.get(0).getPolygon()); + } + + public static class PostgisRecord { + + private Integer id; + private Point point; + private LineString lineString; + private Polygon polygon; + + public PostgisRecord() {} + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public Point getPoint() { + return point; + } + + public void setPoint(Point point) { + this.point = point; + } + + public LineString getLineString() { + return lineString; + } + + public void setLineString(LineString lineString) { + this.lineString = lineString; + } + + public Polygon getPolygon() { + return polygon; + } + + public void setPolygon(Polygon polygon) { + this.polygon = polygon; + } + } + +}