001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common Development
008: * and Distribution License("CDDL") (collectively, the "License"). You
009: * may not use this file except in compliance with the License. You can obtain
010: * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
011: * or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific
012: * language governing permissions and limitations under the License.
013: *
014: * When distributing the software, include this License Header Notice in each
015: * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
016: * Sun designates this particular file as subject to the "Classpath" exception
017: * as provided by Sun in the GPL Version 2 section of the License file that
018: * accompanied this code. If applicable, add the following below the License
019: * Header, with the fields enclosed by brackets [] replaced by your own
020: * identifying information: "Portions Copyrighted [year]
021: * [name of copyright owner]"
022: *
023: * Contributor(s):
024: *
025: * If you wish your version of this file to be governed by only the CDDL or
026: * only the GPL Version 2, indicate your decision by adding "[Contributor]
027: * elects to include this software in this distribution under the [CDDL or GPL
028: * Version 2] license." If you don't indicate a single choice of license, a
029: * recipient has the option to distribute your version of this file under
030: * either the CDDL, the GPL Version 2 or to extend the choice of license to
031: * its licensees as provided above. However, if you add GPL Version 2 code
032: * and therefore, elected the GPL Version 2 license, then the option applies
033: * only if the new code is made subject to such option by the copyright
034: * holder.
035: */
036: package com.sun.tools.xjc;
037:
038: import java.io.IOException;
039: import java.io.StringReader;
040:
041: import com.sun.codemodel.JCodeModel;
042: import com.sun.tools.xjc.model.Model;
043: import com.sun.tools.xjc.reader.Const;
044: import com.sun.tools.xjc.reader.ExtensionBindingChecker;
045: import com.sun.tools.xjc.reader.dtd.TDTDReader;
046: import com.sun.tools.xjc.reader.internalizer.DOMForest;
047: import com.sun.tools.xjc.reader.internalizer.DOMForestScanner;
048: import com.sun.tools.xjc.reader.internalizer.InternalizationLogic;
049: import com.sun.tools.xjc.reader.internalizer.VersionChecker;
050: import com.sun.tools.xjc.reader.internalizer.SCDBasedBindingSet;
051: import com.sun.tools.xjc.reader.relaxng.RELAXNGCompiler;
052: import com.sun.tools.xjc.reader.relaxng.RELAXNGInternalizationLogic;
053: import com.sun.tools.xjc.reader.xmlschema.BGMBuilder;
054: import com.sun.tools.xjc.reader.xmlschema.bindinfo.AnnotationParserFactoryImpl;
055: import com.sun.tools.xjc.reader.xmlschema.parser.CustomizationContextChecker;
056: import com.sun.tools.xjc.reader.xmlschema.parser.IncorrectNamespaceURIChecker;
057: import com.sun.tools.xjc.reader.xmlschema.parser.SchemaConstraintChecker;
058: import com.sun.tools.xjc.reader.xmlschema.parser.XMLSchemaInternalizationLogic;
059: import com.sun.tools.xjc.util.ErrorReceiverFilter;
060: import com.sun.xml.bind.v2.WellKnownNamespace;
061: import com.sun.xml.xsom.XSSchemaSet;
062: import com.sun.xml.xsom.parser.JAXPParser;
063: import com.sun.xml.xsom.parser.XMLParser;
064: import com.sun.xml.xsom.parser.XSOMParser;
065:
066: import org.kohsuke.rngom.ast.builder.SchemaBuilder;
067: import org.kohsuke.rngom.ast.util.CheckingSchemaBuilder;
068: import org.kohsuke.rngom.digested.DPattern;
069: import org.kohsuke.rngom.digested.DSchemaBuilderImpl;
070: import org.kohsuke.rngom.parse.IllegalSchemaException;
071: import org.kohsuke.rngom.parse.Parseable;
072: import org.kohsuke.rngom.parse.compact.CompactParseable;
073: import org.kohsuke.rngom.parse.xml.SAXParseable;
074: import org.kohsuke.rngom.xml.sax.XMLReaderCreator;
075: import org.w3c.dom.Document;
076: import org.w3c.dom.Element;
077: import org.w3c.dom.NodeList;
078: import org.xml.sax.Attributes;
079: import org.xml.sax.ContentHandler;
080: import org.xml.sax.EntityResolver;
081: import org.xml.sax.ErrorHandler;
082: import org.xml.sax.InputSource;
083: import org.xml.sax.SAXException;
084: import org.xml.sax.SAXParseException;
085: import org.xml.sax.XMLFilter;
086: import org.xml.sax.XMLReader;
087: import org.xml.sax.helpers.XMLFilterImpl;
088:
089: /**
090: * Builds a {@link Model} object.
091: *
092: * This is an utility class that makes it easy to load a grammar object
093: * from various sources.
094: *
095: * @author
096: * Kohsuke Kawaguchi (kohsuke.kawaguchi@sun.com)
097: */
098: public final class ModelLoader {
099:
100: private final Options opt;
101: private final ErrorReceiverFilter errorReceiver;
102: private final JCodeModel codeModel;
103: /**
104: * {@link DOMForest#transform(boolean)} creates this on the side.
105: */
106: private SCDBasedBindingSet scdBasedBindingSet;
107:
108: /**
109: * A convenience method to load schemas into a {@link Model}.
110: */
111: public static Model load(Options opt, JCodeModel codeModel,
112: ErrorReceiver er) {
113: return new ModelLoader(opt, codeModel, er).load();
114: }
115:
116: public ModelLoader(Options _opt, JCodeModel _codeModel,
117: ErrorReceiver er) {
118: this .opt = _opt;
119: this .codeModel = _codeModel;
120: this .errorReceiver = new ErrorReceiverFilter(er);
121: }
122:
123: private Model load() {
124: Model grammar;
125:
126: if (!sanityCheck())
127: return null;
128:
129: try {
130: switch (opt.getSchemaLanguage()) {
131: case DTD:
132: // TODO: make sure that bindFiles,size()<=1
133: InputSource bindFile = null;
134: if (opt.getBindFiles().length > 0)
135: bindFile = opt.getBindFiles()[0];
136: // if there is no binding file, make a dummy one.
137: if (bindFile == null) {
138: // if no binding information is specified, provide a default
139: bindFile = new InputSource(
140: new StringReader(
141: "<?xml version='1.0'?><xml-java-binding-schema><options package='"
142: + (opt.defaultPackage == null ? "generated"
143: : opt.defaultPackage)
144: + "'/></xml-java-binding-schema>"));
145: }
146:
147: checkTooManySchemaErrors();
148: grammar = loadDTD(opt.getGrammars()[0], bindFile);
149: break;
150:
151: case RELAXNG:
152: checkTooManySchemaErrors();
153: grammar = loadRELAXNG();
154: break;
155:
156: case RELAXNG_COMPACT:
157: checkTooManySchemaErrors();
158: grammar = loadRELAXNGCompact();
159: break;
160:
161: case WSDL:
162: grammar = annotateXMLSchema(loadWSDL());
163: break;
164:
165: case XMLSCHEMA:
166: grammar = annotateXMLSchema(loadXMLSchema());
167: break;
168:
169: default:
170: throw new AssertionError(); // assertion failed
171: }
172:
173: if (errorReceiver.hadError()) {
174: grammar = null;
175: } else {
176: grammar
177: .setPackageLevelAnnotations(opt.packageLevelAnnotations);
178: }
179:
180: return grammar;
181:
182: } catch (SAXException e) {
183: // parsing error in the input document.
184: // this error must have been reported to the user vis error handler
185: // so don't print it again.
186: if (opt.verbose) {
187: // however, a bug in XJC might throw unexpected SAXException.
188: // thus when one is debugging, it is useful to print what went
189: // wrong.
190: if (e.getException() != null)
191: e.getException().printStackTrace();
192: else
193: e.printStackTrace();
194: }
195: return null;
196: } catch (AbortException e) {
197: // error should have been reported already, since this is requested by the error receiver
198: return null;
199: }
200: }
201:
202: /**
203: * Do some extra checking and return false if the compilation
204: * should abort.
205: */
206: private boolean sanityCheck() {
207: if (opt.getSchemaLanguage() == Language.XMLSCHEMA) {
208: Language guess = opt.guessSchemaLanguage();
209:
210: String[] msg = null;
211: switch (guess) {
212: case DTD:
213: msg = new String[] { "DTD", "-dtd" };
214: break;
215: case RELAXNG:
216: msg = new String[] { "RELAX NG", "-relaxng" };
217: break;
218: case RELAXNG_COMPACT:
219: msg = new String[] { "RELAX NG compact syntax",
220: "-relaxng-compact" };
221: break;
222: case WSDL:
223: msg = new String[] { "WSDL", "-wsdl" };
224: break;
225: }
226: if (msg != null)
227: errorReceiver.warning(null, Messages.format(
228: Messages.EXPERIMENTAL_LANGUAGE_WARNING, msg[0],
229: msg[1]));
230: }
231: return true;
232: }
233:
234: /**
235: * {@link XMLParser} implementation that adds additional processors into the chain.
236: *
237: * <p>
238: * This parser will parse a DOM forest as:
239: * DOMForestParser -->
240: * ExtensionBindingChecker -->
241: * ProhibitedFeatureFilter -->
242: * XSOMParser
243: */
244: private class XMLSchemaParser implements XMLParser {
245: private final XMLParser baseParser;
246:
247: private XMLSchemaParser(XMLParser baseParser) {
248: this .baseParser = baseParser;
249: }
250:
251: public void parse(InputSource source, ContentHandler handler,
252: ErrorHandler errorHandler, EntityResolver entityResolver)
253: throws SAXException, IOException {
254: // set up the chain of handlers.
255: handler = wrapBy(new ExtensionBindingChecker(
256: WellKnownNamespace.XML_SCHEMA, opt, errorReceiver),
257: handler);
258: handler = wrapBy(new IncorrectNamespaceURIChecker(
259: errorReceiver), handler);
260: handler = wrapBy(new CustomizationContextChecker(
261: errorReceiver), handler);
262: // handler = wrapBy( new VersionChecker(controller), handler );
263:
264: baseParser.parse(source, handler, errorHandler,
265: entityResolver);
266: }
267:
268: /**
269: * Wraps the specified content handler by a filter.
270: * It is little awkward to use a helper implementation class like XMLFilterImpl
271: * as the method parameter, but this simplifies the code.
272: */
273: private ContentHandler wrapBy(XMLFilterImpl filter,
274: ContentHandler handler) {
275: filter.setContentHandler(handler);
276: return filter;
277: }
278: }
279:
280: private void checkTooManySchemaErrors() {
281: if (opt.getGrammars().length != 1)
282: errorReceiver.error(null, Messages
283: .format(Messages.ERR_TOO_MANY_SCHEMA));
284: }
285:
286: /**
287: * Parses a DTD file into an annotated grammar.
288: *
289: * @param source
290: * DTD file
291: * @param bindFile
292: * External binding file.
293: */
294: private Model loadDTD(InputSource source, InputSource bindFile) {
295:
296: // parse the schema as a DTD.
297: return TDTDReader.parse(source, bindFile, errorReceiver, opt);
298: }
299:
300: /**
301: * Builds DOMForest and performs the internalization.
302: *
303: * @throws SAXException
304: * when a fatal happe
305: */
306: public DOMForest buildDOMForest(InternalizationLogic logic)
307: throws SAXException {
308:
309: // parse into DOM forest
310: DOMForest forest = new DOMForest(logic);
311:
312: forest.setErrorHandler(errorReceiver);
313: if (opt.entityResolver != null)
314: forest.setEntityResolver(opt.entityResolver);
315:
316: // parse source grammars
317: for (InputSource value : opt.getGrammars()) {
318: errorReceiver.pollAbort();
319: forest.parse(value, true);
320: }
321:
322: // parse external binding files
323: for (InputSource value : opt.getBindFiles()) {
324: errorReceiver.pollAbort();
325: Document dom = forest.parse(value, true);
326: if (dom == null)
327: continue; // error must have been reported
328: Element root = dom.getDocumentElement();
329: // TODO: it somehow doesn't feel right to do a validation in the Driver class.
330: // think about moving it to somewhere else.
331: if (!fixNull(root.getNamespaceURI()).equals(
332: Const.JAXB_NSURI)
333: || !root.getLocalName().equals("bindings"))
334: errorReceiver.error(new SAXParseException(
335: Messages.format(
336: Messages.ERR_NOT_A_BINDING_FILE, root
337: .getNamespaceURI(), root
338: .getLocalName()), null, value
339: .getSystemId(), -1, -1));
340: }
341:
342: scdBasedBindingSet = forest.transform(opt.isExtensionMode());
343:
344: return forest;
345: }
346:
347: private String fixNull(String s) {
348: if (s == null)
349: return "";
350: else
351: return s;
352: }
353:
354: /**
355: * Parses a set of XML Schema files into an annotated grammar.
356: */
357: public XSSchemaSet loadXMLSchema() throws SAXException {
358:
359: if (opt.strictCheck
360: && !SchemaConstraintChecker.check(opt.getGrammars(),
361: errorReceiver, opt.entityResolver)) {
362: // schema error. error should have been reported
363: return null;
364: }
365:
366: if (opt.getBindFiles().length == 0) {
367: // no external binding. try the speculative no DOMForest execution,
368: // which is faster if the speculation succeeds.
369: try {
370: return createXSOMSpeculative();
371: } catch (SpeculationFailure _) {
372: // failed. go the slow way
373: }
374: }
375:
376: // the default slower way is to parse everything into DOM first.
377: // so that we can take external annotations into account.
378: DOMForest forest = buildDOMForest(new XMLSchemaInternalizationLogic());
379: return createXSOM(forest, scdBasedBindingSet);
380: }
381:
382: /**
383: * Parses a set of schemas inside a WSDL file.
384: *
385: * A WSDL file may contain multiple <xsd:schema> elements.
386: */
387: private XSSchemaSet loadWSDL() throws SAXException {
388:
389: // build DOMForest just like we handle XML Schema
390: DOMForest forest = buildDOMForest(new XMLSchemaInternalizationLogic());
391:
392: DOMForestScanner scanner = new DOMForestScanner(forest);
393:
394: XSOMParser xsomParser = createXSOMParser(forest);
395:
396: // find <xsd:schema>s and parse them individually
397: for (InputSource grammar : opt.getGrammars()) {
398: Document wsdlDom = forest.get(grammar.getSystemId());
399:
400: NodeList schemas = wsdlDom.getElementsByTagNameNS(
401: WellKnownNamespace.XML_SCHEMA, "schema");
402: for (int i = 0; i < schemas.getLength(); i++)
403: scanner.scan((Element) schemas.item(i), xsomParser
404: .getParserHandler());
405: }
406: return xsomParser.getResult();
407: }
408:
409: /**
410: * Annotates the obtained schema set.
411: *
412: * @return
413: * null if an error happens. In that case, the error messages
414: * will be properly reported to the controller by this method.
415: */
416: public Model annotateXMLSchema(XSSchemaSet xs) {
417: if (xs == null)
418: return null;
419: return BGMBuilder.build(xs, codeModel, errorReceiver, opt);
420: }
421:
422: public XSOMParser createXSOMParser(XMLParser parser) {
423: // set up other parameters to XSOMParser
424: XSOMParser reader = new XSOMParser(new XMLSchemaParser(parser));
425: reader
426: .setAnnotationParser(new AnnotationParserFactoryImpl(
427: opt));
428: reader.setErrorHandler(errorReceiver);
429: reader.setEntityResolver(opt.entityResolver);
430: return reader;
431: }
432:
433: public XSOMParser createXSOMParser(final DOMForest forest) {
434: XSOMParser p = createXSOMParser(forest.createParser());
435: p.setEntityResolver(new EntityResolver() {
436: public InputSource resolveEntity(String publicId,
437: String systemId) throws SAXException, IOException {
438: // DOMForest only parses documents that are rearchable through systemIds,
439: // and it won't pick up references like <xs:import namespace="..." /> without
440: // @schemaLocation. So we still need to use an entity resolver here to resolve
441: // these references, yet we don't want to just run them blindly, since if we do that
442: // DOMForestParser always get the translated system ID when catalog is used
443: // (where DOMForest records trees with their original system IDs.)
444: if (systemId != null && forest.get(systemId) != null)
445: return new InputSource(systemId);
446: if (opt.entityResolver != null)
447: return opt.entityResolver.resolveEntity(publicId,
448: systemId);
449:
450: return null;
451: }
452: });
453: return p;
454: }
455:
456: private static final class SpeculationFailure extends Error {
457: }
458:
459: private static final class SpeculationChecker extends XMLFilterImpl {
460: public void startElement(String uri, String localName,
461: String qName, Attributes attributes)
462: throws SAXException {
463: if (localName.equals("bindings")
464: && uri.equals(Const.JAXB_NSURI))
465: throw new SpeculationFailure();
466: super .startElement(uri, localName, qName, attributes);
467: }
468: }
469:
470: /**
471: * Parses schemas directly into XSOM by assuming that there's
472: * no external annotations.
473: * <p>
474: * When an external annotation is found, a {@link SpeculationFailure} is thrown,
475: * and we will do it all over again by using the slow way.
476: */
477: private XSSchemaSet createXSOMSpeculative() throws SAXException,
478: SpeculationFailure {
479:
480: // check if the schema contains external binding files. If so, speculation is a failure.
481:
482: XMLParser parser = new XMLParser() {
483: private final JAXPParser base = new JAXPParser();
484:
485: public void parse(InputSource source,
486: ContentHandler handler, ErrorHandler errorHandler,
487: EntityResolver entityResolver) throws SAXException,
488: IOException {
489: // set up the chain of handlers.
490: handler = wrapBy(new SpeculationChecker(), handler);
491: handler = wrapBy(new VersionChecker(null,
492: errorReceiver, entityResolver), handler);
493:
494: base.parse(source, handler, errorHandler,
495: entityResolver);
496: }
497:
498: /**
499: * Wraps the specified content handler by a filter.
500: * It is little awkward to use a helper implementation class like XMLFilterImpl
501: * as the method parameter, but this simplifies the code.
502: */
503: private ContentHandler wrapBy(XMLFilterImpl filter,
504: ContentHandler handler) {
505: filter.setContentHandler(handler);
506: return filter;
507: }
508: };
509:
510: XSOMParser reader = createXSOMParser(parser);
511:
512: // parse source grammars
513: for (InputSource value : opt.getGrammars())
514: reader.parse(value);
515:
516: return reader.getResult();
517: }
518:
519: /**
520: * Parses a {@link DOMForest} into a {@link XSSchemaSet}.
521: */
522: public XSSchemaSet createXSOM(DOMForest forest,
523: SCDBasedBindingSet scdBasedBindingSet) throws SAXException {
524: // set up other parameters to XSOMParser
525: XSOMParser reader = createXSOMParser(forest);
526:
527: // re-parse the transformed schemas
528: for (String systemId : forest.getRootDocuments()) {
529: errorReceiver.pollAbort();
530: Document dom = forest.get(systemId);
531: if (!dom.getDocumentElement().getNamespaceURI().equals(
532: Const.JAXB_NSURI))
533: reader.parse(systemId);
534: }
535:
536: XSSchemaSet result = reader.getResult();
537:
538: scdBasedBindingSet.apply(result, errorReceiver);
539:
540: return result;
541: }
542:
543: /**
544: * Parses a RELAX NG grammar into an annotated grammar.
545: */
546: private Model loadRELAXNG() throws SAXException {
547:
548: // build DOM forest
549: final DOMForest forest = buildDOMForest(new RELAXNGInternalizationLogic());
550:
551: // use JAXP masquerading to validate the input document.
552: // DOMForest -> ExtensionBindingChecker -> RNGOM
553:
554: XMLReaderCreator xrc = new XMLReaderCreator() {
555: public XMLReader createXMLReader() {
556:
557: // foreset parser cannot change the receivers while it's working,
558: // so we need to have one XMLFilter that works as a buffer
559: XMLFilter buffer = new XMLFilterImpl() {
560: public void parse(InputSource source)
561: throws IOException, SAXException {
562: forest.createParser().parse(source, this , this ,
563: this );
564: }
565: };
566:
567: XMLFilter f = new ExtensionBindingChecker(
568: Const.RELAXNG_URI, opt, errorReceiver);
569: f.setParent(buffer);
570:
571: f.setEntityResolver(opt.entityResolver);
572:
573: return f;
574: }
575: };
576:
577: Parseable p = new SAXParseable(opt.getGrammars()[0],
578: errorReceiver, xrc);
579:
580: return loadRELAXNG(p);
581:
582: }
583:
584: /**
585: * Loads RELAX NG compact syntax
586: */
587: private Model loadRELAXNGCompact() {
588: if (opt.getBindFiles().length > 0)
589: errorReceiver
590: .error(new SAXParseException(
591: Messages
592: .format(Messages.ERR_BINDING_FILE_NOT_SUPPORTED_FOR_RNC),
593: null));
594:
595: // TODO: entity resolver?
596: Parseable p = new CompactParseable(opt.getGrammars()[0],
597: errorReceiver);
598:
599: return loadRELAXNG(p);
600:
601: }
602:
603: /**
604: * Common part between the XML syntax and the compact syntax.
605: */
606: private Model loadRELAXNG(Parseable p) {
607: SchemaBuilder sb = new CheckingSchemaBuilder(
608: new DSchemaBuilderImpl(), errorReceiver);
609:
610: try {
611: DPattern out = (DPattern) p.parse(sb);
612: return RELAXNGCompiler.build(out, codeModel, opt);
613: } catch (IllegalSchemaException e) {
614: errorReceiver.error(e.getMessage(), e);
615: return null;
616: }
617: }
618: }
|