Validator.java

/*
 * GovWay - A customizable API Gateway 
 * https://govway.org
 * 
 * Copyright (c) 2005-2024 Link.it srl (https://link.it). 
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 3, as published by
 * the Free Software Foundation.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

package org.openspcoop2.utils.openapi.validator;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.InputStream;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.net.URI;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.UUID;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.openapi4j.core.model.v3.OAI3Context;
import org.openapi4j.core.model.v3.OAI3SchemaKeywords;
import org.openapi4j.core.util.StringUtil;
import org.openapi4j.core.util.TreeUtil;
import org.openapi4j.core.validation.ValidationResults;
import org.openapi4j.operation.validator.model.Request;
import org.openapi4j.operation.validator.model.Response;
import org.openapi4j.operation.validator.model.impl.Body;
import org.openapi4j.operation.validator.model.impl.DefaultRequest;
import org.openapi4j.operation.validator.model.impl.DefaultResponse;
import org.openapi4j.operation.validator.validation.OperationValidator;
import org.openapi4j.parser.model.v3.OpenApi3;
import org.openapi4j.parser.model.v3.Operation;
import org.openapi4j.parser.model.v3.Path;
import org.openapi4j.parser.validation.v3.OpenApi3Validator;
import org.openapi4j.schema.validator.ValidationData;
import org.openspcoop2.utils.date.DateUtils;
import org.openspcoop2.utils.json.AbstractUtils;
import org.openspcoop2.utils.json.IJsonSchemaValidator;
import org.openspcoop2.utils.json.JSONUtils;
import org.openspcoop2.utils.json.JsonPathExpressionEngine;
import org.openspcoop2.utils.json.JsonSchemaValidatorConfig;
import org.openspcoop2.utils.json.JsonSchemaValidatorConfig.ADDITIONAL;
import org.openspcoop2.utils.json.JsonSchemaValidatorConfig.POLITICA_INCLUSIONE_TIPI;
import org.openspcoop2.utils.json.JsonValidatorAPI.ApiName;
import org.openspcoop2.utils.json.ValidationException;
import org.openspcoop2.utils.json.ValidationResponse;
import org.openspcoop2.utils.json.ValidationResponse.ESITO;
import org.openspcoop2.utils.json.ValidatorFactory;
import org.openspcoop2.utils.json.YAMLUtils;
import org.openspcoop2.utils.openapi.OpenapiApi;
import org.openspcoop2.utils.openapi.OpenapiApiValidatorStructure;
import org.openspcoop2.utils.openapi.UniqueInterfaceGenerator;
import org.openspcoop2.utils.openapi.UniqueInterfaceGeneratorConfig;
import org.openspcoop2.utils.openapi.validator.swagger.SwaggerOpenApiValidator;
import org.openspcoop2.utils.openapi.validator.swagger.SwaggerRequestValidator;
import org.openspcoop2.utils.openapi.validator.swagger.SwaggerResponseValidator;
import org.openspcoop2.utils.regexp.RegularExpressionEngine;
import org.openspcoop2.utils.resources.Charset;
import org.openspcoop2.utils.resources.FileSystemUtilities;
import org.openspcoop2.utils.rest.AbstractApiValidator;
import org.openspcoop2.utils.rest.ApiFormats;
import org.openspcoop2.utils.rest.ApiParameterType;
import org.openspcoop2.utils.rest.ApiValidatorConfig;
import org.openspcoop2.utils.rest.IApiValidator;
import org.openspcoop2.utils.rest.ProcessingException;
import org.openspcoop2.utils.rest.ValidatorException;
import org.openspcoop2.utils.rest.api.Api;
import org.openspcoop2.utils.rest.api.ApiBodyParameter;
import org.openspcoop2.utils.rest.api.ApiOperation;
import org.openspcoop2.utils.rest.api.ApiReference;
import org.openspcoop2.utils.rest.api.ApiResponse;
import org.openspcoop2.utils.rest.api.ApiSchema;
import org.openspcoop2.utils.rest.api.ApiSchemaType;
import org.openspcoop2.utils.rest.api.ApiSchemaTypeRestriction;
import org.openspcoop2.utils.rest.entity.Cookie;
import org.openspcoop2.utils.rest.entity.HttpBaseEntity;
import org.openspcoop2.utils.rest.entity.HttpBaseRequestEntity;
import org.openspcoop2.utils.rest.entity.HttpBaseResponseEntity;
import org.openspcoop2.utils.transport.TransportUtils;
import org.openspcoop2.utils.transport.http.HttpConstants;
import org.openspcoop2.utils.transport.http.HttpRequestMethod;
import org.openspcoop2.utils.xml.XMLUtils;
import org.slf4j.Logger;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

import com.atlassian.oai.validator.model.ApiPath;
import com.atlassian.oai.validator.model.ApiPathImpl;
import com.atlassian.oai.validator.model.NormalisedPath;
import com.atlassian.oai.validator.model.NormalisedPathImpl;
import com.atlassian.oai.validator.model.Request.Method;
import com.atlassian.oai.validator.model.SimpleRequest;
import com.atlassian.oai.validator.model.SimpleResponse;
import com.atlassian.oai.validator.report.SimpleValidationReportFormat;
import com.atlassian.oai.validator.report.ValidationReport;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.node.TextNode;
import com.networknt.schema.SpecVersion.VersionFlag;

import io.swagger.v3.core.util.Json;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.PathItem;
import io.swagger.v3.oas.models.PathItem.HttpMethod;
import io.swagger.v3.oas.models.headers.Header;
import io.swagger.v3.oas.models.media.ArraySchema;
import io.swagger.v3.oas.models.media.ComposedSchema;
import io.swagger.v3.oas.models.media.Content;
import io.swagger.v3.oas.models.media.MediaType;
import io.swagger.v3.oas.models.media.ObjectSchema;
import io.swagger.v3.oas.models.media.Schema;
import io.swagger.v3.oas.models.media.StringSchema;
import io.swagger.v3.oas.models.parameters.Parameter;
import io.swagger.v3.oas.models.parameters.RequestBody;

/**
 * Validator
 *
 *
 * @author Poli Andrea (apoli@link.it)
 * @author $Author$
 * @version $Rev$, $Date$
 */
public class Validator extends AbstractApiValidator implements IApiValidator {

	private Api api;
	// JSONSchema Validation
	private Map<String, IJsonSchemaValidator> validatorMap;
	private Map<String, File> fileSchema;
	// OpenAPI4j Validation
	private OpenApi3 openApi4j;
	
	// SwaggerRequestValidator
	private SwaggerRequestValidator swaggerRequestValidator;
	private SwaggerResponseValidator swaggerResponseValidator;
	private OpenAPI openApiSwagger;

	// Configuration
	private OpenapiLibraryValidatorConfig openApi4jConfig;
	
	boolean onlySchemas = false;
	
	private Logger log;
	
	private static final String VALIDATION_STRUCTURE = "VALIDATION_STRUCTURE";
	private static final String VALIDATION_SWAGGER_REQUEST_VALIDATOR_OPENAPI = "VALIDATION_SWAGGER_REQUEST_VALIDATOR_OPENAPI";
	private org.openspcoop2.utils.Semaphore semaphore = new org.openspcoop2.utils.Semaphore("OpenAPIValidator");
	
	@Override
	public void init(Logger log, Api api, ApiValidatorConfig config)
			throws ProcessingException {
		
		this.log = log;
		if(api == null)
			throw new ProcessingException("Api cannot be null");

		// la sincronizzazione sull'API serve per evitare che venga inizializzati più volte in maniera concorrente l'API
		//synchronized (api) {
		this.semaphore.acquireThrowRuntime("init");
		try {
					
			this.api = api;
			Api apiRest = null;
			OpenapiApi openapiApi = null;
			OpenapiApiValidatorStructure apiValidatorStructure = null;
			SwaggerRequestValidatorOpenAPI swaggerRequestValidatorOpenAPI = null;
			if((api instanceof OpenapiApi)) {
				openapiApi = (OpenapiApi) this.api;
				apiRest = this.api;
				apiValidatorStructure = openapiApi.getValidationStructure();
				if(apiRest.containsKey(VALIDATION_SWAGGER_REQUEST_VALIDATOR_OPENAPI)) {
					swaggerRequestValidatorOpenAPI = (SwaggerRequestValidatorOpenAPI) apiRest.getVendorImpl(VALIDATION_SWAGGER_REQUEST_VALIDATOR_OPENAPI);
				}
			}
			else {
				apiRest = this.api;
				if(apiRest.containsKey(VALIDATION_STRUCTURE)) {
					apiValidatorStructure = (OpenapiApiValidatorStructure) apiRest.getVendorImpl(VALIDATION_STRUCTURE);
				}
				this.onlySchemas = true;
			}
			
			ApiName jsonValidatorAPI = null;
			OpenAPILibrary openApiLibrary = null; // ottimizzazione per OpenAPI
			ADDITIONAL policyAdditionalProperties = config.getPolicyAdditionalProperties();
			if(config instanceof OpenapiApiValidatorConfig) {
				jsonValidatorAPI = ((OpenapiApiValidatorConfig)config).getJsonValidatorAPI();
				if(openapiApi!=null) {
					OpenapiApiValidatorConfig c = (OpenapiApiValidatorConfig) config;
					if(c.getOpenApiValidatorConfig()!=null) {
						openApiLibrary = c.getOpenApiValidatorConfig().getOpenApiLibrary();
						if(OpenAPILibrary.openapi4j.equals(openApiLibrary) || OpenAPILibrary.swagger_request_validator.equals(openApiLibrary)) {
							this.openApi4jConfig = c.getOpenApiValidatorConfig();
						}
					}
				}
			} 
			if(jsonValidatorAPI==null) {
				jsonValidatorAPI = ApiName.NETWORK_NT;
			}

			try {
			
				if(OpenAPILibrary.openapi4j.equals(openApiLibrary) || 
						OpenAPILibrary.swagger_request_validator.equals(openApiLibrary)) {
					
					// leggo JSON Node degli schemi
					
					JsonNode schemaNodeRoot = null;
					URL uriSchemaNodeRoot = null;
					Map<URL, JsonNode> schemaMap = null;
					String root = "file:/";
					boolean validateSchema = true;
					if(apiValidatorStructure!=null && apiValidatorStructure.getNodeValidatorePrincipale()!=null && !apiValidatorStructure.getNodeValidatorePrincipale().isEmpty()) {
						validateSchema = false; // validazione dello schema effettuata quando viene costruito
						for (String nome : apiValidatorStructure.getNodeValidatorePrincipale().keySet()) {
							if(root.equals(nome)) {
								schemaNodeRoot = apiValidatorStructure.getNodeValidatorePrincipale().get(nome);
								uriSchemaNodeRoot = new URL(root);
							}
							else {
								if(schemaMap==null) {
									schemaMap = new HashMap<URL, JsonNode>();
								}
								schemaMap.put(new URL(nome), apiValidatorStructure.getNodeValidatorePrincipale().get(nome));
							}
						}
					}
					else {
						
						YAMLUtils yamlUtils = YAMLUtils.getInstance();
						JSONUtils jsonUtils = JSONUtils.getInstance();
												
						String apiRaw = openapiApi.getApiRaw();
						boolean apiRawIsYaml = yamlUtils.isYaml(apiRaw);
						boolean readApiSchemas = true;
						if(this.openApi4jConfig.isMergeAPISpec()) {
							
							readApiSchemas = false;
							
							Map<String, String> attachments = new HashMap<>();
							if(api.getSchemas()!=null && api.getSchemas().size()>0) {

								for (ApiSchema apiSchema : api.getSchemas()) {
								
									if(!ApiSchemaType.JSON.equals(apiSchema.getType()) && !ApiSchemaType.YAML.equals(apiSchema.getType())) {
										continue;
									}
									byte [] schema = apiSchema.getContent();
									if(ApiSchemaType.JSON.equals(apiSchema.getType())) {
										if(jsonUtils.isJson(schema)) {
											attachments.put(apiSchema.getName(), new String(apiSchema.getContent()));
										}
									}
									else {
										if(yamlUtils.isYaml(schema)) {
											attachments.put(apiSchema.getName(), new String(apiSchema.getContent()));
										}
									}
									
								}
							}
							
							if(!attachments.isEmpty()) {							
								UniqueInterfaceGeneratorConfig configUniqueInterfaceGeneratorConfig = new UniqueInterfaceGeneratorConfig();
								configUniqueInterfaceGeneratorConfig.setFormat(ApiFormats.OPEN_API_3);
								configUniqueInterfaceGeneratorConfig.setYaml(apiRawIsYaml);
								configUniqueInterfaceGeneratorConfig.setMaster(apiRaw);
								configUniqueInterfaceGeneratorConfig.setAttachments(attachments);
								try {
									String apiMerged = UniqueInterfaceGenerator.generate(configUniqueInterfaceGeneratorConfig, null, null, true, log);
									if(apiMerged==null) {
										throw new Exception("empty ApiSpec");
									}
									apiRaw = apiMerged;
								}catch(Throwable t) {
									log.error("Merge API Spec failed: "+t.getMessage(),t);
									readApiSchemas = true; // torno al metodo tradizionale
								}
							}
						}
						
						if(apiRawIsYaml) {
							// Fix merge key '<<: *'
							if(YAMLUtils.containsMergeKeyAnchor(apiRaw)) {
								// Risoluzione merge key '<<: *'
								String jsonRepresentation = YAMLUtils.resolveMergeKeyAndConvertToJson(apiRaw);
								schemaNodeRoot = jsonUtils.getAsNode(jsonRepresentation);
							}
							else {
								schemaNodeRoot = yamlUtils.getAsNode(apiRaw);
							}
						}
						else {
							schemaNodeRoot = jsonUtils.getAsNode(apiRaw);
						}
						normalizeRefs(schemaNodeRoot);
						uriSchemaNodeRoot = new URL(root);
						
						if(readApiSchemas && api.getSchemas()!=null && api.getSchemas().size()>0) {
							
							for (ApiSchema apiSchema : api.getSchemas()) {
								
								if(!ApiSchemaType.JSON.equals(apiSchema.getType()) && !ApiSchemaType.YAML.equals(apiSchema.getType())) {
									continue;
								}
								byte [] schema = apiSchema.getContent();
								JsonNode schemaNodeInternal = null;
								if(ApiSchemaType.JSON.equals(apiSchema.getType())) {
									if(jsonUtils.isJson(schema)) {
										schemaNodeInternal = jsonUtils.getAsNode(schema);
									}
								}
								else {
									if(yamlUtils.isYaml(schema)) {
										// Vedi fix descritto sopra
										String sSchema = new String(schema);
										// Fix merge key '<<: *'
										if(YAMLUtils.containsMergeKeyAnchor(sSchema)) {
											// Risoluzione merge key '<<: *'
											String jsonRepresentation = YAMLUtils.resolveMergeKeyAndConvertToJson(sSchema);
											schemaNodeInternal = jsonUtils.getAsNode(jsonRepresentation);
										}
										else {
											schemaNodeInternal = yamlUtils.getAsNode(schema);
										}
									}
								}
								if(schemaNodeInternal==null) {
									continue;
								}
								normalizeRefs(schemaNodeInternal);
								if(schemaMap==null) {
									schemaMap = new HashMap<URL, JsonNode>();
								}
								schemaMap.put(new URL(root+apiSchema.getName()), schemaNodeInternal);
								
							}
							
						}
					}

					if(OpenAPILibrary.openapi4j.equals(openApiLibrary)) {
					
						// Costruisco OpenAPI3					
						OAI3Context context = new OAI3Context(uriSchemaNodeRoot, schemaNodeRoot, schemaMap);
						if(this.openApi4jConfig!=null) {
							context.setMultipartOptimization(this.openApi4jConfig.isValidateMultipartOptimization());
						}
						this.openApi4j = TreeUtil.json.convertValue(context.getBaseDocument(), OpenApi3.class);
						this.openApi4j.setContext(context);
						
						// Explicit validation of the API spec
						
						if(validateSchema && this.openApi4jConfig!=null && this.openApi4jConfig.isValidateAPISpec()) {
							try {
								ValidationResults results = OpenApi3Validator.instance().validate(this.openApi4j);
								if(!results.isValid()) {
									throw new ProcessingException("OpenAPI3 not valid: "+results.toString());
								}
							}catch(org.openapi4j.core.validation.ValidationException valExc) {
								if(valExc.results()!=null) {
									throw new ProcessingException("OpenAPI3 not valid: "+valExc.results().toString());
								}
								else {
									throw new ProcessingException("OpenAPI3 not valid: "+valExc.getMessage());
								}
							}
						}						
					} // fine openapi4j
					
					else if(OpenAPILibrary.swagger_request_validator.equals(openApiLibrary)) {
						// Validazione sintattica se richiesta
						
						if(validateSchema && this.openApi4jConfig.isValidateAPISpec()) {
							var validationResult = new SwaggerOpenApiValidator().validate(schemaNodeRoot);
							if (validationResult.isPresent()) {
								throw new ProcessingException(
										"OpenAPI3 not valid: " + validationResult.get()
										);								
							}					    
						}
						
						// Parsing
						if(swaggerRequestValidatorOpenAPI!=null) {
							this.openApiSwagger = swaggerRequestValidatorOpenAPI.getOpenApiSwagger();
						}
						else {
							SwaggerRequestValidatorOpenAPI newInstanceSwaggerRequestValidatorOpenAPI = new SwaggerRequestValidatorOpenAPI(schemaNodeRoot, this.openApi4jConfig, api);
							this.openApiSwagger = newInstanceSwaggerRequestValidatorOpenAPI.getOpenApiSwagger(); // init
							apiRest.addVendorImpl(VALIDATION_SWAGGER_REQUEST_VALIDATOR_OPENAPI, newInstanceSwaggerRequestValidatorOpenAPI);
						}
						
						this.swaggerRequestValidator = new SwaggerRequestValidator(this.openApiSwagger, this.openApi4jConfig);
				        this.swaggerResponseValidator = new SwaggerResponseValidator(this.openApiSwagger, this.openApi4jConfig);
					
					} // fine swagger_request_validator
					

					// Salvo informazioni ricostruite
					if(apiValidatorStructure==null) {
						OpenapiApiValidatorStructure validationStructure = new OpenapiApiValidatorStructure();
						Map<String, JsonNode> nodeValidatorePrincipale = new HashMap<String, JsonNode>();
						nodeValidatorePrincipale.put(root, schemaNodeRoot);
						if(schemaMap!=null && !schemaMap.isEmpty()) {
							for (URL url : schemaMap.keySet()) {
								nodeValidatorePrincipale.put(url.toString(), schemaMap.get(url));
							}
						}
						validationStructure.setNodeValidatorePrincipale(nodeValidatorePrincipale);
						openapiApi.setValidationStructure(validationStructure);
					}

					return; // finish
				}
				
				
				
				this.validatorMap = new HashMap<>();
				
				
				// Verifico se gli schemi importati o gli elementi definition dentro l'interfaccia api, 
				// a loro volta importano altri schemi tramite il $ref standard di json schema
				// Se cosi non è non serve serializzarli su file system, la cui serializzazione è ovviamente più costosa in termini di performance.
				boolean existsRefInternal = false;
				
				
				
				
				/* *** Validatore Principale *** */
				
				Map<String, byte[]> schemiValidatorePrincipale = new HashMap<>();
				Map<String, JsonNode> nodeValidatorePrincipale = new HashMap<>();
	 			
				if(apiValidatorStructure!=null) {
					schemiValidatorePrincipale = apiValidatorStructure.getSchemiValidatorePrincipale();
					nodeValidatorePrincipale = apiValidatorStructure.getNodeValidatorePrincipale();
				}
				else {

					if(openapiApi!=null) {

						Map<String, Schema<?>> definitions = openapiApi.getAllDefinitions();
						String definitionString = Json.mapper().writeValueAsString(definitions);
						definitionString = definitionString.replaceAll("#/components/schemas", "#/definitions");
						for(String schemaName: definitions.keySet()) {
							
							ByteArrayOutputStream bout = new ByteArrayOutputStream();
							bout.write("{".getBytes());
							String defOggetto = Json.mapper().writeValueAsString(definitions.get(schemaName));
							defOggetto = defOggetto.trim();
							defOggetto = defOggetto.replaceAll("#/components/schemas", "#/definitions");
							if(defOggetto.startsWith("{")) {
								defOggetto = defOggetto.substring(1);
							}
							if(defOggetto.endsWith("}")) {
								defOggetto = defOggetto.substring(0,defOggetto.length()-1);
							}
							defOggetto = defOggetto.trim();
							bout.write(defOggetto.getBytes());
							// DEVE ESSERE DEFINITO NEL JSON SCHEMA, NON POSSO DEFINIRLO STATICAMENTE
							/*
								bout.write(",\"additionalProperties\": ".getBytes());
								if(defOggetto.startsWith("\"allOf\"") || defOggetto.startsWith("\"anyOf\"")){
									// INDICARE ARTICOLO CHE SPIEGA
									bout.write("true".getBytes());
								}
								else {
									bout.write("false".getBytes());
								}
							*/
							bout.write(",".getBytes());
							bout.write("\"definitions\" : ".getBytes());
							bout.write(definitionString.getBytes());
							bout.write("}".getBytes());
							
							// Normalizzo schemi importati
							JSONUtils jsonUtils = JSONUtils.getInstance();
							JsonNode schemaNode = jsonUtils.getAsNode(bout.toByteArray());
							nodeValidatorePrincipale.put(schemaName, schemaNode);
							schemiValidatorePrincipale.put(schemaName, bout.toByteArray());
							
							// Verifico se gli elementi definition, a loro volta importano altri schemi
							JsonPathExpressionEngine engine = new JsonPathExpressionEngine();
							List<String> refPath = engine.getStringMatchPattern(schemaNode, "$..$ref");
							if(refPath!=null && !refPath.isEmpty()) {
								for (String ref : refPath) {
									String path = this.getRefPath(ref);
									if(path!=null) {
										existsRefInternal = true;
										break;
									}
								}
							}
			
						}
					}
				}
				
				
				
				
				
				// *** Gestione file importati ***
				
				this.fileSchema = new HashMap<>();
				if(apiValidatorStructure!=null) {
					this.fileSchema = apiValidatorStructure.getFileSchema();
				}
				else {
					if(api.getSchemas()!=null && api.getSchemas().size()>0) {
					
						HashMap<String, JsonNode> tmpNode = new HashMap<>();
						HashMap<String, byte[]> tmpByteArraySchema = new HashMap<>();
						HashMap<String, ApiSchemaType> tmpSchemaType = new HashMap<>();
						
						for (ApiSchema apiSchema : api.getSchemas()) {
							
							if(!ApiSchemaType.JSON.equals(apiSchema.getType()) && !ApiSchemaType.YAML.equals(apiSchema.getType())) {
								
								continue;
								
							}
							byte [] schema = apiSchema.getContent();
							JsonNode schemaNode = null;
							AbstractUtils utils = null;
							JSONUtils jsonUtils = JSONUtils.getInstance();
							if(ApiSchemaType.JSON.equals(apiSchema.getType())) {
								utils = JSONUtils.getInstance();
								if(((JSONUtils)utils).isJson(schema)) {
									schemaNode = utils.getAsNode(schema);
								}
							}
							else {
								utils = YAMLUtils.getInstance();
								if(((YAMLUtils)utils).isYaml(schema)) {
									schemaNode = utils.getAsNode(schema);
								}
							}
							if(schemaNode==null) {
								continue;
							}
							
							
							/* ** Potenziale import from swagger/openapi che utilizza uno standard components/schema invece di definition, ed inoltre non inizia con { } **/
							if(schemaNode instanceof TextNode) {
								ByteArrayOutputStream bout = new ByteArrayOutputStream();
								bout.write("{".getBytes());
								bout.write(schema);
								bout.write("}".getBytes());
								bout.flush();
								bout.close();
								schemaNode = utils.getAsNode(bout.toByteArray());
							}
							if(schemaNode instanceof ObjectNode) {
								ObjectNode objectNode = (ObjectNode) schemaNode;
								
								boolean foundDefinitions = false;
								JsonNode nodeDefinitions = objectNode.get("definitions");
								if(nodeDefinitions!=null && nodeDefinitions instanceof ObjectNode) {
									foundDefinitions = true;
								}
								
								if(!foundDefinitions) {
									JsonNode nodeComponents = objectNode.get("components");
									if(nodeComponents instanceof ObjectNode) {
										ObjectNode objectNodeComponents = (ObjectNode) nodeComponents;
										JsonNode nodeSchemas = objectNodeComponents.get("schemas");
										if(nodeSchemas!=null && nodeSchemas instanceof ObjectNode) {
											
											ObjectNode objectNodeDefinitions = (ObjectNode) utils.newObjectNode();
											objectNodeDefinitions.set("definitions", nodeSchemas);
											String schemaAsString = null;
											if(ApiSchemaType.YAML.equals(apiSchema.getType())) {
												// converto comunque in json poichè la validazione è supportata per json solo
												schemaAsString = jsonUtils.toString(objectNodeDefinitions);
											}
											else {
												schemaAsString = utils.toString(objectNodeDefinitions);
											}
											schemaAsString = schemaAsString.replaceAll("#/components/schemas", "#/definitions");
											schema = schemaAsString.getBytes();
											schemaNode = objectNodeDefinitions;
											//System.out.println("SCHEMA ["+new String(schema)+"]");
											
										}
										else {
											schema = null;
											schemaNode = null;
										}
									}
								}
							}
							
							if(schemaNode!=null) {
								if(this.onlySchemas) {
									
									// Interfaccia Registro senza OpenAPI
									
									IJsonSchemaValidator validator = ValidatorFactory.newJsonSchemaValidator(jsonValidatorAPI);
									JsonSchemaValidatorConfig schemaValidationConfig = new JsonSchemaValidatorConfig();
									schemaValidationConfig.setVerbose(config.isVerbose());
									schemaValidationConfig.setAdditionalProperties(policyAdditionalProperties);
									schemaValidationConfig.setPoliticaInclusioneTipi(POLITICA_INCLUSIONE_TIPI.DEFAULT);
									schemaValidationConfig.setEmitLogError(config.isEmitLogError());
									schemaValidationConfig.setJsonSchemaVersion(VersionFlag.V4); // https://github.com/OAI/OpenAPI-Specification/blob/main/schemas/v3.0/schema.json
									//System.out.println("ADD SCHEMA PER ["+apiSchemaName+"#"+nameInternal+"] ["+new String(bout.toByteArray())+"]");
									validator.setSchema(schema, schemaValidationConfig, log);
									
									this.validatorMap.put(apiSchema.getName(), validator);
									
								}
								else {
		
									File tmp = FileSystemUtilities.createTempFile("validator", "."+ apiSchema.getType().name().toLowerCase());
									this.fileSchema.put(apiSchema.getName(), tmp);
									tmpNode.put(apiSchema.getName(), schemaNode);
									tmpByteArraySchema.put(apiSchema.getName(), schema);
									tmpSchemaType.put(apiSchema.getName(), apiSchema.getType());
									
								}
							}
						}
						
						
						if(!this.onlySchemas) {
						
							// Verifico se gli schemi importati, a loro volta importano altri schemi
							// Se cosi non è non serve serializzarli su file system
							if(!existsRefInternal && !tmpByteArraySchema.isEmpty()) {
								Iterator<String> itSchemas = tmpByteArraySchema.keySet().iterator();
								while (itSchemas.hasNext()) {
									String apiSchemaName = (String) itSchemas.next();
									JsonNode schemaNode = tmpNode.get(apiSchemaName);
									JsonPathExpressionEngine engine = new JsonPathExpressionEngine();
									List<String> refPath = engine.getStringMatchPattern(schemaNode, "$..$ref");
									if(refPath!=null && !refPath.isEmpty()) {
										for (String ref : refPath) {
											String path = this.getRefPath(ref);
											if(path!=null) {
												existsRefInternal = true;
												break;
											}
										}
									}
									if(existsRefInternal) {
										break;
									}
								}
							}
							
							if(existsRefInternal && !tmpByteArraySchema.isEmpty()) {
								Iterator<String> itSchemas = tmpByteArraySchema.keySet().iterator();
								while (itSchemas.hasNext()) {
									String apiSchemaName = (String) itSchemas.next();
									JsonNode schemaNode = tmpNode.get(apiSchemaName);
									byte [] schemaContent = tmpByteArraySchema.get(apiSchemaName);
									
									JsonPathExpressionEngine engine = new JsonPathExpressionEngine();
									List<String> refPath = engine.getStringMatchPattern(schemaNode, "$..$ref");
									String schemaRebuild = null;
									if(refPath!=null && !refPath.isEmpty()) {
										for (String ref : refPath) {
											String path = this.getRefPath(ref);
											if(path!=null) {
												String normalizePath = this.normalizePath(path);
												if(normalizePath!=null && this.fileSchema.containsKey(normalizePath)) {
													if(schemaRebuild==null) {
														schemaRebuild = new String(schemaContent);
													}
													if(schemaRebuild.contains(path)==false) {
														if(path.startsWith("./")) {
															path = path.substring(2);
														}
													}
													File file = this.fileSchema.get(normalizePath);
													while(schemaRebuild.contains(path)) {
														schemaRebuild = schemaRebuild.replace(path, "file://"+file.getAbsolutePath());
													}
												}
											}
										}
									}
									
									File f = this.fileSchema.get(apiSchemaName);
									if(schemaRebuild!=null) {
										FileSystemUtilities.writeFile(f, schemaRebuild.getBytes());
										AbstractUtils utils = null;
										if(ApiSchemaType.JSON.equals(tmpSchemaType.get(apiSchemaName))) {
											utils = JSONUtils.getInstance();
										}
										else {
											utils = YAMLUtils.getInstance();
										}
										schemaNode = utils.getAsNode(schemaRebuild);
										tmpNode.put(apiSchemaName, schemaNode);
										tmpByteArraySchema.put(apiSchemaName,schemaRebuild.getBytes());
									}
									else {
										FileSystemUtilities.writeFile(f, schemaContent);
									}
								}
									
								
								if(!tmpByteArraySchema.isEmpty()) {
									itSchemas = tmpByteArraySchema.keySet().iterator();
									while (itSchemas.hasNext()) {
										String apiSchemaName = (String) itSchemas.next();
										JsonNode schemaNode = tmpNode.get(apiSchemaName);
										if(schemaNode instanceof ObjectNode) {
											ObjectNode objectNode = (ObjectNode) schemaNode;
											Iterator<String> it = objectNode.fieldNames();
											String name = null;
											while (it.hasNext()) {
												name = (String) it.next();
												if("definitions".equalsIgnoreCase(name)) {
													JsonNode internalNode = objectNode.get(name);
													if(internalNode instanceof ObjectNode) {
														ObjectNode internalObjectNode = (ObjectNode) internalNode;
														Iterator<String> itInternal = internalObjectNode.fieldNames();
														while (itInternal.hasNext()) {
															String nameInternal = (String) itInternal.next();
															JsonNode typeDefinition = internalObjectNode.get(nameInternal);
															
															ByteArrayOutputStream bout = new ByteArrayOutputStream();
															bout.write("{".getBytes());
															String defOggetto = JSONUtils.getInstance().toString(typeDefinition);
															defOggetto = defOggetto.trim();
															defOggetto = defOggetto.replaceAll("#/components/schemas", "#/definitions");
															if(defOggetto.startsWith("{")) {
																defOggetto = defOggetto.substring(1);
															}
															if(defOggetto.endsWith("}")) {
																defOggetto = defOggetto.substring(0,defOggetto.length()-1);
															}
															defOggetto = defOggetto.trim();
															bout.write(defOggetto.getBytes());
															// DEVE ESSERE DEFINITO NEL JSON SCHEMA, NON POSSO DEFINIRLO STATICAMENTE
															/*
															bout.write(",\"additionalProperties\": ".getBytes());
															if(defOggetto.startsWith("\"allOf\"") || defOggetto.startsWith("\"anyOf\"")){
																// INDICARE ARTICOLO CHE SPIEGA
																bout.write("true".getBytes());
															}
															else {
																bout.write("false".getBytes());
															}*/
															bout.write(",".getBytes());
															bout.write("\"definitions\" : ".getBytes());
															String definitionStringSchema = JSONUtils.getInstance().toString(internalNode);
															definitionStringSchema = definitionStringSchema.replaceAll("#/components/schemas", "#/definitions");
															bout.write(definitionStringSchema.getBytes());
															bout.write("}".getBytes());
																									
															IJsonSchemaValidator validator = ValidatorFactory.newJsonSchemaValidator(jsonValidatorAPI);
															JsonSchemaValidatorConfig schemaValidationConfig = new JsonSchemaValidatorConfig();
															schemaValidationConfig.setVerbose(config.isVerbose());
															schemaValidationConfig.setAdditionalProperties(policyAdditionalProperties);
															schemaValidationConfig.setPoliticaInclusioneTipi(POLITICA_INCLUSIONE_TIPI.DEFAULT);
															schemaValidationConfig.setEmitLogError(config.isEmitLogError());
															schemaValidationConfig.setJsonSchemaVersion(VersionFlag.V4); // https://github.com/OAI/OpenAPI-Specification/blob/main/schemas/v3.0/schema.json
															//System.out.println("ADD SCHEMA PER ["+apiSchemaName+"#"+nameInternal+"] ["+new String(bout.toByteArray())+"]");
															validator.setSchema(bout.toByteArray(), schemaValidationConfig, log);
															
															this.validatorMap.put(apiSchemaName+"#"+nameInternal, validator);
															
														}
													}
												}
											}
										}
									
									}
									
								}
								
							}
						}
					}
				}
				
				
				/* *** Validatore Principale *** */
				
				if(apiValidatorStructure!=null) {
					this.validatorMap = apiValidatorStructure.getValidatorMap();
				}
				else {
					if(schemiValidatorePrincipale!=null && schemiValidatorePrincipale.size()>0) {
						Iterator<String> it = schemiValidatorePrincipale.keySet().iterator();
						while (it.hasNext()) {
							String schemaName = (String) it.next();
							byte [] schema = schemiValidatorePrincipale.get(schemaName);
							JsonNode schemaNode = nodeValidatorePrincipale.get(schemaName);
						
							JsonPathExpressionEngine engine = new JsonPathExpressionEngine();
							List<String> refPath = engine.getStringMatchPattern(schemaNode, "$..$ref");
							String schemaRebuild = null;
							if(refPath!=null && !refPath.isEmpty()) {
								for (String ref : refPath) {
									String path = this.getRefPath(ref);
									if(path!=null) {
										String normalizePath = this.normalizePath(path);
										if(normalizePath!=null && this.fileSchema.containsKey(normalizePath)) {
											if(schemaRebuild==null) {
												schemaRebuild = new String(schema);
											}
											if(schemaRebuild.contains(path)==false) {
												if(path.startsWith("./")) {
													path = path.substring(2);
												}
											}
											File file = this.fileSchema.get(normalizePath);
											while(schemaRebuild.contains(path)) {
												schemaRebuild = schemaRebuild.replace(path, "file://"+file.getAbsolutePath());
											}
										}
									}
								}
							}
							if(schemaRebuild!=null) {
								schema = schemaRebuild.getBytes();
							}		
							
							// Costruisco validatore
							IJsonSchemaValidator validator = ValidatorFactory.newJsonSchemaValidator(jsonValidatorAPI);
							JsonSchemaValidatorConfig schemaValidationConfig = new JsonSchemaValidatorConfig();
							schemaValidationConfig.setVerbose(config.isVerbose());
							schemaValidationConfig.setAdditionalProperties(policyAdditionalProperties);
							schemaValidationConfig.setPoliticaInclusioneTipi(POLITICA_INCLUSIONE_TIPI.DEFAULT);
							schemaValidationConfig.setEmitLogError(config.isEmitLogError());
							schemaValidationConfig.setJsonSchemaVersion(VersionFlag.V4); // https://github.com/OAI/OpenAPI-Specification/blob/main/schemas/v3.0/schema.json
							//System.out.println("ADD SCHEMA PER ["+schemaName+"] ["+new String(schema)+"]");
							validator.setSchema(schema, schemaValidationConfig, log);
							
							this.validatorMap.put(schemaName, validator);
						}
					}
				}
				
				
				
				// Salvo informazioni ricostruite
				if(apiValidatorStructure==null) {
					OpenapiApiValidatorStructure validationStructure = new OpenapiApiValidatorStructure();
					validationStructure.setSchemiValidatorePrincipale(schemiValidatorePrincipale);
					validationStructure.setNodeValidatorePrincipale(nodeValidatorePrincipale);
					validationStructure.setFileSchema(this.fileSchema);
					validationStructure.setValidatorMap(this.validatorMap);
					if(openapiApi!=null) {
						openapiApi.setValidationStructure(validationStructure);
					}
					else if(apiRest!=null) {
						apiRest.addVendorImpl(VALIDATION_STRUCTURE, validationStructure);
					}
				}
				
								
			} catch(Throwable e) {
				try {
					this.close(log, api, config); // per chiudere eventuali risorse parzialmente inizializzate
				}catch(Throwable t) {}
				
				throw new ProcessingException(e);
			}
			
		}finally {
			this.semaphore.release("init");
		}
	}

	private String getRefPath(String ref) {
		if(ref.trim().startsWith("#")) {
			return null;
		}
		return ref.trim().substring(0, ref.indexOf("#"));
	}
	private static String getRefType(String ref) {
		if(ref.trim().startsWith("#")) {
			return ref;
		}
		return ref.trim().substring(ref.indexOf("#"), ref.length());
	}
	private String normalizePath(String path) throws ProcessingException {
		if(path.startsWith("http://") || path.startsWith("https://") || path.startsWith("file://")){	
			try {
				URL url = new URL(path);
				File fileUrl = new File(url.getFile());
				return fileUrl.getName();
			}catch(Exception e) {
				throw new ProcessingException(e.getMessage(),e);
			}
		}
		else{
			File f = new File(path);
			return f.getName();
		}
	}
	private void normalizeRefs(JsonNode node) throws ProcessingException {
		List<JsonNode> listRef = node.findParents(OAI3SchemaKeywords.$REF);
		if(listRef!=null) {
			for (JsonNode jsonNodeRef : listRef) {
				//System.out.println("REF ("+jsonNodeRef.getClass().getName()+") : "+jsonNodeRef);
				if(jsonNodeRef instanceof ObjectNode) {
					ObjectNode oNode = (ObjectNode) jsonNodeRef;
					JsonNode valore = oNode.get(OAI3SchemaKeywords.$REF);
					String ref = valore.asText();
					//System.out.println("VALORE:"+v);
					String path = getRefPath(ref);
					if(path!=null) {
						String normalizePath = normalizePath(path);
						String refType = getRefType(ref);
						//System.out.println("REF ("+jsonNodeRef.getClass().getName()+") : "+jsonNodeRef);
						//System.out.println("Tipo ("+refType+") VALORE:"+normalizePath);
						oNode.remove(OAI3SchemaKeywords.$REF);
						oNode.put(OAI3SchemaKeywords.$REF, normalizePath+refType);
					}
				}
			}
		}
	}
	
	@Override
	public void close(Logger log, Api api, ApiValidatorConfig config) throws ProcessingException{
		if(this.fileSchema!=null) {
			Iterator<String> itFiles = this.fileSchema.keySet().iterator();
			while (itFiles.hasNext()) {
				String key = (String) itFiles.next();
				File file = this.fileSchema.get(key);
				if(file.delete()==false) {
					log.error("Eliminazione file temporaneo ["+file.getAbsolutePath()+"] associato allo schema ["+key+"] non riuscita");
				}
			}
		}
	}
	
	@Override
	public void validate(HttpBaseEntity<?> httpEntity)
			throws ProcessingException, ValidatorException {
		List<Object> args = new ArrayList<>();
		super.validate(this.api, httpEntity, args);
	}

	private List<ApiBodyParameter> getBodyParameters(HttpBaseEntity<?> httpEntity, ApiOperation operation){
		List<ApiBodyParameter> bodyParameters = null;
		if(httpEntity instanceof HttpBaseRequestEntity) {
			if(operation.getRequest()!=null) {
				bodyParameters = operation.getRequest().getBodyParameters();
			}
		}
		else if(httpEntity instanceof HttpBaseResponseEntity<?>) {
				
			HttpBaseResponseEntity<?> response = (HttpBaseResponseEntity<?>) httpEntity;
			ApiResponse apiResponseFound = null;
			ApiResponse apiResponseDefault = null;
			
			if(operation.getResponses()!=null) {
				for (ApiResponse apiResponse : operation.getResponses()) {
					if(apiResponse.isDefaultHttpReturnCode()) {
						apiResponseDefault = apiResponse;
					}
					if(response.getStatus() == apiResponse.getHttpReturnCode()){
						apiResponseFound = apiResponse;
						break;
					}										
				}
			}
			
			if(apiResponseFound==null && apiResponseDefault!=null) {
				apiResponseFound = apiResponseDefault;
			}	
			if(apiResponseFound!=null){
				// eventuali errori di stato non trovato sono gestiti successivavemnte nella validazione
				bodyParameters = apiResponseFound.getBodyParameters();
			}
					
		}
		return bodyParameters;
	}
	
	@Override
	public void validatePreConformanceCheck(HttpBaseEntity<?> httpEntity,
			ApiOperation operation, Object... args) throws ProcessingException, ValidatorException {

		List<ApiBodyParameter> bodyParameters = this.getBodyParameters(httpEntity, operation);

		// se e' attivo openApi4j intanto valido subito
		if(this.openApi4j!=null) {
			validateWithOpenApi4j(httpEntity, operation);
		}
		else if(this.swaggerRequestValidator!=null) {
			validateWithSwaggerRequestValidator(httpEntity, operation);
		}
		
		
		// Controllo poi i campi required come controllo aggiuntivo a openApi4j
		boolean required = false;
		if(bodyParameters!=null && !bodyParameters.isEmpty()) {
			for(ApiBodyParameter body: bodyParameters) {
				if(body.isRequired())
					required = true;
			}
		}
		if(required) {
			if(httpEntity.getContent() == null) {
				throw new ValidatorException("Required body undefined");
			}
		}
		
		// infine se non e' attivo openApi4j effettuo la validazione alternativa
		if(this.openApi4j==null && this.swaggerRequestValidator==null) {
			if(bodyParameters!=null && !bodyParameters.isEmpty()) {
			
				try {
					
					boolean isJson =  httpEntity.getContentType()!=null && httpEntity.getContentType().toLowerCase().contains("json"); // supporta per adesso solo json, la validazione xml non è funzionante
					if(isJson) {
					
						//System.out.println("==================== ("+httpEntity.getClass().getName()+") ====================");
						List<IJsonSchemaValidator> validatorLst = getValidatorList(operation, httpEntity);
						//System.out.println("SIZE: "+validatorLst.size());
						boolean valid = false;
						Exception exc = null;
						if(httpEntity.getContent()!=null) {
							byte[] bytes = httpEntity.getContent().toString().getBytes();
							for(IJsonSchemaValidator validator: validatorLst) {
								ValidationResponse response = validator.validate(bytes);
								if(!ESITO.OK.equals(response.getEsito())) {
									exc = response.getException();
								} else {
									valid = true;
								}
							}
						}
						else {
							throw new ValidatorException("Content undefined");
						}
						
						if(!valid) {
							throw new ValidatorException(exc);
						}
						
					}
					
				} catch (ValidationException e) {
					throw new ValidatorException(e);
				}
			}
		}

	}

	/**
	 * @param operation
	 * @return
	 */
	private List<IJsonSchemaValidator> getValidatorList(ApiOperation operation, HttpBaseEntity<?> httpEntity) throws ValidatorException {
		List<IJsonSchemaValidator> lst = new ArrayList<>();
		
		if(this.onlySchemas) {
			if(this.validatorMap!=null) {
				lst.addAll(this.validatorMap.values());
			}
		}
		else {
		
			List<ApiBodyParameter> bodyParameters = this.getBodyParameters(httpEntity, operation);
			
			if(bodyParameters!=null && !bodyParameters.isEmpty()) {
				for(ApiBodyParameter body: bodyParameters) {
					
					String key = null;
					if(body.getElement() instanceof ApiReference) {
						ApiReference apiRef = (ApiReference) body.getElement();
						//System.out.println("API REF ref["+apiRef.getSchemaRef()+"] ["+apiRef.getType()+"]");
						key = apiRef.getSchemaRef()+"#"+apiRef.getType();
					}
					else {
						key = body.getElement().toString();
					}
					
					//System.out.println("SEARCH ["+body.getElement().getClass().getName()+"] ["+body.getElement()+"] key["+key+"] ...");
					
					if(this.validatorMap!=null && this.validatorMap.containsKey(key)) {
						//System.out.println("ADD VALIDATORE ["+key+"]: ["+this.validatorMap.get(key)+"]");
						lst.add(this.validatorMap.get(key));
					}
				}
			}
			
		}
			
		if(lst.isEmpty())
			throw new ValidatorException("Validator not found");
		
		return lst;
	}
	
	private void validateWithOpenApi4j(HttpBaseEntity<?> httpEntity, ApiOperation operation) throws ProcessingException, ValidatorException {
				
		Operation operationOpenApi4j = null;
		Path pathOpenApi4j = null;
		for (String path :this.openApi4j.getPaths().keySet()) {
			Path pathO = this.openApi4j.getPaths().get(path);
			for (String method : pathO.getOperations().keySet()) {
				Operation op = pathO.getOperation(method);
				//System.out.println("CHECK: ["+method+"] "+path);
				String normalizePath = ApiOperation.normalizePath(path);
				if(operation.getHttpMethod().toString().equalsIgnoreCase(method) && operation.getPath().equals(normalizePath)) {
					operationOpenApi4j = op;
					pathOpenApi4j = pathO;
					break;
				}
			}
		}
		if(operationOpenApi4j==null || pathOpenApi4j==null) {
			throw new ProcessingException("Resource "+operation.getHttpMethod()+" "+operation.getPath()+" not found in OpenAPI 3");
		}
		
		try {
		
			ValidationData<Void> vData = new ValidationData<>();
			this.openApi4j.setServers(null); // se lascio la definizione dei server, il validatePath sottostante verifica che la url corrisponda anche con la parte del server
			OperationValidator val = new OperationValidator(this.openApi4j, pathOpenApi4j, operationOpenApi4j);
			
			if(httpEntity instanceof HttpBaseRequestEntity) {
				
				HttpBaseRequestEntity<?> httpRequest = (HttpBaseRequestEntity<?>) httpEntity;
				Request requestOpenApi4j = buildRequestOpenApi4j(httpRequest.getUrl(), httpRequest.getMethod().toString(), 
						httpRequest.getParameters(), httpRequest.getCookies(), httpRequest.getHeaders(),
						httpRequest.getContent());
				if(this.openApi4jConfig.isValidateRequestPath()) {
					val.validatePath(requestOpenApi4j, vData); // LA url fornita deve corrispondere alla parte delle risorse SENZA la parte del server
				}
				if(this.openApi4jConfig.isValidateRequestQuery()) {
					val.validateQuery(requestOpenApi4j, vData);
				}
				if(this.openApi4jConfig.isValidateRequestHeaders()) {
					val.validateHeaders(requestOpenApi4j, vData);
				}
				if(this.openApi4jConfig.isValidateRequestCookie()) {
					val.validateCookies(requestOpenApi4j, vData);
				}
				if(this.openApi4jConfig.isValidateRequestBody()) {
					val.validateBody(requestOpenApi4j, vData);
				}
			}
			else if(httpEntity instanceof HttpBaseResponseEntity<?>) {
					
				HttpBaseResponseEntity<?> response = (HttpBaseResponseEntity<?>) httpEntity;
				Response responseOpenApi4j = buildResponseOpenApi4j(response.getStatus(), response.getHeaders(), 
						response.getContent());
				if(this.openApi4jConfig.isValidateResponseHeaders()) {
					val.validateHeaders(responseOpenApi4j, vData);
				}
				if(this.openApi4jConfig.isValidateResponseBody()) {
					val.validateBody(responseOpenApi4j, vData);
				}
			}
			
			if(vData.isValid()==false) {
				if(vData.results()!=null) {
					throw new ValidatorException(vData.results().toString());
				}
				else {
					throw new ValidatorException("Validation failed");
				}
			}
			
		}catch(ValidatorException e) {
			throw e;
		}catch(Exception e) {
			throw new ProcessingException(e.getMessage(),e);
		}
	}
	private Request buildRequestOpenApi4j(String urlInvocazione, String method, 
			Map<String, List<String>> queryParams, List<Cookie> cookies, Map<String, List<String>> headers, 
			Object content) throws ProcessingException {

		try {
		
			// Method e path
		    final DefaultRequest.Builder builder = new DefaultRequest.Builder(
		    		urlInvocazione,
		    		Request.Method.getMethod(method));
	
		    String queryString = null;
			if(queryParams!=null && !queryParams.isEmpty()) {
				StringBuilder sb = new StringBuilder();
				Iterator<String> keys = queryParams.keySet().iterator();
				while (keys.hasNext()) {
					String key = (String) keys.next();
					List<String> values = queryParams.get(key);
					try{
						key = TransportUtils.urlEncodeParam(key,Charset.UTF_8.getValue());
					}catch(Exception e){
						if(this.log!=null) {
							this.log.error("URLEncode key["+key+"] error: "+e.getMessage(),e);
						}
						else {
							e.printStackTrace(System.out);
						}
					}
					
					for (String value : values) {
						if(sb.length()>0) {
							sb.append("&");
						}
						try{
							value = TransportUtils.urlEncodeParam(value,Charset.UTF_8.getValue());
						}catch(Exception e){
							if(this.log!=null) {
								this.log.error("URLEncode value["+value+"] error: "+e.getMessage(),e);
							}
							else {
								e.printStackTrace(System.out);
							}
						}
						sb.append(key);
						sb.append("=");
						sb.append(value);	
					}
				}
				queryString = sb.toString();
			}
		    
		    // Query string or body
		    if (HttpRequestMethod.GET.toString().equalsIgnoreCase(method)) {
		    	builder.query(queryString);
		    } else {
		    	if(queryString!=null) {
		    		builder.query(queryString); // senno non vengono validati i query parameters
		    	}
		    	if(content!=null) {
		    		String s = null;
		    		byte[] b = null;
		    		InputStream is = null;
		    		if(content instanceof String) {
		    			s = (String) content;
		    		}
		    		else if(content instanceof byte[]) {
		    			b=(byte[])content;
		    		}
		    		else if(content instanceof InputStream) {
		    			is = (InputStream) content;
		    		}
		    		if(s!=null) {
		    			builder.body(Body.from(s));
		    		}
		    		else if(b!=null) {
		    			try(ByteArrayInputStream bin =new ByteArrayInputStream(b)){
		    				builder.body(Body.from(bin));
		    			}
		    		}
		    		else if(is!=null) {
		    			builder.body(Body.from(is));
		    		}
		    		else {
		    			throw new Exception("Type '"+content.getClass().getName()+"' unsupported");
		    		}
		    	}
		    }
	
		    // Cookies
		    if (cookies != null) {
		    	for (Cookie cookie : cookies) {
		    		builder.cookie(cookie.getName(), cookie.getValue());
		    	}
		    }
	
		    // Headers
		    if(headers!=null) {
		    	Iterator<String> headerNames = headers.keySet().iterator();
		    	if (headerNames != null) {
		    		while (headerNames.hasNext()) {
		    			String headerName = headerNames.next();
		    			builder.header(headerName, headers.get(headerName));
		    		}
		    	}
		    }
	
		    return builder.build();
		    
		}catch(Exception e) {
			throw new ProcessingException(e.getMessage(),e);
		}
	}
	
	private static Response buildResponseOpenApi4j(int status, Map<String, List<String>> headers, Object content) throws ProcessingException {
		
		try {
		
			// status
			final DefaultResponse.Builder builder = new DefaultResponse.Builder(status);
			
			// body
			if(content!=null) {
	    		String s = null;
	    		byte[] b = null;
	    		InputStream is = null;
	    		if(content instanceof String) {
	    			s = (String) content;
	    		}
	    		else if(content instanceof byte[]) {
	    			b=(byte[])content;
	    		}
	    		else if(content instanceof InputStream) {
	    			is = (InputStream) content;
	    		}
	    		else if(content instanceof Document) {
	    			b=XMLUtils.getInstance().toByteArray(((Document)content));
	    		}
	    		else if(content instanceof Element) {
	    			b=XMLUtils.getInstance().toByteArray(((Element)content));
	    		}
	    		
	    		if(s!=null) {
	    			builder.body(Body.from(s));
	    		}
	    		else if(b!=null) {
	    			try(ByteArrayInputStream bin =new ByteArrayInputStream(b)){
	    				builder.body(Body.from(bin));
	    			}
	    		}
	    		else if(is!=null) {
	    			builder.body(Body.from(is));
	    		}
	    		else {
	    			throw new Exception("Type '"+content.getClass().getName()+"' unsupported");
	    		}
	    	}
			
		    // Headers
		    if(headers!=null) {
			    Iterator<String> headerNames = headers.keySet().iterator();
			    if (headerNames != null) {
			      while (headerNames.hasNext()) {
			        String headerName = headerNames.next();
			        builder.header(headerName, headers.get(headerName));
			      }
			    }
		    }
		    
		    return builder.build();
		    
		}catch(Exception e) {
			throw new ProcessingException(e.getMessage(),e);
		}
	}
	
	

	private void validateWithSwaggerRequestValidator(HttpBaseEntity<?> httpEntity, ApiOperation gwOperation) throws ProcessingException, ValidatorException {
		
		// IMPROVEMENT: Invece di fare la readOperationsMap, meglio uno switch che fa la getGET, getPOST ecc.. giusta.		
		
		// Dentro l'openApiSwagger ho i path col dollaro, nell'oggetto openapi di openspcoop invece
		// ho il path puliti 		
		Optional<Entry<String, PathItem>> item = this.openApiSwagger
				.getPaths()
				.entrySet()
				.stream()
				.filter( pathItem -> 
						ApiOperation.normalizePath(pathItem.getKey()).equals(gwOperation.getPath()))
				.findFirst();
		
		if(item.isEmpty()) {
			throw new ProcessingException(
					"Resource " + gwOperation.getHttpMethod() + " " + gwOperation.getPath() + " not found in OpenAPI 3");
		}
		
		HttpMethod method = HttpMethod.valueOf(gwOperation.getHttpMethod().toString());
		
		io.swagger.v3.oas.models.Operation swaggerOperation = 
				item.get().getValue().readOperationsMap().get(method);
	
		ApiPath apiPath = new ApiPathImpl(httpEntity.getUrl(), null);
		NormalisedPath requestPath = new NormalisedPathImpl(httpEntity.getUrl(), null);
						
		com.atlassian.oai.validator.model.ApiOperation swaggerValidatorOperation = 
				new com.atlassian.oai.validator.model.ApiOperation(apiPath, requestPath, method, swaggerOperation); 
		
		ValidationReport report;
		
		
		
		if(httpEntity instanceof HttpBaseRequestEntity) {			
		
			HttpBaseRequestEntity<?> request = (HttpBaseRequestEntity<?>) httpEntity;
			var swaggerRequest = buildSwaggerRequest(request);		    
		    report = this.swaggerRequestValidator.validateRequest(swaggerRequest, swaggerValidatorOperation);		    
		}
		else if(httpEntity instanceof HttpBaseResponseEntity<?>) {				
			HttpBaseResponseEntity<?> response = (HttpBaseResponseEntity<?>) httpEntity;
			var swaggerResponse = buildSwaggerResponse(response);
			report = this.swaggerResponseValidator.validateResponse(swaggerResponse, swaggerValidatorOperation);			
		} else {
			throw new ProcessingException("Unknown type for HttpBaseEntity: " + httpEntity.getClass().toString());
		
		}
		
		if (report.hasErrors()) {
			String msgReport = 	SimpleValidationReportFormat.getInstance().apply(report);		
			throw new ValidatorException(msgReport);
		}
		
	}
	



	@Override
	public void validatePostConformanceCheck(HttpBaseEntity<?> httpEntity,
			ApiOperation operation, Object... args) throws ProcessingException,
	ValidatorException {
		
	}

	@Override
	public void validateValueAsType(ApiParameterType parameterType, String value, String type, ApiSchemaTypeRestriction typeRestriction)
			throws ProcessingException, ValidatorException {

		if(type!=null){
			type = type.trim();

			BigDecimal numberValue = null;
			String stringValue = null;
			
			if("string".equalsIgnoreCase(type)){
				stringValue = value;
			}
			else if("byte".equalsIgnoreCase(type) || "unsignedByte".equalsIgnoreCase(type)){
				try{
					byte v = Byte.parseByte(value);
					numberValue = new BigDecimal(v);
				}catch(Throwable e){
					throw new ValidatorException(e.getMessage(),e);
				}
			}
			else if("char".equalsIgnoreCase(type)){
				if(value.length()>1){
					throw new ValidatorException("More than one character");
				}
				stringValue = value;
			}
			else if("double".equalsIgnoreCase(type) || "decimal".equalsIgnoreCase(type)){
				try{
					double v = Double.parseDouble(value);
					numberValue = BigDecimal.valueOf(v);
				}catch(Throwable e){
					throw new ValidatorException(e.getMessage(),e);
				}
			}
			else if("float".equalsIgnoreCase(type)){
				try{
					float v = Float.parseFloat(value);
					numberValue = BigDecimal.valueOf(v);
				}catch(Throwable e){
					throw new ValidatorException(e.getMessage(),e);
				}
			}
			else if("int".equalsIgnoreCase(type) || "integer".equalsIgnoreCase(type) || 
					"positiveInteger".equalsIgnoreCase(type) || "negativeInteger".equalsIgnoreCase(type) ||
					"nonPositiveInteger".equalsIgnoreCase(type) || "nonNegativeInteger".equalsIgnoreCase(type) || 
					"unsignedInt".equalsIgnoreCase(type) ||
					"int32".equalsIgnoreCase(type)){
				try{
					int i = Integer.parseInt(value);
					if("positiveInteger".equalsIgnoreCase(type)){
						if(i<=0){
							throw new ValidatorException("Expected a positive value");
						}
					}
					else if("nonNegativeInteger".equalsIgnoreCase(type)){
						if(i<0){
							throw new ValidatorException("Expected a non negative value");
						}
					}
					else if("negativeInteger".equalsIgnoreCase(type)){
						if(i>=0){
							throw new ValidatorException("Expected a negative value");
						}
					}
					else if("nonPositiveInteger".equalsIgnoreCase(type)){
						if(i>0){
							throw new ValidatorException("Expected a non positive value");
						}
					}
					else if("unsignedInt".equalsIgnoreCase(type)){
						if(i<0){
							throw new ValidatorException("Expected a unsigned value");
						}
					}
					numberValue = BigDecimal.valueOf(i);
				}catch(Throwable e){
					throw new ValidatorException(e.getMessage(),e);
				}
			}
			else if("long".equalsIgnoreCase(type) || "unsignedLong".equalsIgnoreCase(type)||
					"int64".equalsIgnoreCase(type)){
				try{
					long l = Long.parseLong(value);
					if("unsignedLong".equalsIgnoreCase(type)){
						if(l<0){
							throw new ValidatorException("Expected a unsigned value");
						}
					}
					numberValue = new BigDecimal(l);
				}catch(Throwable e){
					throw new ValidatorException(e.getMessage(),e);
				}
			}
			else if("number".equalsIgnoreCase(type)) {
				try{
					// Any numbers.
					try{
						double d = Double.parseDouble(value);
						numberValue = BigDecimal.valueOf(d);
					}catch(Exception e){
						long l = Long.parseLong(value);
						numberValue = new BigDecimal(l);
					}
				}catch(Throwable e){
					throw new ValidatorException(e.getMessage(),e);
				}
			}
			else if("short".equalsIgnoreCase(type) || "unsignedShort".equalsIgnoreCase(type)){
				try{
					short s = Short.parseShort(value);
					if("unsignedShort".equalsIgnoreCase(type)){
						if(s<0){
							throw new ValidatorException("Expected a unsigned value");
						}
					}
					numberValue = new BigDecimal(s);
				}catch(Throwable e){
					throw new ValidatorException(e.getMessage(),e);
				}
			}
			else if("boolean".equalsIgnoreCase(type)){
				try{
					if(!"true".equals(value) && !"false".equals(value)) {
						throw new Exception("Only true/false value expected (found: "+value+"); Note that truthy and falsy values such as \"true\", \"\", 0 or null are not considered boolean values.");
					}
				}catch(Throwable e){
					throw new ValidatorException(e.getMessage(),e);
				}
			}
			else if("anyURI".equalsIgnoreCase(type)){
				try{
					new URI(value);
				}catch(Throwable e){
					throw new ValidatorException(e.getMessage(),e);
				}
			}
			else if("uuid".equalsIgnoreCase(type)){
				try{
					UUID.fromString(value);
				}catch(Throwable e){
					throw new ValidatorException(e.getMessage(),e);
				}
			}
			else if("date-time".equalsIgnoreCase(type)){
				try {
					DateUtils.validateDateTimeAsRFC3339_sec5_6(value);
				}catch(Throwable e){
					throw new ValidatorException(e.getMessage(),e);
				}
			}
			else if("date".equalsIgnoreCase(type)){
				try {
					DateUtils.validateDateAsRFC3339_sec5_6(value);
				}catch(Throwable e){
					throw new ValidatorException(e.getMessage(),e);
				}
			}
//			else if("time".equalsIgnoreCase(type)){
//				try {
//					DateUtils.validateTimeAsRFC3339_sec5_6(value);
//				}catch(Throwable e){
//					throw new ValidatorException(e.getMessage(),e);
//				}
//			}
			
			if(typeRestriction!=null) {
				
				if(numberValue!=null) {
					
					// max
					if(typeRestriction.getMaximum()!=null) {
						int compare = numberValue.compareTo(typeRestriction.getMaximum());
						if(compare<0) {
							// numberValue < maximum
						}
						else if(compare>0) {
							// numberValue > maximum
							throw new ValidatorException("Value higher than the maximum '"+typeRestriction.getMaximum()+"'");
						}
						else {
							// numberValue == maximum
							if(typeRestriction.getExclusiveMaximum()!=null && typeRestriction.getExclusiveMaximum()) {
								throw new ValidatorException("Value equals to the maximum '"+typeRestriction.getMaximum()+"' and exclusive maximum is enabled");
							}
						}
					}
					
					// min
					if(typeRestriction.getMinimum()!=null) {
						int compare = numberValue.compareTo(typeRestriction.getMinimum());
						if(compare<0) {
							// numberValue < minimum
							throw new ValidatorException("Value lowest than the minimum '"+typeRestriction.getMinimum()+"'");
						}
						else if(compare>0) {
							// numberValue > minimum
						}
						else {
							// numberValue == minimum
							if(typeRestriction.getExclusiveMinimum()!=null && typeRestriction.getExclusiveMinimum()) {
								throw new ValidatorException("Value equals to the minimum '"+typeRestriction.getMinimum()+"' and exclusive minimum is enabled");
							}
						}
					}
					
					// multipleOf
					if(typeRestriction.getMultipleOf()!=null) {
						if (numberValue.compareTo(typeRestriction.getMultipleOf()) != 0) {
						   try{
							   @SuppressWarnings("unused")
							   BigDecimal bd = numberValue.divide(typeRestriction.getMultipleOf(), 0, RoundingMode.UNNECESSARY);
						   }
						   catch(ArithmeticException e) {
							   throw new ValidatorException("Value is not multiple of '"+typeRestriction.getMultipleOf()+"'");
						   }
						}
					}
				}
				
				if(stringValue!=null) {
					
					// enum
					if(typeRestriction.getEnumValues()!=null && !typeRestriction.getEnumValues().isEmpty()) {
						
						List<String> valoriPresenti = new ArrayList<>();
						if(typeRestriction.isArrayParameter()) {
							if(ApiParameterType.query.equals(parameterType) || ApiParameterType.form.equals(parameterType)) {
								if(typeRestriction.isStyleQueryForm() || typeRestriction.getStyle()==null) { // form è il default
									if(typeRestriction.isExplodeDisabled()) {
										List<String> l = StringUtil.tokenize(stringValue, ",", false, false);
										if(l!=null && !l.isEmpty()) {
											valoriPresenti.addAll(l);
										}
									}
								}
								else if(typeRestriction.isStyleQuerySpaceDelimited()) {
									if(typeRestriction.isExplodeDisabled()) {
										List<String> l = StringUtil.tokenize(stringValue, Pattern.quote(" "), false, false);
										if(l!=null && !l.isEmpty()) {
											valoriPresenti.addAll(l);
										}
									}
								}
								else if(typeRestriction.isStyleQueryPipeDelimited()) {
									if(typeRestriction.isExplodeDisabled()) {
										List<String> l = StringUtil.tokenize(stringValue, Pattern.quote("|"), false, false);
										if(l!=null && !l.isEmpty()) {
											valoriPresenti.addAll(l);
										}
									}
								}
							}
							else if(ApiParameterType.header.equals(parameterType)) {
								if(typeRestriction.isStyleHeaderSimple() || typeRestriction.getStyle()==null) { // simple è il default
									List<String> l = StringUtil.tokenize(stringValue, ",", false, false);
									if(l!=null && !l.isEmpty()) {
										valoriPresenti.addAll(l);
									}
								}
							}
							else if(ApiParameterType.path.equals(parameterType)) {
								if(typeRestriction.isStylePathSimple() || typeRestriction.getStyle()==null) { // simple è il default
									List<String> l = StringUtil.tokenize(stringValue, ",", false, false);
									if(l!=null && !l.isEmpty()) {
										valoriPresenti.addAll(l);
									}
								}
								else if(typeRestriction.isStylePathLabel()) {
									if(stringValue.length()>1) {
										String splitPattern = typeRestriction.isExplodeEnabled() ?  "\\." : ",";
										String [] v = stringValue.substring(1).split(splitPattern);
										if(v!=null && v.length>0) {
											for (String valore : v) {
												valoriPresenti.add(valore);
											}
										}
									}
								}
								else if(typeRestriction.isStylePathMatrix()) {
									String splitPattern = typeRestriction.isExplodeEnabled() ?  ";" : ",";
									List<String> l = getArrayValues(typeRestriction.isExplodeEnabled(), stringValue, splitPattern);
									if(l!=null && !l.isEmpty()) {
										valoriPresenti.addAll(l);
									}
								}
							}
						}
						if(valoriPresenti.isEmpty()) {
							valoriPresenti.add(stringValue);
						}
						
						for (String valorePresente : valoriPresenti) {
							boolean found = false;
							StringBuilder sbList = new StringBuilder();
							for (Object o : typeRestriction.getEnumValues()) {
								if(o!=null) {
									String check = o.toString();
									if(sbList.length()>0) {
										sbList.append(",");
									}
									sbList.append(check);
									if(valorePresente.equals(check)) {
										found = true;
										break;
									}
								}
							}
							if(!found) {
								throw new ValidatorException("Uncorrect enum value '"+valorePresente+"', expected: '"+sbList.toString()+"'");
							}
						}
					}
					
					// min length
					if(typeRestriction.getMinLength()!=null) {
						if(stringValue.length()<typeRestriction.getMinLength().intValue()) {
							throw new ValidatorException("Too short, expected min length '"+typeRestriction.getMinLength()+"'");
						}
					}
					
					// max length
					if(typeRestriction.getMaxLength()!=null) {
						if(stringValue.length()>typeRestriction.getMaxLength().intValue()) {
							throw new ValidatorException("Too big, expected max length '"+typeRestriction.getMaxLength()+"'");
						}
					}
					
					/*
					 * Note that the regular expression is enclosed in the ^…$ tokens, where ^ means the beginning of the string, and $ means the end of the string. 
					 * Without ^…$, pattern works as a partial match, that is, matches any string that contains the specified regular expression. 
					 * For example, pattern: pet matches pet, petstore and carpet. The ^…$ token forces an exact match.
					 **/
					if(typeRestriction.getPattern()!=null) {
						String pattern = typeRestriction.getPattern().trim();
						try {
							if(pattern.startsWith("^") && pattern.endsWith("$")) {
								if(!RegularExpressionEngine.isMatch(stringValue, pattern)) {
									throw new ValidatorException("Pattern match failed ('"+pattern+"')");
								}
							}
							else {
								if(!RegularExpressionEngine.isFind(stringValue, pattern)) {
									throw new ValidatorException("Pattern match failed ('"+pattern+"')");
								}
							}
						}
						catch(ValidatorException e) {
							throw e;
						}
						catch(Throwable e) {
							throw new ValidatorException("Pattern validation error '"+pattern+"': "+e.getMessage(),e);
						}
					}
				}
			}
			
		}

		// altri tipi non li valido per ora


	}

	private static final Pattern PREFIXED_SEMICOLON_NAME_REGEX = Pattern.compile("(?:;)([^;]+)(?:=)([^;]*)");
	private List<String> getArrayValues(boolean explode, String rawValue, String splitPattern) {
		
		try {
			Matcher matcher = PREFIXED_SEMICOLON_NAME_REGEX.matcher(rawValue);
	
			if (explode) {
				List<String> arrayValues = new ArrayList<>();
				int index = 0;
				int limit = 1000; // per gestire DOS
				while (matcher.find() && index<limit) {
					arrayValues.add(matcher.group(2));
					index++;
				}
				return arrayValues;
			} else {
				return matcher.matches()
						? Arrays.asList(matcher.group(2).split(splitPattern))
								: null;
			}
		}catch(Throwable t) {
			this.log.error(t.getMessage(), t);
			return null;
		}
	}
	
	
	// ================= SWAGGER REQUEST VALIDATOR GLUE CODE =====================

	private static Method fromHttpMethod(HttpRequestMethod method) {
		return Method.valueOf(method.toString());
	}
	
	
	private static com.atlassian.oai.validator.model.Response buildSwaggerResponse(HttpBaseResponseEntity<?> response) throws ProcessingException {

		final SimpleResponse.Builder builder = 
				new SimpleResponse.Builder(response.getStatus());
		
		if(response.getHeaders()!=null && !response.getHeaders().isEmpty()) {
			response.getHeaders().forEach(builder::withHeader);
		}
		
		Object content = response.getContent();
		if(content instanceof String) {
			builder.withBody( (String) content);
		}
		else if(content instanceof byte[]) {
			builder.withBody((byte[]) content);
		}
		else if(content instanceof InputStream) {
			builder.withBody((InputStream) content);
		}
		else if(content instanceof Document) {
			try {
				builder.withBody(XMLUtils.getInstance().toByteArray(((Document)content)));
			} catch (Exception e) {
				throw new ProcessingException(e.getMessage(),e);
			}
		}
		else if(content instanceof Element) {
			try {
				builder.withBody(XMLUtils.getInstance().toByteArray(((Element)content)));
			} catch (Exception e) {
				throw new ProcessingException(e.getMessage(),e);
			}
		}
		else if (content == null) {
			// nop
		} else {
			throw new ProcessingException("Type '"+content.getClass().getName()+"' unsupported");
		}
		
		return builder.build();
	
	}
	
	private static com.atlassian.oai.validator.model.Request buildSwaggerRequest(HttpBaseRequestEntity<?> request) throws ProcessingException {
		Object content = request.getContent();
		final SimpleRequest.Builder builder =
                new SimpleRequest.Builder(fromHttpMethod(request.getMethod()),request.getUrl());
   
		// BugFix: 
		// Request Accept header '*; q=.2' is not a valid media type
		
		// BugFix2:
		// Request Accept header '[text/xml]' does not match any defined response types. Must be one of: [text/*, application/*].
		
		Map<String, List<String>> hdr = request.getHeaders();
		if(hdr!=null && !hdr.isEmpty()) {
			List<String> originalValues = TransportUtils.getValues(hdr, HttpConstants.ACCEPT);
//			List<String> newList = null;
//			if(originalValues!=null && !originalValues.isEmpty()) {
//				newList = new ArrayList<>();
//				for (String original : originalValues) {
//					
//					String [] acceptHeaders = null;
//					if(original.contains(",")) {
//						acceptHeaders = original.split(",");
//						for (int i = 0; i < acceptHeaders.length; i++) {
//							acceptHeaders[i] = acceptHeaders[i].trim();
//						}
//					}
//					else {
//						acceptHeaders = new String [] {original.trim()};
//					}
//					StringBuilder sbNewList = new StringBuilder();
//					for (String hdrCheck : acceptHeaders) {
//						if(hdrCheck!=null && org.apache.commons.lang.StringUtils.isNotEmpty(hdrCheck.trim())) {
//							if(hdrCheck.contains(";")) {
//								String [] tmp = hdrCheck.split(";");
//								String media = tmp[0];
//								if(media!=null && org.apache.commons.lang.StringUtils.isNotEmpty(media.trim())) {
//									if(sbNewList.length()>0) {
//										sbNewList.append(", ");
//									}
//									String v = media.trim();
//									if(v.equals("*")) {
//										v = "*/*";
//									}
//									sbNewList.append(v);
//								}
//							}
//							else {
//								if(sbNewList.length()>0) {
//									sbNewList.append(", ");
//								}
//								String v = hdrCheck.trim();
//								if(v.equals("*")) {
//									v = "*/*";
//								}
//								sbNewList.append(v);
//							}
//						}
//					}
//					if(sbNewList.length()>0) {
//						newList.add(sbNewList.toString());
//					}
//				}
//				TransportUtils.removeRawObject(hdr, HttpConstants.ACCEPT);
//				if(!newList.isEmpty()) {
//					hdr.put(HttpConstants.ACCEPT, newList);
//				}
//			}
			TransportUtils.removeRawObject(hdr, HttpConstants.ACCEPT); // lo rimuovo direttamente e non lo faccio validare per risolvere BugFix2
			
			if(!hdr.isEmpty()) {
				hdr.forEach(builder::withHeader);
			}
			
			//if(newList!=null) {
			if(originalValues!=null) {
				// ripristino
				//TransportUtils.removeRawObject(hdr, HttpConstants.ACCEPT);
				hdr.put(HttpConstants.ACCEPT, originalValues);
			}
		}
		
		if(request.getParameters()!=null && !request.getParameters().isEmpty()) {
			request.getParameters().forEach(builder::withQueryParam);
		}
		if (request.getCookies() != null && !request.getCookies().isEmpty()) {
			Cookie cookie = request.getCookies().get(0);
			String cookiesValue = cookie.getName() + HttpConstants.COOKIE_NAME_VALUE_SEPARATOR + cookie.getValue();
						
			for (int i = 1; i < request.getCookies().size(); i++) {
				cookie = request.getCookies().get(i);
				cookiesValue = cookiesValue + HttpConstants.COOKIE_SEPARATOR+" " + cookie.getName() + HttpConstants.COOKIE_NAME_VALUE_SEPARATOR + cookie.getValue();  
			}
			builder.withHeader(HttpConstants.COOKIE, cookiesValue);
		}
	                        
		if(content instanceof String) {
			builder.withBody((String) content);
		}
		else if(content instanceof byte[]) {
			builder.withBody((byte[]) content);
		}
		else if(content instanceof InputStream) {
			builder.withBody( (InputStream) content);
		}
		else if(content != null){
			throw new ProcessingException("Type '"+content.getClass().getName()+"' unsupported");
		}
		
		return builder.build();
	}
	
	
	/** 
	 * 
	 * 	Questo codice è copiato dall'OpenApiLoader di atlassian, utilizzato perchè il 
	 * 	resolverfully fa modifiche alla openApi che vanno a interferire con la validazione
	 */
	 // Adding this method to strip off the object type association applied by
    // io.swagger.v3.parser.util.ResolverFully (ln 410) where the operation sets
    // type field to "object" if type field is null. This causes issues for anyOf
    // and oneOf validations.
	
    protected static void removeTypeObjectAssociationWithOneOfAndAnyOfModels(final OpenAPI openAPI) {
        if (openAPI.getComponents() != null) {
            removeTypeObjectFromEachValue(openAPI.getComponents().getSchemas(), schema -> schema);
        }
    }

    private static <T> void removeTypeObjectFromEachValue(final Map<String, T> map, final Function<T, Object> function) {
        if (map != null) {
            map.values().forEach(it -> removeTypeObjectAssociationWithOneOfAndAnyOfFromSchema(function.apply(it)));
        }
    }

    private static void removeTypeObjectAssociationWithOneOfAndAnyOfFromSchema(final Object object) {
        if (object instanceof ObjectSchema) {
            removeTypeObjectFromEachValue(((ObjectSchema) object).getProperties(), schema -> schema);
        } else if (object instanceof ArraySchema) {
            removeTypeObjectAssociationWithOneOfAndAnyOfFromSchema(((ArraySchema) object).getItems());
        } else if (object instanceof ComposedSchema) {
            final ComposedSchema composedSchema = (ComposedSchema) object;
            composedSchema.setType(null);
        }
    }

    /**
     * Removes the Base64 pattern on the {@link OpenAPI} model.
     * <p>
     * If that pattern would stay on the model all fields of type string / byte would be validated twice. Once
     * with the {@link com.github.fge.jsonschema.keyword.validator.common.PatternValidator} and once with
     * the {@link com.atlassian.oai.validator.schema.format.Base64Attribute}.
     * To improve validation performance and memory footprint the pattern on string / byte fields will be
     * removed - so the PatternValidator will not be triggered for those kind of fields.
     *
     * @param openAPI the {@link OpenAPI} to correct
     */
    protected static void removeRegexPatternOnStringsOfFormatByte(final OpenAPI openAPI) {
        if (openAPI.getPaths() != null) {
            openAPI.getPaths().values().forEach(pathItem -> {
                pathItem.readOperations().forEach(operation -> {
                    excludeBase64PatternFromEachValue(operation.getResponses(), io.swagger.v3.oas.models.responses.ApiResponse::getContent);
                    if (operation.getRequestBody() != null) {
                        excludeBase64PatternFromSchema(operation.getRequestBody().getContent());
                    }
                    if (operation.getParameters() != null) {
                        operation.getParameters().forEach(it -> excludeBase64PatternFromSchema(it.getContent()));
                        operation.getParameters().forEach(it -> excludeBase64PatternFromSchema(it.getSchema()));
                    }
                });
            });
        }
        if (openAPI.getComponents() != null) {
            excludeBase64PatternFromEachValue(openAPI.getComponents().getResponses(), io.swagger.v3.oas.models.responses.ApiResponse::getContent);
            excludeBase64PatternFromEachValue(openAPI.getComponents().getRequestBodies(), RequestBody::getContent);
            excludeBase64PatternFromEachValue(openAPI.getComponents().getHeaders(), Header::getContent);
            excludeBase64PatternFromEachValue(openAPI.getComponents().getHeaders(), Header::getSchema);
            excludeBase64PatternFromEachValue(openAPI.getComponents().getParameters(), Parameter::getContent);
            excludeBase64PatternFromEachValue(openAPI.getComponents().getParameters(), Parameter::getSchema);
            excludeBase64PatternFromEachValue(openAPI.getComponents().getSchemas(), schema -> schema);
        }
    }

    private static <T> void excludeBase64PatternFromEachValue(final Map<String, T> map, final Function<T, Object> function) {
        if (map != null) {
            map.values().forEach(it -> excludeBase64PatternFromSchema(function.apply(it)));
        }
    }

    private static void excludeBase64PatternFromSchema(final Object object) {
        if (object instanceof Content) {
            excludeBase64PatternFromEachValue((Content) object, MediaType::getSchema);
        } else if (object instanceof ObjectSchema) {
            excludeBase64PatternFromEachValue(((ObjectSchema) object).getProperties(), schema -> schema);
        } else if (object instanceof ArraySchema) {
            excludeBase64PatternFromSchema(((ArraySchema) object).getItems());
        } else if (object instanceof StringSchema) {
            final StringSchema stringSchema = (StringSchema) object;
            // remove the pattern _only_ if it's a String / Byte field
            if ("byte".equals(stringSchema.getFormat())) {
                stringSchema.setPattern(null);
            }
        }
    }
	
}