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