Author: ozizka(a)redhat.com
Date: 2009-01-19 13:21:11 -0500 (Mon, 19 Jan 2009)
New Revision: 110
Added:
trunk/jsfunit/src/test/java/org/jboss/jopr/jsfunit/DatasourceTestBase.java
Log:
Added: Datasource test base class - common denominator for AS 4 and AS 5 test subclasses.
Added: trunk/jsfunit/src/test/java/org/jboss/jopr/jsfunit/DatasourceTestBase.java
===================================================================
--- trunk/jsfunit/src/test/java/org/jboss/jopr/jsfunit/DatasourceTestBase.java
(rev 0)
+++ trunk/jsfunit/src/test/java/org/jboss/jopr/jsfunit/DatasourceTestBase.java 2009-01-19
18:21:11 UTC (rev 110)
@@ -0,0 +1,544 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, 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.jopr.jsfunit;
+
+import org.jboss.jopr.jsfunit.*;
+import com.gargoylesoftware.htmlunit.html.*;
+import java.io.IOException;
+import junit.framework.Test;
+import junit.framework.TestSuite;
+import org.jboss.mx.util.MBeanServerLocator;
+import javax.management.MBeanServer;
+import javax.management.ObjectName;
+import java.util.Set;
+import java.util.Iterator;
+import org.jdom.Document;
+import org.jdom.Element;
+import org.jdom.input.SAXBuilder;
+import java.io.File;
+import java.util.Map;
+import java.util.HashMap;
+import javax.naming.InitialContext;
+import java.sql.Connection;
+import javax.sql.DataSource;
+import javax.naming.Context;
+import java.sql.SQLException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.management.*;
+
+
+
+
+/**
+ * When complete, this class will contain tests for creating,
+ * configuring, and deleting various types of datasources. This
+ * test class should be run against JBAS 5.x.
+ *
+ * @author Farah Juma
+ * @author Ondrej Zizka
+ *
+ */
+public abstract class DatasourceTestBase extends EmbjoprTestCase {
+
+
+ // Datasource types, as they appear in the left nav
+ protected enum DatasourceType {
+
+ // Some of these values are specific for AS 5. TODO: Think up some way to split
nicely.
+ LOCAL_TX_DATASOURCE("Local TX Datasources", "LocalTxCM",
"local-tx-datasource", "default__Local TX Datasource"),
+ NO_TX_DATASOURCE( "No TX Datasources", "NoTxCM",
"no-tx-datasource", null), // TODO: Fill the value.
+ XA_DATASOURCE( "XA Datasources", "XATxCM",
"xa-datasource", "default__XA Datasource");
+
+ protected String label;
+ public String getLabel() { return label; }
+ //public void setLabel(String label) { this.label = label; }
+
+ protected final String serviceName;
+ public String getServiceName() { return serviceName; }
+
+
+ protected final String xmlElementName;
+ public String getXmlElementName() { return xmlElementName; }
+
+ protected final String templateHtmlSelectValue;
+ public String getTemplateHtmlSelectValue() { return templateHtmlSelectValue; }
+
+
+
+ private DatasourceType(String label, String serviceName,
+ String xmlElementName, String htmlSelectValue)
+ {
+ this.label = label;
+ this.serviceName = serviceName;
+ this.xmlElementName = xmlElementName;
+ this.templateHtmlSelectValue = htmlSelectValue;
+ }
+
+ }// DatasourceTypes
+
+
+
+ // --- Datasource Templates --- //
+ /*
+ <select id="resourceCreateForm:selectedTemplate" size="1"
name="resourceCreateForm:selectedTemplate">
+ <option value="">Select Template</option>
+ <option value="Oracle Local TX__Datasource">Oracle Local TX
(Datasource)</option>
+ <option value="Oracle XA__Datasource">Oracle XA
(Datasource)</option>
+ <option value="default__Datasource">default
(Datasource)</option>
+ </select>
+ */
+ protected enum DatasourceTemplate {
+
+ ORACLE_LOCAL_TX("Oracle Local TX__Datasource"),
+ ORACLE_XA("Oracle XA__Datasource"),
+ DEFAULT("default__Datasource");
+
+ protected final String templateHtmlSelectValue;
+
+
+ private DatasourceTemplate(String templateHtmlSelectValue) {
+ this.templateHtmlSelectValue = templateHtmlSelectValue;
+ }
+
+
+
+ /**
+ * Value of HTML select option for this template.
+ */
+ public String getTemplateHtmlSelectValue() {
+ return templateHtmlSelectValue;
+ }
+
+ }
+
+
+
+
+
+ // Datasource properties
+
+ private Map<String, String> datasourceProperties = createDatasourceProperties();
+
+ /**
+ * @returns a set of properties created upon initialization by overriden
createDatasourceProperties().
+ */
+ public Map<String, String> getDatasourceProperties() { return
datasourceProperties; }
+
+
+
+
+
+ /**
+ * Create a new datasource using the given type, template, and properties.
+ *
+ * @param datasourceType must be the name of a datasource type, as it
+ * appears in the left nav (eg. "Local TX Datasources")
+ */
+ protected abstract void createDatasource(DatasourceType datasourceType,
+ String datasourceTemplate,
+ Map<String, String> propertiesMap) throws
IOException;
+
+ /**
+ * Delete the datasource given by datasourceName.
+ */
+ protected abstract void deleteDatasource(String datasourceName) throws IOException;
+
+ /**
+ * Use JMX to check if the datasource given by datasourceName is deployed.
+ */
+ protected boolean isDatasourceDeployed(String jndiName, DatasourceType
datasourceType) {
+ try {
+
+ String[] dsMBeanServices = {"DataSourceBinding",
+ "ManagedConnectionPool",
+ "ManagedConnectionFactory",
+ datasourceType.getServiceName() };
+
+ // Query the MBean server to check if the datasource is deployed
+ MBeanServer jmxServer = MBeanServerLocator.locateJBoss();
+
+ // Inspect these MBeans and make sure that the their state indicates
successful deployment
+ // (e.g. for AS 5, "State" attribute is "DEPLOYED"):
+ // 1)
"jboss.jca:name=TestDS,service=DataSourceBinding",type=Component
+ // 2)
"jboss.jca:name=TestDS,service=ManagedConnectionPool",type=Component
+ // 3)
"jboss.jca:name=TestDS,service=ManagedConnectionFactory",type=Component
+ // 4) The fourth MBean inspected depends on the type of datasource.
+ for(int i = 0; i < dsMBeanServices.length; i++) {
+
+ // Is this necessary? Can't we just query? See InstanceNotFoundException;
+ // And AFAIK, full MBean name is unique. I vote for minimalistic code.
+ /*ObjectName objName = new ObjectName( this.getMBeanName(jndiName,
dsMBeanServices[i]));
+
+ Set dsMBeans = jmxServer.queryNames(objName, null);
+ if (dsMBeans.size() != 1) return false;
+
+ // Get the first and only one MBean returned.
+ ObjectName deploymentMBean = (ObjectName)dsMBeans.iterator().next();
/**/
+
+ ObjectName deploymentMBean = new ObjectName( this.getMBeanName(jndiName,
dsMBeanServices[i]) );
+
+ ///Object state = jmxServer.getAttribute(deploymentMBean,
"State");
+ ///if(!("DEPLOYED".equals(state.toString()))) return false;
+ if( !this.isMBeanStateDeployed( deploymentMBean ) )
+ return false;
+
+ }
+
+ return true;
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+
+
+ /**
+ * checkProperties reads the *-ds.xml file corresponding to the datasource
+ * given by jndiName and compares the property values in this file to the
+ * given expected values. checkProperties returns true if all the given
+ * properties are correctly set in the *-ds.xml file and false otherwise.
+ */
+ protected boolean checkProperties(String jndiName,
+ DatasourceType datasourceType,
+ Map<String, String> expectedValuesMap) {
+
+ Map<String, String> actualValuesMap = new HashMap<String, String>();
+
+
+ try {
+
+ // Parse the *-ds.xml file; create appropriate file name for AS 4 or 5.
+ File file = new File( this.getDatasourceConfigFile());
+
+ SAXBuilder builder = new SAXBuilder();
+ Document doc = builder.build(file);
+
+ Element root = doc.getRootElement();
+ assertTrue(root.getName().equals("datasources"));
+
+ // Get the datasource element
+ Element datasource = root.getChild( datasourceType.getXmlElementName() );
+
+ // Create actualValuesMap by mapping property names to
+ // property values
+ Iterator itr = (datasource.getChildren()).iterator();
+ while(itr.hasNext()) {
+ Element property = (Element)itr.next();
+ actualValuesMap.put(property.getName(), property.getValue());
+ }
+
+ // Compare the actual values to the expected ones
+ itr = expectedValuesMap.keySet().iterator();
+ while(itr.hasNext()) {
+ String key = (String)itr.next();
+ if(actualValuesMap.containsKey(key)) {
+ if(!expectedValuesMap.get(key).equals(actualValuesMap.get(key))) {
+ return false; // incorrect value
+ }
+ } else {
+ return false; // value was not set
+ }
+ }
+
+ return true;
+
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Connect to the database identified by the given JNDI name,
+ * using the given username and password. Return the Connection
+ * object.
+ */
+ public Connection connectDB(String jndiName, String username,
+ String password) throws Exception{
+ Context ctx = new InitialContext();
+ DataSource ds = (DataSource)ctx.lookup(jndiName);
+ return ds.getConnection(username, password);
+ }
+
+ /**
+ * Disconnect from the database.
+ */
+ public void disconnectDB(Connection con) throws SQLException {
+ if(con != null) {
+ con.close();
+ }
+ }
+
+ /**
+ * @return the suite of tests being tested
+ * @throws UnsupportedOperationException - you have to override this.
+ * Can't be abstract, must be static.
+ */
+ public static Test suite()
+ {
+ throw new UnsupportedOperationException("This has to be overriden.");
+ }
+
+
+
+
+ /*
+ * --- Some preliminary creation tests ---
+ */
+
+
+
+ /**
+ * Create a new datasource. Leave some property values that aren't
+ * required unset.
+ */
+ public void testCreateDatasource() throws IOException {
+
+
+ // The properties we want to configure
+ Map<String, String> propertiesMap = this.getDatasourceProperties();
+
+ createDatasource(DatasourceType.LOCAL_TX_DATASOURCE,
+ //was: "default__Local TX Datasource",
+ DatasourceType.LOCAL_TX_DATASOURCE.getTemplateHtmlSelectValue(), // TODO:
Redundant - remove.
+ propertiesMap);
+ client.click("resourceConfigurationForm:saveButton");
+
+ // Check for the appropriate success messages
+ String expectedMessage = "Successfully added new Local TX Datasource";
+ checkClientAndServerMessages(expectedMessage, expectedMessage, false);
+
+ assertTrue(isDatasourceDeployed(propertiesMap.get("jndi-name"),
+ DatasourceType.LOCAL_TX_DATASOURCE));
+ assertTrue(checkProperties(propertiesMap.get("jndi-name"),
+ DatasourceType.LOCAL_TX_DATASOURCE,
+ propertiesMap));
+
+ // TODO: need to verify that appropriate default values were
+ // set for properties that were not specified above
+
+ // Clean up
+ deleteDatasource(propertiesMap.get("jndi-name"));
+ expectedMessage = "Successfully deleted Local TX Datasource '"
+ + propertiesMap.get("jndi-name") +
"'";
+ checkClientAndServerMessages(expectedMessage, expectedMessage, false);
+ }
+
+
+
+
+
+
+
+ /**
+ * Attempt to create a new datasource but leave at least one required
+ * value unset. An error should occur.
+ *
+ * TODO: Check if works for AS 4 .
+ */
+ public void testCreateDatasourceMissingRequiredValues() throws IOException {
+
+ // Leave jndi-name and connection-url unset
+ Map<String, String> propertiesMap = new HashMap<String, String>();
+ propertiesMap.put("user-name", "testUser");
+ propertiesMap.put("max-pool-size", "10");
+ propertiesMap.put("prefill", "true");
+ propertiesMap.put("idle-timeout-minutes", "20");
+ propertiesMap.put("set-tx-query-timeout", "true");
+ propertiesMap.put("query-timeout", "1800");
+
+ createDatasource(DatasourceType.NO_TX_DATASOURCE,
+ DatasourceType.NO_TX_DATASOURCE.getTemplateHtmlSelectValue(),
//"default__No TX Datasource",
+ propertiesMap);
+ client.click("resourceConfigurationForm:saveButton");
+
+ // Check for the appropriate error messages
+ checkClientAndServerMessages("An invalid value was specified for one "
+ + "or more properties",
+ "Value is required",
+ true);
+ }
+
+ /**
+ * Attempt to create a new datasource but set a property value
+ * beyond its expected range of values. An error should occur.
+ *
+ * TODO: Check if works for AS 4 .
+ */
+ public void testCreateDatasourcePropertyOutOfRange() throws IOException {
+ Map<String, String> propertiesMap = new HashMap<String, String>();
+ propertiesMap.put("jndi-name", "InvalidDS");
+ propertiesMap.put("user-name", "testUser");
+ propertiesMap.put("password", "password");
+ propertiesMap.put("xa-datasource-class",
"org.postgresql.xa.PGXADataSource");
+ propertiesMap.put("xa-resource-timeout", "36000");
+
+ // This number is too big
+ propertiesMap.put("max-pool-size", "100000000000000");
+
+ createDatasource(DatasourceType.XA_DATASOURCE,
+ DatasourceType.XA_DATASOURCE.getTemplateHtmlSelectValue(),
+ propertiesMap);
+ client.click("resourceConfigurationForm:saveButton");
+
+ // Check for the appropriate error messages
+ checkClientAndServerMessages("An invalid value was specified for one or more
properties",
+ "Specified attribute is not between the
expected values",
+ true);
+ }
+
+
+ /**
+ * Attempt to create a new datasource but set a property value
+ * to an invalid type. An error should occur.
+ *
+ * TODO: Check if works for AS 4 .
+ */
+ public void testCreateDatasourceInvalidPropertyType() throws IOException {
+ Map<String, String> propertiesMap = new HashMap<String, String>();
+ propertiesMap.put("jndi-name", "InvalidDS");
+ propertiesMap.put("min-pool-size", "10");
+ propertiesMap.put("max-pool-size", "20");
+ propertiesMap.put("driver-class", "org.hsqldb.jdbcDriver");
+ propertiesMap.put("connection-url", "jdbc:hsqldb:.");
+ propertiesMap.put("share-prepared-statements", "false");
+
+ // This property value is supposed to be an integer
+ propertiesMap.put("background-validation-millis", "abcde");
+
+ createDatasource(DatasourceType.LOCAL_TX_DATASOURCE,
+ DatasourceType.LOCAL_TX_DATASOURCE.getTemplateHtmlSelectValue(),
// TODO: Redundant, remove.
+ propertiesMap);
+ client.click("resourceConfigurationForm:saveButton");
+
+ // Check for the appropriate error messages
+ checkClientAndServerMessages("An invalid value was specified for one or more
properties",
+ "Value is not a valid integer",
+ true);
+ }
+
+
+
+
+
+
+ /*
+ * --- Various support methods. ---
+ */
+
+
+
+
+ /**
+ * Creates the default properties for the datasource - shared by AS4 and AS5.
+ * Overriding method should get result of this method and overwrite it with it's
custom properties.
+ * @returns a set of default properties.
+ */
+ protected Map<String, String> createDatasourceProperties()
+ {
+ Map<String, String> propertiesMap = new HashMap();
+
+ // The properties we want to configure
+ propertiesMap.put("jndi-name", "TestDS");
+ propertiesMap.put("user-name", "testUser");
+ propertiesMap.put("password", "password");
+ propertiesMap.put("min-pool-size", "5");
+ propertiesMap.put("driver-class", "org.hsqldb.jdbcDriver");
+ propertiesMap.put("connection-url", "jdbc:hsqldb:."); //
Store data current working dir.
+ propertiesMap.put("idle-timeout-minutes", "20");
+ //propertiesMap.put("query-timeout", "180"); // AS 5 only - moved
there
+ propertiesMap.put("prepared-statement-cache-size", "2");
+ // Share Prepared Statements - AS 5
+ //propertiesMap.put("share-prepared-statements", "false"); //
AS 5
+
+ propertiesMap.put("valid-connection-checker-class-name",
+
"org.jboss.resource.adapter.jdbc.CheckValidConnectionSQL");
+ //propertiesMap.put("stale-connection-checker-class-name",
+ //
"org.jboss.resource.adapter.jdbc.StaleConnectionChecker"); // AS 5
+ propertiesMap.put("exception-sorter-class-name",
+ "org.jboss.resource.adapter.jdbc.ExceptionSorter");
+ //propertiesMap.put("allocation-retry", "10000"); // AS 5
+ //propertiesMap.put("allocation-retry-wait-millis", "10000");
// AS 5
+
+ //propertiesMap.put("background-validation-millis", "15000");
// AS 5
+ //propertiesMap.put("prefill", "true");
// AS 5
+ //propertiesMap.put("use-try-lock", "61000");
// AS 5
+
+ return propertiesMap;
+
+ }
+
+
+
+ /**
+ *
+ * @returns a name of the Datasource's config file to check the properties in.
+ */
+ protected abstract String getDatasourceConfigFile();
+
+ protected abstract String getMBeanName( String jndiName, String serviceName );
+
+ /**
+ * This method is wrapper that handles JMX exceptions at Base level.
+ * Intended to keep AS-version-specific method, isMBeanStateDeployedImpl(), as small as
possible.
+ * @param deploymentMBean
+ * @return
+ * @throws javax.management.JMException
+ * @throws java.io.IOException
+ */
+ protected boolean isMBeanStateDeployed( ObjectName deploymentMBean ) {
+
+ try {
+ return this.isMBeanStateDeployedImpl(deploymentMBean);
+ }
+ // This super-exception includes JMX operation failures, including:
+ // AttributeNotFoundException, InstanceNotFoundException,
MalformedObjectNameException, ServiceNotFoundException
+ catch (OperationsException ex) {
+ log.log(Level.WARNING, "JMX operation error when retrieving MBean
attribute.", ex);
+ return false;
+ }
+ // All other JMX failures...
+ catch (JMException ex) {
+ log.log(Level.WARNING, "JMX error when retrieving MBean attribute.", ex);
+ return false;
+ }
+ catch (IOException ex) {
+ log.log(Level.SEVERE, "I/O error when retrieving MBean attribute.", ex);
+ return false;
+ }
+
+ }// isMBeanStateDeployed()
+
+ /**
+ * This method should query the JMX server and decide whether the given MBean displays
deployed resource.
+ * @param deploymentMBean Name of the MBean to examine. Differs between AS4 and 5.
+ * @return true if the MBean indicates that the resource is deployed, false
otherwise.
+ * @throws javax.management.JMException upon JMX related error, including invalid
+ * or non-existent MBean / attribute name.
+ * @throws java.io.IOException upon I/O error.
+ */
+ protected abstract boolean isMBeanStateDeployedImpl( ObjectName deploymentMBean )
throws JMException, IOException;
+
+
+}
+