/*
* @(#)ClassBean.java
*
* Copyright 2011 Instituto Superior Tecnico
* Founding Authors: Luis Cruz
*
* https://fenix-ashes.ist.utl.pt/
*
* This file is part of the Scheduler Module.
*
* The Scheduler Module is free software: you can
* redistribute it and/or modify it under the terms of the GNU Lesser General
* Public License as published by the Free Software Foundation, either version
* 3 of the License, or (at your option) any later version.
*
* The Scheduler Module 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 the Scheduler Module. If not, see <http://www.gnu.org/licenses/>.
*
*/
package org.fenixedu.bennu.scheduler.custom;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.io.Serializable;
import java.io.StringWriter;
import java.io.Writer;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLClassLoader;
import java.security.CodeSource;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import javax.tools.JavaCompiler;
import javax.tools.JavaCompiler.CompilationTask;
import javax.tools.JavaFileObject;
import javax.tools.SimpleJavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.StandardLocation;
import javax.tools.ToolProvider;
import org.fenixedu.bennu.core.security.Authenticate;
import org.joda.time.DateTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.JsonObject;
/**
*
* @author Luis Cruz
*
*/
public class ClassBean implements Serializable {
private static final long serialVersionUID = 6558922605683134259L;
public static final Logger LOGGER = LoggerFactory.getLogger(ClassBean.class);
private String className;
private String contents;
public static class JavaSourceFromString extends SimpleJavaFileObject {
final String code;
JavaSourceFromString(String name, String code) {
super(URI.create("string:///" + name.replace('.', '/') + Kind.SOURCE.extension), Kind.SOURCE);
this.code = code;
}
@Override
public CharSequence getCharContent(boolean ignoreEncodingErrors) {
return code;
}
}
public static class MyClassLoader extends URLClassLoader {
private final ClassLoader parent;
public MyClassLoader(final URL[] urls, final ClassLoader parent) {
super(urls);
/* The parent is not explicitly defined, instead we manually
* delegate whenever necessary.
* This ensures that the newly uploaded class is always chosen
* instead of an existing class with the same name.
*/
this.parent = parent;
}
@Override
public synchronized void clearAssertionStatus() {
parent.clearAssertionStatus();
super.clearAssertionStatus();
}
@Override
public URL getResource(String name) {
final URL url = super.getResource(name);
return url == null ? parent.getResource(name) : url;
}
@Override
public InputStream getResourceAsStream(final String name) {
final InputStream inputStream = parent.getResourceAsStream(name);
return inputStream == null ? super.getResourceAsStream(name) : inputStream;
}
@Override
public Enumeration<URL> getResources(final String name) throws IOException {
final List<URL> urls = new ArrayList<>();
for (final Enumeration<URL> e = super.getResources(name); e.hasMoreElements(); urls.add(e.nextElement())) {
}
for (final Enumeration<URL> e = parent.getResources(name); e.hasMoreElements(); urls.add(e.nextElement())) {
}
return Collections.enumeration(urls);
}
@Override
protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
try {
return super.loadClass(name, resolve);
} catch (ClassNotFoundException e) {
return parent.loadClass(name);
}
}
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
try {
return super.loadClass(name);
} catch (ClassNotFoundException e) {
return parent.loadClass(name);
}
}
@Override
public synchronized void setClassAssertionStatus(String className, boolean enabled) {
parent.setClassAssertionStatus(className, enabled);
super.setClassAssertionStatus(className, enabled);
}
@Override
public synchronized void setDefaultAssertionStatus(boolean enabled) {
parent.setDefaultAssertionStatus(enabled);
super.setDefaultAssertionStatus(enabled);
}
@Override
public synchronized void setPackageAssertionStatus(String packageName, boolean enabled) {
parent.setPackageAssertionStatus(packageName, enabled);
super.setPackageAssertionStatus(packageName, enabled);
}
}
public ClassBean(String className, String contents) {
super();
this.className = className;
this.contents = contents.trim();
}
public String getClassName() {
return className;
}
public void setClassName(String className) {
this.className = className;
}
public String getContents() {
return contents;
}
public void setContents(String contents) {
this.contents = contents;
}
private static final List<Executer> runningExecuters = Collections.synchronizedList(new ArrayList<Executer>());
public static List<Executer> getRunningExecuters() {
return Collections.unmodifiableList(runningExecuters);
}
public class Executer extends Thread {
private final DateTime uploadTime = new DateTime();
private final Writer out = new StringWriter();
private final String username = Authenticate.isLogged() ? Authenticate.getUser().getUsername() : null;
public Executer() {
runningExecuters.add(this);
}
private String getBaseClassPathDirName() {
final String tmpDirName = System.getProperty("java.io.tmpdir");
return tmpDirName + File.separatorChar + "ClassBean_classpath_" + hashCode();
}
private String getBaseFileName() {
final String baseClassPathDirName = getBaseClassPathDirName();
final String pathFileName = className.replace('.', File.separatorChar);
return baseClassPathDirName + File.separatorChar + pathFileName;
}
private String getJavaFileName() {
return getBaseFileName() + ".java";
}
private void createDirs() {
final String filename = getJavaFileName();
final File file = new File(filename);
file.getParentFile().mkdirs();
}
private Boolean compileFile() throws IOException, SecurityException, NoSuchMethodException, IllegalArgumentException,
IllegalAccessException, InvocationTargetException, URISyntaxException {
final ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
final Method method = classLoader.getClass().getMethod("getURLs", new Class[0]);
final URL[] urls = (URL[]) method.invoke(classLoader, new Object[0]);
final List<File> files = new ArrayList<>();
for (final URL url : urls) {
files.add(new File(url.toURI()));
}
CodeSource servletCodeSource = HttpServletRequest.class.getProtectionDomain().getCodeSource();
if (servletCodeSource != null && servletCodeSource.getLocation() != null) {
files.add(new File(servletCodeSource.getLocation().toURI()));
}
final JavaSourceFromString javaSourceFromString = new JavaSourceFromString(getClassName(), getContents());
final JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler();
try (StandardJavaFileManager standardJavaFileManager = javaCompiler.getStandardFileManager(null, null, null)) {
standardJavaFileManager.setLocation(StandardLocation.CLASS_OUTPUT,
Collections.singleton(new File(getBaseClassPathDirName())));
standardJavaFileManager.setLocation(StandardLocation.CLASS_PATH, files);
final Collection<JavaFileObject> javaFileObjects = new ArrayList<>();
javaFileObjects.add(javaSourceFromString);
final Iterable<String> options = Arrays.asList("-g");
final CompilationTask compilationTask =
javaCompiler.getTask(out, standardJavaFileManager, null, options, null, javaFileObjects);
if (compilationTask.call() == false) {
return false;
}
return true;
}
}
private void runTask() throws ClassNotFoundException, InstantiationException, IllegalAccessException, SecurityException,
IllegalArgumentException, IOException {
final ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
final URL[] urls = new URL[] { new File(getBaseClassPathDirName()).toURI().toURL() };
try (MyClassLoader urlClassLoader = new MyClassLoader(urls, classLoader)) {
urlClassLoader.loadClass(getClassName());
@SuppressWarnings("unchecked")
final Class<? extends CustomTask> clazz =
(Class<? extends CustomTask>) Class.forName(getClassName(), true, urlClassLoader);
setName("CustomTaskRunner-" + clazz.getSimpleName() + "-" + uploadTime.getMillis());
CustomTask task = clazz.newInstance();
task.init(contents, username);
task.run();
}
}
@Override
public void run() {
runCompileAndExecute();
}
public void runCompileAndExecute() {
try {
try {
try {
createDirs();
compileFile();
runTask();
} catch (InstantiationException | IllegalAccessException | IllegalArgumentException
| InvocationTargetException | SecurityException | NoSuchMethodException | IOException
| URISyntaxException | ClassNotFoundException e) {
throw new Error(e);
} finally {
}
} finally {
cleanup();
}
} finally {
runningExecuters.remove(this);
}
}
public JsonObject runCompile() {
final JsonObject result;
Boolean compiledSuccessfully = false;
try {
createDirs();
compiledSuccessfully = compileFile();
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException | SecurityException
| NoSuchMethodException | IOException | URISyntaxException e) {
try (PrintWriter pout = new PrintWriter(out)) {
e.printStackTrace(pout);
}
} finally {
cleanup();
}
result = new JsonObject();
result.addProperty("compileOK", compiledSuccessfully);
if (!compiledSuccessfully) {
result.addProperty("error", out.toString());
}
return result;
}
private void cleanup() {
final String dirName = getBaseClassPathDirName();
final File file = new File(dirName);
delete(file);
}
private void delete(final File file) {
if (file.isDirectory()) {
for (final File subFile : file.listFiles()) {
delete(subFile);
}
}
file.delete();
}
public String getClassBeanClassName() {
return getClassName();
}
public DateTime getUploaded() {
return uploadTime;
}
}
public void run() {
final Executer executer = new Executer();
executer.start();
}
public JsonObject compile() {
final Executer executer = new Executer();
return executer.runCompile();
}
}