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