Java tutorial
/** * The contents of this file are subject to the OpenMRS Public License * Version 1.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://license.openmrs.org * * Software distributed under the License is distributed on an "AS IS" * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the * License for the specific language governing rights and limitations * under the License. * * Copyright (C) OpenMRS, LLC. All Rights Reserved. */ package org.openmrs.logic; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.net.URL; import java.net.URLClassLoader; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; import javax.tools.Diagnostic; import javax.tools.DiagnosticCollector; import javax.tools.JavaCompiler; import javax.tools.JavaFileObject; import javax.tools.StandardJavaFileManager; import javax.tools.StandardLocation; import javax.tools.ToolProvider; import javax.tools.JavaCompiler.CompilationTask; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.openmrs.api.context.Context; import org.openmrs.logic.util.LogicUtil; import org.openmrs.module.ModuleClassLoader; import org.openmrs.module.ModuleFactory; import org.openmrs.util.OpenmrsClassLoader; import org.openmrs.util.OpenmrsUtil; /** * Class loader that will automatically compile source as necessary when looking for class files. * This class only deal with the rule java and class file. Separate handler must be registered to create the java file. * * Implementation based on http://www.ibm.com/developerworks/edu/j-dw-javaclass-i.html and code from Tammy Dugan and Vibha Anand * in the dss module. * * @see org.openmrs.logic.rule.definition.service.impl.LanguageHandler */ public class CompilingClassLoader extends ClassLoader { protected Log log = LogFactory.getLog(CompilingClassLoader.class); // This class only deal with the rule java and class file. Processing from arden to java will be performed // using ArdenService from the core OpenMRS // Given a filename, read the entirety of that file from disk // and return it as a byte array. private byte[] getBytes(String filename) throws IOException { // Find out the length of the file File file = new File(filename); long len = file.length(); // Create an array that's just the right size for the file's // contents byte raw[] = new byte[(int) len]; // Open the file FileInputStream fin = new FileInputStream(file); // Read all of it into the array; if we don't get all, // then it's an error. int r = fin.read(raw); if (r != len) throw new IOException("Can't read all bytes from java file, " + r + " != " + len); // Don't forget to close the file! fin.close(); // And finally return the file contents as an array return raw; } private List<File> getCompilerClasspath() { List<File> files = new ArrayList<File>(); Collection<ModuleClassLoader> moduleClassLoaders = ModuleFactory.getModuleClassLoaders(); //check module dependencies for (ModuleClassLoader moduleClassLoader : moduleClassLoaders) { URL[] urls = moduleClassLoader.getURLs(); for (URL url : urls) files.add(new File(url.getFile())); } // check current class loader and all its parents ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); while (classLoader != null) { if (classLoader instanceof URLClassLoader) { URLClassLoader urlClassLoader = (URLClassLoader) classLoader; URL[] urls = urlClassLoader.getURLs(); for (URL url : urls) files.add(new File(url.getFile())); } classLoader = classLoader.getParent(); } return files; } // Spawn a process to compile the java source code file // specified in the 'javaFile' parameter. Return null if // the compilation worked, or the compile errors otherwise. private String compile(String javaFile) throws IOException { String errorMessage = null; String ruleClassDir = Context.getAdministrationService() .getGlobalProperty(LogicConstants.RULE_DEFAULT_CLASS_FOLDER); String ruleJavaDir = Context.getAdministrationService() .getGlobalProperty(LogicConstants.RULE_DEFAULT_SOURCE_FOLDER); JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); log.info("JavaCompiler is null: " + compiler == null); if (compiler != null) { // java compiler only available on JDK. This part of "IF" will not get executed when we run JUnit test File outputFolder = OpenmrsUtil.getDirectoryInApplicationDataDirectory(ruleClassDir); String[] options = {}; DiagnosticCollector<JavaFileObject> diagnosticCollector = new DiagnosticCollector<JavaFileObject>(); StandardJavaFileManager fileManager = compiler.getStandardFileManager(diagnosticCollector, Context.getLocale(), Charset.defaultCharset()); fileManager.setLocation(StandardLocation.CLASS_OUTPUT, Arrays.asList(outputFolder)); // create compiling classpath String stringProperties = System.getProperty("java.class.path"); if (log.isDebugEnabled()) log.debug("Compiler classpath: " + stringProperties); String[] properties = StringUtils.split(stringProperties, File.pathSeparator); List<File> classpathFiles = new ArrayList<File>(); for (String property : properties) { File f = new File(property); if (f.exists()) classpathFiles.add(f); } classpathFiles.addAll(getCompilerClasspath()); fileManager.setLocation(StandardLocation.CLASS_PATH, classpathFiles); // create the compilation task CompilationTask compilationTask = compiler.getTask(null, fileManager, diagnosticCollector, Arrays.asList(options), null, fileManager.getJavaFileObjects(javaFile)); boolean success = compilationTask.call(); fileManager.close(); if (success) { return null; } else { errorMessage = ""; for (Diagnostic<?> diagnostic : diagnosticCollector.getDiagnostics()) { errorMessage += diagnostic.getLineNumber() + ": " + diagnostic.getMessage(Context.getLocale()) + "\n"; } return errorMessage; } } else { File outputFolder = OpenmrsUtil.getDirectoryInApplicationDataDirectory(ruleClassDir); String[] commands = { "javac", "-classpath", System.getProperty("java.class.path"), "-d", outputFolder.getAbsolutePath(), javaFile }; // Start up the compiler File workingDirectory = OpenmrsUtil.getDirectoryInApplicationDataDirectory(ruleJavaDir); boolean success = LogicUtil.executeCommand(commands, workingDirectory); return success ? null : "Compilation error. (You must be using the JDK to see details.)"; } } // The heart of the ClassLoader -- automatically compile // source as necessary when looking for class files /** * @see java.lang.ClassLoader#loadClass(String, boolean) * @should compile and load java file at runtime */ @Override public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { // Our goal is to get a Class object Class<?> clas = null; // First, see if we've already dealt with this one clas = findLoadedClass(name); // Create a pathname from the class name // E.g. java.lang.Object => java/lang/Object String fileStub = name.replace('.', File.separatorChar); // Build objects pointing to the source code (.java) and object // code (.class) String javaFilename = fileStub + ".java"; String classFilename = fileStub + ".class"; String ruleClassDir = Context.getAdministrationService() .getGlobalProperty(LogicConstants.RULE_DEFAULT_CLASS_FOLDER); String ruleJavaDir = Context.getAdministrationService() .getGlobalProperty(LogicConstants.RULE_DEFAULT_SOURCE_FOLDER); File javaFile = new File(OpenmrsUtil.getDirectoryInApplicationDataDirectory(ruleJavaDir), javaFilename); File classFile = new File(OpenmrsUtil.getDirectoryInApplicationDataDirectory(ruleClassDir), classFilename); // First, see if we want to try compiling. We do if (a) there // is source code, and either (b0) there is no object code, // or (b1) there is object code, but it's older than the source if (javaFile.exists() && (!classFile.exists() || javaFile.lastModified() > classFile.lastModified())) { try { // Try to compile it. If this doesn't work, then // we must declare failure. (It's not good enough to use // an already-existing, but out-of-date, class file) String error = compile(javaFile.getAbsolutePath()); if (error != null) throw new LogicException(error); if (!classFile.exists()) throw new ClassNotFoundException("Compilation process failed for " + javaFilename); } catch (IOException ie) { // Another place where we might come to if we fail // to compile throw new ClassNotFoundException(ie.toString(), ie); } } // Let's try to load up the raw bytes, assuming they were // properly compiled, or didn't need to be compiled try { // read the bytes byte raw[] = getBytes(classFile.getAbsolutePath()); // try to turn them into a class clas = defineClass(name, raw, 0, raw.length); } catch (IOException ie) { // This is not a failure! If we reach here, it might // mean that we are dealing with a class in a library, // such as java.lang.Object } // Maybe it is in the openmrs loader if (clas == null) clas = OpenmrsClassLoader.getInstance().loadClass(name); // Maybe the class is in a library -- try loading // the normal way if (clas == null) clas = findSystemClass(name); // Resolve the class, if any, but only if the "resolve" // flag is set to true if (resolve && clas != null) resolveClass(clas); // If we still don't have a class, it's an error if (clas == null) throw new ClassNotFoundException(name); // Otherwise, return the class return clas; } }