Package org.fao.geonet.kernel.schema

Source Code of org.fao.geonet.kernel.schema.SchemaLoader

//==============================================================================
//===
//===   SchemaLoader
//===
//==============================================================================
//===  Copyright (C) 2001-2007 Food and Agriculture Organization of the
//===  United Nations (FAO-UN), United Nations World Food Programme (WFP)
//===  and United Nations Environment Programme (UNEP)
//===
//===  This program is free software; you can redistribute it and/or modify
//===  it under the terms of the GNU General Public License as published by
//===  the Free Software Foundation; either version 2 of the License, or (at
//===  your option) any later version.
//===
//===  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, write to the Free Software
//===  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
//===
//===  Contact: Jeroen Ticheler - FAO - Viale delle Terme di Caracalla 2,
//===  Rome - Italy. email: geonetwork@osgeo.org
//==============================================================================

package org.fao.geonet.kernel.schema;

import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;

import org.fao.geonet.repository.SchematronCriteriaGroupRepository;
import org.fao.geonet.repository.SchematronRepository;
import org.fao.geonet.utils.Log;
import org.fao.geonet.utils.Xml;

import org.fao.geonet.constants.Edit;
import org.fao.geonet.constants.Geonet;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.Namespace;
import org.fao.geonet.utils.ResolverWrapper;
import org.fao.geonet.utils.Resolver;

import java.net.URI;
import java.util.*;

//==============================================================================

public class SchemaLoader
{
    private Element   elFirst = null;
  private Map<String, String> hmElements = new HashMap<String, String>();
  private Map<String, ComplexTypeEntry> hmTypes    = new HashMap<String, ComplexTypeEntry>();
  private Map<String, List<AttributeEntry>>   hmAttrGrp  = new HashMap<String, List<AttributeEntry>>();
  private Map<String, AttributeGroupEntry> hmAttrGpEn = new HashMap<String, AttributeGroupEntry>();
  private Map<String, String> hmAbsElems = new HashMap<String, String>();
  private Map<String, ArrayList<ElementEntry>> hmSubsGrp  = new HashMap<String, ArrayList<ElementEntry>>();
  private Map<String, String> hmSubsLink = new HashMap<String, String>();
  private Map<String, List<String>>   hmSubsNames = new HashMap<String, List<String>>();
  private Map<String, AttributeEntry> hmAttribs  = new HashMap<String, AttributeEntry>();
  private Map<String, AttributeEntry> hmAllAttrs = new HashMap<String, AttributeEntry>();
  private Map<String, GroupEntry> hmGroups   = new HashMap<String, GroupEntry>();

    private SchemaSubstitutions ssOverRides;

  private String targetNS;
  private String targetNSPrefix;

  /** Restrictions for simple types (element restriction) */
  private Map<String, List<String>> hmElemRestr = new HashMap<String, List<String>>();

  /** Restrictions for simple types (type restriction) */
  private Map<String, List<String>> hmTypeRestr = new HashMap<String, List<String>>();

  private Map<String, List<String>> hmMemberTypeRestr = new HashMap<String, List<String>>();

  //---------------------------------------------------------------------------
  //---
  //--- Constructor
  //---
  //---------------------------------------------------------------------------

  public SchemaLoader() {}

  //---------------------------------------------------------------------------
  //---
  //--- API methods
  //---
  //---------------------------------------------------------------------------

  public MetadataSchema load(String xmlSchemaFile, String xmlSubstitutionsFile, SchematronRepository schemaRepo,
                               SchematronCriteriaGroupRepository criteriaGroupRepository) throws Exception
  {
    ssOverRides = new SchemaSubstitutions(xmlSubstitutionsFile);

    if (!new File(xmlSchemaFile).exists()) return new MetadataSchema(schemaRepo, criteriaGroupRepository);

    //--- PHASE 1 : pre-processing
    //---
    //--- the xml file is parsed and simplified. Xml schema subtrees are
    //--- wrapped in some classes

    List<ElementInfo> alElementFiles = loadFile(xmlSchemaFile, new HashSet<String>());

    parseElements(alElementFiles);

    //--- PHASE 2 : resolve abstract elements

        for (String o : hmSubsGrp.keySet()) {

            List<ElementEntry> elements = hmSubsGrp.get(o);
            List<String> subsNames = new ArrayList<String>();
            hmSubsNames.put(o, subsNames);
            for (ElementEntry ee : elements) {
                if (ee.type == null) {
                    ee.type = hmAbsElems.get(o);

                    if (ee.type == null) {
                        // If we don't have a type then insert with null and fix
                        // when all elements have been added to hmElements
                        Logger.log();
                    }
                }

                hmElements.put(ee.name, ee.type);
                subsNames.add(ee.name);
            }
        }

    //--- PHASE 3 : get appinfo, add namespaces and elements

    MetadataSchema mds = new MetadataSchema(schemaRepo, criteriaGroupRepository);
    mds.setPrimeNS(elFirst.getAttributeValue("targetNamespace"));


        @SuppressWarnings("unchecked")
                List<Element> annotation = elFirst.getChildren("annotation", Geonet.Namespaces.XSD);

        if (annotation!=null) {
          List<Element> allAppInfo = new ArrayList<Element>();

          for (Element currAnnotation: annotation) {
            @SuppressWarnings("unchecked")
                        List<Element> currAppInfo = currAnnotation.getChildren("appinfo",Geonet.Namespaces.XSD);

            if (currAppInfo != null) {
              allAppInfo.addAll(currAppInfo);
            }
          }
          mds.setRootAppInfoElements(allAppInfo);
        }

        for (ElementInfo ei : alElementFiles) {
            mds.addNS(ei.targetNSPrefix, ei.targetNS);
        }

        for (String elem : hmElements.keySet()) {
            String type = hmElements.get(elem);

            // fix any null types by back tracking through substitution links
            // until we get a concrete type or die trying :-)
            if (type == null) {
                Logger.log();
                type = recurseOnSubstitutionLinks(elem);
                if (type == null) {
                    Log.warning(Geonet.SCHEMA_MANAGER, "WARNING: Cannot find type for " + elem + ": assuming string");
                    type = "string";
                }
                else {
                    Logger.log();
                }
            }

            List<String> elemRestr = hmElemRestr.get(elem);
            List<String> typeRestr = hmTypeRestr.get(type);

            if (elemRestr == null) {
                elemRestr = new ArrayList<String>();
            }

            if (typeRestr != null) {
                elemRestr.addAll(typeRestr);
            }

            List<String> elemSubs = hmSubsNames.get(elem);
            if (elemSubs == null) {
                elemSubs = new ArrayList<String>();
            }
            String elemSubsLink = hmSubsLink.get(elem);
            if (elemSubsLink == null) {
                elemSubsLink = "";
            }
            mds.addElement(elem, type, elemRestr, elemSubs, elemSubsLink);
        }

    //--- PHASE 4 : resolve references in attribute groups

        for (AttributeGroupEntry age : hmAttrGpEn.values()) {
            for (int k = 0; k < age.alAttrs.size(); k++) {
                AttributeEntry attr = age.alAttrs.get(k);
                if (attr.name != null) {
                    hmAllAttrs.put(attr.name, attr);
                }
            }
            ArrayList<AttributeEntry> attrs = resolveNestedAttributeGroups(age);
            hmAttrGrp.put(age.name, attrs);
        }

    //--- PHASE 5 : check attributes to see whether they should be qualified

    Map<String, AttributeEntry> hmAttrChk = new HashMap<String, AttributeEntry>();
        for (AttributeEntry attr : hmAllAttrs.values()) {
            AttributeEntry attrPrev = hmAttrChk.get(attr.unqualifiedName);
            if (attrPrev != null) {
                attr.form = "qualified";
                attrPrev.form = "qualified";
            }
            else {
                hmAttrChk.put(attr.unqualifiedName, attr);
            }
        }

    //--- PHASE 6 : post-processing
    //---
    //--- resolve type inheritance and elements

    List<ComplexTypeEntry> alTypes = new ArrayList<ComplexTypeEntry>(hmTypes.values());
    for(ListIterator<ComplexTypeEntry> i=alTypes.listIterator(); i.hasNext();) {
      ComplexTypeEntry cte = i.next();

      MetadataType mdt = new MetadataType();

      mdt.setOrType(cte.isOrType);

      //--- resolve element and attribute inheritance from complexContent

      if (cte.complexContent != null) {

        if (cte.complexContent.base != null) {

          //--- add elements
          cte.alElements = resolveInheritance(cte);

          //--- add attribs (if any)
          List<AttributeEntry> complexContentAttribs = resolveAttributeInheritance(cte);
                    for (AttributeEntry ae : complexContentAttribs) {
                        mdt.addAttribute(buildMetadataAttrib(ae));
                    }

          //--- if the base type is an ortype then we need to make this an
          //--- or type as well
          ComplexTypeEntry baseCTE = hmTypes.get(cte.complexContent.base);
          if (baseCTE.isOrType) {
            cte.isOrType = true;
            mdt.setOrType(true);
            Logger.log();
          }
        } else {
          throw new IllegalArgumentException("base not defined for complexContent in "+cte.name);
        }

      //--- resolve attribute inheritance from simpleContent
     
      } else if (cte.simpleContent != null) {
        List<AttributeEntry> simpleContentAttribs = resolveAttributeInheritanceFromSimpleContent(cte);
                for (AttributeEntry ae : simpleContentAttribs) {
                    mdt.addAttribute(buildMetadataAttrib(ae));
                }

      //--- otherwise process the attributes and attribute groups for this type

      } else {
        for(int j=0; j<cte.alAttribs.size(); j++) {
          AttributeEntry ae = cte.alAttribs.get(j);
          mdt.addAttribute(buildMetadataAttrib(ae));
        }
        for (int k=0;k < cte.alAttribGroups.size();k++) {
          String attribGroup = cte.alAttribGroups.get(k);
          List<AttributeEntry> al = hmAttrGrp.get(attribGroup);
 
          if (al == null)
            throw new IllegalArgumentException("Attribute group not found : " + attribGroup);

                    for (AttributeEntry ae : al) {
                        mdt.addAttribute(buildMetadataAttrib(ae));
                    }
        }
      }
 
      //--- now add the elements belonging to this complex type to the mdt

      for(int j=0; j<cte.alElements.size(); j++) {
        ElementEntry ee = (ElementEntry) cte.alElements.get(j);

// Three situations:
// 1. element is a container element - group, choice or sequence - so recurse
// and get elements from any containers nested inside this container -
// we generate a name to use from the cte.name and element position

        if ( ee.groupElem || ee.choiceElem || ee.sequenceElem ) {
                    String baseName = cte.name;
          String extension;
          ArrayList<ElementEntry> elements;
          if (ee.choiceElem) {
            extension = Edit.RootChild.CHOICE;
            elements = ee.alContainerElems;
          } else if (ee.groupElem) {
            extension = Edit.RootChild.GROUP;
            GroupEntry group = hmGroups.get(ee.ref);
            elements = group.alElements;
          } else {
            extension = Edit.RootChild.SEQUENCE;
            elements = ee.alContainerElems;
          }
          String type = ee.name = baseName+extension+ (Integer) j;
          ArrayList<ComplexTypeEntry> newCtes = createTypeAndResolveNestedContainers(
                            mds,elements,
                                            baseName,extension, j);
          if (newCtes.size() != 0) {
                        for (ComplexTypeEntry newCte : newCtes) {
                            i.add(newCte);
                            i.previous();
                        }
          }
          mds.addElement(ee.name, type, new ArrayList<String>(), new ArrayList<String>(), "");
          mdt.addElementWithType(ee.name, type, ee.min, ee.max);

// 2. element is a reference to a global element so check if abstract or
//    if the type needs to be turned into a choice ie. it has one element
//    which is the head of a substitution group or a new choice type
//    is created for the element or just add it if none of
//    the above
        } else if (ee.ref != null) {
          boolean choiceType = (cte.alElements.size() == 1);
          handleRefElement(j, cte.name,choiceType,ee,mdt,mds);


// 3. element is a local element so get type or process local complex/simpleType//    and add to the ListIterator if complex
        } else if (ee.name != null) {
          ComplexTypeEntry newCte = handleLocalElement(j, cte.name,ee,mdt,mds);
          if (newCte != null) {
            i.add(newCte); i.previous();
          }

        } else {
          throw new IllegalArgumentException("Unknown element type at position "+j+" in complexType "+cte.name);
        }
      }
      mds.addType(cte.name, mdt);
    }
   
   
    // now set the schema to be editable and return
    mds.setCanEdit(true);
    return mds;
  }
 
  //---------------------------------------------------------------------------
  //---
  //--- Recurse on substitution links until we get a type that we can use
  //---
  //---------------------------------------------------------------------------
  private String recurseOnSubstitutionLinks(String elemName) {
    String elemLinkName = hmSubsLink.get(elemName);
    if (elemLinkName != null) {
      String elemLinkType = hmElements.get(elemLinkName);
      if (elemLinkType != null) return elemLinkType; // found concrete type!
      else recurseOnSubstitutionLinks(elemLinkName); // keep trying
    }
    return null; // Cannot find a type so return null
  }

  //---------------------------------------------------------------------------
  //---
  //--- Build a local element into the MetadataType and Schema
  //---
  //---------------------------------------------------------------------------
  private ComplexTypeEntry handleLocalElement(Integer elementNr, String baseName, ElementEntry ee, MetadataType mdt, MetadataSchema mds) {

    ComplexTypeEntry cteInt = null;
    ArrayList<String> elemRestr = new ArrayList<String>();

    if (ee.type == null) {
      if (ee.complexType != null) {
        cteInt = ee.complexType;
        ee.type = cteInt.name = ee.name+"HSI"+elementNr+
                        getUnqualifiedName(baseName);
      } else if (ee.simpleType != null) {
        ee.type = "string";
        if (ee.simpleType.alEnum != null) // add enumerations if any
          elemRestr.addAll(ee.simpleType.alEnum);
      } else {
          Log.warning(Geonet.SCHEMA_MANAGER, "WARNING: Could not find type for "+ee.name+" - assuming string");
        ee.type = "string";
      }
    }

    mds.addElement(ee.name, ee.type, elemRestr, new ArrayList<String>(), "");
    mdt.addElementWithType(ee.name, ee.type, ee.min, ee.max);

    return(cteInt);

  }

  //---------------------------------------------------------------------------
  //---
  //--- Return list of substitutes if we want to override those derived
  //--- from the schema XSDs - this is schema dependent and defined in
  //--- the schema-substitutes files and is used for elements such as
  //--- gco:CharacterString in the iso19139 schema
  //---
  //--- returns null if there are no user defined substitutes,
  //---     OR  an empty list if removal of all schema substitutes is required
  //---     OR  a list of ElementEntry objects to use as substitutes
  //---
  //---------------------------------------------------------------------------
  private ArrayList<ElementEntry> getOverRideSubstitutes(String elementName) {

    ArrayList<ElementEntry> subs = hmSubsGrp.get(elementName);
    List<String> ssOs = ssOverRides.getSubstitutes(elementName);
    if (ssOs != null && subs != null) {
      ArrayList<ElementEntry> results = new ArrayList<ElementEntry>();
      List<String> validSubs = hmSubsNames.get(elementName);
            for (String altSub : ssOs) {
                if (validSubs != null && !validSubs.contains(altSub)) {
                    Log.warning(Geonet.SCHEMA_MANAGER, "WARNING: schema-substitutions.xml specified " + altSub + " for element " + elementName + " but the schema does not define this as a valid substitute");
                }
                for (ElementEntry ee : subs) {
                    if (ee.name.equals(altSub)) {
                        results.add(ee);
                    }
                }
            }
      if (results.size() == 0 && validSubs != null) {
          Log.warning(Geonet.SCHEMA_MANAGER, "WARNING: schema-substitutions.xml has wiped out XSD substitution list for "+elementName);
      }
      return results;
    }
    return null;
  }

  //---------------------------------------------------------------------------
  //---
  //--- Build a reference to a global element into the MetadataType and Schema
  //---
  //---------------------------------------------------------------------------
  private void handleRefElement(Integer elementNr, String baseName, boolean choiceType, ElementEntry ee, MetadataType mdt, MetadataSchema mds) {

    String type = hmElements.get(ee.ref);
    boolean isAbstract = hmAbsElems.containsKey(ee.ref);

    // If we have user specified substitutions then use them otherwise
    // use those from the schema
    boolean doSubs = true;
    ArrayList<ElementEntry> al = getOverRideSubstitutes(ee.ref);
    if (al == null) al = hmSubsGrp.get(ee.ref);
    else doSubs = false;

    if ((al != null && al.size() > 0) || isAbstract ) {
      if (choiceType) {
      // The complex type has only one element then make it a choice type if
      // there are concrete elements in the substitution group
        int elementsAdded = assembleChoiceElements(mdt,al,doSubs);
        if (!isAbstract && doSubs) {
          /*
           * Control of substitution lists is via the schema-substitutions.xml
           * file because some profiles do not mandate substitutions of this
           * kind eg. wmo
           *
          if (elementsAdded == 1 &&
              (getPrefix(mdt.getElementAt(0)).equals(getProfile(schemaId))) &&
                      schemaId.startsWith("iso19139")) {
            Logger.log("Sticking with "+mdt.toString()+" for "+ee.ref);
          } else {
           *
           */
            mdt.addRefElementWithType(ee.ref,type,ee.min,ee.max);
            elementsAdded++;
          /*}*/
        }
        mdt.setOrType(elementsAdded > 1);
      } else {
      // The complex type has real elements and/or attributes so make a new
      // choice element with type and replace this element with it
        MetadataType mdtc = new MetadataType();
        Integer elementsAdded = assembleChoiceElements(mdtc,al,doSubs);
        if (!isAbstract && doSubs) {
          mdtc.addRefElementWithType(ee.ref,ee.type,ee.min,ee.max);
          elementsAdded++;
        }
        mdtc.setOrType(elementsAdded > 1);
        type = ee.ref+Edit.RootChild.CHOICE+elementNr;
        String name = type;
        mds.addType(type,mdtc);
        mds.addElement(name,type,new ArrayList<String>(),new ArrayList<String>(), "");
        mdt.addElementWithType(name,type,ee.min,ee.max);
      }
    } else if (!isAbstract) {
      mdt.addRefElementWithType(ee.ref,type,ee.min,ee.max);
    } else {
        Log.warning(Geonet.SCHEMA_MANAGER, "WARNING: element "+ee.ref+" from "+baseName+" has fallen through the logic (abstract: "+isAbstract+") - ignoring");
    }
  }

  //---------------------------------------------------------------------------
  //---
  //--- Recurse on attributeGroups to build a list of AttributeEntry objects
  //---
  //---------------------------------------------------------------------------
  private ArrayList<AttributeEntry> resolveNestedAttributeGroups(AttributeGroupEntry age) {
    ArrayList<AttributeEntry> attrs = new ArrayList<AttributeEntry>();

    if (age.alAttrGrps.size() > 0) {
      for (int i=0;i<age.alAttrGrps.size();i++) {
        AttributeGroupEntry ageInternal =
                        age.alAttrGrps.get(i);
        AttributeGroupEntry ageRef =
                        hmAttrGpEn.get(ageInternal.ref);
        if (ageRef == null)
          throw new IllegalArgumentException
              ("ERROR: cannot find attributeGroup with ref "+ageInternal.ref);
        attrs.addAll(resolveNestedAttributeGroups(ageRef));
      }
    }
    attrs.addAll(age.alAttrs);
    return attrs;
  }

  //---------------------------------------------------------------------------
  //---
  //--- Descend recursively to deal with nested containers
  //---
  //---------------------------------------------------------------------------
  private ArrayList<ComplexTypeEntry> createTypeAndResolveNestedContainers(
            MetadataSchema mds, ArrayList<ElementEntry> al, String baseName,
            String extension, Integer baseNr) {

    ArrayList<ComplexTypeEntry> complexTypes = new ArrayList<ComplexTypeEntry>();

    Integer oldBaseNr = baseNr;
    if (al == null) return complexTypes;
    MetadataType mdt = new MetadataType();
    if (extension.contains(Edit.RootChild.CHOICE)) mdt.setOrType(true);
    for(int k=0; k<al.size(); k++)
    {
      ElementEntry ee = al.get(k);
      baseNr++;

      // CHOICE
      if (ee.choiceElem) {
        String newExtension = Edit.RootChild.CHOICE;
        ArrayList<ComplexTypeEntry> newCtes = createTypeAndResolveNestedContainers(mds,ee.alContainerElems,baseName,newExtension,baseNr);
        if (newCtes.size() > 0) complexTypes.addAll(newCtes);
        ee.name = ee.type = baseName+newExtension+baseNr;
        mds.addElement(ee.name,ee.type,new ArrayList<String>(),new ArrayList<String>(), "");
        mdt.addElementWithType(ee.name, ee.type, ee.min, ee.max);

      // GROUP
      } else if (ee.groupElem) {
        String newExtension = Edit.RootChild.GROUP;
        if (ee.ref != null) {
          GroupEntry group = hmGroups.get(ee.ref);
          ArrayList<ElementEntry> alGroupElements = group.alElements;
          ArrayList<ComplexTypeEntry> newCtes = createTypeAndResolveNestedContainers(mds,alGroupElements,baseName,newExtension,baseNr);
          if (newCtes.size() > 0) complexTypes.addAll(newCtes);
          ee.name = ee.type = baseName+newExtension+baseNr;
          mds.addElement(ee.name,ee.type,new ArrayList<String>(),new ArrayList<String>(), "");
          mdt.addElementWithType(ee.name, ee.type, ee.min, ee.max);
        } else {
            Log.warning(Geonet.SCHEMA_MANAGER, "WARNING: group element ref is NULL in "+baseName+extension+baseNr);
        }

      // SEQUENCE
      } else if (ee.sequenceElem) {
        String newExtension = Edit.RootChild.SEQUENCE;
        ArrayList<ComplexTypeEntry> newCtes = createTypeAndResolveNestedContainers(mds,ee.alContainerElems,baseName,newExtension,baseNr);
        if (newCtes.size() > 0) complexTypes.addAll(newCtes);
        ee.name = ee.type = baseName+newExtension+baseNr;
        mds.addElement(ee.name,ee.type,new ArrayList<String>(),new ArrayList<String>(), "");
        mdt.addElementWithType(ee.name, ee.type, ee.min, ee.max);

      // ELEMENT
      } else {
        if (ee.name != null) {
          ComplexTypeEntry newCte = handleLocalElement(k, baseName,ee,mdt,mds);
          if (newCte != null) complexTypes.add(newCte);
        }
        else {
          handleRefElement(k, baseName,false,ee,mdt,mds);
        }
      }
    }
    mds.addType(baseName+extension+oldBaseNr,mdt);
    return complexTypes;
  }

  //---------------------------------------------------------------------------
  //---
  //--- Descend recursively to deal with abstract elements
  //---
  //---------------------------------------------------------------------------
  private int assembleChoiceElements(MetadataType mdt, ArrayList<ElementEntry> al,boolean doSubs) {

    int number = 0;
    if (al == null) return number;
        for (ElementEntry ee : al) {
            if (ee.abstrElem) {
                Integer numberRecursed = assembleChoiceElements(mdt, hmSubsGrp.get(ee.name), doSubs);
                number = number + numberRecursed;
            }
            else {
                number++;
                mdt.addElementWithType(ee.name, ee.type, ee.min, ee.max);
                // Also add any elements that substitute for this one so that we can
                // complete the list of choices if required
                if (doSubs) {
                    ArrayList<ElementEntry> elemSubs = hmSubsGrp.get(ee.name);
                    if (elemSubs != null) {
                        for (ElementEntry eeSub : elemSubs) {
                            mdt.addElementWithType(eeSub.name, eeSub.type, eeSub.min, eeSub.max);
                            number++;
                        }
                    }
                }
            }
        }
    return number;
  }

  //---------------------------------------------------------------------------
  //---
  //--- PHASE 1 : Schema loading
  //---
  //---------------------------------------------------------------------------

  /** Loads the xml-schema file, removes annotations and resolve imports/includes */

  private List<ElementInfo> loadFile(String xmlSchemaFile, HashSet<String> loadedFiles) throws Exception
  {
    loadedFiles.add(new File(xmlSchemaFile).getCanonicalPath());

    String path = new File(xmlSchemaFile).getParent() + "/";

    //--- load xml-schema
        Log.debug(Geonet.SCHEMA_MANAGER, "Loading schema " + xmlSchemaFile);

        Element elRoot = Xml.loadFile(xmlSchemaFile);
    if (elFirst == null) elFirst = elRoot;

    // change target namespace
    String oldtargetNS       = targetNS;
    String oldtargetNSPrefix = targetNSPrefix;
    targetNS = elRoot.getAttributeValue("targetNamespace");
    targetNSPrefix = null;


    if (targetNS != null) {
            for (Object o : elRoot.getAdditionalNamespaces()) {
                Namespace ns = (Namespace) o;
                if (targetNS.equals(ns.getURI())) {
                    targetNSPrefix = ns.getPrefix();
                    break;
                }
            }
      if ("".equals(targetNSPrefix)) targetNSPrefix = null;
    }
    // This is a bug in jdom - seems that if the namespace prefix is xml: and
    // namespace is as shown in the if statement then getAdditionalNamespaces
    // doesn't return the namespaces and we can't get a prefix - this fix gets
    // around that bug
    if ((xmlSchemaFile.contains("xml.xsd") || xmlSchemaFile.contains("xml-mod.xsd")) && targetNS.equals("http://www.w3.org/XML/1998/namespace")) targetNSPrefix="xml";

    @SuppressWarnings("unchecked")
        List<Element> children = elRoot.getChildren();

    //--- collect elements into an array because we have to add elements
    //--- when we encounter the "import" element

    List<ElementInfo> alElementFiles = new ArrayList<ElementInfo>();

        for (Element elChild : children) {
            String name = elChild.getName();

            if (name.equals("annotation")) {
               
            }

            else if (name.equals("import") || name.equals("include")) {
                String schemaLoc = elChild.getAttributeValue("schemaLocation");

                //--- we must try to resolve imports from the web using the
                //--- oasis catalog
                String scFile;
                if (schemaLoc.startsWith("http:")) {
                    Resolver resolver = ResolverWrapper.getInstance();
                    scFile = resolver.getXmlResolver().resolveURI(schemaLoc);

                    if (Log.isDebugEnabled(Geonet.SCHEMA_MANAGER)) {
                          Log.debug(Geonet.SCHEMA_MANAGER, "Cats: " +
                                  Arrays.toString(resolver.getXmlResolver().getCatalogList()) +
                                  " Resolved " + schemaLoc +
                                  " " + scFile);
                    }

                    if (scFile == null) { // what use is this? old behaviour
                        Log.warning(Geonet.SCHEMA_MANAGER, "Cannot resolve " + schemaLoc +
                                ": will append last component to current path (not sure it will help though!)");
                        int lastSlash = schemaLoc.lastIndexOf('/');
                        scFile = path + schemaLoc.substring(lastSlash + 1);
                    } else // this is good - get the path of resolved URI
                        scFile = new URI(scFile).getPath();
                    }
                } else {
                    scFile = path + schemaLoc;
                }
                if (!loadedFiles.contains(new File(scFile).getCanonicalPath())) {
                    alElementFiles.addAll(loadFile(scFile, loadedFiles));
                }
            }
            else {
                alElementFiles.add(new ElementInfo(elChild, xmlSchemaFile, targetNS, targetNSPrefix));
            }
        }
    // restore target namespace
    targetNS       = oldtargetNS;
    targetNSPrefix = oldtargetNSPrefix;

    return alElementFiles;
  }

  //---------------------------------------------------------------------------
  //---
  //--- PHASE 2 : Parse elements building intermediate data structures
  //---
  //---------------------------------------------------------------------------

  private void parseElements(List<ElementInfo> alElementFiles) throws JDOMException
  {
    //--- clear some structures

    hmElements .clear();
    hmTypes    .clear();
    hmAttrGrp  .clear();
    hmAbsElems .clear();
    hmSubsGrp  .clear();
    hmSubsLink .clear();
    hmElemRestr.clear();
    hmTypeRestr.clear();
    hmAttribs  .clear();
    hmAllAttrs .clear();
    hmGroups   .clear();

        for (ElementInfo ei : alElementFiles) {
            Element elChild = ei.element;
            String name = elChild.getName();

            if (name.equals("element")) {
                buildGlobalElement(ei);
            }

            else if (name.equals("complexType")) {
                buildComplexType(ei);
            }

            else if (name.equals("simpleType")) {
                buildSimpleType(ei);
            }

            else if (name.equals("attribute")) {
                buildGlobalAttrib(ei);
            }

            else if (name.equals("group")) {
                buildGlobalGroup(ei);
            }

            else if (name.equals("attributeGroup")) {
                buildGlobalAttributeGroup(ei);
            }

            else {
                Logger.log();
            }
        }
  }

  //---------------------------------------------------------------------------

  private void buildGlobalElement(ElementInfo ei)
  {
    ElementEntry ee = new ElementEntry(ei);

    if (ee.name == null)
      throw new IllegalArgumentException("Name is null for element : " + ee.name);


    if (ee.substGroup != null)
    {
      ArrayList<ElementEntry> al = hmSubsGrp.get(ee.substGroup);

      if (al == null) {
        al = new ArrayList<ElementEntry>();
        hmSubsGrp.put(ee.substGroup, al);
      }
      al.add(ee);

            String existingSubstitionGroup = hmSubsLink.get(ee.name);
      if (existingSubstitionGroup != null
                    && !ee.substGroup.equals(existingSubstitionGroup)) {
        throw new IllegalArgumentException("Substitution link collision" +
                        " for " + ee.name +
                        " link to " + existingSubstitionGroup +
                        ". Already bound to " + ee.substGroup);
      } else {
        hmSubsLink.put(ee.name, ee.substGroup);
      }
    }
    if (ee.abstrElem)
    {

            String existingType = hmAbsElems.get(ee.name);
      if (existingType != null && !ee.type.equals(existingType)) {
        throw new IllegalArgumentException("Namespace collision" +
                        " for " + ee.name +
                        " type " + existingType +
                        ". Already bound to " + ee.type);
            } else {
          hmAbsElems.put(ee.name, ee.type);
            }
      return;
    }
    if (ee.complexType != null)
    {
      String type = ee.name+"HSI";
      ee.complexType.name = type;
      ee.type = type;
      if (hmElements.containsKey(ee.name))
        throw new IllegalArgumentException("Namespace collision for : " + ee.name);

      hmElements.put(ee.name, type);
      hmTypes.put(type, ee.complexType);

    }
    else if (ee.simpleType != null)
    {
            if (hmElements.containsKey(ee.name))
        throw new IllegalArgumentException("Namespace collision for : " + ee.name);
      ee.type = "string";
      hmElements .put(ee.name, ee.type);
      hmElemRestr.put(ee.name, ee.simpleType.alEnum);

    }
    else
    {
      if (ee.type == null && ee.substGroup == null) {
          Log.warning(Geonet.SCHEMA_MANAGER, "WARNING: "+ee.name+" is a global element without a type - assuming a string");
        ee.type ="string";
      }
      hmElements.put(ee.name, ee.type);

    }
    if (ee.name.contains("SensorML")) {
      Logger.log();
    }
  }

  //---------------------------------------------------------------------------

  private void buildComplexType(ElementInfo ei)
  {
    ComplexTypeEntry ct = new ComplexTypeEntry(ei);

        ComplexTypeEntry existingType = hmTypes.get(ct.name);
        if (existingType != null && !ct.name.equals(existingType.name)) {
            throw new IllegalArgumentException("Namespace collision" +
                    " for complex type " + ct.name +
                    " type " + existingType.name + "already defined.");
        }
        hmTypes.put(ct.name, ct);
    }

  //---------------------------------------------------------------------------

  private void buildSimpleType(ElementInfo ei)
  {
    SimpleTypeEntry  st = new SimpleTypeEntry(ei);
    if (hmTypeRestr.containsKey(st.name))
      throw new IllegalArgumentException("Namespace collision for : " + st.name);

    hmTypeRestr.put(st.name, st.alEnum);
   
    if (!hmMemberTypeRestr.containsKey(st.name))
      hmMemberTypeRestr.put(st.name, st.alTypes);
  }

  //---------------------------------------------------------------------------

  private void buildGlobalAttrib(ElementInfo ei)
  {
    AttributeEntry at = new AttributeEntry(ei);
    if (hmAttribs.containsKey(at.name))
      throw new IllegalArgumentException("Namespace collision for : " + at.name);

    hmAttribs.put(at.name, at);
    hmAllAttrs.put(at.name, at);
  }

  //---------------------------------------------------------------------------

  private void buildGlobalGroup(ElementInfo ei)
  {
    GroupEntry ge = new GroupEntry(ei);
    if (hmGroups.containsKey(ge.name))
      throw new IllegalArgumentException("Namespace collision for : " + ge.name);

    hmGroups.put(ge.name, ge);
  }

  //---------------------------------------------------------------------------

  private void buildGlobalAttributeGroup(ElementInfo ei)
  {

    AttributeGroupEntry age = new AttributeGroupEntry(ei);
    if (hmAttrGpEn.containsKey(age.name))
      throw new IllegalArgumentException("Namespace collision for : " + age.name);
    hmAttrGpEn.put(age.name, age);

  }

  //---------------------------------------------------------------------------
  //---
  //--- Add in attributes from complexType with SimpleContent that restricts
  //--- or extends a base type (if any)
  //---
  //---------------------------------------------------------------------------

  private List<AttributeEntry> resolveAttributeInheritanceFromSimpleContent(ComplexTypeEntry cte)
  {
    List<AttributeEntry> result = new ArrayList<AttributeEntry>();

    if (cte.simpleContent == null) {
      throw new IllegalArgumentException("SimpleContent must be present in base type of the SimpleContent in "+cte.name);
    } else {

      // recurse if we need to follow the base type
     
      String baseType = cte.simpleContent.base;
      ComplexTypeEntry baseCTE = hmTypes.get(baseType);
      if (baseCTE != null)
        result = new ArrayList<AttributeEntry>(resolveAttributeInheritanceFromSimpleContent(baseCTE));
 
      // if the base type was a restriction then replace the attributes we got
      // from the restriction with these
      if (cte.simpleContent.restriction) {
        @SuppressWarnings("unchecked")
                List<AttributeEntry> adds = (List<AttributeEntry>)cte.simpleContent.alAttribs.clone();
        for (int i = 0;i < result.size();i++) {
          AttributeEntry attrib = (AttributeEntry)result.get(i);
                    for (Object add : adds) {
                        AttributeEntry attribOther = (AttributeEntry) add;
                        boolean eqAttrib = eqAttribs(attribOther, attrib);
                        if (eqAttrib) {
                            result.set(i, attribOther);
                        }
                    }
        }
      }
      // otherwise base type was an extension so add the attributes we got
      // from the extension to these
      else {
         @SuppressWarnings("unchecked")
                List<AttributeEntry> clone = (List<AttributeEntry>)cte.simpleContent.alAttribs.clone();
                result.addAll(clone );
      }
   
      // No one seems clear on what to do with attributeGroups so treat them
      // as an extension
      if (cte.simpleContent.alAttribGroups != null) {
        for (int k=0;k<cte.simpleContent.alAttribGroups.size();k++) {
          String attribGroup = cte.simpleContent.alAttribGroups.get(k);
          List<AttributeEntry> al = hmAttrGrp.get(attribGroup);

          if (al == null)
            throw new IllegalArgumentException("Attribute group not found : " + attribGroup);

                    for (AttributeEntry anAl : al) {
                        result.add(anAl);
                    }
        }
      }
    }


    return result;
  }

  /** function to test whether two AttributeEntry objects have the same name
   */
  boolean eqAttribs(AttributeEntry attribOther,AttributeEntry attrib) {
    if (attribOther.name != null) {
      if (attrib.name != null) {
        if (attribOther.name.equals(attrib.name)) return true;
      } else {
        if (attribOther.name.equals(attrib.reference)) return true;
      }
    } else {
      if (attrib.name != null) {
        if (attribOther.reference.equals(attrib.name)) return true;
      } else {
        if (attribOther.reference.equals(attrib.reference)) return true;
      }
    }
    return false;
  }

  //---------------------------------------------------------------------------
  //---
  //--- Add in attributes from complexType with ComplexContent that restricts
  //--- or extends a base type (if any)
  //---
  //---------------------------------------------------------------------------

  private List<AttributeEntry> resolveAttributeInheritance(ComplexTypeEntry cte)
  {

    if (cte.complexContent == null)
      return cte.alAttribs;
   
    String baseType = cte.complexContent.base;
    ComplexTypeEntry baseCTE = hmTypes.get(baseType);
    if (baseCTE == null)
      throw new IllegalArgumentException("Base type not found for : " + baseType);

    List<AttributeEntry> result = new ArrayList<AttributeEntry>(resolveAttributeInheritance(baseCTE));

    // if the base type was a restriction then replace the attributes we got
    // from the restriction with these

    if (cte.complexContent.restriction) {
      List<AttributeEntry> adds = cte.complexContent.alAttribs;
      for (int i = 0;i < result.size();i++) {
        AttributeEntry attrib = (AttributeEntry)result.get(i);
                for (AttributeEntry attribOther : adds) {
                    boolean eqAttrib = eqAttribs(attribOther, attrib);
                    if (eqAttrib) {
                        result.set(i, attribOther);
                    }
                }
      }
    }
    // otherwise base type was an extension so add the attributes we got
    // from the extension to these
    else {
      result.addAll(cte.complexContent.alAttribs);
      if (cte.complexContent.alAttribGroups != null) {
        for (int k=0;k<cte.complexContent.alAttribGroups.size();k++) {
          String attribGroup = cte.complexContent.alAttribGroups.get(k);         
          List<AttributeEntry> al = hmAttrGrp.get(attribGroup);
          if (al == null)
            throw new IllegalArgumentException("Attribute group not found : " + attribGroup);
                    for (AttributeEntry anAl : al) {
                        result.add(anAl);
                    }
        }
      }
    }

    // No one seems clear on what to do with attributeGroups so treat them
    // as an extension
    if (baseCTE.alAttribGroups != null) {
      for (int k=0;k<baseCTE.alAttribGroups.size();k++) {
        String attribGroup = baseCTE.alAttribGroups.get(k);
        List<AttributeEntry> al = hmAttrGrp.get(attribGroup);

        if (al == null)
          throw new IllegalArgumentException("Attribute group not found : " + attribGroup);

                for (AttributeEntry anAl : al) {
                    result.add(anAl);
                }
      }
    }

    return result;
  }

  //---------------------------------------------------------------------------
  //---
  //--- Add in elements to complexType that come from base type (if any)
  //---
  //---------------------------------------------------------------------------

  private List<ElementEntry> resolveInheritance(ComplexTypeEntry cte)
  {
    if (cte == null || cte.complexContent == null)
            if (cte != null) {
                return cte.alElements;
            }

        String baseType = null;
        if (cte != null) {
            baseType = cte.complexContent.base;
        }
        ComplexTypeEntry baseCTE = hmTypes.get(baseType);
    if (baseCTE == null)
      throw new IllegalArgumentException("Base type not found for : " + baseType);

    // skip over the elements in the base type of a restricted complex type
    // by ending the recursion
    List<ElementEntry> result = new ArrayList<ElementEntry>();
    if (!cte.complexContent.restriction)
     result = new ArrayList<ElementEntry>(resolveInheritance(baseCTE));

    result.addAll(cte.complexContent.alElements);

    return result;
  }

  //---------------------------------------------------------------------------

  private MetadataAttribute buildMetadataAttrib(AttributeEntry ae)
  {
    String name = ae.name;
    String ref  = ae.reference;
    String value = ae.defValue;
    boolean overRequired = ae.required;

    MetadataAttribute ma = new MetadataAttribute();

    if (ref != null) {
      ae = hmAttribs.get(ref);
      if (ae == null)
        throw new IllegalArgumentException("Reference '"+ref+"' not found for attrib : " +name+":"+ref);
    }

    if (ref != null && ref.contains(":"))
      ma.name = ref;
    else
      ma.name = ae.unqualifiedName;

    if (value != null)
      ma.defValue = value;
    else
      ma.defValue = ae.defValue;

    ma.required = overRequired;

    // Load simple type entry values
    if (ae.type != null) {
      List<String> values = hmTypeRestr.get(ae.type);
      if (values != null){
        for (String v : values) {
                    ae.alValues.add(v);
        }
      }
      // Load member types entry values
      List<String> memberTypes = hmMemberTypeRestr.get(ae.type);
      if (memberTypes != null) {
        for (String type : memberTypes) {
          List<String> memberTypeValues = hmTypeRestr.get((String)type);
          if (memberTypeValues != null){
            for (String v : memberTypeValues) {
              ma.values.add((String) v);
            }
          }
        }
      }
    }


    for(int k=0; k<ae.alValues.size(); k++) {
      ma.values.add(ae.alValues.get(k));
        }

    return ma;
  }

  //---------------------------------------------------------------------------

  public String getUnqualifiedName(String qname) {
    int pos = qname.indexOf(':');
    if (pos < 0) return qname;
    else         return qname.substring(pos + 1);
  }
}

//==============================================================================

class ElementInfo
{
  public Element element;
  public String  file;
  public String  targetNS;
  public String  targetNSPrefix;

  //---------------------------------------------------------------------------

  public ElementInfo(Element e, String f, String tns, String tnsp)
  {
    element        = e;
    file           = f;
    targetNS       = tns;
    targetNSPrefix = tnsp;
  }
}

//==============================================================================
TOP

Related Classes of org.fao.geonet.kernel.schema.SchemaLoader

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.