|
18 | 18 | import io.micronaut.core.annotation.Internal; |
19 | 19 | import io.micronaut.core.reflect.ClassUtils; |
20 | 20 | import io.micronaut.data.annotation.Relation; |
| 21 | +import io.micronaut.data.exceptions.DataAccessException; |
21 | 22 | import io.micronaut.data.model.runtime.RuntimeAssociation; |
22 | 23 | import io.micronaut.data.model.runtime.RuntimePersistentEntity; |
23 | 24 | import io.micronaut.data.model.runtime.RuntimePersistentProperty; |
|
57 | 58 | public final class NitriteFilterBuilder { |
58 | 59 |
|
59 | 60 | private static final Logger LOG = LoggerFactory.getLogger(NitriteFilterBuilder.class); |
| 61 | + |
| 62 | + // Spatial filter class names (optional dependencies) |
60 | 63 | private static final String SPATIAL_FLUENT_FILTER_CLASS = "org.dizitart.no2.spatial.SpatialFluentFilter"; |
61 | | - private static final String GEOMETRY_CLASS = "org.locationtech.jts.geom.Geometry"; |
| 64 | + private static final String GEO_POINT_CLASS = "org.dizitart.no2.spatial.GeoPoint"; |
| 65 | + private static final String JTS_GEOMETRY_CLASS = "org.locationtech.jts.geom.Geometry"; |
| 66 | + private static final String JTS_POINT_CLASS = "org.locationtech.jts.geom.Point"; |
| 67 | + private static final String JTS_COORDINATE_CLASS = "org.locationtech.jts.geom.Coordinate"; |
62 | 68 |
|
63 | 69 | /** |
64 | 70 | * A filter that matches no documents (used for empty IN clauses). |
@@ -1135,59 +1141,107 @@ private Filter buildNearFilter(String field, Object value, Object[] params, Map< |
1135 | 1141 | Object center = entityMapper.toNitriteFilterValue(preConvertForFilter(resolveValue(m.get("center"), params, namedParameters)), field); |
1136 | 1142 | Object distanceObj = resolveValue(m.get("distance"), params, namedParameters); |
1137 | 1143 | double distance = distanceObj instanceof Number n ? n.doubleValue() : 0.0; |
1138 | | - return createSpatialFilter(field, "near", new Class<?>[]{Object.class, double.class}, center, distance); |
1139 | | - } |
1140 | | - return Filter.ALL; |
1141 | | - } |
1142 | | - |
1143 | | - private Filter createSpatialFilter(String field, String method, Class<?>[] argTypes, Object... args) { |
1144 | | - if (ClassUtils.isPresent(SPATIAL_FLUENT_FILTER_CLASS, null)) { |
1145 | | - try { |
1146 | | - Class<?> spatialClass = Class.forName(SPATIAL_FLUENT_FILTER_CLASS); |
1147 | | - Method whereMethod = spatialClass.getMethod("where", String.class); |
1148 | | - Object spatialFluentFilter = whereMethod.invoke(null, field); |
1149 | | - if ("near".equals(method) && args.length == 2) { |
1150 | | - Object center = args[0]; |
1151 | | - double distance = args[1] instanceof Number n ? n.doubleValue() : 0.0; |
1152 | | - Object coordinate = null; |
1153 | | - if (center != null && ClassUtils.isPresent(GEOMETRY_CLASS, null)) { |
1154 | | - Class<?> geometryClass = Class.forName(GEOMETRY_CLASS); |
| 1144 | + |
| 1145 | + // Use reflection to create spatial filters (nitrite-spatial is optional) |
| 1146 | + if (ClassUtils.isPresent(SPATIAL_FLUENT_FILTER_CLASS, null)) { |
| 1147 | + try { |
| 1148 | + Class<?> spatialClass = Class.forName(SPATIAL_FLUENT_FILTER_CLASS); |
| 1149 | + Method whereMethod = spatialClass.getMethod("where", String.class); |
| 1150 | + Object spatialFluentFilter = whereMethod.invoke(null, field); |
| 1151 | + |
| 1152 | + // Handle GeoPoint (via reflection to avoid hard dependency) |
| 1153 | + if (center != null && ClassUtils.isPresent(GEO_POINT_CLASS, null)) { |
| 1154 | + Class<?> geoPointClass = Class.forName(GEO_POINT_CLASS); |
| 1155 | + if (geoPointClass.isInstance(center)) { |
| 1156 | + Object point = geoPointClass.getMethod("getPoint").invoke(center); |
| 1157 | + Method nearMethod = spatialFluentFilter.getClass().getMethod("near", Class.forName(JTS_POINT_CLASS), Double.class); |
| 1158 | + return (Filter) nearMethod.invoke(spatialFluentFilter, point, distance); |
| 1159 | + } |
| 1160 | + } |
| 1161 | + |
| 1162 | + // Handle JTS Point |
| 1163 | + if (center != null && ClassUtils.isPresent(JTS_POINT_CLASS, null)) { |
| 1164 | + Class<?> pointClass = Class.forName(JTS_POINT_CLASS); |
| 1165 | + if (pointClass.isInstance(center)) { |
| 1166 | + Method nearMethod = spatialFluentFilter.getClass().getMethod("near", pointClass, Double.class); |
| 1167 | + return (Filter) nearMethod.invoke(spatialFluentFilter, center, distance); |
| 1168 | + } |
| 1169 | + } |
| 1170 | + |
| 1171 | + // Handle JTS Geometry - extract coordinate |
| 1172 | + if (center != null && ClassUtils.isPresent(JTS_GEOMETRY_CLASS, null)) { |
| 1173 | + Class<?> geometryClass = Class.forName(JTS_GEOMETRY_CLASS); |
1155 | 1174 | if (geometryClass.isInstance(center)) { |
1156 | | - Method getCoordinateMethod = geometryClass.getMethod("getCoordinate"); |
1157 | | - coordinate = getCoordinateMethod.invoke(center); |
| 1175 | + Method getCoordMethod = geometryClass.getMethod("getCoordinate"); |
| 1176 | + Object coord = getCoordMethod.invoke(center); |
| 1177 | + if (coord != null) { |
| 1178 | + Method nearMethod = spatialFluentFilter.getClass().getMethod("near", Class.forName(JTS_COORDINATE_CLASS), Double.class); |
| 1179 | + return (Filter) nearMethod.invoke(spatialFluentFilter, coord, distance); |
| 1180 | + } |
1158 | 1181 | } |
1159 | 1182 | } |
1160 | | - if (coordinate != null) { |
1161 | | - Method filterMethod = spatialFluentFilter.getClass().getMethod("near", Class.forName("org.locationtech.jts.geom.Coordinate"), Double.class); |
1162 | | - return (Filter) filterMethod.invoke(spatialFluentFilter, coordinate, Double.valueOf(distance)); |
| 1183 | + |
| 1184 | + // Fallback for Coordinate |
| 1185 | + if (center != null && ClassUtils.isPresent(JTS_COORDINATE_CLASS, null)) { |
| 1186 | + Class<?> coordClass = Class.forName(JTS_COORDINATE_CLASS); |
| 1187 | + if (coordClass.isInstance(center)) { |
| 1188 | + Method nearMethod = spatialFluentFilter.getClass().getMethod("near", coordClass, Double.class); |
| 1189 | + return (Filter) nearMethod.invoke(spatialFluentFilter, center, distance); |
| 1190 | + } |
1163 | 1191 | } |
1164 | | - } else { |
1165 | | - Method filterMethod = spatialFluentFilter.getClass().getMethod(method, argTypes); |
1166 | | - return (Filter) filterMethod.invoke(spatialFluentFilter, args); |
| 1192 | + |
| 1193 | + if (center != null) { |
| 1194 | + throw new DataAccessException("Unsupported center type for $near spatial filter: " + center.getClass().getName()); |
| 1195 | + } |
| 1196 | + } catch (DataAccessException e) { |
| 1197 | + throw e; |
| 1198 | + } catch (Exception e) { |
| 1199 | + throw new DataAccessException("Failed to create $near spatial filter on field '" + field + "': " + e.getMessage(), e); |
1167 | 1200 | } |
1168 | | - } catch (Exception e) { |
1169 | | - throw new RuntimeException("Failed to create spatial filter for method: " + method, e); |
1170 | 1201 | } |
1171 | 1202 | } |
1172 | 1203 | return Filter.ALL; |
1173 | 1204 | } |
1174 | 1205 |
|
1175 | 1206 | private Filter createSpatialFilter(String field, Object geometry, String method) { |
1176 | | - if (geometry == null || !ClassUtils.isPresent(GEOMETRY_CLASS, null)) { |
| 1207 | + if (geometry == null) { |
1177 | 1208 | return Filter.ALL; |
1178 | 1209 | } |
1179 | | - try { |
1180 | | - Class<?> geometryClass = Class.forName(GEOMETRY_CLASS); |
1181 | | - if (geometryClass.isInstance(geometry)) { |
| 1210 | + |
| 1211 | + // Use reflection to create spatial filters (nitrite-spatial is optional) |
| 1212 | + if (ClassUtils.isPresent(SPATIAL_FLUENT_FILTER_CLASS, null)) { |
| 1213 | + try { |
1182 | 1214 | Class<?> spatialClass = Class.forName(SPATIAL_FLUENT_FILTER_CLASS); |
1183 | 1215 | Method whereMethod = spatialClass.getMethod("where", String.class); |
1184 | 1216 | Object spatialFluentFilter = whereMethod.invoke(null, field); |
1185 | | - Method filterMethod = spatialFluentFilter.getClass().getMethod(method, geometryClass); |
1186 | | - return (Filter) filterMethod.invoke(spatialFluentFilter, geometry); |
| 1217 | + |
| 1218 | + // Handle GeoPoint for within/intersects (convert to Point via reflection) |
| 1219 | + if (ClassUtils.isPresent(GEO_POINT_CLASS, null)) { |
| 1220 | + Class<?> geoPointClass = Class.forName(GEO_POINT_CLASS); |
| 1221 | + if (geoPointClass.isInstance(geometry)) { |
| 1222 | + Object point = geoPointClass.getMethod("getPoint").invoke(geometry); |
| 1223 | + Method filterMethod = spatialFluentFilter.getClass().getMethod(method, Class.forName(JTS_POINT_CLASS)); |
| 1224 | + return (Filter) filterMethod.invoke(spatialFluentFilter, point); |
| 1225 | + } |
| 1226 | + } |
| 1227 | + |
| 1228 | + // Handle JTS Geometry directly |
| 1229 | + if (ClassUtils.isPresent(JTS_GEOMETRY_CLASS, null)) { |
| 1230 | + Class<?> geometryClass = Class.forName(JTS_GEOMETRY_CLASS); |
| 1231 | + if (geometryClass.isInstance(geometry)) { |
| 1232 | + Method filterMethod = spatialFluentFilter.getClass().getMethod(method, geometryClass); |
| 1233 | + return (Filter) filterMethod.invoke(spatialFluentFilter, geometry); |
| 1234 | + } |
| 1235 | + } |
| 1236 | + |
| 1237 | + throw new DataAccessException("Unsupported geometry type for $" + method + " spatial filter: " + geometry.getClass().getName()); |
| 1238 | + } catch (DataAccessException e) { |
| 1239 | + throw e; |
| 1240 | + } catch (Exception e) { |
| 1241 | + throw new DataAccessException("Failed to create $" + method + " spatial filter on field '" + field + "': " + e.getMessage(), e); |
1187 | 1242 | } |
1188 | | - } catch (Exception e) { |
1189 | | - throw new RuntimeException("Failed to create spatial filter for method: " + method, e); |
1190 | 1243 | } |
| 1244 | + |
1191 | 1245 | return Filter.ALL; |
1192 | 1246 | } |
1193 | 1247 |
|
|
0 commit comments