All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.opengis.cite.ogcapiprocesses10.openapi3.OpenApiUtils Maven / Gradle / Ivy

There is a newer version: 1.2
Show newest version
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.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 org.glassfish.jersey.uri.UriTemplate;
import org.glassfish.jersey.uri.internal.UriTemplateParser;

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;

/**
 * 

* OpenApiUtils class. *

* * @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()); } /** *

* retrieveParameterByName. *

* @param collectionItemPath a {@link java.lang.String} object * @param apiModel a {@link com.reprezen.kaizen.oasparser.model3.OpenApi3} object * @param name a {@link java.lang.String} object * @return a {@link com.reprezen.kaizen.oasparser.model3.Parameter} object */ 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; } /** *

* isFreeFormParameterSupportedForCollection. *

* @param apiModel a {@link com.reprezen.kaizen.oasparser.model3.OpenApi3} object * @param collectionName a {@link java.lang.String} object * @return a boolean */ 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; } /** *

* isParameterSupportedForCollection. *

* @param apiModel a {@link com.reprezen.kaizen.oasparser.model3.OpenApi3} object * @param collectionName a {@link java.lang.String} object * @param queryParam a {@link java.lang.String} object * @return a boolean */ 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; } } }