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.wadl.validator;
import java.io.InputStream;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import javax.ws.rs.core.MultivaluedMap;
import javax.xml.validation.Schema;
import org.jvnet.ws.wadl.ast.AbstractNode;
import org.jvnet.ws.wadl.ast.FaultNode;
import org.jvnet.ws.wadl.ast.MethodNode;
import org.jvnet.ws.wadl.ast.RepresentationNode;
import org.jvnet.ws.wadl.ast.ResourceNode;
import org.openspcoop2.utils.rest.AbstractApiValidator;
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.ApiOperation;
import org.openspcoop2.utils.rest.api.ApiSchemaTypeRestriction;
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.wadl.ApplicationWrapper;
import org.openspcoop2.utils.wadl.WADLApi;
import org.openspcoop2.utils.wadl.WADLException;
import org.openspcoop2.utils.wadl.WADLUtilities;
import org.openspcoop2.utils.xml.AbstractValidatoreXSD;
import org.openspcoop2.utils.xml.AbstractXMLUtils;
import org.openspcoop2.utils.xml.ValidatoreXSD;
import org.slf4j.Logger;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
/**
* Validator
*
*
* @author Poli Andrea (apoli@link.it)
* @author $Author$
* @version $Rev$, $Date$
*/
public class Validator extends AbstractApiValidator implements IApiValidator {
private WADLApi wadlApi;
private ApplicationWrapper application;
private Logger log;
private AbstractXMLUtils xmlUtils;
private Schema schema;
private boolean initialize = false;
@Override
public void init(Logger log, Api api, ApiValidatorConfig config) throws WADLException {
try{
this.wadlApi = (WADLApi) api;
this.log = log;
this.application = this.wadlApi.getApplicationWadlWrapper();
this.xmlUtils = config.getXmlUtils();
// Costruisco l'eventuale schema XSD necessario per una validazione rispetto a schemi XSD
if(this.application.getResources().size()>0){
this.schema = this.application.buildSchemaCollection(this.log).buildSchema(this.log);
}
this.initialize = true;
}catch(Exception e){
throw new WADLException(e.getMessage(),e);
}
}
public Validator(){}
@Override
public void close(Logger log, Api api, ApiValidatorConfig config) throws ProcessingException{
}
@Override
public void validate(HttpBaseEntity<?> httpEntity) throws ProcessingException, ValidatorException{
if(!this.initialize){
throw new WADLException("Validatore non inizializzato");
}
List<Object> context = new ArrayList<>();
this.validate(this.wadlApi, httpEntity, context);
}
@SuppressWarnings("unchecked")
@Override
public void validatePreConformanceCheck(HttpBaseEntity<?> httpEntity,ApiOperation operation,Object ... args) throws ProcessingException,ValidatorException{
// Effettuo la validazione del contenuto
// Recupera il resource node corrispondente alla url
ResourceNode resourceNode = WADLUtilities.findResourceNode(this.application.getApplicationNode(),httpEntity.getUrl());
// Recupera il metodo corrispondente al method invocato
MethodNode methodNode = WADLUtilities.findMethodNode(resourceNode,httpEntity.getMethod());
// Aggiungo in context
((List<Object>)args[0]).add(resourceNode);
((List<Object>)args[0]).add(methodNode);
this.validateAgainstXSDSchema(httpEntity, resourceNode, methodNode);
}
@SuppressWarnings("unchecked")
@Override
public void validatePostConformanceCheck(HttpBaseEntity<?> httpEntity,ApiOperation operation,Object ... args) throws ProcessingException,ValidatorException{
// Verifico che un eventuale contenuto che rispetta lo schema, sia esattamente anche quello atteso per l'operazione
ResourceNode resourceNode = (ResourceNode) ((List<Object>)args[0]).get(0);
MethodNode methodNode = (MethodNode) ((List<Object>)args[0]).get(1);
this.wadlConformanceCheck(httpEntity, resourceNode, methodNode);
}
@Override
public void validateValueAsType(ApiParameterType parameterType, String value,String type, ApiSchemaTypeRestriction typeRestriction) throws ProcessingException,ValidatorException{
// Tipi XSD : {http://www.w3.org/2001/XMLSchema}string
if(type.startsWith("{http://www.w3.org/2001/XMLSchema}")){
String tipoEffettivo = type.substring("{http://www.w3.org/2001/XMLSchema}".length());
if(tipoEffettivo!=null){
tipoEffettivo = tipoEffettivo.trim();
if("byte".equalsIgnoreCase(tipoEffettivo) || "unsignedByte".equalsIgnoreCase(tipoEffettivo)){
try{
Byte.parseByte(value);
}catch(Exception e){
throw new ValidatorException(e.getMessage(),e);
}
}
else if("char".equalsIgnoreCase(tipoEffettivo)){
if(value.length()>1){
throw new ValidatorException("More than one character");
}
}
else if("double".equalsIgnoreCase(tipoEffettivo) || "decimal".equalsIgnoreCase(tipoEffettivo)){
try{
Double.parseDouble(value);
}catch(Exception e){
throw new ValidatorException(e.getMessage(),e);
}
}
else if("float".equalsIgnoreCase(tipoEffettivo)){
try{
Float.parseFloat(value);
}catch(Exception e){
throw new ValidatorException(e.getMessage(),e);
}
}
else if("int".equalsIgnoreCase(tipoEffettivo) || "integer".equalsIgnoreCase(tipoEffettivo) ||
"positiveInteger".equalsIgnoreCase(tipoEffettivo) || "negativeInteger".equalsIgnoreCase(tipoEffettivo) ||
"nonPositiveInteger".equalsIgnoreCase(tipoEffettivo) || "nonNegativeInteger".equalsIgnoreCase(tipoEffettivo) ||
"unsignedInt".equalsIgnoreCase(tipoEffettivo)){
try{
int i = Integer.parseInt(value);
if("positiveInteger".equalsIgnoreCase(tipoEffettivo)){
if(i<=0){
throw new ValidatorException("Expected a positive value");
}
}
else if("nonNegativeInteger".equalsIgnoreCase(tipoEffettivo)){
if(i<0){
throw new ValidatorException("Expected a non negative value");
}
}
else if("negativeInteger".equalsIgnoreCase(tipoEffettivo)){
if(i>=0){
throw new ValidatorException("Expected a negative value");
}
}
else if("nonPositiveInteger".equalsIgnoreCase(tipoEffettivo)){
if(i>0){
throw new ValidatorException("Expected a non positive value");
}
}
else if("unsignedInt".equalsIgnoreCase(tipoEffettivo)){
if(i<0){
throw new ValidatorException("Expected a unsigned value");
}
}
}catch(Exception e){
throw new ValidatorException(e.getMessage(),e);
}
}
else if("long".equalsIgnoreCase(tipoEffettivo) || "unsignedLong".equalsIgnoreCase(tipoEffettivo)){
try{
long l = Long.parseLong(value);
if("unsignedLong".equalsIgnoreCase(tipoEffettivo)){
if(l<0){
throw new ValidatorException("Expected a unsigned value");
}
}
}catch(Exception e){
throw new ValidatorException(e.getMessage(),e);
}
}
else if("short".equalsIgnoreCase(tipoEffettivo) || "unsignedShort".equalsIgnoreCase(tipoEffettivo)){
try{
short s = Short.parseShort(value);
if("unsignedShort".equalsIgnoreCase(tipoEffettivo)){
if(s<0){
throw new ValidatorException("Expected a unsigned value");
}
}
}catch(Exception e){
throw new ValidatorException(e.getMessage(),e);
}
}
else if("boolean".equalsIgnoreCase(tipoEffettivo)){
try{
Boolean.parseBoolean(value);
}catch(Exception e){
throw new ValidatorException(e.getMessage(),e);
}
}
else if("anyURI".equalsIgnoreCase(tipoEffettivo)){
try{
new URI(value);
}catch(Exception e){
throw new ValidatorException(e.getMessage(),e);
}
}
}
}
// altri tipi non li valido per ora
}
private void validateAgainstXSDSchema(HttpBaseEntity<?> httpEntity,ResourceNode resourceNode,MethodNode methodNode) throws WADLException,WADLValidatorException{
if(this.schema!=null){
try{
AbstractValidatoreXSD validatore = null;
try{
validatore = new ValidatoreXSD(this.schema);
}catch(Exception e){
throw new WADLException("Riscontrato errore durante la costruzione del validatore XSD per il contenuto applicativo: "+e.getMessage(),e);
}
javax.xml.namespace.QName elementAtteso = null;
AbstractNode nodeXML = getNode(httpEntity, methodNode);
if(nodeXML != null) {
if(nodeXML instanceof RepresentationNode) {
elementAtteso = ((RepresentationNode)nodeXML).getElement();
} else if(nodeXML instanceof FaultNode) {
elementAtteso = ((FaultNode)nodeXML).getElement();
}
}
if(elementAtteso!=null){
// Devo effettivamente effettuare la validazione XSD
Node node = this.readNode(elementAtteso, httpEntity);
String nomeElemento = null;
try{
nomeElemento = node.getLocalName();
validatore.valida(node,true);
}catch(Exception e){
StringBuilder errorMsgValidazioneXSD = new StringBuilder();
errorMsgValidazioneXSD.append("validation failed");
errorMsgValidazioneXSD.append(" (element "+nomeElemento+"): "+e.getMessage());
String elementNonValidato = null;
try{
elementNonValidato = this.xmlUtils.toString(node);
}catch(Exception eString){
this.log.error("Errore durante la conversione del Node in String: "+eString.getMessage(),eString);
}
this.log.error("Validazione fallita (elemento "+nomeElemento+") ["+elementNonValidato+"]: "+e.getMessage(),e);
throw new WADLValidatorException(errorMsgValidazioneXSD.toString(),e);
}
}
}catch(WADLException e){
throw e;
}catch(WADLValidatorException e){
throw e;
}catch(Exception e){
throw new WADLException(e.getMessage(),e);
}
}
}
private AbstractNode getNode(HttpBaseEntity<?> httpEntity, MethodNode methodNode)throws WADLException,WADLValidatorException{
// Navigare il methodNode secondo la seguente specifica:
// Se siamo nella richiesta (httpEntity instanceof HttpRequestEntity)
// verificare se esiste un input con mediaType 'application/xml (o text/xml)'
// se esiste recuperare l'elementNameAtteso
// else se siamo nella risposta (httpEntity instanceof HttpResponseEntity)
// verificare se esiste un output o un fault con mediaType 'application/xml (o text/xml)' e status uguale a quello presente nella risposta http
// se esiste recuperare l'elementNameAtteso
if(httpEntity instanceof HttpBaseRequestEntity<?>) {
if(methodNode.getSupportedInputs() != null) {
for(RepresentationNode input: methodNode.getSupportedInputs()) {
if(input.getMediaType().equals("application/xml") || input.getMediaType().equals("text/xml")) {
return input;
}
}
}
} else if(httpEntity instanceof HttpBaseResponseEntity<?>) {
int status = ((HttpBaseResponseEntity<?>)httpEntity).getStatus();
List<RepresentationNode> lstOutputs = getSupportedOutputs(methodNode.getSupportedOutputs(), status);
if(lstOutputs != null) {
for(RepresentationNode output: lstOutputs) {
if(output.getMediaType().equals("application/xml") || output.getMediaType().equals("text/xml")) {
return output;
}
}
}
List<FaultNode> lstFaults = getSupportedFaults(methodNode.getFaults(), status);
if(lstFaults != null) {
for(FaultNode fault: lstFaults) {
if(fault.getMediaType().equals("application/xml") || fault.getMediaType().equals("text/xml")) {
return fault;
}
}
}
lstOutputs = methodNode.getSupportedOutputs().get(new ArrayList<Long>());
if(lstOutputs != null) {
for(RepresentationNode output: lstOutputs) {
if(output.getMediaType().equals("application/xml") || output.getMediaType().equals("text/xml")) {
return output;
}
}
}
}
return null;
}
private List<RepresentationNode> getSupportedOutputs(MultivaluedMap<List<Long>, RepresentationNode> outputs,
int status) {
for(List<Long> lst: outputs.keySet()) {
for(Long longValue: lst) {
if(longValue.intValue() == status)
return outputs.get(lst);
}
}
return null;
}
private List<FaultNode> getSupportedFaults(MultivaluedMap<List<Long>, FaultNode> faults,
int status) {
for(List<Long> lst: faults.keySet()) {
for(Long longValue: lst) {
if(longValue.intValue() == status)
return faults.get(lst);
}
}
return null;
}
private void wadlConformanceCheck(HttpBaseEntity<?> httpEntity,ResourceNode resourceNode,MethodNode methodNode) throws WADLException,WADLValidatorException{
try{
javax.xml.namespace.QName elementAtteso = null;
// ... altri vedere un po ....
AbstractNode nodeXML = getNode(httpEntity, methodNode);
if(nodeXML != null) {
if(nodeXML instanceof RepresentationNode) {
RepresentationNode representationNode = (RepresentationNode)nodeXML;
elementAtteso = representationNode.getElement();
} else if(nodeXML instanceof FaultNode) {
FaultNode faultNode = (FaultNode)nodeXML;
elementAtteso = faultNode.getElement();
}
}
if(elementAtteso!=null){
// Verifica rootNode
Node node = this.readNode(elementAtteso, httpEntity);
if(node.getLocalName()==null){
throw new WADLValidatorException("Verifica presenza element ["+elementAtteso+"] fallita. Element presente nel payload http non contiene un local-name?");
}
if(node.getNamespaceURI()==null){
throw new WADLValidatorException("Verifica presenza element ["+elementAtteso+"] fallita. Element presente nel payload http non contiene un namespace?");
}
if(node.getLocalName().equals(elementAtteso.getLocalPart())==false){
throw new WADLValidatorException("Verifica presenza element ["+elementAtteso+"] fallita. Element presente nel payload http contiene un local-name ["+node.getLocalName()+"] differente da quello atteso ["+elementAtteso.getLocalPart()+"]");
}
if(node.getNamespaceURI().equals(elementAtteso.getNamespaceURI())==false){
throw new WADLValidatorException("Verifica presenza element ["+elementAtteso+"] fallita. Element presente nel payload http contiene un namespace ["+node.getNamespaceURI()+"] differente da quello atteso ["+elementAtteso.getNamespaceURI()+"]");
}
}
}catch(WADLException e){
throw e;
}catch(WADLValidatorException e){
throw e;
}catch(Exception e){
throw new WADLException(e.getMessage(),e);
}
}
private Node readNode(javax.xml.namespace.QName elementAtteso, HttpBaseEntity<?> httpEntity) throws WADLValidatorException, WADLException{
Node node = null;
try{
if(httpEntity.getContent()==null){
throw new WADLValidatorException("Verifica presenza element ["+elementAtteso+"] fallita. Non è stato riscontrato alcun dato nel payload http");
}
Object content = httpEntity.getContent();
if(content instanceof Element){
node = ((Element) content);
}
else if(content instanceof Document){
node = ((Document) content).getDocumentElement();
}
else if(content instanceof byte[]){
byte [] bytes = (byte[]) content;
node = this.xmlUtils.newDocument(bytes);
}
else if(content instanceof String){
byte [] bytes = ((String)content).getBytes();
node = this.xmlUtils.newDocument(bytes);
}
else if(content instanceof InputStream){
InputStream is = (InputStream) content;
node = this.xmlUtils.newDocument(is);
}
else{
throw new WADLException("HttpBaseEntity ["+httpEntity.getClass().getName()+"] non gestita");
}
return node;
}catch(WADLException e){
throw e;
}catch(WADLValidatorException e){
throw e;
}catch(Exception e){
throw new WADLValidatorException("Verifica presenza element ["+elementAtteso+"] fallita. Non è stato riscontrato nel payload http dei dati che contengano una struttura xml valida, "+e.getMessage(),e);
}
}
}