/*******************************************************************************
* Copyright (c) 2012 VMWare, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* VMWare, Inc. - initial API and implementation
*******************************************************************************/
package org.grails.ide.api.impl;
import grails.build.logging.GrailsConsole;
import grails.util.BuildSettings;
import grails.util.BuildSettingsHolder;
import groovy.lang.ExpandoMetaClass;
import java.io.File;
import java.net.URLClassLoader;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import org.codehaus.groovy.grails.cli.GrailsScriptRunner;
import org.codehaus.groovy.grails.cli.parsing.CommandLine;
import org.codehaus.groovy.grails.cli.parsing.CommandLineParser;
import org.codehaus.groovy.grails.cli.support.ClasspathConfigurer;
import org.grails.ide.api.GrailsConnector;
import org.grails.ide.eclipse.runtime.GrailsEclipseConsole;
public class GrailsConnectorImpl implements GrailsConnector {
public static boolean instantiated = false;
/**
* The directory that we are connected to. Once connected, the directory can not be changed.
*/
private final File baseDir;
//TODO: encapsulate all important state
//Ideally all of the important state of a GrailsConnector should be represented / contained
//within the instance fields below. This is probably not really the case yet. Some
//state may linger elsewhere (static fields, system properties, etc.)
private BuildSettings buildSettings = null;
private GrailsScriptRunner scriptRunner;
private CommandLineParser parser = GrailsScriptRunner.getCommandLineParser();
private Map<String,String> savedSystemProps = null; //used to 'reset' system props prior to executing a command.
public GrailsConnectorImpl(File baseDir) {
//For now, we only allow one connector to be created per JVM.
//Maybe if are more confident that Grails is able to keep several connectors
//in a single JVM without 'mixing' their states together this restriction can be lifted.
//What may be feasible, for example is have only multiple instances in the same JVM
//as long as an 'old' instance is disposed of before creating a new one.
if (instantiated) {
throw new IllegalStateException("Only one connector per JVM is supported by this Tooling API implementation");
}
instantiated = true;
this.baseDir = baseDir;
System.setProperty("net.sf.ehcache.skipUpdateCheck", "true"); //TODO: Copied from GrailsScriptRunner, what's this for?
System.setProperty("jline.terminal", "jline.UnsupportedTerminal"); //TODO: Bad! Needs API way.
System.setProperty("grails.console.class", GrailsEclipseConsole.class.getName()); //TODO: Bad! Needs API way.
System.setProperty("grails.disable.exit", "true"); //TODO: do we really need this?
ExpandoMetaClass.enableGlobally(); //TODO: copied from GrailsScriptRunner. What's this for?
saveSystemProperties();
}
public File getBaseDir() {
if (buildSettings==null) {
return baseDir; // not yet initialised, so our originally set baseDir is the one.
} else {
//buildSettings already created its state takes priority over the 'original' baseDir
return buildSettings.getBaseDir();
}
// The above appears to work, but below is a ,ore conservative version: treats mismatching baseDir between buildSettings and our own instance as
// a 'corrupted' state.
// if (baseDir!=null) {
// if (getBuildSettings()!=null) {
// //BuildSettings and initial basedir must agree, otherwise flaky results ensue.
// if (baseDir.equals(getBuildSettings().getBaseDir())) {
// return baseDir;
// } else {
// return null; //To the client this will mean we are in an not very well defined state
// // and the process shouldn't be re-used.
// }
// }
// }
// return baseDir;
}
private BuildSettings createBuildSettings() {
BuildSettings buildSettings = null;
// Get hold of the grails.home system property.
String grailsHome = System.getProperty("grails.home");
try {
buildSettings = new BuildSettings(new File(grailsHome), baseDir);
buildSettings.setRootLoader((URLClassLoader) this.getClass().getClassLoader());
} catch (Exception e) {
throw new Error("An error occurred loading the grails-app/conf/BuildConfig.groovy file: " + e.getMessage());
}
return buildSettings;
}
private void ensureInitialized() {
if (buildSettings==null) {
buildSettings = createBuildSettings();
scriptRunner = new GrailsScriptRunner(buildSettings);
GrailsConsole.getInstance().log("Loading Grails "+buildSettings.getGrailsVersion());
// File grailsHome = buildSettings.getGrailsHome();
// debug("Starting Remote Script runner for Grails " + buildSettings.getGrailsVersion());
// debug("Grails home is " + (grailsHome == null ? "not set" : "set to: " + grailsHome) + '\n');
buildSettings.loadConfig();
//if (resolveDeps) {
ClasspathConfigurer.cleanResolveCache(buildSettings);
buildSettings.setModified(true);
//}
scriptRunner.initializeState();
if (BuildSettingsHolder.getSettings()==null) {
//^^^ See https://issuetracker.springsource.com/browse/STS-3358
//in Grails 2.2.2 "schema export" command
//will throw an NPE if buildSettings isn't explicitly set by us. I.e even though we pass
//buildSettings to GrailsScriptRunner Grails 2.2.2 does not end up putting it
//into BuildSettingsHolder so we must do it ourselves.
//Note: do this as late as possible. If we do it too early things are going !@#$
// and a lot of other commands (not schema-export are breaking).
BuildSettingsHolder.setSettings(buildSettings);
}
//scriptRunner.setInteractive(false);
} else {
GrailsConsole.getInstance().log("Loading Grails "+buildSettings.getGrailsVersion());
}
}
public int executeCommand(String cmdString, GrailsConsole console) {
resetSystemProperties();
ReflectionHacks.GrailsConsole_setInstance(console);
CommandLine command = parser.parseString(cmdString);
if (command.hasOption(CommandLine.REFRESH_DEPENDENCIES_ARGUMENT)) {
if (buildSettings!=null) {
buildSettings.setModified(true);
}
// buildSettings.setIn
//buildSettings = null; //force complete(?) reinitialization
}
ensureInitialized();
scriptRunner.setInteractive(!command.hasOption(CommandLine.NON_INTERACTIVE_ARGUMENT));
return scriptRunner.executeScriptWithCaching(command);
}
public BuildSettings getBuildSettings() {
return buildSettings;
}
private void saveSystemProperties() {
try {
savedSystemProps = new HashMap<String, String>();
Properties currentProps = System.getProperties();
Enumeration<?> enumeration = currentProps.propertyNames();
while (enumeration.hasMoreElements()) {
String prop = (String)enumeration.nextElement();
savedSystemProps.put(prop, System.getProperty(prop));
}
} catch (Exception e) {
savedSystemProps = null;
}
}
private void resetSystemProperties() {
if (savedSystemProps!=null) {
Properties currentProps = System.getProperties();
//1: clear any properties that got added since we saved...
Enumeration<?> enumeration = currentProps.propertyNames();
while (enumeration.hasMoreElements()) {
String prop = (String)enumeration.nextElement();
if (!savedSystemProps.containsKey(prop)) {
System.clearProperty(prop);
}
}
//2: reset values of any properties that were set when we saved
for (Entry<String, String> entry : savedSystemProps.entrySet()) {
if (entry.getValue()!=null) {
System.setProperty(entry.getKey(), entry.getValue());
}
}
}
}
}