001/**
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.xbean.spring.generator;
018
019import java.io.File;
020import java.io.FileWriter;
021import java.io.IOException;
022import java.io.PrintWriter;
023import java.util.Iterator;
024import java.util.List;
025
026/**
027 * @author Dain Sundstrom
028 * @version $Id$
029 * @since 1.0
030 */
031public class XsdGenerator implements GeneratorPlugin {
032    private final File destFile;
033    private LogFacade log;
034    private boolean strictOrder;
035
036    public XsdGenerator(File destFile) {
037        this(destFile, true);
038    }
039    public XsdGenerator(File destFile, boolean strictOrder) {
040        this.destFile = destFile;
041        this.strictOrder = strictOrder;
042    }
043
044    public void generate(NamespaceMapping namespaceMapping) throws IOException {
045        // TODO can only handle 1 schema document so far...
046        File file = destFile;
047        log.log("Generating XSD file: " + file + " for namespace: " + namespaceMapping.getNamespace());
048        PrintWriter out = new PrintWriter(new FileWriter(file));
049        try {
050            generateSchema(out, namespaceMapping);
051        } finally {
052            out.close();
053        }
054    }
055
056    public void generateSchema(PrintWriter out, NamespaceMapping namespaceMapping) {
057        out.println("<?xml version='1.0'?>");
058        out.println("<!-- NOTE: this file is autogenerated by Apache XBean -->");
059        out.println();
060        out.println("<xs:schema elementFormDefault='qualified'");
061        out.println("           targetNamespace='" + namespaceMapping.getNamespace() + "'");
062        out.println("           xmlns:xs='http://www.w3.org/2001/XMLSchema'");
063        out.println("           xmlns:tns='" + namespaceMapping.getNamespace() + "'>");
064
065        for (Iterator iter = namespaceMapping.getElements().iterator(); iter.hasNext();) {
066            ElementMapping element = (ElementMapping) iter.next();
067            generateElementMapping(out, namespaceMapping, element);
068        }
069
070        out.println();
071        out.println("</xs:schema>");
072    }
073
074    private void generateElementMapping(PrintWriter out, NamespaceMapping namespaceMapping, ElementMapping element) {
075        out.println();
076        out.println("  <!-- element for type: " + element.getClassName() + " -->");
077
078        String localName = element.getElementName();
079
080        out.println("  <xs:element name='" + localName + "'>");
081
082        if (!isEmptyString(element.getDescription())) {
083            out.println("    <xs:annotation>");
084            out.println("      <xs:documentation><![CDATA[");
085            out.println("        " + element.getDescription());
086            out.println("      ]]></xs:documentation>");
087            out.println("    </xs:annotation>");
088        }
089
090        out.println("    <xs:complexType>");
091
092        int complexCount = 0;
093        for (Iterator iterator = element.getAttributes().iterator(); iterator.hasNext();) {
094            AttributeMapping attributeMapping = (AttributeMapping) iterator.next();
095            if (!namespaceMapping.isSimpleType(attributeMapping.getType())) {
096                complexCount++;
097            }
098        }
099        if (complexCount > 0) {
100            if(strictOrder){
101                out.println("      <xs:sequence>");
102            } else {
103                out.println("      <xs:choice minOccurs=\"0\" maxOccurs=\"unbounded\"><xs:choice>");
104            }
105            for (Iterator iterator = element.getAttributes().iterator(); iterator.hasNext();) {
106                AttributeMapping attributeMapping = (AttributeMapping) iterator.next();
107                if (!namespaceMapping.isSimpleType(attributeMapping.getType())) {
108                    generateElementMappingComplexProperty(out, namespaceMapping, attributeMapping);
109                }
110            }
111            out.println("        <xs:any namespace='##other' minOccurs='0' maxOccurs='unbounded'/>");
112            if(strictOrder){
113                out.println("      </xs:sequence>");
114            } else {
115                out.println("      </xs:choice></xs:choice>");
116            }
117        }
118
119        for (Iterator iterator = element.getAttributes().iterator(); iterator.hasNext();) {
120            AttributeMapping attributeMapping = (AttributeMapping) iterator.next();
121            if (namespaceMapping.isSimpleType(attributeMapping.getType())) {
122                generateElementMappingSimpleProperty(out, attributeMapping);
123            } else if (!attributeMapping.getType().isCollection()) {
124                generateElementMappingComplexPropertyAsRef(out, attributeMapping);
125            }
126        }
127        generateIDAttributeMapping(out, namespaceMapping, element);
128
129        out.println("      <xs:anyAttribute namespace='##other' processContents='lax'/>");
130        out.println("    </xs:complexType>");
131        out.println("  </xs:element>");
132        out.println();
133    }
134
135    private boolean isEmptyString(String str) {
136        if (str == null) {
137            return true;
138        }
139        for (int i = 0; i < str.length(); i++) {
140            if (!Character.isWhitespace(str.charAt(i))) {
141                return false;
142            }
143        }
144        return true;
145    }
146
147    private void generateIDAttributeMapping(PrintWriter out, NamespaceMapping namespaceMapping, ElementMapping element) {
148        for (Iterator iterator = element.getAttributes().iterator(); iterator.hasNext();) {
149            AttributeMapping attributeMapping = (AttributeMapping) iterator.next();
150            if ("id".equals(attributeMapping.getAttributeName())) {
151                return;
152            }
153        }
154        out.println("      <xs:attribute name='id' type='xs:ID'/>");
155    }
156
157    private void generateElementMappingSimpleProperty(PrintWriter out, AttributeMapping attributeMapping) {
158        // types with property editors need to be xs:string in the schema to validate
159        String type = attributeMapping.getPropertyEditor() != null ? 
160                        Utils.getXsdType(Type.newSimpleType(String.class.getName())) : Utils.getXsdType(attributeMapping.getType());
161        if (!isEmptyString(attributeMapping.getDescription())) {
162            out.println("      <xs:attribute name='" + attributeMapping.getAttributeName() + "' type='" + type + "'>");
163            out.println("        <xs:annotation>");
164            out.println("          <xs:documentation><![CDATA[");
165            out.println("            " + attributeMapping.getDescription());
166            out.println("          ]]></xs:documentation>");
167            out.println("        </xs:annotation>");
168            out.println("      </xs:attribute>");
169        } else {
170            out.println("      <xs:attribute name='" + attributeMapping.getAttributeName() + "' type='" + type + "'/>");
171        }
172    }
173
174    private void generateElementMappingComplexPropertyAsRef(PrintWriter out, AttributeMapping attributeMapping) {
175        if (!isEmptyString(attributeMapping.getDescription())) {
176            out.println("      <xs:attribute name='" + attributeMapping.getAttributeName() + "' type='xs:string'>");
177            out.println("        <xs:annotation>");
178            out.println("          <xs:documentation><![CDATA[");
179            out.println("            " + attributeMapping.getDescription());
180            out.println("          ]]></xs:documentation>");
181            out.println("        </xs:annotation>");
182            out.println("      </xs:attribute>");
183        } else {
184            out.println("      <xs:attribute name='" + attributeMapping.getAttributeName() + "' type='xs:string'/>");
185        }
186    }
187
188    private void generateElementMappingComplexProperty(PrintWriter out, NamespaceMapping namespaceMapping, AttributeMapping attributeMapping) {
189        Type type = attributeMapping.getType();
190        List types;
191        if (type.isCollection()) {
192            types = Utils.findImplementationsOf(namespaceMapping, type.getNestedType());
193        } else {
194            types = Utils.findImplementationsOf(namespaceMapping, type);
195        }
196        String maxOccurs = type.isCollection() || "java.util.Map".equals(type.getName()) ? "unbounded" : "1";
197
198        out.println("        <xs:element name='" + attributeMapping.getAttributeName() + "' minOccurs='0' maxOccurs='" + maxOccurs + "'>");
199        if (!isEmptyString(attributeMapping.getDescription())) {
200            out.println("          <xs:annotation>");
201            out.println("            <xs:documentation><![CDATA[");
202            out.println("              " + attributeMapping.getDescription());
203            out.println("            ]]></xs:documentation>");
204            out.println("          </xs:annotation>");
205        }
206        out.println("          <xs:complexType>");
207        if (types.isEmpty()) {
208            // We don't know the type because it's generic collection.  Allow folks to insert objets from any namespace
209            out.println("            <xs:sequence minOccurs='0' maxOccurs='" + maxOccurs + "'><xs:any minOccurs='0' maxOccurs='unbounded'/></xs:sequence>");
210        } else {
211            out.println("            <xs:choice minOccurs='0' maxOccurs='" + maxOccurs + "'>");
212            for (Iterator iterator = types.iterator(); iterator.hasNext();) {
213                ElementMapping element = (ElementMapping) iterator.next();
214                out.println("              <xs:element ref='tns:" + element.getElementName() + "'/>");
215            }
216            out.println("              <xs:any namespace='##other'/>");
217            out.println("            </xs:choice>");
218        }
219        out.println("          </xs:complexType>");
220        out.println("        </xs:element>");
221    }
222
223    public LogFacade getLog() {
224        return log;
225    }
226
227    public void setLog(LogFacade log) {
228        this.log = log;
229    }
230
231}