
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