/*
* Copyright 2011 cruxframework.org.
*
* 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 org.cruxframework.crux.tools.compile;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.AccessController;
import java.security.Permission;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.cruxframework.crux.core.client.screen.InterfaceConfigException;
import org.cruxframework.crux.core.config.ConfigurationFactory;
import org.cruxframework.crux.core.declarativeui.ViewProcessor;
import org.cruxframework.crux.core.rebind.DevelopmentScanners;
import org.cruxframework.crux.core.rebind.module.Module;
import org.cruxframework.crux.core.rebind.screen.ScreenResourceResolverInitializer;
import org.cruxframework.crux.core.server.CruxBridge;
import org.cruxframework.crux.core.server.classpath.ClassPathResolverInitializer;
import org.cruxframework.crux.core.server.dispatch.ServiceFactoryInitializer;
import org.cruxframework.crux.core.server.rest.core.registry.RestServiceFactoryInitializer;
import org.cruxframework.crux.core.utils.FileUtils;
import org.cruxframework.crux.scanner.ClasspathUrlFinder;
import org.cruxframework.crux.scanner.Scanners;
import org.cruxframework.crux.tools.compile.utils.ClassPathUtils;
import org.cruxframework.crux.tools.compile.utils.ModuleUtils;
import org.cruxframework.crux.tools.parameters.ConsoleParameter;
import org.cruxframework.crux.tools.parameters.ConsoleParameterOption;
import org.cruxframework.crux.tools.parameters.ConsoleParametersProcessor;
import com.google.gwt.dev.Compiler;
/**
* A Tool for crux projects compilation
* @author Thiago da Rosa de Bustamante
*
*/
public abstract class AbstractCruxCompiler
{
private static final Log logger = LogFactory.getLog(AbstractCruxCompiler.class);
protected File compilerWorkDir;
protected boolean indentPages;
protected boolean initialized = false;
protected boolean keepPagesGeneratedFiles;
protected String outputCharset;
protected File outputDir;
protected String pageFileExtension;
protected File pagesOutputDir;
protected File webDir;
private List<String> alreadyCompiledModules = new ArrayList<String>();
private List<String> gwtCompilerArgs = new ArrayList<String>();
private SecurityManager originalSecurityManager = null;
private List<CruxPostProcessor> postProcessors = new ArrayList<CruxPostProcessor>();
private boolean preCompileJavaSource = true;
private List<CruxPreProcessor> preProcessors = new ArrayList<CruxPreProcessor>();
protected File sourceDir;
protected File resourcesDir;
private File classpathDir;
/**
* @param parameters
*/
public AbstractCruxCompiler()
{
CruxBridge.getInstance().setSingleVM(true);
CruxRegisterUtil.removeOldTempFiles();
}
/**
* Runs the crux compilation loop.
*
* <p>First, if a sourceFolder is provided, it is compiled.</p>
* <p>Then, a scanner searches for modules, based on all crux pages found (returned by <code>getUrls()</code> method.).</p>
* <p>Each of those modules is compiled and all pages found is pre processed and post processed.</p>
* @throws CompilerException
*/
public void execute() throws CompilerException
{
try
{
compileJavaSource();
initializeCompiler();
ServiceFactoryInitializer.initialize(null);
RestServiceFactoryInitializer.initialize(null);
for (Module module : getModules())
{
boolean isModuleNotCompiled = !isModuleCompiled(module);
if (isModuleNotCompiled)
{
deleteModuleOutputDir(module);
}
Set<String> allScreenIDs = ScreenResourceResolverInitializer.getScreenResourceResolver().getAllScreenIDs(module.getName());
for (String screenID : allScreenIDs)
{
doCompileModule(new URL(screenID), module);
}
}
releaseCompilerResources();
}
catch (Throwable e)
{
logger.error(e.getMessage(), e);
throw new CompilerException(e.getMessage(), e);
}
}
public String getOutputCharset()
{
return outputCharset;
}
public File getOutputDir()
{
return outputDir;
}
public String getPageFileExtension()
{
return pageFileExtension;
}
public File getPagesOutputDir()
{
return pagesOutputDir;
}
public File getWebDir()
{
return webDir;
}
/**
*
*/
public void initializeCompiler()
{
if (!initialized)
{
URL[] urls = ClasspathUrlFinder.findClassPaths();
ModuleUtils.initializeScannerURLs(urls);
Scanners.setSearchURLs(urls);
DevelopmentScanners.initializeScanners();
initializeProcessors();
for (int i=0; i< this.preProcessors.size(); i++)
{
this.preProcessors.get(i).initialize(urls);
}
for (int i=0; i< this.postProcessors.size(); i++)
{
this.postProcessors.get(i).initialize(urls);
}
this.initialized = true;
}
}
public boolean isIndentPages()
{
return indentPages;
}
public boolean isKeepPagesGeneratedFiles()
{
return keepPagesGeneratedFiles;
}
public boolean isPreCompileJavaSource()
{
return preCompileJavaSource;
}
public void setIndentPages(boolean indentPages)
{
this.indentPages = indentPages;
}
public void setKeepPagesGeneratedFiles(boolean keepPagesGeneratedFiles)
{
this.keepPagesGeneratedFiles = keepPagesGeneratedFiles;
}
public void setOutputCharset(String outputCharset)
{
ViewProcessor.setOutputCharset(outputCharset);
this.outputCharset = outputCharset;
}
/**
* @param parameter
*/
public void setOutputDir(File file)
{
this.outputDir = file;
this.gwtCompilerArgs.add("-war");
try
{
this.gwtCompilerArgs.add(this.outputDir.getCanonicalPath());
}
catch (IOException e)
{
logger.error("Invalid output dir.", e);
}
if (this.webDir == null)
{
setWebDir(file);
}
}
public void setPageFileExtension(String pageFileExtension)
{
this.pageFileExtension = pageFileExtension;
}
public void setPagesOutputDir(File pagesOutputDir)
{
this.pagesOutputDir = pagesOutputDir;
}
public void setPreCompileJavaSource(boolean preCompileJavaSource)
{
this.preCompileJavaSource = preCompileJavaSource;
}
/**
* @param parameter
*/
public void setScanAllowedPackages(String packages)
{
ConfigurationFactory.getConfigurations().setScanAllowedPackages(packages);
}
/**
* @param parameter
*/
public void setScanIgnoredPackages(String packages)
{
ConfigurationFactory.getConfigurations().setScanIgnoredPackages(packages);
}
/**
* @param parameter
*/
public void setWebDir(File file)
{
this.webDir = file;
try
{
ClassPathResolverInitializer.getClassPathResolver().setWebInfClassesPath(new File(webDir, "WEB-INF/classes/").toURI().toURL());
ClassPathResolverInitializer.getClassPathResolver().setWebInfLibPath(new File(webDir, "WEB-INF/lib/").toURI().toURL());
}
catch (MalformedURLException e)
{
logger.error("Invalid web folder");
}
if (this.outputDir == null)
{
setOutputDir(file);
}
}
/**
* @param postProcessor
*/
protected void addPostProcessor(CruxPostProcessor postProcessor)
{
this.postProcessors.add(postProcessor);
}
/**
* @param preProcessor
*/
protected void addPreProcessor(CruxPreProcessor preProcessor)
{
this.preProcessors.add(preProcessor);
}
/**
* Compile files using GWT compiler
* @param url
* @throws Exception
*/
protected boolean compileFile(URL url, Module module) throws Exception
{
boolean compiled = false;
if(module != null)
{
if(!isModuleCompiled(module))
{
setModuleAsCompiled(module);
doCompileFile(url, module.getFullName());
compiled = true;
}
}
return compiled;
}
/**
* Pre compile java source folder, if provided
*/
protected void compileJavaSource() throws CompilerException
{
if (preCompileJavaSource && sourceDir != null)
{
try
{
initializeCompilerDir();
JCompiler compiler = new JCompiler();
compiler.setOutputDirectory(compilerWorkDir);
compiler.setSourcepath(sourceDir);
if(classpathDir != null)
{
compiler.setClasspath(getClasspath());
}
logger.info("Compiling java source");
if (!compiler.compile(sourceDir))
{
throw new CompilerException("Error compiling java code. See console for details");
}
}
catch (Exception e)
{
throw new CompilerException("Error initializing Java Compiler", e);
}
}
}
private String getClasspath()
{
//Wildcard is not supported by all JVM's, so let's use ';' approach.
File[] listOfFiles = classpathDir.listFiles();
StringBuffer classpath = new StringBuffer();
for (int i = 0; i < listOfFiles.length; i++)
{
if (listOfFiles[i].isFile())
{
classpath.append(listOfFiles[i].getAbsolutePath() + ";");
}
}
return classpath.toString();
}
/**
* @return
*/
protected ConsoleParametersProcessor createParametersProcessor()
{
ConsoleParameter parameter;
ConsoleParametersProcessor parametersProcessor = new ConsoleParametersProcessor(getProgramName());
parameter = new ConsoleParameter("outputDir", "The folder where the compiled files will be created.", false, true);
parameter.addParameterOption(new ConsoleParameterOption("dirName", "Folder name"));
parametersProcessor.addSupportedParameter(parameter);
parameter = new ConsoleParameter("sourceDir", "The project source folder.", false, true);
parameter.addParameterOption(new ConsoleParameterOption("dirName", "Folder name"));
parametersProcessor.addSupportedParameter(parameter);
parameter = new ConsoleParameter("webDir", "The application web root folder.", false, true);
parameter.addParameterOption(new ConsoleParameterOption("dirName", "Folder name"));
parametersProcessor.addSupportedParameter(parameter);
parameter = new ConsoleParameter("classpathDir", "The classpath folder.", false, true);
parameter.addParameterOption(new ConsoleParameterOption("classpathDir", "Classpath dir"));
parametersProcessor.addSupportedParameter(parameter);
parameter = new ConsoleParameter("resourcesDir", "The resources folder.", false, true);
parameter.addParameterOption(new ConsoleParameterOption("resourcesDir", "Resources dir"));
parametersProcessor.addSupportedParameter(parameter);
parameter = new ConsoleParameter("pagesOutputDir", "The folder where the generated page files will be created.", false, true);
parameter.addParameterOption(new ConsoleParameterOption("output", "Folder name"));
parametersProcessor.addSupportedParameter(parameter);
parameter = new ConsoleParameter("scanAllowedPackages",
"A list of packages (separated by commas) that will be scanned to find Controllers, Modules and CrossDevices", false, true);
parameter.addParameterOption(new ConsoleParameterOption("allowed", "Allowed packages"));
parametersProcessor.addSupportedParameter(parameter);
parameter =new ConsoleParameter("scanIgnoredPackages",
"A list of packages (separated by commas) that will not be scanned to find Controllers, Modules and CrossDevices", false, true);
parameter.addParameterOption(new ConsoleParameterOption("ignored", "Ignored packages"));
parametersProcessor.addSupportedParameter(parameter);
parameter = new ConsoleParameter("outputCharset", "Charset used on output files", true, true);
parameter.addParameterOption(new ConsoleParameterOption("charset", "Output charset"));
parametersProcessor.addSupportedParameter(parameter);
parameter = new ConsoleParameter("pageFileExtension", "Extension of the pages generated", false, true);
parameter.addParameterOption(new ConsoleParameterOption("fileExtension", "File Extension"));
parametersProcessor.addSupportedParameter(parameter);
parametersProcessor.addSupportedParameter(new ConsoleParameter("-indentPages", "If true, the output pages will be indented.", false, true));
parametersProcessor.addSupportedParameter(new ConsoleParameter("-keepPagesGeneratedFiles",
"If false, the output pages will be removed after compilation.", false, true));
parametersProcessor.addSupportedParameter(new ConsoleParameter("-doNotPreCompileJavaSource", "Makes compiler ignore java pre compilation.", false, true));
parameter = new ConsoleParameter("-gen", "Specify the folder where the GWT generators will output generated classes.", false, true);
parameter.addParameterOption(new ConsoleParameterOption("genFolder", "Folder Name"));
parametersProcessor.addSupportedParameter(parameter);
parameter = new ConsoleParameter("-style", "Specify the output style for GWT generated code.", false, true);
parameter.addParameterOption(new ConsoleParameterOption("style", "GWT output Style"));
parametersProcessor.addSupportedParameter(parameter);
parameter = new ConsoleParameter("-extra", "The directory into which extra files, not intended for deployment, will be written.", false, true);
parameter.addParameterOption(new ConsoleParameterOption("extraFolder", "Folder Name"));
parametersProcessor.addSupportedParameter(parameter);
parameter = new ConsoleParameter("-localWorkers", "Number of threads used to compile the permutations in parallel.", false, true);
parameter.addParameterOption(new ConsoleParameterOption("numberOfWorkers", "Number of Workers"));
parametersProcessor.addSupportedParameter(parameter);
parameter = new ConsoleParameter("-logLevel", "Level of Logging", false, true);
parameter.addParameterOption(new ConsoleParameterOption("level", "Level"));
parametersProcessor.addSupportedParameter(parameter);
parametersProcessor.addSupportedParameter(new ConsoleParameter("-validateOnly", " Validate all source code, but do not compile.", false, true));
parametersProcessor.addSupportedParameter(new ConsoleParameter("-compileReport", "Create a compile report that tells the Story of Your Compile.", false, true));
parametersProcessor.addSupportedParameter(new ConsoleParameter("-draftCompile", "Disable compiler optimizations and run faster.", false, true));
parametersProcessor.addSupportedParameter(new ConsoleParameter("-strict", "Only succeed if no input files have errors.", false, true));
parametersProcessor.addSupportedParameter(new ConsoleParameter("-XenableClosureCompiler", "Enable JS size optimizations.", false, true));
parametersProcessor.addSupportedParameter(new ConsoleParameter("-XfragmentCount", "Enable automatic fragment merging.", false, false));
parametersProcessor.addSupportedParameter(new ConsoleParameter("-help", "Display the usage screen.", false, true));
parametersProcessor.addSupportedParameter(new ConsoleParameter("-h", "Display the usage screen.", false, true));
return parametersProcessor;
}
/**
* @param module
*/
protected void deleteModuleOutputDir(Module module)
{
File output = new File(outputDir, module.getName());
if (output.exists())
{
FileUtils.recursiveDelete(output);
}
File backupFile = new File(FileUtils.getTempDirFile(), "_moduleBackup");
if (backupFile.exists())
{
FileUtils.recursiveDelete(backupFile);
}
}
/**
* @param url
* @param moduleName
*/
protected void doCompileFile(URL url, String moduleName)
{
logger.info("Compiling:"+url.toString());
// Because of an AWT BUG, GWT needs to execute a System.exit command to
// finish its compilation. This class calls the Compiler from an ant command,
// so, this bug is not a problem here. We need to compile all the modules on the same
// JVM. Call prerocessCruxPages on a separated JVM would cost a lot to our performance.
setSecurityManagerToAvoidSystemExit();
try
{
Compiler.main(getGwtArgs(moduleName));
}
catch (DoNotExitException e)
{
//Do nothing...continue compile looping
}
finally
{
restoreSecurityManager();
}
}
/**
* @param url
* @param module
* @throws Exception
*/
protected void doCompileModule(URL url, Module module) throws Exception
{
boolean isModuleNotCompiled = !isModuleCompiled(module);
CruxBridge.getInstance().registerLastPageRequested(url.toString());
URL preprocessedFile = preProcessCruxPage(url, module);
if (isModuleNotCompiled)
{
maybeBackupPreProcessorsOutput(module);
try
{
if (compileFile(preprocessedFile, module))
{
maybeRestoreBackup(module);
}
}
catch (InterfaceConfigException e)
{
logger.error(e.getMessage());
}
}
else
{
logger.info("Module '"+ module.getFullName()+"' was already compiled. Skipping compilation.");
}
postProcessCruxPage(preprocessedFile, module);
}
/**
*
* @param args
*/
public void addGwtCompilerArgs(String[] args)
{
if (args != null)
{
for (String arg : args)
{
gwtCompilerArgs.add(arg);
}
}
}
/**
* @param moduleName
* @return
*/
protected String[] getGwtArgs(String moduleName)
{
String[] gwtArgs = new String[gwtCompilerArgs.size()+1];
for (int i=0; i<gwtCompilerArgs.size(); i++)
{
gwtArgs[i] = gwtCompilerArgs.get(i);
}
gwtArgs[gwtCompilerArgs.size()] = moduleName;
return gwtArgs;
}
protected String getProgramName()
{
return "CruxCompiler";
}
/**
* Gets the list of modules that will be compiled
* @return
*/
protected abstract List<Module> getModules() throws Exception;
/**
* @throws IOException
* @throws MalformedURLException
*/
protected void initializeCompilerDir() throws IOException, MalformedURLException
{
compilerWorkDir = new File (FileUtils.getTempDirFile(), "crux_compiler"+System.currentTimeMillis());
compilerWorkDir.mkdirs();
ClassPathUtils.addURL(compilerWorkDir.toURI().toURL());
ClassPathResolverInitializer.getClassPathResolver().setWebInfClassesPath(compilerWorkDir.toURI().toURL());
}
protected abstract void initializeProcessors();
/**
* @param moduleName
* @return
*/
protected boolean isModuleCompiled(Module module)
{
return module!= null && alreadyCompiledModules.contains(module.getFullName());
}
/**
* @throws IOException
*
*/
protected void maybeBackupPreProcessorsOutput(Module module) throws IOException
{
File output = new File(outputDir, module.getName());
if (output.exists())
{
File backupFile = new File(FileUtils.getTempDirFile(), "_moduleBackup");
FileUtils.copyDirectory(output, backupFile);
}
}
/**
* @param module
* @throws IOException
*/
protected void maybeRestoreBackup(Module module) throws IOException
{
File backupFile = new File(FileUtils.getTempDirFile(), "_moduleBackup");
if (backupFile.exists())
{
File output = new File(outputDir, module.getName());
FileUtils.copyDirectory(backupFile, output);
backupFile.delete();
}
}
/**
* A chain composed by CruxPostProcessor object is used.
* @param url
* @param module
* @return
* @throws Exception
*/
protected void postProcessCruxPage(URL url, Module module) throws Exception
{
for (CruxPostProcessor postProcessor : this.postProcessors)
{
url = postProcessor.postProcess(url, module);
}
logger.info("File ["+url.toString()+"] post-processed.");
}
/**
* A chain composed by CruxPreProcessor object is used.
* @param url
* @param module
* @return
* @throws Exception
*/
protected URL preProcessCruxPage(URL url, Module module) throws Exception
{
for (CruxPreProcessor preprocess : this.preProcessors)
{
url = preprocess.preProcess(url, module);
}
logger.info("File ["+url.toString()+"] pre-processed.");
return url;
}
/**
* @param parameters
*/
protected void processParameters(Collection<ConsoleParameter> parameters)
{
for (ConsoleParameter parameter : parameters)
{
String parameterName = parameter.getName();
//GWT Parameters
if (parameterName.equals("-gen") ||
parameterName.equals("-style") ||
parameterName.equals("-extra") ||
parameterName.equals("-localWorkers") ||
parameterName.equals("-logLevel") ||
parameterName.equals("-XfragmentCount"))
{
gwtCompilerArgs.add(parameterName);
gwtCompilerArgs.add(parameter.getValue());
}
else if (parameterName.equals("-compileReport") ||
parameterName.equals("-strict") ||
parameterName.equals("-draftCompile") ||
parameterName.equals("-validateOnly") ||
parameterName.equals("-XenableClosureCompiler")
)
{
gwtCompilerArgs.add(parameterName);
}
//Crux Parameters
else if (parameterName.equals("outputDir"))
{
setOutputDir(new File(parameter.getValue()));
}
else if (parameterName.equals("webDir"))
{
setWebDir(new File(parameter.getValue()));
}
else if (parameterName.equals("scanAllowedPackages"))
{
setScanAllowedPackages(parameter.getValue());
}
else if (parameterName.equals("scanIgnoredPackages"))
{
setScanIgnoredPackages(parameter.getValue());
}
else if (parameterName.equals("pagesOutputDir"))
{
this.pagesOutputDir = new File(parameter.getValue());
}
else if (parameterName.equals("-indentPages"))
{
this.indentPages = true;
}
else if (parameterName.equals("-keepPagesGeneratedFiles"))
{
this.keepPagesGeneratedFiles = true;
}
else if (parameterName.equals("outputCharset"))
{
setOutputCharset(parameter.getValue());
}
else if (parameterName.equals("-doNotPreCompileJavaSource"))
{
this.preCompileJavaSource = false;
}
else if (parameterName.equals("pageFileExtension"))
{
this.pageFileExtension = parameter.getValue();
}
}
if (this.outputDir == null && this.webDir == null)
{
logger.error("You must inform at least one of outputDir and webDir parameters.");
System.exit(1);
}
}
/**
* @param parameters
*/
protected void processSourceParameter(ConsoleParameter parameter)
{
if (parameter != null)
{
this.sourceDir = new File(parameter.getValue());
try
{
ClassPathUtils.addURL(sourceDir.toURI().toURL());
}
catch (Exception e)
{
logger.error("Invalid sourceDir informed.", e);
System.exit(1);
}
}
}
/**
* @param parameters
*/
public void processResourcesParameter(ConsoleParameter parameter) {
if (parameter != null)
{
this.resourcesDir = new File(parameter.getValue());
try
{
ClassPathUtils.addURL(resourcesDir.toURI().toURL());
}
catch (Exception e)
{
logger.error("Invalid resourcesDir informed.", e);
System.exit(1);
}
}
}
/**
* @param parameters
*/
protected void processClasspathParameter(ConsoleParameter parameter)
{
if (parameter != null)
{
try
{
this.classpathDir = new File(parameter.getValue());
String[] classpaths = getClasspath().split(";");
for(String classpath : classpaths)
{
ClassPathUtils.addURL(new File(classpath).toURI().toURL());
}
}
catch (Exception e)
{
logger.error("Invalid classpathDir informed.", e);
System.exit(1);
}
}
}
/**
* Release any resource reserved during compilation
*/
protected void releaseCompilerResources()
{
if (compilerWorkDir != null && compilerWorkDir.exists())
{
FileUtils.recursiveDelete(compilerWorkDir);
}
}
/**
* @param module
*/
protected void setModuleAsCompiled(Module module)
{
if (module!= null)
{
alreadyCompiledModules.add(module.getFullName());
}
}
/**
*
*/
private void restoreSecurityManager()
{
AccessController.doPrivileged(new PrivilegedAction<Boolean>()
{
public Boolean run()
{
System.setSecurityManager(originalSecurityManager);
return true;
}
});
}
/**
*
*/
private void setSecurityManagerToAvoidSystemExit()
{
AccessController.doPrivileged(new PrivilegedAction<Boolean>()
{
public Boolean run()
{
originalSecurityManager = System.getSecurityManager();
System.setSecurityManager(new SecurityManager(){
@Override
public void checkExit(int status)
{
if (status == 0)
{
throw new DoNotExitException();
}
super.checkExit(status);
}
@Override
public void checkPermission(Permission perm){}
@Override
public void checkPermission(Permission perm, Object context){}
});
return true;
}
});
}
/**
* @author Thiago da Rosa de Bustamante
*
*/
private static class DoNotExitException extends SecurityException
{
private static final long serialVersionUID = -5285052847615664828L;
}
}