/*
* (C) Copyright 2009 Nuxeo SA (http://nuxeo.com/) and others.
*
* 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.
*
* Contributors:
* Florent Guillaume
*/
package org.nuxeo.runtime.datasource;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
import java.util.Map.Entry;
import javax.naming.Context;
import javax.naming.Name;
import javax.naming.NamingException;
import javax.naming.Reference;
import javax.naming.StringRefAddr;
import javax.naming.spi.ObjectFactory;
import javax.sql.DataSource;
import javax.sql.XADataSource;
import org.apache.commons.logging.LogFactory;
import org.apache.tomcat.jdbc.naming.GenericNamingResourcesFactory;
import org.nuxeo.common.xmap.annotation.XNode;
import org.nuxeo.common.xmap.annotation.XNodeMap;
import org.nuxeo.common.xmap.annotation.XObject;
import org.nuxeo.runtime.api.Framework;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
/**
* The descriptor for a Nuxeo-defined datasource.
* <p>
* The attributes of a {@code <datasource>} element are:
* <ul>
* <li><b>name</b>: the JNDI name (for instance {@code jdbc/foo})</li>
* <li><b>driverClassName</b>: the JDBC driver class name (only for a non-XA
* datasource)</li>
* <li><b>xaDataSource</b>: the XA datasource class name (only for a XA
* datasource)</li>
* </ul>
* <p>
* To configure the characteristics of the pool:
* <ul>
* <li><b>maxActive</b>: the maximum number of active connections</li>
* <li><b>minIdle</b>: the minimum number of idle connections</li>
* <li><b>maxIdle</b>: the maximum number of idle connections</li>
* <li><b>maxWait</b>: the maximum number of milliseconds to wait for a
* connection to be available, or -1 (the default) to wait indefinitely</li>
* <li>... see {@link org.apache.commons.dbcp.BasicDataSource BasicDataSource}
* setters for more</li>
* </ul>
* <p>
* To configure the datasource connections, individual {@code <property>}
* sub-elements are used.
* <p>
* For a non-XA datasource, you must specify at least a <b>url</b>:
*
* <pre>
* <property name="url">jdbc:derby:foo/bar</property>
* <property name="username">nuxeo</property>
* <property name="password">nuxeo</property>
* </pre>
*
* For a XA datasource, see the documentation for your JDBC driver.
*/
@XObject("datasource")
public class DataSourceDescriptor {
/*
* It is not possible to expand the variables in the setters because in
* tests, values are not available in context. A clean up needs to be done
* to have the values during startup.
*/
@XNode("@name")
protected String name;
public String getName() {
return Framework.expandVars(name);
}
@XNode("@xaDataSource")
protected String xaDataSource;
public String getXaDataSource() {
return Framework.expandVars(xaDataSource);
}
@XNode("@dataSource")
protected String dataSource;
public String getDataSource() {
return Framework.expandVars(dataSource);
}
@XNode("@driverClassName")
protected String driverClasssName;
public String getDriverClasssName() {
return Framework.expandVars(driverClasssName);
}
@XNode("")
public Element element;
@XNodeMap(value = "property", key = "@name", type = HashMap.class, componentType = String.class)
public Map<String, String> properties;
protected Reference poolReference;
protected Reference xaReference;
public static class PoolFactory implements ObjectFactory {
@Override
public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> env) {
return Framework.getService(PooledDataSourceRegistry.class).getOrCreatePool(obj, name, nameCtx, env);
}
}
public void bindSelf(Context naming) throws NamingException {
if (xaDataSource != null) {
String xaName = DataSourceHelper.relativize(getName() + "-xa");
poolReference = new Reference(XADataSource.class.getName(), PoolFactory.class.getName(), null);
poolReference.add(new StringRefAddr("dataSourceJNDI", xaName));
xaReference = new Reference(Framework.expandVars(xaDataSource),
GenericNamingResourcesFactory.class.getName(), null);
for (Entry<String, String> e : properties.entrySet()) {
String key = e.getKey();
String value = Framework.expandVars(e.getValue());
StringRefAddr addr = new StringRefAddr(key, value);
xaReference.add(addr);
}
naming.bind(DataSourceHelper.getDataSourceJNDIName(xaName), xaReference);
} else if (dataSource != null) {
poolReference = new Reference(DataSource.class.getName(), PoolFactory.class.getName(), null);
final String name = Framework.expandVars(dataSource);
poolReference.add(new StringRefAddr("dataSourceJNDI", DataSourceHelper.getDataSourceJNDIName(name)));
} else if (driverClasssName != null) {
poolReference = new Reference(DataSource.class.getName(), PoolFactory.class.getName(), null);
} else {
throw new RuntimeException("Datasource " + getName()
+ " should have xaDataSource or driverClassName attribute");
}
for (Entry<String, String> e : properties.entrySet()) {
String key = e.getKey();
String value = Framework.expandVars(e.getValue());
StringRefAddr addr = new StringRefAddr(key, value);
poolReference.add(addr);
}
NamedNodeMap attrs = element.getAttributes();
for (int i = 0; i < attrs.getLength(); i++) {
Node attr = attrs.item(i);
String attrName = attr.getNodeName();
String value = Framework.expandVars(attr.getNodeValue());
StringRefAddr addr = new StringRefAddr(attrName, value);
poolReference.add(addr);
}
LogFactory.getLog(DataSourceDescriptor.class).info("binding " + getName());
String jndiName = DataSourceHelper.getDataSourceJNDIName(getName());
naming.bind(jndiName, poolReference);
// create pooled
naming.lookup(jndiName);
}
public void unbindSelf(Context naming) throws NamingException {
try {
final PooledDataSourceRegistry registry = Framework.getLocalService(PooledDataSourceRegistry.class);
if (registry != null) {
registry.clearPool(getName());
}
} finally {
try {
if (xaReference != null) {
naming.unbind(DataSourceHelper.getDataSourceJNDIName(getName() + "-xa"));
}
} finally {
naming.unbind(DataSourceHelper.getDataSourceJNDIName(getName()));
}
}
}
}