From ac31e9c0108fdc7f41057abc9c0146ce89f7821f Mon Sep 17 00:00:00 2001 From: Gobe Hobona Date: Thu, 19 Nov 2020 10:51:31 +0000 Subject: [PATCH] Added tests for Conformance Declaration and Landing Page --- .classpath | 43 ++ .project | 23 + .settings/org.eclipse.core.resources.prefs | 7 + .settings/org.eclipse.jdt.core.prefs | 8 + .settings/org.eclipse.m2e.core.prefs | 4 + pom.xml | 27 + .../cite/ogcapiprocesses10/CommonFixture.java | 58 +- .../cite/ogcapiprocesses10/ETSAssert.java | 53 +- .../ogcapiprocesses10/OgcApiProcesses10.java | 17 + .../ogcapiprocesses10/SuiteAttribute.java | 52 +- .../SuiteFixtureListener.java | 93 ++- .../cite/ogcapiprocesses10/TestRunArg.java | 10 +- .../conformance/Conformance.java | 121 ++++ .../conformance/RequirementClass.java | 75 +++ .../landingpage/LandingPage.java | 92 +++ .../level1/Capability1Tests.java | 95 --- .../level1/package-info.java | 14 - .../openapi3/OpenApiUtils.java | 599 ++++++++++++++++++ .../ogcapiprocesses10/openapi3/TestPoint.java | 110 ++++ .../openapi3/UriBuilder.java | 88 +++ .../cite/ogcapiprocesses10/util/BBox.java | 81 +++ .../ogcapiprocesses10/util/ClientUtils.java | 1 + .../ogcapiprocesses10/util/JsonUtils.java | 388 ++++++++++++ .../util/NamespaceBindings.java | 1 + .../util/TemporalExtent.java | 27 + .../cite/ogcapiprocesses10/util/URIUtils.java | 112 +--- .../util/ValidationUtils.java | 162 ----- .../cite/ogcapiprocesses10/util/XMLUtils.java | 384 +---------- .../opengis/cite/ogcapiprocesses10/testng.xml | 5 +- src/main/resources/test-run-props.xml | 4 +- .../VerifyTestNGController.java | 71 --- .../level1/VerifyCapability1Tests.java | 72 --- .../util/VerifyURIUtils.java | 83 --- .../util/VerifyValidationUtils.java | 43 -- .../util/VerifyXMLUtils.java | 147 ----- 35 files changed, 1915 insertions(+), 1255 deletions(-) create mode 100644 .classpath create mode 100644 .project create mode 100644 .settings/org.eclipse.core.resources.prefs create mode 100644 .settings/org.eclipse.jdt.core.prefs create mode 100644 .settings/org.eclipse.m2e.core.prefs create mode 100644 src/main/java/org/opengis/cite/ogcapiprocesses10/OgcApiProcesses10.java create mode 100644 src/main/java/org/opengis/cite/ogcapiprocesses10/conformance/Conformance.java create mode 100644 src/main/java/org/opengis/cite/ogcapiprocesses10/conformance/RequirementClass.java create mode 100644 src/main/java/org/opengis/cite/ogcapiprocesses10/landingpage/LandingPage.java delete mode 100644 src/main/java/org/opengis/cite/ogcapiprocesses10/level1/Capability1Tests.java delete mode 100644 src/main/java/org/opengis/cite/ogcapiprocesses10/level1/package-info.java create mode 100644 src/main/java/org/opengis/cite/ogcapiprocesses10/openapi3/OpenApiUtils.java create mode 100644 src/main/java/org/opengis/cite/ogcapiprocesses10/openapi3/TestPoint.java create mode 100644 src/main/java/org/opengis/cite/ogcapiprocesses10/openapi3/UriBuilder.java create mode 100644 src/main/java/org/opengis/cite/ogcapiprocesses10/util/BBox.java create mode 100644 src/main/java/org/opengis/cite/ogcapiprocesses10/util/JsonUtils.java create mode 100644 src/main/java/org/opengis/cite/ogcapiprocesses10/util/TemporalExtent.java delete mode 100644 src/main/java/org/opengis/cite/ogcapiprocesses10/util/ValidationUtils.java delete mode 100644 src/test/java/org/opengis/cite/ogcapiprocesses10/VerifyTestNGController.java delete mode 100644 src/test/java/org/opengis/cite/ogcapiprocesses10/level1/VerifyCapability1Tests.java delete mode 100644 src/test/java/org/opengis/cite/ogcapiprocesses10/util/VerifyURIUtils.java delete mode 100644 src/test/java/org/opengis/cite/ogcapiprocesses10/util/VerifyValidationUtils.java delete mode 100644 src/test/java/org/opengis/cite/ogcapiprocesses10/util/VerifyXMLUtils.java diff --git a/.classpath b/.classpath new file mode 100644 index 0000000..7dc4cce --- /dev/null +++ b/.classpath @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.project b/.project new file mode 100644 index 0000000..22f241f --- /dev/null +++ b/.project @@ -0,0 +1,23 @@ + + + ets-ogcapi-processes10 + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.eclipse.jdt.core.javanature + org.eclipse.m2e.core.maven2Nature + + diff --git a/.settings/org.eclipse.core.resources.prefs b/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 0000000..289b5ef --- /dev/null +++ b/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,7 @@ +eclipse.preferences.version=1 +encoding//src/main/java=UTF-8 +encoding//src/main/javadoc=UTF-8 +encoding//src/main/resources=UTF-8 +encoding//src/test/java=UTF-8 +encoding//src/test/resources=UTF-8 +encoding/=UTF-8 diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000..2f5cc74 --- /dev/null +++ b/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,8 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 +org.eclipse.jdt.core.compiler.compliance=1.8 +org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled +org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning +org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=ignore +org.eclipse.jdt.core.compiler.release=disabled +org.eclipse.jdt.core.compiler.source=1.8 diff --git a/.settings/org.eclipse.m2e.core.prefs b/.settings/org.eclipse.m2e.core.prefs new file mode 100644 index 0000000..f897a7f --- /dev/null +++ b/.settings/org.eclipse.m2e.core.prefs @@ -0,0 +1,4 @@ +activeProfiles= +eclipse.preferences.version=1 +resolveWorkspaceProjects=true +version=1 diff --git a/pom.xml b/pom.xml index cc3628e..94fdc5d 100644 --- a/pom.xml +++ b/pom.xml @@ -65,6 +65,21 @@ com.sun.jersey jersey-client + + io.rest-assured + rest-assured + 3.1.0 + + + com.reprezen.kaizen + openapi-parser + 4.0.4 + + + com.google.inject + guice + 4.0 + junit junit @@ -73,6 +88,18 @@ org.mockito mockito-core + + net.jadler + jadler-core + 1.3.0 + test + + + net.jadler + jadler-jetty + 1.3.0 + test + diff --git a/src/main/java/org/opengis/cite/ogcapiprocesses10/CommonFixture.java b/src/main/java/org/opengis/cite/ogcapiprocesses10/CommonFixture.java index d3dcf37..74cc415 100644 --- a/src/main/java/org/opengis/cite/ogcapiprocesses10/CommonFixture.java +++ b/src/main/java/org/opengis/cite/ogcapiprocesses10/CommonFixture.java @@ -3,10 +3,20 @@ import com.sun.jersey.api.client.Client; import com.sun.jersey.api.client.ClientRequest; import com.sun.jersey.api.client.ClientResponse; + +import io.restassured.filter.log.RequestLoggingFilter; +import io.restassured.filter.log.ResponseLoggingFilter; +import io.restassured.specification.RequestSpecification; + +import static io.restassured.RestAssured.given; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; import java.net.URI; import java.util.Map; import javax.ws.rs.core.MediaType; import org.opengis.cite.ogcapiprocesses10.util.ClientUtils; +import org.opengis.cite.ogcapiprocesses10.SuiteAttribute; import org.testng.ITestContext; import org.testng.SkipException; import org.testng.annotations.BeforeClass; @@ -19,6 +29,16 @@ */ public class CommonFixture { + + protected RequestLoggingFilter requestLoggingFilter; + + protected ResponseLoggingFilter responseLoggingFilter; + + private ByteArrayOutputStream requestOutputStream = new ByteArrayOutputStream(); + + private ByteArrayOutputStream responseOutputStream = new ByteArrayOutputStream(); + + /** * Root test suite package (absolute path). */ @@ -35,6 +55,9 @@ public class CommonFixture { * An HTTP response message. */ protected ClientResponse response; + + + protected URI rootUri; /** * Initializes the common test fixture with a client component for @@ -45,16 +68,15 @@ public class CommonFixture { */ @BeforeClass public void initCommonFixture(ITestContext testContext) { - Object obj = testContext.getSuite().getAttribute(SuiteAttribute.CLIENT.getName()); - if (null != obj) { - this.client = Client.class.cast(obj); - } - obj = testContext.getSuite().getAttribute(SuiteAttribute.TEST_SUBJECT.getName()); - if (null == obj) { - throw new SkipException("Test subject not found in ITestContext."); - } + initLogging(); + rootUri = (URI) testContext.getSuite().getAttribute( SuiteAttribute.IUT.getName() ); + } + protected RequestSpecification init() { + return given().filters( requestLoggingFilter, responseLoggingFilter ).log().all(); + } + @BeforeMethod public void clearMessages() { this.request = null; @@ -95,5 +117,25 @@ public ClientRequest buildGetRequest(URI endpoint, Map qryParams, MediaType... mediaTypes) { return ClientUtils.buildGetRequest(endpoint, qryParams, mediaTypes); } + + /** + * Builds an HTTP request message that uses the GET method. This convenience method wraps a static method call to + * facilitate unit testing (Mockito workaround). + * + * @return A ClientRequest object. + * + * @see ClientUtils#buildGetRequest public ClientRequest buildGetRequest( URI endpoint, Map + * qryParams, MediaType... mediaTypes ) { return ClientUtils.buildGetRequest( endpoint, qryParams, mediaTypes + * ); } + */ + + private void initLogging() { + this.requestOutputStream = new ByteArrayOutputStream(); + this.responseOutputStream = new ByteArrayOutputStream(); + PrintStream requestPrintStream = new PrintStream( requestOutputStream, true ); + PrintStream responsePrintStream = new PrintStream( responseOutputStream, true ); + requestLoggingFilter = new RequestLoggingFilter( requestPrintStream ); + responseLoggingFilter = new ResponseLoggingFilter( responsePrintStream ); + } } diff --git a/src/main/java/org/opengis/cite/ogcapiprocesses10/ETSAssert.java b/src/main/java/org/opengis/cite/ogcapiprocesses10/ETSAssert.java index fbd2c4d..b4e4587 100644 --- a/src/main/java/org/opengis/cite/ogcapiprocesses10/ETSAssert.java +++ b/src/main/java/org/opengis/cite/ogcapiprocesses10/ETSAssert.java @@ -170,41 +170,24 @@ public static void assertDescendantElementCount(Document xmlEntity, QName elemen } /** - * Asserts that the given response message contains an OGC exception report. - * The message body must contain an XML document that has a document element - * with the following properties: - * - *
    - *
  • [local name] = "ExceptionReport"
  • - *
  • [namespace name] = "http://www.opengis.net/ows/2.0"
  • - *
- * - * @param rsp - * A ClientResponse object representing an HTTP response message. - * @param exceptionCode - * The expected OGC exception code. - * @param locator - * A case-insensitive string value expected to occur in the - * locator attribute (e.g. a parameter name); the attribute value - * will be ignored if the argument is null or empty. + * @param valueToAssert + * the boolean to assert to be true + * @param failureMsg + * the message to throw in case of a failure, should not be null */ - public static void assertExceptionReport(ClientResponse rsp, String exceptionCode, String locator) { - Assert.assertEquals(rsp.getStatus(), ClientResponse.Status.BAD_REQUEST.getStatusCode(), - ErrorMessage.get(ErrorMessageKeys.UNEXPECTED_STATUS)); - Document doc = rsp.getEntity(Document.class); - String expr = String.format("//ows:Exception[@exceptionCode = '%s']", exceptionCode); - NodeList nodeList = null; - try { - nodeList = XMLUtils.evaluateXPath(doc, expr, null); - } catch (XPathExpressionException xpe) { - // won't happen - } - Assert.assertTrue(nodeList.getLength() > 0, "Exception not found in response: " + expr); - if (null != locator && !locator.isEmpty()) { - Element exception = (Element) nodeList.item(0); - String locatorValue = exception.getAttribute("locator").toLowerCase(); - Assert.assertTrue(locatorValue.contains(locator.toLowerCase()), - String.format("Expected locator attribute to contain '%s']", locator)); - } + public static void assertTrue( boolean valueToAssert, String failureMsg ) { + if ( !valueToAssert ) + throw new AssertionError( failureMsg ); + } + + /** + * @param valueToAssert + * the boolean to assert to be false + * @param failureMsg + * the message to throw in case of a failure, should not be null + */ + public static void assertFalse( boolean valueToAssert, String failureMsg ) { + if ( valueToAssert ) + throw new AssertionError( failureMsg ); } } diff --git a/src/main/java/org/opengis/cite/ogcapiprocesses10/OgcApiProcesses10.java b/src/main/java/org/opengis/cite/ogcapiprocesses10/OgcApiProcesses10.java new file mode 100644 index 0000000..2cbc8c9 --- /dev/null +++ b/src/main/java/org/opengis/cite/ogcapiprocesses10/OgcApiProcesses10.java @@ -0,0 +1,17 @@ +package org.opengis.cite.ogcapiprocesses10; + +/** + * Contains various constants pertaining to WFS 3.0 specification and related standards. + * + * @author Lyn Goltz + */ +public class OgcApiProcesses10 { + + private OgcApiProcesses10() { + } + + public static final String OPEN_API_MIME_TYPE = "application/vnd.oai.openapi+json;version=3.0"; + + public static final String GEOJSON_MIME_TYPE = "application/geo+json"; + +} diff --git a/src/main/java/org/opengis/cite/ogcapiprocesses10/SuiteAttribute.java b/src/main/java/org/opengis/cite/ogcapiprocesses10/SuiteAttribute.java index 02d4c79..5bbc867 100644 --- a/src/main/java/org/opengis/cite/ogcapiprocesses10/SuiteAttribute.java +++ b/src/main/java/org/opengis/cite/ogcapiprocesses10/SuiteAttribute.java @@ -1,8 +1,12 @@ package org.opengis.cite.ogcapiprocesses10; +import com.reprezen.kaizen.oasparser.model3.OpenApi3; import com.sun.jersey.api.client.Client; import java.io.File; +import java.net.URI; +import java.util.List; +import java.util.Map; import org.w3c.dom.Document; @@ -16,19 +20,53 @@ public enum SuiteAttribute { /** * A client component for interacting with HTTP endpoints. */ - CLIENT("httpClient", Client.class), + CLIENT( "httpClient", Client.class ), + + /** + * The root URL. + */ + IUT( "instanceUnderTest", URI.class ), + /** - * A DOM Document that represents the test subject or metadata about it. + * A File containing the test subject or a description of it. */ - TEST_SUBJECT("testSubject", Document.class), + TEST_SUBJ_FILE( "testSubjectFile", File.class ), + /** * A File containing the test subject or a description of it. */ - TEST_SUBJ_FILE("testSubjectFile", File.class); + TEST_SUBJECT( "testSubject", File.class ), + + /** + * The number of collections to test. + */ + NO_OF_COLLECTIONS( "noOfCollections", Integer.class ), + + /** + * Parsed OpenApi3 document resource /api; Added during execution. + */ + API_MODEL( "apiModel", OpenApi3.class ), + + /** + * Requirement classes parsed from /conformance; Added during execution. + */ + REQUIREMENTCLASSES( "requirementclasses", List.class ), + + /** + * Parsed collections from resource /collections; Added during execution. + */ + COLLECTIONS( "collections", List.class ), + + /** + * Collection names assigned to a feature id parsed from resource /collections/{name}/items; Added during execution. + */ + FEATUREIDS( "featureIds", Map.class ); + private final Class attrType; + private final String attrName; - private SuiteAttribute(String attrName, Class attrType) { + SuiteAttribute( String attrName, Class attrType ) { this.attrName = attrName; this.attrType = attrType; } @@ -43,8 +81,8 @@ public String getName() { @Override public String toString() { - StringBuilder sb = new StringBuilder(attrName); - sb.append('(').append(attrType.getName()).append(')'); + StringBuilder sb = new StringBuilder( attrName ); + sb.append( '(' ).append( attrType.getName() ).append( ')' ); return sb.toString(); } } diff --git a/src/main/java/org/opengis/cite/ogcapiprocesses10/SuiteFixtureListener.java b/src/main/java/org/opengis/cite/ogcapiprocesses10/SuiteFixtureListener.java index 6d0eb02..456e250 100644 --- a/src/main/java/org/opengis/cite/ogcapiprocesses10/SuiteFixtureListener.java +++ b/src/main/java/org/opengis/cite/ogcapiprocesses10/SuiteFixtureListener.java @@ -10,6 +10,8 @@ import org.opengis.cite.ogcapiprocesses10.util.TestSuiteLogger; import org.opengis.cite.ogcapiprocesses10.util.URIUtils; import org.opengis.cite.ogcapiprocesses10.util.XMLUtils; +import org.opengis.cite.ogcapiprocesses10.SuiteAttribute; +import org.opengis.cite.ogcapiprocesses10.TestRunArg; import org.testng.ISuite; import org.testng.ISuiteListener; import org.w3c.dom.Document; @@ -32,89 +34,86 @@ public class SuiteFixtureListener implements ISuiteListener { @Override - public void onStart(ISuite suite) { - processSuiteParameters(suite); - registerClientComponent(suite); + public void onStart( ISuite suite ) { + processSuiteParameters( suite ); + registerClientComponent( suite ); } @Override - public void onFinish(ISuite suite) { - if (null != System.getProperty("deleteSubjectOnFinish")) { - deleteTempFiles(suite); - System.getProperties().remove("deleteSubjectOnFinish"); + public void onFinish( ISuite suite ) { + if ( null != System.getProperty( "deleteSubjectOnFinish" ) ) { + deleteTempFiles( suite ); + System.getProperties().remove( "deleteSubjectOnFinish" ); } } /** - * Processes test suite arguments and sets suite attributes accordingly. The - * entity referenced by the {@link TestRunArg#IUT iut} argument is retrieved - * and written to a File that is set as the value of the suite attribute - * {@link SuiteAttribute#TEST_SUBJ_FILE testSubjectFile}. + * Processes test suite arguments and sets suite attributes accordingly. The entity referenced by the + * {@link TestRunArg#IUT iut} argument is retrieved and written to a File that is set as the value of the suite + * attribute {@link SuiteAttribute#TEST_SUBJ_FILE testSubjectFile}. * * @param suite * An ISuite object representing a TestNG test suite. */ - void processSuiteParameters(ISuite suite) { + void processSuiteParameters( ISuite suite ) { Map params = suite.getXmlSuite().getParameters(); - TestSuiteLogger.log(Level.CONFIG, "Suite parameters\n" + params.toString()); - String iutParam = params.get(TestRunArg.IUT.toString()); - if ((null == iutParam) || iutParam.isEmpty()) { - throw new IllegalArgumentException("Required test run parameter not found: " + TestRunArg.IUT.toString()); + TestSuiteLogger.log( Level.CONFIG, "Suite parameters\n" + params.toString() ); + String iutParam = params.get( TestRunArg.IUT.toString() ); + if ( ( null == iutParam ) || iutParam.isEmpty() ) { + throw new IllegalArgumentException( "Required test run parameter not found: " + TestRunArg.IUT.toString() ); } - URI iutRef = URI.create(iutParam.trim()); + URI iutRef = URI.create( iutParam.trim() ); + suite.setAttribute( SuiteAttribute.IUT.getName(), iutRef ); File entityFile = null; try { - entityFile = URIUtils.dereferenceURI(iutRef); - } catch (IOException iox) { - throw new RuntimeException("Failed to dereference resource located at " + iutRef, iox); + entityFile = URIUtils.dereferenceURI( iutRef ); + } catch ( IOException iox ) { + throw new RuntimeException( "Failed to dereference resource located at " + iutRef, iox ); } - TestSuiteLogger.log(Level.FINE, String.format("Wrote test subject to file: %s (%d bytes)", - entityFile.getAbsolutePath(), entityFile.length())); - suite.setAttribute(SuiteAttribute.TEST_SUBJ_FILE.getName(), entityFile); - Document iutDoc = null; + TestSuiteLogger.log( Level.FINE, String.format( "Wrote test subject to file: %s (%d bytes)", + entityFile.getAbsolutePath(), entityFile.length() ) ); + suite.setAttribute( SuiteAttribute.TEST_SUBJ_FILE.getName(), entityFile ); + + String noOfCollections = params.get( TestRunArg.NOOFCOLLECTIONS.toString() ); try { - iutDoc = URIUtils.parseURI(entityFile.toURI()); - } catch (Exception x) { - throw new RuntimeException("Failed to parse resource retrieved from " + iutRef, x); - } - suite.setAttribute(SuiteAttribute.TEST_SUBJECT.getName(), iutDoc); - if (TestSuiteLogger.isLoggable(Level.FINE)) { - StringBuilder logMsg = new StringBuilder("Parsed resource retrieved from "); - logMsg.append(iutRef).append("\n"); - logMsg.append(XMLUtils.writeNodeToString(iutDoc)); - TestSuiteLogger.log(Level.FINE, logMsg.toString()); + if ( noOfCollections != null ) { + int noOfCollectionsInt = Integer.parseInt( noOfCollections ); + suite.setAttribute( SuiteAttribute.NO_OF_COLLECTIONS.getName(), noOfCollectionsInt ); + } + } catch ( NumberFormatException e ) { + TestSuiteLogger.log( Level.WARNING, + String.format( "Could not parse parameter %s: %s. Expected is a valid integer", + TestRunArg.NOOFCOLLECTIONS.toString(), noOfCollections ) ); } } /** - * A client component is added to the suite fixture as the value of the - * {@link SuiteAttribute#CLIENT} attribute; it may be subsequently accessed - * via the {@link org.testng.ITestContext#getSuite()} method. + * A client component is added to the suite fixture as the value of the {@link SuiteAttribute#CLIENT} attribute; it + * may be subsequently accessed via the {@link org.testng.ITestContext#getSuite()} method. * * @param suite * The test suite instance. */ - void registerClientComponent(ISuite suite) { + void registerClientComponent( ISuite suite ) { Client client = ClientUtils.buildClient(); - if (null != client) { - suite.setAttribute(SuiteAttribute.CLIENT.getName(), client); + if ( null != client ) { + suite.setAttribute( SuiteAttribute.CLIENT.getName(), client ); } } /** - * Deletes temporary files created during the test run if TestSuiteLogger is - * enabled at the INFO level or higher (they are left intact at the CONFIG - * level or lower). + * Deletes temporary files created during the test run if TestSuiteLogger is enabled at the INFO level or higher + * (they are left intact at the CONFIG level or lower). * * @param suite * The test suite. */ - void deleteTempFiles(ISuite suite) { - if (TestSuiteLogger.isLoggable(Level.CONFIG)) { + void deleteTempFiles( ISuite suite ) { + if ( TestSuiteLogger.isLoggable( Level.CONFIG ) ) { return; } - File testSubjFile = (File) suite.getAttribute(SuiteAttribute.TEST_SUBJ_FILE.getName()); - if (testSubjFile.exists()) { + File testSubjFile = (File) suite.getAttribute( SuiteAttribute.TEST_SUBJ_FILE.getName() ); + if ( testSubjFile.exists() ) { testSubjFile.delete(); } } diff --git a/src/main/java/org/opengis/cite/ogcapiprocesses10/TestRunArg.java b/src/main/java/org/opengis/cite/ogcapiprocesses10/TestRunArg.java index 200312f..062ef06 100644 --- a/src/main/java/org/opengis/cite/ogcapiprocesses10/TestRunArg.java +++ b/src/main/java/org/opengis/cite/ogcapiprocesses10/TestRunArg.java @@ -5,11 +5,15 @@ */ public enum TestRunArg { + /** + * An absolute URI that refers to a representation of the test subject or metadata about it. + */ + IUT, + /** - * An absolute URI that refers to a representation of the test subject or - * metadata about it. + * The number of collections to test (a value less or equal to 0 means all collections). */ - IUT; + NOOFCOLLECTIONS; @Override public String toString() { diff --git a/src/main/java/org/opengis/cite/ogcapiprocesses10/conformance/Conformance.java b/src/main/java/org/opengis/cite/ogcapiprocesses10/conformance/Conformance.java new file mode 100644 index 0000000..6ffa2d4 --- /dev/null +++ b/src/main/java/org/opengis/cite/ogcapiprocesses10/conformance/Conformance.java @@ -0,0 +1,121 @@ +package org.opengis.cite.ogcapiprocesses10.conformance; + +import static io.restassured.http.ContentType.JSON; +import static io.restassured.http.Method.GET; +import static org.opengis.cite.ogcapiprocesses10.conformance.RequirementClass.CORE; +import static org.opengis.cite.ogcapiprocesses10.openapi3.OpenApiUtils.retrieveTestPointsForConformance; +import static org.opengis.cite.ogcapiprocesses10.SuiteAttribute.API_MODEL; +import static org.opengis.cite.ogcapiprocesses10.SuiteAttribute.IUT; +import static org.opengis.cite.ogcapiprocesses10.SuiteAttribute.REQUIREMENTCLASSES; +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertTrue; + +import java.net.URI; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.opengis.cite.ogcapiprocesses10.openapi3.TestPoint; +import org.opengis.cite.ogcapiprocesses10.openapi3.UriBuilder; +import org.opengis.cite.ogcapiprocesses10.CommonFixture; +import org.testng.ITestContext; +import org.testng.annotations.AfterClass; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +import com.reprezen.kaizen.oasparser.model3.MediaType; +import com.reprezen.kaizen.oasparser.model3.OpenApi3; + +import io.restassured.path.json.JsonPath; +import io.restassured.response.Response; + +/** + * + * A.?.?. Conformance Path {root}/conformance + * + * + * @author Lyn Goltz + */ +public class Conformance extends CommonFixture { + + private List requirementClasses; + + @DataProvider(name = "conformanceUris") + public Object[][] conformanceUris( ITestContext testContext ) { + OpenApi3 apiModel = (OpenApi3) testContext.getSuite().getAttribute( API_MODEL.getName() ); + URI iut = (URI) testContext.getSuite().getAttribute( IUT.getName() ); + + TestPoint tp = new TestPoint(rootUri.toString(),"/conformance",null); + + + List testPoints = new ArrayList(); + testPoints.add(tp); + Object[][] testPointsData = new Object[1][]; + int i = 0; + for ( TestPoint testPoint : testPoints ) { + testPointsData[i++] = new Object[] { testPoint }; + } + return testPointsData; + } + + @AfterClass + public void storeRequirementClassesInTestContext( ITestContext testContext ) { + testContext.getSuite().setAttribute( REQUIREMENTCLASSES.getName(), this.requirementClasses ); + } + + /** + * Partly addresses Requirement TBA + * + * @param testPoint + * the test point to test, never null + */ + @Test(description = "Implements A.?.?. Conformance Path {root}/conformance,", groups = "conformance", dataProvider = "conformanceUris") + public void validateConformanceOperationAndResponse( TestPoint testPoint ) { + String testPointUri = new UriBuilder( testPoint ).buildUrl(); + Response response = init().baseUri( testPointUri ).accept( JSON ).when().request( GET ); + validateConformanceOperationResponse( testPointUri, response ); + } + + /** + * Requirement TBA + * + * Abstract Test ?: /ats/core/conformance-success + */ + private void validateConformanceOperationResponse( String testPointUri, Response response ) { + response.then().statusCode( 200 ); + + JsonPath jsonPath = response.jsonPath(); + this.requirementClasses = parseAndValidateRequirementClasses( jsonPath ); + assertTrue( this.requirementClasses.contains( CORE ), + "Conformance class \"http://www.opengis.net/spec/ogcapi-processes-1/1.0/conf/core\" is not available from path " + + testPointUri ); + } + + /** + * @param jsonPath + * never null + * @return the parsed requirement classes, never null + * @throws AssertionError + * if the json does not follow the expected structure + */ + List parseAndValidateRequirementClasses( JsonPath jsonPath ) { + List conformsTo = jsonPath.getList( "conformsTo" ); + assertNotNull( conformsTo, "Missing member 'conformsTo'." ); + + List requirementClasses = new ArrayList<>(); + for ( Object conformTo : conformsTo ) { + System.out.println("conformsTo "+conformsTo); + if ( conformTo instanceof String ) { + String conformanceClass = (String) conformTo; + RequirementClass requirementClass = RequirementClass.byConformanceClass( conformanceClass ); + if ( requirementClass != null ) + requirementClasses.add( requirementClass ); + } else + throw new AssertionError( "At least one element array 'conformsTo' is not a string value (" + conformTo + + ")" ); + } + return requirementClasses; + } + +} \ No newline at end of file diff --git a/src/main/java/org/opengis/cite/ogcapiprocesses10/conformance/RequirementClass.java b/src/main/java/org/opengis/cite/ogcapiprocesses10/conformance/RequirementClass.java new file mode 100644 index 0000000..896d22a --- /dev/null +++ b/src/main/java/org/opengis/cite/ogcapiprocesses10/conformance/RequirementClass.java @@ -0,0 +1,75 @@ +package org.opengis.cite.ogcapiprocesses10.conformance; + +/** + * Updated at the OGC API - Tiles Sprint 2020 by ghobona + * + * Encapsulates all known requirement classes. + * + * @author Lyn Goltz + */ +public enum RequirementClass { + + CORE( "http://www.opengis.net/spec/ogcapi-processes-1/1.0/conf/core" ); + + + + private final String conformanceClass; + + private final String mediaTypeFeaturesAndCollections; + + private final String mediaTypeOtherResources; + + RequirementClass( String conformanceClass ) { + this( conformanceClass, null, null ); + } + + RequirementClass( String conformanceClass, String mediaTypeFeaturesAndCollections, String mediaTypeOtherResources ) { + this.conformanceClass = conformanceClass; + this.mediaTypeFeaturesAndCollections = mediaTypeFeaturesAndCollections; + this.mediaTypeOtherResources = mediaTypeOtherResources; + } + + /** + * @return true if the RequirementClass has a media type for features and collections, + * true otherwise + */ + public boolean hasMediaTypeForFeaturesAndCollections() { + return mediaTypeFeaturesAndCollections != null; + } + + /** + * @return media type for features and collections, null if not available + */ + public String getMediaTypeFeaturesAndCollections() { + return mediaTypeFeaturesAndCollections; + } + + /** + * @return true if the RequirementClass has a media type for other resources, + * true otherwise + */ + public boolean hasMediaTypeForOtherResources() { + return mediaTypeOtherResources != null; + } + + /** + * @return media type of other resources, null if not available + */ + public String getMediaTypeOtherResources() { + return mediaTypeOtherResources; + } + + /** + * @param conformanceClass + * the conformance class of the RequirementClass to return. + * @return the RequirementClass with the passed conformance class, null if RequirementClass exists + */ + public static RequirementClass byConformanceClass( String conformanceClass ) { + for ( RequirementClass requirementClass : values() ) { + if ( requirementClass.conformanceClass.equals( conformanceClass ) ) + return requirementClass; + } + return null; + } + +} diff --git a/src/main/java/org/opengis/cite/ogcapiprocesses10/landingpage/LandingPage.java b/src/main/java/org/opengis/cite/ogcapiprocesses10/landingpage/LandingPage.java new file mode 100644 index 0000000..8ceea18 --- /dev/null +++ b/src/main/java/org/opengis/cite/ogcapiprocesses10/landingpage/LandingPage.java @@ -0,0 +1,92 @@ +package org.opengis.cite.ogcapiprocesses10.landingpage; + +import static io.restassured.http.ContentType.JSON; +import static io.restassured.http.Method.GET; +import static org.opengis.cite.ogcapiprocesses10.ETSAssert.assertTrue; + +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.opengis.cite.ogcapiprocesses10.CommonFixture; +import org.testng.annotations.Test; + +import io.restassured.path.json.JsonPath; +import io.restassured.response.Response; +import io.restassured.specification.RequestSpecification; + +/** + * + * A.2.2. Landing Page {root}/ + * + * @author Lyn Goltz + */ +public class LandingPage extends CommonFixture { + + private JsonPath response; + + + /** + *
+     * Abstract Test 3: /ats/core/root-op
+     * Test Purpose: Validate that a landing page can be retrieved from the expected location.
+     * Requirement: /req/core/root-op
+     *
+     * Test Method:
+     *  1. Issue an HTTP GET request to the URL {root}/
+     *  2. Validate that a document was returned with a status code 200
+     *  3. Validate the contents of the returned document using test /ats/core/root-success.
+     * 
+ */ + @Test(description = "Implements Requirement TBA", groups = "landingpage") + public void landingPageRetrieval() { + + Response request = null; + try { + + RequestSpecification s = init(); + + request = init().baseUri( rootUri.toString() ).accept( JSON ).when().request( GET, "/" ); + } + catch(Exception er) + { + System.out.println("Message "+er.getMessage()); + } + + request.then().statusCode( 200 ); + response = request.jsonPath(); + + } + + /** + *
+     * Requirement TBA
+     * 
+ */ + @Test(description = "Implements Requirement TBA", groups = "landingpage") + public void landingPageValidation() { + + List links = response.getList( "links" ); + + Set linkTypes = collectLinkTypes( links ); + + boolean expectedLinkTypesExists = linkTypes.contains( "processes" ); + assertTrue( expectedLinkTypesExists, + "The landing page must include at least links with relation type 'processes', but contains " + + String.join( ", ", linkTypes ) ); + + + } + + private Set collectLinkTypes( List links ) { + Set linkTypes = new HashSet<>(); + for ( Object link : links ) { + Map linkMap = (Map) link; + Object linkType = linkMap.get( "rel" ); + linkTypes.add( (String) linkType ); + } + return linkTypes; + } + +} \ No newline at end of file diff --git a/src/main/java/org/opengis/cite/ogcapiprocesses10/level1/Capability1Tests.java b/src/main/java/org/opengis/cite/ogcapiprocesses10/level1/Capability1Tests.java deleted file mode 100644 index 9acc478..0000000 --- a/src/main/java/org/opengis/cite/ogcapiprocesses10/level1/Capability1Tests.java +++ /dev/null @@ -1,95 +0,0 @@ -package org.opengis.cite.ogcapiprocesses10.level1; - -import java.io.IOException; -import java.net.URL; -import javax.xml.transform.Source; -import javax.xml.transform.dom.DOMSource; -import org.opengis.cite.ogcapiprocesses10.CommonFixture; -import org.opengis.cite.ogcapiprocesses10.ErrorMessage; -import org.opengis.cite.ogcapiprocesses10.ErrorMessageKeys; -import org.opengis.cite.ogcapiprocesses10.SuiteAttribute; -import org.opengis.cite.validation.RelaxNGValidator; -import org.opengis.cite.validation.ValidationErrorHandler; -import org.testng.Assert; -import org.testng.ITestContext; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.Test; -import org.w3c.dom.Document; -import org.xml.sax.SAXException; - -/** - * Includes various tests of capability 1. - */ -public class Capability1Tests extends CommonFixture { - - private Document testSubject; - - /** - * Obtains the test subject from the ISuite context. The suite attribute - * {@link org.opengis.cite.ogcapiprocesses10.SuiteAttribute#TEST_SUBJECT} should - * evaluate to a DOM Document node. - * - * @param testContext - * The test (group) context. - */ - @BeforeClass - public void obtainTestSubject(ITestContext testContext) { - Object obj = testContext.getSuite().getAttribute( - SuiteAttribute.TEST_SUBJECT.getName()); - if ((null != obj) && Document.class.isAssignableFrom(obj.getClass())) { - this.testSubject = Document.class.cast(obj); - } - } - - /** - * Sets the test subject. This method is intended to facilitate unit - * testing. - * - * @param testSubject A Document node representing the test subject or - * metadata about it. - */ - public void setTestSubject(Document testSubject) { - this.testSubject = testSubject; - } - - /** - * Verifies the string is empty. - */ - @Test(description = "Implements ATC 1-1") - public void isEmpty() { - String str = " foo "; - Assert.assertTrue(str.isEmpty(), - ErrorMessage.get(ErrorMessageKeys.EMPTY_STRING)); - } - - /** - * Checks the behavior of the trim function. - */ - @Test(description = "Implements ATC 1-2") - public void trim() { - String str = " foo "; - Assert.assertTrue("foo".equals(str.trim())); - } - - /** - * Verify the test subject is a valid Atom feed. - * - * @throws SAXException - * If the resource cannot be parsed. - * @throws IOException - * If the resource is not accessible. - */ - @Test(description = "Implements ATC 1-3") - public void docIsValidAtomFeed() throws SAXException, IOException { - URL schemaRef = getClass().getResource( - "/org/opengis/cite/ogcapiprocesses10/rnc/atom.rnc"); - RelaxNGValidator rngValidator = new RelaxNGValidator(schemaRef); - Source xmlSource = (null != testSubject) - ? new DOMSource(testSubject) : null; - rngValidator.validate(xmlSource); - ValidationErrorHandler err = rngValidator.getErrorHandler(); - Assert.assertFalse(err.errorsDetected(), - ErrorMessage.format(ErrorMessageKeys.NOT_SCHEMA_VALID, - err.getErrorCount(), err.toString())); - } -} diff --git a/src/main/java/org/opengis/cite/ogcapiprocesses10/level1/package-info.java b/src/main/java/org/opengis/cite/ogcapiprocesses10/level1/package-info.java deleted file mode 100644 index d75c227..0000000 --- a/src/main/java/org/opengis/cite/ogcapiprocesses10/level1/package-info.java +++ /dev/null @@ -1,14 +0,0 @@ -/** - * Conformance Level 1 includes basic facilities. The following capabilities - * must be supported: - *
    - *
  • Capability 1.1
  • - *
  • Capability 1.2
  • - *
  • Capability 1.3
  • - *
- * - * @see HTML5 - * - Conformance classes - */ -package org.opengis.cite.ogcapiprocesses10.level1; diff --git a/src/main/java/org/opengis/cite/ogcapiprocesses10/openapi3/OpenApiUtils.java b/src/main/java/org/opengis/cite/ogcapiprocesses10/openapi3/OpenApiUtils.java new file mode 100644 index 0000000..ea9cdc8 --- /dev/null +++ b/src/main/java/org/opengis/cite/ogcapiprocesses10/openapi3/OpenApiUtils.java @@ -0,0 +1,599 @@ +package org.opengis.cite.ogcapiprocesses10.openapi3; + +import static org.opengis.cite.ogcapiprocesses10.openapi3.OpenApiUtils.PATH.COLLECTIONS; +import static org.opengis.cite.ogcapiprocesses10.openapi3.OpenApiUtils.PATH.CONFORMANCE; + +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Predicate; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +import com.reprezen.kaizen.oasparser.model3.MediaType; +import com.reprezen.kaizen.oasparser.model3.OpenApi3; +import com.reprezen.kaizen.oasparser.model3.Operation; +import com.reprezen.kaizen.oasparser.model3.Parameter; +import com.reprezen.kaizen.oasparser.model3.Path; +import com.reprezen.kaizen.oasparser.model3.Response; +import com.reprezen.kaizen.oasparser.model3.Schema; +import com.reprezen.kaizen.oasparser.model3.Server; +import com.sun.jersey.api.uri.UriTemplate; +import com.sun.jersey.api.uri.UriTemplateParser; + +/** + * @author Lyn Goltz + */ +public class OpenApiUtils { + + // as described in https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#fixed-fields + private static final String DEFAULT_SERVER_URL = "/"; + + @FunctionalInterface + private interface PathMatcherFunction { + A apply( B b, C c ); + } + + enum PATH { + + CONFORMANCE( "conformance" ), COLLECTIONS( "collections" ); + + private String pathItem; + + PATH( String pathItem ) { + + this.pathItem = pathItem; + } + + private String getPathItem() { + return pathItem; + } + } + + private static class PathMatcher implements PathMatcherFunction { + @Override + public Boolean apply( String pathUnderTest, String pathToMatch ) { + UriTemplateParser parser = new UriTemplateParser( pathUnderTest ); + Matcher matcher = parser.getPattern().matcher( pathToMatch ); + return matcher.matches(); + } + } + + private static class ExactMatchFilter implements Predicate { + + private final String requestedPath; + + ExactMatchFilter( String requestedPath ) { + this.requestedPath = requestedPath; + } + + @Override + public boolean test( TestPoint testPoint ) { + UriTemplate uriTemplate = new UriTemplate( testPoint.getPath() ); + Map templateReplacement = new HashMap<>( testPoint.getPredefinedTemplateReplacement() ); + List templateVariables = uriTemplate.getTemplateVariables(); + for ( String templateVariable : templateVariables ) { + if ( !templateReplacement.containsKey( templateVariable ) ) + templateReplacement.put( templateVariable, ".*" ); + } + String uri = uriTemplate.createURI( templateReplacement ); + Pattern pattern = Pattern.compile( uri ); + return pattern.matcher( requestedPath ).matches(); + } + } + + private OpenApiUtils() { + } + + /** + * Parse all test points from the passed OpenApi3 document as described in A.4.3. Identify the Test Points. + * + * @param apiModel + * never null + * @param iut + * the url of the instance under test, never null + * @return the parsed test points, may be empty but never null + */ + static List retrieveTestPoints( OpenApi3 apiModel, URI iut ) { + List pathItemObjects = identifyTestPoints( apiModel ); + List pathItemAndServers = identifyServerUrls( apiModel, iut, pathItemObjects ); + return processServerObjects( pathItemAndServers, true ); + } + + /** + * Parse the CONFORMANCE test points from the passed OpenApi3 document as described in A.4.3. Identify the Test + * Points. + * + * @param apiModel + * never null + * @param iut + * the url of the instance under test, never null + * @return the parsed test points, may be empty but never null + */ + public static List retrieveTestPointsForConformance( OpenApi3 apiModel, URI iut ) { + return retrieveTestPoints( apiModel, iut, CONFORMANCE, false ); + } + + /** + * Parse the COLLECTIONS METADATA test points from the passed OpenApi3 document as described in A.4.3. Identify the + * Test Points. + * + * @param apiModel + * never null + * @param iut + * the url of the instance under test, never null + * @return the parsed test points, may be empty but never null + */ + public static List retrieveTestPointsForCollectionsMetadata( OpenApi3 apiModel, URI iut ) { + return retrieveTestPoints( apiModel, iut, COLLECTIONS, false ); + } + + /** + * Parse the COLLECTION METADATA test points for the passed collectionName including the extended path from the + * passed OpenApi3 document as described in A.4.3. Identify the Test Points. + * + * @param apiModel + * never null + * @param iut + * the url of the instance under test, never null + * @param collectionName + * the extended path, may be null + * @return the parsed test points, may be empty but never null + */ + public static List retrieveTestPointsForCollectionMetadata( OpenApi3 apiModel, URI iut, String collectionName ) { + StringBuilder requestedPath = new StringBuilder(); + requestedPath.append( "/" ); + requestedPath.append( COLLECTIONS.getPathItem() ); + requestedPath.append( "/" ); + requestedPath.append( collectionName ); + + List testPoints = retrieveTestPoints( apiModel, iut, requestedPath.toString(), true ); + return testPoints.stream().filter( new ExactMatchFilter( requestedPath.toString() ) ).collect( Collectors.toList() ); + } + + /** + * Parse the COLLECTIONS test points from the passed OpenApi3 document as described in A.4.3. Identify the Test + * Points. + * + * @param apiModel + * never null + * @param iut + * the url of the instance under test, never null + * @param noOfCollection + * the number of collections to return test points for (-1 means the test points of all collections + * should be returned) + * @return the parsed test points, may be empty but never null + */ + public static List retrieveTestPointsForCollections( OpenApi3 apiModel, URI iut, int noOfCollection ) { + StringBuilder requestedPath = new StringBuilder(); + requestedPath.append( "/" ); + requestedPath.append( COLLECTIONS.getPathItem() ); + requestedPath.append( "/.*/items" ); + + List allTestPoints = retrieveTestPoints( apiModel, iut, requestedPath.toString(), + ( a, b ) -> a.matches( b ), true ); + if ( noOfCollection < 0 || allTestPoints.size() <= noOfCollection ) { + return allTestPoints; + } + return allTestPoints.subList( 0, noOfCollection ); + } + + /** + * Parse the test points with the passed path including the extended path from the passed OpenApi3 document as + * described in A.4.3. Identify the Test Points. + * + * @param apiModel + * never null + * @param iut + * the url of the instance under test, never null + * @param collectionName + * the extended path, may be null + * @return the parsed test points, may be empty but never null + */ + public static List retrieveTestPointsForCollection( OpenApi3 apiModel, URI iut, String collectionName ) { + String requestedPath = createCollectionPath( collectionName ); + + List testPoints = retrieveTestPoints( apiModel, iut, requestedPath, true ); + return testPoints.stream().filter( new ExactMatchFilter( requestedPath ) ).collect( Collectors.toList() ); + } + + /** + * Parse the test points with the passed path including the extended path from the passed OpenApi3 document as + * described in A.4.3. Identify the Test Points. + * + * @param apiModel + * never null + * @param iut + * the url of the instance under test, never null + * @param collectionName + * the extended path, may be null + * @param featureId + * the id of the feature, never null + * @return the parsed test points, may be empty but never null + */ + public static List retrieveTestPointsForFeature( OpenApi3 apiModel, URI iut, String collectionName, + String featureId ) { + StringBuilder requestedPath = new StringBuilder(); + requestedPath.append( "/" ); + requestedPath.append( COLLECTIONS.getPathItem() ); + requestedPath.append( "/" ); + requestedPath.append( collectionName ); + requestedPath.append( "/items/" ); + requestedPath.append( featureId ); + + List testPoints = retrieveTestPoints( apiModel, iut, requestedPath.toString(), true ); + return testPoints.stream().filter( new ExactMatchFilter( requestedPath.toString() ) ).collect( Collectors.toList() ); + } + + public static Parameter retrieveParameterByName( String collectionItemPath, OpenApi3 apiModel, String name ) { + Path path = apiModel.getPath( collectionItemPath ); + if ( path != null ) { + for ( Parameter parameter : path.getParameters() ) + if ( name.equals( parameter.getName() ) ) + return parameter; + Operation get = path.getOperation( "get" ); + for ( Parameter parameter : get.getParameters() ) + if ( name.equals( parameter.getName() ) ) + return parameter; + } + return null; + } + + public static boolean isFreeFormParameterSupportedForCollection( OpenApi3 apiModel, String collectionName ) { + String requestedPath = createCollectionPath( collectionName ); + + List paths = identifyTestPoints( apiModel, requestedPath, new PathMatcher() ); + for ( Path path : paths ) { + Collection parameters = path.getGet().getParameters(); + for ( Parameter parameter : parameters ) { + if ( parameter.getSchema() != null && parameter.getSchema().isAdditionalProperties() ) { + return true; + } + } + } + return false; + } + + public static boolean isParameterSupportedForCollection( OpenApi3 apiModel, String collectionName, + String queryParam ) { + String requestedPath = createCollectionPath( collectionName ); + + List paths = identifyTestPoints( apiModel, requestedPath, new PathMatcher() ); + for ( Path path : paths ) { + Collection parameters = path.getGet().getParameters(); + for ( Parameter parameter : parameters ) { + if ( queryParam.equalsIgnoreCase( parameter.getName() ) ) { + return true; + } + } + } + return false; + } + + private static String createCollectionPath( String collectionName ) { + StringBuilder requestedPath = new StringBuilder(); + requestedPath.append( "/" ); + requestedPath.append( COLLECTIONS.getPathItem() ); + requestedPath.append( "/" ); + requestedPath.append( collectionName ); + requestedPath.append( "/items" ); + return requestedPath.toString(); + } + + private static List retrieveTestPoints( OpenApi3 apiModel, URI iut, PATH path, boolean allowEmptyTemplateReplacements ) { + String requestedPath = "/" + path.getPathItem(); + return retrieveTestPoints( apiModel, iut, requestedPath, allowEmptyTemplateReplacements ); + } + + private static List retrieveTestPoints( OpenApi3 apiModel, URI iut, String requestedPath, boolean allowEmptyTemplateReplacements ) { + return retrieveTestPoints( apiModel, iut, requestedPath, new PathMatcher(), allowEmptyTemplateReplacements ); + } + + private static List retrieveTestPoints( OpenApi3 apiModel, URI iut, String requestedPath, + PathMatcherFunction pathMatcher, boolean allowEmptyTemplateReplacements ) { + List pathItemObjects = identifyTestPoints( apiModel, requestedPath, pathMatcher ); + List pathItemAndServers = identifyServerUrls( apiModel, iut, pathItemObjects ); + return processServerObjects( pathItemAndServers, allowEmptyTemplateReplacements ); + } + + /** + * A.4.3.1. Identify Test Points: + * + * a) Purpose: To identify the test points associated with each Path in the OpenAPI document + * + * b) Pre-conditions: + * + * An OpenAPI document has been obtained + * + * A list of URLs for the servers to be included in the compliance test has been provided + * + * A list of the paths specified in the WFS 3.0 specification + * + * c) Method: + * + * FOR EACH paths property in the OpenAPI document If the path name is one of those specified in the WFS 3.0 + * specification Retrieve the Server URIs using A.4.3.2. FOR EACH Server URI Concatenate the Server URI with the + * path name to form a test point. Add that test point to the list. + * + * d) References: None + * + * @param apiModel + * never null + */ + private static List identifyTestPoints( OpenApi3 apiModel ) { + List allTestPoints = new ArrayList<>(); + for ( PATH path : PATH.values() ) + allTestPoints.addAll( identifyTestPoints( apiModel, "/" + path.getPathItem(), new PathMatcher() ) ); + return allTestPoints; + } + + private static List identifyTestPoints( OpenApi3 apiModel, String path, + PathMatcherFunction pathMatch ) { + List pathItems = new ArrayList<>(); + Map pathItemObjects = apiModel.getPaths(); + for ( Path pathItemObject : pathItemObjects.values() ) { + String pathString = pathItemObject.getPathString(); + if ( pathMatch.apply( pathString, path ) ) { + pathItems.add( pathItemObject ); + } + } + return pathItems; + } + + /** + * A.4.3.2. Identify Server URIs: + * + * a) Purpose: To identify all server URIs applicable to an OpenAPI Operation Object + * + * b) Pre-conditions: + * + * Server Objects from the root level of the OpenAPI document have been obtained + * + * A Path Item Object has been retrieved + * + * An Operation Object has been retrieved + * + * The Operation Object is associated with the Path Item Object + * + * A list of URLs for the servers to be included in the compliance test has been provided + * + * c) Method: + * + * 1) Identify the Server Objects which are in-scope for this operationObject + * + * IF Server Objects are defined at the Operation level, then those and only those Server Objects apply to that + * Operation. + * + * IF Server Objects are defined at the Path Item level, then those and only those Server Objects apply to that Path + * Item. + * + * IF Server Objects are not defined at the Operation level, then the Server Objects defined for the parent Path + * Item apply to that Operation. + * + * IF Server Objects are not defined at the Path Item level, then the Server Objects defined for the root level + * apply to that Path. + * + * IF no Server Objects are defined at the root level, then the default server object is assumed as described in the + * OpenAPI specification. + * + * 2) Process each Server Object using A.4.3.3. + * + * 3) Delete any Server URI which does not reference a server on the list of servers to test. + * + * d) References: None + * + * @param apiModel + * never null + * @param iut + * never null + * @param pathItemObjects + * never null + */ + private static List identifyServerUrls( OpenApi3 apiModel, URI iut, List pathItemObjects ) { + List pathItemAndServers = new ArrayList<>(); + + for ( Path pathItemObject : pathItemObjects ) { + Map operationObjects = pathItemObject.getOperations(); + for ( Operation operationObject : operationObjects.values() ) { + List serverUrls = identifyServerObjects( apiModel, pathItemObject, operationObject ); + for ( String serverUrl : serverUrls ) { + if ( DEFAULT_SERVER_URL.equalsIgnoreCase( serverUrl ) ) { + serverUrl = iut.toString(); + } else if ( serverUrl.startsWith( "/" ) ) { + URI resolvedUri = iut.resolve( serverUrl ); + serverUrl = resolvedUri.toString(); + } + PathItemAndServer pathItemAndServer = new PathItemAndServer( pathItemObject, operationObject, + serverUrl ); + pathItemAndServers.add( pathItemAndServer ); + } + } + } + return pathItemAndServers; + } + + /** + * A.4.3.3. Process Server Object: + * + * a) Purpose: To expand the contents of a Server Object into a set of absolute URIs. + * + * b) Pre-conditions: A Server Object has been retrieved + * + * c) Method: + * + * Processing the Server Object results in a set of absolute URIs. This set contains all of the URIs that can be + * created given the URI template and variables defined in that Server Object. + * + * If there are no variables in the URI template, then add the URI to the return set. + * + * For each variable in the URI template which does not have an enumerated set of valid values: + * + * generate a URI using the default value, + * + * add this URI to the return set, + * + * flag this URI as non-exhaustive + * + * For each variable in the URI template which has an enumerated set of valid values: + * + * generate a URI for each value in the enumerated set, + * + * add each generated URI to the return set. + * + * Perform this processing in an iterative manner so that there is a unique URI for all possible combinations of + * enumerated and default values. + * + * Convert all relative URIs to absolute URIs by rooting them on the URI to the server hosting the OpenAPI document. + * + * d) References: None + * + * @param pathItemAndServers + * never null + */ + private static List processServerObjects( List pathItemAndServers, boolean allowEmptyTemplateReplacements ) { + List uris = new ArrayList<>(); + for ( PathItemAndServer pathItemAndServer : pathItemAndServers ) { + processServerObject( uris, pathItemAndServer, allowEmptyTemplateReplacements ); + } + return uris; + } + + private static void processServerObject( List uris, PathItemAndServer pathItemAndServer, boolean allowEmptyTemplateReplacements ) { + String pathString = pathItemAndServer.pathItemObject.getPathString(); + Response response = getResponse(pathItemAndServer); + if ( response == null ) + return; + Map contentMediaTypes = response.getContentMediaTypes(); + + UriTemplate uriTemplate = new UriTemplate( pathItemAndServer.serverUrl + pathString ); + if ( uriTemplate.getNumberOfTemplateVariables() == 0 ) { + TestPoint testPoint = new TestPoint( pathItemAndServer.serverUrl, pathString, contentMediaTypes ); + uris.add( testPoint ); + } else { + List> templateReplacements = collectTemplateReplacements( pathItemAndServer, + uriTemplate ); + + if ( templateReplacements.isEmpty() && allowEmptyTemplateReplacements ) { + TestPoint testPoint = new TestPoint( pathItemAndServer.serverUrl, pathString, contentMediaTypes ); + uris.add( testPoint ); + } else { + for ( Map templateReplacement : templateReplacements ) { + TestPoint testPoint = new TestPoint( pathItemAndServer.serverUrl, pathString, templateReplacement, + contentMediaTypes ); + uris.add( testPoint ); + } + } + } + } + + private static Response getResponse( PathItemAndServer pathItemAndServer ) { + if ( pathItemAndServer.operationObject.hasResponse( "200" ) ) + return pathItemAndServer.operationObject.getResponse( "200" ); + if ( pathItemAndServer.operationObject.hasResponse( "default" ) ) + return pathItemAndServer.operationObject.getResponse( "default" ); + return null; + } + + private static List> collectTemplateReplacements( PathItemAndServer pathItemAndServer, + UriTemplate uriTemplate ) { + List> templateReplacements = new ArrayList<>(); + Collection parameters = pathItemAndServer.operationObject.getParameters(); + for ( String templateVariable : uriTemplate.getTemplateVariables() ) { + for ( Parameter parameter : parameters ) { + if ( templateVariable.equals( parameter.getName() ) ) { + Schema schema = parameter.getSchema(); + if ( schema.hasEnums() ) { + addEnumTemplateValues( templateReplacements, templateVariable, schema ); + } else if ( schema.getDefault() != null ) { + addDefaultTemplateValue( templateReplacements, templateVariable, schema ); + } else { + // TODO: What should be done if the parameter does not have a default value and no + // enumerated set of valid values? + } + } + } + } + return templateReplacements; + } + + private static void addEnumTemplateValues( List> templateReplacements, String templateVariable, + Schema schema ) { + Collection enums = schema.getEnums(); + if ( enums.size() == 1 ) { + for ( Object enumValue : enums ) { + Map replacement = new HashMap<>(); + replacement.put( templateVariable, enumValue.toString() ); + templateReplacements.add( replacement ); + } + } else { + if ( templateReplacements.isEmpty() ) { + Map replacement = new HashMap<>(); + templateReplacements.add( replacement ); + } + List> templateReplacementsToAdd = new ArrayList<>(); + for ( Map templateReplacement : templateReplacements ) { + for ( Object enumValue : enums ) { + Map newTemplateReplacement = new HashMap<>(); + newTemplateReplacement.putAll( templateReplacement ); + newTemplateReplacement.put( templateVariable, enumValue.toString() ); + templateReplacementsToAdd.add( newTemplateReplacement ); + } + } + templateReplacements.clear(); + templateReplacements.addAll( templateReplacementsToAdd ); + } + } + + private static void addDefaultTemplateValue( List> templateReplacements, + String templateVariable, Schema schema ) { + if ( templateReplacements.isEmpty() ) { + Map replacement = new HashMap<>(); + templateReplacements.add( replacement ); + } + for ( Map templateReplacement : templateReplacements ) { + templateReplacement.put( templateVariable, schema.getDefault().toString() ); + } + } + + private static List identifyServerObjects( OpenApi3 apiModel, Path pathItemObject, Operation operationObject ) { + if ( operationObject.hasServers() ) + return parseUrls( operationObject.getServers() ); + if ( pathItemObject.hasServers() ) + return parseUrls( pathItemObject.getServers() ); + if ( apiModel.hasServers() ) + return parseUrls( apiModel.getServers() ); + return Collections.singletonList( DEFAULT_SERVER_URL ); + } + + private static List parseUrls( Collection servers ) { + List urls = new ArrayList<>(); + for ( Server server : servers ) + urls.add( server.getUrl() ); + return urls; + } + + private static class PathItemAndServer { + + private final Path pathItemObject; + + private Operation operationObject; + + // TODO: must be a server object to consider server variables + private String serverUrl; + + private PathItemAndServer( Path pathItemObject, Operation operationObject, String serverUrl ) { + this.pathItemObject = pathItemObject; + this.operationObject = operationObject; + this.serverUrl = serverUrl; + } + + } + +} diff --git a/src/main/java/org/opengis/cite/ogcapiprocesses10/openapi3/TestPoint.java b/src/main/java/org/opengis/cite/ogcapiprocesses10/openapi3/TestPoint.java new file mode 100644 index 0000000..bafa21f --- /dev/null +++ b/src/main/java/org/opengis/cite/ogcapiprocesses10/openapi3/TestPoint.java @@ -0,0 +1,110 @@ +package org.opengis.cite.ogcapiprocesses10.openapi3; + +import java.util.Collections; +import java.util.Map; +import java.util.Objects; + +import com.reprezen.kaizen.oasparser.model3.MediaType; + +/** + * Encapsulates a Test Point with the UriTemplate and predefined replacements. + * + * @author Lyn Goltz + */ +public class TestPoint { + + private final String serverUrl; + + private final String path; + + private Map predefinedTemplateReplacement; + + private Map contentMediaTypes; + + /** + * Instantiates a TestPoint with UriTemplate but without predefined replacements. + * + * @param serverUrl + * the serverUrl, never null + * @param path + * the path never, null + * @param contentMediaTypes + * the content media types for the GET operation with response "200", may be null + */ + public TestPoint( String serverUrl, String path, Map contentMediaTypes ) { + this( serverUrl, path, Collections.emptyMap(), contentMediaTypes ); + } + + /** + * Instantiates a TestPoint with UriTemplate and predefined replacements. + * + * @param serverUrl + * the serverUrl, never null + * @param path + * the path, never null + * @param predefinedTemplateReplacement + * a list of predefined replacements never null + * @param contentMediaTypes + * the content media types for the GET operation with response "200", may be null + */ + public TestPoint( String serverUrl, String path, Map predefinedTemplateReplacement, + Map contentMediaTypes ) { + this.serverUrl = serverUrl; + this.path = path; + this.predefinedTemplateReplacement = Collections.unmodifiableMap( predefinedTemplateReplacement ); + this.contentMediaTypes = contentMediaTypes; + } + + /** + * + * @return the serverUrl never null + */ + public String getServerUrl() { + return serverUrl; + } + + /** + * @return the path never, null + */ + public String getPath() { + return path; + } + + /** + * @return an unmodifiable mao with predefined replacements, may be empty but never null + */ + public Map getPredefinedTemplateReplacement() { + return predefinedTemplateReplacement; + } + + /** + * @return the content media types for the GET operation with response "200", may be null + */ + public Map getContentMediaTypes() { + return contentMediaTypes; + } + + @Override + public String toString() { + return "Server URL: " + serverUrl + " , Path: " + path + ", Replacements: " + predefinedTemplateReplacement; + } + + @Override + public boolean equals( Object o ) { + if ( this == o ) + return true; + if ( o == null || getClass() != o.getClass() ) + return false; + TestPoint testPoint = (TestPoint) o; + return Objects.equals( serverUrl, testPoint.serverUrl ) + && Objects.equals( path, testPoint.predefinedTemplateReplacement ) + && Objects.equals( predefinedTemplateReplacement, testPoint.path ) + && Objects.equals( contentMediaTypes, testPoint.contentMediaTypes ); + } + + @Override + public int hashCode() { + return Objects.hash( serverUrl, path, predefinedTemplateReplacement, contentMediaTypes ); + } + +} diff --git a/src/main/java/org/opengis/cite/ogcapiprocesses10/openapi3/UriBuilder.java b/src/main/java/org/opengis/cite/ogcapiprocesses10/openapi3/UriBuilder.java new file mode 100644 index 0000000..5851b5a --- /dev/null +++ b/src/main/java/org/opengis/cite/ogcapiprocesses10/openapi3/UriBuilder.java @@ -0,0 +1,88 @@ +package org.opengis.cite.ogcapiprocesses10.openapi3; + +import java.util.HashMap; +import java.util.Map; + +import com.sun.jersey.api.uri.UriTemplate; +import com.sun.jersey.api.uri.UriTemplateParser; + +/** + * Builds a URL out of a TestPoint. + * + * @author Lyn Goltz + */ +public class UriBuilder { + + private final TestPoint testPoint; + + private final Map templateReplacements = new HashMap<>(); + + /** + * @param testPoint + * never null + */ + public UriBuilder( TestPoint testPoint ) { + this.testPoint = testPoint; + this.templateReplacements.putAll( testPoint.getPredefinedTemplateReplacement() ); + } + + /** + * Adds the collectionName to the URI + * + * @param collectionName + * never null + * @return this UrlBuilder + */ + public UriBuilder collectionName( String collectionName ) { + String templateName = retrieveCollectionNameTemplateName(); + addTemplateReplacement( collectionName, templateName ); + return this; + } + + /** + * Adds the featureId to the URI + * + * @param featureId + * never null + * @return this UrlBuilder + */ + public UriBuilder featureId( String featureId ) { + String templateName = retrieveFeatureIdTemplateName(); + addTemplateReplacement( featureId, templateName ); + return this; + } + + /** + * @return this URI, never null + */ + public String buildUrl() { + UriTemplate uriTemplate = new UriTemplate( testPoint.getServerUrl() + testPoint.getPath() ); + return uriTemplate.createURI( templateReplacements ); + } + + private void addTemplateReplacement( String collectionName, String templateName ) { + if ( templateName != null ) + templateReplacements.put( templateName, collectionName ); + } + + private String retrieveCollectionNameTemplateName() { + String path = testPoint.getPath(); + UriTemplateParser uriTemplateParser = new UriTemplateParser( path ); + for ( String templateName : uriTemplateParser.getNames() ) { + if ( path.startsWith( "/collections/{" + templateName + "}" ) ) + return templateName; + } + return null; + } + + private String retrieveFeatureIdTemplateName() { + String path = testPoint.getPath(); + UriTemplateParser uriTemplateParser = new UriTemplateParser( path ); + for ( String templateName : uriTemplateParser.getNames() ) { + if ( path.endsWith( "items/{" + templateName + "}" ) ) + return templateName; + } + return null; + } + +} \ No newline at end of file diff --git a/src/main/java/org/opengis/cite/ogcapiprocesses10/util/BBox.java b/src/main/java/org/opengis/cite/ogcapiprocesses10/util/BBox.java new file mode 100644 index 0000000..d2175c9 --- /dev/null +++ b/src/main/java/org/opengis/cite/ogcapiprocesses10/util/BBox.java @@ -0,0 +1,81 @@ +package org.opengis.cite.ogcapiprocesses10.util; + +import java.text.DecimalFormat; +import java.text.NumberFormat; +import java.util.Locale; +import java.util.Objects; + +/** + * @author Lyn Goltz + */ +public class BBox { + + private static final String PATTERN = "###.0000000"; + + private final double minX; + + private final double minY; + + private final double maxX; + + private final double maxY; + + /** + * @param minX + * Lower left corner, coordinate axis 1 + * @param minY + * Lower left corner, coordinate axis 2 + * @param maxX + * Upper right corner, coordinate axis 1 + * @param maxY + * Upper right corner, coordinate axis 2 + */ + public BBox( double minX, double minY, double maxX, double maxY ) { + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + /** + * @return the bbox as query string like '-12,10, 12,20' + */ + public String asQueryParameter() { + StringBuilder sb = new StringBuilder(); + DecimalFormat formatter = formatter(); + sb.append( formatter.format( minX ) ).append( "," ); + sb.append( formatter.format( minY ) ).append( "," ); + sb.append( formatter.format( maxX ) ).append( "," ); + sb.append( formatter.format( maxY ) ); + return sb.toString(); + } + + @Override + public String toString() { + return asQueryParameter(); + } + + @Override + public boolean equals( Object o ) { + if ( this == o ) + return true; + if ( o == null || getClass() != o.getClass() ) + return false; + BBox bBox = (BBox) o; + return Double.compare( bBox.minX, minX ) == 0 && Double.compare( bBox.minY, minY ) == 0 + && Double.compare( bBox.maxX, maxX ) == 0 && Double.compare( bBox.maxY, maxY ) == 0; + } + + @Override + public int hashCode() { + return Objects.hash( minX, minY, maxX, maxY ); + } + + private DecimalFormat formatter() { + NumberFormat nf = NumberFormat.getNumberInstance( Locale.ENGLISH ); + DecimalFormat df = (DecimalFormat) nf; + df.applyPattern( PATTERN ); + return df; + } + +} \ No newline at end of file diff --git a/src/main/java/org/opengis/cite/ogcapiprocesses10/util/ClientUtils.java b/src/main/java/org/opengis/cite/ogcapiprocesses10/util/ClientUtils.java index 693766c..214c094 100644 --- a/src/main/java/org/opengis/cite/ogcapiprocesses10/util/ClientUtils.java +++ b/src/main/java/org/opengis/cite/ogcapiprocesses10/util/ClientUtils.java @@ -24,6 +24,7 @@ import javax.ws.rs.core.UriBuilder; import javax.xml.transform.Source; import javax.xml.transform.dom.DOMSource; + import org.opengis.cite.ogcapiprocesses10.ReusableEntityFilter; import org.w3c.dom.Document; diff --git a/src/main/java/org/opengis/cite/ogcapiprocesses10/util/JsonUtils.java b/src/main/java/org/opengis/cite/ogcapiprocesses10/util/JsonUtils.java new file mode 100644 index 0000000..e7bec64 --- /dev/null +++ b/src/main/java/org/opengis/cite/ogcapiprocesses10/util/JsonUtils.java @@ -0,0 +1,388 @@ +package org.opengis.cite.ogcapiprocesses10.util; + +import static io.restassured.RestAssured.given; +import static io.restassured.http.Method.GET; +import static org.opengis.cite.ogcapiprocesses10.OgcApiProcesses10.GEOJSON_MIME_TYPE; + +import java.net.URI; +import java.net.URISyntaxException; +import java.time.LocalDate; +import java.time.Period; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.util.*; + +import io.restassured.path.json.JsonPath; +import io.restassured.response.Response; +import io.restassured.specification.RequestSpecification; + +/** + * @author Lyn Goltz + */ +public class JsonUtils { + + private JsonUtils() { + } + + /** + * Parses the id of the first feature from the passed json. + * + * @param collectionItemJson + * the json document containing the features, never null + * @return the parsed id, may be null if no feature could be found + */ + public static String parseFeatureId( JsonPath collectionItemJson ) { + List> features = collectionItemJson.get( "features" ); + if ( features == null ) + return null; + for ( Map feature : features ) { + if ( feature.containsKey( "id" ) ) + return feature.get( "id" ).toString(); + } + return null; + } + + /** + * Parses the temporal extent from the passed collection. + * + * @param collection + * the collection containing the extent to parse, never null + * @return the parsed temporal extent, null if no extent exists + * @throws IllegalArgumentException + * if the number of items in the extent invalid + * + */ + public static TemporalExtent parseTemporalExtent( Map collection ) { + Object extent = collection.get( "extent" ); + if ( extent == null || !( extent instanceof Map ) ) + return null; + Object spatial = ( (Map) extent ).get( "temporal" ); + if ( spatial == null || !( spatial instanceof List ) ) + return null; + List coords = (List) spatial; + if ( coords.size() == 2 ) { + ZonedDateTime begin = parseAsDate( (String) coords.get( 0 ) ); + ZonedDateTime end = parseAsDate( (String) coords.get( 1 ) ); + return new TemporalExtent( begin, end ); + } + throw new IllegalArgumentException( "Temporal extent with " + coords.size() + " items is invalid" ); + } + + /** + * Parses the passed string as ISO 8601 date. + * + * @param dateTime + * the dateTime to parse, never null + * @return the parsed date, never null + */ + public static ZonedDateTime parseAsDate( String dateTime ) { + return ZonedDateTime.parse( dateTime ); + } + + /** + * Formats the passed string as ISO 8601 date. Example: "2018-02-12T23:20:50Z" + * + * @param dateTime + * the dateTime to format, never null + * @return the formatted date, never null + */ + public static String formatDate( ZonedDateTime dateTime ) { + return DateTimeFormatter.ISO_INSTANT.format( dateTime ); + } + + /** + * Formats the passed string as ISO 8601 date. Example: "2018-02-12" + * + * @param date + * the dateTime to format, never null + * @return the formatted date, never null + */ + public static String formatDate( LocalDate date ) { + return DateTimeFormatter.ISO_DATE.format( date ); + } + + /** + * Formats the passed string as a period using a start and end time. Example: + * "2018-02-12T00:00:00Z/2018-03-18T12:31:12Z" + * + * @param beginDateTime + * the begin dateTime to format, never null + * @param endDateTime + * the end dateTime to format, never null + * @return the formatted date, never null + */ + public static String formatDateRange( ZonedDateTime beginDateTime, ZonedDateTime endDateTime ) { + return formatDate( beginDateTime ) + "/" + formatDate( endDateTime ); + } + + /** + * Formats the passed string as a period using start time and a duration. Example: + * "2018-02-12T00:00:00Z/P1M6DT12H31M12S" + * + * @param beginDate + * the begin date to format, never null + * @param endDate + * the end date to format, never null + * @return the formatted date, never null + */ + public static String formatDateRangeWithDuration( LocalDate beginDate, LocalDate endDate ) { + Period betweenDate = Period.between( beginDate, endDate ); + return formatDate( beginDate ) + "/" + betweenDate; + } + + /** + * Parses the spatial extent from the passed collection. + * + * @param collection + * the collection containing the extent to parse, never null + * @return the parsed bbox, null if no extent exists + * @throws IllegalArgumentException + * if the number of items in the extent invalid + * + */ + public static BBox parseSpatialExtent( Map collection ) { + Object extent = collection.get( "extent" ); + if ( extent == null || !( extent instanceof Map ) ) + return null; + Object spatial = ( (Map) extent ).get( "spatial" ); + if ( spatial == null || !( spatial instanceof List ) ) + return null; + List coords = (List) spatial; + if ( coords.size() == 4 ) { + double minX = parseValueAsDouble( coords.get( 0 ) ); + double minY = parseValueAsDouble( coords.get( 1 ) ); + double maxX = parseValueAsDouble( coords.get( 2 ) ); + double maxY = parseValueAsDouble( coords.get( 3 ) ); + return new BBox( minX, minY, maxX, maxY ); + } else if ( coords.size() == 6 ) { + throw new IllegalArgumentException( "BBox with " + coords.size() + + " coordinates is currently not supported" ); + } + throw new IllegalArgumentException( "BBox with " + coords.size() + " coordinates is invalid" ); + } + + /** + * Parses all links with 'type' of one of the passed mediaTypes and the 'rel' property with the passed value. + * + * @param links + * list of all links, never null + * @param mediaTypesToSupport + * a list of media types the links searched for should support, may be empty but never null + * @param expectedRel + * the expected value of the property 'rel', never null + * @return a list of links supporting one of the media types and with the expected 'rel' property, may be empty but + * never null + */ + public static List> findLinksWithSupportedMediaTypeByRel( List> links, + List mediaTypesToSupport, + String expectedRel ) { + List> alternateLinks = new ArrayList<>(); + for ( Map link : links ) { + Object type = link.get( "type" ); + Object rel = link.get( "rel" ); + if ( expectedRel.equals( rel ) && isSupportedMediaType( type, mediaTypesToSupport ) ) + alternateLinks.add( link ); + } + return alternateLinks; + } + + /** + * Parsing the media types which does not have a link woth property 'type' for. + * + * @param links + * list of links to search in, never null + * @param mediaTypesToSuppport + * a list of media types which should be supported, never null + * @return the media types which does not have a link for. + */ + public static List findUnsupportedTypes( List> links, List mediaTypesToSuppport ) { + List unsupportedType = new ArrayList<>(); + for ( String contentMediaType : mediaTypesToSuppport ) { + boolean hasLinkForContentType = hasLinkForContentType( links, contentMediaType ); + if ( !hasLinkForContentType ) + unsupportedType.add( contentMediaType ); + } + return unsupportedType; + } + + /** + * Parses the links without 'rel' or 'type' property. + * + * @param links + * list of links to search in, never null + * @param rels + * rels null + * @return the links without 'rel' or 'type' property + */ + public static List findLinksWithoutRelOrType( List> links, Set rels ) { + List linksWithoutRelOrType = new ArrayList<>(); + for ( Map link : links ) { + if ( rels.contains(link.get( "rel" )) && !linkIncludesRelAndType( link ) ) + linksWithoutRelOrType.add( (String) link.get( "href" ) ); + } + return linksWithoutRelOrType; + } + + /** + * Parses the link with 'rel=self'. + * + * @param links + * list of links to search in, never null + * @param expectedRel + * the expected value of the property 'rel', never null + * @return the link to itself or null if no such link exists + */ + public static Map findLinkByRel( List> links, String expectedRel ) { + if ( links == null ) + return null; + for ( Map link : links ) { + Object rel = link.get( "rel" ); + if ( expectedRel.equals( rel ) ) + return link; + } + return null; + } + + /** + * Checks if the passed link contains 'rel' and 'type' properties. + * + * @param link + * to check, never null + * @return true if the link contains 'rel' and 'type' properties, false otherwise + */ + public static boolean linkIncludesRelAndType( Map link ) { + Object rel = link.get( "rel" ); + Object type = link.get( "type" ); + if ( rel != null && type != null ) + return true; + return false; + } + + /** + * Checks if a property with the passed name exists in the jsonPath. + * + * @param propertyName + * name of the property to check, never null + * @param jsonPath + * to check, never null + * @return true if the property exists, false otherwise + */ + public static boolean hasProperty( String propertyName, JsonPath jsonPath ) { + return jsonPath.get( propertyName ) != null; + } + + /** + * Collects the number of all returned features by iterating over all 'next' links and summarizing the size of + * features in 'features' array property. + * + * @param jsonPath + * the initial collection, never null + * @param maximumLimit + * the limit parameter value to use, if <= 0 the parameter is omitted + * @return the number of all returned features + * @throws URISyntaxException + * if the creation of a uri fails + */ + public static int collectNumberOfAllReturnedFeatures( JsonPath jsonPath, int maximumLimit ) + throws URISyntaxException { + int numberOfAllReturnedFeatures = jsonPath.getList( "features" ).size(); + Map nextLink = findLinkByRel( jsonPath.getList( "links" ), "next" ); + while ( nextLink != null ) { + String nextUrl = (String) nextLink.get( "href" ); + URI uri = new URI( nextUrl ); + + RequestSpecification accept = given().log().all().baseUri( nextUrl ).accept( GEOJSON_MIME_TYPE ); + String[] pairs = uri.getQuery().split( "&" ); + String limitParamFromUri = null; + for ( String pair : pairs ) { + int idx = pair.indexOf( "=" ); + String key = pair.substring( 0, idx ); + String value = pair.substring( idx + 1 ); + if ( "limit".equals( key ) ) { + limitParamFromUri = value; + } else { + accept.param( key, value ); + } + } + if ( maximumLimit > 0 ) { + accept.param( "limit", maximumLimit ); + } else if ( limitParamFromUri != null ) { + accept.param( "limit", limitParamFromUri ); + } + + Response response = accept.when().request( GET ); + response.then().statusCode( 200 ); + + JsonPath nextJsonPath = response.jsonPath(); + int features = nextJsonPath.getList( "features" ).size(); + if ( features > 0 ) { + numberOfAllReturnedFeatures += features; + nextLink = findLinkByRel( nextJsonPath.getList( "links" ), "next" ); + } else { + nextLink = null; + } + } + return numberOfAllReturnedFeatures; + } + + private static boolean isSameMediaType( String mediaType1, String mediaType2 ) { + if ( mediaType1.contains(";") || mediaType2.contains(";") ) { + // media types are not case sensitive + String[] components1 = mediaType1.toLowerCase().split(";"); + String[] components2 = mediaType2.toLowerCase().split(";"); + // type and subtype must match + if ( !components1[0].trim().equals(components2[0].trim()) ) + return false; + Set parameters1 = new HashSet<>(); + Set parameters2 = new HashSet<>(); + // normalize parameter values and compare them + for ( int i=1; i> alternateLinks, String mediaType ) { + for ( Map alternateLink : alternateLinks ) { + Object type = alternateLink.get( "type" ); + if ( type instanceof String && isSameMediaType( mediaType, (String) type ) ) + return true; + } + return false; + } + + private static boolean isSupportedMediaType( Object type, List mediaTypes ) { + for ( String mediaType : mediaTypes ) { + if ( type instanceof String && isSameMediaType( mediaType, (String) type ) ) + return true; + } + return false; + } + + private static double parseValueAsDouble( Object cords ) { + if ( cords instanceof Integer ) { + return ( (Integer) cords ).doubleValue(); + } else if ( cords instanceof Float ) { + return ( (Float) cords ).doubleValue(); + } else if ( cords instanceof Double ) { + return (Double) cords; + } else { + return Double.parseDouble( cords.toString() ); + } + } + +} diff --git a/src/main/java/org/opengis/cite/ogcapiprocesses10/util/NamespaceBindings.java b/src/main/java/org/opengis/cite/ogcapiprocesses10/util/NamespaceBindings.java index 344e5c0..6d6577b 100644 --- a/src/main/java/org/opengis/cite/ogcapiprocesses10/util/NamespaceBindings.java +++ b/src/main/java/org/opengis/cite/ogcapiprocesses10/util/NamespaceBindings.java @@ -6,6 +6,7 @@ import java.util.Iterator; import java.util.Map; import javax.xml.namespace.NamespaceContext; + import org.opengis.cite.ogcapiprocesses10.Namespaces; /** diff --git a/src/main/java/org/opengis/cite/ogcapiprocesses10/util/TemporalExtent.java b/src/main/java/org/opengis/cite/ogcapiprocesses10/util/TemporalExtent.java new file mode 100644 index 0000000..e637da5 --- /dev/null +++ b/src/main/java/org/opengis/cite/ogcapiprocesses10/util/TemporalExtent.java @@ -0,0 +1,27 @@ +package org.opengis.cite.ogcapiprocesses10.util; + +import java.time.ZonedDateTime; + +/** + * @author Lyn Goltz + */ +public class TemporalExtent { + + private ZonedDateTime begin; + + private ZonedDateTime end; + + public TemporalExtent( ZonedDateTime begin, ZonedDateTime end ) { + this.begin = begin; + this.end = end; + } + + public ZonedDateTime getBegin() { + return begin; + } + + public ZonedDateTime getEnd() { + return end; + } + +} \ No newline at end of file diff --git a/src/main/java/org/opengis/cite/ogcapiprocesses10/util/URIUtils.java b/src/main/java/org/opengis/cite/ogcapiprocesses10/util/URIUtils.java index ed996ea..2fab076 100644 --- a/src/main/java/org/opengis/cite/ogcapiprocesses10/util/URIUtils.java +++ b/src/main/java/org/opengis/cite/ogcapiprocesses10/util/URIUtils.java @@ -9,129 +9,57 @@ import java.util.logging.Level; import javax.ws.rs.core.HttpHeaders; -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.parsers.ParserConfigurationException; - -import org.w3c.dom.Document; -import org.xml.sax.SAXException; import com.sun.jersey.api.client.Client; import com.sun.jersey.api.client.ClientResponse; import com.sun.jersey.api.client.WebResource; /** - * Provides a collection of utility methods for manipulating or resolving URI - * references. + * Provides a collection of utility methods for manipulating or resolving URI references. */ public class URIUtils { - private static final String FIXUP_BASE_URI = "http://apache.org/xml/features/xinclude/fixup-base-uris"; - - /** - * Parses the content of the given URI as an XML document and returns a new - * DOM Document object. Entity reference nodes will not be expanded. XML - * inclusions (xi:include elements) will be processed if present. - * - * @param uriRef - * An absolute URI specifying the location of an XML resource. - * @return A DOM Document node representing an XML resource. - * @throws SAXException - * If the resource cannot be parsed. - * @throws IOException - * If the resource is not accessible. - */ - public static Document parseURI(URI uriRef) throws SAXException, - IOException { - if ((null == uriRef) || !uriRef.isAbsolute()) { - throw new IllegalArgumentException( - "Absolute URI is required, but received " + uriRef); - } - DocumentBuilderFactory docFactory = DocumentBuilderFactory - .newInstance(); - docFactory.setNamespaceAware(true); - docFactory.setExpandEntityReferences(false); - docFactory.setXIncludeAware(true); - Document doc = null; - try { - // XInclude processor will not add xml:base attributes - docFactory.setFeature(FIXUP_BASE_URI, false); - DocumentBuilder docBuilder = docFactory.newDocumentBuilder(); - doc = docBuilder.parse(uriRef.toString()); - } catch (ParserConfigurationException x) { - TestSuiteLogger.log(Level.WARNING, - "Failed to create DocumentBuilder." + x); - } - if (null != doc) { - doc.setDocumentURI(uriRef.toString()); - } - return doc; - } - /** - * Dereferences the given URI and stores the resulting resource - * representation in a local file. The file will be located in the default - * temporary file directory. + * Dereferences the given URI and stores the resulting resource representation in a local file. The file will be + * located in the default temporary file directory. * * @param uriRef * An absolute URI specifying the location of some resource. - * @return A File containing the content of the resource; it may be empty if - * resolution failed for any reason. + * @return A File containing the content of the resource; it may be empty if resolution failed for any reason. * @throws IOException * If an IO error occurred. */ - public static File dereferenceURI(URI uriRef) throws IOException { - if ((null == uriRef) || !uriRef.isAbsolute()) { - throw new IllegalArgumentException( - "Absolute URI is required, but received " + uriRef); + public static File dereferenceURI( URI uriRef ) + throws IOException { + if ( ( null == uriRef ) || !uriRef.isAbsolute() ) { + throw new IllegalArgumentException( "Absolute URI is required, but received " + uriRef ); } - if (uriRef.getScheme().equalsIgnoreCase("file")) { - return new File(uriRef); + if ( uriRef.getScheme().equalsIgnoreCase( "file" ) ) { + return new File( uriRef ); } Client client = Client.create(); - WebResource webRes = client.resource(uriRef); - ClientResponse rsp = webRes.get(ClientResponse.class); + WebResource webRes = client.resource( uriRef ); + ClientResponse rsp = webRes.get( ClientResponse.class ); String suffix = null; - if (rsp.getHeaders().getFirst(HttpHeaders.CONTENT_TYPE).endsWith("xml")) { + if ( rsp.getHeaders().getFirst( HttpHeaders.CONTENT_TYPE ).endsWith( "xml" ) ) { suffix = ".xml"; } - File destFile = File.createTempFile("entity-", suffix); - if (rsp.hasEntity()) { + File destFile = File.createTempFile( "entity-", suffix ); + if ( rsp.hasEntity() ) { InputStream is = rsp.getEntityInputStream(); - OutputStream os = new FileOutputStream(destFile); + OutputStream os = new FileOutputStream( destFile ); byte[] buffer = new byte[8 * 1024]; int bytesRead; - while ((bytesRead = is.read(buffer)) != -1) { - os.write(buffer, 0, bytesRead); + while ( ( bytesRead = is.read( buffer ) ) != -1 ) { + os.write( buffer, 0, bytesRead ); } is.close(); os.flush(); os.close(); } - TestSuiteLogger.log(Level.FINE, "Wrote " + destFile.length() - + " bytes to file at " + destFile.getAbsolutePath()); + TestSuiteLogger.log( Level.FINE, + "Wrote " + destFile.length() + " bytes to file at " + destFile.getAbsolutePath() ); return destFile; } - /** - * Constructs an absolute URI from the given URI reference and a base URI. - * - * @see RFC 3986, - * 5.2 - * - * @param baseURI - * The base URI; if present, it must be an absolute URI. - * @param uriRef - * A URI reference that may be relative to the given base URI. - * @return The resulting URI. - * - */ - public static URI resolveRelativeURI(String baseURI, String uriRef) { - URI uri = (null != baseURI) ? URI.create(baseURI) : URI.create(""); - if (null != baseURI && null == uri.getScheme()) { - throw new IllegalArgumentException( - "Base URI has no scheme component: " + baseURI); - } - return uri.resolve(uriRef); - } } diff --git a/src/main/java/org/opengis/cite/ogcapiprocesses10/util/ValidationUtils.java b/src/main/java/org/opengis/cite/ogcapiprocesses10/util/ValidationUtils.java deleted file mode 100644 index da72a73..0000000 --- a/src/main/java/org/opengis/cite/ogcapiprocesses10/util/ValidationUtils.java +++ /dev/null @@ -1,162 +0,0 @@ -package org.opengis.cite.ogcapiprocesses10.util; - -import java.io.File; -import java.io.IOException; -import java.net.URI; -import java.net.URL; -import java.util.Arrays; -import java.util.HashSet; -import java.util.Set; -import java.util.logging.Level; - -import javax.xml.XMLConstants; -import javax.xml.namespace.QName; -import javax.xml.stream.XMLEventReader; -import javax.xml.stream.XMLInputFactory; -import javax.xml.stream.XMLStreamException; -import javax.xml.stream.events.Attribute; -import javax.xml.stream.events.StartElement; -import javax.xml.transform.Source; -import javax.xml.transform.stream.StreamSource; - -import org.apache.xerces.util.XMLCatalogResolver; -import org.opengis.cite.ogcapiprocesses10.Namespaces; -import org.opengis.cite.validation.SchematronValidator; -import org.w3c.dom.ls.LSResourceResolver; - -/** - * A utility class that provides convenience methods to support schema - * validation. - */ -public class ValidationUtils { - - static final String ROOT_PKG = "/org/opengis/cite/ogcapiprocesses10/"; - private static final XMLCatalogResolver SCH_RESOLVER = initCatalogResolver(); - - private static XMLCatalogResolver initCatalogResolver() { - return (XMLCatalogResolver) createSchemaResolver(Namespaces.SCH); - } - - /** - * Creates a resource resolver suitable for locating schemas using an entity - * catalog. In effect, local copies of standard schemas are returned instead - * of retrieving them from external repositories. - * - * @param schemaLanguage - * A URI that identifies a schema language by namespace name. - * @return A {@code LSResourceResolver} object that is configured to use an - * OASIS entity catalog. - */ - public static LSResourceResolver createSchemaResolver(URI schemaLanguage) { - String catalogFileName; - if (schemaLanguage.equals(Namespaces.XSD)) { - catalogFileName = "schema-catalog.xml"; - } else { - catalogFileName = "schematron-catalog.xml"; - } - URL catalogURL = ValidationUtils.class.getResource(ROOT_PKG - + catalogFileName); - XMLCatalogResolver resolver = new XMLCatalogResolver(); - resolver.setCatalogList(new String[] { catalogURL.toString() }); - return resolver; - } - - /** - * Constructs a SchematronValidator that will check an XML resource against - * the rules defined in a Schematron schema. An attempt is made to resolve - * the schema reference using an entity catalog; if this fails the reference - * is used as given. - * - * @param schemaRef - * A reference to a Schematron schema; this is expected to be a - * relative or absolute URI value, possibly matching the system - * identifier for some entry in an entity catalog. - * @param phase - * The name of the phase to invoke. - * @return A SchematronValidator instance, or {@code null} if the validator - * cannot be constructed (e.g. invalid schema reference or phase - * name). - */ - public static SchematronValidator buildSchematronValidator( - String schemaRef, String phase) { - Source source = null; - try { - String catalogRef = SCH_RESOLVER - .resolveSystem(schemaRef.toString()); - if (null != catalogRef) { - source = new StreamSource(URI.create(catalogRef).toString()); - } else { - source = new StreamSource(schemaRef); - } - } catch (IOException x) { - TestSuiteLogger.log(Level.WARNING, - "Error reading Schematron schema catalog.", x); - } - SchematronValidator validator = null; - try { - validator = new SchematronValidator(source, phase); - } catch (Exception e) { - TestSuiteLogger.log(Level.WARNING, - "Error creating Schematron validator.", e); - } - return validator; - } - - /** - * Extracts a set of XML Schema references from a source XML document. The - * document element is expected to include the standard xsi:schemaLocation - * attribute. - * - * @param source - * The source instance to read from; its base URI (systemId) - * should be set. - * @param baseURI - * An alternative base URI to use if the source does not have a - * system identifier set or if its system id is a {@code file} - * URI. This will usually be the URI used to retrieve the - * resource; it may be null. - * @return A Set containing absolute URI references that specify the - * locations of XML Schema resources. - * @throws XMLStreamException - * If an error occurs while reading the source instance. - */ - public static Set extractSchemaReferences(Source source, String baseURI) - throws XMLStreamException { - XMLInputFactory factory = XMLInputFactory.newInstance(); - XMLEventReader reader = factory.createXMLEventReader(source); - // advance to document element - StartElement docElem = reader.nextTag().asStartElement(); - Attribute schemaLoc = docElem.getAttributeByName(new QName( - XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI, "schemaLocation")); - if (null == schemaLoc) { - throw new RuntimeException( - "No xsi:schemaLocation attribute found. See ISO 19136, A.3.1."); - } - String[] uriValues = schemaLoc.getValue().split("\\s+"); - if (uriValues.length % 2 != 0) { - throw new RuntimeException( - "xsi:schemaLocation attribute contains an odd number of URI values:\n" - + Arrays.toString(uriValues)); - } - Set schemaURIs = new HashSet(); - // one or more pairs of [namespace name] [schema location] - for (int i = 0; i < uriValues.length; i += 2) { - URI schemaURI = null; - if (!URI.create(uriValues[i + 1]).isAbsolute() - && (null != source.getSystemId())) { - String schemaRef = URIUtils.resolveRelativeURI( - source.getSystemId(), uriValues[i + 1]).toString(); - if (schemaRef.startsWith("file") - && !new File(schemaRef).exists() && (null != baseURI)) { - schemaRef = URIUtils.resolveRelativeURI(baseURI, - uriValues[i + 1]).toString(); - } - schemaURI = URI.create(schemaRef); - } else { - schemaURI = URI.create(uriValues[i + 1]); - } - schemaURIs.add(schemaURI); - } - return schemaURIs; - } -} diff --git a/src/main/java/org/opengis/cite/ogcapiprocesses10/util/XMLUtils.java b/src/main/java/org/opengis/cite/ogcapiprocesses10/util/XMLUtils.java index 6486c79..adc8f73 100644 --- a/src/main/java/org/opengis/cite/ogcapiprocesses10/util/XMLUtils.java +++ b/src/main/java/org/opengis/cite/ogcapiprocesses10/util/XMLUtils.java @@ -1,410 +1,50 @@ package org.opengis.cite.ogcapiprocesses10.util; -import java.io.OutputStream; -import java.io.Reader; -import java.io.StringReader; import java.io.StringWriter; import java.io.Writer; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; import java.util.Properties; import java.util.logging.Level; -import java.util.logging.Logger; -import javax.xml.namespace.QName; -import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.parsers.ParserConfigurationException; -import javax.xml.stream.XMLInputFactory; -import javax.xml.stream.XMLStreamException; -import javax.xml.stream.XMLStreamReader; import javax.xml.transform.OutputKeys; -import javax.xml.transform.Source; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; -import javax.xml.xpath.XPath; -import javax.xml.xpath.XPathConstants; -import javax.xml.xpath.XPathExpressionException; -import javax.xml.xpath.XPathFactory; -import net.sf.saxon.s9api.DOMDestination; -import net.sf.saxon.s9api.DocumentBuilder; -import net.sf.saxon.s9api.Processor; -import net.sf.saxon.s9api.SaxonApiException; -import net.sf.saxon.s9api.XPathCompiler; -import net.sf.saxon.s9api.XPathSelector; -import net.sf.saxon.s9api.XQueryCompiler; -import net.sf.saxon.s9api.XQueryEvaluator; -import net.sf.saxon.s9api.XQueryExecutable; -import net.sf.saxon.s9api.XdmNode; -import net.sf.saxon.s9api.XdmValue; -import net.sf.saxon.s9api.XsltCompiler; -import net.sf.saxon.s9api.XsltExecutable; -import net.sf.saxon.s9api.XsltTransformer; - -import org.w3c.dom.Document; -import org.w3c.dom.Element; import org.w3c.dom.Node; -import org.w3c.dom.NodeList; /** - * Provides various utility methods for accessing or manipulating XML - * representations. + * Provides various utility methods for accessing or manipulating XML representations. */ public class XMLUtils { - private static final Logger LOGR = Logger.getLogger(XMLUtils.class.getPackage().getName()); - private static final XMLInputFactory STAX_FACTORY = initXMLInputFactory(); - private static final XPathFactory XPATH_FACTORY = initXPathFactory(); - - private static XPathFactory initXPathFactory() { - XPathFactory factory = XPathFactory.newInstance(); - return factory; - } - - private static XMLInputFactory initXMLInputFactory() { - XMLInputFactory factory = XMLInputFactory.newInstance(); - factory.setProperty(XMLInputFactory.IS_COALESCING, Boolean.TRUE); - return factory; - } - /** - * Writes the content of a DOM Node to a string. The XML declaration is - * omitted and the character encoding is set to "US-ASCII" (any character - * outside of this set is serialized as a numeric character reference). + * Writes the content of a DOM Node to a string. The XML declaration is omitted and the character encoding is set to + * "US-ASCII" (any character outside of this set is serialized as a numeric character reference). * * @param node * The DOM Node to be serialized. * @return A String representing the content of the given node. */ - public static String writeNodeToString(Node node) { - if (null == node) { + public static String writeNodeToString( Node node ) { + if ( null == node ) { return ""; } Writer writer = null; try { Transformer idTransformer = TransformerFactory.newInstance().newTransformer(); Properties outProps = new Properties(); - outProps.setProperty(OutputKeys.ENCODING, "US-ASCII"); - outProps.setProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); - outProps.setProperty(OutputKeys.INDENT, "yes"); - idTransformer.setOutputProperties(outProps); + outProps.setProperty( OutputKeys.ENCODING, "US-ASCII" ); + outProps.setProperty( OutputKeys.OMIT_XML_DECLARATION, "yes" ); + outProps.setProperty( OutputKeys.INDENT, "yes" ); + idTransformer.setOutputProperties( outProps ); writer = new StringWriter(); - idTransformer.transform(new DOMSource(node), new StreamResult(writer)); - } catch (TransformerException ex) { - TestSuiteLogger.log(Level.WARNING, "Failed to serialize node " + node.getNodeName(), ex); + idTransformer.transform( new DOMSource( node ), new StreamResult( writer ) ); + } catch ( TransformerException ex ) { + TestSuiteLogger.log( Level.WARNING, "Failed to serialize node " + node.getNodeName(), ex ); } return writer.toString(); } - /** - * Writes the content of a DOM Node to a byte stream. An XML declaration is - * always omitted. - * - * @param node - * The DOM Node to be serialized. - * @param outputStream - * The destination OutputStream reference. - */ - public static void writeNode(Node node, OutputStream outputStream) { - try { - Transformer idTransformer = TransformerFactory.newInstance().newTransformer(); - Properties outProps = new Properties(); - outProps.setProperty(OutputKeys.METHOD, "xml"); - outProps.setProperty(OutputKeys.ENCODING, "UTF-8"); - outProps.setProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); - outProps.setProperty(OutputKeys.INDENT, "yes"); - idTransformer.setOutputProperties(outProps); - idTransformer.transform(new DOMSource(node), new StreamResult(outputStream)); - } catch (TransformerException ex) { - String nodeName = (node.getNodeType() == Node.DOCUMENT_NODE) - ? Document.class.cast(node).getDocumentElement().getNodeName() : node.getNodeName(); - TestSuiteLogger.log(Level.WARNING, "Failed to serialize DOM node: " + nodeName, ex); - } - } - - /** - * Evaluates an XPath 1.0 expression using the given context and returns the - * result as a node set. - * - * @param context - * The context node. - * @param expr - * An XPath expression. - * @param namespaceBindings - * A collection of namespace bindings for the XPath expression, - * where each entry maps a namespace URI (key) to a prefix - * (value). Standard bindings do not need to be declared (see - * {@link NamespaceBindings#withStandardBindings()}. - * @return A NodeList containing nodes that satisfy the expression (it may - * be empty). - * @throws XPathExpressionException - * If the expression cannot be evaluated for any reason. - */ - public static NodeList evaluateXPath(Node context, String expr, Map namespaceBindings) - throws XPathExpressionException { - Object result = evaluateXPath(context, expr, namespaceBindings, XPathConstants.NODESET); - if (!NodeList.class.isInstance(result)) { - throw new XPathExpressionException("Expression does not evaluate to a NodeList: " + expr); - } - return (NodeList) result; - } - - /** - * Evaluates an XPath expression using the given context and returns the - * result as the specified type. - * - *

- * Note: The Saxon implementation supports XPath 2.0 - * expressions when using the JAXP XPath APIs (the default implementation - * will throw an exception). - *

- * - * @param context - * The context node. - * @param expr - * An XPath expression. - * @param namespaceBindings - * A collection of namespace bindings for the XPath expression, - * where each entry maps a namespace URI (key) to a prefix - * (value). Standard bindings do not need to be declared (see - * {@link NamespaceBindings#withStandardBindings()}. - * @param returnType - * The desired return type (as declared in {@link XPathConstants} - * ). - * @return The result converted to the desired returnType. - * @throws XPathExpressionException - * If the expression cannot be evaluated for any reason. - */ - public static Object evaluateXPath(Node context, String expr, Map namespaceBindings, - QName returnType) throws XPathExpressionException { - NamespaceBindings bindings = NamespaceBindings.withStandardBindings(); - bindings.addAllBindings(namespaceBindings); - XPathFactory factory = XPATH_FACTORY; - // WARNING: If context node is Saxon NodeOverNodeInfo, the factory must - // use the same Configuration object to avoid IllegalArgumentException - XPath xpath = factory.newXPath(); - xpath.setNamespaceContext(bindings); - Object result = xpath.evaluate(expr, context, returnType); - return result; - } - - /** - * Evaluates an XPath 2.0 expression using the Saxon s9api interfaces. - * - * @param xmlSource - * The XML Source. - * @param expr - * The XPath expression to be evaluated. - * @param nsBindings - * A collection of namespace bindings required to evaluate the - * XPath expression, where each entry maps a namespace URI (key) - * to a prefix (value); this may be {@code null} if not needed. - * @return An XdmValue object representing a value in the XDM data model; - * this is a sequence of zero or more items, where each item is - * either an atomic value or a node. - * @throws SaxonApiException - * If an error occurs while evaluating the expression; this - * always wraps some other underlying exception. - */ - public static XdmValue evaluateXPath2(Source xmlSource, String expr, Map nsBindings) - throws SaxonApiException { - Processor proc = new Processor(false); - XPathCompiler compiler = proc.newXPathCompiler(); - if (null != nsBindings) { - for (String nsURI : nsBindings.keySet()) { - compiler.declareNamespace(nsBindings.get(nsURI), nsURI); - } - } - XPathSelector xpath = compiler.compile(expr).load(); - DocumentBuilder builder = proc.newDocumentBuilder(); - XdmNode node = null; - if (DOMSource.class.isInstance(xmlSource)) { - DOMSource domSource = (DOMSource) xmlSource; - node = builder.wrap(domSource.getNode()); - } else { - node = builder.build(xmlSource); - } - xpath.setContextItem(node); - return xpath.evaluate(); - } - - /** - * Evaluates an XQuery 1.0 expression using the Saxon s9api interfaces. - * - * @param source - * The XML Source. - * @param query - * The query expression. - * @param nsBindings - * A collection of namespace bindings required to evaluate the - * query, where each entry maps a namespace URI (key) to a prefix - * (value). - * @return An XdmValue object representing a value in the XDM data model. - * @throws SaxonApiException - * If an error occurs while evaluating the query (this always - * wraps some other underlying exception). - */ - public static XdmValue evaluateXQuery(Source source, String query, Map nsBindings) - throws SaxonApiException { - Processor proc = new Processor(false); - XQueryCompiler xqCompiler = proc.newXQueryCompiler(); - if (null != nsBindings) { - for (String nsURI : nsBindings.keySet()) { - xqCompiler.declareNamespace(nsBindings.get(nsURI), nsURI); - } - } - XQueryExecutable xqExec = xqCompiler.compile(query); - XQueryEvaluator xqEval = xqExec.load(); - xqEval.setSource(source); - return xqEval.evaluate(); - } - - /** - * Creates a new Element having the specified qualified name. The element - * must be {@link Document#adoptNode(Node) adopted} when inserted into - * another Document. - * - * @param qName - * A QName object. - * @return An Element node (with a Document owner but no parent). - */ - public static Element createElement(QName qName) { - Document doc = null; - try { - doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument(); - } catch (ParserConfigurationException e) { - throw new RuntimeException(e); - } - Element elem = doc.createElementNS(qName.getNamespaceURI(), qName.getLocalPart()); - return elem; - } - - /** - * Returns a List of all descendant Element nodes having the specified - * [namespace name] property. The elements are listed in document order. - * - * @param node - * The node to search from. - * @param namespaceURI - * An absolute URI denoting a namespace name. - * @return A List containing elements in the specified namespace; the list - * is empty if there are no elements in the namespace. - */ - public static List getElementsByNamespaceURI(Node node, String namespaceURI) { - List list = new ArrayList(); - NodeList children = node.getChildNodes(); - for (int i = 0; i < children.getLength(); i++) { - Node child = children.item(i); - if (child.getNodeType() != Node.ELEMENT_NODE) - continue; - if (child.getNamespaceURI().equals(namespaceURI)) - list.add((Element) child); - } - return list; - } - - /** - * Transforms the content of a DOM Node using a specified XSLT stylesheet. - * - * @param xslt - * A Source object representing a stylesheet (XSLT 1.0 or 2.0). - * @param source - * A Node representing the XML source. If it is an Element node - * it will be imported into a new DOM Document. - * @return A DOM Document containing the result of the transformation. - */ - public static Document transform(Source xslt, Node source) { - Document sourceDoc = null; - Document resultDoc = null; - try { - resultDoc = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument(); - if (source.getNodeType() == Node.DOCUMENT_NODE) { - sourceDoc = (Document) source; - } else { - sourceDoc = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument(); - sourceDoc.appendChild(sourceDoc.importNode(source, true)); - } - } catch (ParserConfigurationException pce) { - throw new RuntimeException(pce); - } - Processor processor = new Processor(false); - XsltCompiler compiler = processor.newXsltCompiler(); - try { - XsltExecutable exec = compiler.compile(xslt); - XsltTransformer transformer = exec.load(); - transformer.setSource(new DOMSource(sourceDoc)); - transformer.setDestination(new DOMDestination(resultDoc)); - transformer.transform(); - } catch (SaxonApiException e) { - throw new RuntimeException(e); - } - return resultDoc; - } - - /** - * Expands character entity ({@literal &name;}) and numeric references ( - * {@literal &#xhhhh;} or {@literal &dddd;}) that occur within a given - * string value. It may be necessary to do this before processing an XPath - * expression. - * - * @param value - * A string representing text content. - * @return A string with all included references expanded. - */ - public static String expandReferencesInText(String value) { - StringBuilder wrapper = new StringBuilder(""); - wrapper.append(value).append(""); - Reader reader = new StringReader(wrapper.toString()); - String str = null; - try { - XMLStreamReader xsr = STAX_FACTORY.createXMLStreamReader(reader); - xsr.nextTag(); // document element - str = xsr.getElementText(); - } catch (XMLStreamException xse) { - LOGR.log(Level.WARNING, xse.getMessage(), xse); - } - return str; - } - - /** - * Creates a DOM Document with the given Element as the document element. A - * deep copy of the element is imported; the source element is not altered. - * - * @param elem - * An Element node. - * @return A Document node. - */ - public static Document importElement(Element elem) { - javax.xml.parsers.DocumentBuilder docBuilder = null; - try { - DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); - factory.setNamespaceAware(true); - docBuilder = factory.newDocumentBuilder(); - } catch (ParserConfigurationException ex) { - LOGR.log(Level.WARNING, null, ex); - } - Document newDoc = docBuilder.newDocument(); - Node newNode = newDoc.importNode(elem, true); - newDoc.appendChild(newNode); - return newDoc; - } - - /** - * Returns a List view of the nodes in the given NodeList collection. - * - * @param nodeList - * An ordered collection of DOM nodes. - * @return A List containing the original sequence of Node objects. - */ - public static List asList(NodeList nodeList) { - List nodes = new ArrayList<>(); - for (int i = 0; i < nodeList.getLength(); i++) { - nodes.add(nodeList.item(i)); - } - return nodes; - } } diff --git a/src/main/resources/org/opengis/cite/ogcapiprocesses10/testng.xml b/src/main/resources/org/opengis/cite/ogcapiprocesses10/testng.xml index 21c3e08..adabc3b 100644 --- a/src/main/resources/org/opengis/cite/ogcapiprocesses10/testng.xml +++ b/src/main/resources/org/opengis/cite/ogcapiprocesses10/testng.xml @@ -10,9 +10,10 @@ - + - + + diff --git a/src/main/resources/test-run-props.xml b/src/main/resources/test-run-props.xml index a5fcf35..195c469 100644 --- a/src/main/resources/test-run-props.xml +++ b/src/main/resources/test-run-props.xml @@ -2,6 +2,6 @@ Sample test run arguments - http://www.w3schools.com/xml/note.xml - class-A,class-B + http://geoprocessing.demo.52north.org:8080/javaps/rest + 3 diff --git a/src/test/java/org/opengis/cite/ogcapiprocesses10/VerifyTestNGController.java b/src/test/java/org/opengis/cite/ogcapiprocesses10/VerifyTestNGController.java deleted file mode 100644 index 4d3125b..0000000 --- a/src/test/java/org/opengis/cite/ogcapiprocesses10/VerifyTestNGController.java +++ /dev/null @@ -1,71 +0,0 @@ -package org.opengis.cite.ogcapiprocesses10; - -import static org.junit.Assert.*; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.net.URL; -import java.util.InvalidPropertiesFormatException; -import java.util.Properties; - -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.parsers.ParserConfigurationException; -import javax.xml.transform.Source; - -import net.sf.saxon.s9api.XdmValue; - -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Test; -import org.opengis.cite.ogcapiprocesses10.util.XMLUtils; -import org.w3c.dom.Document; - -/** - * Verifies the results of executing a test run using the main controller - * (TestNGController). - * - */ -public class VerifyTestNGController { - - private static DocumentBuilder docBuilder; - private Properties testRunProps; - - @BeforeClass - public static void initParser() throws ParserConfigurationException { - DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); - dbf.setNamespaceAware(true); - dbf.setValidating(false); - dbf.setFeature( - "http://apache.org/xml/features/nonvalidating/load-external-dtd", - false); - docBuilder = dbf.newDocumentBuilder(); - } - - @Before - public void loadDefaultTestRunProperties() - throws InvalidPropertiesFormatException, IOException { - this.testRunProps = new Properties(); - this.testRunProps.loadFromXML(getClass().getResourceAsStream( - "/test-run-props.xml")); - } - - @Test - public void doTestRun() throws Exception { - URL testSubject = getClass().getResource("/atom-feed-2.xml"); - this.testRunProps.setProperty(TestRunArg.IUT.toString(), testSubject - .toURI().toString()); - ByteArrayOutputStream outStream = new ByteArrayOutputStream(1024); - this.testRunProps.storeToXML(outStream, "Integration test"); - Document testRunArgs = docBuilder.parse(new ByteArrayInputStream( - outStream.toByteArray())); - TestNGController controller = new TestNGController(); - Source results = controller.doTestRun(testRunArgs); - String xpath = "/testng-results/@failed"; - XdmValue failed = XMLUtils.evaluateXPath2(results, xpath, null); - int numFailed = Integer.parseInt(failed.getUnderlyingValue() - .getStringValue()); - assertEquals("Unexpected number of fail verdicts.", 2, numFailed); - } -} diff --git a/src/test/java/org/opengis/cite/ogcapiprocesses10/level1/VerifyCapability1Tests.java b/src/test/java/org/opengis/cite/ogcapiprocesses10/level1/VerifyCapability1Tests.java deleted file mode 100644 index e6129cd..0000000 --- a/src/test/java/org/opengis/cite/ogcapiprocesses10/level1/VerifyCapability1Tests.java +++ /dev/null @@ -1,72 +0,0 @@ -package org.opengis.cite.ogcapiprocesses10.level1; - -import java.io.IOException; -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; -import org.junit.AfterClass; -import org.junit.BeforeClass; -import org.junit.Test; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; -import org.testng.ISuite; -import org.testng.ITestContext; -import org.w3c.dom.Document; -import org.xml.sax.SAXException; - -/** - * Verifies the behavior of the Capability1Tests test class. Test stubs replace - * fixture constituents where appropriate. - */ -public class VerifyCapability1Tests { - - private static final String SUBJ = "testSubject"; - private static DocumentBuilder docBuilder; - private static ITestContext testContext; - private static ISuite suite; - - public VerifyCapability1Tests() { - } - - @BeforeClass - public static void setUpClass() throws Exception { - testContext = mock(ITestContext.class); - suite = mock(ISuite.class); - when(testContext.getSuite()).thenReturn(suite); - DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); - dbf.setNamespaceAware(true); - docBuilder = dbf.newDocumentBuilder(); - } - - @AfterClass - public static void tearDownClass() throws Exception { - } - - @Test(expected = AssertionError.class) - public void testIsEmpty() { - Capability1Tests iut = new Capability1Tests(); - iut.isEmpty(); - } - - @Test - public void testTrim() { - Capability1Tests iut = new Capability1Tests(); - iut.trim(); - } - - @Test(expected = NullPointerException.class) - public void supplyNullTestSubject() throws SAXException, IOException { - Capability1Tests iut = new Capability1Tests(); - iut.docIsValidAtomFeed(); - } - - @Test - public void supplyValidAtomFeedViaTestContext() throws SAXException, - IOException { - Document doc = docBuilder.parse(this.getClass().getResourceAsStream( - "/atom-feed.xml")); - when(suite.getAttribute(SUBJ)).thenReturn(doc); - Capability1Tests iut = new Capability1Tests(); - iut.obtainTestSubject(testContext); - iut.docIsValidAtomFeed(); - } -} diff --git a/src/test/java/org/opengis/cite/ogcapiprocesses10/util/VerifyURIUtils.java b/src/test/java/org/opengis/cite/ogcapiprocesses10/util/VerifyURIUtils.java deleted file mode 100644 index 6326b24..0000000 --- a/src/test/java/org/opengis/cite/ogcapiprocesses10/util/VerifyURIUtils.java +++ /dev/null @@ -1,83 +0,0 @@ -package org.opengis.cite.ogcapiprocesses10.util; - -import java.io.File; -import java.io.IOException; -import java.net.URI; -import java.net.URISyntaxException; -import java.net.URL; - -import org.junit.Assert; -import org.junit.BeforeClass; -import org.junit.Ignore; -import org.junit.Test; -import org.w3c.dom.Document; -import org.w3c.dom.NodeList; -import org.xml.sax.SAXException; - -/** - * Verifies the behavior of the URIUtils class. - */ -public class VerifyURIUtils { - - public VerifyURIUtils() { - } - - @BeforeClass - public static void setUpClass() { - } - - @Ignore - @Test - // comment out @Ignore to run test (requires network connection) - public void resolveHttpUriAsDocument() throws SAXException, IOException { - URI uriRef = URI.create("http://www.w3schools.com/xml/note.xml"); - Document doc = URIUtils.parseURI(uriRef); - Assert.assertNotNull(doc); - Assert.assertEquals("Document element has unexpected [local name].", - "note", doc.getDocumentElement().getLocalName()); - } - - @Ignore - @Test - // comment out @Ignore to run test (requires network connection) - public void resolveHttpUriAsFile() throws SAXException, IOException { - URI uriRef = URI.create("http://www.w3schools.com/xml/note.xml"); - File file = URIUtils.dereferenceURI(uriRef); - Assert.assertNotNull(file); - Assert.assertTrue("File should not be empty", file.length() > 0); - } - - @Test - public void resolveClasspathResource() throws SAXException, IOException, - URISyntaxException { - URL url = this.getClass().getResource("/atom-feed.xml"); - Document doc = URIUtils.parseURI(url.toURI()); - Assert.assertNotNull(doc); - Assert.assertEquals("Document element has unexpected [local name].", - "feed", doc.getDocumentElement().getLocalName()); - } - - @Test - public void resolveFileRefWithXInclude() throws SAXException, IOException, - URISyntaxException { - File file = new File("src/test/resources/Alpha-xinclude.xml"); - Document doc = URIUtils.parseURI(file.toURI()); - Assert.assertNotNull(doc); - Assert.assertEquals("Document element has unexpected [local name].", - "Alpha", doc.getDocumentElement().getLocalName()); - NodeList nodes = doc.getDocumentElement().getElementsByTagNameNS( - "http://www.example.net/gamma", "Gamma"); - Assert.assertEquals( - "Expected element {http://www.example.net/gamma}Gamma", 1, - nodes.getLength()); - } - - @Test(expected = IllegalArgumentException.class) - public void resolveMissingClasspathResource() throws SAXException, - URISyntaxException, IOException { - URL url = this.getClass().getResource("/alpha.xml"); - URI uri = (null != url) ? url.toURI() : null; - Document doc = URIUtils.parseURI(uri); - Assert.assertNull(doc); - } -} diff --git a/src/test/java/org/opengis/cite/ogcapiprocesses10/util/VerifyValidationUtils.java b/src/test/java/org/opengis/cite/ogcapiprocesses10/util/VerifyValidationUtils.java deleted file mode 100644 index ab8e058..0000000 --- a/src/test/java/org/opengis/cite/ogcapiprocesses10/util/VerifyValidationUtils.java +++ /dev/null @@ -1,43 +0,0 @@ -package org.opengis.cite.ogcapiprocesses10.util; - -import static org.junit.Assert.*; - -import java.io.File; -import java.io.FileNotFoundException; -import java.net.URI; -import java.util.Set; - -import javax.xml.stream.XMLStreamException; -import javax.xml.transform.stream.StreamSource; - -import org.junit.Test; -import org.opengis.cite.validation.SchematronValidator; - -/** - * Verifies the behavior of the ValidationUtils class. - */ -public class VerifyValidationUtils { - - public VerifyValidationUtils() { - } - - @Test - public void testBuildSchematronValidator() { - String schemaRef = "http://schemas.opengis.net/gml/3.2.1/SchematronConstraints.xml"; - String phase = ""; - SchematronValidator result = ValidationUtils.buildSchematronValidator( - schemaRef, phase); - assertNotNull(result); - } - - @Test - public void extractRelativeSchemaReference() throws FileNotFoundException, - XMLStreamException { - File xmlFile = new File("src/test/resources/Alpha-1.xml"); - Set xsdSet = ValidationUtils.extractSchemaReferences( - new StreamSource(xmlFile), null); - URI schemaURI = xsdSet.iterator().next(); - assertTrue("Expected schema reference */xsd/alpha.xsd", schemaURI - .toString().endsWith("/xsd/alpha.xsd")); - } -} diff --git a/src/test/java/org/opengis/cite/ogcapiprocesses10/util/VerifyXMLUtils.java b/src/test/java/org/opengis/cite/ogcapiprocesses10/util/VerifyXMLUtils.java deleted file mode 100644 index 5856e80..0000000 --- a/src/test/java/org/opengis/cite/ogcapiprocesses10/util/VerifyXMLUtils.java +++ /dev/null @@ -1,147 +0,0 @@ -package org.opengis.cite.ogcapiprocesses10.util; - -import java.io.IOException; -import java.util.HashMap; -import java.util.Map; -import javax.xml.namespace.QName; -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.transform.dom.DOMSource; -import javax.xml.xpath.XPathExpressionException; -import net.sf.saxon.s9api.SaxonApiException; -import net.sf.saxon.s9api.XdmValue; -import net.sf.saxon.trans.XPathException; - -import org.junit.Assert; -import org.junit.BeforeClass; -import org.junit.Test; -import org.w3c.dom.Document; -import org.w3c.dom.Element; -import org.w3c.dom.Node; -import org.w3c.dom.NodeList; -import org.xml.sax.SAXException; - -/** - * Verifies the behavior of the XMLUtils class. - */ -public class VerifyXMLUtils { - - private static final String ATOM_NS = "http://www.w3.org/2005/Atom"; - private static final String EX_NS = "http://example.org/ns1"; - private static DocumentBuilder docBuilder; - - public VerifyXMLUtils() { - } - - @BeforeClass - public static void setUpClass() throws Exception { - DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); - dbf.setNamespaceAware(true); - docBuilder = dbf.newDocumentBuilder(); - } - - @Test - public void writeDocToString() throws SAXException, IOException { - Document doc = docBuilder.parse(this.getClass().getResourceAsStream( - "/atom-feed.xml")); - String content = XMLUtils.writeNodeToString(doc); - Assert.assertTrue("String should start with ' nsBindings = new HashMap(); - nsBindings.put(ATOM_NS, "tns"); - nsBindings.put(EX_NS, "ns1"); - NodeList results = XMLUtils.evaluateXPath(doc, expr, nsBindings); - Assert.assertTrue("Expected 1 node in results.", - results.getLength() == 1); - Assert.assertEquals("author", results.item(0).getLocalName()); - } - - @Test - public void evaluateXPathExpression_noMatch() - throws XPathExpressionException, SAXException, IOException { - Document doc = docBuilder.parse(this.getClass().getResourceAsStream( - "/atom-feed.xml")); - String expr = "/tns:feed/tns:author[ns1:blog]"; - Map nsBindings = new HashMap(); - nsBindings.put(ATOM_NS, "tns"); - nsBindings.put(EX_NS, "ns1"); - NodeList results = XMLUtils.evaluateXPath(doc, expr, nsBindings); - Assert.assertTrue("Expected empty results.", results.getLength() == 0); - } - - @Test(expected = XPathExpressionException.class) - public void evaluateXPathExpression_booleanResult() - throws XPathExpressionException, SAXException, IOException { - Document doc = docBuilder.parse(this.getClass().getResourceAsStream( - "/atom-feed.xml")); - String expr = "count(//tns:entry) > 0"; - Map nsBindings = new HashMap(); - nsBindings.put(ATOM_NS, "tns"); - NodeList results = XMLUtils.evaluateXPath(doc, expr, nsBindings); - Assert.assertNull(results); - } - - @Test - public void createElement_Alpha() { - QName qName = new QName("http://example.org", "Alpha"); - Element elem = XMLUtils.createElement(qName); - Assert.assertEquals("Alpha", elem.getLocalName()); - Assert.assertNull(elem.getParentNode()); - Assert.assertNotNull(elem.getOwnerDocument()); - } - - @Test - public void evaluateXPath2ExpressionAgainstDocument() throws SAXException, - IOException, SaxonApiException, XPathException { - Document doc = docBuilder.parse(this.getClass().getResourceAsStream( - "/atom-feed.xml")); - String expr = "matches(//tns:entry/tns:title, '.*Robots')"; - Map nsBindings = new HashMap(); - nsBindings.put(ATOM_NS, "tns"); - XdmValue result = XMLUtils.evaluateXPath2(new DOMSource(doc), expr, - nsBindings); - Assert.assertTrue("Expected non-empty result.", result.size() > 0); - Assert.assertEquals("Result has unexpected string value.", "true", - result.getUnderlyingValue().getStringValue()); - } - - @Test - public void evaluateXPath2ExpressionAgainstElement() throws SAXException, - IOException, SaxonApiException, XPathException { - Document doc = docBuilder.parse(this.getClass().getResourceAsStream( - "/atom-feed.xml")); - Node entry = doc.getElementsByTagNameNS(ATOM_NS, "entry").item(0); - String expr = "matches(tns:title, '.*Robots')"; - Map nsBindings = new HashMap(); - nsBindings.put(ATOM_NS, "tns"); - XdmValue result = XMLUtils.evaluateXPath2(new DOMSource(entry), expr, - nsBindings); - Assert.assertTrue("Expected non-empty result.", result.size() > 0); - Assert.assertEquals("Result has unexpected string value.", "true", - result.getUnderlyingValue().getStringValue()); - } - - @Test - public void expandCharacterEntity() { - String text = "Ce n'est pas"; - String result = XMLUtils.expandReferencesInText(text); - Assert.assertTrue("Expected result to contain an apostrophe (')", - result.contains("'")); - } - - @Test - public void expandNumericCharacterReference() { - String text = "Montréal"; - String result = XMLUtils.expandReferencesInText(text); - Assert.assertEquals("Expected result to contain character é (U+00E9)", - "Montréal", result); - } -}