/** * Copyright (c) 2009--2014 Red Hat, Inc. * * This software is licensed to you under the GNU General Public License, * version 2 (GPLv2). There is NO WARRANTY for this software, express or * implied, including the implied warranties of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2 * along with this software; if not, see * http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. * * Red Hat trademarks are not licensed under GPLv2. No permission is * granted to use or replicate Red Hat trademarks that are incorporated * in this software or its documentation. */ package com.redhat.rhn.common.validator; import org.apache.log4j.Logger; import org.jdom.Document; import org.jdom.Element; import org.jdom.JDOMException; import org.jdom.Namespace; import org.jdom.input.SAXBuilder; import java.io.IOException; import java.net.URL; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; /** * <p> * The <code>SchemaParser</code> class parses an XML Schema and creates * <code>{@link Constraint}</code> objects from it. * </p> * @version $Rev$ */ public class SchemaParser { private static Logger log = Logger.getLogger(SchemaParser.class); /** The URL of the schema to parse */ private URL schemaURL; /** The constraints from the schema */ private Map<String, Constraint> constraints; /** XML Schema Namespace */ private Namespace schemaNamespace; /** XML Schema Namespace URI */ private static final String SCHEMA_NAMESPACE_URI = "http://www.w3.org/1999/XMLSchema"; /** * <p> * This will create a new <code>SchemaParser</code>, given * the URL of the schema to parse. * </p> * * @param schemaURLIn the <code>URL</code> of the schema to parse. * @throws IOException when parsing errors occur. */ public SchemaParser(URL schemaURLIn) throws IOException { this.schemaURL = schemaURLIn; constraints = new LinkedHashMap<String, Constraint>(); schemaNamespace = Namespace.getNamespace(SCHEMA_NAMESPACE_URI); // Parse the schema and prepare constraints parseSchema(); } /** * <p> * This will return constraints found within the document. * </p> * * @return <code>Map</code> - the schema-defined constraints. */ public Map<String, Constraint> getConstraints() { return constraints; } /** * <p> * This will get the <code>Constraint</code> object for * a specific constraint name. If none is found, this * will return <code>null</code>. * </p> * * @param constraintName name of constraint to look up. * @return <code>Constraint</code> - constraints for * supplied name. */ public Constraint getConstraint(String constraintName) { Object o = constraints.get(constraintName); if (o != null) { return (Constraint)o; } return null; } /** * <p> * This will do the work of parsing the schema. * </p> * * @throws IOException - when parsing errors occur. */ private void parseSchema() throws IOException { /** * Create builder to generate JDOM representation of XML Schema, * without validation and using Apache Xerces. */ // XXX: Allow validation, and allow alternate parsers SAXBuilder builder = new SAXBuilder(); try { Document schemaDoc = builder.build(schemaURL); // Handle attributes List attributes = schemaDoc.getRootElement() .getChildren("attribute", schemaNamespace); for (Iterator i = attributes.iterator(); i.hasNext();) { // Iterate and handle Element attribute = (Element)i.next(); handleAttribute(attribute); } // Handle attributes nested within complex types } catch (JDOMException e) { throw new IOException(e.getMessage()); } } /** * <p> * This will convert an attribute into constraints. * TODO: make everyone happy: replace this with Digester * </p> * * @throws IOException - when parsing errors occur. */ private void handleAttribute(Element attribute) throws IOException { // Get the attribute name and create a Constraint String name = attribute.getAttributeValue("name"); if (name == null) { throw new IOException("All schema attributes must have names."); } // Get the simpleType - if none, we are done with this attribute Element simpleType = attribute.getChild("simpleType", schemaNamespace); if (simpleType == null) { return; } // Handle the data type String schemaType = simpleType.getAttributeValue("baseType"); if (schemaType == null) { throw new IOException("No data type specified for constraint " + name); } Constraint constraint; // = new Constraint(name); Element child; if (schemaType.equals("long") || schemaType.equals("int")) { NumericConstraint nc = new NumericConstraint(name); nc.setOptional(parseOptional(simpleType)); processRequiredIfConstraint(simpleType, nc); // Handle ranges child = simpleType.getChild("minInclusive", schemaNamespace); if (child != null) { Double value = new Double(child.getAttributeValue("value")); nc.setMinInclusive(value); } child = simpleType.getChild("maxInclusive", schemaNamespace); if (child != null) { Double value = new Double(child.getAttributeValue("value")); nc.setMaxInclusive(value); } constraint = nc; } else if (schemaType.equals("string")) { StringConstraint lc = new StringConstraint(name); lc.setOptional(parseOptional(simpleType)); processRequiredIfConstraint(simpleType, lc); child = simpleType.getChild("ascii", schemaNamespace); if (child != null) { lc.setASCII(true); } child = simpleType.getChild("username", schemaNamespace); if (child != null) { lc.setUserName(true); } child = simpleType.getChild("posix", schemaNamespace); if (child != null) { lc.setPosix(true); } child = simpleType.getChild("maxLength", schemaNamespace); if (child != null) { Double value = new Double(child.getAttributeValue("value")); lc.setMaxLength(value); } child = simpleType.getChild("minLength", schemaNamespace); if (child != null) { Double value = new Double(child.getAttributeValue("value")); lc.setMinLength(value); } child = simpleType.getChild("matchesExpression", schemaNamespace); if (child != null) { String value = new String(child.getAttributeValue("value")); lc.setRegEx(value); } constraint = lc; } else { constraint = new ParsedConstraint(name); } constraint.setDataType(DataConverter.getInstance().getJavaType(schemaType)); // Store this constraint log.debug("Adding: constraint name: " + name + " datatype: " + constraint.getDataType()); constraints.put(name, constraint); } private Boolean parseOptional(Element simpleType) { Element child; Boolean optional = Boolean.FALSE; child = simpleType.getChild("optional", schemaNamespace); if (child != null) { String value = child.getAttributeValue("value"); if (value == null) { // <optional/> consider as true optional = Boolean.TRUE; } else { optional = Boolean.valueOf(value); } } return optional; } private void processRequiredIfConstraint(Element simpleType, RequiredIfConstraint lc) { List requiredIfFields = simpleType.getChildren("requiredIf", schemaNamespace); if (requiredIfFields != null && requiredIfFields.size() > 0) { for (Iterator i = requiredIfFields.iterator(); i.hasNext();) { Element requiredIf = (Element)i.next(); String fieldName = requiredIf.getAttributeValue("field"); String fieldValue = requiredIf.getAttributeValue("value"); lc.addField(fieldName, fieldValue); } } } }