All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.juneau.xml.XmlSchemaSerializerSession Maven / Gradle / Ivy

There is a newer version: 9.0.1
Show newest version
// ***************************************************************************************************************************
// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.  See the NOTICE file *
// * distributed with this work for additional information regarding copyright ownership.  The ASF licenses this file        *
// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance            *
// * with the License.  You may obtain a copy of the License at                                                              *
// *                                                                                                                         *
// *  http://www.apache.org/licenses/LICENSE-2.0                                                                             *
// *                                                                                                                         *
// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an  *
// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the        *
// * specific language governing permissions and limitations under the License.                                              *
// ***************************************************************************************************************************
package org.apache.juneau.xml;

import static org.apache.juneau.internal.ArrayUtils.*;
import static org.apache.juneau.xml.annotation.XmlFormat.*;

import java.io.*;
import java.util.*;
import java.util.regex.*;

import javax.xml.*;
import javax.xml.transform.stream.*;
import javax.xml.validation.*;

import org.apache.juneau.*;
import org.apache.juneau.serializer.*;
import org.apache.juneau.xml.annotation.*;
import org.w3c.dom.bootstrap.*;
import org.w3c.dom.ls.*;

/**
 * Session object that lives for the duration of a single use of {@link XmlSchemaSerializer}.
 *
 * 

* This class is NOT thread safe. * It is typically discarded after one-time use although it can be reused within the same thread. */ public class XmlSchemaSerializerSession extends XmlSerializerSession { /** * Create a new session using properties specified in the context. * * @param ctx * The context creating this session object. * The context contains all the configuration settings for this object. * @param args * Runtime arguments. * These specify session-level information such as locale and URI context. * It also include session-level properties that override the properties defined on the bean and * serializer contexts. */ protected XmlSchemaSerializerSession(XmlSerializerContext ctx, SerializerSessionArgs args) { super(ctx, args); } @Override /* SerializerSession */ protected void doSerialize(SerializerPipe out, Object o) throws Exception { if (enableNamespaces && autoDetectNamespaces) findNsfMappings(o); Namespace xs = xsNamespace; Namespace[] allNs = append(new Namespace[]{defaultNamespace}, namespaces); Schemas schemas = new Schemas(this, xs, defaultNamespace, allNs); schemas.process(o); schemas.serializeTo(out.getWriter()); } /** * Returns an XML-Schema validator based on the output returned by {@link #doSerialize(SerializerPipe, Object)}; * * @param out The target writer. * @param o The object to serialize. * @return The new validator. * @throws Exception If a problem was detected in the XML-Schema output produced by this serializer. */ public Validator getValidator(SerializerPipe out, Object o) throws Exception { doSerialize(out, o); String xmlSchema = out.getWriter().toString(); // create a SchemaFactory capable of understanding WXS schemas SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); if (xmlSchema.indexOf('\u0000') != -1) { // Break it up into a map of namespaceURI->schema document final Map schemas = new HashMap(); String[] ss = xmlSchema.split("\u0000"); xmlSchema = ss[0]; for (String s : ss) { Matcher m = pTargetNs.matcher(s); if (m.find()) schemas.put(m.group(1), s); } // Create a custom resolver factory.setResourceResolver( new LSResourceResolver() { @Override /* LSResourceResolver */ public LSInput resolveResource(String type, String namespaceURI, String publicId, String systemId, String baseURI) { String schema = schemas.get(namespaceURI); if (schema == null) throw new FormattedRuntimeException("No schema found for namespaceURI ''{0}''", namespaceURI); try { DOMImplementationRegistry registry = DOMImplementationRegistry.newInstance(); DOMImplementationLS domImplementationLS = (DOMImplementationLS)registry.getDOMImplementation("LS 3.0"); LSInput in = domImplementationLS.createLSInput(); in.setCharacterStream(new StringReader(schema)); in.setSystemId(systemId); return in; } catch (Exception e) { throw new RuntimeException(e); } } } ); } return factory.newSchema(new StreamSource(new StringReader(xmlSchema))).newValidator(); } private static Pattern pTargetNs = Pattern.compile("targetNamespace=['\"]([^'\"]+)['\"]"); /* An instance of a global element, global attribute, or XML type to be serialized. */ private static class QueueEntry { Namespace ns; String name; ClassMeta cm; QueueEntry(Namespace ns, String name, ClassMeta cm) { this.ns = ns; this.name = name; this.cm = cm; } } /* An encapsulation of all schemas present in the metamodel of the serialized object. */ private class Schemas extends LinkedHashMap { private static final long serialVersionUID = 1L; private Namespace defaultNs; private BeanSession session; private LinkedList elementQueue = new LinkedList(), attributeQueue = new LinkedList(), typeQueue = new LinkedList(); private Schemas(BeanSession session, Namespace xs, Namespace defaultNs, Namespace[] allNs) throws IOException { this.session = session; this.defaultNs = defaultNs; for (Namespace ns : allNs) put(ns, new Schema(this, xs, ns, defaultNs, allNs)); } private Schema getSchema(Namespace ns) { if (ns == null) ns = defaultNs; Schema s = get(ns); if (s == null) throw new FormattedRuntimeException("No schema defined for namespace ''{0}''", ns); return s; } private void process(Object o) throws IOException { ClassMeta cm = getClassMetaForObject(o); Namespace ns = defaultNs; if (cm == null) queueElement(ns, "null", object()); else { XmlClassMeta xmlMeta = cm.getExtendedMeta(XmlClassMeta.class); if (cm.getDictionaryName() != null && xmlMeta.getNamespace() != null) ns = xmlMeta.getNamespace(); queueElement(ns, cm.getDictionaryName(), cm); } processQueue(); } private void processQueue() throws IOException { boolean b; do { b = false; while (! elementQueue.isEmpty()) { QueueEntry q = elementQueue.removeFirst(); b |= getSchema(q.ns).processElement(q.name, q.cm); } while (! typeQueue.isEmpty()) { QueueEntry q = typeQueue.removeFirst(); b |= getSchema(q.ns).processType(q.name, q.cm); } while (! attributeQueue.isEmpty()) { QueueEntry q = attributeQueue.removeFirst(); b |= getSchema(q.ns).processAttribute(q.name, q.cm); } } while (b); } private void queueElement(Namespace ns, String name, ClassMeta cm) { elementQueue.add(new QueueEntry(ns, name, cm)); } private void queueType(Namespace ns, String name, ClassMeta cm) { if (name == null) name = XmlUtils.encodeElementName(cm); typeQueue.add(new QueueEntry(ns, name, cm)); } private void queueAttribute(Namespace ns, String name, ClassMeta cm) { attributeQueue.add(new QueueEntry(ns, name, cm)); } private void serializeTo(Writer w) throws IOException { boolean b = false; for (Schema s : values()) { if (b) w.append('\u0000'); w.append(s.toString()); b = true; } } } /* An encapsulation of a single schema. */ private class Schema { private StringWriter sw = new StringWriter(); private XmlWriter w; private Namespace defaultNs, targetNs; private Schemas schemas; private Set processedTypes = new HashSet(), processedAttributes = new HashSet(), processedElements = new HashSet(); public Schema(Schemas schemas, Namespace xs, Namespace targetNs, Namespace defaultNs, Namespace[] allNs) throws IOException { this.schemas = schemas; this.defaultNs = defaultNs; this.targetNs = targetNs; w = new XmlWriter(sw, isUseWhitespace(), getMaxIndent(), isTrimStrings(), getQuoteChar(), null, true, null); int i = indent; w.oTag(i, "schema"); w.attr("xmlns", xs.getUri()); w.attr("targetNamespace", targetNs.getUri()); w.attr("elementFormDefault", "qualified"); if (targetNs != defaultNs) w.attr("attributeFormDefault", "qualified"); for (Namespace ns2 : allNs) w.attr("xmlns", ns2.name, ns2.uri); w.append('>').nl(i); for (Namespace ns : allNs) { if (ns != targetNs) { w.oTag(i+1, "import") .attr("namespace", ns.getUri()) .attr("schemaLocation", ns.getName()+".xsd") .append("/>").nl(i+1); } } } private boolean processElement(String name, ClassMeta cm) throws IOException { if (processedElements.contains(name)) return false; processedElements.add(name); ClassMeta ft = cm.getSerializedClassMeta(schemas.session); if (name == null) name = getElementName(ft); Namespace ns = first(ft.getExtendedMeta(XmlClassMeta.class).getNamespace(), defaultNs); String type = getXmlType(ns, ft); w.oTag(indent+1, "element") .attr("name", XmlUtils.encodeElementName(name)) .attr("type", type) .append('/').append('>').nl(indent+1); schemas.queueType(ns, null, ft); schemas.processQueue(); return true; } private boolean processAttribute(String name, ClassMeta cm) throws IOException { if (processedAttributes.contains(name)) return false; processedAttributes.add(name); String type = getXmlAttrType(cm); w.oTag(indent+1, "attribute") .attr("name", name) .attr("type", type) .append('/').append('>').nl(indent+1); return true; } private boolean processType(String name, ClassMeta cm) throws IOException { if (processedTypes.contains(name)) return false; processedTypes.add(name); int i = indent + 1; cm = cm.getSerializedClassMeta(schemas.session); XmlBeanMeta xbm = cm.isBean() ? cm.getBeanMeta().getExtendedMeta(XmlBeanMeta.class) : null; w.oTag(i, "complexType") .attr("name", name); // This element can have mixed content if: // 1) It's a generic Object (so it can theoretically be anything) // 2) The bean has a property defined with @XmlFormat.CONTENT. if ((xbm != null && (xbm.getContentFormat() != null && xbm.getContentFormat().isOneOf(TEXT,TEXT_PWS,MIXED,MIXED_PWS,XMLTEXT))) || ! cm.isMapOrBean()) w.attr("mixed", "true"); w.cTag().nl(i); if (! (cm.isMapOrBean() || cm.isCollectionOrArray() || (cm.isAbstract() && ! cm.isNumber()) || cm.isObject())) { w.oTag(i+1, "attribute").attr("name", getBeanTypePropertyName(cm)).attr("type", "string").ceTag().nl(i+1); } else { //----- Bean ----- if (cm.isBean()) { BeanMeta bm = cm.getBeanMeta(); boolean hasChildElements = false; for (BeanPropertyMeta pMeta : bm.getPropertyMetas()) { XmlFormat pMetaFormat = pMeta.getExtendedMeta(XmlBeanPropertyMeta.class).getXmlFormat(); if (pMetaFormat != XmlFormat.ATTR) hasChildElements = true; } XmlBeanMeta xbm2 = bm.getExtendedMeta(XmlBeanMeta.class); if (xbm2.getContentProperty() != null && xbm2.getContentFormat() == ELEMENTS) { w.sTag(i+1, "sequence").nl(i+1); w.oTag(i+2, "any") .attr("processContents", "skip") .attr("minOccurs", 0) .ceTag().nl(i+2); w.eTag(i+1, "sequence").nl(i+1); } else if (hasChildElements) { boolean hasOtherNsElement = false; boolean hasCollapsed = false; for (BeanPropertyMeta pMeta : bm.getPropertyMetas()) { XmlBeanPropertyMeta xmlMeta = pMeta.getExtendedMeta(XmlBeanPropertyMeta.class); if (xmlMeta.getXmlFormat() != ATTR) { if (xmlMeta.getNamespace() != null) { ClassMeta ct2 = pMeta.getClassMeta(); Namespace cNs = first(xmlMeta.getNamespace(), ct2.getExtendedMeta(XmlClassMeta.class).getNamespace(), cm.getExtendedMeta(XmlClassMeta.class).getNamespace(), defaultNs); // Child element is in another namespace. schemas.queueElement(cNs, pMeta.getName(), ct2); hasOtherNsElement = true; } if (xmlMeta.getXmlFormat() == COLLAPSED) hasCollapsed = true; } } if (hasOtherNsElement || hasCollapsed) { // If this bean has any child elements in another namespace, // we need to add an element. w.oTag(i+1, "choice").attr("maxOccurs", "unbounded").cTag().nl(i+1); w.oTag(i+2, "any") .attr("processContents", "skip") .attr("minOccurs", 0) .ceTag().nl(i+2); w.eTag(i+1, "choice").nl(i+1); } else { w.sTag(i+1, "all").nl(i+1); for (BeanPropertyMeta pMeta : bm.getPropertyMetas()) { XmlBeanPropertyMeta xmlMeta = pMeta.getExtendedMeta(XmlBeanPropertyMeta.class); if (xmlMeta.getXmlFormat() != ATTR) { boolean isCollapsed = xmlMeta.getXmlFormat() == COLLAPSED; ClassMeta ct2 = pMeta.getClassMeta(); String childName = pMeta.getName(); if (isCollapsed) { if (xmlMeta.getChildName() != null) childName = xmlMeta.getChildName(); ct2 = pMeta.getClassMeta().getElementType(); } Namespace cNs = first(xmlMeta.getNamespace(), ct2.getExtendedMeta(XmlClassMeta.class).getNamespace(), cm.getExtendedMeta(XmlClassMeta.class).getNamespace(), defaultNs); if (xmlMeta.getNamespace() == null) { w.oTag(i+2, "element") .attr("name", XmlUtils.encodeElementName(childName), false) .attr("type", getXmlType(cNs, ct2)) .attr("minOccurs", 0); w.ceTag().nl(i+2); } else { // Child element is in another namespace. schemas.queueElement(cNs, pMeta.getName(), ct2); hasOtherNsElement = true; } } } w.eTag(i+1, "all").nl(i+1); } } for (BeanPropertyMeta pMeta : bm.getExtendedMeta(XmlBeanMeta.class).getAttrProperties().values()) { Namespace pNs = pMeta.getExtendedMeta(XmlBeanPropertyMeta.class).getNamespace(); if (pNs == null) pNs = defaultNs; // If the bean attribute has a different namespace than the bean, then it needs to // be added as a top-level entry in the appropriate schema file. if (pNs != targetNs) { schemas.queueAttribute(pNs, pMeta.getName(), pMeta.getClassMeta()); w.oTag(i+1, "attribute") //.attr("name", pMeta.getName(), true) .attr("ref", pNs.getName() + ':' + pMeta.getName()) .ceTag().nl(i+1); } // Otherwise, it's just a plain attribute of this bean. else { w.oTag(i+1, "attribute") .attr("name", pMeta.getName(), true) .attr("type", getXmlAttrType(pMeta.getClassMeta())) .ceTag().nl(i+1); } } //----- Collection ----- } else if (cm.isCollectionOrArray()) { ClassMeta elementType = cm.getElementType(); if (elementType.isObject()) { w.sTag(i+1, "sequence").nl(i+1); w.oTag(i+2, "any") .attr("processContents", "skip") .attr("maxOccurs", "unbounded") .attr("minOccurs", "0") .ceTag().nl(i+2); w.eTag(i+1, "sequence").nl(i+1); } else { Namespace cNs = first(elementType.getExtendedMeta(XmlClassMeta.class).getNamespace(), cm.getExtendedMeta(XmlClassMeta.class).getNamespace(), defaultNs); schemas.queueType(cNs, null, elementType); w.sTag(i+1, "sequence").nl(i+1); w.oTag(i+2, "any") .attr("processContents", "skip") .attr("maxOccurs", "unbounded") .attr("minOccurs", "0") .ceTag().nl(i+2); w.eTag(i+1, "sequence").nl(i+1); } //----- Map ----- } else if (cm.isMap() || cm.isAbstract() || cm.isObject()) { w.sTag(i+1, "sequence").nl(i+1); w.oTag(i+2, "any") .attr("processContents", "skip") .attr("maxOccurs", "unbounded") .attr("minOccurs", "0") .ceTag().nl(i+2); w.eTag(i+1, "sequence").nl(i+1); } w.oTag(i+1, "attribute") .attr("name", getBeanTypePropertyName(null)) .attr("type", "string") .ceTag().nl(i+1); } w.eTag(i, "complexType").nl(i); schemas.processQueue(); return true; } private String getElementName(ClassMeta cm) { cm = cm.getSerializedClassMeta(schemas.session); String name = cm.getDictionaryName(); if (name == null) { if (cm.isBoolean()) name = "boolean"; else if (cm.isNumber()) name = "number"; else if (cm.isCollectionOrArray()) name = "array"; else if (! (cm.isMapOrBean() || cm.isCollectionOrArray() || cm.isObject() || cm.isAbstract())) name = "string"; else name = "object"; } return name; } @Override /* Object */ public String toString() { try { w.eTag(indent, "schema").nl(indent); } catch (IOException e) { throw new RuntimeException(e); // Shouldn't happen. } return sw.toString(); } private String getXmlType(Namespace currentNs, ClassMeta cm) { String name = null; cm = cm.getSerializedClassMeta(schemas.session); if (currentNs == targetNs) { if (cm.isPrimitive()) { if (cm.isBoolean()) name = "boolean"; else if (cm.isNumber()) { if (cm.isDecimal()) name = "decimal"; else name = "integer"; } } } if (name == null) { name = XmlUtils.encodeElementName(cm); schemas.queueType(currentNs, name, cm); return currentNs.getName() + ":" + name; } return name; } } @SafeVarargs private static T first(T...tt) { for (T t : tt) if (t != null) return t; return null; } private static String getXmlAttrType(ClassMeta cm) { if (cm.isBoolean()) return "boolean"; if (cm.isNumber()) { if (cm.isDecimal()) return "decimal"; return "integer"; } return "string"; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy