/*
* Copyright 2014 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.codeserver;
import java.net.InetAddress;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.cruxframework.crux.core.rebind.DevelopmentScanners;
import org.cruxframework.crux.core.rebind.module.Module;
import org.cruxframework.crux.core.rebind.module.Modules;
import org.cruxframework.crux.core.rebind.screen.ScreenResourceResolverInitializer;
import org.cruxframework.crux.core.server.CruxBridge;
import org.cruxframework.crux.core.server.dispatch.ServiceFactoryInitializer;
import org.cruxframework.crux.core.server.rest.core.registry.RestServiceFactoryInitializer;
import org.cruxframework.crux.scanner.ClasspathUrlFinder;
import org.cruxframework.crux.scanner.Scanners;
import org.cruxframework.crux.tools.codeserver.CodeServerRecompileListener.CompilationCallback;
import org.cruxframework.crux.tools.codeserver.client.CodeServerNotifier;
import org.cruxframework.crux.tools.compile.CruxRegisterUtil;
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.ConsoleParametersProcessingException;
import org.cruxframework.crux.tools.parameters.ConsoleParametersProcessor;
import org.cruxframework.crux.tools.server.JettyDevServer;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.nio.SelectChannelConnector;
import org.eclipse.jetty.websocket.WebSocket;
import org.eclipse.jetty.websocket.WebSocket.Connection;
import org.eclipse.jetty.websocket.WebSocketHandler;
import org.json.JSONObject;
import com.google.gwt.dev.codeserver.Options;
/**
* @author Thiago da Rosa de Bustamante
*
*/
public class CodeServer
{
private static final Log logger = LogFactory.getLog(CodeServer.class);
private String moduleName;
private String sourceDir;
private boolean noPrecompile = false;
private String workDir;
private String bindAddress = "localhost";
private int port = getDefaultPort();
private int notificationServerPort = getDefaultNotificationPort();
private String webDir;
private boolean startHotDeploymentScanner;
@SuppressWarnings("unused")
private boolean startJetty = false;
private String userAgent;
private String locale;
private CodeServerRecompileListener recompileListener;
private Connection compilerNotificationConnection;
public String getModuleName()
{
return moduleName;
}
public void setModuleName(String moduleName)
{
this.moduleName = moduleName;
}
public String getSourceDir()
{
return sourceDir;
}
public void setSourceDir(String sourceDir)
{
this.sourceDir = sourceDir;
}
public String getWorkDir()
{
return workDir;
}
public void setWorkDir(String workDir)
{
this.workDir = workDir;
}
protected int getDefaultPort()
{
return 9876;
}
protected int getDefaultNotificationPort()
{
return CodeServerNotifier.DEFAULT_COMPILER_NOTIFIER_PORT;
}
protected void execute() throws Exception
{
URL[] urls = ClasspathUrlFinder.findClassPaths();
Scanners.setSearchURLs(urls);
ModuleUtils.initializeScannerURLs(urls);
CruxRegisterUtil.registerFilesCruxBridge(null);
DevelopmentScanners.initializeScanners();
ServiceFactoryInitializer.initialize(null);
RestServiceFactoryInitializer.initialize(null);
Set<String> screenIDs = null;
try
{
screenIDs = ScreenResourceResolverInitializer.getScreenResourceResolver().getAllScreenIDs(moduleName);
}
catch (Exception e)
{
logger.info("Error retrieving crux pages list for module ["+moduleName+"]. "
+ "Please, verify if the module name parameter matches the module short name on your .gwt file", e);
}
if (screenIDs != null && !screenIDs.isEmpty())
{
logger.info("Starting code server for module ["+moduleName+"]");
CruxBridge.getInstance().registerLastPageRequested(screenIDs.iterator().next());
String[] args = getServerParameters();
initializeRecompileListener();
runGWTCodeServer(args);
}
}
protected void initializeRecompileListener()
{
recompileListener = new CodeServerRecompileListener(webDir);
if (startHotDeploymentScanner)
{
HotDeploymentScanner.scanProjectDirs(bindAddress, port, moduleName, userAgent, locale);
runCompileNotificationServer();
registerCompileNotificationCallback();
}
}
protected void registerCompileNotificationCallback()
{
recompileListener.setCompilationCallback(new CompilationCallback()
{
@Override
public void onCompilationStart(String moduleName)
{
if (compilerNotificationConnection != null)
{
try
{
JSONObject data = new JSONObject();
data.put("op", "START");
data.put("module", moduleName);
compilerNotificationConnection.sendMessage(data.toString());
}
catch (Exception e)
{
logger.error("Error notifying client about module compilation", e);
}
}
}
@Override
public void onCompilationEnd(String moduleName, boolean success)
{
if (compilerNotificationConnection != null)
{
try
{
JSONObject data = new JSONObject();
data.put("op", "END");
data.put("module", moduleName);
data.put("status", success);
compilerNotificationConnection.sendMessage(data.toString());
}
catch (Exception e)
{
logger.error("Error notifying client about module compilation", e);
}
}
}
});
}
protected void runCompileNotificationServer()
{
try
{
SelectChannelConnector connector = new SelectChannelConnector();
connector.setHost(bindAddress);
connector.setPort(notificationServerPort);
connector.setReuseAddress(false);
connector.setSoLingerTime(0);
Server server = new Server();
server.addConnector(connector);
server.setHandler(new WebSocketHandler()
{
@Override
public WebSocket doWebSocketConnect(HttpServletRequest arg0, String arg1)
{
return new WebSocket()
{
@Override
public void onOpen(Connection connection)
{
compilerNotificationConnection = connection;
}
@Override
public void onClose(int closeCode, String message)
{
compilerNotificationConnection = null;
}
};
}
});
server.start();
}
catch (Exception e)
{
logger.error("Error starting the compilation notifier service.", e);
}
}
protected void runGWTCodeServer(String[] args) throws Exception
{
Options options = new Options();
if (!options.parseArgs(args))
{
System.exit(1);
}
options.setRecompileListener(recompileListener);
try
{
com.google.gwt.dev.codeserver.CodeServer.main(options);
}
catch (Throwable t)
{
logger.error("Error running code server", t);
}
}
protected String[] getServerParameters()
{
List<String> args = new ArrayList<String>();
if (noPrecompile)
{
args.add("-noprecompile");
}
if (bindAddress != null && bindAddress.length() > 0)
{
args.add("-bindAddress");
args.add(bindAddress);
}
if (port > 0)
{
args.add("-port");
args.add(Integer.toString(port));
}
if (workDir != null && workDir.length() > 0)
{
args.add("-workDir");
args.add(workDir);
}
if (sourceDir != null && sourceDir.length() > 0)
{
args.add("-src");
args.add(sourceDir);
}
if (moduleName != null && moduleName.length() > 0)
{
String moduleFullName = Modules.getInstance().getModule(moduleName).getFullName();
args.add(moduleFullName);
}
if(StringUtils.isEmpty(userAgent))
{
String foundUserAgent = findUserAgentInModules(moduleName);
if(foundUserAgent == null)
{
logger.error("Please inform an user agent property in the "+moduleName+" (.gwt.xml) config file.");
System.exit(1);
}
userAgent = foundUserAgent;
}
return args.toArray(new String[args.size()]);
}
private String findUserAgentInModules(String currentModuleName)
{
Module module = Modules.getInstance().getModule(currentModuleName);
if(module == null)
{
return null;
}
if(module.getUserAgent() != null)
{
return module.getUserAgent();
}
for(String inherits : module.getInherits())
{
String foundUserAgent = findUserAgentInModules(inherits);
if(StringUtils.isEmpty(foundUserAgent))
{
return foundUserAgent;
}
}
return null;
}
protected void processParameters(Collection<ConsoleParameter> parameters)
{
for (ConsoleParameter parameter : parameters)
{
if (parameter.getName().equals("-moduleName"))
{
moduleName = parameter.getValue();
}
else if (parameter.getName().equals("-noprecompile"))
{
this.noPrecompile = true;
}
else if (parameter.getName().equals("-startHotDeploymentScanner"))
{
this.startHotDeploymentScanner = true;
}
else if (parameter.getName().equals("-startJetty"))
{
this.startJetty = true;
}
else if (parameter.getName().equals("-userAgent"))
{
userAgent = parameter.getValue();
}
else if (parameter.getName().equals("-locale"))
{
locale = parameter.getValue();
}
else if (parameter.getName().equals("-sourceDir"))
{
sourceDir = parameter.getValue();
}
else if (parameter.getName().equals("-bindAddress"))
{
try
{
InetAddress bindAddress = InetAddress.getByName(parameter.getValue());
if (bindAddress.isAnyLocalAddress())
{
this.bindAddress = InetAddress.getLocalHost().getHostAddress();
}
else
{
this.bindAddress = parameter.getValue();
}
}
catch(Exception e)
{
//Use default
}
}
else if (parameter.getName().equals("-port"))
{
port =Integer.parseInt(parameter.getValue());
}
else if (parameter.getName().equals("-notificationServerPort"))
{
notificationServerPort =Integer.parseInt(parameter.getValue());
}
else if (parameter.getName().equals("-workDir"))
{
workDir = parameter.getValue();
}
else if (parameter.getName().equals("-webDir"))
{
webDir = parameter.getValue();
}
}
}
protected ConsoleParametersProcessor createParametersProcessor()
{
ConsoleParameter parameter;
ConsoleParametersProcessor parametersProcessor = new ConsoleParametersProcessor("CodeServer");
parameter = new ConsoleParameter("-moduleName", "The name of the module to be compiled.", false, true);
parameter.addParameterOption(new ConsoleParameterOption("name", "Module name"));
parametersProcessor.addSupportedParameter(parameter);
parameter = new ConsoleParameter("-sourceDir", "The application source folder.", false, true);
parameter.addParameterOption(new ConsoleParameterOption("dir", "Source dir"));
parametersProcessor.addSupportedParameter(parameter);
parameter = new ConsoleParameter("-bindAddress", "The ip address of the code server. Defaults to 127.0.0.1.", false, true);
parameter.addParameterOption(new ConsoleParameterOption("ip", "Ip address"));
parametersProcessor.addSupportedParameter(parameter);
parametersProcessor.addSupportedParameter(new ConsoleParameter("-noprecompile", "If informed, code server will not pre compile the source.", false, true));
parametersProcessor.addSupportedParameter(new ConsoleParameter("-startHotDeploymentScanner", "If informed, a hotdeployment scanner will be started to automatically recompile the module when changes are made on the project.", false, true));
parametersProcessor.addSupportedParameter(new ConsoleParameter("-startJetty", "If informed, starts the default application server (Jetty).", false, true));
parameter = new ConsoleParameter("-userAgent", "The userAgent used by hotdeployment scanner to recompile the project.", false, true);
parameter.addParameterOption(new ConsoleParameterOption("agent", "user agent"));
parametersProcessor.addSupportedParameter(parameter);
parameter = new ConsoleParameter("-locale", "The locale used by hotdeployment scanner to recompile the project.", false, true);
parameter.addParameterOption(new ConsoleParameterOption("locale", "locale"));
parametersProcessor.addSupportedParameter(parameter);
parameter = new ConsoleParameter("-port", "The port where the code server will run.", false, true);
parameter.addParameterOption(new ConsoleParameterOption("port", "Port"));
parametersProcessor.addSupportedParameter(parameter);
parameter = new ConsoleParameter("-notificationServerPort", "The port where the compile notification server will run.", false, true);
parameter.addParameterOption(new ConsoleParameterOption("port", "Port"));
parametersProcessor.addSupportedParameter(parameter);
parameter = new ConsoleParameter("-workDir", "The root of the directory tree where the code server will write compiler output. If not supplied, a temporary directory will be used.", false, true);
parameter.addParameterOption(new ConsoleParameterOption("dir", "Work dir"));
parametersProcessor.addSupportedParameter(parameter);
parameter = new ConsoleParameter("-webDir", "The directory to be updated by code server compiler. If provided, after each code server compilation, this folder will be updated.", false, true);
parameter.addParameterOption(new ConsoleParameterOption("dir", "Web dir"));
parametersProcessor.addSupportedParameter(parameter);
parametersProcessor.addSupportedParameter(new ConsoleParameter("-help", "Display the usage screen.", false, true));
parametersProcessor.addSupportedParameter(new ConsoleParameter("-h", "Display the usage screen.", false, true));
return parametersProcessor;
}
public static void main(String[] args)
{
try
{
CodeServer codeServer = new CodeServer();
ConsoleParametersProcessor parametersProcessor = codeServer.createParametersProcessor();
Map<String, ConsoleParameter> parameters = parametersProcessor.processConsoleParameters(args);
if (parameters.containsKey("-help") || parameters.containsKey("-h"))
{
parametersProcessor.showsUsageScreen();
}
else
{
codeServer.processParameters(parameters.values());
codeServer.execute();
if(parameters.containsKey("-startJetty"))
{
JettyDevServer.main(args);
}
}
}
catch (ConsoleParametersProcessingException e)
{
logger.error("Error processing program parameters: "+e.getLocalizedMessage()+". Program aborted.",e);
}
catch (Exception e)
{
logger.error("Error running code server: "+e.getLocalizedMessage()+". Program aborted.",e);
}
}
}