/*
* Copyright 2010 Chad Retz
*
* Licensed 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.statmantis.xml;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.math.BigDecimal;
import java.sql.Date;
import java.sql.Time;
import java.sql.Timestamp;
import java.util.Collection;
import javax.persistence.Column;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.statmantis.BaseRuntimeException;
import org.statmantis.annotation.XmlInternalProperty;
import org.statmantis.annotation.XmlTopLevel;
import org.statmantis.model.Game;
import org.statmantis.util.ClassesInPackage;
import org.statmantis.util.StringUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import com.google.common.primitives.Primitives;
/**
* Creates schema from the model POJOs
*
* @author Chad Retz
*/
public class XmlSchemaBuilder {
private static final String XML_SCHEMA_NAMESPACE = "http://www.w3.org/2001/XMLSchema";
public Document buildXmlSchema(String namespace, boolean everythingOptional) {
//make the structure
Document doc;
try {
doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
} catch (ParserConfigurationException e) {
throw new BaseRuntimeException(e);
}
Element schema = doc.createElement("schema");
schema.setAttribute("xmlns", XML_SCHEMA_NAMESPACE);
schema.setAttribute("targetNamespace", namespace);
schema.setAttribute("elementFormDefault", "qualified");
schema.setAttribute("xmlns:bb", namespace);
doc.appendChild(schema);
//we loop through each class and make complex types
for (Class<?> clazz : Game.class.getPackage().getAnnotation(
ClassesInPackage.class).value()) {
if (!clazz.isInterface()) {
for (Element element : createComplexTypeFromClass(clazz, doc,
namespace, everythingOptional)) {
schema.appendChild(element);
}
}
}
//return
return doc;
}
private Element[] createComplexTypeFromClass(Class<?> clazz, Document doc,
String namespace, boolean everythingOptional) {
if (clazz.isEnum()) {
//this is a simple type extending string w/ an enumeration restriction
Element simpleType = doc.createElement("simpleType");
simpleType.setAttribute("name", StringUtils.toCamelCase(clazz.getSimpleName()));
//restriction
Element restriction = doc.createElement("restriction");
restriction.setAttribute("base", "string");
//add all enum values
for (Object constant : clazz.getEnumConstants()) {
Element enumeration = doc.createElement("enumeration");
enumeration.setAttribute("value", constant.toString());
restriction.appendChild(enumeration);
}
simpleType.appendChild(restriction);
return new Element[] { simpleType };
} else {
//all fields as either attributes or elements
Element complexType = doc.createElement("complexType");
complexType.setAttribute("name", StringUtils.toCamelCase(clazz.getSimpleName()));
Element choice = doc.createElement("choice");
choice.setAttribute("minOccurs", "0");
choice.setAttribute("maxOccurs", "unbounded");
complexType.appendChild(choice);
for (Field field : clazz.getDeclaredFields()) {
field.setAccessible(true);
//first, does it have an xml internal property?
Class<?> type = Primitives.wrap(field.getType());
//grab column reference to check nullable
Column column = field.getAnnotation(Column.class);
//check internal
XmlInternalProperty internal = field.getAnnotation(XmlInternalProperty.class);
if (internal != null) {
//k, it's a long then
Element attribute = doc.createElement("attribute");
attribute.setAttribute("name", internal.value());
attribute.setAttribute("type", getXmlType(Long.class));
if (!everythingOptional && column != null && !column.nullable()) {
attribute.setAttribute("use", "required");
}
complexType.appendChild(attribute);
} else if (!Collection.class.isAssignableFrom(type) &&
(!type.getPackage().equals(clazz.getPackage()) ||
type.isEnum())) {
//attribute worthy
Element attribute = doc.createElement("attribute");
attribute.setAttribute("name", field.getName());
attribute.setAttribute("type", getXmlType(type));
if (!everythingOptional && column != null && !column.nullable()) {
attribute.setAttribute("use", "required");
}
complexType.appendChild(attribute);
} else {
Element element = doc.createElement("element");
if (column == null || column.nullable()) {
element.setAttribute("minOccurs", "0");
} else {
element.setAttribute("minOccurs", "1");
}
if (Collection.class.isAssignableFrom(type)) {
//collection? k, get the parameter type and work w/ that
type = Primitives.wrap((Class<?>) ((ParameterizedType) field.getGenericType()).
getActualTypeArguments()[0]);
element.setAttribute("maxOccurs", "unbounded");
}
element.setAttribute("name", field.getName());
element.setAttribute("type", getXmlType(type));
choice.appendChild(element);
}
}
if (!choice.hasChildNodes()) {
//no children? remove the choice
complexType.removeChild(choice);
}
//top level?
if (clazz.isAnnotationPresent(XmlTopLevel.class)) {
Element element = doc.createElement("element");
String name = StringUtils.toCamelCase(clazz.getSimpleName());
element.setAttribute("name", name);
element.setAttribute("type", "bb:" + name);
return new Element[] { element, complexType };
} else {
return new Element[] { complexType };
}
}
}
private String getXmlType(Class<?> clazz) {
if (clazz.equals(String.class)) {
return "string";
} else if (clazz.equals(Long.class)) {
return "long";
} else if (clazz.equals(Integer.class)) {
return "int";
} else if (clazz.equals(Boolean.class)) {
return "boolean";
} else if (clazz.equals(Float.class)) {
return "float";
} else if (clazz.equals(BigDecimal.class)) {
return "decimal";
} else if (clazz.equals(Date.class)) {
return "date";
} else if (clazz.equals(Time.class)) {
return "time";
} else if (clazz.equals(Timestamp.class)) {
return "dateTime";
} else {
return "bb:" + StringUtils.toCamelCase(clazz.getSimpleName());
}
}
}