001/*
002 * (C) Copyright 2009 Nuxeo SA (http://nuxeo.com/) and contributors.
003 *
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the GNU Lesser General Public License
006 * (LGPL) version 2.1 which accompanies this distribution, and is available at
007 * http://www.gnu.org/licenses/lgpl.html
008 *
009 * This library is distributed in the hope that it will be useful,
010 * but WITHOUT ANY WARRANTY; without even the implied warranty of
011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012 * Lesser General Public License for more details.
013 *
014 * Contributors:
015 *     Florent Guillaume
016 */
017
018package org.nuxeo.runtime.datasource;
019
020import java.util.HashMap;
021import java.util.Hashtable;
022import java.util.Map;
023import java.util.Map.Entry;
024
025import javax.naming.Context;
026import javax.naming.Name;
027import javax.naming.NamingException;
028import javax.naming.Reference;
029import javax.naming.StringRefAddr;
030import javax.naming.spi.ObjectFactory;
031import javax.sql.DataSource;
032import javax.sql.XADataSource;
033
034import org.apache.commons.logging.LogFactory;
035import org.apache.tomcat.jdbc.naming.GenericNamingResourcesFactory;
036import org.nuxeo.common.xmap.annotation.XNode;
037import org.nuxeo.common.xmap.annotation.XNodeMap;
038import org.nuxeo.common.xmap.annotation.XObject;
039import org.nuxeo.runtime.api.Framework;
040import org.w3c.dom.Element;
041import org.w3c.dom.NamedNodeMap;
042import org.w3c.dom.Node;
043
044/**
045 * The descriptor for a Nuxeo-defined datasource.
046 * <p>
047 * The attributes of a {@code <datasource>} element are:
048 * <ul>
049 * <li><b>name</b>: the JNDI name (for instance {@code jdbc/foo})</li>
050 * <li><b>driverClassName</b>: the JDBC driver class name (only for a non-XA datasource)</li>
051 * <li><b>xaDataSource</b>: the XA datasource class name (only for a XA datasource)</li>
052 * </ul>
053 * <p>
054 * To configure the characteristics of the pool:
055 * <ul>
056 * <li><b>maxActive</b>: the maximum number of active connections</li>
057 * <li><b>minIdle</b>: the minimum number of idle connections</li>
058 * <li><b>maxIdle</b>: the maximum number of idle connections</li>
059 * <li><b>maxWait</b>: the maximum number of milliseconds to wait for a connection to be available, or -1 (the default)
060 * to wait indefinitely</li>
061 * <li>... see {@link org.apache.commons.dbcp.BasicDataSource BasicDataSource} setters for more</li>
062 * </ul>
063 * <p>
064 * To configure the datasource connections, individual {@code <property>} sub-elements are used.
065 * <p>
066 * For a non-XA datasource, you must specify at least a <b>url</b>:
067 *
068 * <pre>
069 *   &lt;property name=&quot;url&quot;&gt;jdbc:derby:foo/bar&lt;/property&gt;
070 *   &lt;property name=&quot;username&quot;&gt;nuxeo&lt;/property&gt;
071 *   &lt;property name=&quot;password&quot;&gt;nuxeo&lt;/property&gt;
072 * </pre>
073 *
074 * For a XA datasource, see the documentation for your JDBC driver.
075 */
076@XObject("datasource")
077public class DataSourceDescriptor {
078
079    /*
080     * It is not possible to expand the variables in the setters because in tests, values are not available in context.
081     * A clean up needs to be done to have the values during startup.
082     */
083
084    @XNode("@name")
085    protected String name;
086
087    public String getName() {
088        return Framework.expandVars(name);
089    }
090
091    @XNode("@xaDataSource")
092    protected String xaDataSource;
093
094    public String getXaDataSource() {
095        return Framework.expandVars(xaDataSource);
096    }
097
098    @XNode("@dataSource")
099    protected String dataSource;
100
101    public String getDataSource() {
102        return Framework.expandVars(dataSource);
103    }
104
105    @XNode("@driverClassName")
106    protected String driverClasssName;
107
108    public String getDriverClasssName() {
109        return Framework.expandVars(driverClasssName);
110    }
111
112    @XNode("")
113    public Element element;
114
115    @XNodeMap(value = "property", key = "@name", type = HashMap.class, componentType = String.class)
116    public Map<String, String> properties;
117
118    protected Reference poolReference;
119
120    protected Reference xaReference;
121
122    public static class PoolFactory implements ObjectFactory {
123
124        @Override
125        public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> env) {
126            return Framework.getService(PooledDataSourceRegistry.class).getOrCreatePool(obj, name, nameCtx, env);
127        }
128
129    }
130
131    public void bindSelf(Context naming) throws NamingException {
132        if (xaDataSource != null) {
133            String xaName = DataSourceHelper.relativize(getName() + "-xa");
134            poolReference = new Reference(XADataSource.class.getName(), PoolFactory.class.getName(), null);
135            poolReference.add(new StringRefAddr("dataSourceJNDI", xaName));
136            xaReference = new Reference(Framework.expandVars(xaDataSource),
137                    GenericNamingResourcesFactory.class.getName(), null);
138            for (Entry<String, String> e : properties.entrySet()) {
139                String key = e.getKey();
140                String value = Framework.expandVars(e.getValue());
141                StringRefAddr addr = new StringRefAddr(key, value);
142                xaReference.add(addr);
143            }
144            naming.bind(DataSourceHelper.getDataSourceJNDIName(xaName), xaReference);
145        } else if (dataSource != null) {
146            poolReference = new Reference(DataSource.class.getName(), PoolFactory.class.getName(), null);
147            final String name = Framework.expandVars(dataSource);
148            poolReference.add(new StringRefAddr("dataSourceJNDI", DataSourceHelper.getDataSourceJNDIName(name)));
149        } else if (driverClasssName != null) {
150            poolReference = new Reference(DataSource.class.getName(), PoolFactory.class.getName(), null);
151        } else {
152            throw new RuntimeException("Datasource " + getName()
153                    + " should have xaDataSource or driverClassName attribute");
154        }
155
156        for (Entry<String, String> e : properties.entrySet()) {
157            String key = e.getKey();
158            String value = Framework.expandVars(e.getValue());
159            StringRefAddr addr = new StringRefAddr(key, value);
160            poolReference.add(addr);
161        }
162
163        NamedNodeMap attrs = element.getAttributes();
164        for (int i = 0; i < attrs.getLength(); i++) {
165            Node attr = attrs.item(i);
166            String attrName = attr.getNodeName();
167            String value = Framework.expandVars(attr.getNodeValue());
168            StringRefAddr addr = new StringRefAddr(attrName, value);
169            poolReference.add(addr);
170        }
171
172        LogFactory.getLog(DataSourceDescriptor.class).info("binding " + getName());
173        String jndiName = DataSourceHelper.getDataSourceJNDIName(getName());
174        naming.bind(jndiName, poolReference);
175        // create pooled
176        naming.lookup(jndiName);
177    }
178
179    public void unbindSelf(Context naming) throws NamingException {
180        try {
181            final PooledDataSourceRegistry registry = Framework.getLocalService(PooledDataSourceRegistry.class);
182            if (registry != null) {
183                registry.clearPool(getName());
184            }
185        } finally {
186            try {
187                if (xaReference != null) {
188                    naming.unbind(DataSourceHelper.getDataSourceJNDIName(getName() + "-xa"));
189                }
190            } finally {
191                naming.unbind(DataSourceHelper.getDataSourceJNDIName(getName()));
192            }
193        }
194    }
195
196}