top.marchand.maven.gaulois.compiler.GCMojo Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of gaulois-compiler-maven-plugin Show documentation
Show all versions of gaulois-compiler-maven-plugin Show documentation
A maven plugin to compile gaulois-pipe config files and XSLT
/**
* Copyright © 2017, Christophe Marchand
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package top.marchand.maven.gaulois.compiler;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.channels.ReadableByteChannel;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.transform.Source;
import javax.xml.transform.SourceLocator;
import javax.xml.transform.TransformerException;
import javax.xml.transform.URIResolver;
import javax.xml.transform.sax.SAXSource;
import javax.xml.transform.stream.StreamSource;
import net.sf.saxon.s9api.Axis;
import net.sf.saxon.s9api.Destination;
import net.sf.saxon.s9api.MessageListener;
import net.sf.saxon.s9api.QName;
import net.sf.saxon.s9api.SaxonApiException;
import net.sf.saxon.s9api.Serializer;
import net.sf.saxon.s9api.TeeDestination;
import net.sf.saxon.s9api.XdmAtomicValue;
import net.sf.saxon.s9api.XdmDestination;
import net.sf.saxon.s9api.XdmNode;
import net.sf.saxon.s9api.XdmSequenceIterator;
import net.sf.saxon.s9api.XdmValue;
import net.sf.saxon.s9api.XsltExecutable;
import net.sf.saxon.s9api.XsltTransformer;
import net.sf.saxon.trans.XPathException;
import org.apache.maven.artifact.DependencyResolutionRequiredException;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugin.logging.Log;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.plugins.annotations.ResolutionScope;
import org.apache.maven.project.MavenProject;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLFilter;
import org.xml.sax.XMLReader;
import org.xml.sax.ext.EntityResolver2;
import org.xml.sax.helpers.ParserAdapter;
import org.xml.sax.helpers.XMLFilterImpl;
import top.marchand.maven.gaulois.compiler.utils.GauloisConfigScanner;
import top.marchand.maven.gaulois.compiler.utils.GauloisSet;
import top.marchand.maven.gaulois.compiler.utils.GauloisXsl;
import top.marchand.maven.saxon.utils.SaxonOptions;
import top.marchand.xml.maven.plugin.xsl.AbstractCompiler;
@Mojo(name="gaulois-compiler", defaultPhase = LifecyclePhase.COMPILE, requiresDependencyResolution = ResolutionScope.COMPILE)
public class GCMojo extends AbstractCompiler {
/**
* The directory containing generated classes of the project being tested.
* This will be included after the test classes in the test classpath.
*/
@Parameter( defaultValue = "${project.build.outputDirectory}" )
private File classesDirectory;
/**
* List of gauloisPipeFileset
*/
@Parameter
List gauloisPipeFilesets;
/**
* The catalog file to use to compile
*/
@Parameter
private File catalog;
@Parameter(defaultValue = "${project.basedir}")
private File projectBaseDir;
@Parameter( defaultValue = "${project}", readonly = true, required = true )
private MavenProject project;
/**
* The directory where imported schemas will be copied to. Be aware that
* if your schema structure uses relatives parent (../xxx) location, no
* file could be copied outside of ${project.build.outputDirectory}
*/
@Parameter (defaultValue = "${project.build.outputDirectory}/gc/schemas")
private File schemasDestination;
/**
* A XSL to post-compile the gaulois-pipe config file, if required
*/
@Parameter
private File postCompiler;
/**
* Saxon options, to configure Saxon.
* See {@linkplain https://github.com/cmarchand/saxonOptions-mvn-plug-utils/wiki}
*/
@Parameter
SaxonOptions saxonOptions;
private XsltExecutable postCompilerXsl;
private XsltExecutable gauloisCompilerXsl;
private XsltExecutable xutScanner;
private XsltExecutable xutFilter;
/**
* The list of directories where XSL sources are located in
*/
@Parameter
List xslSourceDirs;
// inner working variables
private Set gauloisSets;
private Map foundXsls;
public static final SAXParserFactory PARSER_FACTORY = SAXParserFactory.newInstance();
private ArrayList classpaths;
private static final String XUT_NS = "https://github.com/mricaud/xml-utilities";
private static final QName QN_DEP_TYPE = new QName("dependency-type");
private static final QName QN_URI = new QName("uri");
private static final QName QN_ABS_URI = new QName("abs-uri");
private static final QName QN_NAME = new QName("name");
private static final QName QN_PARAM_SCHEMAS = new QName("schemas");
@Override
public void execute() throws MojoExecutionException, MojoFailureException {
if(gauloisPipeFilesets==null) {
getLog().error(LOG_PREFIX+"\n"+ERROR_MESSAGE);
throw new MojoExecutionException(ERROR_MESSAGE);
}
if(xslSourceDirs==null) xslSourceDirs = new ArrayList<>();
if(xslSourceDirs.isEmpty()) {
xslSourceDirs.add(new File(projectBaseDir, "src/main/xsl"));
}
Log log = getLog();
try {
initSaxon();
} catch(XPathException ex) {
getLog().error("while configuring saxon:",ex);
}
loadClasspath();
gauloisSets = new TreeSet<>();
foundXsls = new HashMap<>();
ThreadLocal th = new ThreadLocal<>();
th.set(getEntityResolver());
getLog().warn(LOG_PREFIX+getXsltCompiler().getProcessor().getUnderlyingConfiguration().getSourceParserClass());
try {
URL url = getClass().getResource("/org/mricaud/xml-utilities/get-xml-file-static-dependency-tree.xsl");
Source src = new StreamSource(url.openStream());
// we have to force the system ID
src.setSystemId(url.toExternalForm());
xutScanner = getXsltCompiler().compile(src);
xutFilter = getXsltCompiler().compile(new StreamSource(getClass().getResource("/top/marchand/maven/gaulois/compiler/schema-filter.xsl").openStream()));
} catch(SaxonApiException | IOException ex) {
throw new MojoFailureException("while compiling xut xsl", ex);
}
Path targetDir = classesDirectory.toPath();
boolean hasError = false;
getLog().debug(LOG_PREFIX+" looking for gaulois-pipe config files");
for(FileSet fs: gauloisPipeFilesets) {
if(fs.getUri()!=null && !fs.getUri().isEmpty()) {
try {
Source source = compiler.getURIResolver().resolve(fs.getUri(), null);
String sPath = fs.getUriPath();
getLog().debug(LOG_PREFIX+" sPath="+sPath);
Path targetPath = targetDir.resolve(sPath).getParent();
getLog().debug(LOG_PREFIX+" targetPath="+targetPath.toString());
String sourceFileName = sPath.substring(sPath.lastIndexOf("/")+1);
if(sourceFileName.contains("?")) {
sourceFileName = sourceFileName.substring(0, sourceFileName.indexOf("?")-1);
}
// we keep the same extension for gaulois config files
File targetFile = targetPath.resolve(sourceFileName).toFile();
getLog().debug(LOG_PREFIX+" targetFile="+targetFile.getAbsolutePath());
hasError |= scanGauloisFile(source, targetFile, targetDir);
} catch(TransformerException| URISyntaxException ex) {
hasError = true;
getLog().error("while parsing "+fs.getUri(), ex);
}
} else {
List pathes = fs.getFiles(projectBaseDir, log);
// this must be call after the call to fs.getFiles, as fs.dir is modified by fs.getFiles
Path basedir = new File(fs.getDir()).toPath();
getLog().debug(LOG_PREFIX+"looking in "+basedir.toString());
for(Path p: pathes) {
getLog().debug(LOG_PREFIX+"found "+p.toString());
File sourceFile = basedir.resolve(p).toFile();
Path targetPath = p.getParent()==null ? targetDir : targetDir.resolve(p.getParent());
String sourceFileName = sourceFile.getName();
// we keep the same extension for gaulois config files
File targetFile = targetPath.resolve(sourceFileName).toFile();
try {
hasError |= scanGauloisFile(sourceFile, targetFile, targetDir);
} catch(FileNotFoundException | URISyntaxException | MalformedURLException ex) {
// it can not be thrown but we are required to catch it
hasError = true;
getLog().error(LOG_PREFIX+"while parsing "+p.toString(), ex);
}
}
}
}
if(!hasError) {
for(String xslSystemId: foundXsls.keySet()) {
try {
getLog().debug(LOG_PREFIX+" compiling "+xslSystemId);
Source xslSource = new StreamSource(xslSystemId);
File targetFile = foundXsls.get(xslSystemId).getTargetFile();
compileFile(xslSource, targetFile);
} catch (FileNotFoundException | SaxonApiException ex) {
getLog().warn(LOG_PREFIX+" while compiling "+xslSystemId, ex);
hasError = true;
}
}
Source xsl = new StreamSource(this.getClass().getResourceAsStream("/top/marchand/maven/gaulois/compiler/gaulois-compiler.xsl"));
try {
gauloisCompilerXsl = getXsltCompiler().compile(xsl);
for(GauloisSet gs: gauloisSets) {
getLog().debug(LOG_PREFIX+" compiling "+gs.getGauloisConfigSystemId());
// passer ici les schemas à déclarer
compileGaulois(new StreamSource(gs.getGauloisConfigSystemId()), gs.getTargetFile(), gs.getAllSchemas());
}
} catch(SaxonApiException ex) {
hasError = true;
getLog().error(ex);
}
} else {
getLog().warn(LOG_PREFIX+" Errors occured");
}
}
private static final String LOG_PREFIX = "[gaulois-compiler]";
private static final String ERROR_MESSAGE = "\n\t\n\t\tsrc/main/xsl... \n\t \n \n is required in gaulois-compiler-maven-plugin configuration";
@Override
public File getCatalogFile() {
return catalog;
}
/**
* Scans a gaulois config file to extract all xslt files, and store them into xslToCompile
* xslt/@href MUST be an absolute URI, in cp:/ protocol.
* Else, the whole gaulois-pipe config file is ignored.
* @param sourceFile The file to scan. It MUST be a gaulois config file.
* @param targetFile The target file where scanned file will be stored.
* @param targetDir The build dir
* @return false if an error occured
* @throws java.io.FileNotFoundException If a file is not found. Should never be thrown.
* @throws java.net.URISyntaxException If any URI is incorrect
* @throws java.net.MalformedURLException Should never occurs
*/
protected boolean scanGauloisFile(File sourceFile, File targetFile, Path targetDir) throws FileNotFoundException, URISyntaxException, MalformedURLException {
SAXSource source = new SAXSource(new InputSource(new FileInputStream(sourceFile)));
source.setSystemId(sourceFile.toURI().toURL().toExternalForm());
return scanGauloisFile(source, targetFile, targetDir);
}
protected boolean scanGauloisFile(Source source, File targetFile, Path targetDir) throws URISyntaxException {
try {
final XMLReader reader = new ParserAdapter(PARSER_FACTORY.newSAXParser().getParser());
final GauloisConfigScanner scanner = new GauloisConfigScanner(xslSourceDirs, classesDirectory, getUriResolver(), getLog(), classpaths);
XMLFilter filter = new XMLFilterImpl(reader) {
@Override
public void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException {
super.startElement(uri, localName, qName, atts);
scanner.startElement(uri, localName, qName, atts);
}
};
// use systemId to create a new InputSource, and to keep the Source not consumed
filter.parse(source.getSystemId());
if(scanner.hasErrors()) {
for(String errorMsg: scanner.getErrorMessages()) {
getLog().error(errorMsg);
}
} else {
GauloisSet set = new GauloisSet(source.getSystemId(), targetFile);
if(!gauloisSets.contains(set)) {
gauloisSets.add(set);
for(Source xslSource: scanner.getXslToCompile().keySet()) {
GauloisXsl xsl = foundXsls.get(xslSource.getSystemId());
if(xsl==null) {
xsl = new GauloisXsl(xslSource.getSystemId(), scanner.getXslToCompile().get(xslSource));
foundXsls.put(xslSource.getSystemId(), xsl);
scanForSchemas(xsl);
}
set.getXsls().add(xsl);
}
}
}
return scanner.hasErrors();
} catch(ParserConfigurationException | SAXException | SaxonApiException | IOException ex) {
getLog().error("while scanning "+source.getSystemId(), ex);
return true;
}
}
protected void compileGaulois(Source source, File target, Set schemas) throws SaxonApiException {
XsltTransformer tr = gauloisCompilerXsl.load();
// TODO: set parameter for schemas
ArrayList values = new ArrayList<>();
for(String schema:schemas) {
getLog().info(LOG_PREFIX+target.getName()+" has schema: "+schema);
values.add(new XdmAtomicValue(schema));
}
XdmValue sequence = new XdmValue(values);
tr.setParameter(QN_PARAM_SCHEMAS, sequence);
XsltTransformer first = tr;
// post compiler ?
XsltTransformer pc = getPostCompiler();
if(pc!=null) {
tr.setDestination(pc);
tr = pc;
}
Serializer ser = getProcessor().newSerializer(target);
tr.setDestination(ser);
XdmNode sourceNode = getBuilder().build(source);
first.setInitialContextNode(sourceNode);
first.transform();
first.close();
}
protected XsltTransformer getPostCompiler() {
if(postCompilerXsl==null && postCompiler!=null && postCompiler.exists() && postCompiler.isFile()) {
try {
postCompilerXsl = getXsltCompiler().compile(new StreamSource(new FileInputStream(postCompiler)));
} catch(SaxonApiException | FileNotFoundException ex) {
getLog().error("while compiling post-compiler "+postCompiler.getAbsolutePath(), ex);
}
}
return postCompilerXsl==null ? null : postCompilerXsl.load();
}
protected void scanForSchemas(GauloisXsl xsl) throws SaxonApiException, URISyntaxException, IOException {
XsltTransformer xut = xutScanner.load();
xut.setMessageListener(new NullMessageListener());
XsltTransformer filter = xutFilter.load();
xut.setDestination(filter);
XdmDestination dest = new XdmDestination();
Serializer console = getProcessor().newSerializer(System.out);
console.setOutputProperty(Serializer.Property.INDENT, "yes");
TeeDestination tee = new TeeDestination(console, dest);
filter.setDestination(tee);
XdmNode xslDocument = getBuilder().build(new StreamSource(xsl.getXslSystemId()));
xut.setInitialContextNode(xslDocument);
xut.setParameter(new QName(XUT_NS, "xut:get-xml-file-static-dependency-tree.filterDuplicatedDependencies"), new XdmAtomicValue(true));
xut.transform();
XdmNode dependencies = dest.getXdmNode();
// now, walk through dependencies
// all first-level childs are imported schemas
XdmNode file = (XdmNode)(dependencies.axisIterator(Axis.CHILD).next());
XdmSequenceIterator xsi = file.axisIterator(Axis.CHILD);
while(xsi.hasNext()) {
exploreFile(xsl, (XdmNode)(xsi.next()));
}
}
private void exploreFile(GauloisXsl xsl, XdmNode node) throws URISyntaxException, IOException {
String dependencyType = node.getAttributeValue(QN_DEP_TYPE);
if(dependencyType.equals("xsl:import-schema")) { // always true, but for documentation
String uri = node.getAttributeValue(QN_URI);
String absUri = node.getAttributeValue(QN_ABS_URI);
String name = node.getAttributeValue(QN_NAME);
SchemaTarget targetSchema = getTargetSchemaFile(name, absUri);
xsl.getSchemas().add(targetSchema.getAccessUri());
getLog().debug(LOG_PREFIX+"\turi is "+absUri);
copyUriToFile(absUri, targetSchema.getFileLocation());
XdmSequenceIterator it = node.axisIterator(Axis.CHILD);
while(it.hasNext()) {
XdmNode schemaNode = (XdmNode)it.next();
copySubSchema(targetSchema.getFileLocation(), schemaNode);
}
}
}
private void copySubSchema(File parent, XdmNode schemaNode) throws URISyntaxException, IOException {
String uri = schemaNode.getAttributeValue(QN_URI);
String absUri = schemaNode.getAttributeValue(QN_ABS_URI);
File schemaFile = parent.toPath().resolve(uri).toFile();
copyFile(new File(new URI(absUri)), schemaFile);
XdmSequenceIterator it = schemaNode.axisIterator(Axis.CHILD);
while(it.hasNext()) {
XdmNode subSchemaNode = (XdmNode)it.next();
copySubSchema(schemaFile, subSchemaNode);
}
}
private SchemaTarget getTargetSchemaFile(String name, String absUri) {
File destSchema = new File(getSchemasDestination(), name);
Path p = classesDirectory.toPath().relativize(destSchema.toPath());
String accessUri = "cp:/"+p.toString();
return new SchemaTarget(accessUri, destSchema);
}
private void copyFile(File source, File dest) throws IOException {
dest.getParentFile().mkdirs();
try (
FileChannel in = new FileInputStream(source).getChannel();
FileChannel out = new FileOutputStream(dest).getChannel()) {
in.transferTo (0, in.size(), out);
}
}
private void copyUriToFile(String uri, File dest) throws IOException, URISyntaxException {
dest.getParentFile().mkdirs();
URL url = new URI(uri).toURL();
InputStream is = url.openStream();
try (
ReadableByteChannel in = Channels.newChannel(is);
FileChannel out = new FileOutputStream(dest).getChannel()) {
final long size = 5*1024;
long offset = 0;
long vol = out.transferFrom(in, 0, size);
while(vol==size) {
offset+=vol;
vol = out.transferFrom(in, offset, size);
}
}
}
private void loadClasspath() {
try {
classpaths = new ArrayList<>(project.getCompileClasspathElements().size());
for(Object i:project.getCompileClasspathElements()) {
File f = new File(i.toString());
classpaths.add(f.toURI().toString());
}
getLog().debug(LOG_PREFIX+"classpaths="+classpaths);
} catch(DependencyResolutionRequiredException ex) {
getLog().error(LOG_PREFIX+ex.getMessage(),ex);
}
}
/**
* Returns the schemas destination
* @return The schemas destination directory
*/
public File getSchemasDestination() {
return schemasDestination;
}
/**
* A class to store a schema location.
*/
private class SchemaTarget {
private final String accessUri;
private final File fileLocation;
public SchemaTarget(final String accessUri, final File fileLocation) {
super();
this.accessUri = accessUri;
this.fileLocation = fileLocation;
}
public String getAccessUri() {
return accessUri;
}
public File getFileLocation() {
return fileLocation;
}
}
@Override
public SaxonOptions getSaxonOptions() {
return saxonOptions;
}
private class NullMessageListener implements MessageListener {
@Override
public void message(XdmNode xn, boolean bln, SourceLocator sl) {}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy