########################################################################
#
# File Name:            SchemaHandler.py
#
# Documentation:        http://docs.4suite.org/4Rdf/SchemaHandler.py.html
#
"""

WWW: http://4suite.org/4RDF         e-mail: support@4suite.org

Copyright (c) 2000-2001 Fourthought Inc, USA.   All Rights Reserved.
See  http://4suite.org/COPYRIGHT  for license and copyright information
"""

rdfs_serial = """
<?xml version="1.0" encoding="iso-8859-1"?>
<rdf:RDF
  xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
  xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#">

  <rdfs:Class rdf:ID="Resource">
    <rdfs:label xml:lang="en">Resource</rdfs:label>
    <rdfs:label xml:lang="fr">Ressource</rdfs:label>
    <rdfs:comment>The most general class</rdfs:comment>
  </rdfs:Class>

                 <rdf:Property about="http://www.w3.org/1999/02/22-rdf-syntax-ns#type">
                   <rdfs:label xml:lang="en">type</rdfs:label>
                   <rdfs:label xml:lang="fr">type</rdfs:label>
                   <rdfs:comment>Indicates membership of a class</rdfs:comment>
                   <rdfs:range rdf:resource="#Class"/>
                 </rdf:Property>

                 <rdf:Property ID="comment">
                   <rdfs:label xml:lang="en">comment</rdfs:label>
                   <rdfs:label xml:lang="fr">commentaire</rdfs:label>
                   <rdfs:domain rdf:resource="#Resource"/>
                   <rdfs:comment>Use this for descriptions</rdfs:comment>
                   <rdfs:range rdf:resource="#Literal"/>
                 </rdf:Property>

                 <rdf:Property ID="label">
                  <rdf:type resource="http://www.w3.org/1999/02/22-rdf-syntax-ns#Property"/>
                  <rdfs:label xml:lang="en">label</rdfs:label>
                  <rdfs:label xml:lang="fr">libellé</rdfs:label>
                  <rdfs:domain rdf:resource="#Resource"/>
                  <rdfs:comment>Provides a human-readable version of a resource name.</rdfs:comment>
                  <rdfs:range rdf:resource="#Literal"/>
                 </rdf:Property>

                 <rdfs:Class rdf:ID="Class">
                   <rdfs:label xml:lang="en">Class</rdfs:label>
                   <rdfs:label xml:lang="fr">Classe</rdfs:label>
                   <rdfs:comment>The concept of Class</rdfs:comment>
                   <rdfs:subClassOf rdf:resource="#Resource"/>
                 </rdfs:Class>

                 <rdf:Property ID="subClassOf">
                   <rdfs:label xml:lang="en">subClassOf</rdfs:label>
                   <rdfs:label xml:lang="fr">sousClasseDe</rdfs:label>
                   <rdfs:comment>Indicates membership of a class</rdfs:comment>
                   <rdfs:range rdf:resource="#Class"/>
                   <rdfs:domain rdf:resource="#Class"/>
                 </rdf:Property>

                 <rdf:Property ID="subPropertyOf">
                   <rdfs:label xml:lang="en">subPropertyOf</rdfs:label>
                   <rdfs:label xml:lang="fr">sousPropriétéDe</rdfs:label>
                   <rdfs:comment>Indicates specialization of properties</rdfs:comment>
                   <rdfs:range rdf:resource="http://www.w3.org/1999/02/22-rdf-syntax-ns#Property"/>
                   <rdfs:domain rdf:resource="http://www.w3.org/1999/02/22-rdf-syntax-ns#Property"/>
                 </rdf:Property>

                 <rdf:Property ID="seeAlso">
                   <rdfs:label xml:lang="en">seeAlso</rdfs:label>
                   <rdfs:label xml:lang="fr">voirAussi</rdfs:label>
                   <rdfs:comment>Indicates a resource that provides information about the subject resource.</rdfs:comment>
                   <rdfs:range rdf:resource="http://www.w3.org/2000/01/rdf-schema#Resource"/>
                   <rdfs:domain rdf:resource="http://www.w3.org/2000/01/rdf-schema#Resource"/>
                 </rdf:Property>

                 <rdf:Property ID="isDefinedBy">
                   <rdf:type resource="http://www.w3.org/1999/02/22-rdf-syntax-ns#Property"/>
                   <rdfs:subPropertyOf rdf:resource="#seeAlso"/>
                   <rdfs:label xml:lang="en">isDefinedBy</rdfs:label>
                   <rdfs:label xml:lang="fr">estDéfiniPar</rdfs:label>
                   <rdfs:comment>Indicates a resource containing and defining the subject resource.</rdfs:comment>
                   <rdfs:range rdf:resource="http://www.w3.org/2000/01/rdf-schema#Resource"/>
                   <rdfs:domain rdf:resource="http://www.w3.org/2000/01/rdf-schema#Resource"/>
                 </rdf:Property>

                 <rdfs:Class rdf:ID="ConstraintResource">
                   <rdfs:label xml:lang="en">ConstraintResource</rdfs:label>
                   <rdfs:label xml:lang="fr">RessourceContrainte</rdfs:label>
                   <rdf:type resource="#Class"/>
                   <rdfs:subClassOf rdf:resource="#Resource"/>
                   <rdfs:comment>Resources used to express RDF Schema constraints.</rdfs:comment>
                 </rdfs:Class>

                 <rdfs:Class rdf:ID="ConstraintProperty">
                   <rdfs:label xml:lang="en">ConstraintProperty</rdfs:label>
                   <rdfs:label xml:lang="fr">PropriétéContrainte</rdfs:label>
                   <rdfs:subClassOf rdf:resource="http://www.w3.org/1999/02/22-rdf-syntax-ns#Property"/>
                   <rdfs:subClassOf rdf:resource="#ConstraintResource"/>
                   <rdfs:comment>Properties used to express RDF Schema constraints.</rdfs:comment>
                 </rdfs:Class>

                 <rdfs:ConstraintProperty rdf:ID="domain">
                   <rdfs:label xml:lang="en">domain</rdfs:label>
                   <rdfs:label xml:lang="fr">domaine</rdfs:label>
                   <rdfs:comment>This is how we associate a class with
                   properties that its instances can have</rdfs:comment>
                 </rdfs:ConstraintProperty>

                 <rdfs:ConstraintProperty rdf:ID="range">
                   <rdfs:label xml:lang="en">range</rdfs:label>
                   <rdfs:label xml:lang="fr">étendue</rdfs:label>
                   <rdfs:comment>Properties that can be used in a
                   schema to provide constraints</rdfs:comment>
                   <rdfs:range rdf:resource="#Class"/>
                   <rdfs:domain rdf:resource="http://www.w3.org/1999/02/22-rdf-syntax-ns#Property"/>
                 </rdfs:ConstraintProperty>

                 <rdfs:Class rdf:about="http://www.w3.org/1999/02/22-rdf-syntax-ns#Property">
                   <rdfs:label xml:lang="en">Property</rdfs:label>
                   <rdfs:label xml:lang="fr">Propriété</rdfs:label>
                   <rdfs:comment>The concept of a property.</rdfs:comment>
                   <rdfs:subClassOf rdf:resource="#Resource"/>
                 </rdfs:Class>

                 <rdfs:Class rdf:ID="Literal">
                   <rdfs:label xml:lang="en">Literal</rdfs:label>
                   <rdfs:label xml:lang="fr">Littéral</rdfs:label>
                   <rdf:type resource="#Class"/>
                   <rdfs:comment>This represents the set of atomic values, eg. textual strings.</rdfs:comment>
                 </rdfs:Class>

                 <rdfs:Class rdf:about="http://www.w3.org/1999/02/22-rdf-syntax-ns#Statement">
                   <rdfs:label xml:lang="en">Statement</rdfs:label>
                   <rdfs:label xml:lang="fr">Déclaration</rdfs:label>
                   <rdfs:subClassOf rdf:resource="#Resource"/>
                   <rdfs:comment>This represents the set of reified statements.</rdfs:comment>
                 </rdfs:Class>

                 <rdf:Property about="http://www.w3.org/1999/02/22-rdf-syntax-ns#subject">
                   <rdfs:label xml:lang="en">subject</rdfs:label>
                   <rdfs:label xml:lang="fr">sujet</rdfs:label>
                   <rdfs:domain rdf:resource="http://www.w3.org/1999/02/22-rdf-syntax-ns#Statement"/>
                   <rdfs:range rdf:resource="#Resource"/>
                 </rdf:Property>

                 <rdf:Property about="http://www.w3.org/1999/02/22-rdf-syntax-ns#predicate">
                   <rdfs:label xml:lang="en">predicate</rdfs:label>
                   <rdfs:label xml:lang="fr">prédicat</rdfs:label>
                   <rdf:type resource="http://www.w3.org/1999/02/22-rdf-syntax-ns#Property"/>
                   <rdfs:domain rdf:resource="http://www.w3.org/1999/02/22-rdf-syntax-ns#Statement"/>
                   <rdfs:range rdf:resource="http://www.w3.org/1999/02/22-rdf-syntax-ns#Property"/>
                 </rdf:Property>

                 <rdf:Property about="http://www.w3.org/1999/02/22-rdf-syntax-ns#object">
                   <rdfs:label xml:lang="en">object</rdfs:label>
                   <rdfs:label xml:lang="fr">objet</rdfs:label>
                   <rdfs:domain rdf:resource="http://www.w3.org/1999/02/22-rdf-syntax-ns#Statement"/>
                 </rdf:Property>

                 <rdfs:Class rdf:ID="Container">
                   <rdfs:label xml:lang="en">Container</rdfs:label>
                   <rdfs:label xml:lang="fr">Enveloppe</rdfs:label>
                   <rdfs:subClassOf rdf:resource="#Resource"/>
                   <rdfs:comment>This represents the set Containers.</rdfs:comment>
                 </rdfs:Class>

                 <rdfs:Class rdf:about="http://www.w3.org/1999/02/22-rdf-syntax-ns#Bag">
                   <rdfs:label xml:lang="en">Bag</rdfs:label>
                   <rdfs:label xml:lang="fr">Ensemble</rdfs:label>
                   <rdfs:subClassOf rdf:resource="#Container"/>
                 </rdfs:Class>

                 <rdfs:Class rdf:about="http://www.w3.org/1999/02/22-rdf-syntax-ns#Seq">
                   <rdfs:label xml:lang="en">Sequence</rdfs:label>
                   <rdfs:label xml:lang="fr">Séquence</rdfs:label>
                   <rdfs:subClassOf rdf:resource="#Container"/>
                 </rdfs:Class>

                 <rdfs:Class rdf:about="http://www.w3.org/1999/02/22-rdf-syntax-ns#Alt">
                   <rdfs:label xml:lang="en">Alt</rdfs:label>
                   <rdfs:label xml:lang="fr">Choix</rdfs:label>
                   <rdfs:subClassOf rdf:resource="#Container"/>
                 </rdfs:Class>

                 <rdfs:Class rdf:ID="ContainerMembershipProperty">
                   <rdfs:label xml:lang="en">ContainerMembershipProperty</rdfs:label>
                   <rdfs:subClassOf rdf:resource="http://www.w3.org/1999/02/22-rdf-syntax-ns#Property"/>
                 </rdfs:Class>

                 <rdf:Property rdf:about="http://www.w3.org/1999/02/22-rdf-syntax-ns#value">
                   <rdfs:label xml:lang="en">object</rdfs:label>
                   <rdfs:label xml:lang="fr">valuer</rdfs:label>
                 </rdf:Property>

                 </rdf:RDF>
"""
#"

from Ft.Lib import Uuid
from Ft.Rdf import Resource, Statement, Model, Container
from Ft.Rdf import RdfException
from Ft.Rdf import RDF_MS_BASE, RDF_SCHEMA_BASE
from Ft.Rdf.Serializers.Dom import Serializer


class RdfsConstraintViolation(Exception):
    PROPERTY_HAS_MULTIPLE_RANGES = 1
    INVALID_DOMAIN_FOR_PROPERTY = 2
    INVALID_RANGE_FOR_PROPERTY = 3

    def __init__(self, errorCode, *args):
        import MessageSource
        self.args = args
        self.errorCode = errorCode
        Exception.__init__(self, MessageSource.RDFS_ERROR_MESSAGES[errorCode]%args)


class SchemaHandler:
    #NOTE: Made into class so that some caching can eventually be added.
    def __init__(self):
        self._coreIsComplete = 0
        from Ft.Rdf.Drivers import Memory
        db = Memory.CreateDb('')
        #FIXME: Unclean, but all of a sudden the Memory driver is requiring tx begin
        db.begin()
        self._coreRdfsModel = Model.Model(db, None)
        from xml.dom.ext.reader import PyExpat
        self._reader = PyExpat.Reader()
        self._serializer = Serializer()

    def initializeModel(self, model):
        checkStatement = Statement.Statement("SCHEMA_INITIALIZED",
                                             RDF_SCHEMA_BASE, "1", RDF_SCHEMA_BASE)
        if model.contains(checkStatement):
            self._coreIsComplete = 1
            return

        from xml.dom.ext.reader import Sax2
        doc = Sax2.FromXml(rdfs_serial)
        from Ft.Rdf.Serializers import Dom
        serializer = Dom.Serializer()
        serializer.deserialize(model, doc, RDF_SCHEMA_BASE)
        #Keep around a copy for future reference
        serializer.deserialize(self._coreRdfsModel, doc, RDF_SCHEMA_BASE)
        self._coreIsComplete = 1

        #Add a flag to the model to mark that it has been initialized
        model.add(checkStatement)
        
        return

    def isCoreRdfs(self, stmt):
        return self._coreRdfsModel.contains(stmt)

    def isInstance(self, model, obj, class_):
        class_stmts = model.complete(obj, RDF_MS_BASE + 'type', None)
        if not class_stmts: return 0
        #Pick the first type stmt as canonical
        return self.isSubClass(model, class_stmts[0].object, class_)
                
    def isSubClass(self, model, class1, class2):
        #Recursive for now.  Is it worth moving to iteration?
        if class1 == class2: return 1
        subclass_stmts = model.complete(class1, RDF_SCHEMA_BASE + 'subClassOf',
                                        None)
        if not subclass_stmts: return 0
        if subclass_stmts[0].object == class2: return 1
        return self.isSubClass(model, subclass_stmts[0].object, class2)

    def processNewStatement(self, model, newStmt):
        self.checkConstraints(model, newStmt)
        if newStmt:
            if newStmt.predicate == RDF_SCHEMA_BASE + 'isDefinedBy':
                doc = self._reader.fromUrl(newStmt.object)
                self._serializer.deserialize(model, doc)
                self._reader.releaseNode(doc)
        return

    def checkConstraints(self, model, newStmt=None):
        '''
        Raises exception if constraint violation found, else returns normally
        Does not yet check Extended Constraints (provide hooks?)
        '''
        if not self._coreIsComplete: return
        if newStmt:
            #First check whether the statement itself
            #is the introduction of a constraint
            if self.isSubClass(model, newStmt.subject, RDF_MS_BASE + 'Property'):
                if newStmt.predicate in [RDF_SCHEMA_BASE + 'domain', RDF_SCHEMA_BASE + 'range']:
                    return
            #Now check whether the statement is governed by another constraint
            if self.isInstance(model, newStmt.predicate,
                              RDF_MS_BASE + 'Property'):
                domains = model.complete(newStmt.predicate,
                                         RDF_SCHEMA_BASE + 'domain', None)
                ranges = model.complete(newStmt.predicate,
                                        RDF_SCHEMA_BASE + 'range', None)
                if len(ranges) > 1:
                    raise RdfsConstraintViolation(RdfsConstraintViolation.PROPERTY_HAS_MULTIPLE_RANGES, newStmt.predicate)
                if domains:
                    meets_constraints = 0
                    for domain in domains:
                        if domain.object == RDF_SCHEMA_BASE + "Resource":
                            meets_constraints = 1
                        if self.isInstance(model, newStmt.subject, domain.object):
                            meets_constraints = 1
                    if not meets_constraints:
                        raise RdfsConstraintViolation(RdfsConstraintViolation.INVALID_DOMAIN_FOR_PROPERTY, str(newStmt.subject), str(newStmt.predicate))
                if ranges:
                    if not self.isInstance(model, newStmt.object, ranges[0].object) and ranges[0].object not in [RDF_SCHEMA_BASE + "Resource", RDF_SCHEMA_BASE + "Literal"]:
                        raise RdfsConstraintViolation(RdfsConstraintViolation.INVALID_RANGE_FOR_PROPERTY, str(newStmt.object), str(newStmt.predicate))
        else:
            #Check the entire model for inconsistencies
            #FIXME: Not yet supported
            pass
        return 1



