src/share/jaxws_classes/com/sun/tools/internal/xjc/reader/internalizer/Internalizer.java

Thu, 31 Aug 2017 15:18:52 +0800

author
aoqi
date
Thu, 31 Aug 2017 15:18:52 +0800
changeset 637
9c07ef4934dd
parent 515
6cd506508147
parent 0
373ffda63c9a
permissions
-rw-r--r--

merge

     1 /*
     2  * Copyright (c) 1997, 2014, 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  */
    26 package com.sun.tools.internal.xjc.reader.internalizer;
    28 import java.net.MalformedURLException;
    29 import java.net.URL;
    30 import java.util.ArrayList;
    31 import java.util.HashMap;
    32 import java.util.HashSet;
    33 import java.util.List;
    34 import java.util.Map;
    35 import java.util.Set;
    36 import java.text.ParseException;
    38 import javax.xml.xpath.XPath;
    39 import javax.xml.xpath.XPathConstants;
    40 import javax.xml.xpath.XPathExpressionException;
    41 import javax.xml.xpath.XPathFactory;
    43 import com.sun.istack.internal.SAXParseException2;
    44 import com.sun.istack.internal.NotNull;
    45 import com.sun.istack.internal.Nullable;
    46 import com.sun.tools.internal.xjc.ErrorReceiver;
    47 import com.sun.tools.internal.xjc.reader.Const;
    48 import com.sun.tools.internal.xjc.util.DOMUtils;
    49 import com.sun.xml.internal.bind.v2.util.EditDistance;
    50 import com.sun.xml.internal.bind.v2.util.XmlFactory;
    51 import com.sun.xml.internal.xsom.SCD;
    52 import java.io.File;
    53 import java.io.IOException;
    54 import java.util.logging.Level;
    55 import java.util.logging.Logger;
    56 import javax.xml.XMLConstants;
    58 import org.w3c.dom.Attr;
    59 import org.w3c.dom.Document;
    60 import org.w3c.dom.Element;
    61 import org.w3c.dom.NamedNodeMap;
    62 import org.w3c.dom.Node;
    63 import org.w3c.dom.NodeList;
    64 import org.xml.sax.SAXParseException;
    66 /**
    67  * Internalizes external binding declarations.
    68  *
    69  * <p>
    70  * The {@link #transform(DOMForest,boolean)} method is the entry point.
    71  *
    72  * @author
    73  *     Kohsuke Kawaguchi (kohsuke.kawaguchi@sun.com)
    74  */
    75 class Internalizer {
    77     private static final String WSDL_NS = "http://schemas.xmlsoap.org/wsdl/";
    79     private final XPath xpath;
    81     /**
    82      * Internalize all &lt;jaxb:bindings> customizations in the given forest.
    83      *
    84      * @return
    85      *      if the SCD support is enabled, the return bindings need to be applied
    86      *      after schema components are parsed.
    87      *      If disabled, the returned binding set will be empty.
    88      *      SCDs are only for XML Schema, and doesn't make any sense for other
    89      *      schema languages.
    90      */
    91     static SCDBasedBindingSet transform( DOMForest forest, boolean enableSCD, boolean disableSecureProcessing ) {
    92         return new Internalizer(forest, enableSCD, disableSecureProcessing).transform();
    93     }
    96     private Internalizer(DOMForest forest, boolean enableSCD, boolean disableSecureProcessing) {
    97         this.errorHandler = forest.getErrorHandler();
    98         this.forest = forest;
    99         this.enableSCD = enableSCD;
   100         xpath = XmlFactory.createXPathFactory(disableSecureProcessing).newXPath();
   101     }
   103     /**
   104      * DOMForest object currently being processed.
   105      */
   106     private final DOMForest forest;
   108     /**
   109      * All errors found during the transformation is sent to this object.
   110      */
   111     private ErrorReceiver errorHandler;
   113     /**
   114      * If true, the SCD-based target selection is supported.
   115      */
   116     private boolean enableSCD;
   119     private SCDBasedBindingSet transform() {
   121         // either target nodes are conventional DOM nodes (as per spec),
   122         Map<Element,List<Node>> targetNodes = new HashMap<Element,List<Node>>();
   123         // ... or it will be schema components by means of SCD (RI extension)
   124         SCDBasedBindingSet scd = new SCDBasedBindingSet(forest);
   126         //
   127         // identify target nodes for all <jaxb:bindings>
   128         //
   129         for (Element jaxbBindings : forest.outerMostBindings) {
   130             // initially, the inherited context is itself
   131             buildTargetNodeMap(jaxbBindings, jaxbBindings, null, targetNodes, scd);
   132         }
   134         //
   135         // then move them to their respective positions.
   136         //
   137         for (Element jaxbBindings : forest.outerMostBindings) {
   138             move(jaxbBindings, targetNodes);
   139         }
   141         return scd;
   142     }
   144     /**
   145      * Validates attributes of a &lt;jaxb:bindings> element.
   146      */
   147     private void validate( Element bindings ) {
   148         NamedNodeMap atts = bindings.getAttributes();
   149         for( int i=0; i<atts.getLength(); i++ ) {
   150             Attr a = (Attr)atts.item(i);
   151             if( a.getNamespaceURI()!=null )
   152                 continue;   // all foreign namespace OK.
   153             if( a.getLocalName().equals("node") )
   154                 continue;
   155             if( a.getLocalName().equals("schemaLocation"))
   156                 continue;
   157             if( a.getLocalName().equals("scd") )
   158                 continue;
   160             // enhancements
   161             if( a.getLocalName().equals("required") ) //
   162                 continue;
   163             if( a.getLocalName().equals("multiple") ) //
   164                 continue;
   167             // TODO: flag error for this undefined attribute
   168         }
   169     }
   171     /**
   172      * Determines the target node of the "bindings" element
   173      * by using the inherited target node, then put
   174      * the result into the "result" map and the "scd" map.
   175      *
   176      * @param inheritedTarget
   177      *      The current target node. This always exists, even if
   178      *      the user starts specifying targets via SCD (in that case
   179      *      this inherited target is just not going to be used.)
   180      * @param inheritedSCD
   181      *      If the ancestor &lt;bindings> node specifies @scd to
   182      *      specify the target via SCD, then this parameter represents that context.
   183      */
   184     private void buildTargetNodeMap( Element bindings, @NotNull Node inheritedTarget,
   185                                      @Nullable SCDBasedBindingSet.Target inheritedSCD,
   186                                      Map<Element,List<Node>> result, SCDBasedBindingSet scdResult ) {
   187         // start by the inherited target
   188         Node target = inheritedTarget;
   189         ArrayList<Node> targetMultiple = null;
   191         validate(bindings); // validate this node
   193         boolean required = true;
   194         boolean multiple = false;
   196         if(bindings.getAttribute("required") != null) {
   197             String requiredAttr = bindings.getAttribute("required");
   199             if(requiredAttr.equals("no") || requiredAttr.equals("false") || requiredAttr.equals("0"))
   200                 required = false;
   201         }
   203         if(bindings.getAttribute("multiple") != null) {
   204             String requiredAttr = bindings.getAttribute("multiple");
   206             if(requiredAttr.equals("yes") || requiredAttr.equals("true") || requiredAttr.equals("1"))
   207                 multiple = true;
   208         }
   211         // look for @schemaLocation
   212         if( bindings.getAttributeNode("schemaLocation")!=null ) {
   213             String schemaLocation = bindings.getAttribute("schemaLocation");
   215             // enhancement - schemaLocation="*" = bind to all schemas..
   216             if(schemaLocation.equals("*")) {
   217                 for(String systemId : forest.listSystemIDs()) {
   218                     if (result.get(bindings) == null)
   219                         result.put(bindings, new ArrayList<Node>());
   220                     result.get(bindings).add(forest.get(systemId).getDocumentElement());
   222                     Element[] children = DOMUtils.getChildElements(bindings, Const.JAXB_NSURI, "bindings");
   223                     for (Element value : children)
   224                         buildTargetNodeMap(value, forest.get(systemId).getDocumentElement(), inheritedSCD, result, scdResult);
   225                 }
   226                 return;
   227             } else {
   228                 try {
   229                     // TODO: use the URI class
   230                     // TODO: honor xml:base
   231                     URL loc = new URL(
   232                                 new URL(forest.getSystemId(bindings.getOwnerDocument())), schemaLocation
   233                               );
   234                     schemaLocation = loc.toExternalForm();
   235                     target = forest.get(schemaLocation);
   236                     if ((target == null) && (loc.getProtocol().startsWith("file"))) {
   237                         File f = new File(loc.getFile());
   238                         schemaLocation = new File(f.getCanonicalPath()).toURI().toString();
   239                     }
   240                 } catch( MalformedURLException e ) {
   241                 } catch( IOException e ) {
   242                     Logger.getLogger(Internalizer.class.getName()).log(Level.FINEST, e.getLocalizedMessage());
   243                 }
   245                 target = forest.get(schemaLocation);
   246                 if(target==null) {
   247                     reportError( bindings,
   248                         Messages.format(Messages.ERR_INCORRECT_SCHEMA_REFERENCE,
   249                             schemaLocation,
   250                             EditDistance.findNearest(schemaLocation,forest.listSystemIDs())));
   252                     return; // abort processing this <jaxb:bindings>
   253                 }
   255                 target = ((Document)target).getDocumentElement();
   256             }
   257         }
   259         // look for @node
   260         if( bindings.getAttributeNode("node")!=null ) {
   261             String nodeXPath = bindings.getAttribute("node");
   263             // evaluate this XPath
   264             NodeList nlst;
   265             try {
   266                 xpath.setNamespaceContext(new NamespaceContextImpl(bindings));
   267                 nlst = (NodeList)xpath.evaluate(nodeXPath,target,XPathConstants.NODESET);
   268             } catch (XPathExpressionException e) {
   269                 if(required) {
   270                     reportError( bindings,
   271                         Messages.format(Messages.ERR_XPATH_EVAL,e.getMessage()), e );
   272                     return; // abort processing this <jaxb:bindings>
   273                 } else {
   274                     return;
   275                 }
   276             }
   278             if( nlst.getLength()==0 ) {
   279                 if(required)
   280                     reportError( bindings,
   281                         Messages.format(Messages.NO_XPATH_EVAL_TO_NO_TARGET, nodeXPath) );
   282                 return; // abort
   283             }
   285             if( nlst.getLength()!=1 ) {
   286                 if(!multiple) {
   287                     reportError( bindings,
   288                         Messages.format(Messages.NO_XPATH_EVAL_TOO_MANY_TARGETS, nodeXPath,nlst.getLength()) );
   290                     return; // abort
   291                 } else {
   292                     if(targetMultiple == null) targetMultiple = new ArrayList<Node>();
   293                     for(int i = 0; i < nlst.getLength(); i++) {
   294                         targetMultiple.add(nlst.item(i));
   295                     }
   296                 }
   297             }
   299             // check
   300             if(!multiple || nlst.getLength() == 1) {
   301                 Node rnode = nlst.item(0);
   302                 if (!(rnode instanceof Element)) {
   303                     reportError(bindings,
   304                             Messages.format(Messages.NO_XPATH_EVAL_TO_NON_ELEMENT, nodeXPath));
   305                     return; // abort
   306                 }
   308                 if (!forest.logic.checkIfValidTargetNode(forest, bindings, (Element) rnode)) {
   309                     reportError(bindings,
   310                             Messages.format(Messages.XPATH_EVAL_TO_NON_SCHEMA_ELEMENT,
   311                             nodeXPath, rnode.getNodeName()));
   312                     return; // abort
   313                 }
   315                 target = rnode;
   316             } else {
   317                 for(Node rnode : targetMultiple) {
   318                     if (!(rnode instanceof Element)) {
   319                         reportError(bindings,
   320                                 Messages.format(Messages.NO_XPATH_EVAL_TO_NON_ELEMENT, nodeXPath));
   321                         return; // abort
   322                     }
   324                     if (!forest.logic.checkIfValidTargetNode(forest, bindings, (Element) rnode)) {
   325                         reportError(bindings,
   326                                 Messages.format(Messages.XPATH_EVAL_TO_NON_SCHEMA_ELEMENT,
   327                                 nodeXPath, rnode.getNodeName()));
   328                         return; // abort
   329                     }
   330                 }
   331             }
   332         }
   334         // look for @scd
   335         if( bindings.getAttributeNode("scd")!=null ) {
   336             String scdPath = bindings.getAttribute("scd");
   337             if(!enableSCD) {
   338                 // SCD selector was found, but it's not activated. report an error
   339                 // but recover by handling it anyway. this also avoids repeated error messages.
   340                 reportError(bindings,
   341                     Messages.format(Messages.SCD_NOT_ENABLED));
   342                 enableSCD = true;
   343             }
   345             try {
   346                 inheritedSCD = scdResult.createNewTarget( inheritedSCD, bindings,
   347                         SCD.create(scdPath, new NamespaceContextImpl(bindings)) );
   348             } catch (ParseException e) {
   349                 reportError( bindings, Messages.format(Messages.ERR_SCD_EVAL,e.getMessage()),e );
   350                 return; // abort processing this bindings
   351             }
   352         }
   354         // update the result map
   355         if (inheritedSCD != null) {
   356             inheritedSCD.addBinidng(bindings);
   357         } else if (!multiple || targetMultiple == null) {
   358             if (result.get(bindings) == null)
   359                 result.put(bindings, new ArrayList<Node>());
   360             result.get(bindings).add(target);
   361         } else {
   362             for (Node rnode : targetMultiple) {
   363                 if (result.get(bindings) == null)
   364                     result.put(bindings, new ArrayList<Node>());
   366                 result.get(bindings).add(rnode);
   367             }
   369         }
   372         // look for child <jaxb:bindings> and process them recursively
   373         Element[] children = DOMUtils.getChildElements( bindings, Const.JAXB_NSURI, "bindings" );
   374         for (Element value : children)
   375             if(!multiple || targetMultiple == null)
   376                 buildTargetNodeMap(value, target, inheritedSCD, result, scdResult);
   377             else {
   378                 for(Node rnode : targetMultiple) {
   379                     buildTargetNodeMap(value, rnode, inheritedSCD, result, scdResult);
   380                 }
   381             }
   382     }
   384     /**
   385      * Moves JAXB customizations under their respective target nodes.
   386      */
   387     private void move(Element bindings, Map<Element, List<Node>> targetNodes) {
   388         List<Node> nodelist = targetNodes.get(bindings);
   390         if(nodelist == null) {
   391                 return; // abort
   392         }
   394         for (Node target : nodelist) {
   395             if (target == null) // this must be the result of an error on the external binding.
   396             // recover from the error by ignoring this node
   397             {
   398                 return;
   399             }
   401             for (Element item : DOMUtils.getChildElements(bindings)) {
   402                 String localName = item.getLocalName();
   404                 if ("bindings".equals(localName)) {
   405                     // process child <jaxb:bindings> recursively
   406                     move(item, targetNodes);
   407                 } else if ("globalBindings".equals(localName)) {
   408                         // <jaxb:globalBindings> always go to the root of document.
   409                     Element root = forest.getOneDocument().getDocumentElement();
   410                     if (root.getNamespaceURI().equals(WSDL_NS)) {
   411                         NodeList elements = root.getElementsByTagNameNS(XMLConstants.W3C_XML_SCHEMA_NS_URI, "schema");
   412                         if ((elements == null) || (elements.getLength() < 1)) {
   413                             reportError(item, Messages.format(Messages.ORPHANED_CUSTOMIZATION, item.getNodeName()));
   414                             return;
   415                         } else {
   416                             moveUnder(item, (Element)elements.item(0));
   417                         }
   418                     } else {
   419                         moveUnder(item, root);
   420                     }
   421                 } else {
   422                     if (!(target instanceof Element)) {
   423                         reportError(item,
   424                                 Messages.format(Messages.CONTEXT_NODE_IS_NOT_ELEMENT));
   425                         return; // abort
   426                     }
   428                     if (!forest.logic.checkIfValidTargetNode(forest, item, (Element) target)) {
   429                         reportError(item,
   430                                 Messages.format(Messages.ORPHANED_CUSTOMIZATION, item.getNodeName()));
   431                         return; // abort
   432                     }
   434                     // move this node under the target
   435                     moveUnder(item, (Element) target);
   436                 }
   437             }
   438         }
   439     }
   441     /**
   442      * Moves the "decl" node under the "target" node.
   443      *
   444      * @param decl
   445      *      A JAXB customization element (e.g., &lt;jaxb:class>)
   446      *
   447      * @param target
   448      *      XML Schema element under which the declaration should move.
   449      *      For example, &lt;xs:element>
   450      */
   451     private void moveUnder( Element decl, Element target ) {
   452         Element realTarget = forest.logic.refineTarget(target);
   454         declExtensionNamespace( decl, target );
   456         // copy in-scope namespace declarations of the decl node
   457         // to the decl node itself so that this move won't change
   458         // the in-scope namespace bindings.
   459         Element p = decl;
   460         Set<String> inscopes = new HashSet<String>();
   461         while(true) {
   462             NamedNodeMap atts = p.getAttributes();
   463             for( int i=0; i<atts.getLength(); i++ ) {
   464                 Attr a = (Attr)atts.item(i);
   465                 if( Const.XMLNS_URI.equals(a.getNamespaceURI()) ) {
   466                     String prefix;
   467                     if( a.getName().indexOf(':')==-1 )  prefix = "";
   468                     else                                prefix = a.getLocalName();
   470                     if( inscopes.add(prefix) && p!=decl ) {
   471                         // if this is the first time we see this namespace bindings,
   472                         // copy the declaration.
   473                         // if p==decl, there's no need to. Note that
   474                         // we want to add prefix to inscopes even if p==Decl
   476                         decl.setAttributeNodeNS( (Attr)a.cloneNode(true) );
   477                     }
   478                 }
   479             }
   481             if( p.getParentNode() instanceof Document )
   482                 break;
   484             p = (Element)p.getParentNode();
   485         }
   487         if( !inscopes.contains("") ) {
   488             // if the default namespace was undeclared in the context of decl,
   489             // it must be explicitly set to "" since the new environment might
   490             // have a different default namespace URI.
   491             decl.setAttributeNS(Const.XMLNS_URI,"xmlns","");
   492         }
   495         // finally move the declaration to the target node.
   496         if( realTarget.getOwnerDocument()!=decl.getOwnerDocument() ) {
   497             // if they belong to different DOM documents, we need to clone them
   498             Element original = decl;
   499             decl = (Element)realTarget.getOwnerDocument().importNode(decl,true);
   501             // this effectively clones a ndoe,, so we need to copy locators.
   502             copyLocators( original, decl );
   503         }
   505         realTarget.appendChild( decl );
   506     }
   508     /**
   509      * Recursively visits sub-elements and declare all used namespaces.
   510      * TODO: the fact that we recognize all namespaces in the extension
   511      * is a bad design.
   512      */
   513     private void declExtensionNamespace(Element decl, Element target) {
   514         // if this comes from external namespaces, add the namespace to
   515         // @extensionBindingPrefixes.
   516         if( !Const.JAXB_NSURI.equals(decl.getNamespaceURI()) )
   517             declareExtensionNamespace( target, decl.getNamespaceURI() );
   519         NodeList lst = decl.getChildNodes();
   520         for( int i=0; i<lst.getLength(); i++ ) {
   521             Node n = lst.item(i);
   522             if( n instanceof Element )
   523                 declExtensionNamespace( (Element)n, target );
   524         }
   525     }
   528     /** Attribute name. */
   529     private static final String EXTENSION_PREFIXES = "extensionBindingPrefixes";
   531     /**
   532      * Adds the specified namespace URI to the jaxb:extensionBindingPrefixes
   533      * attribute of the target document.
   534      */
   535     private void declareExtensionNamespace( Element target, String nsUri ) {
   536         // look for the attribute
   537         Element root = target.getOwnerDocument().getDocumentElement();
   538         Attr att = root.getAttributeNodeNS(Const.JAXB_NSURI,EXTENSION_PREFIXES);
   539         if( att==null ) {
   540             String jaxbPrefix = allocatePrefix(root,Const.JAXB_NSURI);
   541             // no such attribute. Create one.
   542             att = target.getOwnerDocument().createAttributeNS(
   543                 Const.JAXB_NSURI,jaxbPrefix+':'+EXTENSION_PREFIXES);
   544             root.setAttributeNodeNS(att);
   545         }
   547         String prefix = allocatePrefix(root,nsUri);
   548         if( att.getValue().indexOf(prefix)==-1 )
   549             // avoid redeclaring the same namespace twice.
   550             att.setValue( att.getValue()+' '+prefix);
   551     }
   553     /**
   554      * Declares a new prefix on the given element and associates it
   555      * with the specified namespace URI.
   556      * <p>
   557      * Note that this method doesn't use the default namespace
   558      * even if it can.
   559      */
   560     private String allocatePrefix( Element e, String nsUri ) {
   561         // look for existing namespaces.
   562         NamedNodeMap atts = e.getAttributes();
   563         for( int i=0; i<atts.getLength(); i++ ) {
   564             Attr a = (Attr)atts.item(i);
   565             if( Const.XMLNS_URI.equals(a.getNamespaceURI()) ) {
   566                 if( a.getName().indexOf(':')==-1 )  continue;
   568                 if( a.getValue().equals(nsUri) )
   569                     return a.getLocalName();    // found one
   570             }
   571         }
   573         // none found. allocate new.
   574         while(true) {
   575             String prefix = "p"+(int)(Math.random()*1000000)+'_';
   576             if(e.getAttributeNodeNS(Const.XMLNS_URI,prefix)!=null)
   577                 continue;   // this prefix is already allocated.
   579             e.setAttributeNS(Const.XMLNS_URI,"xmlns:"+prefix,nsUri);
   580             return prefix;
   581         }
   582     }
   585     /**
   586      * Copies location information attached to the "src" node to the "dst" node.
   587      */
   588     private void copyLocators( Element src, Element dst ) {
   589         forest.locatorTable.storeStartLocation(
   590             dst, forest.locatorTable.getStartLocation(src) );
   591         forest.locatorTable.storeEndLocation(
   592             dst, forest.locatorTable.getEndLocation(src) );
   594         // recursively process child elements
   595         Element[] srcChilds = DOMUtils.getChildElements(src);
   596         Element[] dstChilds = DOMUtils.getChildElements(dst);
   598         for( int i=0; i<srcChilds.length; i++ )
   599             copyLocators( srcChilds[i], dstChilds[i] );
   600     }
   603     private void reportError( Element errorSource, String formattedMsg ) {
   604         reportError( errorSource, formattedMsg, null );
   605     }
   607     private void reportError( Element errorSource,
   608         String formattedMsg, Exception nestedException ) {
   610         SAXParseException e = new SAXParseException2( formattedMsg,
   611             forest.locatorTable.getStartLocation(errorSource),
   612             nestedException );
   613         errorHandler.error(e);
   614     }
   615 }

mercurial