1 /*
   2  * Copyright (c) 1997, 2015, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package com.sun.xml.internal.xsom.impl.parser;
  27 
  28 import com.sun.xml.internal.xsom.XSDeclaration;
  29 import com.sun.xml.internal.xsom.XmlString;
  30 import com.sun.xml.internal.xsom.XSSimpleType;
  31 import com.sun.xml.internal.xsom.impl.ForeignAttributesImpl;
  32 import com.sun.xml.internal.xsom.impl.SchemaImpl;
  33 import com.sun.xml.internal.xsom.impl.UName;
  34 import com.sun.xml.internal.xsom.impl.Const;
  35 import com.sun.xml.internal.xsom.impl.parser.state.NGCCRuntime;
  36 import com.sun.xml.internal.xsom.impl.parser.state.Schema;
  37 import com.sun.xml.internal.xsom.impl.util.Uri;
  38 import com.sun.xml.internal.xsom.parser.AnnotationParser;
  39 import com.sun.xml.internal.org.relaxng.datatype.ValidationContext;
  40 import org.xml.sax.Attributes;
  41 import org.xml.sax.EntityResolver;
  42 import org.xml.sax.ErrorHandler;
  43 import org.xml.sax.InputSource;
  44 import org.xml.sax.Locator;
  45 import org.xml.sax.SAXException;
  46 import org.xml.sax.SAXParseException;
  47 import org.xml.sax.helpers.LocatorImpl;
  48 
  49 import java.io.IOException;
  50 import java.net.URI;
  51 import java.text.MessageFormat;
  52 import java.util.Stack;
  53 
  54 /**
  55  * NGCCRuntime extended with various utility methods for
  56  * parsing XML Schema.
  57  *
  58  * @author Kohsuke Kawaguchi (kohsuke.kawaguchi@sun.com)
  59  */
  60 public class NGCCRuntimeEx extends NGCCRuntime implements PatcherManager {
  61 
  62     /** coordinator. */
  63     public final ParserContext parser;
  64 
  65     /** The schema currently being parsed. */
  66     public SchemaImpl currentSchema;
  67 
  68     /** The @finalDefault value of the current schema. */
  69     public int finalDefault = 0;
  70     /** The @blockDefault value of the current schema. */
  71     public int blockDefault = 0;
  72 
  73     /**
  74      * The @elementFormDefault value of the current schema.
  75      * True if local elements are qualified by default.
  76      */
  77     public boolean elementFormDefault = false;
  78 
  79     /**
  80      * The @attributeFormDefault value of the current schema.
  81      * True if local attributes are qualified by default.
  82      */
  83     public boolean attributeFormDefault = false;
  84 
  85     /**
  86      * True if the current schema is in a chameleon mode.
  87      * This changes the way QNames are interpreted.
  88      *
  89      * Life is very miserable with XML Schema, as you see.
  90      */
  91     public boolean chameleonMode = false;
  92 
  93     /**
  94      * URI that identifies the schema document.
  95      * Maybe null if the system ID is not available.
  96      */
  97     private String documentSystemId;
  98 
  99     /**
 100      * Keep the local name of elements encountered so far.
 101      * This information is passed to AnnotationParser as
 102      * context information
 103      */
 104     private final Stack<String> elementNames = new Stack<String>();
 105 
 106     /**
 107      * Points to the schema document (the parser of it) that included/imported
 108      * this schema.
 109      */
 110     private final NGCCRuntimeEx referer;
 111 
 112     /**
 113      * Points to the {@link SchemaDocumentImpl} that represents the
 114      * schema document being parsed.
 115      */
 116     public SchemaDocumentImpl document;
 117 
 118     NGCCRuntimeEx( ParserContext _parser ) {
 119         this(_parser,false,null);
 120     }
 121 
 122     private NGCCRuntimeEx( ParserContext _parser, boolean chameleonMode, NGCCRuntimeEx referer ) {
 123         this.parser = _parser;
 124         this.chameleonMode = chameleonMode;
 125         this.referer = referer;
 126 
 127         // set up the default namespace binding
 128         currentContext = new Context("","",null);
 129         currentContext = new Context("xml","http://www.w3.org/XML/1998/namespace",currentContext);
 130     }
 131 
 132     public void checkDoubleDefError( XSDeclaration c ) throws SAXException {
 133         if(c==null || ignorableDuplicateComponent(c)) return;
 134 
 135         reportError( Messages.format(Messages.ERR_DOUBLE_DEFINITION,c.getName()) );
 136         reportError( Messages.format(Messages.ERR_DOUBLE_DEFINITION_ORIGINAL), c.getLocator() );
 137     }
 138 
 139     public static boolean ignorableDuplicateComponent(XSDeclaration c) {
 140         if(c.getTargetNamespace().equals(Const.schemaNamespace)) {
 141             if(c instanceof XSSimpleType)
 142                 // hide artificial "double definitions" on simple types
 143                 return true;
 144             if(c.isGlobal() && c.getName().equals("anyType"))
 145                 return true; // ditto for anyType
 146         }
 147         return false;
 148     }
 149 
 150 
 151 
 152     /* registers a patcher that will run after all the parsing has finished. */
 153     public void addPatcher( Patch patcher ) {
 154         parser.patcherManager.addPatcher(patcher);
 155     }
 156     public void addErrorChecker( Patch patcher ) {
 157         parser.patcherManager.addErrorChecker(patcher);
 158     }
 159     public void reportError( String msg, Locator loc ) throws SAXException {
 160         parser.patcherManager.reportError(msg,loc);
 161     }
 162     public void reportError( String msg ) throws SAXException {
 163         reportError(msg,getLocator());
 164     }
 165 
 166 
 167     /**
 168      * Resolves relative URI found in the document.
 169      *
 170      * @param namespaceURI
 171      *      passed to the entity resolver.
 172      * @param relativeUri
 173      *      value of the schemaLocation attribute. Can be null.
 174      *
 175      * @return
 176      *      non-null if {@link EntityResolver} returned an {@link InputSource},
 177      *      or if the relativeUri parameter seems to be pointing to something.
 178      *      Otherwise it returns null, in which case import/include should be abandoned.
 179      */
 180     private InputSource resolveRelativeURL( String namespaceURI, String relativeUri ) throws SAXException {
 181         try {
 182             String baseUri = getLocator().getSystemId();
 183             if(baseUri==null)
 184                 // if the base URI is not available, the document system ID is
 185                 // better than nothing.
 186                 baseUri=documentSystemId;
 187 
 188             EntityResolver er = parser.getEntityResolver();
 189             String systemId = null;
 190 
 191             if (relativeUri!=null)
 192                 systemId = Uri.resolve(baseUri,relativeUri);
 193 
 194             if (er!=null) {
 195                 InputSource is = er.resolveEntity(namespaceURI,systemId);
 196                 if (is == null) {
 197                     try {
 198                         String normalizedSystemId = URI.create(systemId).normalize().toASCIIString();
 199                         is = er.resolveEntity(namespaceURI,normalizedSystemId);
 200                     } catch (Exception e) {
 201                         // just ignore, this is a second try, return the fallback if this breaks
 202                     }
 203                 }
 204                 if (is != null) {
 205                     return is;
 206                 }
 207             }
 208 
 209             if (systemId!=null)
 210                 return new InputSource(systemId);
 211             else
 212                 return null;
 213         } catch (IOException e) {
 214             SAXParseException se = new SAXParseException(e.getMessage(),getLocator(),e);
 215             parser.errorHandler.error(se);
 216             return null;
 217         }
 218     }
 219 
 220     /** Includes the specified schema. */
 221     public void includeSchema( String schemaLocation ) throws SAXException {
 222         NGCCRuntimeEx runtime = new NGCCRuntimeEx(parser,chameleonMode,this);
 223         runtime.currentSchema = this.currentSchema;
 224         runtime.blockDefault = this.blockDefault;
 225         runtime.finalDefault = this.finalDefault;
 226 
 227         if( schemaLocation==null ) {
 228             SAXParseException e = new SAXParseException(
 229                 Messages.format( Messages.ERR_MISSING_SCHEMALOCATION ), getLocator() );
 230             parser.errorHandler.fatalError(e);
 231             throw e;
 232         }
 233 
 234         runtime.parseEntity( resolveRelativeURL(null,schemaLocation),
 235             true, currentSchema.getTargetNamespace(), getLocator() );
 236     }
 237 
 238     /** Imports the specified schema. */
 239     public void importSchema( String ns, String schemaLocation ) throws SAXException {
 240         NGCCRuntimeEx newRuntime = new NGCCRuntimeEx(parser,false,this);
 241         InputSource source = resolveRelativeURL(ns,schemaLocation);
 242         if(source!=null)
 243             newRuntime.parseEntity( source, false, ns, getLocator() );
 244         // if source == null,
 245         // we can't locate this document. Let's just hope that
 246         // we already have the schema components for this schema
 247         // or we will receive them in the future.
 248     }
 249 
 250     /**
 251      * Called when a new document is being parsed and checks
 252      * if the document has already been parsed before.
 253      *
 254      * <p>
 255      * Used to avoid recursive inclusion. Note that the same
 256      * document will be parsed multiple times if they are for different
 257      * target namespaces.
 258      *
 259      * <h2>Document Graph Model</h2>
 260      * <p>
 261      * The challenge we are facing here is that you have a graph of
 262      * documents that reference each other. Each document has an unique
 263      * URI to identify themselves, and references are done by using those.
 264      * The graph may contain cycles.
 265      *
 266      * <p>
 267      * Our goal here is to parse all the documents in the graph, without
 268      * parsing the same document twice. This method implements this check.
 269      *
 270      * <p>
 271      * One complication is the chameleon schema; a document can be parsed
 272      * multiple times if they are under different target namespaces.
 273      *
 274      * <p>
 275      * Also, note that when you resolve relative URIs in the @schemaLocation,
 276      * their base URI is *NOT* the URI of the document.
 277      *
 278      * @return true if the document has already been processed and thus
 279      *      needs to be skipped.
 280      */
 281     public boolean hasAlreadyBeenRead() {
 282         if( documentSystemId!=null ) {
 283             if( documentSystemId.startsWith("file:///") )
 284                 // change file:///abc to file:/abc
 285                 // JDK File.toURL method produces the latter, but according to RFC
 286                 // I don't think that's a valid URL. Since two different ways of
 287                 // producing URLs could produce those two different forms,
 288                 // we need to canonicalize one to the other.
 289                 documentSystemId = "file:/"+documentSystemId.substring(8);
 290         } else {
 291             // if the system Id is not provided, we can't test the identity,
 292             // so we have no choice but to read it.
 293             // the newly created SchemaDocumentImpl will be unique one
 294         }
 295 
 296         assert document ==null;
 297         document = new SchemaDocumentImpl( currentSchema, documentSystemId );
 298 
 299         SchemaDocumentImpl existing = parser.parsedDocuments.get(document);
 300         if(existing==null) {
 301             parser.parsedDocuments.put(document,document);
 302         } else {
 303             document = existing;
 304         }
 305 
 306         assert document !=null;
 307 
 308         if(referer!=null) {
 309             assert referer.document !=null : "referer "+referer.documentSystemId+" has docIdentity==null";
 310             referer.document.references.add(this.document);
 311             this.document.referers.add(referer.document);
 312         }
 313 
 314         return existing!=null;
 315     }
 316 
 317     /**
 318      * Parses the specified entity.
 319      *
 320      * @param importLocation
 321      *      The source location of the import/include statement.
 322      *      Used for reporting errors.
 323      */
 324     public void parseEntity( InputSource source, boolean includeMode, String expectedNamespace, Locator importLocation )
 325             throws SAXException {
 326 
 327         documentSystemId = source.getSystemId();
 328         try {
 329             Schema s = new Schema(this,includeMode,expectedNamespace);
 330             setRootHandler(s);
 331             try {
 332                 parser.parser.parse(source,this, getErrorHandler(), parser.getEntityResolver());
 333             } catch( IOException fnfe ) {
 334                 SAXParseException se = new SAXParseException(fnfe.toString(), importLocation, fnfe);
 335                 parser.errorHandler.warning(se);
 336             }
 337         } catch( SAXException e ) {
 338             parser.setErrorFlag();
 339             throw e;
 340         }
 341     }
 342 
 343     /**
 344      * Creates a new instance of annotation parser.
 345      */
 346     public AnnotationParser createAnnotationParser() {
 347         if(parser.getAnnotationParserFactory()==null)
 348             return DefaultAnnotationParser.theInstance;
 349         else
 350             return parser.getAnnotationParserFactory().create();
 351     }
 352 
 353     /**
 354      * Gets the element name that contains the annotation element.
 355      * This method works correctly only when called by the annotation handler.
 356      */
 357     public String getAnnotationContextElementName() {
 358         return elementNames.get( elementNames.size()-2 );
 359     }
 360 
 361     /** Creates a copy of the current locator object. */
 362     public Locator copyLocator() {
 363         return new LocatorImpl(getLocator());
 364     }
 365 
 366     public ErrorHandler getErrorHandler() {
 367         return parser.errorHandler;
 368     }
 369 
 370     @Override
 371     public void onEnterElementConsumed(String uri, String localName, String qname, Attributes atts)
 372         throws SAXException {
 373         super.onEnterElementConsumed(uri, localName, qname, atts);
 374         elementNames.push(localName);
 375     }
 376 
 377     @Override
 378     public void onLeaveElementConsumed(String uri, String localName, String qname) throws SAXException {
 379         super.onLeaveElementConsumed(uri, localName, qname);
 380         elementNames.pop();
 381     }
 382 
 383 
 384 
 385 //
 386 //
 387 // ValidationContext implementation
 388 //
 389 //
 390     // this object lives longer than the parser itself,
 391     // so it's important for this object not to have any reference
 392     // to the parser.
 393     private static class Context implements ValidationContext {
 394         Context( String _prefix, String _uri, Context _context ) {
 395             this.previous = _context;
 396             this.prefix = _prefix;
 397             this.uri = _uri;
 398         }
 399 
 400         public String resolveNamespacePrefix(String p) {
 401             if(p.equals(prefix))    return uri;
 402             if(previous==null)      return null;
 403             else                    return previous.resolveNamespacePrefix(p);
 404         }
 405 
 406         private final String prefix;
 407         private final String uri;
 408         private final Context previous;
 409 
 410         // XSDLib don't use those methods, so we cut a corner here.
 411         public String getBaseUri() { return null; }
 412         public boolean isNotation(String arg0) { return false; }
 413         public boolean isUnparsedEntity(String arg0) { return false; }
 414     }
 415 
 416     private Context currentContext=null;
 417 
 418     /** Returns an immutable snapshot of the current context. */
 419     public ValidationContext createValidationContext() {
 420         return currentContext;
 421     }
 422 
 423     public XmlString createXmlString(String value) {
 424         if(value==null)     return null;
 425         else    return new XmlString(value,createValidationContext());
 426     }
 427 
 428     @Override
 429     public void startPrefixMapping( String prefix, String uri ) throws SAXException {
 430         super.startPrefixMapping(prefix,uri);
 431         currentContext = new Context(prefix,uri,currentContext);
 432     }
 433     @Override
 434     public void endPrefixMapping( String prefix ) throws SAXException {
 435         super.endPrefixMapping(prefix);
 436         currentContext = currentContext.previous;
 437     }
 438 
 439 
 440 
 441 
 442 
 443 //
 444 //
 445 // Utility functions
 446 //
 447 //
 448 
 449 
 450     /** Parses UName under the given context. */
 451     public UName parseUName( String qname ) throws SAXException {
 452         int idx = qname.indexOf(':');
 453         if(idx<0) {
 454             String uri = resolveNamespacePrefix("");
 455 
 456             // chamelon behavior. ugly...
 457             if( uri.equals("") && chameleonMode )
 458                 uri = currentSchema.getTargetNamespace();
 459 
 460             // this is guaranteed to resolve
 461             return new UName(uri,qname,qname);
 462         } else {
 463             String prefix = qname.substring(0,idx);
 464             String uri = currentContext.resolveNamespacePrefix(prefix);
 465             if(uri==null) {
 466                 // prefix failed to resolve.
 467                 reportError(Messages.format(
 468                     Messages.ERR_UNDEFINED_PREFIX,prefix));
 469                 uri="undefined"; // replace with a dummy
 470             }
 471             return new UName( uri, qname.substring(idx+1), qname );
 472         }
 473     }
 474 
 475     public boolean parseBoolean(String v) {
 476         if(v==null) return false;
 477         v=v.trim();
 478         return v.equals("true") || v.equals("1");
 479     }
 480 
 481 
 482     @Override
 483     protected void unexpectedX(String token) throws SAXException {
 484         SAXParseException e = new SAXParseException(MessageFormat.format(
 485             "Unexpected {0} appears at line {1} column {2}",
 486                 token,
 487                 getLocator().getLineNumber(),
 488                 getLocator().getColumnNumber()),
 489             getLocator());
 490 
 491         parser.errorHandler.fatalError(e);
 492         throw e;    // we will abort anyway
 493     }
 494 
 495     public ForeignAttributesImpl parseForeignAttributes( ForeignAttributesImpl next ) {
 496         ForeignAttributesImpl impl = new ForeignAttributesImpl(createValidationContext(),copyLocator(),next);
 497 
 498         Attributes atts = getCurrentAttributes();
 499         for( int i=0; i<atts.getLength(); i++ ) {
 500             if(atts.getURI(i).length()>0) {
 501                 impl.addAttribute(
 502                     atts.getURI(i),
 503                     atts.getLocalName(i),
 504                     atts.getQName(i),
 505                     atts.getType(i),
 506                     atts.getValue(i)
 507                 );
 508             }
 509         }
 510 
 511         return impl;
 512     }
 513 
 514 
 515     public static final String XMLSchemaNSURI = "http://www.w3.org/2001/XMLSchema";
 516 }