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}