package net.wasdev.wlp.ant.jsp;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.Task;
import org.apache.tools.ant.taskdefs.Copy;
import org.apache.tools.ant.taskdefs.War;
import org.apache.tools.ant.types.FileSet;
import org.apache.tools.ant.types.Path;
import org.apache.tools.ant.types.ZipFileSet;
import net.wasdev.wlp.ant.ServerTask;
public class CompileJSPs extends Task {
private File wlpHome;
private File war;
private String ref;
private List<String> features = new ArrayList<>();
private String featureVersion = "2.3";
// By default delete temporary server afterwards
private boolean cleanup = true;
private File srcdir;
private File tmpdir;
private File destdir;
private String classpath = "";
private String classpathRef;
private String source;
// By default allow 30 seconds to compile the jsps
private int timeout = 30;
@Override
public void execute() {
validate();
try {
// Need somewhere to compile to so create a tmp file and turn it to
// a directory
File compileDir;
if (tmpdir == null) {
compileDir = File.createTempFile("compileJsp", "");
compileDir.delete();
} else {
compileDir = new File(tmpdir, "compileJsp");
}
try {
File serverDir = new File(compileDir, "servers/defaultServer/");
String warSuffix = (war == null) ? "fake" : trimExtension(war.getName());
File jspCompileDir = new File(serverDir, "jsps/default_node/SMF_WebContainer/jspCompile/" + warSuffix);
if (jspCompileDir.exists()) {
delete(jspCompileDir);
}
if (serverDir.exists() || serverDir.mkdirs()) {
writeServerXML(serverDir);
createAppXML(serverDir);
// Compile jsps by having the server start with eager app
// start and compilation
ServerTask server = createServerTask(compileDir);
server.setOperation("start");
server.execute();
boolean compileSuccess = false;
try {
checkFeaturesExist(serverDir);
compileSuccess = waitForCompilation(serverDir, jspCompileDir, war);
} finally {
// Stop the server
server.setOperation("stop");
server.execute();
}
if (compileSuccess) {
if (war != null) {
updateSourceWar(jspCompileDir);
} else {
copyClassFiles(jspCompileDir);
}
} else {
printCompileErrors(new File(serverDir, "logs/console.log"));
throw new BuildException("JSP compile failed");
}
} else {
throw new BuildException("Unable to create folder for usr dir");
}
} finally {
if (cleanup) {
delete(compileDir);
}
}
} catch (IOException e) {
throw new BuildException("A failure occurred: " + e.toString(), e);
}
}
private void copyClassFiles(File jspCompileDir) {
Copy copy = new Copy();
copy.setProject(getProject());
copy.setTaskName(getTaskName());
destdir.mkdirs();
copy.setTodir(destdir);
FileSet files = new FileSet();
files.setDir(jspCompileDir);
files.setIncludes("**/*.class");
copy.addFileset(files);
copy.execute();
}
private void updateSourceWar(File jspCompileDir) {
// Finally need to merge the compiled jsps in
War warTask = new War();
warTask.setProject(getProject());
warTask.setDestFile(war);
warTask.setUpdate(true);
ZipFileSet jspFiles = new ZipFileSet();
// The JSPs will be in the a well known location. The
// app name from server.xml and the war file name will
// be
// in the path, the war name minus the .war extension
// (if present) will also be used.
jspFiles.setDir(jspCompileDir);
warTask.addClasses(jspFiles);
warTask.setTaskName(getTaskName());
warTask.execute();
}
private void validate() {
if (war == null && srcdir == null) {
throw new BuildException("One of war or srcdir must be specified");
}
if (srcdir != null && destdir == null) {
throw new BuildException("The destdir must be specified");
}
if (wlpHome == null) {
throw new BuildException("Liberty installation directory must be set");
}
if (source == null) {
setSource(System.getProperty("java.specification.version"));
}
}
private void checkFeaturesExist(File serverDir) {
BufferedReader reader = null;
boolean fail = false;
try {
reader = new BufferedReader(new FileReader(new File(serverDir, "logs/console.log")));
String line;
while ((line = reader.readLine()) != null) {
if (line.contains("CWWKF0001E")) {
log(line);
fail = true;
}
}
} catch (IOException e) {
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException ioe) {
}
}
}
if (fail) {
throw new BuildException("Features required to compile are missing");
}
}
private void printCompileErrors(File log) {
try {
BufferedReader reader = new BufferedReader(new FileReader(log));
try {
String line;
boolean reprint = false;
while ((line = reader.readLine()) != null) {
if (line.startsWith("JSPG0049E") || line.startsWith("JSPG0091E") || line.startsWith("JSPG0093E")) {
reprint = true;
}
if (line.startsWith("[")) {
reprint = false;
}
if (reprint) {
log(line, Project.MSG_ERR);
}
}
} finally {
reader.close();
}
} catch (IOException ioe) {
throw new BuildException("Unable to load compile log");
}
}
private boolean waitForCompilation(File serverDir, File jspCompileDir, File war2) throws IOException {
List<File> jsps = new ArrayList<>();
if (war2 != null) {
fillFromWar(jsps, war2, jspCompileDir);
} else {
fillFromSource(jsps, srcdir, jspCompileDir);
}
Set<File> javaFiles = new HashSet<>();
boolean equalDetected = false;
// Allow timeout seconds to compile the jsps.
for (int i = 0; i < timeout; i++) {
if (jspCompileDir.exists()) {
// Look to see if the class file exists yet.
Iterator<File> it = jsps.iterator();
while (it.hasNext()) {
File classFile = it.next();
if (classFile.exists()) {
it.remove();
equalDetected = false;
}
String classFileName = classFile.getName();
String javaFileName = classFileName.substring(0, classFileName.length() - 6) + ".java";
File javaFile = new File(classFile.getParentFile(), javaFileName);
// If the class file doesn't exist yet look to see if a
// .java file exists which might indicate
// a compile failure and store it away.
if (javaFile.exists()) {
if (javaFiles.add(javaFile)) {
equalDetected = false;
}
} else {
if (javaFiles.remove(javaFile)) {
equalDetected = false;
}
}
}
}
if (!!!equalDetected) {
equalDetected = jsps.size() == javaFiles.size();
if (jsps.isEmpty()) {
break;
}
try {
// Wait for a second for compilation to progress
Thread.sleep(1000);
} catch (InterruptedException e) {
}
} else {
break;
}
}
if (!jsps.isEmpty()) {
log("Failed to create: " + jsps, Project.MSG_ERR);
}
return jsps.isEmpty();
}
private void fillFromSource(List<File> jsps, File srcdir2, File jspCompileDir) {
File[] files = srcdir2.listFiles();
if (files != null) {
for (File file : files) {
String fileName = file.getName();
if (file.isFile() && fileName.endsWith(".jsp")) {
String expectedJSPName = jspToClassFileName(fileName);
jsps.add(new File(jspCompileDir, expectedJSPName));
} else if (file.isDirectory()) {
fillFromSource(jsps, file, new File(jspCompileDir, file.getName()));
}
}
}
}
private String jspToClassFileName(String name) {
StringBuilder sb = new StringBuilder();
sb.append('_');
char[] chars = name.toCharArray();
for (int i = 0; i < name.length() - 4; i++) {
char c = chars[i];
if (Character.isLetterOrDigit(c)) {
sb.append(c);
} else {
sb.append('_');
sb.append(Integer.toHexString(c).toUpperCase());
sb.append('_');
}
}
sb.append(".class");
return sb.toString();
}
private void fillFromWar(List<File> jsps, File war2, File jspCompileDir) {
ZipInputStream zipIn = null;
try {
zipIn = new ZipInputStream(new FileInputStream(war2));
// Find all the jsps in the war file and work out where the compiled
// result should be
ZipEntry entry;
while ((entry = zipIn.getNextEntry()) != null) {
String entryName = entry.getName();
if (entryName.endsWith(".jsp")) {
String expectedJSPName = jspToClassFileName(entryName);
jsps.add(new File(jspCompileDir, expectedJSPName));
}
}
} catch (IOException ioe) {
} finally {
if (zipIn != null) {
try {
zipIn.close();
} catch (IOException ioe) {
}
}
}
}
private ServerTask createServerTask(File usrDir) {
ServerTask server = new ServerTask();
server.setProject(getProject());
server.setRef(ref);
server.setInstallDir(wlpHome);
server.setUserDir(usrDir);
server.setTaskName(getTaskName());
return server;
}
private void writeServerXML(File serverDir) throws FileNotFoundException {
PrintStream ps = new PrintStream(new File(serverDir, "server.xml"));
ps.println("<server>");
ps.println("<featureManager>");
for (String feature : features) {
ps.print("<feature>");
ps.print(feature);
ps.println("</feature>");
}
ps.print("<feature>jsp-");
ps.print(featureVersion);
ps.println("</feature>");
ps.println("</featureManager>");
if (war != null) {
ps.println("<webApplication name=\"jspCompile\" location=\"" + war.getAbsolutePath() + "\"/>");
} else {
ps.println("<webApplication name=\"jspCompile\" location=\"fake.war\"/>");
}
ps.println("<httpEndpoint id=\"defaultHttpEndpoint\" host=\"localhost\" httpPort=\"0\"/>");
ps.print("<jspEngine prepareJsps=\"0\" scratchdir=\"" + serverDir.getAbsolutePath() + "/jsps\" jdkSourceLevel=\"" + source + "\"/>");
ps.println("<webContainer deferServletLoad=\"false\"/>");
ps.println("<keyStore password=\"dummyKeystore\"/>");
ps.println("</server>");
ps.close();
}
private void createAppXML(File serverDir) throws FileNotFoundException {
if (srcdir != null) {
// TODO write the loose application xml.
File appsDir = new File(serverDir, "apps");
appsDir.mkdirs();
PrintStream ps = new PrintStream(new File(appsDir, "fake.war.xml"));
ps.println("<archive>");
ps.println(" <dir targetInArchive=\"/\" sourceOnDisk=\"" + srcdir.getAbsolutePath() + "\"/>");
Path p = new Path(getProject(), classpath);
if (classpathRef != null) {
Path path = (Path) getProject().getReference(classpathRef);
p.add(path);
}
String[] cp = p.toString().split(File.pathSeparator);
for (String entry : cp) {
File f = new File(entry);
String basename = f.getName();
if (f.isFile() && f.exists() && f.getName().endsWith(".jar")) {
ps.println(" <file targetInArchive=\"/WEB-INF/lib/" + basename + "\" sourceOnDisk=\"" + f.getAbsolutePath() + "\"/>");
} else if (f.isDirectory() && f.exists()) {
// TODO: What if basename is NOT "classes"?
ps.println(" <dir targetInArchive=\"/WEB-INF/classes\" sourceOnDisk=\"" + f.getAbsolutePath() + "\"/>");
}
}
ps.println("</archive>");
ps.close();
}
}
private String trimExtension(String name) {
if (name.endsWith(".war")) {
return name.substring(0, name.length() - 4);
}
return name;
}
private void delete(File f) {
if (f.isFile()) {
f.delete();
} else if (f.isDirectory()) {
File[] files = f.listFiles();
if (files != null) {
for (File file : files) {
delete(file);
}
}
f.delete();
}
}
public void setCleanup(boolean cleanup) {
this.cleanup = cleanup;
}
public void setInstallDir(File home) {
wlpHome = home;
}
public void setRef(String ref) {
this.ref = ref;
}
public void setWar(File war) {
this.war = war;
}
public void setFeatures(String features) {
String[] featuresArray = features.split(",");
this.features.addAll(Arrays.asList(featuresArray));
}
public void setJspVersion(String version) {
featureVersion = version;
}
public void setSrcdir(File srcdir) {
this.srcdir = srcdir;
}
public void setDestdir(File destdir) {
this.destdir = destdir;
}
public void setTempdir(File tmpdir) {
this.tmpdir = tmpdir;
}
public void setClasspath(String classpath) {
this.classpath = classpath;
}
public void setClasspathRef(String classpathRef) {
this.classpathRef = classpathRef;
}
public void setSource(String src) {
if ("1.3".equals(src)) {
source = "13";
} else if ("1.4".equals(src)) {
source = "14";
} else if ("1.5".equals(src) || "5".equals(src)) {
source = "15";
} else if ("1.6".equals(src) || "6".equals(src)) {
source = "16";
} else if ("1.7".equals(src) || "7".equals(src)) {
source = "17";
} else if ("1.8".equals(src) || "8".equals(src)) {
source = "18";
}
}
public void setTimeout(int timeout) {
this.timeout = timeout;
}
}