View Javadoc
1   /*
2    * Copyright (c) 1997, 2011, 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 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 }