Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ARQ-1729 Add ability to deploy application to WAS Liberty server.xml file #16

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,13 @@
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.jboss.arquillian.container.spi.client.container.DeployableContainer;
import org.jboss.arquillian.container.spi.client.container.DeploymentException;
Expand All @@ -41,6 +48,10 @@
import org.jboss.shrinkwrap.api.Archive;
import org.jboss.shrinkwrap.api.exporter.ZipExporter;
import org.jboss.shrinkwrap.descriptor.api.Descriptor;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import com.sun.tools.attach.VirtualMachine;
import com.sun.tools.attach.VirtualMachineDescriptor;
Expand Down Expand Up @@ -228,7 +239,7 @@ private String findVirtualMachineIdByName(String serverName) {

return null;
}

public ProtocolMetaData deploy(final Archive<?> archive) throws DeploymentException
{
if (log.isLoggable(Level.FINER)) {
Expand All @@ -237,47 +248,108 @@ public ProtocolMetaData deploy(final Archive<?> archive) throws DeploymentExcept
log.finer("Archive provided to deploy method: " + archive.toString(true));
}

// Save the archive to disk so it can be loaded by the container.
String dropInDir = getDropInDirectory();
File exportedArchiveLocation = new File(dropInDir, archive.getName());
archive.as(ZipExporter.class).exportTo(exportedArchiveLocation, true);

// Wait until the application is deployed and available
waitForApplicationTargetState(createDeploymentName(archive.getName()), true, containerConfiguration.getAppDeployTimeout());

// Return metadata on how to contact the deployed application
ProtocolMetaData metaData = new ProtocolMetaData();
HTTPContext httpContext = new HTTPContext("localhost", containerConfiguration.getHttpPort());
httpContext.add(new Servlet("ArquillianServletRunner", createDeploymentName(archive.getName())));
metaData.addContext(httpContext);
String archiveName = archive.getName();
String archiveType = createDeploymentType(archiveName);
String deployName = createDeploymentName(archiveName);

if (log.isLoggable(Level.FINER)) {
log.exiting(className, "deploy");
try {
// If the deployment is to server.xml, then update server.xml with the application information
if (containerConfiguration.isDeployTypeXML()) {
// Throw error if deployment type is not ear, war, or eba
if (!archiveType.equalsIgnoreCase("ear") && !archiveType.equalsIgnoreCase("war") && !archiveType.equalsIgnoreCase("eba"))
throw new DeploymentException("Invalid archive type: " + archiveType + ". Valid archive types are ear, war, and eba.");

// Save the archive to disk so it can be loaded by the container.
String appDir = getAppDirectory();
File exportedArchiveLocation = new File(appDir, archiveName);
archive.as(ZipExporter.class).exportTo(exportedArchiveLocation, true);

// Read server.xml file into Memory
Document document = readServerXML();

// Add the archive as appropriate to the server.xml file
addApplication(document, deployName, archiveName, archiveType);

// Update server.xml on file system
writeServerXML(document);
}
// Otherwise put the application in the dropins directory
else {
// Save the archive to disk so it can be loaded by the container.
String dropInDir = getDropInDirectory();
File exportedArchiveLocation = new File(dropInDir, archiveName);
archive.as(ZipExporter.class).exportTo(exportedArchiveLocation, true);
}

// Wait until the application is deployed and available
waitForApplicationTargetState(deployName, true, containerConfiguration.getAppDeployTimeout());

// Return metadata on how to contact the deployed application
ProtocolMetaData metaData = new ProtocolMetaData();
HTTPContext httpContext = new HTTPContext("localhost", containerConfiguration.getHttpPort());
httpContext.add(new Servlet("ArquillianServletRunner", deployName));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there no API in Liberty that would allow you to dynamically resolve contextRoots for the different Servlets within a Deployment?

That would allow the user to use apis like @ArquillianResource(MySerlvet.class) to resolve the correct contextRoot within a multi module deployment.

metaData.addContext(httpContext);

if (log.isLoggable(Level.FINER)) {
log.exiting(className, "deploy");
}

return metaData;
} catch (Exception e) {
throw new DeploymentException("Exception while deploying application.", e);
}

return metaData;
}

public void undeploy(final Archive<?> archive) throws DeploymentException
{
if (log.isLoggable(Level.FINER)) {
log.entering(className, "undeploy");
}

// Remove archive from the dropIn directory, which causes undeploy
String dropInDir = getDropInDirectory();
File exportedArchiveLocation = new File(dropInDir, archive.getName());
if (!exportedArchiveLocation.delete())
throw new DeploymentException("Unable to delete archive from dropIn directory");

String archiveName = archive.getName();
String deployName = createDeploymentName(archiveName);

try {
//If deploy type is xml, then remove the application from the xml file, which causes undeploy
if (containerConfiguration.isDeployTypeXML()) {
// Read the server.xml file into Memory
Document document = readServerXML();

// Wait until the application is undeployed
waitForApplicationTargetState(createDeploymentName(archive.getName()), false, containerConfiguration.getAppUndeployTimeout());
// Remove the archive from the server.xml file
removeApplication(document);

if (log.isLoggable(Level.FINER)) {
// Update server.xml on file system
writeServerXML(document);

// Wait until the application is undeployed
waitForApplicationTargetState(deployName, false, containerConfiguration.getAppUndeployTimeout());

// Remove archive from the apps directory
String appDir = getAppDirectory();
File exportedArchiveLocation = new File(appDir, archiveName);
if (!exportedArchiveLocation.delete())
throw new DeploymentException("Unable to delete archive from apps directory");
}
else {
// Remove archive from the dropIn directory, which causes undeploy
String dropInDir = getDropInDirectory();
File exportedArchiveLocation = new File(dropInDir, archiveName);
if (!exportedArchiveLocation.delete())
throw new DeploymentException("Unable to delete archive from dropIn directory");

// Wait until the application is undeployed
waitForApplicationTargetState(deployName, false, containerConfiguration.getAppUndeployTimeout());
}

} catch (Exception e) {
throw new DeploymentException("Exception while undeploying application.", e);
}

if (log.isLoggable(Level.FINER)) {
log.exiting(className, "undeploy");
}
}
}

private String getDropInDirectory() {
String dropInDir = containerConfiguration.getWlpHome() + "/usr/servers/" +
containerConfiguration.getServerName() + "/dropins";
Expand All @@ -286,11 +358,91 @@ private String getDropInDirectory() {
return dropInDir;
}

private String getAppDirectory() {
String appDir = containerConfiguration.getWlpHome() + "/usr/servers/" +
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might want to use File.separator instead of /, even tho / will currently work on both win and *nix systems.

containerConfiguration.getServerName() + "/apps";
if (log.isLoggable(Level.FINER))
log.finer("appDir: " + appDir);
return appDir;
}

private String getServerXML() {
String serverXML = containerConfiguration.getWlpHome() + "/usr/servers/" +
containerConfiguration.getServerName() + "/server.xml";
if (log.isLoggable(Level.FINER))
log.finer("server.xml: " + serverXML);
return serverXML;
}

private String createDeploymentName(String archiveName)
{
return archiveName.substring(0, archiveName.lastIndexOf("."));
}

private String createDeploymentType(String archiveName)
{
return archiveName.substring(archiveName.lastIndexOf(".")+1);
}

private Document readServerXML() throws DeploymentException {
try {
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
return documentBuilder.parse(new File(getServerXML()));
} catch (Exception e) {
throw new DeploymentException("Exception while reading server.xml file.", e);
}
}

private void writeServerXML(Document doc) throws DeploymentException {
try {
TransformerFactory tf = TransformerFactory.newInstance();
Transformer tr = tf.newTransformer();
tr.setOutputProperty(OutputKeys.INDENT, "yes");
DOMSource source = new DOMSource(doc);
StreamResult res = new StreamResult(new File(getServerXML()));
tr.transform(source, res);
} catch (Exception e) {
throw new DeploymentException("Exception wile writing server.xml file.", e);
}
}

private Element createApplication(Document doc, String deploymentName, String archiveName, String type) {

//create new Application
Element application = doc.createElement("application");
application.setAttribute("id", deploymentName);
application.setAttribute("location", archiveName);
application.setAttribute("name", deploymentName);
application.setAttribute("type", type);

//create shared library
if (containerConfiguration.getSharedLib() != null) {
Element sharedLib = doc.createElement("classloader");
sharedLib.setAttribute("commonLibraryRef", containerConfiguration.getSharedLib());
application.appendChild(sharedLib);
}

return application;
}

private void addApplication(Document doc, String deployName, String archiveName, String type) {
NodeList rootList = doc.getElementsByTagName("server");
Node root = rootList.item(0);
root.appendChild(createApplication(doc, deployName, archiveName, type));
}

private void removeApplication(Document doc) {
Node server = doc.getElementsByTagName("server").item(0);
NodeList serverlist = server.getChildNodes();
for (int i=0; serverlist.getLength() > i; i++) {
Node node = serverlist.item(i);
if (node.getNodeName().equals("application")) {
node.getParentNode().removeChild(node);
}
}
}

private void waitForApplicationTargetState(String applicationName, boolean targetState, int timeout) throws DeploymentException {
if (log.isLoggable(Level.FINER)) {
log.entering(className, "waitForMBeanTargetState");
Expand All @@ -311,7 +463,7 @@ private void waitForApplicationTargetState(String applicationName, boolean targe
try {
int timeleft = timeout * 1000;
while(mbsc.isRegistered(appMBean) != targetState) {
Thread.sleep(100);
Thread.sleep(1000);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we really want to wait a whole second for each attempt?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm going to backout this particular line and increase the appDeployTimeout instead. This will have the effect of waiting longer, but not sleeping longer.

if (timeleft <= 0)
throw new DeploymentException("Timeout while waiting for ApplicationMBean to reach targetState");
timeleft -= 100;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ public class WLPManagedContainerConfiguration implements
private int serverStartTimeout = 30;
private int appDeployTimeout = 2;
private int appUndeployTimeout = 2;
private String sharedLib = null;
private String deployType = "dropins";

private boolean allowConnectingToRunningServer = Boolean.parseBoolean(
System.getProperty("org.jboss.arquillian.container.was.wlp_managed_8_5.allowConnectingToRunningServer", "false"));
Expand All @@ -56,6 +58,19 @@ public void validate() throws ConfigurationException {
// Validate httpPort
if (httpPort > 65535 || httpPort <= 0)
throw new ConfigurationException("httpPort provided is not valid: " + httpPort);

// Validate deployType
if (!deployType.equalsIgnoreCase("xml") && !deployType.equalsIgnoreCase("dropins"))
throw new ConfigurationException("deployType provided is not valid: " + deployType + ". deployType should be xml or dropins.");

//Validate sharedLib
if (!sharedLib.equals(null)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If sharedLib is not set via xml to 'something', this cause NullPointerException

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm going to add a commit to change this to:

  if (sharedLib != null) {

if (!sharedLib.isEmpty()) {
if (!deployType.equalsIgnoreCase("xml"))
throw new ConfigurationException("deployType must be set to xml when sharedLib is not empty");
}
}

}

public String getWlpHome() {
Expand All @@ -81,7 +96,22 @@ public int getHttpPort() {
public void setHttpPort(int httpPort) {
this.httpPort = httpPort;
}

public void setSharedLib(String sharedLib) {
this.sharedLib = sharedLib;
}

public String getSharedLib() {
return sharedLib;
}

public void setDeployType(String deployType) {
this.deployType = deployType;
}

public String getDeployType() {
return deployType;
}
public boolean isAllowConnectingToRunningServer() {
return allowConnectingToRunningServer;
}
Expand Down Expand Up @@ -117,5 +147,19 @@ public int getAppUndeployTimeout() {
public void setAppUndeployTimeout(int appUndeployTimeout) {
this.appUndeployTimeout = appUndeployTimeout;
}

public boolean isDeployTypeXML() {
if (deployType.equalsIgnoreCase("xml"))
return true;
else
return false;
}

public boolean isDeployTypeDropins() {
if (deployType.equalsIgnoreCase("dropins"))
return true;
else
return false;
}

}