package org.neo4j.rdf.validation; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import org.neo4j.meta.model.ClassRange; import org.neo4j.meta.model.DatatypeClassRange; import org.neo4j.meta.model.MetaModel; import org.neo4j.meta.model.MetaModelClass; import org.neo4j.meta.model.MetaModelProperty; import org.neo4j.meta.model.PropertyRange; public class Validator { // private void debug( Validatable instance, String string ) // { // debug( instance, string, true ); // } // // private void debug( Validatable instance, String string, boolean doit ) // { // if ( instance instanceof MessageOperation && doit ) // { // System.out.println( string ); // } // } private MetaModel model; public Validator( MetaModel model ) { this.model = model; } public void validate( Validatable instance ) throws Exception { ValidationContext context = new ValidationContext( instance ); // Gather which properties regards to this instance Set<MetaModelProperty> properties = new HashSet<MetaModelProperty>(); for ( MetaModelClass cls : context.getClasses() ) { properties.addAll( cls.getAllProperties() ); } // Check cardinality and range on all properties for ( MetaModelProperty property : properties ) { validatePropertyIsWithinCardinality( context, property ); validatePropertyRange( context, property ); } // Check so that the properties on the instance are valid properties // from the ontology Set<String> existingPropertyKeys = new HashSet<String>( Arrays.asList( instance.getAllPropertyKeys() ) ); for ( String key : existingPropertyKeys ) { validatePropertyExists( instance, key ); MetaModelProperty property = model.getGlobalNamespace().getMetaProperty( key, false ); if ( !properties.contains( property ) ) { throw new Exception( "Property '" + key + "' isn't allowed on " + instance ); } } } private void validatePropertyExists( Validatable instance, String key ) throws Exception { if ( model.getGlobalNamespace().getMetaProperty( key, false ) == null ) { throw new Exception( "Invalid property " + key + " on " + instance ); } } private void validatePropertyRange( ValidationContext context, MetaModelProperty metaProperty ) throws Exception { String key = metaProperty.getName(); PerPropertyContext pContext = context.property( metaProperty ); PropertyRange propertyRange = pContext.getRange(); Validatable instance = context.validatable; if ( propertyRange == null ) { return; } else if ( propertyRange instanceof ClassRange ) { ClassRange classRange = ( ClassRange ) propertyRange; for ( MetaModelClass rangeClass : classRange.getRangeClasses() ) { for ( Validatable property : instance.complexProperties( key ) ) { boolean found = false; for ( MetaModelClass propertyCls : property.getClasses() ) { // TODO // if ( rangeClass.isAssignableFrom( propertyCls ) ) // { // found = true; // break; // } } if ( !found ) { throw new Exception( "Property " + key + " has an invalid value type for " + instance + ", expected " + rangeClass ); } } } } else if ( propertyRange instanceof DatatypeClassRange ) { if ( instance.hasProperty( key ) ) { Class<?> rangeClass = ( ( DatatypeClassRange ) propertyRange ).getRangeClass(); for ( Object property : instance.getProperties( key ) ) { if ( !rangeClass.equals( property.getClass() ) ) { throw new Exception( "Property " + key + " has an invalid type, expected " + rangeClass + ", but was " + property.getClass() ); } } } } // else if ( propertyRange instanceof Collection ) // { // if ( instance.hasProperty( key ) ) // { // // DataRange // Collection<?> dataRange = // ( Collection<?> ) propertyRange; // for ( Object property : instance.getProperties( key ) ) // { // if ( !dataRange.contains( property ) ) // { // throw new IdmException( "Property " + key + // " has an invalid value (" + property + // "), expected (one of) " + dataRange ); // } // } // } // } // else if ( propertyRange instanceof OwlInstance ) // { // OwlInstance expectedInstance = ( OwlInstance ) propertyRange; // for ( Validatable value : instance.complexProperties( key ) ) // { // if ( !expectedInstance.equals( value ) ) // { // throw new IdmException( "Property " + key + // " should have the value of instance " + // expectedInstance + ", but has the value " + value ); // } // } // } else { throw new RuntimeException( "Couldn't validate " + instance + ", unknown property range " + propertyRange + " (" + propertyRange.getClass() + ")" ); } } private void validatePropertyIsWithinCardinality( ValidationContext context, MetaModelProperty property ) throws Exception { String key = property.getName(); PerPropertyContext pContext = context.property( property ); Integer minCardinality = pContext.getMinCardinality(); Integer maxCardinality = pContext.getMaxCardinality(); if ( maxCardinality == null && minCardinality == null ) { String functionality = ( String ) property.getAdditionalProperty( "functionality" ); if ( functionality != null ) { maxCardinality = 1; minCardinality = 0; } else { return; } } int valueCardinality = 0; PropertyRange range = pContext.getRange(); if ( range == null ) { throw new RuntimeException( "Property " + property + " is neither DatatypeProperty nor ObjectProperty" ); } else if ( range.isDatatype() ) { // TODO if ( context.validatable.hasProperty( key ) ) { valueCardinality = context.validatable.getProperties( key ).length; } } else { // TODO valueCardinality = context.validatable.complexProperties( key ).size(); } if ( minCardinality != null && valueCardinality < minCardinality ) { throw new Exception( "Cardinality for property " + key + " is " + valueCardinality + ", minimum required is " + minCardinality ); } if ( maxCardinality != null && valueCardinality > maxCardinality ) { throw new Exception( "Cardinality for property " + key + " is " + valueCardinality + ", maximum allowed is " + maxCardinality ); } } private class ValidationContext { private Validatable validatable; private MetaModelClass[] instanceOfClasses; private Map<MetaModelProperty, PerPropertyContext> propertyContexts = new HashMap<MetaModelProperty, PerPropertyContext>(); ValidationContext( Validatable validatable ) { this.validatable = validatable; } MetaModelClass[] getClasses() { if ( instanceOfClasses == null ) { Collection<MetaModelClass> list = validatable.getClasses(); instanceOfClasses = list.toArray( new MetaModelClass[ list.size() ] ); } return instanceOfClasses; } PerPropertyContext property( MetaModelProperty property ) { PerPropertyContext result = propertyContexts.get( property ); if ( result == null ) { result = new PerPropertyContext( this, property ); propertyContexts.put( property, result ); } return result; } } private class PerPropertyContext { private ValidationContext context; private MetaModelProperty property; private PropertyRange range; private Integer minCardinality; private Integer maxCardinality; PerPropertyContext( ValidationContext context, MetaModelProperty property ) { this.context = context; this.property = property; } PropertyRange getRange() { if ( range == null ) { range = model.lookup( property, MetaModel.LOOKUP_PROPERTY_RANGE, context.getClasses() ); } return range; } Integer getMinCardinality() { if ( minCardinality == null ) { minCardinality = model.lookup( property, MetaModel.LOOKUP_MIN_CARDINALITY, context.getClasses() ); } return minCardinality; } Integer getMaxCardinality() { if ( maxCardinality == null ) { maxCardinality = model.lookup( property, MetaModel.LOOKUP_MAX_CARDINALITY, context.getClasses() ); } return maxCardinality; } } }