From cbe65842ce9fce9e193df828fa3d44b464f0906d Mon Sep 17 00:00:00 2001 From: Sara Aspery Date: Thu, 9 Nov 2017 11:14:32 +0000 Subject: [PATCH 01/12] Yaml unit test --- .../rm/rest/api/impl/RMYamlUnitTest.java | 138 ++++++++++++++++++ 1 file changed, 138 insertions(+) create mode 100644 rm-community/rm-community-repo/unit-test/java/org/alfresco/rm/rest/api/impl/RMYamlUnitTest.java diff --git a/rm-community/rm-community-repo/unit-test/java/org/alfresco/rm/rest/api/impl/RMYamlUnitTest.java b/rm-community/rm-community-repo/unit-test/java/org/alfresco/rm/rest/api/impl/RMYamlUnitTest.java new file mode 100644 index 0000000000..c06741e97b --- /dev/null +++ b/rm-community/rm-community-repo/unit-test/java/org/alfresco/rm/rest/api/impl/RMYamlUnitTest.java @@ -0,0 +1,138 @@ +/* + * #%L + * Alfresco Records Management Module + * %% + * Copyright (C) 2005 - 2017 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * - + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * - + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * - + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * - + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + * #L% + */ + +package org.alfresco.rm.rest.api.impl; + +import static junit.framework.TestCase.assertFalse; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.HashSet; +import java.util.Set; +import java.util.regex.Pattern; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; +import com.github.fge.jackson.JsonLoader; +import com.github.fge.jsonschema.core.exceptions.ProcessingException; +import com.github.fge.jsonschema.core.report.ProcessingReport; +import com.github.fge.jsonschema.main.JsonSchema; +import com.github.fge.jsonschema.main.JsonSchemaFactory; + +import io.swagger.models.Swagger; +import io.swagger.parser.SwaggerParser; +import org.alfresco.module.org_alfresco_module_rm.test.util.BaseUnitTest; +import org.junit.Test; +import org.reflections.Reflections; +import org.reflections.scanners.ResourcesScanner; + +/** + * Unit Test class for RM Yaml file validation. + * + * @author Sara Aspery + * @since 2.6 + * + */ +public class RMYamlUnitTest extends BaseUnitTest +{ + private static String SWAGGER_2_SCHEMA = "C:\\Users\\saspery\\Documents\\iWeek\\schema.json"; + private static String OPEN_API_SPECIFICATION = "2.0"; + + @Test + public void validateYamlFile() throws Exception + { + final JsonSchema swaggerSchema = getSwaggerSchema(SWAGGER_2_SCHEMA); + + final Set yamlFileNames = getYamlFileNames(); + assertFalse(yamlFileNames.isEmpty()); + + for (String yamlFilePath : yamlFileNames) + { + // check the yaml file is valid against Swagger JSON schema + assertTrue(validateYamlFile(yamlFilePath, swaggerSchema)); + + // check we can read the swagger object for the swagger version + Swagger swagger = new SwaggerParser().read(yamlFilePath); + assertEquals(swagger.getSwagger(), OPEN_API_SPECIFICATION); + } + } + + private JsonSchema getSwaggerSchema(final String filePath) throws IOException, ProcessingException + { + final String swaggerSchema = new String(Files.readAllBytes(Paths.get(filePath))); + final JsonNode schemaNode = JsonLoader.fromString(swaggerSchema); + final JsonSchemaFactory factory = JsonSchemaFactory.byDefault(); + return factory.getJsonSchema(schemaNode); + } + + /** + * Helper method to return a list of Yaml filenames + */ + private Set getYamlFileNames() + { + /* + String basePackageName = "org.alfresco.module.org_alfresco_module_rm"; + Reflections reflections = new Reflections(basePackageName, new ResourcesScanner()); + Set yamlFileNames = reflections.getResources(Pattern.compile("\\*")); + + String s = this.getClass().getClassLoader().getResource("").getPath(); + */ + Set yamlFileNames = new HashSet<>(); + yamlFileNames.add("C:\\dev5\\records-management\\rm-community\\rm-community-rest-api-explorer\\src\\main\\webapp\\definitions\\gs-core-api.yaml"); + yamlFileNames.add("C:\\dev5\\records-management\\rm-enterprise\\rm-enterprise-rest-api-explorer\\src\\main\\webapp\\definitions\\ig-classification-api.yaml"); + //yamlFileNames.add("C:\\dev5\\records-management\\rm-community\\rm-community-rest-api-explorer\\src\\main\\webapp\\definitions\\gs-core-api02.yaml"); + + return yamlFileNames; + } + + private boolean validateYamlFile(final String swaggerFilePath, final JsonSchema jsonSchema) throws IOException, ProcessingException + { + // Get yaml string and convert to JSON string + final String yaml = new String(Files.readAllBytes(Paths.get(swaggerFilePath))); + final ObjectMapper yamlReader = new ObjectMapper(new YAMLFactory()); + final Object obj = yamlReader.readValue(yaml, Object.class); + final ObjectMapper jsonWriter = new ObjectMapper(); + final String yamlAsJson = jsonWriter.writeValueAsString(obj); + + return validateJSON(yamlAsJson, jsonSchema); + } + + /** + * Helper method to validate JSON string against JSON schema + */ + private boolean validateJSON(final String jsonData, final JsonSchema schema) throws IOException, ProcessingException + { + final JsonNode dataNode = JsonLoader.fromString(jsonData); + final ProcessingReport report = schema.validate(dataNode); + return report.isSuccess(); + } +} + From 956ecce5691c15677bb3383f27bf55d60e24de19 Mon Sep 17 00:00:00 2001 From: Sara Aspery Date: Mon, 13 Nov 2017 15:01:13 +0000 Subject: [PATCH 02/12] Yaml unit test --- rm-community/rm-community-repo/pom.xml | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/rm-community/rm-community-repo/pom.xml b/rm-community/rm-community-repo/pom.xml index 4a59bbbdb7..8821fed9f7 100644 --- a/rm-community/rm-community-repo/pom.xml +++ b/rm-community/rm-community-repo/pom.xml @@ -378,6 +378,30 @@ 0.9.10 test + + + org.slf4j + slf4j-api + 1.7.2 + + + + io.swagger + swagger-parser + 1.0.23 + + + + com.github.fge + json-schema-validator + 2.2.6 + + + com.google.guava + guava + 18.0 + + From 4181fe4884639d040f4e1138889eefe30b326df4 Mon Sep 17 00:00:00 2001 From: Sara Aspery Date: Tue, 14 Nov 2017 09:24:48 +0000 Subject: [PATCH 03/12] Yaml unit test - split community and enterprise tests --- .../test/util/BaseYamlUnitTest.java | 108 ++ .../rm/rest/api/impl/RMYamlUnitTest.java | 98 +- .../unit-test/resources/rest/schema.json | 1607 +++++++++++++++++ 3 files changed, 1720 insertions(+), 93 deletions(-) create mode 100644 rm-community/rm-community-repo/unit-test/java/org/alfresco/module/org_alfresco_module_rm/test/util/BaseYamlUnitTest.java create mode 100644 rm-community/rm-community-repo/unit-test/resources/rest/schema.json diff --git a/rm-community/rm-community-repo/unit-test/java/org/alfresco/module/org_alfresco_module_rm/test/util/BaseYamlUnitTest.java b/rm-community/rm-community-repo/unit-test/java/org/alfresco/module/org_alfresco_module_rm/test/util/BaseYamlUnitTest.java new file mode 100644 index 0000000000..2c776b038f --- /dev/null +++ b/rm-community/rm-community-repo/unit-test/java/org/alfresco/module/org_alfresco_module_rm/test/util/BaseYamlUnitTest.java @@ -0,0 +1,108 @@ +package org.alfresco.module.org_alfresco_module_rm.test.util; + +import static junit.framework.TestCase.assertFalse; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; +import com.github.fge.jackson.JsonLoader; +import com.github.fge.jsonschema.core.exceptions.ProcessingException; +import com.github.fge.jsonschema.core.report.ProcessingReport; +import com.github.fge.jsonschema.main.JsonSchema; +import com.github.fge.jsonschema.main.JsonSchemaFactory; + +import io.swagger.models.Swagger; +import io.swagger.parser.SwaggerParser; +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.IOUtils; +import org.apache.commons.io.filefilter.WildcardFileFilter; + + +/** + * Unit tests for Yaml files. + * + * @author Sara Aspery + * @since 2.6 + */ +public class BaseYamlUnitTest +{ + private static String SWAGGER_2_SCHEMA_LOCATION = "/rest/schema.json"; + private static String OPEN_API_SPECIFICATION = "2.0"; + + protected Set getYamlFilesList(String pathName) throws Exception + { + Set yamlFilePathNames = new HashSet<>(); + File directory = new File(pathName); + Collection yamlFiles = FileUtils.listFiles(directory, new WildcardFileFilter("*.yaml"), null); + for (File file : yamlFiles) { + yamlFilePathNames.add(file.getCanonicalPath()); + } + return yamlFilePathNames; + } + + protected void validateYamlFiles(final Set yamlFileNames) throws Exception + { + assertFalse("Expected at least 1 yaml file to validate", yamlFileNames.isEmpty()); + + final JsonSchema swaggerSchema = getSwaggerSchema(SWAGGER_2_SCHEMA_LOCATION); + assertNotNull("Failed to obtain the Swagger schema", swaggerSchema); + + for (String yamlFilePath : yamlFileNames) + { + // check the yaml file is valid against Swagger JSON schema + assertTrue("Yaml file is not a valid Swagger file", validateYamlFile(yamlFilePath, swaggerSchema)); + + // check we can read the swagger object for the swagger version + Swagger swagger = new SwaggerParser().read(yamlFilePath); + assertEquals("Failed to obtain Swagger version from yaml file", swagger.getSwagger(), OPEN_API_SPECIFICATION); + } + } + + private JsonSchema getSwaggerSchema(final String schemaLocation) throws IOException, ProcessingException + { + JsonSchema swaggerSchema = null; + final InputStream in = this.getClass().getResourceAsStream(schemaLocation); + if (in != null) + { + final String swaggerSchemaAsString = IOUtils.toString(in); + final JsonNode schemaNode = JsonLoader.fromString(swaggerSchemaAsString); + final JsonSchemaFactory factory = JsonSchemaFactory.byDefault(); + swaggerSchema = factory.getJsonSchema(schemaNode); + } + return swaggerSchema; + } + + private boolean validateYamlFile(final String yamlFilePath, final JsonSchema jsonSchema) throws IOException, ProcessingException + { + // Get yaml string and convert to JSON string + final String yaml = new String(Files.readAllBytes(Paths.get(yamlFilePath))); + final ObjectMapper yamlReader = new ObjectMapper(new YAMLFactory()); + final Object obj = yamlReader.readValue(yaml, Object.class); + final ObjectMapper jsonWriter = new ObjectMapper(); + final String yamlAsJson = jsonWriter.writeValueAsString(obj); + + return validateJSON(yamlAsJson, jsonSchema); + } + + /** + * Helper method to validate JSON string against JSON schema + */ + private boolean validateJSON(final String jsonData, final JsonSchema schema) throws IOException, ProcessingException + { + final JsonNode dataNode = JsonLoader.fromString(jsonData); + final ProcessingReport report = schema.validate(dataNode); + return report.isSuccess(); + } +} diff --git a/rm-community/rm-community-repo/unit-test/java/org/alfresco/rm/rest/api/impl/RMYamlUnitTest.java b/rm-community/rm-community-repo/unit-test/java/org/alfresco/rm/rest/api/impl/RMYamlUnitTest.java index c06741e97b..669fe4e5b2 100644 --- a/rm-community/rm-community-repo/unit-test/java/org/alfresco/rm/rest/api/impl/RMYamlUnitTest.java +++ b/rm-community/rm-community-repo/unit-test/java/org/alfresco/rm/rest/api/impl/RMYamlUnitTest.java @@ -27,32 +27,8 @@ package org.alfresco.rm.rest.api.impl; -import static junit.framework.TestCase.assertFalse; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.util.HashSet; -import java.util.Set; -import java.util.regex.Pattern; - -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; -import com.github.fge.jackson.JsonLoader; -import com.github.fge.jsonschema.core.exceptions.ProcessingException; -import com.github.fge.jsonschema.core.report.ProcessingReport; -import com.github.fge.jsonschema.main.JsonSchema; -import com.github.fge.jsonschema.main.JsonSchemaFactory; - -import io.swagger.models.Swagger; -import io.swagger.parser.SwaggerParser; -import org.alfresco.module.org_alfresco_module_rm.test.util.BaseUnitTest; +import org.alfresco.module.org_alfresco_module_rm.test.util.BaseYamlUnitTest; import org.junit.Test; -import org.reflections.Reflections; -import org.reflections.scanners.ResourcesScanner; /** * Unit Test class for RM Yaml file validation. @@ -61,78 +37,14 @@ import org.reflections.scanners.ResourcesScanner; * @since 2.6 * */ -public class RMYamlUnitTest extends BaseUnitTest +public class RMYamlUnitTest extends BaseYamlUnitTest { - private static String SWAGGER_2_SCHEMA = "C:\\Users\\saspery\\Documents\\iWeek\\schema.json"; - private static String OPEN_API_SPECIFICATION = "2.0"; - + private static String RM_COMMUNITY_YAML_FILES_PATH = "/dev6/records-management/rm-community/rm-community-rest-api-explorer/src/main/webapp/definitions"; + @Test public void validateYamlFile() throws Exception { - final JsonSchema swaggerSchema = getSwaggerSchema(SWAGGER_2_SCHEMA); - - final Set yamlFileNames = getYamlFileNames(); - assertFalse(yamlFileNames.isEmpty()); - - for (String yamlFilePath : yamlFileNames) - { - // check the yaml file is valid against Swagger JSON schema - assertTrue(validateYamlFile(yamlFilePath, swaggerSchema)); - - // check we can read the swagger object for the swagger version - Swagger swagger = new SwaggerParser().read(yamlFilePath); - assertEquals(swagger.getSwagger(), OPEN_API_SPECIFICATION); - } - } - - private JsonSchema getSwaggerSchema(final String filePath) throws IOException, ProcessingException - { - final String swaggerSchema = new String(Files.readAllBytes(Paths.get(filePath))); - final JsonNode schemaNode = JsonLoader.fromString(swaggerSchema); - final JsonSchemaFactory factory = JsonSchemaFactory.byDefault(); - return factory.getJsonSchema(schemaNode); - } - - /** - * Helper method to return a list of Yaml filenames - */ - private Set getYamlFileNames() - { - /* - String basePackageName = "org.alfresco.module.org_alfresco_module_rm"; - Reflections reflections = new Reflections(basePackageName, new ResourcesScanner()); - Set yamlFileNames = reflections.getResources(Pattern.compile("\\*")); - - String s = this.getClass().getClassLoader().getResource("").getPath(); - */ - Set yamlFileNames = new HashSet<>(); - yamlFileNames.add("C:\\dev5\\records-management\\rm-community\\rm-community-rest-api-explorer\\src\\main\\webapp\\definitions\\gs-core-api.yaml"); - yamlFileNames.add("C:\\dev5\\records-management\\rm-enterprise\\rm-enterprise-rest-api-explorer\\src\\main\\webapp\\definitions\\ig-classification-api.yaml"); - //yamlFileNames.add("C:\\dev5\\records-management\\rm-community\\rm-community-rest-api-explorer\\src\\main\\webapp\\definitions\\gs-core-api02.yaml"); - - return yamlFileNames; - } - - private boolean validateYamlFile(final String swaggerFilePath, final JsonSchema jsonSchema) throws IOException, ProcessingException - { - // Get yaml string and convert to JSON string - final String yaml = new String(Files.readAllBytes(Paths.get(swaggerFilePath))); - final ObjectMapper yamlReader = new ObjectMapper(new YAMLFactory()); - final Object obj = yamlReader.readValue(yaml, Object.class); - final ObjectMapper jsonWriter = new ObjectMapper(); - final String yamlAsJson = jsonWriter.writeValueAsString(obj); - - return validateJSON(yamlAsJson, jsonSchema); - } - - /** - * Helper method to validate JSON string against JSON schema - */ - private boolean validateJSON(final String jsonData, final JsonSchema schema) throws IOException, ProcessingException - { - final JsonNode dataNode = JsonLoader.fromString(jsonData); - final ProcessingReport report = schema.validate(dataNode); - return report.isSuccess(); + validateYamlFiles(getYamlFilesList(RM_COMMUNITY_YAML_FILES_PATH)); } } diff --git a/rm-community/rm-community-repo/unit-test/resources/rest/schema.json b/rm-community/rm-community-repo/unit-test/resources/rest/schema.json new file mode 100644 index 0000000000..f12a8c0e47 --- /dev/null +++ b/rm-community/rm-community-repo/unit-test/resources/rest/schema.json @@ -0,0 +1,1607 @@ +{ + "title": "A JSON Schema for Swagger 2.0 API.", + "id": "http://swagger.io/v2/schema.json#", + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "required": [ + "swagger", + "info", + "paths" + ], + "additionalProperties": false, + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + }, + "properties": { + "swagger": { + "type": "string", + "enum": [ + "2.0" + ], + "description": "The Swagger version of this document." + }, + "info": { + "$ref": "#/definitions/info" + }, + "host": { + "type": "string", + "pattern": "^[^{}/ :\\\\]+(?::\\d+)?$", + "description": "The host (name or ip) of the API. Example: 'swagger.io'" + }, + "basePath": { + "type": "string", + "pattern": "^/", + "description": "The base path to the API. Example: '/api'." + }, + "schemes": { + "$ref": "#/definitions/schemesList" + }, + "consumes": { + "description": "A list of MIME types accepted by the API.", + "allOf": [ + { + "$ref": "#/definitions/mediaTypeList" + } + ] + }, + "produces": { + "description": "A list of MIME types the API can produce.", + "allOf": [ + { + "$ref": "#/definitions/mediaTypeList" + } + ] + }, + "paths": { + "$ref": "#/definitions/paths" + }, + "definitions": { + "$ref": "#/definitions/definitions" + }, + "parameters": { + "$ref": "#/definitions/parameterDefinitions" + }, + "responses": { + "$ref": "#/definitions/responseDefinitions" + }, + "security": { + "$ref": "#/definitions/security" + }, + "securityDefinitions": { + "$ref": "#/definitions/securityDefinitions" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/definitions/tag" + }, + "uniqueItems": true + }, + "externalDocs": { + "$ref": "#/definitions/externalDocs" + } + }, + "definitions": { + "info": { + "type": "object", + "description": "General information about the API.", + "required": [ + "version", + "title" + ], + "additionalProperties": false, + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + }, + "properties": { + "title": { + "type": "string", + "description": "A unique and precise title of the API." + }, + "version": { + "type": "string", + "description": "A semantic version number of the API." + }, + "description": { + "type": "string", + "description": "A longer description of the API. Should be different from the title. GitHub Flavored Markdown is allowed." + }, + "termsOfService": { + "type": "string", + "description": "The terms of service for the API." + }, + "contact": { + "$ref": "#/definitions/contact" + }, + "license": { + "$ref": "#/definitions/license" + } + } + }, + "contact": { + "type": "object", + "description": "Contact information for the owners of the API.", + "additionalProperties": false, + "properties": { + "name": { + "type": "string", + "description": "The identifying name of the contact person/organization." + }, + "url": { + "type": "string", + "description": "The URL pointing to the contact information.", + "format": "uri" + }, + "email": { + "type": "string", + "description": "The email address of the contact person/organization.", + "format": "email" + } + }, + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + } + }, + "license": { + "type": "object", + "required": [ + "name" + ], + "additionalProperties": false, + "properties": { + "name": { + "type": "string", + "description": "The name of the license type. It's encouraged to use an OSI compatible license." + }, + "url": { + "type": "string", + "description": "The URL pointing to the license.", + "format": "uri" + } + }, + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + } + }, + "paths": { + "type": "object", + "description": "Relative paths to the individual endpoints. They must be relative to the 'basePath'.", + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + }, + "^/": { + "$ref": "#/definitions/pathItem" + } + }, + "additionalProperties": false + }, + "definitions": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/schema" + }, + "description": "One or more JSON objects describing the schemas being consumed and produced by the API." + }, + "parameterDefinitions": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/parameter" + }, + "description": "One or more JSON representations for parameters" + }, + "responseDefinitions": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/response" + }, + "description": "One or more JSON representations for parameters" + }, + "externalDocs": { + "type": "object", + "additionalProperties": false, + "description": "information about external documentation", + "required": [ + "url" + ], + "properties": { + "description": { + "type": "string" + }, + "url": { + "type": "string", + "format": "uri" + } + }, + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + } + }, + "examples": { + "type": "object", + "additionalProperties": true + }, + "mimeType": { + "type": "string", + "description": "The MIME type of the HTTP message." + }, + "operation": { + "type": "object", + "required": [ + "responses" + ], + "additionalProperties": false, + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + }, + "properties": { + "tags": { + "type": "array", + "items": { + "type": "string" + }, + "uniqueItems": true + }, + "summary": { + "type": "string", + "description": "A brief summary of the operation." + }, + "description": { + "type": "string", + "description": "A longer description of the operation, GitHub Flavored Markdown is allowed." + }, + "externalDocs": { + "$ref": "#/definitions/externalDocs" + }, + "operationId": { + "type": "string", + "description": "A unique identifier of the operation." + }, + "produces": { + "description": "A list of MIME types the API can produce.", + "allOf": [ + { + "$ref": "#/definitions/mediaTypeList" + } + ] + }, + "consumes": { + "description": "A list of MIME types the API can consume.", + "allOf": [ + { + "$ref": "#/definitions/mediaTypeList" + } + ] + }, + "parameters": { + "$ref": "#/definitions/parametersList" + }, + "responses": { + "$ref": "#/definitions/responses" + }, + "schemes": { + "$ref": "#/definitions/schemesList" + }, + "deprecated": { + "type": "boolean", + "default": false + }, + "security": { + "$ref": "#/definitions/security" + } + } + }, + "pathItem": { + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + }, + "properties": { + "$ref": { + "type": "string" + }, + "get": { + "$ref": "#/definitions/operation" + }, + "put": { + "$ref": "#/definitions/operation" + }, + "post": { + "$ref": "#/definitions/operation" + }, + "delete": { + "$ref": "#/definitions/operation" + }, + "options": { + "$ref": "#/definitions/operation" + }, + "head": { + "$ref": "#/definitions/operation" + }, + "patch": { + "$ref": "#/definitions/operation" + }, + "parameters": { + "$ref": "#/definitions/parametersList" + } + } + }, + "responses": { + "type": "object", + "description": "Response objects names can either be any valid HTTP status code or 'default'.", + "minProperties": 1, + "additionalProperties": false, + "patternProperties": { + "^([0-9]{3})$|^(default)$": { + "$ref": "#/definitions/responseValue" + }, + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + }, + "not": { + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + } + } + }, + "responseValue": { + "oneOf": [ + { + "$ref": "#/definitions/response" + }, + { + "$ref": "#/definitions/jsonReference" + } + ] + }, + "response": { + "type": "object", + "required": [ + "description" + ], + "properties": { + "description": { + "type": "string" + }, + "schema": { + "oneOf": [ + { + "$ref": "#/definitions/schema" + }, + { + "$ref": "#/definitions/fileSchema" + } + ] + }, + "headers": { + "$ref": "#/definitions/headers" + }, + "examples": { + "$ref": "#/definitions/examples" + } + }, + "additionalProperties": false, + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + } + }, + "headers": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/header" + } + }, + "header": { + "type": "object", + "additionalProperties": false, + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "string", + "number", + "integer", + "boolean", + "array" + ] + }, + "format": { + "type": "string" + }, + "items": { + "$ref": "#/definitions/primitivesItems" + }, + "collectionFormat": { + "$ref": "#/definitions/collectionFormat" + }, + "default": { + "$ref": "#/definitions/default" + }, + "maximum": { + "$ref": "#/definitions/maximum" + }, + "exclusiveMaximum": { + "$ref": "#/definitions/exclusiveMaximum" + }, + "minimum": { + "$ref": "#/definitions/minimum" + }, + "exclusiveMinimum": { + "$ref": "#/definitions/exclusiveMinimum" + }, + "maxLength": { + "$ref": "#/definitions/maxLength" + }, + "minLength": { + "$ref": "#/definitions/minLength" + }, + "pattern": { + "$ref": "#/definitions/pattern" + }, + "maxItems": { + "$ref": "#/definitions/maxItems" + }, + "minItems": { + "$ref": "#/definitions/minItems" + }, + "uniqueItems": { + "$ref": "#/definitions/uniqueItems" + }, + "enum": { + "$ref": "#/definitions/enum" + }, + "multipleOf": { + "$ref": "#/definitions/multipleOf" + }, + "description": { + "type": "string" + } + }, + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + } + }, + "vendorExtension": { + "description": "Any property starting with x- is valid.", + "additionalProperties": true, + "additionalItems": true + }, + "bodyParameter": { + "type": "object", + "required": [ + "name", + "in", + "schema" + ], + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + }, + "properties": { + "description": { + "type": "string", + "description": "A brief description of the parameter. This could contain examples of use. GitHub Flavored Markdown is allowed." + }, + "name": { + "type": "string", + "description": "The name of the parameter." + }, + "in": { + "type": "string", + "description": "Determines the location of the parameter.", + "enum": [ + "body" + ] + }, + "required": { + "type": "boolean", + "description": "Determines whether or not this parameter is required or optional.", + "default": false + }, + "schema": { + "$ref": "#/definitions/schema" + } + }, + "additionalProperties": false + }, + "headerParameterSubSchema": { + "additionalProperties": false, + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + }, + "properties": { + "required": { + "type": "boolean", + "description": "Determines whether or not this parameter is required or optional.", + "default": false + }, + "in": { + "type": "string", + "description": "Determines the location of the parameter.", + "enum": [ + "header" + ] + }, + "description": { + "type": "string", + "description": "A brief description of the parameter. This could contain examples of use. GitHub Flavored Markdown is allowed." + }, + "name": { + "type": "string", + "description": "The name of the parameter." + }, + "type": { + "type": "string", + "enum": [ + "string", + "number", + "boolean", + "integer", + "array" + ] + }, + "format": { + "type": "string" + }, + "items": { + "$ref": "#/definitions/primitivesItems" + }, + "collectionFormat": { + "$ref": "#/definitions/collectionFormat" + }, + "default": { + "$ref": "#/definitions/default" + }, + "maximum": { + "$ref": "#/definitions/maximum" + }, + "exclusiveMaximum": { + "$ref": "#/definitions/exclusiveMaximum" + }, + "minimum": { + "$ref": "#/definitions/minimum" + }, + "exclusiveMinimum": { + "$ref": "#/definitions/exclusiveMinimum" + }, + "maxLength": { + "$ref": "#/definitions/maxLength" + }, + "minLength": { + "$ref": "#/definitions/minLength" + }, + "pattern": { + "$ref": "#/definitions/pattern" + }, + "maxItems": { + "$ref": "#/definitions/maxItems" + }, + "minItems": { + "$ref": "#/definitions/minItems" + }, + "uniqueItems": { + "$ref": "#/definitions/uniqueItems" + }, + "enum": { + "$ref": "#/definitions/enum" + }, + "multipleOf": { + "$ref": "#/definitions/multipleOf" + } + } + }, + "queryParameterSubSchema": { + "additionalProperties": false, + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + }, + "properties": { + "required": { + "type": "boolean", + "description": "Determines whether or not this parameter is required or optional.", + "default": false + }, + "in": { + "type": "string", + "description": "Determines the location of the parameter.", + "enum": [ + "query" + ] + }, + "description": { + "type": "string", + "description": "A brief description of the parameter. This could contain examples of use. GitHub Flavored Markdown is allowed." + }, + "name": { + "type": "string", + "description": "The name of the parameter." + }, + "allowEmptyValue": { + "type": "boolean", + "default": false, + "description": "allows sending a parameter by name only or with an empty value." + }, + "type": { + "type": "string", + "enum": [ + "string", + "number", + "boolean", + "integer", + "array" + ] + }, + "format": { + "type": "string" + }, + "items": { + "$ref": "#/definitions/primitivesItems" + }, + "collectionFormat": { + "$ref": "#/definitions/collectionFormatWithMulti" + }, + "default": { + "$ref": "#/definitions/default" + }, + "maximum": { + "$ref": "#/definitions/maximum" + }, + "exclusiveMaximum": { + "$ref": "#/definitions/exclusiveMaximum" + }, + "minimum": { + "$ref": "#/definitions/minimum" + }, + "exclusiveMinimum": { + "$ref": "#/definitions/exclusiveMinimum" + }, + "maxLength": { + "$ref": "#/definitions/maxLength" + }, + "minLength": { + "$ref": "#/definitions/minLength" + }, + "pattern": { + "$ref": "#/definitions/pattern" + }, + "maxItems": { + "$ref": "#/definitions/maxItems" + }, + "minItems": { + "$ref": "#/definitions/minItems" + }, + "uniqueItems": { + "$ref": "#/definitions/uniqueItems" + }, + "enum": { + "$ref": "#/definitions/enum" + }, + "multipleOf": { + "$ref": "#/definitions/multipleOf" + } + } + }, + "formDataParameterSubSchema": { + "additionalProperties": false, + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + }, + "properties": { + "required": { + "type": "boolean", + "description": "Determines whether or not this parameter is required or optional.", + "default": false + }, + "in": { + "type": "string", + "description": "Determines the location of the parameter.", + "enum": [ + "formData" + ] + }, + "description": { + "type": "string", + "description": "A brief description of the parameter. This could contain examples of use. GitHub Flavored Markdown is allowed." + }, + "name": { + "type": "string", + "description": "The name of the parameter." + }, + "allowEmptyValue": { + "type": "boolean", + "default": false, + "description": "allows sending a parameter by name only or with an empty value." + }, + "type": { + "type": "string", + "enum": [ + "string", + "number", + "boolean", + "integer", + "array", + "file" + ] + }, + "format": { + "type": "string" + }, + "items": { + "$ref": "#/definitions/primitivesItems" + }, + "collectionFormat": { + "$ref": "#/definitions/collectionFormatWithMulti" + }, + "default": { + "$ref": "#/definitions/default" + }, + "maximum": { + "$ref": "#/definitions/maximum" + }, + "exclusiveMaximum": { + "$ref": "#/definitions/exclusiveMaximum" + }, + "minimum": { + "$ref": "#/definitions/minimum" + }, + "exclusiveMinimum": { + "$ref": "#/definitions/exclusiveMinimum" + }, + "maxLength": { + "$ref": "#/definitions/maxLength" + }, + "minLength": { + "$ref": "#/definitions/minLength" + }, + "pattern": { + "$ref": "#/definitions/pattern" + }, + "maxItems": { + "$ref": "#/definitions/maxItems" + }, + "minItems": { + "$ref": "#/definitions/minItems" + }, + "uniqueItems": { + "$ref": "#/definitions/uniqueItems" + }, + "enum": { + "$ref": "#/definitions/enum" + }, + "multipleOf": { + "$ref": "#/definitions/multipleOf" + } + } + }, + "pathParameterSubSchema": { + "additionalProperties": false, + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + }, + "required": [ + "required" + ], + "properties": { + "required": { + "type": "boolean", + "enum": [ + true + ], + "description": "Determines whether or not this parameter is required or optional." + }, + "in": { + "type": "string", + "description": "Determines the location of the parameter.", + "enum": [ + "path" + ] + }, + "description": { + "type": "string", + "description": "A brief description of the parameter. This could contain examples of use. GitHub Flavored Markdown is allowed." + }, + "name": { + "type": "string", + "description": "The name of the parameter." + }, + "type": { + "type": "string", + "enum": [ + "string", + "number", + "boolean", + "integer", + "array" + ] + }, + "format": { + "type": "string" + }, + "items": { + "$ref": "#/definitions/primitivesItems" + }, + "collectionFormat": { + "$ref": "#/definitions/collectionFormat" + }, + "default": { + "$ref": "#/definitions/default" + }, + "maximum": { + "$ref": "#/definitions/maximum" + }, + "exclusiveMaximum": { + "$ref": "#/definitions/exclusiveMaximum" + }, + "minimum": { + "$ref": "#/definitions/minimum" + }, + "exclusiveMinimum": { + "$ref": "#/definitions/exclusiveMinimum" + }, + "maxLength": { + "$ref": "#/definitions/maxLength" + }, + "minLength": { + "$ref": "#/definitions/minLength" + }, + "pattern": { + "$ref": "#/definitions/pattern" + }, + "maxItems": { + "$ref": "#/definitions/maxItems" + }, + "minItems": { + "$ref": "#/definitions/minItems" + }, + "uniqueItems": { + "$ref": "#/definitions/uniqueItems" + }, + "enum": { + "$ref": "#/definitions/enum" + }, + "multipleOf": { + "$ref": "#/definitions/multipleOf" + } + } + }, + "nonBodyParameter": { + "type": "object", + "required": [ + "name", + "in", + "type" + ], + "oneOf": [ + { + "$ref": "#/definitions/headerParameterSubSchema" + }, + { + "$ref": "#/definitions/formDataParameterSubSchema" + }, + { + "$ref": "#/definitions/queryParameterSubSchema" + }, + { + "$ref": "#/definitions/pathParameterSubSchema" + } + ] + }, + "parameter": { + "oneOf": [ + { + "$ref": "#/definitions/bodyParameter" + }, + { + "$ref": "#/definitions/nonBodyParameter" + } + ] + }, + "schema": { + "type": "object", + "description": "A deterministic version of a JSON Schema object.", + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + }, + "properties": { + "$ref": { + "type": "string" + }, + "format": { + "type": "string" + }, + "title": { + "$ref": "http://json-schema.org/draft-04/schema#/properties/title" + }, + "description": { + "$ref": "http://json-schema.org/draft-04/schema#/properties/description" + }, + "default": { + "$ref": "http://json-schema.org/draft-04/schema#/properties/default" + }, + "multipleOf": { + "$ref": "http://json-schema.org/draft-04/schema#/properties/multipleOf" + }, + "maximum": { + "$ref": "http://json-schema.org/draft-04/schema#/properties/maximum" + }, + "exclusiveMaximum": { + "$ref": "http://json-schema.org/draft-04/schema#/properties/exclusiveMaximum" + }, + "minimum": { + "$ref": "http://json-schema.org/draft-04/schema#/properties/minimum" + }, + "exclusiveMinimum": { + "$ref": "http://json-schema.org/draft-04/schema#/properties/exclusiveMinimum" + }, + "maxLength": { + "$ref": "http://json-schema.org/draft-04/schema#/definitions/positiveInteger" + }, + "minLength": { + "$ref": "http://json-schema.org/draft-04/schema#/definitions/positiveIntegerDefault0" + }, + "pattern": { + "$ref": "http://json-schema.org/draft-04/schema#/properties/pattern" + }, + "maxItems": { + "$ref": "http://json-schema.org/draft-04/schema#/definitions/positiveInteger" + }, + "minItems": { + "$ref": "http://json-schema.org/draft-04/schema#/definitions/positiveIntegerDefault0" + }, + "uniqueItems": { + "$ref": "http://json-schema.org/draft-04/schema#/properties/uniqueItems" + }, + "maxProperties": { + "$ref": "http://json-schema.org/draft-04/schema#/definitions/positiveInteger" + }, + "minProperties": { + "$ref": "http://json-schema.org/draft-04/schema#/definitions/positiveIntegerDefault0" + }, + "required": { + "$ref": "http://json-schema.org/draft-04/schema#/definitions/stringArray" + }, + "enum": { + "$ref": "http://json-schema.org/draft-04/schema#/properties/enum" + }, + "additionalProperties": { + "anyOf": [ + { + "$ref": "#/definitions/schema" + }, + { + "type": "boolean" + } + ], + "default": {} + }, + "type": { + "$ref": "http://json-schema.org/draft-04/schema#/properties/type" + }, + "items": { + "anyOf": [ + { + "$ref": "#/definitions/schema" + }, + { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/definitions/schema" + } + } + ], + "default": {} + }, + "allOf": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/definitions/schema" + } + }, + "properties": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/schema" + }, + "default": {} + }, + "discriminator": { + "type": "string" + }, + "readOnly": { + "type": "boolean", + "default": false + }, + "xml": { + "$ref": "#/definitions/xml" + }, + "externalDocs": { + "$ref": "#/definitions/externalDocs" + }, + "example": {} + }, + "additionalProperties": false + }, + "fileSchema": { + "type": "object", + "description": "A deterministic version of a JSON Schema object.", + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + }, + "required": [ + "type" + ], + "properties": { + "format": { + "type": "string" + }, + "title": { + "$ref": "http://json-schema.org/draft-04/schema#/properties/title" + }, + "description": { + "$ref": "http://json-schema.org/draft-04/schema#/properties/description" + }, + "default": { + "$ref": "http://json-schema.org/draft-04/schema#/properties/default" + }, + "required": { + "$ref": "http://json-schema.org/draft-04/schema#/definitions/stringArray" + }, + "type": { + "type": "string", + "enum": [ + "file" + ] + }, + "readOnly": { + "type": "boolean", + "default": false + }, + "externalDocs": { + "$ref": "#/definitions/externalDocs" + }, + "example": {} + }, + "additionalProperties": false + }, + "primitivesItems": { + "type": "object", + "additionalProperties": false, + "properties": { + "type": { + "type": "string", + "enum": [ + "string", + "number", + "integer", + "boolean", + "array" + ] + }, + "format": { + "type": "string" + }, + "items": { + "$ref": "#/definitions/primitivesItems" + }, + "collectionFormat": { + "$ref": "#/definitions/collectionFormat" + }, + "default": { + "$ref": "#/definitions/default" + }, + "maximum": { + "$ref": "#/definitions/maximum" + }, + "exclusiveMaximum": { + "$ref": "#/definitions/exclusiveMaximum" + }, + "minimum": { + "$ref": "#/definitions/minimum" + }, + "exclusiveMinimum": { + "$ref": "#/definitions/exclusiveMinimum" + }, + "maxLength": { + "$ref": "#/definitions/maxLength" + }, + "minLength": { + "$ref": "#/definitions/minLength" + }, + "pattern": { + "$ref": "#/definitions/pattern" + }, + "maxItems": { + "$ref": "#/definitions/maxItems" + }, + "minItems": { + "$ref": "#/definitions/minItems" + }, + "uniqueItems": { + "$ref": "#/definitions/uniqueItems" + }, + "enum": { + "$ref": "#/definitions/enum" + }, + "multipleOf": { + "$ref": "#/definitions/multipleOf" + } + }, + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + } + }, + "security": { + "type": "array", + "items": { + "$ref": "#/definitions/securityRequirement" + }, + "uniqueItems": true + }, + "securityRequirement": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + }, + "uniqueItems": true + } + }, + "xml": { + "type": "object", + "additionalProperties": false, + "properties": { + "name": { + "type": "string" + }, + "namespace": { + "type": "string" + }, + "prefix": { + "type": "string" + }, + "attribute": { + "type": "boolean", + "default": false + }, + "wrapped": { + "type": "boolean", + "default": false + } + }, + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + } + }, + "tag": { + "type": "object", + "additionalProperties": false, + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "externalDocs": { + "$ref": "#/definitions/externalDocs" + } + }, + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + } + }, + "securityDefinitions": { + "type": "object", + "additionalProperties": { + "oneOf": [ + { + "$ref": "#/definitions/basicAuthenticationSecurity" + }, + { + "$ref": "#/definitions/apiKeySecurity" + }, + { + "$ref": "#/definitions/oauth2ImplicitSecurity" + }, + { + "$ref": "#/definitions/oauth2PasswordSecurity" + }, + { + "$ref": "#/definitions/oauth2ApplicationSecurity" + }, + { + "$ref": "#/definitions/oauth2AccessCodeSecurity" + } + ] + } + }, + "basicAuthenticationSecurity": { + "type": "object", + "additionalProperties": false, + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "basic" + ] + }, + "description": { + "type": "string" + } + }, + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + } + }, + "apiKeySecurity": { + "type": "object", + "additionalProperties": false, + "required": [ + "type", + "name", + "in" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "apiKey" + ] + }, + "name": { + "type": "string" + }, + "in": { + "type": "string", + "enum": [ + "header", + "query" + ] + }, + "description": { + "type": "string" + } + }, + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + } + }, + "oauth2ImplicitSecurity": { + "type": "object", + "additionalProperties": false, + "required": [ + "type", + "flow", + "authorizationUrl" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "oauth2" + ] + }, + "flow": { + "type": "string", + "enum": [ + "implicit" + ] + }, + "scopes": { + "$ref": "#/definitions/oauth2Scopes" + }, + "authorizationUrl": { + "type": "string", + "format": "uri" + }, + "description": { + "type": "string" + } + }, + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + } + }, + "oauth2PasswordSecurity": { + "type": "object", + "additionalProperties": false, + "required": [ + "type", + "flow", + "tokenUrl" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "oauth2" + ] + }, + "flow": { + "type": "string", + "enum": [ + "password" + ] + }, + "scopes": { + "$ref": "#/definitions/oauth2Scopes" + }, + "tokenUrl": { + "type": "string", + "format": "uri" + }, + "description": { + "type": "string" + } + }, + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + } + }, + "oauth2ApplicationSecurity": { + "type": "object", + "additionalProperties": false, + "required": [ + "type", + "flow", + "tokenUrl" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "oauth2" + ] + }, + "flow": { + "type": "string", + "enum": [ + "application" + ] + }, + "scopes": { + "$ref": "#/definitions/oauth2Scopes" + }, + "tokenUrl": { + "type": "string", + "format": "uri" + }, + "description": { + "type": "string" + } + }, + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + } + }, + "oauth2AccessCodeSecurity": { + "type": "object", + "additionalProperties": false, + "required": [ + "type", + "flow", + "authorizationUrl", + "tokenUrl" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "oauth2" + ] + }, + "flow": { + "type": "string", + "enum": [ + "accessCode" + ] + }, + "scopes": { + "$ref": "#/definitions/oauth2Scopes" + }, + "authorizationUrl": { + "type": "string", + "format": "uri" + }, + "tokenUrl": { + "type": "string", + "format": "uri" + }, + "description": { + "type": "string" + } + }, + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + } + }, + "oauth2Scopes": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "mediaTypeList": { + "type": "array", + "items": { + "$ref": "#/definitions/mimeType" + }, + "uniqueItems": true + }, + "parametersList": { + "type": "array", + "description": "The parameters needed to send a valid API call.", + "additionalItems": false, + "items": { + "oneOf": [ + { + "$ref": "#/definitions/parameter" + }, + { + "$ref": "#/definitions/jsonReference" + } + ] + }, + "uniqueItems": true + }, + "schemesList": { + "type": "array", + "description": "The transfer protocol of the API.", + "items": { + "type": "string", + "enum": [ + "http", + "https", + "ws", + "wss" + ] + }, + "uniqueItems": true + }, + "collectionFormat": { + "type": "string", + "enum": [ + "csv", + "ssv", + "tsv", + "pipes" + ], + "default": "csv" + }, + "collectionFormatWithMulti": { + "type": "string", + "enum": [ + "csv", + "ssv", + "tsv", + "pipes", + "multi" + ], + "default": "csv" + }, + "title": { + "$ref": "http://json-schema.org/draft-04/schema#/properties/title" + }, + "description": { + "$ref": "http://json-schema.org/draft-04/schema#/properties/description" + }, + "default": { + "$ref": "http://json-schema.org/draft-04/schema#/properties/default" + }, + "multipleOf": { + "$ref": "http://json-schema.org/draft-04/schema#/properties/multipleOf" + }, + "maximum": { + "$ref": "http://json-schema.org/draft-04/schema#/properties/maximum" + }, + "exclusiveMaximum": { + "$ref": "http://json-schema.org/draft-04/schema#/properties/exclusiveMaximum" + }, + "minimum": { + "$ref": "http://json-schema.org/draft-04/schema#/properties/minimum" + }, + "exclusiveMinimum": { + "$ref": "http://json-schema.org/draft-04/schema#/properties/exclusiveMinimum" + }, + "maxLength": { + "$ref": "http://json-schema.org/draft-04/schema#/definitions/positiveInteger" + }, + "minLength": { + "$ref": "http://json-schema.org/draft-04/schema#/definitions/positiveIntegerDefault0" + }, + "pattern": { + "$ref": "http://json-schema.org/draft-04/schema#/properties/pattern" + }, + "maxItems": { + "$ref": "http://json-schema.org/draft-04/schema#/definitions/positiveInteger" + }, + "minItems": { + "$ref": "http://json-schema.org/draft-04/schema#/definitions/positiveIntegerDefault0" + }, + "uniqueItems": { + "$ref": "http://json-schema.org/draft-04/schema#/properties/uniqueItems" + }, + "enum": { + "$ref": "http://json-schema.org/draft-04/schema#/properties/enum" + }, + "jsonReference": { + "type": "object", + "required": [ + "$ref" + ], + "additionalProperties": false, + "properties": { + "$ref": { + "type": "string" + } + } + } + } +} \ No newline at end of file From 730f715dc1407195722e7addd1b5d5a690e02f69 Mon Sep 17 00:00:00 2001 From: Sara Aspery Date: Wed, 15 Nov 2017 16:36:13 +0000 Subject: [PATCH 04/12] RM-5875 fix path and add yaml name to exception --- .../test/util/BaseYamlUnitTest.java | 45 ++++++++++++++----- .../rm/rest/api/impl/RMYamlUnitTest.java | 2 +- 2 files changed, 35 insertions(+), 12 deletions(-) diff --git a/rm-community/rm-community-repo/unit-test/java/org/alfresco/module/org_alfresco_module_rm/test/util/BaseYamlUnitTest.java b/rm-community/rm-community-repo/unit-test/java/org/alfresco/module/org_alfresco_module_rm/test/util/BaseYamlUnitTest.java index 2c776b038f..ce474cbcfa 100644 --- a/rm-community/rm-community-repo/unit-test/java/org/alfresco/module/org_alfresco_module_rm/test/util/BaseYamlUnitTest.java +++ b/rm-community/rm-community-repo/unit-test/java/org/alfresco/module/org_alfresco_module_rm/test/util/BaseYamlUnitTest.java @@ -17,6 +17,7 @@ import java.util.Set; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; +import com.fasterxml.jackson.dataformat.yaml.snakeyaml.parser.ParserException; import com.github.fge.jackson.JsonLoader; import com.github.fge.jsonschema.core.exceptions.ProcessingException; import com.github.fge.jsonschema.core.report.ProcessingReport; @@ -29,7 +30,6 @@ import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; import org.apache.commons.io.filefilter.WildcardFileFilter; - /** * Unit tests for Yaml files. * @@ -41,6 +41,9 @@ public class BaseYamlUnitTest private static String SWAGGER_2_SCHEMA_LOCATION = "/rest/schema.json"; private static String OPEN_API_SPECIFICATION = "2.0"; + /** + * Helper method to obtain path names for all yaml files found on the given path + */ protected Set getYamlFilesList(String pathName) throws Exception { Set yamlFilePathNames = new HashSet<>(); @@ -52,7 +55,10 @@ public class BaseYamlUnitTest return yamlFilePathNames; } - protected void validateYamlFiles(final Set yamlFileNames) throws Exception + /** + * Helper method to validate that all given yaml files are valid readable Swagger format + */ + protected void validateYamlFiles(final Set yamlFileNames) throws ProcessingException, IOException { assertFalse("Expected at least 1 yaml file to validate", yamlFileNames.isEmpty()); @@ -61,15 +67,27 @@ public class BaseYamlUnitTest for (String yamlFilePath : yamlFileNames) { - // check the yaml file is valid against Swagger JSON schema - assertTrue("Yaml file is not a valid Swagger file", validateYamlFile(yamlFilePath, swaggerSchema)); + try + { + // check the yaml file is valid against Swagger JSON schema + assertTrue("Yaml file is not a valid Swagger file: " + yamlFilePath, validateYamlFile(yamlFilePath, swaggerSchema)); - // check we can read the swagger object for the swagger version - Swagger swagger = new SwaggerParser().read(yamlFilePath); - assertEquals("Failed to obtain Swagger version from yaml file", swagger.getSwagger(), OPEN_API_SPECIFICATION); + // check can read the swagger object to obtain the swagger version + Swagger swagger = new SwaggerParser().read(yamlFilePath); + assertEquals("Failed to obtain Swagger version from yaml file " + yamlFilePath, swagger.getSwagger(), OPEN_API_SPECIFICATION); + } + catch (ParserException ex) + { + // ensure the yaml filename is included in the message + String context = String.format(yamlFilePath + ": %n" + ex.getContext()); + throw new ParserException(context, ex.getContextMark(), ex.getProblem(), ex.getProblemMark()) ; + } } } + /** + * Helper method to read in the Swagger JSON schema file + */ private JsonSchema getSwaggerSchema(final String schemaLocation) throws IOException, ProcessingException { JsonSchema swaggerSchema = null; @@ -84,16 +102,21 @@ public class BaseYamlUnitTest return swaggerSchema; } + /** + * Helper method to validate Yaml file against JSON schema + */ private boolean validateYamlFile(final String yamlFilePath, final JsonSchema jsonSchema) throws IOException, ProcessingException { - // Get yaml string and convert to JSON string + // Get yaml file as a string final String yaml = new String(Files.readAllBytes(Paths.get(yamlFilePath))); + + // Convert yaml string to JSON string final ObjectMapper yamlReader = new ObjectMapper(new YAMLFactory()); final Object obj = yamlReader.readValue(yaml, Object.class); final ObjectMapper jsonWriter = new ObjectMapper(); - final String yamlAsJson = jsonWriter.writeValueAsString(obj); - - return validateJSON(yamlAsJson, jsonSchema); + final String yamlAsJsonString = jsonWriter.writeValueAsString(obj); + + return validateJSON(yamlAsJsonString, jsonSchema); } /** diff --git a/rm-community/rm-community-repo/unit-test/java/org/alfresco/rm/rest/api/impl/RMYamlUnitTest.java b/rm-community/rm-community-repo/unit-test/java/org/alfresco/rm/rest/api/impl/RMYamlUnitTest.java index 669fe4e5b2..e82c372d43 100644 --- a/rm-community/rm-community-repo/unit-test/java/org/alfresco/rm/rest/api/impl/RMYamlUnitTest.java +++ b/rm-community/rm-community-repo/unit-test/java/org/alfresco/rm/rest/api/impl/RMYamlUnitTest.java @@ -39,7 +39,7 @@ import org.junit.Test; */ public class RMYamlUnitTest extends BaseYamlUnitTest { - private static String RM_COMMUNITY_YAML_FILES_PATH = "/dev6/records-management/rm-community/rm-community-rest-api-explorer/src/main/webapp/definitions"; + private static String RM_COMMUNITY_YAML_FILES_PATH = "../rm-community-rest-api-explorer/src/main/webapp/definitions"; @Test public void validateYamlFile() throws Exception From 797baf833aec0448942f5995d89716589d5b7419 Mon Sep 17 00:00:00 2001 From: Sara Aspery Date: Thu, 16 Nov 2017 09:51:13 +0000 Subject: [PATCH 05/12] RM-5875 add Swagger version to exception message --- .../org_alfresco_module_rm/test/util/BaseYamlUnitTest.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/rm-community/rm-community-repo/unit-test/java/org/alfresco/module/org_alfresco_module_rm/test/util/BaseYamlUnitTest.java b/rm-community/rm-community-repo/unit-test/java/org/alfresco/module/org_alfresco_module_rm/test/util/BaseYamlUnitTest.java index ce474cbcfa..350aac6c8a 100644 --- a/rm-community/rm-community-repo/unit-test/java/org/alfresco/module/org_alfresco_module_rm/test/util/BaseYamlUnitTest.java +++ b/rm-community/rm-community-repo/unit-test/java/org/alfresco/module/org_alfresco_module_rm/test/util/BaseYamlUnitTest.java @@ -70,11 +70,13 @@ public class BaseYamlUnitTest try { // check the yaml file is valid against Swagger JSON schema - assertTrue("Yaml file is not a valid Swagger file: " + yamlFilePath, validateYamlFile(yamlFilePath, swaggerSchema)); + assertTrue("Yaml file is not valid Swagger " + OPEN_API_SPECIFICATION + ": " + yamlFilePath, + validateYamlFile(yamlFilePath, swaggerSchema)); // check can read the swagger object to obtain the swagger version Swagger swagger = new SwaggerParser().read(yamlFilePath); - assertEquals("Failed to obtain Swagger version from yaml file " + yamlFilePath, swagger.getSwagger(), OPEN_API_SPECIFICATION); + assertEquals("Failed to obtain Swagger version from yaml file " + yamlFilePath, + swagger.getSwagger(), OPEN_API_SPECIFICATION); } catch (ParserException ex) { From de4f99d26b29cb31aa02dfc84be806abfbe4b144 Mon Sep 17 00:00:00 2001 From: Sara Aspery Date: Fri, 17 Nov 2017 16:40:07 +0000 Subject: [PATCH 06/12] RM-5875 add license header --- .../test/util/BaseYamlUnitTest.java | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/rm-community/rm-community-repo/unit-test/java/org/alfresco/module/org_alfresco_module_rm/test/util/BaseYamlUnitTest.java b/rm-community/rm-community-repo/unit-test/java/org/alfresco/module/org_alfresco_module_rm/test/util/BaseYamlUnitTest.java index 350aac6c8a..b95e40dcdb 100644 --- a/rm-community/rm-community-repo/unit-test/java/org/alfresco/module/org_alfresco_module_rm/test/util/BaseYamlUnitTest.java +++ b/rm-community/rm-community-repo/unit-test/java/org/alfresco/module/org_alfresco_module_rm/test/util/BaseYamlUnitTest.java @@ -1,3 +1,30 @@ +/* + * #%L + * Alfresco Records Management Module + * %% + * Copyright (C) 2005 - 2017 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * - + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * - + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * - + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * - + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + * #L% + */ + package org.alfresco.module.org_alfresco_module_rm.test.util; import static junit.framework.TestCase.assertFalse; From 3a1db54876cb4280aeb8dcc2128f67833326eb5f Mon Sep 17 00:00:00 2001 From: Sara Aspery Date: Mon, 20 Nov 2017 07:39:37 +0000 Subject: [PATCH 07/12] RM-5875 review changes --- rm-community/rm-community-repo/pom.xml | 9 ++------- .../test/util/BaseYamlUnitTest.java | 4 ++-- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/rm-community/rm-community-repo/pom.xml b/rm-community/rm-community-repo/pom.xml index 15bd98c96f..a0d4d7b8c9 100644 --- a/rm-community/rm-community-repo/pom.xml +++ b/rm-community/rm-community-repo/pom.xml @@ -378,30 +378,25 @@ 0.9.10 test - - - org.slf4j - slf4j-api - 1.7.2 - io.swagger swagger-parser 1.0.23 + test com.github.fge json-schema-validator 2.2.6 + test com.google.guava guava 18.0 - diff --git a/rm-community/rm-community-repo/unit-test/java/org/alfresco/module/org_alfresco_module_rm/test/util/BaseYamlUnitTest.java b/rm-community/rm-community-repo/unit-test/java/org/alfresco/module/org_alfresco_module_rm/test/util/BaseYamlUnitTest.java index b95e40dcdb..fc33aec1b5 100644 --- a/rm-community/rm-community-repo/unit-test/java/org/alfresco/module/org_alfresco_module_rm/test/util/BaseYamlUnitTest.java +++ b/rm-community/rm-community-repo/unit-test/java/org/alfresco/module/org_alfresco_module_rm/test/util/BaseYamlUnitTest.java @@ -27,7 +27,7 @@ package org.alfresco.module.org_alfresco_module_rm.test.util; -import static junit.framework.TestCase.assertFalse; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; @@ -58,7 +58,7 @@ import org.apache.commons.io.IOUtils; import org.apache.commons.io.filefilter.WildcardFileFilter; /** - * Unit tests for Yaml files. + * Base class for unit tests for Yaml files. * * @author Sara Aspery * @since 2.6 From 7af2e0430b63574b2cdf0e73d1d94856708a5271 Mon Sep 17 00:00:00 2001 From: Tom Page Date: Wed, 22 Nov 2017 09:10:44 +0000 Subject: [PATCH 08/12] RM-5875 Manage the version of guava from the root pom. We'll need to use the regression tests to check that upgrading guava like this doesn't break anything in the application, but it fixes the unit tests. --- pom.xml | 10 ++++++++++ rm-community/rm-community-repo/pom.xml | 5 ----- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/pom.xml b/pom.xml index ee8531c7d2..645292fcbb 100644 --- a/pom.xml +++ b/pom.xml @@ -173,6 +173,16 @@ false + + + + com.google.guava + guava + 18.0 + + + + junit diff --git a/rm-community/rm-community-repo/pom.xml b/rm-community/rm-community-repo/pom.xml index a0d4d7b8c9..44cd926d24 100644 --- a/rm-community/rm-community-repo/pom.xml +++ b/rm-community/rm-community-repo/pom.xml @@ -392,11 +392,6 @@ 2.2.6 test - - com.google.guava - guava - 18.0 - From 75469a0f5bf1c30d438a03571fe8e1cae4f8427d Mon Sep 17 00:00:00 2001 From: Ana Bozianu Date: Thu, 23 Nov 2017 22:21:51 +0200 Subject: [PATCH 09/12] MNT-18793 - added paginated navigation in the disposition action executer --- .../job/DispositionLifecycleJobExecuter.java | 133 ++++++++++-------- ...spositionLifecycleJobExecuterUnitTest.java | 11 +- 2 files changed, 86 insertions(+), 58 deletions(-) diff --git a/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/job/DispositionLifecycleJobExecuter.java b/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/job/DispositionLifecycleJobExecuter.java index 6715fa8550..6969f6ef18 100644 --- a/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/job/DispositionLifecycleJobExecuter.java +++ b/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/job/DispositionLifecycleJobExecuter.java @@ -42,6 +42,7 @@ import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.repository.StoreRef; import org.alfresco.service.cmr.search.ResultSet; +import org.alfresco.service.cmr.search.SearchParameters; import org.alfresco.service.cmr.search.SearchService; import org.alfresco.service.cmr.security.PersonService; import org.apache.commons.logging.Log; @@ -164,68 +165,32 @@ public class DispositionLifecycleJobExecuter extends RecordsManagementJobExecute if (dispositionActions != null && !dispositionActions.isEmpty()) { - // execute search - ResultSet results = searchService.query(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, - SearchService.LANGUAGE_FTS_ALFRESCO, getQuery()); - List resultNodes = results.getNodeRefs(); - results.close(); - - if (logger.isDebugEnabled()) + boolean hasMore = true; + int skipCount = 0; + while(hasMore) { - logger.debug("Processing " + resultNodes.size() + " nodes"); - } + SearchParameters params = new SearchParameters(); + params.addStore(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE); + params.setLanguage(SearchService.LANGUAGE_FTS_ALFRESCO); + params.setQuery(getQuery()); + params.setSkipCount(skipCount); - // process search results - for (NodeRef node : resultNodes) - { - final NodeRef currentNode = node; + // execute search + ResultSet results = searchService.query(params); + List resultNodes = results.getNodeRefs(); + hasMore = results.hasMore(); + skipCount += results.length(); + results.close(); - RetryingTransactionCallback processTranCB = new RetryingTransactionCallback() + if (logger.isDebugEnabled()) { - public Boolean execute() - { - final String dispAction = (String) nodeService.getProperty(currentNode, - RecordsManagementModel.PROP_DISPOSITION_ACTION); + logger.debug("Processing " + resultNodes.size() + " nodes"); + } - // Run disposition action - if (dispAction != null && dispositionActions.contains(dispAction)) - { - ChildAssociationRef parent = nodeService.getPrimaryParent(currentNode); - if (parent.getTypeQName().equals(RecordsManagementModel.ASSOC_NEXT_DISPOSITION_ACTION)) - { - Map props = new HashMap(1); - props.put(RMDispositionActionExecuterAbstractBase.PARAM_NO_ERROR_CHECK, - Boolean.FALSE); - - try - { - // execute disposition action - recordsManagementActionService.executeRecordsManagementAction( - parent.getParentRef(), dispAction, props); - - if (logger.isDebugEnabled()) - { - logger.debug("Processed action: " + dispAction + "on" + parent); - } - } - catch (AlfrescoRuntimeException exception) - { - if (logger.isDebugEnabled()) - { - logger.debug(exception); - } - } - } - } - - return Boolean.TRUE; - } - }; - - // if exists - if (nodeService.exists(currentNode)) + // process search results + for (NodeRef node : resultNodes) { - retryingTransactionHelper.doInTransaction(processTranCB); + executeAction(node); } } } @@ -241,6 +206,62 @@ public class DispositionLifecycleJobExecuter extends RecordsManagementJobExecute } } + /** + * Helper method that executes a disposition action + * + * @param actionNode - the disposition action to execute + */ + private void executeAction(final NodeRef actionNode) + { + RetryingTransactionCallback processTranCB = new RetryingTransactionCallback() + { + public Boolean execute() + { + final String dispAction = (String) nodeService.getProperty(actionNode, + RecordsManagementModel.PROP_DISPOSITION_ACTION); + + // Run disposition action + if (dispAction != null && dispositionActions.contains(dispAction)) + { + ChildAssociationRef parent = nodeService.getPrimaryParent(actionNode); + if (parent.getTypeQName().equals(RecordsManagementModel.ASSOC_NEXT_DISPOSITION_ACTION)) + { + Map props = new HashMap(1); + props.put(RMDispositionActionExecuterAbstractBase.PARAM_NO_ERROR_CHECK, + Boolean.FALSE); + + try + { + // execute disposition action + recordsManagementActionService.executeRecordsManagementAction( + parent.getParentRef(), dispAction, props); + + if (logger.isDebugEnabled()) + { + logger.debug("Processed action: " + dispAction + "on" + parent); + } + } + catch (AlfrescoRuntimeException exception) + { + if (logger.isDebugEnabled()) + { + logger.debug(exception); + } + } + } + } + + return Boolean.TRUE; + } + }; + + // if exists + if (nodeService.exists(actionNode)) + { + retryingTransactionHelper.doInTransaction(processTranCB); + } + } + public PersonService getPersonService() { return personService; diff --git a/rm-community/rm-community-repo/unit-test/java/org/alfresco/module/org_alfresco_module_rm/job/DispositionLifecycleJobExecuterUnitTest.java b/rm-community/rm-community-repo/unit-test/java/org/alfresco/module/org_alfresco_module_rm/job/DispositionLifecycleJobExecuterUnitTest.java index f93bb7b2d6..bd0f3979bd 100644 --- a/rm-community/rm-community-repo/unit-test/java/org/alfresco/module/org_alfresco_module_rm/job/DispositionLifecycleJobExecuterUnitTest.java +++ b/rm-community/rm-community-repo/unit-test/java/org/alfresco/module/org_alfresco_module_rm/job/DispositionLifecycleJobExecuterUnitTest.java @@ -29,6 +29,7 @@ package org.alfresco.module.org_alfresco_module_rm.job; import static org.alfresco.module.org_alfresco_module_rm.test.util.AlfMock.generateQName; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyMap; import static org.mockito.Matchers.anyString; @@ -39,6 +40,7 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.when; import java.util.Collections; import java.util.List; @@ -50,9 +52,11 @@ import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.StoreRef; import org.alfresco.service.cmr.search.ResultSet; +import org.alfresco.service.cmr.search.SearchParameters; import org.alfresco.service.cmr.search.SearchService; import org.junit.Before; import org.junit.Test; +import org.mockito.ArgumentCaptor; import org.mockito.InjectMocks; import org.mockito.Mock; @@ -92,7 +96,8 @@ public class DispositionLifecycleJobExecuterUnitTest extends BaseUnitTest executer.setDispositionActions(dispositionActions); // setup interactions - doReturn(mockedResultSet).when(mockedSearchService).query(eq(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE), eq(SearchService.LANGUAGE_FTS_ALFRESCO), anyString()); + doReturn(mockedResultSet).when(mockedSearchService).query(any(SearchParameters.class)); + when(mockedResultSet.hasMore()).thenReturn(false); } /** @@ -100,7 +105,9 @@ public class DispositionLifecycleJobExecuterUnitTest extends BaseUnitTest */ private void verifyQuery() { - verify(mockedSearchService, times(1)).query(eq(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE), eq(SearchService.LANGUAGE_FTS_ALFRESCO), contains(QUERY)); + ArgumentCaptor paramsCaptor = ArgumentCaptor.forClass(SearchParameters.class); + verify(mockedSearchService, times(1)).query(paramsCaptor.capture()); + assertTrue(paramsCaptor.getValue().getQuery().contains(QUERY)); verify(mockedResultSet, times(1)).getNodeRefs(); verify(mockedResultSet, times(1)).close(); } From 33af594d47a7c5ca7026151225dee8c1c061dfde Mon Sep 17 00:00:00 2001 From: Ana Bozianu Date: Fri, 24 Nov 2017 14:27:03 +0000 Subject: [PATCH 10/12] MNT-18793 - added paginated navigation in the disposition action executer --- .../job/DispositionLifecycleJobExecuter.java | 135 ++++++++++-------- ...spositionLifecycleJobExecuterUnitTest.java | 78 +++++++++- 2 files changed, 149 insertions(+), 64 deletions(-) diff --git a/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/job/DispositionLifecycleJobExecuter.java b/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/job/DispositionLifecycleJobExecuter.java index 6715fa8550..cebad424b0 100644 --- a/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/job/DispositionLifecycleJobExecuter.java +++ b/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/job/DispositionLifecycleJobExecuter.java @@ -42,6 +42,7 @@ import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.repository.StoreRef; import org.alfresco.service.cmr.search.ResultSet; +import org.alfresco.service.cmr.search.SearchParameters; import org.alfresco.service.cmr.search.SearchService; import org.alfresco.service.cmr.security.PersonService; import org.apache.commons.logging.Log; @@ -122,7 +123,7 @@ public class DispositionLifecycleJobExecuter extends RecordsManagementJobExecute { StringBuilder sb = new StringBuilder(); - sb.append("TYPE:\"rma:dispositionAction\" + "); + sb.append("TYPE:\"rma:dispositionAction\" AND "); sb.append("(@rma\\:dispositionAction:("); boolean bFirst = true; @@ -164,68 +165,32 @@ public class DispositionLifecycleJobExecuter extends RecordsManagementJobExecute if (dispositionActions != null && !dispositionActions.isEmpty()) { - // execute search - ResultSet results = searchService.query(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, - SearchService.LANGUAGE_FTS_ALFRESCO, getQuery()); - List resultNodes = results.getNodeRefs(); - results.close(); - - if (logger.isDebugEnabled()) + boolean hasMore = true; + int skipCount = 0; + while(hasMore) { - logger.debug("Processing " + resultNodes.size() + " nodes"); - } + SearchParameters params = new SearchParameters(); + params.addStore(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE); + params.setLanguage(SearchService.LANGUAGE_FTS_ALFRESCO); + params.setQuery(getQuery()); + params.setSkipCount(skipCount); - // process search results - for (NodeRef node : resultNodes) - { - final NodeRef currentNode = node; + // execute search + ResultSet results = searchService.query(params); + List resultNodes = results.getNodeRefs(); + hasMore = results.hasMore(); + skipCount += resultNodes.size(); // increase by page size + results.close(); - RetryingTransactionCallback processTranCB = new RetryingTransactionCallback() + if (logger.isDebugEnabled()) { - public Boolean execute() - { - final String dispAction = (String) nodeService.getProperty(currentNode, - RecordsManagementModel.PROP_DISPOSITION_ACTION); + logger.debug("Processing " + resultNodes.size() + " nodes"); + } - // Run disposition action - if (dispAction != null && dispositionActions.contains(dispAction)) - { - ChildAssociationRef parent = nodeService.getPrimaryParent(currentNode); - if (parent.getTypeQName().equals(RecordsManagementModel.ASSOC_NEXT_DISPOSITION_ACTION)) - { - Map props = new HashMap(1); - props.put(RMDispositionActionExecuterAbstractBase.PARAM_NO_ERROR_CHECK, - Boolean.FALSE); - - try - { - // execute disposition action - recordsManagementActionService.executeRecordsManagementAction( - parent.getParentRef(), dispAction, props); - - if (logger.isDebugEnabled()) - { - logger.debug("Processed action: " + dispAction + "on" + parent); - } - } - catch (AlfrescoRuntimeException exception) - { - if (logger.isDebugEnabled()) - { - logger.debug(exception); - } - } - } - } - - return Boolean.TRUE; - } - }; - - // if exists - if (nodeService.exists(currentNode)) + // process search results + for (NodeRef node : resultNodes) { - retryingTransactionHelper.doInTransaction(processTranCB); + executeAction(node); } } } @@ -241,6 +206,62 @@ public class DispositionLifecycleJobExecuter extends RecordsManagementJobExecute } } + /** + * Helper method that executes a disposition action + * + * @param actionNode - the disposition action to execute + */ + private void executeAction(final NodeRef actionNode) + { + RetryingTransactionCallback processTranCB = new RetryingTransactionCallback() + { + public Boolean execute() + { + final String dispAction = (String) nodeService.getProperty(actionNode, + RecordsManagementModel.PROP_DISPOSITION_ACTION); + + // Run disposition action + if (dispAction != null && dispositionActions.contains(dispAction)) + { + ChildAssociationRef parent = nodeService.getPrimaryParent(actionNode); + if (parent.getTypeQName().equals(RecordsManagementModel.ASSOC_NEXT_DISPOSITION_ACTION)) + { + Map props = new HashMap(1); + props.put(RMDispositionActionExecuterAbstractBase.PARAM_NO_ERROR_CHECK, + Boolean.FALSE); + + try + { + // execute disposition action + recordsManagementActionService.executeRecordsManagementAction( + parent.getParentRef(), dispAction, props); + + if (logger.isDebugEnabled()) + { + logger.debug("Processed action: " + dispAction + "on" + parent); + } + } + catch (AlfrescoRuntimeException exception) + { + if (logger.isDebugEnabled()) + { + logger.debug(exception); + } + } + } + } + + return Boolean.TRUE; + } + }; + + // if exists + if (nodeService.exists(actionNode)) + { + retryingTransactionHelper.doInTransaction(processTranCB); + } + } + public PersonService getPersonService() { return personService; diff --git a/rm-community/rm-community-repo/unit-test/java/org/alfresco/module/org_alfresco_module_rm/job/DispositionLifecycleJobExecuterUnitTest.java b/rm-community/rm-community-repo/unit-test/java/org/alfresco/module/org_alfresco_module_rm/job/DispositionLifecycleJobExecuterUnitTest.java index f93bb7b2d6..537c03e9e3 100644 --- a/rm-community/rm-community-repo/unit-test/java/org/alfresco/module/org_alfresco_module_rm/job/DispositionLifecycleJobExecuterUnitTest.java +++ b/rm-community/rm-community-repo/unit-test/java/org/alfresco/module/org_alfresco_module_rm/job/DispositionLifecycleJobExecuterUnitTest.java @@ -29,17 +29,19 @@ package org.alfresco.module.org_alfresco_module_rm.job; import static org.alfresco.module.org_alfresco_module_rm.test.util.AlfMock.generateQName; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyMap; -import static org.mockito.Matchers.anyString; -import static org.mockito.Matchers.contains; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.when; +import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -48,13 +50,15 @@ import org.alfresco.module.org_alfresco_module_rm.test.util.BaseUnitTest; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.repository.StoreRef; import org.alfresco.service.cmr.search.ResultSet; -import org.alfresco.service.cmr.search.SearchService; +import org.alfresco.service.cmr.search.SearchParameters; import org.junit.Before; import org.junit.Test; +import org.mockito.ArgumentCaptor; import org.mockito.InjectMocks; import org.mockito.Mock; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; /** * Disposition lifecycle job execution unit test. @@ -92,7 +96,8 @@ public class DispositionLifecycleJobExecuterUnitTest extends BaseUnitTest executer.setDispositionActions(dispositionActions); // setup interactions - doReturn(mockedResultSet).when(mockedSearchService).query(eq(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE), eq(SearchService.LANGUAGE_FTS_ALFRESCO), anyString()); + doReturn(mockedResultSet).when(mockedSearchService).query(any(SearchParameters.class)); + when(mockedResultSet.hasMore()).thenReturn(false); } /** @@ -100,7 +105,9 @@ public class DispositionLifecycleJobExecuterUnitTest extends BaseUnitTest */ private void verifyQuery() { - verify(mockedSearchService, times(1)).query(eq(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE), eq(SearchService.LANGUAGE_FTS_ALFRESCO), contains(QUERY)); + ArgumentCaptor paramsCaptor = ArgumentCaptor.forClass(SearchParameters.class); + verify(mockedSearchService, times(1)).query(paramsCaptor.capture()); + assertTrue(paramsCaptor.getValue().getQuery().contains(QUERY)); verify(mockedResultSet, times(1)).getNodeRefs(); verify(mockedResultSet, times(1)).close(); } @@ -249,7 +256,64 @@ public class DispositionLifecycleJobExecuterUnitTest extends BaseUnitTest { String actual = executer.getQuery(); - String expected = "TYPE:\"rma:dispositionAction\" + (@rma\\:dispositionAction:(\"cutoff\" OR \"retain\")) AND ISUNSET:\"rma:dispositionActionCompletedAt\" AND ( @rma\\:dispositionEventsEligible:true OR @rma\\:dispositionAsOf:[MIN TO NOW] ) "; + String expected = "TYPE:\"rma:dispositionAction\" AND " + + "(@rma\\:dispositionAction:(\"cutoff\" OR \"retain\")) " + + "AND ISUNSET:\"rma:dispositionActionCompletedAt\" " + + "AND ( @rma\\:dispositionEventsEligible:true OR @rma\\:dispositionAsOf:[MIN TO NOW] ) "; + assertEquals(expected, actual); } + + /** + * Given the maximum page of elements for search service is 2 + * and search service finds more than one page of elements + * When the job executer runs + * Then the executer retrieves both pages and iterates all elements + */ + @Test + public void testPagination() + { + final NodeRef node1 = generateNodeRef(); + final NodeRef node2 = generateNodeRef(); + final NodeRef node3 = generateNodeRef(); + final NodeRef node4 = generateNodeRef(); + + // mock the search service to return the right page + when(mockedSearchService.query(any(SearchParameters.class))).thenAnswer( + new Answer() + { + @Override + public ResultSet answer(InvocationOnMock invocation) + { + SearchParameters params = invocation.getArgumentAt(0, SearchParameters.class); + if (params.getSkipCount() == 0) + { + // mock first page + ResultSet result1 = mock(ResultSet.class); + when(result1.getNodeRefs()).thenReturn(Arrays.asList(node1, node2)); + when(result1.hasMore()).thenReturn(true); + return result1; + } + else if (params.getSkipCount() == 2) + { + // mock second page + ResultSet result2 = mock(ResultSet.class); + when(result2.getNodeRefs()).thenReturn(Arrays.asList(node3, node4)); + when(result2.hasMore()).thenReturn(false); + return result2; + } + throw new IndexOutOfBoundsException("Pagination did not stop after the second page!"); + } + }); + + // call the service + executer.executeImpl(); + + // check the loop iterated trough all the elements + verify(mockedNodeService).exists(node1); + verify(mockedNodeService).exists(node2); + verify(mockedNodeService).exists(node3); + verify(mockedNodeService).exists(node4); + verify(mockedSearchService, times(2)).query(any(SearchParameters.class)); + } } From 020b2a6c38848aae3cd79626aa2fe42c7731fb16 Mon Sep 17 00:00:00 2001 From: alfresco-build Date: Mon, 27 Nov 2017 10:37:45 +0000 Subject: [PATCH 11/12] [maven-release-plugin] prepare release V2.5.0.5 --- pom.xml | 4 ++-- rm-automation/pom.xml | 2 +- rm-community/pom.xml | 2 +- rm-community/rm-community-repo/pom.xml | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pom.xml b/pom.xml index 4f8d47420b..8afa861be4 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ org.alfresco alfresco-rm pom - 2.5.0.5-SNAPSHOT + 2.5.0.5 Alfresco Records Management @@ -24,7 +24,7 @@ scm:git:https://git.alfresco.com/records-management/records-management.git scm:git:https://git.alfresco.com/records-management/records-management.git https://git.alfresco.com/records-management/records-management - HEAD + V2.5.0.5 diff --git a/rm-automation/pom.xml b/rm-automation/pom.xml index a515d63d99..3513fd2de6 100644 --- a/rm-automation/pom.xml +++ b/rm-automation/pom.xml @@ -8,7 +8,7 @@ org.alfresco alfresco-rm - 2.5.0.5-SNAPSHOT + 2.5.0.5 diff --git a/rm-community/pom.xml b/rm-community/pom.xml index 834fc9a4e9..13ef68b45b 100644 --- a/rm-community/pom.xml +++ b/rm-community/pom.xml @@ -8,7 +8,7 @@ org.alfresco alfresco-rm - 2.5.0.5-SNAPSHOT + 2.5.0.5 diff --git a/rm-community/rm-community-repo/pom.xml b/rm-community/rm-community-repo/pom.xml index 541873704f..a040a9e64e 100644 --- a/rm-community/rm-community-repo/pom.xml +++ b/rm-community/rm-community-repo/pom.xml @@ -9,7 +9,7 @@ org.alfresco alfresco-rm-community - 2.5.0.5-SNAPSHOT + 2.5.0.5 From d9b6c0d6f501f813535d881e8fed8089efd605e9 Mon Sep 17 00:00:00 2001 From: alfresco-build Date: Mon, 27 Nov 2017 10:37:48 +0000 Subject: [PATCH 12/12] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- rm-automation/pom.xml | 2 +- rm-community/pom.xml | 2 +- rm-community/rm-community-repo/pom.xml | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pom.xml b/pom.xml index 8afa861be4..77109e1989 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ org.alfresco alfresco-rm pom - 2.5.0.5 + 2.5.0.6-SNAPSHOT Alfresco Records Management @@ -24,7 +24,7 @@ scm:git:https://git.alfresco.com/records-management/records-management.git scm:git:https://git.alfresco.com/records-management/records-management.git https://git.alfresco.com/records-management/records-management - V2.5.0.5 + HEAD diff --git a/rm-automation/pom.xml b/rm-automation/pom.xml index 3513fd2de6..2e5e27212c 100644 --- a/rm-automation/pom.xml +++ b/rm-automation/pom.xml @@ -8,7 +8,7 @@ org.alfresco alfresco-rm - 2.5.0.5 + 2.5.0.6-SNAPSHOT diff --git a/rm-community/pom.xml b/rm-community/pom.xml index 13ef68b45b..0515416bb4 100644 --- a/rm-community/pom.xml +++ b/rm-community/pom.xml @@ -8,7 +8,7 @@ org.alfresco alfresco-rm - 2.5.0.5 + 2.5.0.6-SNAPSHOT diff --git a/rm-community/rm-community-repo/pom.xml b/rm-community/rm-community-repo/pom.xml index a040a9e64e..b847b75961 100644 --- a/rm-community/rm-community-repo/pom.xml +++ b/rm-community/rm-community-repo/pom.xml @@ -9,7 +9,7 @@ org.alfresco alfresco-rm-community - 2.5.0.5 + 2.5.0.6-SNAPSHOT