/*
* JBoss, Home of Professional Open Source.
* Copyright 2009, Red Hat Middleware LLC, and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.test.profileservice.test;
import java.net.URL;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import javax.jms.Queue;
import javax.jms.QueueConnection;
import javax.jms.QueueConnectionFactory;
import javax.jms.QueueSender;
import javax.jms.QueueSession;
import javax.jms.TextMessage;
import javax.naming.Context;
import junit.framework.Assert;
import junit.framework.TestCase;
import org.jboss.deployers.spi.management.ManagementView;
import org.jboss.deployers.spi.management.deploy.DeploymentManager;
import org.jboss.deployers.spi.management.deploy.DeploymentProgress;
import org.jboss.managed.api.ComponentType;
import org.jboss.managed.api.ManagedComponent;
import org.jboss.managed.api.ManagedOperation;
import org.jboss.managed.api.ManagedProperty;
import org.jboss.metatype.api.values.CompositeValue;
import org.jboss.metatype.api.values.SimpleValue;
import org.jboss.test.profileservice.test.ejb3.LocalExpositionBean;
import org.jboss.test.profileservice.test.ejb3.LocalExpositionRemoteBusiness;
import org.jboss.test.profileservice.test.ejb3.TestLoggingMDB;
import org.jboss.test.profileservice.test.ejb3.TestRemoteBusiness;
import org.jboss.test.profileservice.test.ejb3.TestStatefulBean;
import org.jboss.test.profileservice.test.ejb3.TestStatelessBean;
/**
* Ejb3MetricsUnitTestCase
*
* Asserts that ManagementObjects and corresponding
* properties/operations are exposed in expected form for
* EJB3 SLSBs, SFSBs, and MDBs.
*
* @author <a href="mailto:emuckenh@redhat.com">Emanuel Muckenhuber</a>
* @author <a href="mailto:andrew.rubinger@redhat.com">ALR</a>
* @version $Revision$
*/
public class Ejb3MetricsUnitTestCase extends AbstractProfileServiceTest
{
// ---------------------------------------------------------------------------------------||
// Class Members -------------------------------------------------------------------------||
// ---------------------------------------------------------------------------------------||
/**
* Component type for the EJB3 namespace
*/
private static final String COMPONENT_TYPE_EJB3 = "EJB3";
/**
* Component subtype for Stateless Session Beans
*/
private static final String COMPONENT_SUBTYPE_STATELESS_SESSION = "StatelessSession";
/**
* Component subtype for Stateful Session Beans
*/
private static final String COMPONENT_SUBTYPE_STATEFUL_SESSION = "StatefulSession";
/**
* Component subtype for Message-Driven Beans
*/
private static final String COMPONENT_SUBTYPE_MESSAGE_DRIVEN = "MessageDriven";
/**
* Properties which should be exposed by SLSB MOs.
*/
private static final String[] PROPERTY_NAMES_SLSB = new String[]
{
// general EJB metrics
"name", "invocationStats",
// session bean specific metrics
"availableCount", "createCount", "currentSize", "maxSize", "removeCount"};
/**
* Properties which should be exposed by SFSB MOs.
*/
private static final String[] PROPERTY_NAMES_SFSB = new String[]
{
// general EJB metrics
"name", "invocationStats",
// session bean specific metrics
"availableCount", "createCount", "currentSize", "maxSize", "removeCount",
// stateful session bean specific metrics
"cacheSize", "passivatedCount", "totalSize"};
/**
* Properties which should be exposed by MDB MOs.
*/
private static final String[] PROPERTY_NAMES_MDB = new String[]
{
// general EJB metrics
"name", "invocationStats",
// message-driven bean specific metrics
"deliveryActive", "keepAliveMillis", "maxMessages", "maxPoolSize", "minPoolSize"};
/**
* Operations which should be exposed by SLSB MOs
*/
private static final String[] OPERATION_NAMES_SLSB = new String[]
{"resetInvocationStats"};
/**
* Operations which should be exposed by SFSB MOs
*/
private static final String[] OPERATION_NAMES_SFSB = new String[]
{"resetInvocationStats"};
/**
* Operations which should be exposed by MDB MOs
*/
private static final String[] OPERATION_NAMES_MDB = new String[]
{"resetInvocationStats", "startDelivery", "stopDelivery"};
/**
* The name of the test EAR to deploy
*/
private static final String NAME_TEST_EAR = "testEjb3xMetrics.ear";
/**
* The name of the test JAR to deploy
*/
private static final String NAME_TEST_JAR = "testEjb3xMetrics.jar";
/**
* Name of the Queue Connection Factory in JNDI
*/
private static final String JNDI_NAME_CONNECTION_FACTORY = "ConnectionFactory";
// ---------------------------------------------------------------------------------------||
// Instance Members ----------------------------------------------------------------------||
// ---------------------------------------------------------------------------------------||
/**
* The hook to ManagementObjects
*/
private ManagementView managementView;
/**
* Profile Service deployer; required to get things in management view
*/
private DeploymentManager deploymentManager;
/**
* Repository names from the deployment
*/
private String[] repositoryNames;
// ---------------------------------------------------------------------------------------||
// Constructor ---------------------------------------------------------------------------||
// ---------------------------------------------------------------------------------------||
/**
* Constructor
*
* @param name
*/
public Ejb3MetricsUnitTestCase(String name)
{
super(name);
}
// ---------------------------------------------------------------------------------------||
// Lifecycle -----------------------------------------------------------------------------||
// ---------------------------------------------------------------------------------------||
/**
* Deploys the test JAR via the ProfileService-aware DeploymentManager
*/
@Override
protected void setUp() throws Exception
{
// Call Super
super.setUp();
// Get the Deployment Manager
deploymentManager = this.getDeploymentManager();
// Set the Management View
managementView = this.getManagementView();
}
/**
* Undeploys the test JAR
*/
@Override
protected void tearDown() throws Exception
{
// Undeploy the test JAR
log.info("Undeploying: " + repositoryNames);
final DeploymentProgress stopProgress = deploymentManager.stop(repositoryNames);
stopProgress.run();
final DeploymentProgress removeProgress = deploymentManager.remove(repositoryNames);
removeProgress.run();
// Null out
repositoryNames = null;
deploymentManager = null;
managementView = null;
// Call super
super.tearDown();
}
// ---------------------------------------------------------------------------------------||
// Tests ---------------------------------------------------------------------------------||
// ---------------------------------------------------------------------------------------||
/**
* Ensures that MOs with the correct metrics and operations are exposed for SLSBs.
*
* @throws Exception
*/
public void testSlsb() throws Exception
{
// Log
this.getLog().info("testSlsb");
// Deploy
this.deployTestJar();
// Ensure component exists.
final String ejbName = TestStatelessBean.EJB_NAME;
final String componentName = NAME_TEST_JAR + '/' + ejbName;
final ManagedComponent component = this.getAndTestManagedComponent(COMPONENT_SUBTYPE_STATELESS_SESSION,
componentName);
// Check component has expected props and ops.
this.ensureManagementPropertiesExposed(component, PROPERTY_NAMES_SLSB);
this.ensureManagementOperationsExposed(component, OPERATION_NAMES_SLSB);
validateInvocationSessionStats(component, ejbName);
}
/**
* Ensures that MOs with the correct metrics and operations are exposed for SFSBs.
*
* @throws Exception
*/
public void testSfsb() throws Exception
{
// Log
this.getLog().info("testSfsb");
// Deploy
this.deployTestJar();
// Ensure component exists.
final String ejbName = TestStatefulBean.EJB_NAME;
final String componentName = NAME_TEST_JAR + '/' + ejbName;
final ManagedComponent component = this.getAndTestManagedComponent(COMPONENT_SUBTYPE_STATEFUL_SESSION,
componentName);
// Check component has expected props and ops.
this.ensureManagementPropertiesExposed(component, PROPERTY_NAMES_SFSB);
this.ensureManagementOperationsExposed(component, OPERATION_NAMES_SFSB);
validateInvocationSessionStats(component, ejbName);
}
/**
* Ensures that SLSB local invocations are recorded
*
* @throws Exception
*/
public void testSlsbLocal() throws Exception
{
// Log
this.getLog().info("testSlsbLocal");
// Deploy
this.deployTestJar();
// Check component has expected props and ops
validateLocalInvocationSlsbStats();
}
/**
* Ensures that the managed component names of
* EJB3 deployments in an EAR have form:
*
* "EARName/JARName/EJBName"
*/
public void testEarManagedComponentNames() throws Exception
{
// Log
this.getLog().info("testEarManagedComponentNames");
// Deploy the EAR (it will be undeployed and cleaned up as part of test lifecycle)
final String deployName = NAME_TEST_EAR;
final URL contentURL = super.getDeployURL(deployName);
this.deploy(deployName, contentURL);
// Generate expected names
final String earName = NAME_TEST_EAR;
final char delimiter = '/';
final String jarName = NAME_TEST_JAR;
final String componentPrefix = earName + delimiter + jarName + delimiter;
final String slsbComponentName = componentPrefix + TestStatelessBean.EJB_NAME;
final String sfsbComponentName = componentPrefix + TestStatefulBean.EJB_NAME;
final String mdbComponentName = componentPrefix + TestLoggingMDB.class.getSimpleName();
// Test
this.getAndTestManagedComponent(COMPONENT_SUBTYPE_STATELESS_SESSION, slsbComponentName);
this.getAndTestManagedComponent(COMPONENT_SUBTYPE_STATEFUL_SESSION, sfsbComponentName);
this.getAndTestManagedComponent(COMPONENT_SUBTYPE_MESSAGE_DRIVEN, mdbComponentName);
}
/**
* Ensures that MOs with the correct metrics and operations are exposed for MDBs.
*
* @throws Exception
*/
public void testMdb() throws Exception
{
// Log
this.getLog().info("testMdb");
// Deploy
this.deployTestJar();
// Ensure component exists.
final String ejbName = TestLoggingMDB.class.getSimpleName();
final String componentName = NAME_TEST_JAR + '/' + ejbName;
final ManagedComponent component = this.getAndTestManagedComponent(COMPONENT_SUBTYPE_MESSAGE_DRIVEN,
componentName);
// Check component has expected props and ops.
this.ensureManagementPropertiesExposed(component, PROPERTY_NAMES_MDB);
this.ensureManagementOperationsExposed(component, OPERATION_NAMES_MDB);
validateInvocationMdbStats(component);
}
// ---------------------------------------------------------------------------------------||
// Internal Helper Methods ---------------------------------------------------------------||
// ---------------------------------------------------------------------------------------||
/**
* Deploys the specified URL under the specified deployment name
* using the profile service; this is required such that ManagedObjects will
* be found (which is not the case using traditional JMX deployment)
*
* @param deployName
* @param url
* @throws Exception
*/
protected void deploy(final String deployName, final URL url) throws Exception
{
// Deploy
final DeploymentProgress distributeProgress = deploymentManager.distribute(deployName, url, true);
distributeProgress.run();
repositoryNames = distributeProgress.getDeploymentID().getRepositoryNames();
final DeploymentProgress startProgress = deploymentManager.start(repositoryNames);
log.info("Deploying: " + repositoryNames);
startProgress.run();
// Reset the Management View
managementView = this.getManagementView();
}
/**
* Deploys the test JAR
*
* @throws Exception
*/
protected void deployTestJar() throws Exception
{
// Deploy the test JAR
final String deployName = NAME_TEST_JAR;
final URL contentURL = super.getDeployURL(deployName);
this.deploy(deployName, contentURL);
}
/**
* Obtains the component of the specified subtype and name, failing the test if not found
*
* @param componentSubType
* @param componentName
* @throws IllegalArgumentException If any of the arguments were not specified
* @throws Exception If a general error occured
*/
protected ManagedComponent getAndTestManagedComponent(final String componentSubType, final String componentName)
throws Exception
{
// Log
this.getLog().info("getAndTestManagedComponent");
// Precondition checks
if (componentSubType == null || componentSubType.length() == 0)
{
throw new IllegalArgumentException("componentSubType must be specified");
}
if (componentName == null || componentName.length() == 0)
{
throw new IllegalArgumentException("componentName must be specified");
}
// Get the SLSB Type
final ComponentType componentType = new ComponentType(COMPONENT_TYPE_EJB3, componentSubType);
final Set<ManagedComponent> components = managementView.getComponentsForType(componentType);
// Get SLSB
final Iterator<ManagedComponent> componentsIt = components.iterator();
ManagedComponent component = null;
while (componentsIt.hasNext())
{
// Obtain the next component
final ManagedComponent currentComponent = componentsIt.next();
final String name = currentComponent.getName();
// Ensure it's the component we're looking for
if (name.equals(componentName))
{
component = currentComponent;
break;
}
}
// Ensure we've got the component
if (component == null)
{
TestCase.fail("Component not found; no managed object with name \"" + componentName + "\" in: " + components);
}
// Obtains the Component
return component;
}
/**
* Ensures that the specified management properties are exposed for
* the specified component
*
* @param The component
* @param expectedProperties Properties expected to be exposed by the specified component
* @throws IllegalArgumentException If any of the arguments are blank or null
* @throws Exception
*/
protected void ensureManagementPropertiesExposed(final ManagedComponent component, final String[] expectedProperties)
throws IllegalArgumentException, Exception
{
// Log
this.getLog().info("ensureManagementPropertiesExposed");
// Precondition checks
if (component == null)
{
throw new IllegalArgumentException("component must be specified");
}
if (expectedProperties == null)
{
throw new IllegalArgumentException("expectedProperties must be specified");
}
// Ensure all expected properties are in place
for (final String expectedProperty : expectedProperties)
{
final ManagedProperty prop = component.getProperty(expectedProperty);
TestCase.assertNotNull("Component did not contain expected managed property \"" + expectedProperty + "\": "
+ component, prop);
}
}
/**
* Ensures that the specified management operations are exposed for
* the specified component
*
* @param The component
* @param expectedOperationNames Operation names expected to be exposed by the specified component
* @throws IllegalArgumentException If any of the arguments are blank or null
* @throws Exception
*/
protected void ensureManagementOperationsExposed(final ManagedComponent component,
final String[] expectedOperationNames) throws IllegalArgumentException, Exception
{
// Log
this.getLog().info("ensureManagementOperationsExposed");
// Precondition checks
if (component == null)
{
throw new IllegalArgumentException("component must be specified");
}
if (expectedOperationNames == null)
{
throw new IllegalArgumentException("expectedProperties must be specified");
}
// Obtain managed operations for this component
final Set<ManagedOperation> operations = component.getOperations();
// For all expected operations
for (final String expectedOperation : expectedOperationNames)
{
// Ensure it's exposed
boolean found = false;
for (final ManagedOperation currentOperation : operations)
{
if (currentOperation.getName().equals(expectedOperation))
{
found = true;
break;
}
}
// Ensure the operation was found
TestCase.assertTrue("Component did not contain expected managed operation \"" + expectedOperation + "\": "
+ component, found);
}
}
/**
* Validates that the invocation statistics are in place for the specified
* EJB3 Session Bean ManagedComponent with specified EJB Name
*
* @param component
* @param ejbName
* @throws IllegalArgumentException If either argument is not specified
*/
protected void validateInvocationSessionStats(final ManagedComponent component, final String ejbName)
throws IllegalArgumentException, Exception
{
// Precondition checks
if (component == null)
{
throw new IllegalArgumentException("component must be specified");
}
if (ejbName == null || ejbName.length() == 0)
{
throw new IllegalArgumentException("ejbName must be specified");
}
// Obtain invocation stats
InvocationStats invocationStats = getInvocationStats(component);
// Ensure the stats are expected (empty)
List<MethodStats> methodStats = invocationStats.methodStats;
log.info("Method stats before invocation: " + methodStats);
Assert.assertTrue("Method stats should be empty", methodStats.isEmpty());
// Invoke upon the EJB
TestRemoteBusiness remoteBusiness = (TestRemoteBusiness) getInitialContext().lookup(ejbName + "/remote");
remoteBusiness.echo("Some test String");
// Ensure the stats reflect the invocation
invocationStats = getInvocationStats(component);
methodStats = invocationStats.methodStats;
log.info("Method stats after invocation: " + methodStats);
Assert.assertEquals("One invocation should be represented in stats", 1, methodStats.size());
// Reset the stats
resetInvocationStats(component);
invocationStats = getInvocationStats(component);
// Ensure the stats were reset
invocationStats = getInvocationStats(component);
methodStats = invocationStats.methodStats;
log.info("Method stats after reset: " + methodStats);
Assert.assertTrue("Method stats should be empty, was instead of size: " + methodStats.size(), methodStats
.isEmpty());
}
/**
* Validates that the invocation statistics are in place
* for local invocations upon a SLSB
*
* @throws IllegalArgumentException If either argument is not specified
*/
protected void validateLocalInvocationSlsbStats()
throws IllegalArgumentException, Exception
{
// Ensure component exists
final String ejbName = TestStatelessBean.EJB_NAME;
final String slsbComponentName = NAME_TEST_JAR + '/' + ejbName;
final ManagedComponent slsbComponent = this.getAndTestManagedComponent(COMPONENT_SUBTYPE_STATELESS_SESSION,
slsbComponentName);
// Obtain invocation stats
InvocationStats invocationStats = getInvocationStats(slsbComponent);
// Ensure the stats are expected (empty)
List<MethodStats> methodStats = invocationStats.methodStats;
log.info("Method stats before invocation: " + methodStats);
Assert.assertTrue("Method stats should be empty", methodStats.isEmpty());
// Invoke upon the EJB locally via the Remote access delegate
LocalExpositionRemoteBusiness remoteBusiness = (LocalExpositionRemoteBusiness) getInitialContext().lookup(LocalExpositionBean.class.getSimpleName() + "/remote");
remoteBusiness.echoViaLocal("Some test String");
// Ensure the stats reflect the invocation in the SLSB
invocationStats = getInvocationStats(slsbComponent);
methodStats = invocationStats.methodStats;
log.info("Method stats after invocation: " + methodStats);
Assert.assertEquals("One invocation (made locally) should be represented in stats", 1, methodStats.size());
// Reset the stats
resetInvocationStats(slsbComponent);
invocationStats = getInvocationStats(slsbComponent);
// Ensure the stats were reset
invocationStats = getInvocationStats(slsbComponent);
methodStats = invocationStats.methodStats;
log.info("Method stats after reset: " + methodStats);
Assert.assertTrue("Method stats should be empty, was instead of size: " + methodStats.size(), methodStats
.isEmpty());
}
/**
* Validates that the invocation statistics are in place for the specified
* EJB3 MDB ManagedComponent with specified EJB Name
*
* @param component
* @throws IllegalArgumentException If the component is not specified
*/
protected void validateInvocationMdbStats(final ManagedComponent component) throws IllegalArgumentException,
Exception
{
// Precondition checks
if (component == null)
{
throw new IllegalArgumentException("component must be specified");
}
// Obtain invocation stats
InvocationStats invocationStats = getInvocationStats(component);
// Ensure the stats are expected (empty)
List<MethodStats> methodStats = invocationStats.methodStats;
Assert.assertTrue("Method stats should be empty", methodStats.isEmpty());
// Get the queue from JNDI
final Context naimgContext = getInitialContext();
final Queue queue = (Queue) naimgContext.lookup(TestLoggingMDB.QUEUE_NAME);
// Get the ConnectionFactory from JNDI
final QueueConnectionFactory factory = (QueueConnectionFactory) naimgContext.lookup(JNDI_NAME_CONNECTION_FACTORY);
// Make a Connection
final QueueConnection connection = factory.createQueueConnection();
final QueueSession sendSession = connection.createQueueSession(false, QueueSession.AUTO_ACKNOWLEDGE);
// Make the message
final String contents = "Test Message Contents";
final TextMessage message = sendSession.createTextMessage(contents);
// Send the message
final QueueSender sender = sendSession.createSender(queue);
sender.send(message);
log.info("Sent message " + message + " with contents: " + contents);
// Clean up
sendSession.close();
connection.close();
// Wait, this is async
Thread.sleep(3000);
// Ensure the stats reflect the invocation
invocationStats = getInvocationStats(component);
methodStats = invocationStats.methodStats;
log.info("Method stats after invocation: " + methodStats);
Assert.assertEquals("One invocation should be represented in stats", 1, methodStats.size());
// Reset the stats
resetInvocationStats(component);
invocationStats = getInvocationStats(component);
// Ensure the stats were reset
invocationStats = getInvocationStats(component);
methodStats = invocationStats.methodStats;
log.info("Method stats after reset: " + methodStats);
Assert.assertTrue("Method stats should be empty", methodStats.isEmpty());
}
private InvocationStats getInvocationStats(ManagedComponent component)
{
InvocationStats invocationStats = new InvocationStats();
List<MethodStats> allMethodStats = new ArrayList<MethodStats>();
ManagedProperty invocationStatsProp = component.getProperty("invocationStats");
invocationStats.endTime = System.currentTimeMillis();
CompositeValue invocationStatsMetaValue = (CompositeValue) invocationStatsProp.getValue();
CompositeValue allMethodStatsMetaValue = (CompositeValue) invocationStatsMetaValue.get("methodStats");
Set<String> methodNames = allMethodStatsMetaValue.getMetaType().keySet();
for (String methodName : methodNames)
{
CompositeValue methodStatsMetaValue = (CompositeValue) allMethodStatsMetaValue.get(methodName);
MethodStats methodStats = new MethodStats();
methodStats.name = methodName;
methodStats.count = Long.parseLong(((SimpleValue) methodStatsMetaValue.get("count")).getValue().toString());
methodStats.totalTime = Long.parseLong(((SimpleValue) methodStatsMetaValue.get("totalTime")).getValue()
.toString());
methodStats.minTime = Long
.parseLong(((SimpleValue) methodStatsMetaValue.get("minTime")).getValue().toString());
methodStats.maxTime = Long
.parseLong(((SimpleValue) methodStatsMetaValue.get("maxTime")).getValue().toString());
allMethodStats.add(methodStats);
}
invocationStats.methodStats = allMethodStats;
SimpleValue lastResetTimeMetaValue = (SimpleValue) invocationStatsMetaValue.get("lastResetTime");
invocationStats.beginTime = Long.valueOf(lastResetTimeMetaValue.getValue().toString()); // TODO: handle null value?
return invocationStats;
}
private void resetInvocationStats(ManagedComponent component)
{
Set<ManagedOperation> operations = component.getOperations();
final List<String> opNames = new ArrayList<String>();
final String resetMethodName = "resetInvocationStats";
for (ManagedOperation operation : operations)
{
final String opName = operation.getName();
opNames.add(opName);
if (opName.equals(resetMethodName))
{
operation.invoke();
return;
}
}
// If we've reached here, fail
Assert
.fail("No operation named \"" + resetMethodName + "\" exists in " + opNames + " for " + component.getName());
}
class InvocationStats
{
List<MethodStats> methodStats;
long beginTime;
long endTime;
@Override
public String toString()
{
return "InvocationStats [beginTime=" + beginTime + ", endTime=" + endTime + ", methodStats=" + methodStats
+ "]";
}
}
class MethodStats
{
String name;
long count;
long minTime;
long maxTime;
long totalTime;
@Override
public String toString()
{
return "MethodStats [count=" + count + ", maxTime=" + maxTime + ", minTime=" + minTime + ", name=" + name
+ ", totalTime=" + totalTime + "]";
}
}
}