/*
* Copyright (C) 2015 Red Hat, Inc. and/or its affiliates.
*
* 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.jboss.errai.cdi.server.gwt;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.BindException;
import javax.xml.stream.XMLStreamException;
import org.apache.commons.io.IOUtils;
import org.jboss.errai.cdi.server.as.JBossServletContainerAdaptor;
import org.jboss.errai.cdi.server.gwt.util.SimpleTranslator;
import org.jboss.errai.cdi.server.gwt.util.SimpleTranslator.AttributeEntry;
import org.jboss.errai.cdi.server.gwt.util.SimpleTranslator.Tag;
import org.jboss.errai.cdi.server.gwt.util.StackTreeLogger;
import org.jboss.errai.cdi.server.gwt.util.JBossUtil;
import com.google.gwt.core.ext.ServletContainer;
import com.google.gwt.core.ext.ServletContainerLauncher;
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.TreeLogger.Type;
import com.google.gwt.core.ext.UnableToCompleteException;
/**
* For starting a {@link JBossServletContainerAdaptor} controlling a standalone
* Jboss/Wildfly AS.
*
* @author Max Barkley <mbarkley@redhat.com>
*/
public class JBossLauncher extends ServletContainerLauncher {
// Property names
private final String JBOSS_DEBUG_PORT_PROPERTY = "errai.jboss.debug.port";
private final String TEMPLATE_CONFIG_FILE_PROPERTY = "errai.jboss.config.file";
private final String CLASS_HIDING_JAVA_AGENT_PROPERTY = "errai.jboss.javaagent.path";
private final String JBOSS_JAVA_OPTS_PROPERTY = "errai.jboss.javaopts";
private final String TMP_CONFIG_FILE = "standalone-errai-dev.xml";
private final String JBOSS_HTTP_REMOTING_ADDRESS = "errai.jboss.httpremotingaddress";
private final String JBOSS_NATIVE_REMOTING_ADDRESS = "errai.jboss.nativeremotingaddress";
private StackTreeLogger logger;
@Override
public ServletContainer start(TreeLogger treeLogger, int port, File appRootDir) throws BindException, Exception {
logger = new StackTreeLogger(treeLogger);
logger.branch(Type.INFO, "Server launcher starting..");
// Get properties
final String DEBUG_PORT = System.getProperty(JBOSS_DEBUG_PORT_PROPERTY, "8001");
final String TEMPLATE_CONFIG_FILE = System.getProperty(TEMPLATE_CONFIG_FILE_PROPERTY, "standalone-full.xml");
final String CLASS_HIDING_JAVA_AGENT = System.getProperty(CLASS_HIDING_JAVA_AGENT_PROPERTY);
String JAVA_OPTS = System.getProperty(JBOSS_JAVA_OPTS_PROPERTY, "");
final String HTTP_REMOTING_ADDRESS = System.getProperty(JBOSS_HTTP_REMOTING_ADDRESS, "http-remoting://localhost:9990");
final String NATIVE_REMOTING_ADDRESS = System.getProperty(JBOSS_NATIVE_REMOTING_ADDRESS, "remote://localhost:9999");
final String jbossHome = JBossUtil.getJBossHome(logger);
validateClassHidingJavaAgent(CLASS_HIDING_JAVA_AGENT);
try {
createTempConfigFile(TEMPLATE_CONFIG_FILE, TMP_CONFIG_FILE, jbossHome, port);
logger.log(Type.INFO,
String.format("Created temporary config file %s, copied from %s.", TMP_CONFIG_FILE, TEMPLATE_CONFIG_FILE));
}
catch (IOException e) {
logger.log(
Type.ERROR,
String.format("Unable to create temporary config file %s from %s", TMP_CONFIG_FILE, TEMPLATE_CONFIG_FILE),
e);
}
final String JBOSS_START = JBossUtil.getStartScriptName(jbossHome);
Process process;
try {
logger.branch(Type.INFO, String.format("Preparing JBoss AS instance (%s)", JBOSS_START));
final File startScript = new File(JBOSS_START);
if (!startScript.canExecute() && !startScript.setExecutable(true)) {
logger.log(Type.ERROR, "Can not execute " + JBOSS_START);
throw new UnableToCompleteException();
}
ProcessBuilder builder = new ProcessBuilder(JBOSS_START, "-c", TMP_CONFIG_FILE);
logger.log(Type.INFO, String.format("Adding JBOSS_HOME=%s to instance environment", jbossHome));
// Necessary for JBoss AS instance to startup
builder.environment().put("JBOSS_HOME", jbossHome);
// Allows JVM to be debugged
builder.environment().put(
"JAVA_OPTS",
String.format("%s -Xrunjdwp:transport=dt_socket,address=%s,server=y,suspend=n -javaagent:%s", JAVA_OPTS,
DEBUG_PORT, CLASS_HIDING_JAVA_AGENT).trim());
process = builder.start();
logger.log(Type.INFO, "Redirecting stdout and stderr to share with this process");
inheritIO(process.getInputStream(), System.out);
inheritIO(process.getErrorStream(), System.err);
logger.log(Type.INFO, "Executing AS instance...");
}
catch (IOException e) {
logger.log(TreeLogger.Type.ERROR, "Failed to start JBoss AS process", e);
logger.unbranch();
throw new UnableToCompleteException();
}
logger.unbranch();
logger.branch(Type.INFO, "Creating servlet container controller...");
try {
JBossServletContainerAdaptor controller = new JBossServletContainerAdaptor(port, appRootDir, JBossUtil.getDeploymentContext(), logger.peek(), process, HTTP_REMOTING_ADDRESS, NATIVE_REMOTING_ADDRESS);
logger.log(Type.INFO, "Controller created");
logger.unbranch();
return controller;
}
catch (UnableToCompleteException e) {
logger.log(Type.ERROR, "Could not start servlet container controller", e);
throw new UnableToCompleteException();
}
}
private void validateClassHidingJavaAgent(final String CLASS_HIDING_JAVA_AGENT) throws UnableToCompleteException {
if (CLASS_HIDING_JAVA_AGENT == null) {
logger.log(
Type.ERROR,
String.format(
"The local path to the artifact errai.org.jboss:class-local-class-hider:jar must be given as the property %s",
CLASS_HIDING_JAVA_AGENT_PROPERTY));
throw new UnableToCompleteException();
}
}
private void createTempConfigFile(String fromName, String toName, String jBossHome, int port) throws IOException,
UnableToCompleteException {
File configDir = new File(jBossHome, JBossUtil.STANDALONE_CONFIGURATION);
File from = new File(configDir, fromName);
File to = new File(configDir, toName);
if (!from.exists()) {
logger.log(
Type.ERROR,
String.format(
"Config file %s does not exit. It must be created or another one must be specified with the %s JVM property.",
from.getAbsolutePath(), TEMPLATE_CONFIG_FILE_PROPERTY));
throw new UnableToCompleteException();
}
if (to.exists()) {
logger.log(Type.WARN,
String.format("Temporary config file %s already exists and will be deleted", to.getAbsolutePath()));
to.delete();
}
to.createNewFile();
to.deleteOnExit();
InputStream inStream = new FileInputStream(from);
OutputStream outStream = new FileOutputStream(to);
// Replace default http port with provided port
SimpleTranslator trans = new SimpleTranslator();
trans.addFilter(new Tag("socket-binding", new AttributeEntry("name", "http")));
trans.addNewTag("socket-binding-group", new Tag("socket-binding", new AttributeEntry("name", "http"),
new AttributeEntry("port", String.valueOf(port))));
try {
trans.translate(inStream, outStream);
}
catch (XMLStreamException e) {
logger.log(Type.ERROR, "Could not create copy of configuration from " + from.getAbsolutePath(), e);
throw new UnableToCompleteException();
}
finally {
inStream.close();
outStream.close();
}
}
private void inheritIO(final InputStream in, final OutputStream to) {
new Thread() {
@Override
public void run() {
try {
IOUtils.copy(in, to);
}
catch (IOException e) {
throw new RuntimeException(e);
}
}
}.start();
}
}