OpenapiApi.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;
import java.net.URL;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang.StringUtils;
import org.openapi4j.core.model.v3.OAI3Context;
import org.openapi4j.core.util.TreeUtil;
import org.openapi4j.core.validation.ValidationResults;
import org.openapi4j.parser.model.v3.OpenApi3;
import org.openapi4j.parser.validation.v3.OpenApi3Validator;
import org.openspcoop2.utils.Utilities;
import org.openspcoop2.utils.json.JSONUtils;
import org.openspcoop2.utils.json.JsonPathExpressionEngine;
import org.openspcoop2.utils.json.JsonPathNotFoundException;
import org.openspcoop2.utils.json.YAMLUtils;
import org.openspcoop2.utils.rest.ApiFormats;
import org.openspcoop2.utils.rest.ParseWarningException;
import org.openspcoop2.utils.rest.ProcessingException;
import org.openspcoop2.utils.rest.api.Api;
import com.fasterxml.jackson.databind.JsonNode;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.media.Schema;
/**
* SwaggerApi
*
* @author Andrea Poli (apoli@link.it)
* @author $Author$
* @version $Rev$, $Date$
*
*/
public class OpenapiApi extends Api {
/**
*
*/
private static final long serialVersionUID = 1L;
private transient OpenAPI api;
private transient Map<String, Schema<?>> definitions;
private String apiRaw;
private String parseWarningResult;
private ApiFormats format;
// struttura una volta che l'api è stata inizializzata per la validazione (e' serializzabile e cachabile)
private OpenapiApiValidatorStructure validationStructure;
public OpenapiApi(ApiFormats format, OpenAPI swagger, String apiRaw, String parseWarningResult) {
this.format = format;
this.api = swagger;
this.apiRaw = apiRaw;
this.parseWarningResult = parseWarningResult;
this.definitions = new HashMap<String, Schema<?>>();
}
public ApiFormats getFormat() {
return this.format;
}
public OpenAPI getApi() {
return this.api;
}
public String getApiRaw() {
return this.apiRaw;
}
public String getParseWarningResult() {
return this.parseWarningResult;
}
public Map<String, Schema<?>> getDefinitions() {
return this.definitions;
}
public Map<String, Schema<?>> getAllDefinitions() {
Map<String, Schema<?>> map = new HashMap<>();
map.putAll(this.getDefinitions());
if(this.api.getComponents() != null && this.api.getComponents().getSchemas() != null)
for(String k: this.api.getComponents().getSchemas().keySet()) {
map.put(k, this.api.getComponents().getSchemas().get(k));
}
return map;
}
public void setDefinitions(Map<String, Schema<?>> definitions) {
this.definitions = definitions;
}
public OpenapiApiValidatorStructure getValidationStructure() {
return this.validationStructure;
}
public void setValidationStructure(OpenapiApiValidatorStructure validationStructure) {
this.validationStructure = validationStructure;
}
@Override
public void validate() throws ProcessingException,ParseWarningException {
this.validate(false);
}
@Override
public void validate(boolean validateBodyParameterElement) throws ProcessingException, ParseWarningException {
this.validate(false, false);
}
@Override
public void validate(boolean usingFromSetProtocolInfo, boolean validateBodyParameterElement) throws ProcessingException, ParseWarningException {
// Primo valido l'openapi
if(!usingFromSetProtocolInfo) {
// Se valido poi non riesco a caricare OpenAPI che comunque non sono validi
if(ApiFormats.OPEN_API_3.equals(this.format)) {
this._validateOpenAPI3();
}
}
// Valido la struttura
super.validate(usingFromSetProtocolInfo, validateBodyParameterElement);
// Se ho trovato dei warning li emetto
if(this.parseWarningResult!=null && StringUtils.isNotEmpty(this.parseWarningResult)) {
throw new ParseWarningException("\n"+this.parseWarningResult);
}
}
private void _validateOpenAPI3() throws ProcessingException {
try {
YAMLUtils yamlUtils = YAMLUtils.getInstance();
JSONUtils jsonUtils = JSONUtils.getInstance();
boolean apiRawIsYaml = yamlUtils.isYaml(this.apiRaw);
JsonNode schemaNodeRoot = null;
if(apiRawIsYaml) {
schemaNodeRoot = yamlUtils.getAsNode(this.apiRaw);
}
else {
schemaNodeRoot = jsonUtils.getAsNode(this.apiRaw);
}
JsonPathExpressionEngine engine = new JsonPathExpressionEngine();
List<String> refPath = null;
try {
refPath = engine.getStringMatchPattern(schemaNodeRoot, "$..$ref");
}catch(JsonPathNotFoundException notFound){
//System.out.println("NOT FOUND: "+notFound.getMessage());
}
boolean refNonRisolvibili = false;
if(refPath!=null && !refPath.isEmpty()) {
for (String refP : refPath) {
if(refP!=null) {
String ref = refP.trim();
if(ref.startsWith("#")) {
continue; // relativo verso il file stesso
}
else {
refNonRisolvibili = true; // ref verso altri file (emettero solo il warning se c'è)
break;
}
}
}
}
if(!refNonRisolvibili) {
// Costruisco OpenAPI3
OAI3Context context = null;
OpenApi3 openApi4j = null;
try {
context = new OAI3Context(new URL("file:/"), schemaNodeRoot, null);
openApi4j = TreeUtil.json.convertValue(context.getBaseDocument(), OpenApi3.class);
openApi4j.setContext(context);
}catch(Throwable e) {
throw new ProcessingException(e.getMessage(), e);
}
try {
ValidationResults results = OpenApi3Validator.instance().validate(openApi4j);
if(!results.isValid()) {
throw new ProcessingException(results.toString());
}
}catch(org.openapi4j.core.validation.ValidationException valExc) {
if(valExc.results()!=null) {
throw new ProcessingException(valExc.results().toString());
}
else {
throw new ProcessingException(valExc.getMessage());
}
}catch(ProcessingException pe) {
throw pe;
}catch(Throwable e) {
e.printStackTrace(System.out);
Throwable eAnalyze = Utilities.getInnerNotEmptyMessageException(e);
throw new ProcessingException(eAnalyze!=null ? eAnalyze.getMessage(): e.getMessage(), e);
}
}
}
catch(ProcessingException pe) {
if(this.parseWarningResult!=null && StringUtils.isNotEmpty(this.parseWarningResult)) {
throw new ProcessingException("\n"+this.parseWarningResult+"\n"+pe.getMessage());
}
else {
throw new ProcessingException(pe.getMessage());
}
}
catch(Exception e) {
throw new ProcessingException(e.getMessage(),e);
}
}
}