Skip to content

Commit

Permalink
Merge pull request #50 from marklogic-community/develop
Browse files Browse the repository at this point in the history
Release 1.1.1
  • Loading branch information
mfgumban authored Feb 25, 2020
2 parents 0420a34 + 1e62b3c commit 17a45a6
Show file tree
Hide file tree
Showing 39 changed files with 912 additions and 56 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -216,3 +216,5 @@ gradle-app.setting

# End of https://www.gitignore.io/api/macos,gradle,windows,sublimetext,visualstudiocode,node
.vscode/settings.json

.idea
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ dependencies {
testImplementation "com.google.guava:guava:27.1-jre"
testImplementation "io.rest-assured:json-path:3.0.6"
testImplementation "io.rest-assured:rest-assured:3.0.6"
testImplementation "io.rest-assured:json-schema-validator:3.0.6"
testImplementation "junit:junit:4.12"
testImplementation "com.googlecode.json-simple:json-simple:1.1.1"
testImplementation "org.gradle:gradle-tooling-api:2.1"
Expand Down
2 changes: 1 addition & 1 deletion examples/sample-project/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ buildscript {
jcenter()
}
dependencies {
classpath "com.marklogic:marklogic-geo-data-services-modules:1.1.0"
classpath "com.marklogic:marklogic-geo-data-services-modules:1.1.1"
classpath "com.google.code.gson:gson:2.8.6"
}
}
Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
mlAppName=geo-data-services
version=1.1.0
version=1.1.1

mlHost=localhost
mlRestPort=8095
Expand Down
26 changes: 26 additions & 0 deletions src/main/ml-modules/ext/gds.sjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
'use strict';

function getServiceModel(serviceName) {
xdmp.trace("KOOP-DEBUG", "Starting getServiceModel");
// TODO: These should be cached

const collection = "http://marklogic.com/feature-services";

xdmp.trace("KOOP-DEBUG", "Searching for Service Model: " + serviceName);
let model = fn.head(
cts.search(cts.andQuery([
cts.collectionQuery(collection),
cts.jsonPropertyValueQuery("name", serviceName, ["exact"])
]))
);

if (model) {
xdmp.trace("KOOP-DEBUG", "Found service: " + serviceName);
return model.toObject();
} else {
xdmp.trace("KOOP-DEBUG", "No service info found for: " + serviceName);
throw "No service info found for: " + serviceName;
}
}

exports.getServiceModel = getServiceModel;
7 changes: 4 additions & 3 deletions src/main/ml-modules/ext/geo/extractor.sjs
Original file line number Diff line number Diff line change
Expand Up @@ -500,7 +500,8 @@ function CustomExtractor(layer) {
selectors.push( op.jsonObject([
op.prop("pointFormat", index.pointFormat),
op.prop("coordinateSystem", index.coordinateSystem),
op.prop("points", op.map.entry("list", op.xpath("doc", getPathXPath(index))))
// convert node value to string so the extractor can handle the values later
op.prop("points", op.map.entry("list", op.call("http://www.w3.org/2005/xpath-functions", "string", op.xpath("doc", getPathXPath(index)))))
]));
}
}
Expand Down Expand Up @@ -685,12 +686,12 @@ function getElementPairLatXPath(index) {

function getAttributePairLonXPath(index) {
xdmp.trace("KOOP-DEBUG", "Using the following extractor helper function: getAttributePairLonXPath");
return `//${ns(index.parentNamespaceUri)}${index.parentLocalname}/@${ns(index.longitudeNamespaceUri)}${index.longitudeLocalname}/node()`;
return `//${ns(index.parentNamespaceUri)}${index.parentLocalname}/@${ns(index.longitudeNamespaceUri)}${index.longitudeLocalname}`;
}

function getAttributePairLatXPath(index) {
xdmp.trace("KOOP-DEBUG", "Using the following extractor helper function: getAttributePairLatXPath");
return `//${ns(index.parentNamespaceUri)}${index.parentLocalname}/@${ns(index.latitudeNamespaceUri)}${index.latitudeLocalname}/node()`;
return `//${ns(index.parentNamespaceUri)}${index.parentLocalname}/@${ns(index.latitudeNamespaceUri)}${index.latitudeLocalname}`;
}

function getExtractFunction(layerModel) {
Expand Down
44 changes: 26 additions & 18 deletions src/main/ml-modules/services/KoopFeatureLayer.sjs
Original file line number Diff line number Diff line change
@@ -1,26 +1,33 @@
'use strict';

const gds = require('/ext/gds.sjs');
const search = require('/MarkLogic/appservices/search/search.xqy');
const sut = require('/MarkLogic/rest-api/lib/search-util.xqy');
const ast = require('/MarkLogic/appservices/search/ast.xqy');


const koopConfigUri = '/koop/config.json';
const collFeatureServices = 'http://marklogic.com/feature-services';

// Returns all feature layers of a feature service
function get(context, params) {
try {
xdmp.trace('GDS-DEBUG', 'Start GET KoopFeatureLayer');

var response = {};

var serviceName = params.service;
var model = getServiceModel(serviceName);
if (!serviceName) {
const errorMsg = 'Request parameter \"service\" is missing or has no value';
xdmp.trace('GDS-DEBUG', errorMsg);
throw { code: 400, msg: errorMsg };
}

if (model === null) {
fn.error(null, 'Unable to find service ' + serviceName + '.');
var model = null;
try { model = gds.getServiceModel(serviceName); }
catch (err) {
const errorMsg = 'Unable to find service with name \"' + serviceName + '\"';
xdmp.trace('GDS-DEBUG', 'Exception thrown by gds.getServiceModel(): ' + JSON.stringify(err));
throw { code: 404, msg: errorMsg };
}

model = model.toObject();
var layers = model.layers;
if (params.readOnly) {
var filterReadOnly = fn.lowerCase(params.readOnly) === 'true';
Expand All @@ -37,19 +44,28 @@ function get(context, params) {
layers: layers
}

xdmp.trace('GDS-DEBUG', 'Response: ' + JSON.stringify(response));
return response;
}
catch (err) {
console.trace(err);
returnErrToClient(500, 'Error handling request', err.toString());
xdmp.trace('GDS-DEBUG', 'Responding with error due to exception: ' + JSON.stringify(err));
if (err.code && err.msg) {
returnErrToClient(err.code, 'Error handling request', err.msg);
}
else {
returnErrToClient(500, 'Error handling request', JSON.stringify(err));
}
}
finally {
xdmp.trace('GDS-DEBUG', 'End GET KoopFeatureLayer');
}
}

// Inserts or replaces a feature layer
function put(context, params, input) {
try {
var serviceName = params.service;
var model = getServiceModel(serviceName);
var model = gds.getServiceModel(serviceName);
var schema = params.schema || serviceName;

if (model === null) {
Expand Down Expand Up @@ -117,14 +133,6 @@ function put(context, params, input) {
}
}

function getServiceModel(serviceName) {
return fn.head(cts.search(
cts.andQuery([
cts.collectionQuery(collFeatureServices),
cts.jsonPropertyValueQuery("name", serviceName)
])));
}

function createNewLayerObj(id, name, desc, geometryType, schema, view) {
return {
"id": id,
Expand Down
60 changes: 27 additions & 33 deletions src/main/ml-modules/services/geoQueryService.sjs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

const op = require('/MarkLogic/optic');
const geojson = require('/MarkLogic/geospatial/geojson.xqy');
const gds = require('/ext/gds.sjs');
const sql2optic = require('/ext/sql/sql2optic.sjs');
const geostats = require('/ext/geo/geostats.js');
const geoextractor = require('/ext/geo/extractor.sjs');
Expand Down Expand Up @@ -68,34 +69,11 @@ function getData(req) {
returnErrToClient(501, 'Request parameters not supported', xdmp.quote(req));
}

function getServiceModel(serviceName) {
xdmp.trace("KOOP-DEBUG", "Starting getServiceModel");
// TODO: These should be cached

const collection = "http://marklogic.com/feature-services";

xdmp.trace("KOOP-DEBUG", "Searching for Service Model: " + serviceName);
let model = fn.head(
cts.search(cts.andQuery([
cts.collectionQuery(collection),
cts.jsonPropertyValueQuery("name", serviceName)
]))
);

if (model) {
xdmp.trace("KOOP-DEBUG", "Found service: " + serviceName);
return model.toObject();
} else {
xdmp.trace("KOOP-DEBUG", "No service info found for: " + serviceName);
throw "No service info found for: " + serviceName;
}
}

function getLayerModel(serviceName, layerId) {
xdmp.trace("KOOP-DEBUG", "Starting getLayerModel");
// TODO: These should be cached

const serviceModel = getServiceModel(serviceName);
const serviceModel = gds.getServiceModel(serviceName);

let layer = null;
if (serviceModel) {
Expand Down Expand Up @@ -153,7 +131,7 @@ function generateServiceDescriptor(serviceName) {

// TODO: we should cache this instead of generating it every time

const model = getServiceModel(serviceName);
const model = gds.getServiceModel(serviceName);

const desc = {
description: model.info.description,
Expand Down Expand Up @@ -405,10 +383,6 @@ function query(req, exportPlan=false) {

geojson.count = Array.from(aggregate(req))[0].count;

// this is a workaround for https://github.com/koopjs/FeatureServer/issues/70
if (geojson.count === 0) {
geojson.features = [];
}
} else if (req.query.outStatistics != null) {

xdmp.trace("KOOP-DEBUG", "running aggregation");
Expand Down Expand Up @@ -464,6 +438,12 @@ function query(req, exportPlan=false) {
geojson.metadata.displayField = layerModel.metadata.displayField;
}

// GeoJSON feature collections must always have a "features" object array, even if empty.
// See GeoJSON RFC: https://tools.ietf.org/html/rfc7946#section-3.2
if (!geojson.hasOwnProperty("features")) {
geojson.features = [];
}

return geojson;
}

Expand Down Expand Up @@ -1054,10 +1034,24 @@ function getObjects(req, exportPlan=false) {
return exported;
}
else {
if (returnGeometry && extractor.hasExtractFunction()) {
xdmp.trace("KOOP-DEBUG", "Getting Extractor function");
pipeline = pipeline.map(extractor.extract);
}
// GeoJSON features must always have a "geometry" property; for cases where the feature has no
// associated geometry data or "returnGeometry" is set to false, set "geometry" property to null.
// See GeoJSON RFC: https://tools.ietf.org/html/rfc7946#section-3.2
pipeline = pipeline.map((feature) => {
var outFeature = feature;

if (returnGeometry && extractor.hasExtractFunction()) {
xdmp.trace("KOOP-DEBUG", "Getting Extractor function");
outFeature = extractor.extract(feature);
}

if (outFeature && !outFeature.hasOwnProperty("geometry")) {
outFeature.geometry = null;
}

return outFeature;
});

xdmp.trace("KOOP-DEBUG", "Now to pull results from the pipeline with the following bindParams");
xdmp.trace("KOOP-DEBUG", bindParams);
const opticResult = Array.from(pipeline.result("object", bindParams));
Expand Down
11 changes: 11 additions & 0 deletions src/test/java/AbstractFeatureServiceTest.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import io.restassured.path.json.config.JsonPathConfig;
import io.restassured.path.json.JsonPath;
import io.restassured.RestAssured;
import io.restassured.module.jsv.JsonSchemaValidator;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.Map;
Expand All @@ -9,9 +10,19 @@
import org.junit.*;

import static io.restassured.RestAssured.*;
import static io.restassured.module.jsv.JsonSchemaValidator.*;

public abstract class AbstractFeatureServiceTest {

private static JsonSchemaValidator geoJsonFeatureCollectionValidator = null;

public JsonSchemaValidator isValidFeatureCollection() {
if (geoJsonFeatureCollectionValidator == null) {
geoJsonFeatureCollectionValidator = matchesJsonSchema(AbstractFeatureServiceTest.class.getResource("geojson-schema/FeatureCollection.json"));
}
return geoJsonFeatureCollectionValidator;
}

static String url = "/LATEST/resources/" + System.getProperty("feature.service");
static String port = System.getProperty("feature.port");
Log logger = null;
Expand Down
2 changes: 2 additions & 0 deletions src/test/java/AliasTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ public void testFieldAlias() {
.log().ifError()
.statusCode(200)
.log().ifValidationFails()
.body(isValidFeatureCollection())

.body("features.size()", is(5))

.body("features[0].properties.OBJECTID", is(20643))
Expand Down
Loading

0 comments on commit 17a45a6

Please sign in to comment.