NetworkNTJsonschemaValidator.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.json.validation;
import java.io.ByteArrayOutputStream;
import java.util.Set;
import org.openspcoop2.utils.LoggerWrapperFactory;
import org.openspcoop2.utils.json.IJsonSchemaValidator;
import org.openspcoop2.utils.json.JSONUtils;
import org.openspcoop2.utils.json.JsonSchemaValidatorConfig;
import org.openspcoop2.utils.json.ValidationException;
import org.openspcoop2.utils.json.ValidationResponse;
import org.openspcoop2.utils.json.ValidationResponse.ESITO;
import org.slf4j.Logger;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.networknt.schema.JsonSchema;
import com.networknt.schema.JsonSchemaFactory;
import com.networknt.schema.SchemaValidatorsConfig;
import com.networknt.schema.SpecVersion;
import com.networknt.schema.SpecVersionDetector;
import com.networknt.schema.ValidationMessage;
/**
* NetworkNTJsonschemaValidator
*
* @author Giovanni Bussu (bussu@link.it)
* @author $Author$
* @version $Rev$, $Date$
*/
public class NetworkNTJsonschemaValidator implements IJsonSchemaValidator {
/*
* NOTA: in caso di errore 'should be valid to one and only one of the schemas'
*
* Significa che più elementi possono matchare in un oneOf. Questo succede se ad esempio non è stato definito "additionalProperties: false" in ogni oggetto riferito dal oneOf
*
* NOTA2: Aggiungendo le additionalProperties a false, se si aggiungono male e non si ha un match con nessun oggetto, viene ritornato l'errore relativo ad uno dei tre a caso.
* */
private JsonSchema schema;
private byte[] schemaBytes;
private JsonNode jsonSchema;
private ObjectMapper mapper;
private Logger log;
private boolean logError;
// Tramite un cast e' possibile cosi personalizzare lo schema validator con le opzioni di NT
private SchemaValidatorsConfig schemaValidatorConfig = null;
public SchemaValidatorsConfig getSchemaValidatorConfig() {
return this.schemaValidatorConfig;
}
public void setSchemaValidatorConfig(SchemaValidatorsConfig schemaValidatorConfig) {
this.schemaValidatorConfig = schemaValidatorConfig;
}
/**
*
*/
public NetworkNTJsonschemaValidator() {
this.mapper = new ObjectMapper();
}
@Override
public void setSchema(byte[] schema, JsonSchemaValidatorConfig config, Logger log) throws ValidationException {
this.log = log;
if(this.log==null) {
this.log = LoggerWrapperFactory.getLogger(NetworkNTJsonschemaValidator.class);
}
this.logError = config!=null ? config.isEmitLogError() : true;
this.schemaBytes = schema;
try {
JsonNode jsonSchema = this.mapper.readTree(schema);
SpecVersion.VersionFlag version = null;
if(config!=null && config.getJsonSchemaVersion()!=null) {
version = config.getJsonSchemaVersion();
}
else {
version = SpecVersionDetector.detect(jsonSchema);
}
JsonSchemaFactory factory = JsonSchemaFactory.getInstance(version);
if(config!=null) {
switch(config.getAdditionalProperties()) {
case DEFAULT:
break;
case FORCE_DISABLE: ValidationUtils.disableAdditionalProperties(this.mapper, jsonSchema, true, true);
break;
case FORCE_STRING: ValidationUtils.disableAdditionalProperties(this.mapper, jsonSchema, false, true);
break;
case IF_NULL_DISABLE: ValidationUtils.disableAdditionalProperties(this.mapper, jsonSchema, true, false);
break;
case IF_NULL_STRING: ValidationUtils.disableAdditionalProperties(this.mapper, jsonSchema, false, false);
break;
default:
break;
}
}
if(config!=null) {
switch(config.getPoliticaInclusioneTipi()) {
case DEFAULT:
break;
case ALL: ValidationUtils.addTypes(this.mapper, jsonSchema, config.getTipi(), true);
break;
case ANY: ValidationUtils.addTypes(this.mapper, jsonSchema, config.getTipi(), false);
break;
default:
break;
}
}
if(config!=null && config.isVerbose()) {
try {
ByteArrayOutputStream bout = new ByteArrayOutputStream();
JSONUtils.getInstance(true).writeTo(jsonSchema, bout);
bout.flush();
bout.close();
this.log.debug("JSON Schema: "+bout.toString());
}catch(Exception e) {
this.log.debug("JSON Schema build error: "+e.getMessage(),e);
}
}
this.jsonSchema = jsonSchema;
/*
* https://github.com/networknt/json-schema-validator/blob/master/doc/config.md
*
* SchemaValidatorsConfig configS = new SchemaValidatorsConfig();
* configS.setHandleNullableField(false);
* configS.setTypeLoose(true);
**/
if(this.schemaValidatorConfig!=null) {
this.schema = factory.getSchema(jsonSchema, this.schemaValidatorConfig);
}
else {
this.schema = factory.getSchema(jsonSchema);
}
} catch(Throwable e) {
throw new ValidationException(e);
}
}
@Override
public ValidationResponse validate(byte[] rawObject) throws ValidationException {
ValidationResponse response = new ValidationResponse();
try {
boolean expectedString = false;
if(this.jsonSchema.has("type")) {
try {
JsonNode type = this.jsonSchema.get("type");
String vType = type.asText();
expectedString = "string".equals(vType);
}catch(Exception e) {}
}
JsonNode node = null;
try {
if(expectedString) {
node = this.mapper.getNodeFactory().textNode(new String(rawObject));
}
else {
node = this.mapper.readTree(rawObject);
}
}
catch(Exception e) {
this.log.error(e.getMessage(),e);
String messageString = "Read rawObject as jsonNode failed: "+e.getMessage();
response.setEsito(ESITO.KO);
if(this.logError) {
ValidationUtils.logError(this.log, messageString.toString(), rawObject, this.schemaBytes, this.jsonSchema);
}
response.setException(new Exception(messageString.toString()));
}
if(node!=null) {
Set<ValidationMessage> validate = this.schema.validate(node);
if(validate.isEmpty()) {
response.setEsito(ESITO.OK);
} else {
response.setEsito(ESITO.KO);
StringBuilder messageString = new StringBuilder();
for(ValidationMessage msg: validate) {
String errorMsg = msg.getCode() + " " + msg.getMessage();
response.getErrors().add(errorMsg);
messageString.append(errorMsg).append("\n");
}
if(this.logError) {
ValidationUtils.logError(this.log, messageString.toString(), rawObject, this.schemaBytes, this.jsonSchema);
}
response.setException(new Exception(messageString.toString()));
}
}
} catch(Exception e) {
throw new ValidationException(e);
}
return response;
}
}