1 /*
   2  * Copyright (c) 1997, 2013, 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.tools.internal.xjc.api.impl.s2j;
  27 
  28 import java.io.IOException;
  29 import java.net.MalformedURLException;
  30 import java.net.URI;
  31 import java.net.URISyntaxException;
  32 import java.net.URL;
  33 
  34 import javax.xml.XMLConstants;
  35 import javax.xml.stream.XMLStreamException;
  36 import javax.xml.stream.XMLStreamReader;
  37 import javax.xml.validation.SchemaFactory;
  38 
  39 import com.sun.codemodel.internal.JCodeModel;
  40 import com.sun.istack.internal.NotNull;
  41 import com.sun.istack.internal.SAXParseException2;
  42 import com.sun.tools.internal.xjc.ErrorReceiver;
  43 import com.sun.tools.internal.xjc.ModelLoader;
  44 import com.sun.tools.internal.xjc.Options;
  45 import com.sun.tools.internal.xjc.api.ClassNameAllocator;
  46 import com.sun.tools.internal.xjc.api.ErrorListener;
  47 import com.sun.tools.internal.xjc.api.SchemaCompiler;
  48 import com.sun.tools.internal.xjc.api.SpecVersion;
  49 import com.sun.tools.internal.xjc.model.Model;
  50 import com.sun.tools.internal.xjc.outline.Outline;
  51 import com.sun.tools.internal.xjc.reader.internalizer.DOMForest;
  52 import com.sun.tools.internal.xjc.reader.internalizer.SCDBasedBindingSet;
  53 import com.sun.tools.internal.xjc.reader.xmlschema.parser.LSInputSAXWrapper;
  54 import com.sun.tools.internal.xjc.reader.xmlschema.parser.XMLSchemaInternalizationLogic;
  55 import com.sun.xml.internal.bind.unmarshaller.DOMScanner;
  56 import com.sun.xml.internal.bind.v2.util.XmlFactory;
  57 import com.sun.xml.internal.xsom.XSSchemaSet;
  58 
  59 import org.w3c.dom.Element;
  60 import org.w3c.dom.ls.LSInput;
  61 import org.w3c.dom.ls.LSResourceResolver;
  62 import org.xml.sax.ContentHandler;
  63 import org.xml.sax.EntityResolver;
  64 import org.xml.sax.InputSource;
  65 import org.xml.sax.SAXException;
  66 import org.xml.sax.SAXParseException;
  67 import org.xml.sax.helpers.LocatorImpl;
  68 
  69 /**
  70  * {@link SchemaCompiler} implementation.
  71  *
  72  * This class builds a {@link DOMForest} until the {@link #bind()} method,
  73  * then this method does the rest of the hard work.
  74  *
  75  * @see ModelLoader
  76  *
  77  * @author
  78  *     Kohsuke Kawaguchi (kohsuke.kawaguchi@sun.com)
  79  */
  80 public final class SchemaCompilerImpl extends ErrorReceiver implements SchemaCompiler {
  81 
  82     /**
  83      * User-specified error receiver.
  84      * This field can be null, in which case errors need to be discarded.
  85      */
  86     private ErrorListener errorListener;
  87 
  88     protected final Options opts = new Options();
  89 
  90     protected @NotNull DOMForest forest;
  91 
  92     /**
  93      * Set to true once an error is found.
  94      */
  95     private boolean hadError;
  96 
  97     public SchemaCompilerImpl() {
  98         opts.compatibilityMode = Options.EXTENSION;
  99         resetSchema();
 100 
 101         if(System.getProperty("xjc-api.test")!=null) {
 102             opts.debugMode = true;
 103             opts.verbose = true;
 104         }
 105     }
 106 
 107     @NotNull
 108     public Options getOptions() {
 109         return opts;
 110     }
 111 
 112     public ContentHandler getParserHandler( String systemId ) {
 113         return forest.getParserHandler(systemId,true);
 114     }
 115 
 116     public void parseSchema( String systemId, Element element ) {
 117         checkAbsoluteness(systemId);
 118         try {
 119             DOMScanner scanner = new DOMScanner();
 120 
 121             // use a locator that sets the system ID correctly
 122             // so that we can resolve relative URLs in most of the case.
 123             // it still doesn't handle xml:base and XInclude and all those things
 124             // correctly. There's just no way to make all those things work with DOM!
 125             LocatorImpl loc = new LocatorImpl();
 126             loc.setSystemId(systemId);
 127             scanner.setLocator(loc);
 128 
 129             scanner.setContentHandler(getParserHandler(systemId));
 130             scanner.scan(element);
 131         } catch (SAXException e) {
 132             // since parsing DOM shouldn't cause a SAX exception
 133             // and our handler will never throw it, it's not clear
 134             // if this will ever happen.
 135             fatalError(new SAXParseException2(
 136                 e.getMessage(), null, systemId,-1,-1, e));
 137         }
 138     }
 139 
 140     public void parseSchema(InputSource source) {
 141         checkAbsoluteness(source.getSystemId());
 142         try {
 143             forest.parse(source,true);
 144         } catch (SAXException e) {
 145             // parsers are required to report an error to ErrorHandler,
 146             // so we should never see this error.
 147             e.printStackTrace();
 148         }
 149     }
 150 
 151     public void setTargetVersion(SpecVersion version) {
 152         if(version==null)
 153             version = SpecVersion.LATEST;
 154         opts.target = version;
 155     }
 156 
 157     public void parseSchema(String systemId, XMLStreamReader reader) throws XMLStreamException {
 158         checkAbsoluteness(systemId);
 159         forest.parse(systemId,reader,true);
 160     }
 161 
 162     /**
 163      * Checks if the system ID is absolute.
 164      */
 165     @SuppressWarnings("ResultOfObjectAllocationIgnored")
 166     private void checkAbsoluteness(String systemId) {
 167         // we need to be able to handle system IDs like "urn:foo", which java.net.URL can't process,
 168         // but OTOH we also need to be able to process system IDs like "file://a b c/def.xsd",
 169         // which java.net.URI can't process. So for now, let's fail only if both of them fail.
 170         // eventually we need a proper URI class that works for us.
 171         try {
 172             new URL(systemId);
 173         } catch( MalformedURLException mue) {
 174             try {
 175                 new URI(systemId);
 176             } catch (URISyntaxException e ) {
 177                 throw new IllegalArgumentException("system ID '"+systemId+"' isn't absolute",e);
 178             }
 179         }
 180     }
 181 
 182     public void setEntityResolver(EntityResolver entityResolver) {
 183         forest.setEntityResolver(entityResolver);
 184         opts.entityResolver = entityResolver;
 185     }
 186 
 187     public void setDefaultPackageName(String packageName) {
 188         opts.defaultPackage2 = packageName;
 189     }
 190 
 191     public void forcePackageName(String packageName) {
 192         opts.defaultPackage = packageName;
 193     }
 194 
 195     public void setClassNameAllocator(ClassNameAllocator allocator) {
 196         opts.classNameAllocator = allocator;
 197     }
 198 
 199     public void resetSchema() {
 200         forest = new DOMForest(new XMLSchemaInternalizationLogic(), opts);
 201         forest.setErrorHandler(this);
 202         forest.setEntityResolver(opts.entityResolver);
 203     }
 204 
 205     public JAXBModelImpl bind() {
 206         // this has been problematic. turn it off.
 207 //        if(!forest.checkSchemaCorrectness(this))
 208 //            return null;
 209 
 210         // parse all the binding files given via XJC -b options.
 211         // this also takes care of the binding files given in the -episode option.
 212         for (InputSource is : opts.getBindFiles())
 213             parseSchema(is);
 214 
 215         // internalization
 216         SCDBasedBindingSet scdBasedBindingSet = forest.transform(opts.isExtensionMode());
 217 
 218         if (!NO_CORRECTNESS_CHECK) {
 219             // correctness check
 220             SchemaFactory sf = XmlFactory.createSchemaFactory(XMLConstants.W3C_XML_SCHEMA_NS_URI, opts.disableXmlSecurity);
 221 
 222             // fix for https://jaxb.dev.java.net/issues/show_bug.cgi?id=795
 223             // taken from SchemaConstraintChecker, TODO XXX FIXME UGLY
 224             if (opts.entityResolver != null) {
 225                 sf.setResourceResolver(new LSResourceResolver() {
 226                     public LSInput resolveResource(String type, String namespaceURI, String publicId, String systemId, String baseURI) {
 227                         try {
 228                             // XSOM passes the namespace URI to the publicID parameter.
 229                             // we do the same here .
 230                             InputSource is = opts.entityResolver.resolveEntity(namespaceURI, systemId);
 231                             if (is == null) return null;
 232                             return new LSInputSAXWrapper(is);
 233                         } catch (SAXException e) {
 234                             // TODO: is this sufficient?
 235                             return null;
 236                         } catch (IOException e) {
 237                             // TODO: is this sufficient?
 238                             return null;
 239                         }
 240                     }
 241                 });
 242             }
 243 
 244             sf.setErrorHandler(new DowngradingErrorHandler(this));
 245             forest.weakSchemaCorrectnessCheck(sf);
 246             if (hadError)
 247                 return null;    // error in the correctness check. abort now
 248         }
 249 
 250         JCodeModel codeModel = new JCodeModel();
 251 
 252         ModelLoader gl = new ModelLoader(opts,codeModel,this);
 253         try {
 254             XSSchemaSet result = gl.createXSOM(forest, scdBasedBindingSet);
 255             if(result==null)
 256                 return null;
 257 
 258             // we need info about each field, so we go ahead and generate the
 259             // skeleton at this point.
 260             // REVISIT: we should separate FieldRenderer and FieldAccessor
 261             // so that accessors can be used before we build the code.
 262             Model model = gl.annotateXMLSchema(result);
 263             if(model==null)   return null;
 264 
 265             if(hadError)        return null;    // if we have any error by now, abort
 266 
 267             model.setPackageLevelAnnotations(opts.packageLevelAnnotations);
 268 
 269             Outline context = model.generateCode(opts,this);
 270             if(context==null)   return null;
 271 
 272             if(hadError)        return null;
 273 
 274             return new JAXBModelImpl(context);
 275         } catch( SAXException e ) {
 276             // since XSOM uses our parser that scans DOM,
 277             // no parser error is possible.
 278             // all the other errors will be directed to ErrorReceiver
 279             // before it's thrown, so when the exception is thrown
 280             // the error should have already been reported.
 281 
 282             // thus ignore.
 283             return null;
 284         }
 285     }
 286 
 287     public void setErrorListener(ErrorListener errorListener) {
 288         this.errorListener = errorListener;
 289     }
 290 
 291     public void info(SAXParseException exception) {
 292         if(errorListener!=null)
 293             errorListener.info(exception);
 294     }
 295     public void warning(SAXParseException exception) {
 296         if(errorListener!=null)
 297             errorListener.warning(exception);
 298     }
 299     public void error(SAXParseException exception) {
 300         hadError = true;
 301         if(errorListener!=null)
 302             errorListener.error(exception);
 303     }
 304     public void fatalError(SAXParseException exception) {
 305         hadError = true;
 306         if(errorListener!=null)
 307             errorListener.fatalError(exception);
 308     }
 309 
 310     /**
 311      * We use JAXP 1.3 to do a schema correctness check, but we know
 312      * it doesn't always work. So in case some people hit the problem,
 313      * this switch is here so that they can turn it off as a workaround.
 314      */
 315     private static boolean NO_CORRECTNESS_CHECK = false;
 316 
 317     static {
 318         try {
 319             NO_CORRECTNESS_CHECK = Boolean.getBoolean(SchemaCompilerImpl.class.getName()+".noCorrectnessCheck");
 320         } catch( Throwable t) {
 321             // ignore
 322         }
 323     }
 324 }