diff --git a/admin/src/main/java/org/teiid/adminapi/AdminPlugin.java b/admin/src/main/java/org/teiid/adminapi/AdminPlugin.java index 6910adc7ae..e34cabd0b5 100644 --- a/admin/src/main/java/org/teiid/adminapi/AdminPlugin.java +++ b/admin/src/main/java/org/teiid/adminapi/AdminPlugin.java @@ -81,6 +81,7 @@ public static enum Event implements BundleUtil.Event { TEIID70053, TEIID70054, TEIID70055, - TEIID70056 + TEIID70056, + TEIID70057 } } diff --git a/admin/src/main/resources/org/teiid/adminapi/i18n.properties b/admin/src/main/resources/org/teiid/adminapi/i18n.properties index 6862b08d51..354113fab6 100644 --- a/admin/src/main/resources/org/teiid/adminapi/i18n.properties +++ b/admin/src/main/resources/org/teiid/adminapi/i18n.properties @@ -100,4 +100,5 @@ unexpected_element7=Unexpected Element {0} encountered, expecting one of {1} {2} TEIID70053=Conflicting mask or condition definition for resource {1} in role {0}. TEIID70054=Invalid empty or missing value for property {0}. TEIID70055=Traslator {0} not found -TEIID70008=Data Source with name {0} still exists in the system. \ No newline at end of file +TEIID70008=Data Source with name {0} still exists in the system. +TEIID70057=Batch operation failed {0} \ No newline at end of file diff --git a/jboss-admin/src/main/java/org/teiid/adminapi/jboss/AdminFactory.java b/jboss-admin/src/main/java/org/teiid/adminapi/jboss/AdminFactory.java index efb5a2e16a..a3768bbdaa 100644 --- a/jboss-admin/src/main/java/org/teiid/adminapi/jboss/AdminFactory.java +++ b/jboss-admin/src/main/java/org/teiid/adminapi/jboss/AdminFactory.java @@ -41,6 +41,8 @@ import javax.security.sasl.RealmChoiceCallback; import org.jboss.as.cli.Util; +import org.jboss.as.cli.batch.impl.DefaultBatch; +import org.jboss.as.cli.batch.impl.DefaultBatchedCommand; import org.jboss.as.cli.operation.OperationFormatException; import org.jboss.as.cli.operation.impl.DefaultOperationRequestAddress; import org.jboss.as.cli.operation.impl.DefaultOperationRequestBuilder; @@ -414,7 +416,17 @@ public String unwrap(ModelNode node) { return null; } }); + List xadrivers = getList(outcome, new AbstractMetadatMapper() { + @Override + public String unwrap(ModelNode node) { + if (node.hasDefined("driver-name") && node.hasDefined("driver-xa-datasource-class-name")) { + return node.get("driver-name").asString()+"-xa"; + } + return null; + } + }); driverList.addAll(drivers); + driverList.addAll(xadrivers); } } catch (IOException e) { throw new AdminComponentException(AdminPlugin.Event.TEIID70052, e); @@ -442,6 +454,17 @@ public String getProfileName() throws AdminException { return this.profileName; } + + /** + * Use this method to create JDBC driver based connection, XA-datasource or Resource Adapter. + * Template Name defines the type of connection, if the template name is ends with "-xa" it is + * considered to be a XA based data source. + * + * @param deploymentName This becomes the pool name, as well as the jndi name of the source + * @param templateName type of source. See {@link getDataSourceNames} for all available types. + * @param properties All properties needed to create a data source, like connection-url, user, password + * to see all the properties use {@link getTemplatePropertyDefinitions} to retrieve the full list + */ @Override public void createDataSource(String deploymentName, String templateName, Properties properties) throws AdminException { flush(); @@ -469,11 +492,13 @@ public void createDataSource(String deploymentName, String templateName, Propert Collection dsProperties = getTemplatePropertyDefinitions(templateName); ArrayList parameters = new ArrayList(); if (properties != null) { - parameters.add("connection-url"); - parameters.add(properties.getProperty("connection-url")); + if (!isXA(templateName)) { + parameters.add("connection-url"); + parameters.add(properties.getProperty("connection-url")); + } for (PropertyDefinition prop : dsProperties) { - if (prop.getName().equals("connection-properties")) { + if (getCustomDatasourceProperties().contains(prop.getName())) { continue; } String value = properties.getProperty(prop.getName()); @@ -490,41 +515,89 @@ public void createDataSource(String deploymentName, String templateName, Propert parameters.add("jndi-name"); parameters.add(addJavaContext(deploymentName)); parameters.add("driver-name"); - parameters.add(templateName); + parameters.add(stripXA(templateName)); parameters.add("pool-name"); parameters.add(deploymentName); - // add data source - cliCall("add", new String[] { "subsystem", "datasources","data-source", deploymentName }, - parameters.toArray(new String[parameters.size()]), - new ResultCallback()); - - // add connection properties that are specific to driver - String cp = properties.getProperty("connection-properties"); - if (cp != null) { - StringTokenizer st = new StringTokenizer(cp, ","); - while(st.hasMoreTokens()) { - String prop = st.nextToken(); - String key = prop.substring(0, prop.indexOf('=')); - String value = prop.substring(prop.indexOf('=')+1); - addConnectionProperty(deploymentName, key, value); - } + DefaultBatch batch = new DefaultBatch(); + try { + if (isXA(templateName)) { + batch.add(new DefaultBatchedCommand("add", + buildRequest("add", new String[] { "subsystem", "datasources","xa-data-source", deploymentName }, + parameters.toArray(new String[parameters.size()])))); + + if (properties.getProperty("connection-url") != null) { + batch.add(new DefaultBatchedCommand("add", addXADatasourceProperty(deploymentName, "connection-url", properties.getProperty("connection-url")))); + } else { + batch.add(new DefaultBatchedCommand("add", addXADatasourceProperty(deploymentName, "DatabaseName", properties.getProperty("DatabaseName")))); + if (properties.getProperty("PortNumber") != null) { + batch.add(new DefaultBatchedCommand("add", addXADatasourceProperty(deploymentName, "PortNumber", properties.getProperty("PortNumber")))); + } + batch.add(new DefaultBatchedCommand("add", addXADatasourceProperty(deploymentName, "ServerName", properties.getProperty("ServerName")))); + } + + // add connection properties that are specific to driver + String cp = properties.getProperty("connection-properties"); + if (cp != null) { + StringTokenizer st = new StringTokenizer(cp, ","); + while(st.hasMoreTokens()) { + String prop = st.nextToken(); + String key = prop.substring(0, prop.indexOf('=')); + String value = prop.substring(prop.indexOf('=')+1); + batch.add(new DefaultBatchedCommand("add", addXADatasourceProperty(deploymentName, key, value))); + } + } + } else { + // add data source + batch.add(new DefaultBatchedCommand("add", + buildRequest("add", new String[] { "subsystem", "datasources","data-source", deploymentName }, + parameters.toArray(new String[parameters.size()])))); + + // add connection properties that are specific to driver + String cp = properties.getProperty("connection-properties"); + if (cp != null) { + StringTokenizer st = new StringTokenizer(cp, ","); + while(st.hasMoreTokens()) { + String prop = st.nextToken(); + String key = prop.substring(0, prop.indexOf('=')); + String value = prop.substring(prop.indexOf('=')+1); + batch.add(new DefaultBatchedCommand("add", addConnectionProperty(deploymentName, key, value))); + } + } + } + } catch (OperationFormatException e) { + throw new AdminProcessingException(AdminPlugin.Event.TEIID70057, + AdminPlugin.Util.gs(AdminPlugin.Event.TEIID70057, batch.toRequest())); } - + cliCall(batch.toRequest(), new ResultCallback()); flush(); } // /subsystem=datasources/data-source=DS/connection-properties=foo:add(value=/home/rareddy/testing) - private void addConnectionProperty(String deploymentName, String key, String value) throws AdminException { + private ModelNode addConnectionProperty(String deploymentName, String key, String value) + throws AdminException, OperationFormatException { if (value == null || value.trim().isEmpty()) { throw new AdminProcessingException(AdminPlugin.Event.TEIID70054, AdminPlugin.Util.gs(AdminPlugin.Event.TEIID70054, key)); } - cliCall("add", new String[] { "subsystem", "datasources", + return buildRequest("add", new String[] { "subsystem", "datasources", "data-source", deploymentName, "connection-properties", key }, - new String[] {"value", value }, new ResultCallback()); + new String[] {"value", value }); } + // /subsystem=datasources/data-source=DS/connection-properties=foo:add(value=/home/rareddy/testing) + private ModelNode addXADatasourceProperty(String deploymentName, + String key, String value) throws AdminException, OperationFormatException { + if (value == null || value.trim().isEmpty()) { + throw new AdminProcessingException(AdminPlugin.Event.TEIID70054, + AdminPlugin.Util.gs(AdminPlugin.Event.TEIID70054, key)); + } + return buildRequest("add", new String[] { "subsystem", "datasources", + "xa-data-source", deploymentName, + "xa-datasource-properties", key }, + new String[] {"value", value }); + } + private void execute(final ModelNode request) throws AdminException { try { ModelNode outcome = this.connection.execute(request); @@ -1187,39 +1260,99 @@ private void buildResourceAdpaterProperties(String rarName, BuildPropertyDefinit "connection-definitions", "any" }, null, builder); } + private boolean isXA(String templateName) { + return templateName.endsWith("-xa"); + } + + private String stripXA(String templateName) { + if (isXA(templateName)) { + return templateName.substring(0, templateName.length()-3); + } + return templateName; + } + + private Set getCustomDatasourceProperties(){ + Set props = new HashSet(); + props.add("connection-properties"); + props.add("DatabaseName"); + props.add("PortNumber"); + props.add("ServerName"); + props.add("connection-url"); + return props; + } + /** * pattern on CLI * /subsystem=datasources/data-source=foo:read-resource-description */ @Override - public Collection getTemplatePropertyDefinitions(String templateName) throws AdminException { + public Collection getTemplatePropertyDefinitions( + String templateName) throws AdminException { BuildPropertyDefinitions builder = new BuildPropertyDefinitions(); - + ArrayList props = builder.getPropertyDefinitions(); + // RAR properties Set resourceAdapters = getResourceAdapterNames(null); if (resourceAdapters.contains(templateName)) { - cliCall("read-rar-description", new String[] {"subsystem", "teiid"}, new String[] {"rar-name", templateName}, builder); + cliCall("read-rar-description", new String[] {"subsystem", "teiid"}, new String[] {"rar-name", + templateName}, builder); buildResourceAdpaterProperties(templateName, builder); return builder.getPropertyDefinitions(); } // get JDBC properties - cliCall("read-resource-description", new String[] {"subsystem", "datasources", "data-source", templateName}, null, builder); - - // add driver specific properties - PropertyDefinitionMetadata cp = new PropertyDefinitionMetadata(); - cp.setName("connection-properties"); - cp.setDisplayName("Addtional Driver Properties"); - cp.setDescription("The connection-properties element allows you to pass in arbitrary connection properties to the Driver.connect(url, props) method. Supply comma separated name-value pairs"); //$NON-NLS-1$ - cp.setRequired(false); - cp.setAdvanced(true); - ArrayList props = builder.getPropertyDefinitions(); - props.add(cp); + if (isXA(templateName)) { + cliCall("read-resource-description", new String[] {"subsystem", "datasources", "data-source", + stripXA(templateName)}, null, builder); + addXAProperties(props); + } else { + cliCall("read-resource-description", new String[] {"subsystem", "datasources", "data-source", + templateName}, null, builder); + addDriverproperties(props); + } return props; } - @Override + private void addProperty(String name, String displayName, + String description, boolean required, boolean advanced, + ArrayList props) { + PropertyDefinitionMetadata cp = new PropertyDefinitionMetadata(); + cp.setName(name); + cp.setDisplayName(displayName); + cp.setDescription(description); //$NON-NLS-1$ + cp.setRequired(required); + cp.setAdvanced(advanced); + props.add(cp); + } + + private void addXAProperties(ArrayList props) { + addProperty("connection-properties", "Addtional XA Datasource Properties", + "The connection-properties element allows you to pass in arbitrary connection " + + "properties to the Data Source setters methods. " + + "Supply comma separated name-value pairs. ex:p1=v1,p2=v2", + false, true, props); + + addProperty("DatabaseName", "Database Name", "Name of the Database", + true, false, props); + addProperty("ServerName", "Server Name", "Server host name where database exists", + true, false, props); + addProperty("PortNumber", "Port Number", "Port number of the database server", + false, false, props); + addProperty("connection-url", "Connection URL", "Connection URL to the data source", + false, false, props); + } + + private void addDriverproperties(ArrayList props) { + // add driver specific properties + addProperty("connection-properties", "Addtional Driver Properties", + "The connection-properties element allows you to pass in arbitrary connection " + + "properties to the Driver.connect(url, props) method. " + + "Supply comma separated name-value pairs. ex:p1=v1,p2=v2", + false, true, props); + } + + @Override @Deprecated public Collection getTranslatorPropertyDefinitions(String translatorName) throws AdminException{ BuildPropertyDefinitions builder = new BuildPropertyDefinitions(); @@ -1532,23 +1665,8 @@ private ModelNode buildRequest(String subsystem, String operationName, String... } private void cliCall(String operationName, String[] address, String[] params, ResultCallback callback) throws AdminException { - DefaultOperationRequestBuilder builder = new DefaultOperationRequestBuilder(); - final ModelNode request; try { - if (address.length % 2 != 0) { - throw new IllegalArgumentException("Failed to build operation"); //$NON-NLS-1$ - } - addProfileNode(builder); - for (int i = 0; i < address.length; i+=2) { - builder.addNode(address[i], address[i+1]); - } - builder.setOperationName(operationName); - request = builder.buildRequest(); - if (params != null && params.length % 2 == 0) { - for (int i = 0; i < params.length; i+=2) { - builder.addProperty(params[i], params[i+1]); - } - } + ModelNode request = buildRequest(operationName, address, params); ModelNode outcome = this.connection.execute(request); ModelNode result = null; if (Util.isSuccess(outcome)) { @@ -1566,6 +1684,44 @@ private void cliCall(String operationName, String[] address, String[] params, Re throw new AdminComponentException(AdminPlugin.Event.TEIID70007, e); } } + + private void cliCall(ModelNode request, ResultCallback callback) throws AdminException { + try { + ModelNode outcome = this.connection.execute(request); + ModelNode result = null; + if (Util.isSuccess(outcome)) { + if (outcome.hasDefined("result")) { + result = outcome.get("result"); + } + callback.onSuccess(outcome, result); + } + else { + callback.onFailure(Util.getFailureDescription(outcome)); + } + } catch (IOException e) { + throw new AdminComponentException(AdminPlugin.Event.TEIID70007, e); + } + } + + private ModelNode buildRequest(String operationName, String[] address, String[] params) + throws AdminException, OperationFormatException { + if (address.length % 2 != 0) { + throw new IllegalArgumentException("Failed to build operation"); //$NON-NLS-1$ + } + DefaultOperationRequestBuilder builder = new DefaultOperationRequestBuilder(); + addProfileNode(builder); + for (int i = 0; i < address.length; i+=2) { + builder.addNode(address[i], address[i+1]); + } + builder.setOperationName(operationName); + ModelNode request = builder.buildRequest(); + if (params != null && params.length % 2 == 0) { + for (int i = 0; i < params.length; i+=2) { + builder.addProperty(params[i], params[i+1]); + } + } + return request; + } private List getDomainAwareList(ModelNode operationResult, MetadataMapper mapper) { if (this.domainMode) { diff --git a/test-integration/common/src/test/java/org/teiid/arquillian/IntegrationTestDeployment.java b/test-integration/common/src/test/java/org/teiid/arquillian/IntegrationTestDeployment.java index 663164490e..e5bb8ea257 100644 --- a/test-integration/common/src/test/java/org/teiid/arquillian/IntegrationTestDeployment.java +++ b/test-integration/common/src/test/java/org/teiid/arquillian/IntegrationTestDeployment.java @@ -401,7 +401,8 @@ public void testGetRequests() throws Exception { @Test public void getDatasourceTemplateNames() throws Exception { Set vals = new HashSet(Arrays.asList(new String[]{"teiid-local", "google", "teiid", "ldap", - "accumulo", "file", "cassandra", "salesforce", "salesforce-34", "mongodb", "solr", "webservice", "simpledb", "h2"})); + "accumulo", "file", "cassandra", "salesforce", "salesforce-34", "mongodb", "solr", "webservice", + "simpledb", "h2", "teiid-xa", "h2-xa", "teiid-local-xa"})); deployVdb(); Set templates = admin.getDataSourceTemplateNames(); assertEquals(vals, templates); @@ -547,6 +548,43 @@ public void testCreateConnectionFactory() throws Exception{ admin.deleteDataSource(deployedName); } + @Test + public void testCreateXADatasource() throws Exception { + String vdbName = "test"; + String deployedName = "fooXA"; + String testVDB = "\n" + + "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + ""; + admin.deploy("test-vdb.xml", new ByteArrayInputStream(testVDB.getBytes())); + AdminUtil.waitForVDBLoad(admin, vdbName, 1, 3); + + assertTrue(admin.getDataSourceTemplateNames().contains("teiid-xa")); + + Properties p = new Properties(); + p.setProperty("DatabaseName", "test"); + try { + admin.createDataSource(deployedName, "teiid-xa", p); + fail("should have fail not find portNumber"); + } catch(AdminException e) { + } + + assertFalse(admin.getDataSourceNames().contains(deployedName)); + + p.setProperty("ServerName", "127.0.0.1"); + p.setProperty("PortNumber", "31000"); + + admin.createDataSource(deployedName, "teiid-xa", p); + + assertTrue(admin.getDataSourceNames().contains(deployedName)); + + + //admin.deleteDataSource(deployedName); + } + @Test public void testVDBRestart() throws Exception{ String vdbName = "test";