/** * 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.cxf.javascript; import java.util.LinkedList; import java.util.List; import java.util.logging.Logger; import javax.xml.namespace.QName; import org.apache.cxf.common.i18n.Message; import org.apache.cxf.common.logging.LogUtils; import org.apache.cxf.common.xmlschema.SchemaCollection; import org.apache.cxf.common.xmlschema.XmlSchemaUtils; import org.apache.ws.commons.schema.XmlSchema; import org.apache.ws.commons.schema.XmlSchemaChoice; import org.apache.ws.commons.schema.XmlSchemaChoiceMember; import org.apache.ws.commons.schema.XmlSchemaElement; import org.apache.ws.commons.schema.XmlSchemaObject; import org.apache.ws.commons.schema.XmlSchemaParticle; import org.apache.ws.commons.schema.XmlSchemaSequence; import org.apache.ws.commons.schema.XmlSchemaSequenceMember; import org.apache.ws.commons.schema.XmlSchemaType; import org.apache.ws.commons.schema.constants.Constants; /** * All the information needed to create the JavaScript for an Xml Schema element * or xs:any. */ public final class ParticleInfo implements ItemInfo { private static final Logger LOG = LogUtils.getL7dLogger(ParticleInfo.class); private XmlSchemaParticle particle; private String javascriptName; private String xmlName; private XmlSchemaType containingType; // in the RPC case, we can have a type and no element. private XmlSchemaType type; private boolean empty; private boolean isGroup; private List<ParticleInfo> children; // These are exactly the same values as we find in the XmlSchemaElement. // there is no rationalization. But the accessors take care of business. private long minOccurs; private long maxOccurs; private boolean nillable; private boolean any; private boolean anyType; private String defaultValue; private boolean global; private ParticleInfo() { } /** * Create an elementInfo that stores information about a global, named, * element. * * @param element the element * @param currentSchema the schema it came from. * @param schemaCollection the collection of all schemas. * @param prefixAccumulator the accumulator that assigns prefixes. * @return */ public static ParticleInfo forGlobalElement(XmlSchemaElement element, XmlSchema currentSchema, SchemaCollection schemaCollection, NamespacePrefixAccumulator prefixAccumulator) { ParticleInfo elementInfo = new ParticleInfo(); elementInfo.particle = element; elementInfo.minOccurs = element.getMinOccurs(); elementInfo.maxOccurs = element.getMaxOccurs(); elementInfo.nillable = element.isNillable(); elementInfo.global = true; factoryCommon(element, currentSchema, schemaCollection, prefixAccumulator, elementInfo); return elementInfo; } /** * Create element information for a part element. For a part, the JavaScript * and Element names are calculated in advance, and the element itself might * be null! In that case, the minOccurs and maxOccurs are conventional. Note * that in some cases the code in ServiceJavascriptBuilder uses a local * element (or xa:any) from inside the part element, instead of the part * element itself. * * @param element the element, or null * @param schemaCollection the schema collection, for resolving types. * @param javascriptName javascript variable name * @param xmlElementName xml element string * @return */ public static ParticleInfo forPartElement(XmlSchemaElement element, SchemaCollection schemaCollection, String javascriptName, String xmlElementName) { ParticleInfo elementInfo = new ParticleInfo(); elementInfo.particle = element; if (element == null) { elementInfo.minOccurs = 1; elementInfo.maxOccurs = 1; } else { elementInfo.minOccurs = element.getMinOccurs(); elementInfo.maxOccurs = element.getMaxOccurs(); elementInfo.nillable = element.isNillable(); factorySetupType(element, schemaCollection, elementInfo); } elementInfo.javascriptName = javascriptName; elementInfo.xmlName = xmlElementName; elementInfo.global = true; return elementInfo; } /** * Fill in an ElementInfo for an element or xs:any from a sequence. * * @param sequenceElement * @param currentSchema * @param schemaCollection * @param prefixAccumulator * @return */ public static ParticleInfo forLocalItem(XmlSchemaObject sequenceObject, XmlSchema currentSchema, SchemaCollection schemaCollection, NamespacePrefixAccumulator prefixAccumulator, QName contextName) { XmlSchemaParticle sequenceParticle = JavascriptUtils.getObjectParticle(sequenceObject, contextName, currentSchema); ParticleInfo elementInfo = new ParticleInfo(); XmlSchemaParticle realParticle = sequenceParticle; elementInfo.setMinOccurs(sequenceParticle.getMinOccurs()); elementInfo.setMaxOccurs(sequenceParticle.getMaxOccurs()); if (sequenceParticle instanceof XmlSchemaElement) { XmlSchemaElement sequenceElement = (XmlSchemaElement)sequenceParticle; if (sequenceElement.getRef().getTargetQName() != null) { XmlSchemaElement refElement = sequenceElement.getRef().getTarget(); if (refElement == null) { Message message = new Message("ELEMENT_DANGLING_REFERENCE", LOG, sequenceElement .getQName(), sequenceElement.getRef().getTargetQName()); throw new UnsupportedConstruct(message.toString()); } realParticle = refElement; elementInfo.global = true; } elementInfo.nillable = ((XmlSchemaElement)realParticle).isNillable(); } else if (sequenceParticle instanceof XmlSchemaChoice) { XmlSchemaChoice choice = (XmlSchemaChoice)sequenceParticle; if (sequenceParticle.getMaxOccurs() > 1) { Message message = new Message("GROUP_ELEMENT_MULTI_OCCURS", LOG, sequenceParticle .getClass().getSimpleName()); throw new UnsupportedConstruct(message.toString()); } elementInfo.children = new LinkedList<>(); List<XmlSchemaChoiceMember> items = choice.getItems(); for (XmlSchemaChoiceMember item : items) { XmlSchemaObject schemaObject = JavascriptUtils.getObjectParticle((XmlSchemaObject)item, contextName, currentSchema); ParticleInfo childParticle = ParticleInfo.forLocalItem(schemaObject, currentSchema, schemaCollection, prefixAccumulator, contextName); if (childParticle.isAny()) { Message message = new Message("GROUP_ELEMENT_ANY", LOG, sequenceParticle.getClass() .getSimpleName()); throw new UnsupportedConstruct(message.toString()); } childParticle.setMinOccurs(0); elementInfo.children.add(childParticle); } } else if (sequenceParticle instanceof XmlSchemaSequence) { XmlSchemaSequence nestedSequence = (XmlSchemaSequence)sequenceParticle; if (sequenceParticle.getMaxOccurs() > 1) { Message message = new Message("SEQUENCE_ELEMENT_MULTI_OCCURS", LOG, sequenceParticle .getClass().getSimpleName()); throw new UnsupportedConstruct(message.toString()); } elementInfo.children = new LinkedList<>(); List<XmlSchemaSequenceMember> items = nestedSequence.getItems(); for (XmlSchemaSequenceMember item : items) { XmlSchemaObject schemaObject = JavascriptUtils.getObjectParticle((XmlSchemaObject)item, contextName, currentSchema); ParticleInfo childParticle = ParticleInfo.forLocalItem(schemaObject, currentSchema, schemaCollection, prefixAccumulator, contextName); if (childParticle.isAny()) { Message message = new Message("SEQUENCE_ELEMENT_ANY", LOG, sequenceParticle.getClass() .getSimpleName()); throw new UnsupportedConstruct(message.toString()); } if (sequenceParticle.getMinOccurs() == 0) { childParticle.setMinOccurs(0); } elementInfo.children.add(childParticle); } } factoryCommon(realParticle, currentSchema, schemaCollection, prefixAccumulator, elementInfo); elementInfo.particle = realParticle; return elementInfo; } private static void factoryCommon(XmlSchemaParticle particle, XmlSchema currentSchema, SchemaCollection schemaCollection, NamespacePrefixAccumulator prefixAccumulator, ParticleInfo elementInfo) { if (particle instanceof XmlSchemaElement) { XmlSchemaElement element = (XmlSchemaElement)particle; QName elementQName = XmlSchemaUtils.getElementQualifiedName(element, currentSchema); String elementNamespaceURI = elementQName.getNamespaceURI(); boolean elementNoNamespace = "".equals(elementNamespaceURI); XmlSchema elementSchema = null; if (!elementNoNamespace) { elementSchema = schemaCollection.getSchemaByTargetNamespace(elementNamespaceURI); if (elementSchema == null) { throw new RuntimeException("Missing schema " + elementNamespaceURI); } } boolean qualified = !elementNoNamespace && XmlSchemaUtils.isElementQualified(element, true, currentSchema, elementSchema); elementInfo.xmlName = prefixAccumulator.xmlElementString(elementQName, qualified); // we are assuming here that we are not dealing, in close proximity, // with elements with identical local names and different // namespaces. elementInfo.javascriptName = elementQName.getLocalPart(); String schemaDefaultValue = element.getDefaultValue(); /* * Schema default values are carried as strings. * In javascript, for actual strings, we need quotes, but not for * numbers. The following is a trick. */ schemaDefaultValue = protectDefaultValue(schemaDefaultValue); elementInfo.defaultValue = schemaDefaultValue; factorySetupType(element, schemaCollection, elementInfo); elementInfo.isGroup = false; } else if (particle instanceof XmlSchemaChoice) { elementInfo.isGroup = true; } else if (particle instanceof XmlSchemaSequence) { elementInfo.isGroup = true; } else { // any elementInfo.any = true; elementInfo.xmlName = null; // unknown until runtime. // TODO: multiple 'any' elementInfo.javascriptName = "any"; elementInfo.type = null; // runtime for any. elementInfo.isGroup = false; } } private static String protectDefaultValue(String schemaDefaultValue) { if (schemaDefaultValue == null) { return null; } boolean leaveAlone = false; try { Long.parseLong(schemaDefaultValue); leaveAlone = true; } catch (NumberFormatException nfe) { try { Double.parseDouble(schemaDefaultValue); leaveAlone = true; } catch (NumberFormatException nfe2) { // } } if (!leaveAlone) { StringBuilder builder = new StringBuilder(); builder.append("'"); for (char c : schemaDefaultValue.toCharArray()) { if (c == '\'') { builder.append("\\'"); } else if (c == '\\') { builder.append("\\\\"); } else { builder.append(c); } } builder.append('\''); schemaDefaultValue = builder.toString(); } return schemaDefaultValue; } private static void factorySetupType(XmlSchemaElement element, SchemaCollection schemaCollection, ParticleInfo elementInfo) { elementInfo.type = element.getSchemaType(); if (elementInfo.type == null) { if (element.getSchemaTypeName() == null // no type at all -> anyType || element.getSchemaTypeName().equals(Constants.XSD_ANYTYPE)) { elementInfo.anyType = true; } else { elementInfo.type = schemaCollection.getTypeByQName(element.getSchemaTypeName()); if (elementInfo.type == null && !element.getSchemaTypeName() .getNamespaceURI().equals(Constants.URI_2001_SCHEMA_XSD)) { JavascriptUtils.unsupportedConstruct("MISSING_TYPE", element.getSchemaTypeName() .toString(), element.getQName(), element); } } } else if (elementInfo.type.getQName() != null && Constants.XSD_ANYTYPE.equals(elementInfo.type.getQName())) { elementInfo.anyType = true; } } /** * As a general rule, the JavaScript code is organized by types. The * exception is global elements that have anonymous types. In those cases, * the JavaScript code has its functions named according to the element. * This method returns the QName for the type or element, accordingly. If a * schema has a local element with an anonymous, complex, type, this will * throw. This will need to be fixed. * * @return the qname. */ public QName getControllingName() { if (type != null && type.getQName() != null) { return type.getQName(); } else if (particle instanceof XmlSchemaElement) { XmlSchemaElement element = (XmlSchemaElement)particle; if (element.getQName() != null) { return element.getQName(); } } Message message = new Message("IMPOSSIBLE_GLOBAL_ITEM", LOG, JavascriptUtils.cleanedUpSchemaSource(particle)); LOG.severe(message.toString()); throw new UnsupportedConstruct(message); } public XmlSchemaParticle getParticle() { return particle; } public String getJavascriptName() { return javascriptName; } public void setJavascriptName(String name) { javascriptName = name; } public String getXmlName() { return xmlName; } public void setXmlName(String elementXmlName) { this.xmlName = elementXmlName; } public XmlSchemaType getContainingType() { return containingType; } public void setContainingType(XmlSchemaType containingType) { this.containingType = containingType; } public XmlSchemaType getType() { return type; } public void setType(XmlSchemaType type) { this.type = type; } public boolean isGroup() { return isGroup; } public List<ParticleInfo> getChildren() { return children; } public boolean isEmpty() { return empty; } public void setEmpty(boolean empty) { this.empty = empty; } public long getMinOccurs() { return minOccurs; } private void setMinOccurs(long value) { minOccurs = value; if (isGroup()) { for (ParticleInfo child : getChildren()) { child.setMinOccurs(value); } } } public long getMaxOccurs() { return maxOccurs; } private void setMaxOccurs(long value) { maxOccurs = value; if (isGroup()) { for (ParticleInfo child : getChildren()) { child.setMaxOccurs(value); } } } public boolean isArray() { return maxOccurs > 1; } public boolean isOptional() { return minOccurs == 0 && maxOccurs == 1; } /** * @return Returns the nillable flag for the element. False for 'xs:any' */ public boolean isNillable() { return nillable; } public boolean isAny() { return any; } public boolean isAnyType() { return anyType; } /** * * * * @return Returns the defaultValue. */ public String getDefaultValue() { if (isOptional()) { return "null"; } else { return defaultValue; } } /** * @param defaultValue The defaultValue to set. */ public void setDefaultValue(String defaultValue) { this.defaultValue = defaultValue; } /** * True if this describes a global, named, element. * * @return */ public boolean isGlobal() { return global; } @Override public String toString() { return "ItemInfo: " + javascriptName; } }