/* * JBoss, Home of Professional Open Source * Copyright 2009-2011, Red Hat Middleware LLC, and individual contributors * by the @authors tag. See the copyright.txt in the distribution for a * full listing of individual contributors. * * 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.arquillian.container.was.remote_8_5; import java.io.File; import java.io.StringReader; import java.lang.IllegalStateException; import java.util.Arrays; import java.util.Hashtable; import java.util.List; import java.util.Locale; import java.util.Properties; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; import javax.management.Attribute; import javax.management.AttributeList; import javax.management.InstanceNotFoundException; import javax.management.MalformedObjectNameException; import javax.management.NotificationFilterSupport; import javax.management.ObjectName; import javax.xml.xpath.XPath; import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathFactory; import org.jboss.arquillian.container.spi.client.container.DeployableContainer; import org.jboss.arquillian.container.spi.client.container.DeploymentException; import org.jboss.arquillian.container.spi.client.container.LifecycleException; import org.jboss.arquillian.container.spi.client.protocol.ProtocolDescription; import org.jboss.arquillian.container.spi.client.protocol.metadata.HTTPContext; import org.jboss.arquillian.container.spi.client.protocol.metadata.ProtocolMetaData; import org.jboss.arquillian.container.spi.client.protocol.metadata.Servlet; import org.jboss.shrinkwrap.api.Archive; import org.jboss.shrinkwrap.api.ShrinkWrap; import org.jboss.shrinkwrap.api.asset.StringAsset; import org.jboss.shrinkwrap.api.exporter.ZipExporter; import org.jboss.shrinkwrap.api.spec.EnterpriseArchive; import org.jboss.shrinkwrap.api.spec.WebArchive; import org.jboss.shrinkwrap.api.spec.JavaArchive; import org.jboss.shrinkwrap.descriptor.api.Descriptor; import org.jboss.shrinkwrap.descriptor.api.Descriptors; import org.jboss.shrinkwrap.descriptor.api.application6.ApplicationDescriptor; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.InputSource; import com.ibm.websphere.management.AdminClient; import com.ibm.websphere.management.AdminClientFactory; import com.ibm.websphere.management.application.AppConstants; import com.ibm.websphere.management.application.AppManagement; import com.ibm.websphere.management.application.AppManagementProxy; import com.ibm.websphere.management.application.AppNotification; import com.ibm.websphere.management.application.client.AppDeploymentController; import com.ibm.websphere.management.configservice.ConfigServiceHelper; import com.ibm.websphere.management.configservice.ConfigServiceProxy; import com.ibm.websphere.management.exception.ConfigServiceException; import com.ibm.websphere.management.exception.ConnectorException; /** * WebSphereRemoteContainer * * @author <a href="mailto:aslak@redhat.com">Aslak Knutsen</a> * @author <a href="mailto:gerhard.poul@gmail.com">Gerhard Poul</a> * @version $Revision: $ */ public class WebSphereRemoteContainer implements DeployableContainer<WebSphereRemoteContainerConfiguration> { //-------------------------------------------------------------------------------------|| // Instance Members -------------------------------------------------------------------|| //-------------------------------------------------------------------------------------|| private static final String className = WebSphereRemoteContainer.class.getName(); private static Logger log = Logger.getLogger(className); private WebSphereRemoteContainerConfiguration containerConfiguration; private AdminClient adminClient; //-------------------------------------------------------------------------------------|| // Required Implementations - DeployableContainer -------------------------------------|| //-------------------------------------------------------------------------------------|| /* (non-Javadoc) * @see org.jboss.arquillian.spi.DeployableContainer#setup(org.jboss.arquillian.spi.Context, org.jboss.arquillian.spi.Configuration) */ public void setup(WebSphereRemoteContainerConfiguration configuration) { if (log.isLoggable(Level.FINER)) { log.entering(className, "setup"); } this.containerConfiguration = configuration; if (log.isLoggable(Level.FINER)) { log.exiting(className, "setup"); } } /* (non-Javadoc) * @see org.jboss.arquillian.spi.DeployableContainer#start(org.jboss.arquillian.spi.Context) */ public void start() throws LifecycleException { if (log.isLoggable(Level.FINER)) { log.entering(className, "start"); } Properties wasServerProps = new Properties(); wasServerProps.setProperty(AdminClient.CONNECTOR_HOST, containerConfiguration.getRemoteServerAddress()); wasServerProps.setProperty(AdminClient.CONNECTOR_PORT, String.valueOf(containerConfiguration.getRemoteServerSoapPort())); wasServerProps.setProperty(AdminClient.CONNECTOR_TYPE, AdminClient.CONNECTOR_TYPE_SOAP); wasServerProps.setProperty(AdminClient.USERNAME, containerConfiguration.getUsername()); if (containerConfiguration.getSecurityEnabled()) { wasServerProps.setProperty(AdminClient.CONNECTOR_SECURITY_ENABLED, "true"); wasServerProps.setProperty(AdminClient.PASSWORD, containerConfiguration.getPassword()); wasServerProps.setProperty(AdminClient.CACHE_DISABLED, "false"); wasServerProps.setProperty("javax.net.ssl.trustStore", containerConfiguration.getSslTrustStore()); wasServerProps.setProperty("javax.net.ssl.keyStore", containerConfiguration.getSslKeyStore()); wasServerProps.setProperty("javax.net.ssl.trustStorePassword", containerConfiguration.getSslTrustStorePassword()); wasServerProps.setProperty("javax.net.ssl.keyStorePassword", containerConfiguration.getSslKeyStorePassword()); if (containerConfiguration.getSslTrustStoreType() != null) wasServerProps.setProperty("javax.net.ssl.trustStoreType", containerConfiguration.getSslTrustStoreType()); if (containerConfiguration.getSslKeyStoreType() != null) wasServerProps.setProperty("javax.net.ssl.keyStoreType", containerConfiguration.getSslKeyStoreType()); } else { wasServerProps.setProperty(AdminClient.CONNECTOR_SECURITY_ENABLED, "false"); } try { adminClient = AdminClientFactory.createAdminClient(wasServerProps); ObjectName serverMBean = adminClient.getServerMBean(); String processType = serverMBean.getKeyProperty("processType"); log.fine("CanonicalKeyPropertyListString: " + serverMBean.getCanonicalKeyPropertyListString()); if (processType.equals("DeploymentManager") || processType.equals("NodeAgent") || processType.equals("ManagedProcess")) throw new IllegalStateException("Connecting to a " + processType + " is not supported."); } catch (Exception e) { throw new LifecycleException("Could not create AdminClient: " + e.getMessage(), e); } if (log.isLoggable(Level.FINER)) { log.exiting(className, "start"); } } /* (non-Javadoc) * @see org.jboss.arquillian.spi.DeployableContainer#deploy(org.jboss.arquillian.spi.Context, org.jboss.shrinkwrap.api.Archive) */ public ProtocolMetaData deploy(final Archive<?> archive) throws DeploymentException { if (log.isLoggable(Level.FINER)) { log.entering(className, "deploy"); log.finer("Archive provided to deploy method: " + archive.toString(true)); } File exportedArchiveLocation = null; ProtocolMetaData metaData = null; EnterpriseArchive deploymentArchive = null; // Create an EAR file from the provided Archive that can be processed by AppDeploymentController if (WebArchive.class.isInstance(archive)) { // Packaging a single WAR file into an EAR String earName = archive.getName().substring(0, archive.getName().lastIndexOf(".")); log.fine("Creating an EnterpriseArchive " + earName + ".ear from provided WebArchive " + archive.getName() + "."); // Create ShrinkWrap EnterpriseArchive and add the WAR file as a module deploymentArchive = ShrinkWrap.create(EnterpriseArchive.class, earName + ".ear") .addAsModule(archive); // Generate the application.xml DD and add it to the EAR ApplicationDescriptor appDescriptor = Descriptors.create(ApplicationDescriptor.class); appDescriptor.createModule().getOrCreateWeb().webUri(archive.getName()).contextRoot(earName); deploymentArchive.setApplicationXML( new StringAsset(appDescriptor.exportAsString())); } else if (EnterpriseArchive.class.isInstance(archive)){ // Use the provided EnterpriseArchive as-is deploymentArchive = (EnterpriseArchive) archive; } else { throw new DeploymentException("Unsupported archive type has been provided for deployment: " + archive.getClass().getName()); } String appName = createDeploymentName(deploymentArchive.getName()); String appExtension = createDeploymentExtension(deploymentArchive.getName()); try { exportedArchiveLocation = File.createTempFile(appName, appExtension); deploymentArchive.as(ZipExporter.class).exportTo(exportedArchiveLocation, true); Hashtable<Object, Object> prefs = new Hashtable<Object, Object>(); prefs.put(AppConstants.APPDEPL_LOCALE, Locale.getDefault()); prefs.put(AppConstants.APPDEPL_CLASSLOADINGMODE, containerConfiguration.getDeploymentClassLoadingMode()); prefs.put(AppConstants.APPDEPL_CLASSLOADERPOLICY, containerConfiguration.getDeploymentClassLoaderPolicy()); log.fine(String.format("Deploying with classloading mode %s", containerConfiguration.getDeploymentClassLoadingMode())); log.fine(String.format("Deploying with classloader policy %s", containerConfiguration.getDeploymentClassLoaderPolicy())); Properties props = new Properties(); prefs.put (AppConstants.APPDEPL_DFLTBNDG, props); props.put (AppConstants.APPDEPL_DFLTBNDG_VHOST, "default_host"); // Prepare application for deployment to WebSphere Application Server AppDeploymentController controller = AppDeploymentController .readArchive(exportedArchiveLocation.getAbsolutePath(), prefs); String[] validationResult = controller.validate(); if (validationResult != null && validationResult.length > 0) { throw new DeploymentException("Unable to complete all task data for deployment preparation. Reason: " + Arrays.toString(validationResult)); } controller.saveAndClose(); if (log.isLoggable(Level.FINER)) { // Log the contents of the saved archive from AppDeploymentController Archive<JavaArchive> savedArchive = ShrinkWrap.createFromZipFile(JavaArchive.class, exportedArchiveLocation); log.finer("Archive prepared for deployment: " + savedArchive.toString(true)); } Hashtable<Object, Object> module2Server = new Hashtable<Object, Object>(); ObjectName serverMBean = adminClient.getServerMBean(); String targetServer = "WebSphere:cell=" + serverMBean.getKeyProperty("cell") + ",node=" + serverMBean.getKeyProperty("node") + ",server=" + serverMBean.getKeyProperty("process"); log.info("Target server for deployment is " + targetServer); module2Server.put("*",targetServer); prefs.put(AppConstants.APPDEPL_MODULE_TO_SERVER, module2Server); prefs.put(AppConstants.APPDEPL_ARCHIVE_UPLOAD, containerConfiguration.isArchiveUploadEnabled()); AppManagement appManagementProxy = AppManagementProxy.getJMXProxyForClient(adminClient); NotificationFilterSupport filterSupport = new NotificationFilterSupport(); filterSupport.enableType(AppConstants.NotificationType); DeploymentNotificationListener listener = new DeploymentNotificationListener( adminClient, filterSupport, "Install " + appName, AppNotification.INSTALL); appManagementProxy.installApplication( exportedArchiveLocation.getAbsolutePath(), appName, prefs, null); synchronized(listener) { listener.wait(); } if(!listener.isSuccessful()) throw new IllegalStateException("Application not sucessfully deployed: " + listener.getMessage()); DeploymentNotificationListener distributionListener = null; int checkCount = 0; while (checkDistributionStatus(distributionListener) != AppNotification.DISTRIBUTION_DONE && ++checkCount < 300) { Thread.sleep(1000); distributionListener = new DeploymentNotificationListener( adminClient, filterSupport, null, AppNotification.DISTRIBUTION_STATUS_NODE); synchronized(distributionListener) { appManagementProxy.getDistributionStatus(appName, new Hashtable<Object, Object>(), null); distributionListener.wait(); } } if (checkCount < 300) { String targetsStarted = appManagementProxy.startApplication(appName, null, null); log.info("Application was started on the following targets: " + targetsStarted); if (targetsStarted == null) throw new IllegalStateException("Start of the application was not successful. WAS JVM logs should contain the detailed error message."); } else { throw new IllegalStateException("Distribution of application did not succeed to all nodes."); } metaData = discoverProtocolMetaDataFromConfiguration(adminClient, serverMBean.getKeyProperty("node"), serverMBean.getKeyProperty("process"), appName); } catch (Exception e) { throw new DeploymentException("Could not deploy application", e); } finally { if(exportedArchiveLocation != null) { exportedArchiveLocation.delete(); } } if (log.isLoggable(Level.FINER)) { log.exiting(className, "deploy"); } return metaData; } @SuppressWarnings("rawtypes") private ProtocolMetaData discoverProtocolMetaDataFromConfiguration(AdminClient adminClient, String targetNode, String targetProcess, String appName) throws InstanceNotFoundException, ConnectorException, ConfigServiceException { ProtocolMetaData metaData = new ProtocolMetaData(); String remoteServerAddress = null; int remoteServerHttpPort = 0; ConfigServiceProxy configServiceProxy = new ConfigServiceProxy(adminClient); ObjectName nodeObjectName = ConfigServiceHelper.createObjectName(null, "Node"); ObjectName[] nodeObjectNames = configServiceProxy.queryConfigObjects(null, null, nodeObjectName, null); ObjectName targetNodeObjectName = null; for (ObjectName node : nodeObjectNames) { String nodeName = (String) configServiceProxy.getAttribute(null, node, "name"); if (nodeName.equals(targetNode)) { targetNodeObjectName = node; remoteServerAddress = (String) configServiceProxy.getAttribute(null, targetNodeObjectName, "hostName"); } } if (remoteServerAddress == null || targetNodeObjectName == null) throw new InstanceNotFoundException("Target node " + targetNode + " was not found."); ObjectName serverEntries = ConfigServiceHelper.createObjectName(null, "ServerEntry"); ObjectName[] serverEntryObjectNames = configServiceProxy.queryConfigObjects(null, targetNodeObjectName, serverEntries, null); for (ObjectName serverEntry : serverEntryObjectNames) { String serverName = (String) configServiceProxy.getAttribute(null, serverEntry, "serverName"); if (serverName.equals(targetProcess)) { List specialEndpoints = (List) configServiceProxy.getAttribute(null, serverEntry, "specialEndpoints"); remoteServerHttpPort = getEndpointPort(specialEndpoints, "WC_defaulthost"); } } log.fine("Generating HTTPContext: " + remoteServerAddress + ", " + remoteServerHttpPort); HTTPContext httpContext = new HTTPContext(remoteServerAddress, remoteServerHttpPort); try { Set applicationObjectNameSet = adminClient.queryNames( new ObjectName("WebSphere:type=J2EEApplication,name=" + appName + ",*"), null); if (applicationObjectNameSet.isEmpty()) throw new InstanceNotFoundException("Unable to find application in JMX: " + appName); ObjectName applicationObjectName = (ObjectName)applicationObjectNameSet.iterator().next(); String applicationDD = (String)adminClient.getAttribute(applicationObjectName, "deploymentDescriptor"); log.fine("applicationDD: " + applicationDD); XPath xpath = XPathFactory.newInstance().newXPath(); xpath.setNamespaceContext(new JavaEENamespaceContext()); NodeList webModules = (NodeList) xpath.evaluate("/javaee:application/javaee:module/javaee:web", new InputSource(new StringReader(applicationDD)), XPathConstants.NODESET); if (webModules.getLength() == 0){ //search J2EE 1.4 XML Schemas for backwards compatibility webModules = (NodeList) xpath.evaluate("/j2ee:application/j2ee:module/j2ee:web", new InputSource(new StringReader(applicationDD)), XPathConstants.NODESET); } if (webModules.getLength() == 0){ //search no-namespace for DTD-based descriptor for backwards compatibility webModules = (NodeList) xpath.evaluate("/application/module/web", new InputSource(new StringReader(applicationDD)), XPathConstants.NODESET); } for (int i=0; i < webModules.getLength(); i++) { Node webModule = webModules.item(i); String weburi="", contextroot=""; NodeList webModuleChildNodes = webModule.getChildNodes(); for (int j=0; j < webModuleChildNodes.getLength(); j++) { Node webModuleChild = webModuleChildNodes.item(j); if (webModuleChild.getNodeName().equals("web-uri")) weburi = webModuleChild.getTextContent(); if (webModuleChild.getNodeName().equals("context-root")) contextroot = webModuleChild.getTextContent(); } // Now look up the currentModule and figure out its servlets Set webmoduleObjectNameSet = adminClient.queryNames( new ObjectName("WebSphere:type=WebModule,name=" + weburi + ",*"), null); if (webmoduleObjectNameSet.isEmpty()) throw new IllegalStateException("Unable to find web module in JMX: " + weburi); ObjectName webmoduleObjectName = (ObjectName)webmoduleObjectNameSet.iterator().next(); String webmoduleDD = (String)adminClient.getAttribute(webmoduleObjectName, "deploymentDescriptor"); log.fine("webmoduleDD: " + webmoduleDD); xpath = XPathFactory.newInstance().newXPath(); xpath.setNamespaceContext(new JavaEENamespaceContext()); NodeList servletMappings = (NodeList) xpath.evaluate("/javaee:web-app/javaee:servlet-mapping", new InputSource(new StringReader(webmoduleDD)), XPathConstants.NODESET); if (servletMappings.getLength() == 0) { //search J2EE 1.4 XML Schemas for backwards compatibility servletMappings = (NodeList) xpath.evaluate("/j2ee:web-app/j2ee:servlet-mapping", new InputSource(new StringReader(webmoduleDD)), XPathConstants.NODESET); } if (servletMappings.getLength() == 0) { //search no-namespace for DTD-based descriptor for backwards compatibility servletMappings = (NodeList) xpath.evaluate("/web-app/servlet-mapping", new InputSource(new StringReader(webmoduleDD)), XPathConstants.NODESET); } for (int j=0; j < servletMappings.getLength(); j++) { Node servletMapping = servletMappings.item(j); NodeList servletMappingChildNodes = servletMapping.getChildNodes(); String servletName = null; for (int k=0; k < servletMappingChildNodes.getLength(); k++) { Node childNode = servletMappingChildNodes.item(k); if (childNode.getNodeName().equals("url-pattern")) servletName = childNode.getTextContent().replaceFirst("/", ""); } if (servletName != null) { log.fine("Adding servlet to context: " + servletName + ", " + contextroot); httpContext.add(new Servlet(servletName, contextroot)); } else { log.warning("Unable to find servlet-name in web-module " + weburi + " deployment descriptor"); } } } } catch (Exception e) { log.log(Level.SEVERE, "Error while processing the deployment descriptor", e); } metaData.addContext(httpContext); return metaData; } @SuppressWarnings("rawtypes") private int getEndpointPort(List specialEndpoints, String endPointIdentifier) { for (Object specialEndpoint : specialEndpoints) { AttributeList specialEndpointAttributeList = (AttributeList)specialEndpoint; String endPointName = (String)getAttributeByName(specialEndpointAttributeList, "endPointName"); if (endPointName.equals(endPointIdentifier)) { AttributeList endpointAttributeList = (AttributeList)getAttributeByName(specialEndpointAttributeList, "endPoint"); return (Integer)getAttributeByName(endpointAttributeList, "port"); } } return 0; } private Object getAttributeByName(AttributeList attrList, String name) { for (Object attrObject : attrList) { Attribute attr = (Attribute)attrObject; if (attr.getName().equals(name)) return attr.getValue(); } return null; } /* * Checks the listener and figures out the aggregate distribution status of all nodes */ private String checkDistributionStatus(DeploymentNotificationListener listener) throws MalformedObjectNameException, NullPointerException, IllegalStateException { String distributionState = AppNotification.DISTRIBUTION_UNKNOWN; if (listener != null) { String compositeStatus = listener.getNotificationProps() .getProperty(AppNotification.DISTRIBUTION_STATUS_COMPOSITE); if (compositeStatus != null) { log.finer("compositeStatus: " + compositeStatus); String[] serverStati = compositeStatus.split("\\+"); int countTrue = 0, countFalse = 0, countUnknown = 0; for (String serverStatus : serverStati) { ObjectName objectName = new ObjectName(serverStatus); distributionState = objectName.getKeyProperty("distribution"); log.finer("distributionState: " + distributionState); if (distributionState.equals("true")) countTrue++; if (distributionState.equals("false")) countFalse++; if (distributionState.equals("unknown")) countUnknown++; } if (countUnknown > 0) { distributionState = AppNotification.DISTRIBUTION_UNKNOWN; } else if (countFalse > 0) { distributionState = AppNotification.DISTRIBUTION_NOT_DONE; } else if (countTrue > 0) { distributionState = AppNotification.DISTRIBUTION_DONE; } else { throw new IllegalStateException("Reported distribution status is invalid."); } } } return distributionState; } /* (non-Javadoc) * @see org.jboss.arquillian.spi.DeployableContainer#undeploy(org.jboss.arquillian.spi.Context, org.jboss.shrinkwrap.api.Archive) */ public void undeploy(final Archive<?> archive) throws DeploymentException { if (log.isLoggable(Level.FINER)) { log.entering(className, "undeploy"); } String appName = createDeploymentName(archive.getName()); try { // Session configSession = new Session(containerConfiguraiton.getUsername(), false); // ConfigServiceProxy configProxy = new ConfigServiceProxy(adminClient); Hashtable<Object, Object> prefs = new Hashtable<Object, Object>(); NotificationFilterSupport filterSupport = new NotificationFilterSupport(); filterSupport.enableType(AppConstants.NotificationType); DeploymentNotificationListener listener = new DeploymentNotificationListener( adminClient, filterSupport, "Uninstall " + appName, AppNotification.UNINSTALL); AppManagement appManagementProxy = AppManagementProxy.getJMXProxyForClient(adminClient); appManagementProxy.uninstallApplication( appName, prefs, null); // configSession.getSessionId()); synchronized(listener) { listener.wait(); } if(listener.isSuccessful()) { //configProxy.save(configSession, true); } else { throw new IllegalStateException("Application not sucessfully undeployed: " + listener.getMessage()); //configProxy.discard(configSession); } } catch (Exception e) { throw new DeploymentException("Could not undeploy application", e); } if (log.isLoggable(Level.FINER)) { log.exiting(className, "undeploy"); } } /* (non-Javadoc) * @see org.jboss.arquillian.spi.DeployableContainer#stop(org.jboss.arquillian.spi.Context) */ public void stop() throws LifecycleException { if (log.isLoggable(Level.FINER)) { log.entering(className, "stop"); } if (log.isLoggable(Level.FINER)) { log.exiting(className, "stop"); } } //-------------------------------------------------------------------------------------|| // Internal Helper Methods ------------------------------------------------------------|| //-------------------------------------------------------------------------------------|| private String createDeploymentName(String archiveName) { return archiveName.substring(0, archiveName.lastIndexOf(".")); } private String createDeploymentExtension(String archiveName) { return archiveName.substring(archiveName.lastIndexOf(".")); } public Class<WebSphereRemoteContainerConfiguration> getConfigurationClass() { // TODO Auto-generated method stub return WebSphereRemoteContainerConfiguration.class; } public ProtocolDescription getDefaultProtocol() { if (log.isLoggable(Level.FINER)) { log.entering(className, "getDefaultProtocol"); } if (log.isLoggable(Level.FINER)) { log.exiting(className, "getDefaultProtocol"); } return new ProtocolDescription("Servlet 3.0"); } public void deploy(Descriptor descriptor) throws DeploymentException { // TODO Auto-generated method stub } public void undeploy(Descriptor descriptor) throws DeploymentException { // TODO Auto-generated method stub } }