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 035 public XsdGenerator(File destFile) { 036 this.destFile = destFile; 037 } 038 039 public void generate(NamespaceMapping namespaceMapping) throws IOException { 040 // TODO can only handle 1 schema document so far... 041 File file = destFile; 042 log.log("Generating XSD file: " + file + " for namespace: " + namespaceMapping.getNamespace()); 043 PrintWriter out = new PrintWriter(new FileWriter(file)); 044 try { 045 generateSchema(out, namespaceMapping); 046 } finally { 047 out.close(); 048 } 049 } 050 051 public void generateSchema(PrintWriter out, NamespaceMapping namespaceMapping) { 052 out.println("<?xml version='1.0'?>"); 053 out.println("<!-- NOTE: this file is autogenerated by Apache XBean -->"); 054 out.println(); 055 out.println("<xs:schema elementFormDefault='qualified'"); 056 out.println(" targetNamespace='" + namespaceMapping.getNamespace() + "'"); 057 out.println(" xmlns:xs='http://www.w3.org/2001/XMLSchema'"); 058 out.println(" xmlns:tns='" + namespaceMapping.getNamespace() + "'>"); 059 060 for (Iterator iter = namespaceMapping.getElements().iterator(); iter.hasNext();) { 061 ElementMapping element = (ElementMapping) iter.next(); 062 generateElementMapping(out, namespaceMapping, element); 063 } 064 065 out.println(); 066 out.println("</xs:schema>"); 067 } 068 069 private void generateElementMapping(PrintWriter out, NamespaceMapping namespaceMapping, ElementMapping element) { 070 out.println(); 071 out.println(" <!-- element for type: " + element.getClassName() + " -->"); 072 073 String localName = element.getElementName(); 074 075 out.println(" <xs:element name='" + localName + "'>"); 076 077 if (!isEmptyString(element.getDescription())) { 078 out.println(" <xs:annotation>"); 079 out.println(" <xs:documentation><![CDATA["); 080 out.println(" " + element.getDescription()); 081 out.println(" ]]></xs:documentation>"); 082 out.println(" </xs:annotation>"); 083 } 084 085 out.println(" <xs:complexType>"); 086 087 int complexCount = 0; 088 for (Iterator iterator = element.getAttributes().iterator(); iterator.hasNext();) { 089 AttributeMapping attributeMapping = (AttributeMapping) iterator.next(); 090 if (!namespaceMapping.isSimpleType(attributeMapping.getType())) { 091 complexCount++; 092 } 093 } 094 if (complexCount > 0) { 095 out.println(" <xs:sequence>"); 096 for (Iterator iterator = element.getAttributes().iterator(); iterator.hasNext();) { 097 AttributeMapping attributeMapping = (AttributeMapping) iterator.next(); 098 if (!namespaceMapping.isSimpleType(attributeMapping.getType())) { 099 generateElementMappingComplexProperty(out, namespaceMapping, attributeMapping); 100 } 101 } 102 out.println(" <xs:any namespace='##other' minOccurs='0' maxOccurs='unbounded'/>"); 103 out.println(" </xs:sequence>"); 104 } 105 106 for (Iterator iterator = element.getAttributes().iterator(); iterator.hasNext();) { 107 AttributeMapping attributeMapping = (AttributeMapping) iterator.next(); 108 if (namespaceMapping.isSimpleType(attributeMapping.getType())) { 109 generateElementMappingSimpleProperty(out, attributeMapping); 110 } else if (!attributeMapping.getType().isCollection()) { 111 generateElementMappingComplexPropertyAsRef(out, attributeMapping); 112 } 113 } 114 generateIDAttributeMapping(out, namespaceMapping, element); 115 116 out.println(" <xs:anyAttribute namespace='##other' processContents='lax'/>"); 117 out.println(" </xs:complexType>"); 118 out.println(" </xs:element>"); 119 out.println(); 120 } 121 122 private boolean isEmptyString(String str) { 123 if (str == null) { 124 return true; 125 } 126 for (int i = 0; i < str.length(); i++) { 127 if (!Character.isWhitespace(str.charAt(i))) { 128 return false; 129 } 130 } 131 return true; 132 } 133 134 private void generateIDAttributeMapping(PrintWriter out, NamespaceMapping namespaceMapping, ElementMapping element) { 135 for (Iterator iterator = element.getAttributes().iterator(); iterator.hasNext();) { 136 AttributeMapping attributeMapping = (AttributeMapping) iterator.next(); 137 if ("id".equals(attributeMapping.getAttributeName())) { 138 return; 139 } 140 } 141 out.println(" <xs:attribute name='id' type='xs:ID'/>"); 142 } 143 144 private void generateElementMappingSimpleProperty(PrintWriter out, AttributeMapping attributeMapping) { 145 // types with property editors need to be xs:string in the schema to validate 146 String type = attributeMapping.getPropertyEditor() != null ? 147 Utils.getXsdType(Type.newSimpleType(String.class.getName())) : Utils.getXsdType(attributeMapping.getType()); 148 if (!isEmptyString(attributeMapping.getDescription())) { 149 out.println(" <xs:attribute name='" + attributeMapping.getAttributeName() + "' type='" + type + "'>"); 150 out.println(" <xs:annotation>"); 151 out.println(" <xs:documentation><![CDATA["); 152 out.println(" " + attributeMapping.getDescription()); 153 out.println(" ]]></xs:documentation>"); 154 out.println(" </xs:annotation>"); 155 out.println(" </xs:attribute>"); 156 } else { 157 out.println(" <xs:attribute name='" + attributeMapping.getAttributeName() + "' type='" + type + "'/>"); 158 } 159 } 160 161 private void generateElementMappingComplexPropertyAsRef(PrintWriter out, AttributeMapping attributeMapping) { 162 if (!isEmptyString(attributeMapping.getDescription())) { 163 out.println(" <xs:attribute name='" + attributeMapping.getAttributeName() + "' type='xs:string'>"); 164 out.println(" <xs:annotation>"); 165 out.println(" <xs:documentation><![CDATA["); 166 out.println(" " + attributeMapping.getDescription()); 167 out.println(" ]]></xs:documentation>"); 168 out.println(" </xs:annotation>"); 169 out.println(" </xs:attribute>"); 170 } else { 171 out.println(" <xs:attribute name='" + attributeMapping.getAttributeName() + "' type='xs:string'/>"); 172 } 173 } 174 175 private void generateElementMappingComplexProperty(PrintWriter out, NamespaceMapping namespaceMapping, AttributeMapping attributeMapping) { 176 Type type = attributeMapping.getType(); 177 List types; 178 if (type.isCollection()) { 179 types = Utils.findImplementationsOf(namespaceMapping, type.getNestedType()); 180 } else { 181 types = Utils.findImplementationsOf(namespaceMapping, type); 182 } 183 String maxOccurs = type.isCollection() || "java.util.Map".equals(type.getName()) ? "unbounded" : "1"; 184 185 out.println(" <xs:element name='" + attributeMapping.getAttributeName() + "' minOccurs='0' maxOccurs='1'>"); 186 if (!isEmptyString(attributeMapping.getDescription())) { 187 out.println(" <xs:annotation>"); 188 out.println(" <xs:documentation><![CDATA["); 189 out.println(" " + attributeMapping.getDescription()); 190 out.println(" ]]></xs:documentation>"); 191 out.println(" </xs:annotation>"); 192 } 193 out.println(" <xs:complexType>"); 194 if (types.isEmpty()) { 195 // We don't know the type because it's generic collection. Allow folks to insert objets from any namespace 196 out.println(" <xs:sequence minOccurs='0' maxOccurs='" + maxOccurs + "'><xs:any minOccurs='0' maxOccurs='unbounded'/></xs:sequence>"); 197 } else { 198 out.println(" <xs:choice minOccurs='0' maxOccurs='" + maxOccurs + "'>"); 199 for (Iterator iterator = types.iterator(); iterator.hasNext();) { 200 ElementMapping element = (ElementMapping) iterator.next(); 201 out.println(" <xs:element ref='tns:" + element.getElementName() + "'/>"); 202 } 203 out.println(" <xs:any namespace='##other'/>"); 204 out.println(" </xs:choice>"); 205 } 206 out.println(" </xs:complexType>"); 207 out.println(" </xs:element>"); 208 } 209 210 public LogFacade getLog() { 211 return log; 212 } 213 214 public void setLog(LogFacade log) { 215 this.log = log; 216 } 217 218}