/* * $Id$ * * License Agreement. * * Rich Faces - Natural Ajax for Java Server Faces (JSF) * * Copyright (C) 2007 Exadel, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License version 2.1 as published by the Free Software Foundation. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ package org.richfaces.builder.mojo; import java.io.File; import java.net.MalformedURLException; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; import org.apache.maven.model.FileSet; import org.apache.maven.model.Resource; import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoFailureException; import org.apache.maven.plugins.annotations.LifecyclePhase; import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.ResolutionScope; import org.apache.maven.project.MavenProject; import org.codehaus.plexus.util.DirectoryScanner; import org.richfaces.builder.maven.MavenLogger; import org.richfaces.cdk.CdkClassLoader; import org.richfaces.cdk.CdkException; import org.richfaces.cdk.Generator; import org.richfaces.cdk.Logger; import org.richfaces.cdk.Outputs; import org.richfaces.cdk.Sources; import org.richfaces.cdk.apt.CdkProcessorImpl; import org.richfaces.cdk.apt.LibraryCache; import org.richfaces.cdk.model.Cacheable; import org.apache.maven.plugins.annotations.Parameter; import com.google.common.collect.Maps; /** * @author asmirnov@exadel.com */ @Mojo(name="generate", defaultPhase = LifecyclePhase.GENERATE_SOURCES, requiresDependencyResolution= ResolutionScope.COMPILE) public class GenerateMojo extends AbstractMojo { private static final String[] JAVA_INCLUDES = new String[] { "**/*.java" }; private static final String MAIN_CONFIG = "src/main/config"; private static final String MAIN_TEMPLATES = "src/main/templates"; private static final String[] STRINGS_ARRAY = new String[0]; private static final String XML_INCLUDES = "**/*.xml"; /** * Project classpath. */ @Parameter(property="project.compileClasspathElements", readonly = true, required = true) protected List<String> classpathElements; /** * The source directories containing the sources to be compiled. */ @Parameter(property="project.compileSourceRoots", readonly = true, required = true) protected List<String> compileSourceRoots; /** * The list of JSF configuration files that will be processed by CDK. By default, CDK looks for all files in the * <code>src/main/config</code> folder with "xml" extension. */ @Parameter protected FileSet[] facesConfigs; @Parameter protected Map<String, String> options = Maps.newHashMap(); @Parameter protected Library library; /** * The directory for compiled classes. */ @Parameter(property="project.build.outputDirectory", readonly = true, required = true) protected File outputDirectory; /** * Directory where the output Java Files will be located. */ @Parameter(defaultValue="${project.build.directory}/generated-sources/main/java") protected File outputJavaDirectory; /** * Directory where the output Java Files will be located. */ @Parameter(defaultValue="${project.build.directory}/generated-sources/main/resources") protected File outputResourcesDirectory; @Parameter(defaultValue="${project.build.directory}/generated-sources/test/java") protected File outputTestDirectory; /** * Directory where the output Java Files will be located. */ @Parameter( defaultValue="${project.build.directory}/generated-sources/test/resources") protected File outputTestResourcesDirectory; /** * Directory where serialized library will be cached */ @Parameter(defaultValue="${project.build.directory}/library-cache") protected File outputLibraryCache; /** * Forces compiler to do not use cache and re-compile all sources from scratch */ @Parameter(property="cdk.recompile", defaultValue="false") protected boolean forceRecompile; /** * Turns off library generation and verification in case when no change was detected in sources which supports * {@link Cacheable} (it does not have to mean no change was done). Warning: when getting undesired results, try to turn off * this option. */ @Parameter(property="cdk.cache.eagerly", defaultValue="false") protected boolean cacheEagerly; /** * Top maven project. */ @Parameter(defaultValue="${project}", readonly = true) protected MavenProject project; /** * List of filename patterns that will be excluded from process by annotations processor. By default, all *.java files will * be processed. */ @Parameter protected String[] sourceExcludes; /** * List of filename patterns that will be included to process by annotations processor. By default, all *.java files will be * processed. */ @Parameter protected String[] sourceIncludes; /** * The list of JsfRenderer template files that will be processed by CDK. By default, CDK looks for all files in the * <code>src/main/templates</code> folder with "xml" extension. */ @Parameter protected FileSet[] templates; @Parameter protected Map<String, String> workers; @Parameter protected String locale = Locale.getDefault().toString(); @Parameter protected String charset = Charset.defaultCharset().name(); /* * (non-Javadoc) * * @see org.apache.maven.plugin.Mojo#execute() */ @Override public void execute() throws MojoExecutionException, MojoFailureException { if ("pom".equals(project.getPackaging())) { getLog().info("Skipping 'pom' packaging project: " + project.getModel().getId()); return; } // Setup logger. Logger logger = new MavenLogger(getLog()); Generator generator = new Generator(); generator.setLog(logger); generator.setLoader(createProjectClassLoader(project)); // Set source folders. ArrayList<File> folders = new ArrayList<File>(compileSourceRoots.size()); for (String sourceFolder : compileSourceRoots) { File folder = new File(sourceFolder); if (folder.exists() && folder.isDirectory()) { folders.add(folder); } } generator.addSources(Sources.JAVA_SOURCES, findJavaFiles(), folders); // detect templates and configs directories. generator.addSources(Sources.RENDERER_TEMPLATES, findTemplateFiles(), null); generator.addSources(Sources.FACES_CONFIGS, findFacesConfigFiles(), null); // Setup output folders. setOutput(generator, outputJavaDirectory, Outputs.JAVA_CLASSES); setOutput(generator, outputResourcesDirectory, Outputs.RESOURCES); setOutput(generator, outputTestDirectory, Outputs.TEST_JAVA_CLASSES); setOutput(generator, outputTestResourcesDirectory, Outputs.TEST_RESOURCES); setOutput(generator, outputLibraryCache, Outputs.LIBRARY_CACHE); // configure CDK workers. setupPlugins(generator); options.put(LibraryCache.CACHE_ENABLED_OPTION, Boolean.toString(!forceRecompile)); options.put(CdkProcessorImpl.CACHE_EAGERLY_OPTION, Boolean.toString(cacheEagerly)); generator.setOptions(options); try { if (this.library != null && this.library.getTaglib() != null && this.library.getTaglib().getShortName() != null) { generator.setNamespace(this.library.getTaglib().getShortName()); } generator.setLocale(localeFromString(locale)); generator.setCharset(Charset.forName(charset)); // Build JSF library. // LibraryBuilder builder = LibraryBuilder.createInstance(context); generator.init(); generator.execute(); if (logger.getErrorCount() > 0) { throw new MojoFailureException("Error occurred while JSF library was built", logger.getFirstError()); } // Tell project about generated files. if (outputJavaDirectory.exists()) { project.addCompileSourceRoot(outputJavaDirectory.getAbsolutePath()); } if (outputResourcesDirectory.exists()) { Resource resource = new Resource(); resource.setDirectory(outputResourcesDirectory.getAbsolutePath()); project.addResource(resource); } if (outputTestDirectory.exists()) { project.addTestCompileSourceRoot(outputTestDirectory.getAbsolutePath()); } if (outputTestResourcesDirectory.exists()) { Resource testResource = new Resource(); testResource.setDirectory(outputTestResourcesDirectory.getAbsolutePath()); project.addTestResource(testResource); } } catch (CdkException e) { throw new MojoExecutionException("CDK build error", e); } } private Locale localeFromString(String locale) { String parts[] = locale.split("_", -1); if (parts.length == 1) return new Locale(parts[0]); else if (parts.length == 2 || (parts.length == 3 && parts[2].startsWith("#"))) return new Locale(parts[0], parts[1]); else return new Locale(parts[0], parts[1], parts[2]); } /** * <p class="changed_added_4_0"> * </p> * * @param generator * @throws MojoFailureException */ private void setupPlugins(Generator generator) throws MojoFailureException { // TODO - get additional modules, as Maven components ? } /** * <p class="changed_added_4_0"> * This utility method sets output directory for particular type. I such directory does not exist, it is created. * </p> * * @param generator * @param directory * @param type */ private static void setOutput(Generator generator, File directory, Outputs type) { // if (!directory.exists()) { // directory.mkdirs(); // } generator.addOutputFolder(type, directory); } private File resolveRelativePath(File file) { File result = file; if (!result.isAbsolute()) { result = new File(project.getBasedir(), result.getPath()); } return result; } private Iterable<File> findTemplateFiles() throws MojoExecutionException { if (null == templates) { File defaultDirectory = resolveRelativePath(new File(MAIN_TEMPLATES)); if (defaultDirectory.exists() && defaultDirectory.isDirectory()) { FileSet fileSet = new FileSet(); fileSet.setDirectory(MAIN_TEMPLATES); fileSet.addInclude(XML_INCLUDES); templates = new FileSet[] { fileSet }; } } return doScan(templates); } private Iterable<File> findJavaFiles() throws MojoExecutionException { Set<File> javaSources = new HashSet<File>(); String[] includes = null == sourceIncludes ? JAVA_INCLUDES : sourceIncludes; for (String compileRoot : compileSourceRoots) { File rootFolder = new File(compileRoot); String[] sources = doScan(includes, sourceExcludes, rootFolder); for (String src : sources) { javaSources.add(new File(rootFolder, src)); } } return javaSources; } private Iterable<File> findFacesConfigFiles() throws MojoExecutionException { if (null == facesConfigs) { File defaultDirectory = resolveRelativePath(new File(MAIN_CONFIG)); if (defaultDirectory.exists() && defaultDirectory.isDirectory()) { FileSet fileSet = new FileSet(); fileSet.setDirectory(MAIN_CONFIG); fileSet.addInclude(XML_INCLUDES); facesConfigs = new FileSet[] { fileSet }; } } return doScan(facesConfigs); } protected CdkClassLoader createProjectClassLoader(MavenProject project) { CdkClassLoader classLoader = null; try { // This Mojo executed befor process-resources phase, therefore we have to use original resource folders. List<Resource> resources = project.getResources(); List<File> urls = new ArrayList<File>(classpathElements.size() + resources.size()); for (Resource resource : resources) { String directory = resource.getDirectory(); // TODO - use includes/excludes and target path. urls.add(resolveRelativePath(new File(directory))); } for (Iterator<String> iter = classpathElements.iterator(); iter.hasNext();) { String element = iter.next(); urls.add(new File(element)); } classLoader = new CdkClassLoader(urls); } catch (MalformedURLException e) { getLog().error("Bad URL in classpath", e); } return classLoader; } protected String[] doScan(String[] includes, String[] excludes, File rootFolder) throws MojoExecutionException { try { DirectoryScanner directoryScanner = new DirectoryScanner(); directoryScanner.setFollowSymlinks(true); directoryScanner.setBasedir(rootFolder); directoryScanner.setExcludes(excludes); directoryScanner.setIncludes(includes); directoryScanner.addDefaultExcludes(); directoryScanner.scan(); return directoryScanner.getIncludedFiles(); } catch (IllegalStateException e) { throw new MojoExecutionException("Error scanning source root: \'" + rootFolder + "\'", e); } } /** * Skan Array of filesets for selected resources. * * @param filesets * @return * @throws MojoExecutionException */ @SuppressWarnings("unchecked") protected Collection<File> doScan(FileSet[] filesets) throws MojoExecutionException { List<File> files = new ArrayList<File>(); if (null != filesets) { for (FileSet fileSet : filesets) { String[] includes = (String[]) fileSet.getIncludes().toArray(STRINGS_ARRAY); String[] excludes = (String[]) fileSet.getExcludes().toArray(STRINGS_ARRAY); File fileSetDirectory = resolveRelativePath(new File(fileSet.getDirectory())); String[] scan = doScan(includes, excludes, fileSetDirectory); for (String filename : scan) { files.add(resolveRelativePath(new File(fileSetDirectory, filename))); } } } return files; } }