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.reader.internalizer;
037:
038: import java.net.MalformedURLException;
039: import java.net.URL;
040: import java.util.HashMap;
041: import java.util.HashSet;
042: import java.util.Map;
043: import java.util.Set;
044: import java.text.ParseException;
045:
046: import javax.xml.xpath.XPath;
047: import javax.xml.xpath.XPathConstants;
048: import javax.xml.xpath.XPathExpressionException;
049: import javax.xml.xpath.XPathFactory;
050:
051: import com.sun.istack.SAXParseException2;
052: import com.sun.istack.NotNull;
053: import com.sun.istack.Nullable;
054: import com.sun.tools.xjc.ErrorReceiver;
055: import com.sun.tools.xjc.reader.Const;
056: import com.sun.tools.xjc.util.DOMUtils;
057: import com.sun.xml.bind.v2.util.EditDistance;
058: import com.sun.xml.xsom.SCD;
059:
060: import org.w3c.dom.Attr;
061: import org.w3c.dom.Document;
062: import org.w3c.dom.Element;
063: import org.w3c.dom.NamedNodeMap;
064: import org.w3c.dom.Node;
065: import org.w3c.dom.NodeList;
066: import org.xml.sax.SAXParseException;
067:
068: /**
069: * Internalizes external binding declarations.
070: *
071: * <p>
072: * The {@link #transform(DOMForest,boolean)} method is the entry point.
073: *
074: * @author
075: * Kohsuke Kawaguchi (kohsuke.kawaguchi@sun.com)
076: */
077: class Internalizer {
078:
079: private static final XPathFactory xpf = XPathFactory.newInstance();
080:
081: private final XPath xpath = xpf.newXPath();
082:
083: /**
084: * Internalize all <jaxb:bindings> customizations in the given forest.
085: *
086: * @return
087: * if the SCD support is enabled, the return bindings need to be applied
088: * after schema components are parsed.
089: * If disabled, the returned binding set will be empty.
090: * SCDs are only for XML Schema, and doesn't make any sense for other
091: * schema languages.
092: */
093: static SCDBasedBindingSet transform(DOMForest forest,
094: boolean enableSCD) {
095: return new Internalizer(forest, enableSCD).transform();
096: }
097:
098: private Internalizer(DOMForest forest, boolean enableSCD) {
099: this .errorHandler = forest.getErrorHandler();
100: this .forest = forest;
101: this .enableSCD = enableSCD;
102: }
103:
104: /**
105: * DOMForest object currently being processed.
106: */
107: private final DOMForest forest;
108:
109: /**
110: * All errors found during the transformation is sent to this object.
111: */
112: private ErrorReceiver errorHandler;
113:
114: /**
115: * If true, the SCD-based target selection is supported.
116: */
117: private boolean enableSCD;
118:
119: private SCDBasedBindingSet transform() {
120:
121: // either target nodes are conventional DOM nodes (as per spec),
122: Map<Element, Node> targetNodes = new HashMap<Element, Node>();
123: // ... or it will be schema components by means of SCD (RI extension)
124: SCDBasedBindingSet scd = new SCDBasedBindingSet(forest);
125:
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,
132: targetNodes, scd);
133: }
134:
135: //
136: // then move them to their respective positions.
137: //
138: for (Element jaxbBindings : forest.outerMostBindings) {
139: move(jaxbBindings, targetNodes);
140: }
141:
142: return scd;
143: }
144:
145: /**
146: * Validates attributes of a <jaxb:bindings> element.
147: */
148: private void validate(Element bindings) {
149: NamedNodeMap atts = bindings.getAttributes();
150: for (int i = 0; i < atts.getLength(); i++) {
151: Attr a = (Attr) atts.item(i);
152: if (a.getNamespaceURI() != null)
153: continue; // all foreign namespace OK.
154: if (a.getLocalName().equals("node"))
155: continue;
156: if (a.getLocalName().equals("schemaLocation"))
157: continue;
158: if (a.getLocalName().equals("scd"))
159: continue;
160:
161: // TODO: flag error for this undefined attribute
162: }
163: }
164:
165: /**
166: * Determines the target node of the "bindings" element
167: * by using the inherited target node, then put
168: * the result into the "result" map and the "scd" map.
169: *
170: * @param inheritedTarget
171: * The current target node. This always exists, even if
172: * the user starts specifying targets via SCD (in that case
173: * this inherited target is just not going to be used.)
174: * @param inheritedSCD
175: * If the ancestor <bindings> node specifies @scd to
176: * specify the target via SCD, then this parameter represents that context.
177: */
178: private void buildTargetNodeMap(Element bindings, @NotNull
179: Node inheritedTarget, @Nullable
180: SCDBasedBindingSet.Target inheritedSCD, Map<Element, Node> result,
181: SCDBasedBindingSet scdResult) {
182: // start by the inherited target
183: Node target = inheritedTarget;
184:
185: validate(bindings); // validate this node
186:
187: // look for @schemaLocation
188: if (bindings.getAttributeNode("schemaLocation") != null) {
189: String schemaLocation = bindings
190: .getAttribute("schemaLocation");
191:
192: try {
193: // absolutize this URI.
194: // TODO: use the URI class
195: // TODO: honor xml:base
196: schemaLocation = new URL(new URL(forest
197: .getSystemId(bindings.getOwnerDocument())),
198: schemaLocation).toExternalForm();
199: } catch (MalformedURLException e) {
200: // continue with the original schemaLocation value
201: }
202:
203: target = forest.get(schemaLocation);
204: if (target == null) {
205: reportError(
206: bindings,
207: Messages
208: .format(
209: Messages.ERR_INCORRECT_SCHEMA_REFERENCE,
210: schemaLocation,
211: EditDistance.findNearest(
212: schemaLocation,
213: forest.listSystemIDs())));
214:
215: return; // abort processing this <jaxb:bindings>
216: }
217:
218: target = ((Document) target).getDocumentElement();
219: }
220:
221: // look for @node
222: if (bindings.getAttributeNode("node") != null) {
223: String nodeXPath = bindings.getAttribute("node");
224:
225: // evaluate this XPath
226: NodeList nlst;
227: try {
228: xpath.setNamespaceContext(new NamespaceContextImpl(
229: bindings));
230: nlst = (NodeList) xpath.evaluate(nodeXPath, target,
231: XPathConstants.NODESET);
232: } catch (XPathExpressionException e) {
233: reportError(bindings, Messages.format(
234: Messages.ERR_XPATH_EVAL, e.getMessage()), e);
235: return; // abort processing this <jaxb:bindings>
236: }
237:
238: if (nlst.getLength() == 0) {
239: reportError(bindings, Messages.format(
240: Messages.NO_XPATH_EVAL_TO_NO_TARGET, nodeXPath));
241: return; // abort
242: }
243:
244: if (nlst.getLength() != 1) {
245: reportError(bindings, Messages.format(
246: Messages.NO_XPATH_EVAL_TOO_MANY_TARGETS,
247: nodeXPath, nlst.getLength()));
248: return; // abort
249: }
250:
251: Node rnode = nlst.item(0);
252: if (!(rnode instanceof Element)) {
253: reportError(bindings, Messages.format(
254: Messages.NO_XPATH_EVAL_TO_NON_ELEMENT,
255: nodeXPath));
256: return; // abort
257: }
258:
259: if (!forest.logic.checkIfValidTargetNode(forest, bindings,
260: (Element) rnode)) {
261: reportError(bindings, Messages.format(
262: Messages.XPATH_EVAL_TO_NON_SCHEMA_ELEMENT,
263: nodeXPath, rnode.getNodeName()));
264: return; // abort
265: }
266:
267: target = rnode;
268: }
269:
270: // look for @scd
271: if (bindings.getAttributeNode("scd") != null) {
272: String scdPath = bindings.getAttribute("scd");
273: if (!enableSCD) {
274: // SCD selector was found, but it's not activated. report an error
275: // but recover by handling it anyway. this also avoids repeated error messages.
276: reportError(bindings, Messages
277: .format(Messages.SCD_NOT_ENABLED));
278: enableSCD = true;
279: }
280:
281: try {
282: inheritedSCD = scdResult.createNewTarget(inheritedSCD,
283: bindings, SCD.create(scdPath,
284: new NamespaceContextImpl(bindings)));
285: } catch (ParseException e) {
286: reportError(bindings, Messages.format(
287: Messages.ERR_SCD_EVAL, e.getMessage()), e);
288: return; // abort processing this bindings
289: }
290: }
291:
292: // update the result map
293: if (inheritedSCD != null)
294: inheritedSCD.addBinidng(bindings);
295: else
296: result.put(bindings, target);
297:
298: // look for child <jaxb:bindings> and process them recursively
299: Element[] children = DOMUtils.getChildElements(bindings,
300: Const.JAXB_NSURI, "bindings");
301: for (Element value : children)
302: buildTargetNodeMap(value, target, inheritedSCD, result,
303: scdResult);
304: }
305:
306: /**
307: * Moves JAXB customizations under their respective target nodes.
308: */
309: private void move(Element bindings, Map<Element, Node> targetNodes) {
310: Node target = targetNodes.get(bindings);
311: if (target == null)
312: // this must be the result of an error on the external binding.
313: // recover from the error by ignoring this node
314: return;
315:
316: for (Element item : DOMUtils.getChildElements(bindings)) {
317: String localName = item.getLocalName();
318:
319: if ("bindings".equals(localName)) {
320: // process child <jaxb:bindings> recursively
321: move(item, targetNodes);
322: } else if ("globalBindings".equals(localName)) {
323: // <jaxb:globalBindings> always go to the root of document.
324: moveUnder(item, forest.getOneDocument()
325: .getDocumentElement());
326: } else {
327: if (!(target instanceof Element)) {
328: reportError(
329: item,
330: Messages
331: .format(Messages.CONTEXT_NODE_IS_NOT_ELEMENT));
332: return; // abort
333: }
334:
335: if (!forest.logic.checkIfValidTargetNode(forest, item,
336: (Element) target)) {
337: reportError(item, Messages.format(
338: Messages.ORPHANED_CUSTOMIZATION, item
339: .getNodeName()));
340: return; // abort
341: }
342:
343: // move this node under the target
344: moveUnder(item, (Element) target);
345: }
346: }
347: }
348:
349: /**
350: * Moves the "decl" node under the "target" node.
351: *
352: * @param decl
353: * A JAXB customization element (e.g., <jaxb:class>)
354: *
355: * @param target
356: * XML Schema element under which the declaration should move.
357: * For example, <xs:element>
358: */
359: private void moveUnder(Element decl, Element target) {
360: Element realTarget = forest.logic.refineTarget(target);
361:
362: declExtensionNamespace(decl, target);
363:
364: // copy in-scope namespace declarations of the decl node
365: // to the decl node itself so that this move won't change
366: // the in-scope namespace bindings.
367: Element p = decl;
368: Set<String> inscopes = new HashSet<String>();
369: while (true) {
370: NamedNodeMap atts = p.getAttributes();
371: for (int i = 0; i < atts.getLength(); i++) {
372: Attr a = (Attr) atts.item(i);
373: if (Const.XMLNS_URI.equals(a.getNamespaceURI())) {
374: String prefix;
375: if (a.getName().indexOf(':') == -1)
376: prefix = "";
377: else
378: prefix = a.getLocalName();
379:
380: if (inscopes.add(prefix) && p != decl) {
381: // if this is the first time we see this namespace bindings,
382: // copy the declaration.
383: // if p==decl, there's no need to. Note that
384: // we want to add prefix to inscopes even if p==Decl
385:
386: decl.setAttributeNodeNS((Attr) a
387: .cloneNode(true));
388: }
389: }
390: }
391:
392: if (p.getParentNode() instanceof Document)
393: break;
394:
395: p = (Element) p.getParentNode();
396: }
397:
398: if (!inscopes.contains("")) {
399: // if the default namespace was undeclared in the context of decl,
400: // it must be explicitly set to "" since the new environment might
401: // have a different default namespace URI.
402: decl.setAttributeNS(Const.XMLNS_URI, "xmlns", "");
403: }
404:
405: // finally move the declaration to the target node.
406: if (realTarget.getOwnerDocument() != decl.getOwnerDocument()) {
407: // if they belong to different DOM documents, we need to clone them
408: Element original = decl;
409: decl = (Element) realTarget.getOwnerDocument().importNode(
410: decl, true);
411:
412: // this effectively clones a ndoe,, so we need to copy locators.
413: copyLocators(original, decl);
414: }
415:
416: realTarget.appendChild(decl);
417: }
418:
419: /**
420: * Recursively visits sub-elements and declare all used namespaces.
421: * TODO: the fact that we recognize all namespaces in the extension
422: * is a bad design.
423: */
424: private void declExtensionNamespace(Element decl, Element target) {
425: // if this comes from external namespaces, add the namespace to
426: // @extensionBindingPrefixes.
427: if (!Const.JAXB_NSURI.equals(decl.getNamespaceURI()))
428: declareExtensionNamespace(target, decl.getNamespaceURI());
429:
430: NodeList lst = decl.getChildNodes();
431: for (int i = 0; i < lst.getLength(); i++) {
432: Node n = lst.item(i);
433: if (n instanceof Element)
434: declExtensionNamespace((Element) n, target);
435: }
436: }
437:
438: /** Attribute name. */
439: private static final String EXTENSION_PREFIXES = "extensionBindingPrefixes";
440:
441: /**
442: * Adds the specified namespace URI to the jaxb:extensionBindingPrefixes
443: * attribute of the target document.
444: */
445: private void declareExtensionNamespace(Element target, String nsUri) {
446: // look for the attribute
447: Element root = target.getOwnerDocument().getDocumentElement();
448: Attr att = root.getAttributeNodeNS(Const.JAXB_NSURI,
449: EXTENSION_PREFIXES);
450: if (att == null) {
451: String jaxbPrefix = allocatePrefix(root, Const.JAXB_NSURI);
452: // no such attribute. Create one.
453: att = target.getOwnerDocument().createAttributeNS(
454: Const.JAXB_NSURI,
455: jaxbPrefix + ':' + EXTENSION_PREFIXES);
456: root.setAttributeNodeNS(att);
457: }
458:
459: String prefix = allocatePrefix(root, nsUri);
460: if (att.getValue().indexOf(prefix) == -1)
461: // avoid redeclaring the same namespace twice.
462: att.setValue(att.getValue() + ' ' + prefix);
463: }
464:
465: /**
466: * Declares a new prefix on the given element and associates it
467: * with the specified namespace URI.
468: * <p>
469: * Note that this method doesn't use the default namespace
470: * even if it can.
471: */
472: private String allocatePrefix(Element e, String nsUri) {
473: // look for existing namespaces.
474: NamedNodeMap atts = e.getAttributes();
475: for (int i = 0; i < atts.getLength(); i++) {
476: Attr a = (Attr) atts.item(i);
477: if (Const.XMLNS_URI.equals(a.getNamespaceURI())) {
478: if (a.getName().indexOf(':') == -1)
479: continue;
480:
481: if (a.getValue().equals(nsUri))
482: return a.getLocalName(); // found one
483: }
484: }
485:
486: // none found. allocate new.
487: while (true) {
488: String prefix = "p" + (int) (Math.random() * 1000000) + '_';
489: if (e.getAttributeNodeNS(Const.XMLNS_URI, prefix) != null)
490: continue; // this prefix is already allocated.
491:
492: e.setAttributeNS(Const.XMLNS_URI, "xmlns:" + prefix, nsUri);
493: return prefix;
494: }
495: }
496:
497: /**
498: * Copies location information attached to the "src" node to the "dst" node.
499: */
500: private void copyLocators(Element src, Element dst) {
501: forest.locatorTable.storeStartLocation(dst, forest.locatorTable
502: .getStartLocation(src));
503: forest.locatorTable.storeEndLocation(dst, forest.locatorTable
504: .getEndLocation(src));
505:
506: // recursively process child elements
507: Element[] srcChilds = DOMUtils.getChildElements(src);
508: Element[] dstChilds = DOMUtils.getChildElements(dst);
509:
510: for (int i = 0; i < srcChilds.length; i++)
511: copyLocators(srcChilds[i], dstChilds[i]);
512: }
513:
514: private void reportError(Element errorSource, String formattedMsg) {
515: reportError(errorSource, formattedMsg, null);
516: }
517:
518: private void reportError(Element errorSource, String formattedMsg,
519: Exception nestedException) {
520:
521: SAXParseException e = new SAXParseException2(formattedMsg,
522: forest.locatorTable.getStartLocation(errorSource),
523: nestedException);
524: errorHandler.error(e);
525: }
526: }
|