All Downloads are FREE. Search and download functionalities are using the official Maven repository.

redora.generator.ModelProcessor Maven / Gradle / Ivy

/*
 * Copyright 2009-2010 Nanjing RedOrange ltd (http://www.red-orange.cn)
 *
 * 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 redora.generator;

import org.apache.commons.io.IOUtils;
import org.dom4j.Element;
import org.jetbrains.annotations.NotNull;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import redora.util.DefaultErrorHandler;
import redora.util.SchemaValidator;

import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;
import java.util.logging.Logger;

import static java.io.File.separator;
import static java.util.logging.Level.SEVERE;
import static javax.xml.xpath.XPathConstants.NODESET;
import static org.w3c.dom.Node.ELEMENT_NODE;
import static redora.generator.Template.Input.*;
import static redora.generator.Template.Input.Enum;
import static redora.generator.XMLUtil.*;

/**
 * Retrieves all available models from the file system in this project and
 * generated all the sources and resources with the available templates. The
 * maven plugin will use the ModelProcessor directly, so just add the maven
 * plugin to your project's plugins.
 * 
 * @author Nanjing RedOrange (http://www.red-orange.cn)
 */
public class ModelProcessor {

    /**
     * Schema file for checking if the model document is correct.
     */
    public static final String MODEL_SCHEMA = "/model.xsd";
    /**
     * Schema file for checking if the application document is correct.
     */
    public static final String APPLICATION_SCHEMA = "/application.xsd";
    /**
     * Schema file for checking if the include document is correct.
     */
    public static final String INCLUDE_SCHEMA = "/include.xsd";

    final FileLocations where;
    final GeneratorTemplate templateTemplate;
    final TemplateProcessor templateProcessor;
    final ModelFileFinder finder;
    final String basePackage;
    final String defaultLanguage;
    final String artifactId;
    final Document allModels;
    Document model;
    final Normalizer normalizer;

    // Useful lists
    final Set sortedModels = new HashSet();
    final Map models = new HashMap();

    /**
     * @param where (Mandatory)
     * @param basePackage (Mandatory) From Maven pom, like 'com.company'
     * @param artifactId (Mandatory) Project name, from maven pom
     * @param defaultLanguage (Mandatory) From Maven pom, from there it defaults to 'en'.
     * @throws ModelGenerationException Passing on
     */
    public ModelProcessor(@NotNull FileLocations where, @NotNull String basePackage
                        , @NotNull String artifactId, @NotNull String defaultLanguage)
                throws ModelGenerationException {
        this.where = where;
        this.basePackage = basePackage;
        this.defaultLanguage = defaultLanguage;
        this.artifactId = artifactId;
        allModels = newDocument(basePackage, "all");
        finder = new ModelFileFinder(where);
        templateTemplate = new GeneratorTemplate(where.resourceDir);
        normalizer = new Normalizer(basePackage);
        this.templateProcessor = new TemplateProcessor(where);
    }

    /**
     * Generate sources for project
     * @throws ModelGenerationException The only exception you could get
     */
    public void generate() throws ModelGenerationException {

        validateAndLoadModels();
        sortedModels();
        int sequence = 0;
        //Normalize all models (mainly add redundancy so the templates are easier to make), and fill AllModels
        for (Map.Entry entry : models.entrySet()) {
            model = entry.getValue(); //keep track of this so you can make nicer exception messages
            normalizer.normalize(model, entry.getKey(), sortedModels, sequence++);
            allModels.getFirstChild()
                    .appendChild(allModels.importNode(model.getFirstChild(), true));
        }
        model = null;

        upgradeFiles();
        loadApplication();
        loadLanguages();
        loadSchemas();
        globalEnums();

        dumpModelToLocalFile();
        dumpAllModelsToLocalFile();

        //Run the per model generation stuff
        for (Map.Entry entry : models.entrySet()) {
            model = entry.getValue();
            for (Template tpl : templateTemplate.byInput(Model)) {
                if (tpl.ignoreProjects == null || !tpl.ignoreProjects.contains(artifactId)) {
                    templateProcessor.process(tpl, model, basePackage + "." + tpl.packageSuffix,
                            tpl.getDestinationFileName(entry.getKey(), null, null), null, artifactId);
                } else {
                    System.out.println("Ignoring " + tpl.name + " for " + artifactId);
                }
            }
            model = null;
        }
        //Run the AllModels
        for (Template tpl : templateTemplate.byInput(AllModels)) {
            if (tpl.ignoreProjects == null || !tpl.ignoreProjects.contains(artifactId)) {
                templateProcessor.process(tpl, allModels, basePackage + "." + tpl.packageSuffix,
                        tpl.getDestinationFileName(null, null, null), null, artifactId);
            } else {
                System.out.println("Ignoring " + tpl.name + " for " + artifactId);
            }
        }

        for (Map.Entry entry : enumerations(allModels, basePackage).entrySet()) {
            System.out.println("Processing enum " + entry.getKey());
            for (Template tpl : templateTemplate.byInput(Enum)) {
                if (tpl.ignoreProjects == null || !tpl.ignoreProjects.contains(artifactId)) {
                    templateProcessor.process(tpl, entry.getValue(), basePackage + "."
                            + tpl.packageSuffix, tpl.getDestinationFileName(null, null, entry
                            .getKey()), null, artifactId);
                } else {
                    System.out.println("Ignoring " + tpl.name + " for " + artifactId);
                }
            }
        }

        for (String language : definedLanguages(allModels)) {
            Map langParam = new HashMap();
            langParam.put("language", language);
            System.out.println("Processing language " + language);
            for (Template tpl : templateTemplate.byInput(Language)) {
                if (tpl.ignoreProjects == null || !tpl.ignoreProjects.contains(artifactId)) {
                    templateProcessor.process(tpl, allModels,
                            basePackage + "." + tpl.packageSuffix, tpl.getDestinationFileName(null,
                                    language, null), langParam, artifactId);
                } else {
                    System.out.println("Ignoring " + tpl.name + " for " + artifactId);
                }
            }
        }
    }

    /**
     * Adds if exists the application.xml to allModels
     * 
     * @throws ModelGenerationException Usually if templates.xml isn't valid XML
     */
    private void loadApplication() throws ModelGenerationException {
        File application = new File(where.applicationFile);
        if (application.exists()) {
            Document doc;
            try {
                doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(application);
            } catch (Exception e) {
                throw new ModelGenerationException(
                        "The application file is not parseable, probably not valid XML.", e);
            }
            allModels.getFirstChild().appendChild(allModels.importNode(doc.getFirstChild(), true));
        }
    }

    /**
     * Loads model from file system. Does the necessary preparation: adding
     * includes and normalizes it. The model is added to allModels.
     * 
     * @param modelFile
     *            (Mandatory)
     * @return The ready to use Document of given modelFile
     * @throws ModelGenerationException
     *             Impossible, when the model is not valid XML.
     */
    @NotNull
    Document loadModel(@NotNull String modelFile) throws ModelGenerationException {
        Document retVal;
        try {
            retVal = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(
                    new File(where.modelDir, modelFile));
        } catch (Exception e) {
            throw new ModelGenerationException("The model " + modelFile
                    + " is not parseable, probably not valid XML.", e);
        }

        loadIncludes(retVal);

        return retVal;
    }

    /**
     * Check if the model files comply with model.xsd. And check if the include files comply with include.xsd.
     * Check if the application file complies with application.xsd.
     * 
* Load the model into a Document and add it to {@link #models}. While loading replace any include tags with * their corresponding include document. * * @throws ModelGenerationException When a model or include file does not comply, this exception is thrown. */ void validateAndLoadModels() throws ModelGenerationException { // Check the application file, if there is one File applicationFile = new File(where.applicationFile); if (applicationFile.exists()) { if (schemaValidation(APPLICATION_SCHEMA, applicationFile)) { throw new ModelGenerationException("Model file " + applicationFile + " failed the schema validation, i will stop now." + ModelGenerationException.printModel(where, applicationFile.getName())); } } // Check the model files and add it to models list for (String modelFile : finder.findModelFiles()) { if (schemaValidation(MODEL_SCHEMA, new File(where.modelDir, modelFile))) { throw new ModelGenerationException("Model file " + modelFile + " failed the schema validation, i will stop now." + ModelGenerationException.printModel(where, modelFile)); } models.put(modelFile.replace(".xml", ""), loadModel(modelFile)); } System.out.println("All model files were checked as valid against model.xsd"); for (String includeFile : finder.findIncludeFiles()) { if (schemaValidation(INCLUDE_SCHEMA, new File(where.includeDir, includeFile))) { throw new ModelGenerationException("Include file " + includeFile + " failed the schema validation, i will stop now." + ModelGenerationException.printInclude(where, includeFile)); } } if (!finder.findIncludeFiles().isEmpty()) { System.out.println("All include files were checked as valid against include.xsd"); } } /** * Validate the xml file according to schema file MODEL_SCHEMA * * @param schema * (Mandatory) Name of the schema file, like 'model.xsd'. * @param testFile * (Mandatory) File you want to validate * @return True if the XML file does not match the XSD. * @throws ModelGenerationException Wrapping IO and XML Exceptions */ @SuppressWarnings("unchecked") public boolean schemaValidation(@NotNull String schema, @NotNull File testFile) throws ModelGenerationException { DefaultErrorHandler errorHandler = new DefaultErrorHandler(); SchemaValidator validator; try { if (schema.equalsIgnoreCase(INCLUDE_SCHEMA)) { String include = IOUtils.toString(ModelProcessor.class .getResourceAsStream(INCLUDE_SCHEMA)); String model = IOUtils.toString(ModelProcessor.class .getResourceAsStream(MODEL_SCHEMA)); InputStream includeStream = IOUtils.toInputStream(include.replace( "", model.substring(model .indexOf(""), model .lastIndexOf("")))); validator = new SchemaValidator(includeStream); } else { validator = new SchemaValidator(ModelProcessor.class .getResourceAsStream(MODEL_SCHEMA)); } } catch (SAXException e) { throw new ModelGenerationException("Failed to prepare validating file " + testFile.getAbsolutePath(), e); } catch (IOException e) { throw new ModelGenerationException("Failed to prepare validating file " + testFile.getAbsolutePath(), e); } try { validator.validate(testFile, errorHandler); } catch (SAXException e) { throw new ModelGenerationException("Model file " + testFile.getName() + " did not test nice. " + ModelGenerationException.printModel(where, testFile.getName()), e); } catch (IOException ex) { Logger.getLogger(ModelProcessor.class.getName()).log(SEVERE, null, ex); } if (errorHandler.getErrors().hasContent()) { System.out.println("XML file:" + testFile.getAbsolutePath() + " failed to do checking according to XSD file:"); Iterator ei = errorHandler.getErrors().elementIterator(); while (ei.hasNext()) { Element el = ei.next(); System.out.println(el.getStringValue()); } return true; } return false; } /** * Search the model for include tags. Then replace these tags with their * corresponding include files. * * @param model * (Mandatory) The model document * @exception ModelGenerationException Wrapping XML exceptions */ void loadIncludes(@NotNull Document model) throws ModelGenerationException { NodeList includes; //all the includes in the model try { includes = (NodeList) XPathFactory.newInstance().newXPath().evaluate("//include", model, NODESET); } catch (XPathExpressionException e) { throw new ModelGenerationException("Failed in searching include", e); } DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); // All includes are different because attributes can't be the same in a // model for (int i = 0; i < includes.getLength(); i++) { Node includeTag = includes.item(i); String includeFileName = where.includeDir + separator + includeTag.getAttributes().getNamedItem("name").getNodeValue() + ".xml"; System.out.println("Including " + includeFileName); Document includeDoc; try { includeDoc = factory.newDocumentBuilder().parse(new File(includeFileName)); } catch (SAXException e) { throw new ModelGenerationException("I failed to create a DOM of this include " + includeFileName, e); } catch (IOException e) { throw new ModelGenerationException("I failed to find this include file: " + includeFileName, e); } catch (ParserConfigurationException e) { throw new ModelGenerationException("I failed to create a DOM of this include " + includeFileName, e); } //Insert the attributes one by one in the model. Usually an include is just one attribute //but include.xsd allows more attributes. //The includes should be inserted on the same position as the include tag. for (int j = 0; j < includeDoc.getDocumentElement().getChildNodes().getLength(); j++) { Node insert = includeDoc.getFirstChild().getChildNodes().item(j); if (insert.getNodeType() == ELEMENT_NODE) { //Add any attributes from the include tag for (int h = 0; h < includeTag.getChildNodes().getLength(); h++) { Node attributeNode = includeTag.getChildNodes().item(h); if ("attribute".equals(attributeNode.getNodeName())) { attribute(insert , attributeNode.getAttributes().getNamedItem("name").getNodeValue() , attributeNode.getAttributes().getNamedItem("value").getNodeValue()); } } model.getElementsByTagName("attributes").item(0).insertBefore( model.importNode(insert, true), includes.item(i)); model.normalizeDocument(); } } model.getElementsByTagName("attributes").item(0).removeChild(includes.item(i)); } model.normalize(); } /** * Finds all different language=... in allModels and adds it as language * tags in allModels. Adds the defaultLanguage tag * * @throws ModelGenerationException Passing on */ void loadLanguages() throws ModelGenerationException { allModels.getFirstChild().appendChild( allModels.importNode(newDocument(null, "languages").getFirstChild(), true)); Node upgradeDoc = allModels.getElementsByTagName("languages").item(0); attribute(upgradeDoc, "defaultLanguage", defaultLanguage); for (String language : definedLanguages(allModels)) { Node tagNode = upgradeDoc.getOwnerDocument().createElement("language"); tagNode.setTextContent(language); upgradeDoc.appendChild(tagNode); } allModels.normalize(); } /** * Finds all different language=... in allModels and adds it as language * tags in allModels. Adds the defaultLanguage tag * * @throws ModelGenerationException Passing on */ void loadSchemas() throws ModelGenerationException { allModels.getFirstChild().appendChild( allModels.importNode(newDocument(null, "schemas").getFirstChild(), true)); Set schemas = new HashSet(); Node upgradeDoc = allModels.getElementsByTagName("languages").item(0); XPath xpath = XPathFactory.newInstance().newXPath(); NodeList schemaNodes; try { schemaNodes = (NodeList) xpath.evaluate("//schema", allModels, NODESET); } catch (XPathExpressionException e) { throw new ModelGenerationException("Filter on schema failed", e); } if (schemaNodes != null) for (int i = 0; i < schemaNodes.getLength(); i++) schemas.add(schemaNodes.item(i).getNodeValue()); for (String schema : schemas) { Node tagNode = upgradeDoc.getOwnerDocument().createElement("schema"); tagNode.setTextContent(schema); upgradeDoc.appendChild(tagNode); } allModels.normalize(); } /** * Make a list of all the models that have the sorted tag. Add it to the {@link #sortedModels} Set. * @throws ModelGenerationException Wrapping XPath exceptions */ void sortedModels() throws ModelGenerationException { for (Map.Entry entry : models.entrySet()) { model = entry.getValue(); try { if (isSortable(model)) { sortedModels.add(entry.getKey()); } } catch (XPathExpressionException e) { throw new ModelGenerationException("Can't define sorted for " + entry.getKey(), e); } } model = null; } /** * Makes a list of all the global enums, adds them to allModels in /all/globals. * @throws ModelGenerationException Wrapping XPath exceptions */ private void globalEnums() throws ModelGenerationException { XPath xpath = XPathFactory.newInstance().newXPath(); NodeList globals; try { globals = (NodeList)xpath.evaluate("//attributes/enum[@scope='global']", allModels, NODESET); } catch (XPathExpressionException e) { throw new ModelGenerationException("It seems to be a problem to create the globals list", e); } HashSet unique = new HashSet(); Document globalDoc = newDocument(null, "globals"); for (int i = 0; i < globals.getLength(); i++) { if (unique.add(globals.item(i).getAttributes().getNamedItem("class").getNodeValue())) { globalDoc.getFirstChild().appendChild(globalDoc.importNode(globals.item(i), true)); } } allModels.getFirstChild().appendChild( allModels.importNode(globalDoc.getFirstChild(), true)); } /** * Finds upgrade files in the upgrade directory and adds them to the * allModels document. * @throws ModelGenerationException Passing on */ private void upgradeFiles() throws ModelGenerationException { Document upgradeDoc = newDocument(null, "upgrades"); for (String file : finder.upgradeFiles()) { Node tagNode = upgradeDoc.createElement("upgrade"); tagNode.setTextContent(file); upgradeDoc.getFirstChild().appendChild(tagNode); } allModels.getFirstChild().appendChild( allModels.importNode(upgradeDoc.getFirstChild(), true)); } @NotNull public String dump() { String retVal = "Dumping model contents.\r\n"; if (model != null) { retVal += "Model:\r\n" + asString(model) + "\r\n"; } retVal += "Model:\r\n" + asString(allModels) + "\r\n"; return retVal; } public void dumpModelToLocalFile() throws ModelGenerationException { for (Map.Entry entry : models.entrySet()) { try { TransformerFactory tFactory = TransformerFactory.newInstance(); Transformer transformer = tFactory.newTransformer(); transformer.setOutputProperty("encoding", "utf-8"); DOMSource source = new DOMSource(entry.getValue()); File modelFile = new File(finder.modelFiles(entry.getKey(), artifactId)); StreamResult result = new StreamResult(modelFile); transformer.transform(source, result); } catch (TransformerException e) { throw new ModelGenerationException("Can't dumping model for " + entry.getKey(), e); } } } public void dumpAllModelsToLocalFile() throws ModelGenerationException { try { TransformerFactory tFactory = TransformerFactory.newInstance(); Transformer transformer = tFactory.newTransformer(); transformer.setOutputProperty("encoding", "utf-8"); DOMSource source = new DOMSource(allModels); File allModelFile = new File(finder.allModelFiles(artifactId)); StreamResult result = new StreamResult(allModelFile); transformer.transform(source, result); } catch (TransformerException e) { throw new ModelGenerationException("Can't dump allmodels", e); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy