/*
* JBoss, Home of Professional Open Source.
* Copyright 2006, 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.resource.adapter.jdbc.xa;
import org.jboss.resource.adapter.jdbc.BaseWrapperManagedConnectionFactory;
import org.jboss.resource.adapter.jdbc.URLSelectorStrategy;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.beans.PropertyEditorManager;
import java.beans.PropertyEditor;
import javax.sql.XADataSource;
import javax.sql.XAConnection;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import javax.resource.ResourceException;
import javax.resource.spi.ConnectionRequestInfo;
import javax.resource.spi.ManagedConnection;
import javax.security.auth.Subject;
import org.jboss.resource.JBossResourceException;
/**
* XAManagedConnectionFactory
*
* @author <a href="mailto:d_jencks@users.sourceforge.net">David Jencks</a>
* @author <a href="mailto:adrian@jboss.com">Adrian Brock</a>
* @version $Revision: 73443 $
*/
public class XAManagedConnectionFactory extends BaseWrapperManagedConnectionFactory
{
private static final long serialVersionUID = 1647927657609573729L;
private String xaDataSourceClass;
private String xaDataSourceProperties;
protected final Properties xaProps = new Properties();
private Boolean isSameRMOverrideValue;
private XADataSource xads;
private String urlProperty;
private URLSelectorStrategy xadsSelector;
public String getURLProperty()
{
return urlProperty;
}
public void setURLProperty(String urlProperty) throws ResourceException
{
this.urlProperty = urlProperty;
initSelector();
}
public void setURLDelimiter(String urlDelimiter) throws ResourceException
{
this.urlDelimiter = urlDelimiter;
initSelector();
}
private void initSelector() throws JBossResourceException
{
if(urlProperty != null && urlProperty.length() > 0)
{
String urlsStr = xaProps.getProperty(urlProperty);
if(urlsStr != null && urlsStr.trim().length() > 0 && urlDelimiter != null && urlDelimiter.trim().length() > 0)
{
List xaDataList = new ArrayList();
// copy xaProps
// ctor doesn't work because iteration won't include defaults
// Properties xaPropsCopy = new Properties(xaProps);
Properties xaPropsCopy = new Properties();
for(Iterator i = xaProps.keySet().iterator(); i.hasNext();)
{
Object key = i.next();
xaPropsCopy.put(key, xaProps.get(key));
}
int urlStart = 0;
int urlEnd = urlsStr.indexOf(urlDelimiter);
while(urlEnd > 0)
{
String url = urlsStr.substring(urlStart, urlEnd);
xaPropsCopy.setProperty(urlProperty, url);
XADataSource xads = createXaDataSource(xaPropsCopy);
xaDataList.add(new XAData(xads, url));
urlStart = ++urlEnd;
urlEnd = urlsStr.indexOf(urlDelimiter, urlEnd);
log.debug("added XA HA connection url: " + url);
}
if(urlStart != urlsStr.length())
{
String url = urlsStr.substring(urlStart, urlsStr.length());
xaPropsCopy.setProperty(urlProperty, url);
XADataSource xads = createXaDataSource(xaPropsCopy);
xaDataList.add(new XAData(xads, url));
log.debug("added XA HA connection url: " + url);
}
if(getUrlSelectorStrategyClassName()==null)
{
xadsSelector = new XADataSelector(xaDataList);
log.debug("Default URLSelectorStrategy is being used : "+xadsSelector);
}
else
{
xadsSelector = (URLSelectorStrategy)loadClass(getUrlSelectorStrategyClassName(),xaDataList);
log.debug("Customized URLSelectorStrategy is being used : "+xadsSelector);
}
}
}
}
private XADataSource createXaDataSource(Properties xaProps)
throws JBossResourceException
{
if(getXADataSourceClass() == null)
{
throw new JBossResourceException("No XADataSourceClass supplied!");
}
XADataSource xads;
try
{
Class clazz = Thread.currentThread().getContextClassLoader().loadClass(getXADataSourceClass());
xads = (XADataSource)clazz.newInstance();
Class[] NOCLASSES = new Class[]{};
for(Iterator i = xaProps.keySet().iterator(); i.hasNext();)
{
String name = (String)i.next();
String value = xaProps.getProperty(name);
char firstCharName = Character.toUpperCase(name.charAt(0));
if (name.length() > 1)
name = firstCharName+name.substring(1);
else
name = ""+firstCharName;
//This is a bad solution. On the other hand the only known example
// of a setter with no getter is for Oracle with password.
//Anyway, each xadatasource implementation should get its
//own subclass of this that explicitly sets the
//properties individually.
Class type = null;
try
{
Method getter = clazz.getMethod("get" + name, NOCLASSES);
type = getter.getReturnType();
}
catch(NoSuchMethodException e)
{
try
{
//HACK for now until we can rethink the XADataSourceProperties variable and pass type information
Method isMethod = clazz.getMethod("is" + name, NOCLASSES);
type = isMethod.getReturnType();
}
catch(NoSuchMethodException nsme)
{
type = String.class;
}
} // end of try-catch
Method setter = clazz.getMethod("set" + name, new Class[]{type});
PropertyEditor editor = PropertyEditorManager.findEditor(type);
if(editor == null)
{
throw new JBossResourceException("No property editor found for type: " + type);
} // end of if ()
editor.setAsText(value);
setter.invoke(xads, new Object[]{editor.getValue()});
} // end of for ()
}
catch(ClassNotFoundException cnfe)
{
throw new JBossResourceException("Class not found for XADataSource " + getXADataSourceClass(), cnfe);
} // end of try-catch
catch(InstantiationException ie)
{
throw new JBossResourceException("Could not create an XADataSource: ", ie);
} // end of catch
catch(IllegalAccessException iae)
{
throw new JBossResourceException("Could not set a property: ", iae);
} // end of catch
catch(IllegalArgumentException iae)
{
throw new JBossResourceException("Could not set a property: ", iae);
} // end of catch
catch(InvocationTargetException ite)
{
throw new JBossResourceException("Could not invoke setter on XADataSource: ", ite);
} // end of catch
catch(NoSuchMethodException nsme)
{
throw new JBossResourceException("Could not find accessor on XADataSource: ", nsme);
} // end of catch
return xads;
}
// Default Implementaion of the URLSelectorStrategy
public static class XADataSelector implements URLSelectorStrategy
{
private final List xaDataList;
private int xaDataIndex;
private XAData xaData;
public XADataSelector(List xaDataList)
{
if(xaDataList == null || xaDataList.size() == 0)
{
throw new IllegalStateException("Expected non-empty list of XADataSource/URL pairs but got: " + xaDataList);
}
this.xaDataList = xaDataList;
}
public synchronized XAData getXAData()
{
if(xaData == null)
{
if(xaDataIndex == xaDataList.size())
{
xaDataIndex = 0;
}
xaData = (XAData)xaDataList.get(xaDataIndex++);
}
return xaData;
}
public synchronized void failedXAData(XAData xads)
{
if(xads.equals(this.xaData))
{
this.xaData = null;
}
}
/* URLSelectorStrategy Implementation goes here*/
public List getCustomSortedUrls()
{
return xaDataList;
}
public void failedUrlObject(Object urlObject)
{
failedXAData((XAData)urlObject);
}
public List getAllUrlObjects()
{
return xaDataList;
}
public Object getUrlObject()
{
return getXAData();
}
}
private static class XAData
{
public final XADataSource xads;
public final String url;
public XAData(XADataSource xads, String url)
{
this.xads = xads;
this.url = url;
}
public boolean equals(Object o)
{
if(this == o)
{
return true;
}
if(!(o instanceof XAData))
{
return false;
}
final XAData xaData = (XAData)o;
if(!url.equals(xaData.url))
{
return false;
}
return true;
}
public int hashCode()
{
return url.hashCode();
}
public String toString()
{
return "[XA URL=" + url + "]";
}
}
public XAManagedConnectionFactory()
{
}
/**
* Get the XaDataSourceClass value.
*
* @return the XaDataSourceClass value.
*/
public String getXADataSourceClass()
{
return xaDataSourceClass;
}
/**
* Set the XaDataSourceClass value.
*
* @param xaDataSourceClass The new XaDataSourceClass value.
*/
public void setXADataSourceClass(String xaDataSourceClass)
{
this.xaDataSourceClass = xaDataSourceClass;
}
/**
* Get the XADataSourceProperties value.
*
* @return the XADataSourceProperties value.
*/
public String getXADataSourceProperties()
{
return xaDataSourceProperties;
}
/**
* Set the XADataSourceProperties value.
*
* @param xaDataSourceProperties The new XADataSourceProperties value.
*/
public void setXADataSourceProperties(String xaDataSourceProperties) throws ResourceException
{
this.xaDataSourceProperties = xaDataSourceProperties;
xaProps.clear();
if (xaDataSourceProperties != null)
{
// Map any \ to \\
xaDataSourceProperties = xaDataSourceProperties.replaceAll("\\\\", "\\\\\\\\");
InputStream is = new ByteArrayInputStream(xaDataSourceProperties.getBytes());
try
{
xaProps.load(is);
}
catch (IOException ioe)
{
throw new JBossResourceException("Could not load connection properties", ioe);
}
}
initSelector();
}
/**
* Get the IsSameRMOverrideValue value.
*
* @return the IsSameRMOverrideValue value.
*/
public Boolean getIsSameRMOverrideValue()
{
return isSameRMOverrideValue;
}
/**
* Set the IsSameRMOverrideValue value.
*
* @param isSameRMOverrideValue The new IsSameRMOverrideValue value.
*/
public void setIsSameRMOverrideValue(Boolean isSameRMOverrideValue)
{
this.isSameRMOverrideValue = isSameRMOverrideValue;
}
public ManagedConnection createManagedConnection(Subject subject, ConnectionRequestInfo cri)
throws javax.resource.ResourceException
{
if(xadsSelector == null)
{
return getXAManagedConnection(subject,cri);
}
// try to get a connection as many times as many urls we have in the list
for(int i = 0; i < xadsSelector.getCustomSortedUrls().size(); ++i)
{
XAData xaData = (XAData)xadsSelector.getUrlObject();
if(log.isTraceEnabled())
{
log.trace("Trying to create an XA connection to " + xaData.url);
}
try
{
return getXAManagedConnection(subject,cri);
}
catch(ResourceException e)
{
log.warn("Failed to create an XA connection to " + xaData.url + ": " + e.getMessage());
xadsSelector.failedUrlObject(xaData);
}
}
// we have supposedly tried all the urls
throw new JBossResourceException(
"Could not create connection using any of the URLs: " + xadsSelector.getAllUrlObjects()
);
}
public ManagedConnection getXAManagedConnection(Subject subject, ConnectionRequestInfo cri)
throws javax.resource.ResourceException
{
XAConnection xaConnection = null;
Properties props = getConnectionProperties(subject, cri);
try
{
final String user = props.getProperty("user");
final String password = props.getProperty("password");
xaConnection = (user != null)
? getXADataSource().getXAConnection(user, password)
: getXADataSource().getXAConnection();
return newXAManagedConnection(props, xaConnection);
}
catch (Throwable e)
{
try
{
if (xaConnection != null)
xaConnection.close();
}
catch (Throwable ignored)
{
}
throw new JBossResourceException("Could not create connection", e);
}
}
/**
* This method can be overwritten by sublcasses to provide rm specific
* implementation of XAManagedConnection
*/
protected ManagedConnection newXAManagedConnection(Properties props, XAConnection xaConnection) throws SQLException
{
return new XAManagedConnection(this, xaConnection, props, transactionIsolation, preparedStatementCacheSize);
}
public ManagedConnection matchManagedConnections(Set mcs, Subject subject, ConnectionRequestInfo cri)
throws ResourceException
{
Properties newProps = getConnectionProperties(subject, cri);
for (Iterator i = mcs.iterator(); i.hasNext();)
{
Object o = i.next();
if (o instanceof XAManagedConnection)
{
XAManagedConnection mc = (XAManagedConnection) o;
if (mc.getProperties().equals(newProps))
{
//Next check to see if we are validating on matchManagedConnections
if ((getValidateOnMatch() && mc.checkValid()) || !getValidateOnMatch())
{
return mc;
}
}
}
}
return null;
}
public int hashCode()
{
int result = 17;
result = result * 37 + ((xaDataSourceClass == null) ? 0 : xaDataSourceClass.hashCode());
result = result * 37 + xaProps.hashCode();
result = result * 37 + ((userName == null) ? 0 : userName.hashCode());
result = result * 37 + ((password == null) ? 0 : password.hashCode());
result = result * 37 + transactionIsolation;
return result;
}
public boolean equals(Object other)
{
if (this == other)
return true;
if (getClass() != other.getClass())
return false;
XAManagedConnectionFactory otherMcf = (XAManagedConnectionFactory) other;
return this.xaDataSourceClass.equals(otherMcf.xaDataSourceClass) && this.xaProps.equals(otherMcf.xaProps)
&& ((this.userName == null) ? otherMcf.userName == null : this.userName.equals(otherMcf.userName))
&& ((this.password == null) ? otherMcf.password == null : this.password.equals(otherMcf.password))
&& this.transactionIsolation == otherMcf.transactionIsolation;
}
protected synchronized XADataSource getXADataSource() throws ResourceException
{
if(xadsSelector != null)
{
XAData xada = (XAData)xadsSelector.getUrlObject();
return xada.xads;
}
if (xads == null)
{
if (xaDataSourceClass == null)
throw new JBossResourceException("No XADataSourceClass supplied!");
try
{
Class clazz = Thread.currentThread().getContextClassLoader().loadClass(xaDataSourceClass);
xads = (XADataSource) clazz.newInstance();
Class[] NOCLASSES = new Class[] {};
for (Iterator i = xaProps.keySet().iterator(); i.hasNext();)
{
String name = (String) i.next();
String value = xaProps.getProperty(name);
char firstCharName = Character.toUpperCase(name.charAt(0));
if (name.length() > 1)
name = firstCharName+name.substring(1);
else
name = ""+firstCharName;
//This is a bad solution. On the other hand the only known example
// of a setter with no getter is for Oracle with password.
//Anyway, each xadatasource implementation should get its
//own subclass of this that explicitly sets the
//properties individually.
Class type = null;
try
{
Method getter = clazz.getMethod("get" + name, NOCLASSES);
type = getter.getReturnType();
}
catch (NoSuchMethodException e)
{
try
{
//HACK for now until we can rethink the XADataSourceProperties variable and pass type information
Method isMethod = clazz.getMethod("is" + name, NOCLASSES);
type = isMethod.getReturnType();
}
catch(NoSuchMethodException nsme)
{
type = String.class;
}
}
Method setter = clazz.getMethod("set" + name, new Class[] { type });
PropertyEditor editor = PropertyEditorManager.findEditor(type);
if (editor == null)
throw new JBossResourceException("No property editor found for type: " + type);
editor.setAsText(value);
setter.invoke(xads, new Object[] { editor.getValue() });
}
}
catch (ClassNotFoundException cnfe)
{
throw new JBossResourceException("Class not found for XADataSource " + xaDataSourceClass, cnfe);
}
catch (InstantiationException ie)
{
throw new JBossResourceException("Could not create an XADataSource: ", ie);
}
catch (IllegalAccessException iae)
{
throw new JBossResourceException("Could not set a property: ", iae);
}
catch (IllegalArgumentException iae)
{
throw new JBossResourceException("Could not set a property: ", iae);
}
catch (InvocationTargetException ite)
{
throw new JBossResourceException("Could not invoke setter on XADataSource: ", ite);
}
catch (NoSuchMethodException nsme)
{
throw new JBossResourceException("Could not find accessor on XADataSource: ", nsme);
}
}
return xads;
}
protected Properties getXaProps()
{
return xaProps;
}
}