001    /*
002    // This software is subject to the terms of the Eclipse Public License v1.0
003    // Agreement, available at the following URL:
004    // http://www.eclipse.org/legal/epl-v10.html.
005    // Copyright (C) 2007-2009 Julian Hyde
006    // All Rights Reserved.
007    // You must accept the terms of that agreement to use this software.
008    */
009    package org.olap4j.driver.xmla;
010    
011    import org.olap4j.driver.xmla.proxy.*;
012    import org.olap4j.impl.Olap4jUtil;
013    
014    import java.net.*;
015    import java.sql.*;
016    import java.util.*;
017    import java.util.concurrent.*;
018    
019    /**
020     * Olap4j driver for generic XML for Analysis (XMLA) providers.
021     *
022     * <p>Since olap4j is a superset of JDBC, you register this driver as you would
023     * any JDBC driver:
024     *
025     * <blockquote>
026     * <code>Class.forName("org.olap4j.driver.xmla.XmlaOlap4jDriver");</code>
027     * </blockquote>
028     *
029     * Then create a connection using a URL with the prefix "jdbc:xmla:".
030     * For example,
031     *
032     * <blockquote>
033     * <code>import java.sql.Connection;<br/>
034     * import java.sql.DriverManager;<br/>
035     * import org.olap4j.OlapConnection;<br/>
036     * <br/>
037     * Connection connection =<br/>
038     * &nbsp;&nbsp;&nbsp;DriverManager.getConnection(<br/>
039     * &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"jdbc:xmla:");<br/>
040     * OlapConnection olapConnection =<br/>
041     * &nbsp;&nbsp;&nbsp;connection.unwrap(OlapConnection.class);</code>
042     * </blockquote>
043     *
044     * <p>Note how we use the {@link java.sql.Connection#unwrap(Class)} method to down-cast
045     * the JDBC connection object to the extension {@link org.olap4j.OlapConnection}
046     * object. This method is only available in JDBC 4.0 (JDK 1.6 onwards).
047     *
048     * <h3>Connection properties</h3>
049     *
050     * <p>Unless otherwise stated, properties are optional. If a property occurs
051     * multiple times in the connect string, the first occurrence is used.
052     *
053     * <table border="1">
054     * <tr><th>Property</th>     <th>Description</th> </tr>
055     *
056     * <tr><td>Server</td>       <td>URL of HTTP server. Required.</td> </tr>
057     *
058     * <tr><td>Catalog</td>      <td>Catalog name to use.
059     *                               By default, the first one returned by the
060     *                               XMLA server will be used.</td> </tr>
061     *
062     * <tr><td>Provider</td>     <td>Name of the XMLA provider.</td> </tr>
063     *
064     * <tr><td>DataSource</td>   <td>Name of the XMLA datasource. When using a
065     *                               Mondrian backed XMLA server, be sure to
066     *                               include the full datasource name between
067     *                               quotes.</td> </tr>
068     *
069     * <tr><td>Cache</td>        <td><p>Class name of the SOAP cache to use.
070     *                               Must implement interface
071     *              {@link org.olap4j.driver.xmla.proxy.XmlaOlap4jCachedProxy}.
072     *                               A built-in memory cache is available with
073     *              {@link org.olap4j.driver.xmla.cache.XmlaOlap4jNamedMemoryCache}.
074     *
075     *                               <p>By default, no SOAP query cache will be
076     *                               used.</td> </tr>
077     *
078     * <tr> <td>Cache.*</td>     <td>Properties to transfer to the selected cache
079     *                               implementation. See
080     *                          {@link org.olap4j.driver.xmla.cache.XmlaOlap4jCache}
081     *                               or your selected implementation for properties
082     *                               details.</td> </tr>
083     *
084     * <tr><td>TestProxyCookie</td><td>String that uniquely identifies a proxy
085     *                               object in {@link #PROXY_MAP} via which to
086     *                               send XMLA requests for testing
087     *                               purposes.</td> </tr>
088     *
089     * </table>
090     *
091     * @author jhyde, Luc Boudreau
092     * @version $Id: XmlaOlap4jDriver.java 279 2009-09-30 19:07:29Z lucboudreau $
093     * @since May 22, 2007
094     */
095    public class XmlaOlap4jDriver implements Driver {
096    
097        private final Factory factory;
098    
099        /**
100         * Executor shared by all connections making asynchronous XMLA calls.
101         */
102        private static final ExecutorService executor;
103    
104        static {
105            executor = Executors.newCachedThreadPool(
106                new ThreadFactory() {
107                    public Thread newThread(Runnable r) {
108                        Thread t = Executors.defaultThreadFactory().newThread(r);
109                        t.setDaemon(true);
110                        return t;
111                   }
112                }
113            );
114        }
115    
116        private static int nextCookie;
117    
118        static {
119            try {
120                register();
121            } catch (SQLException e) {
122                e.printStackTrace();
123            } catch (RuntimeException e) {
124                e.printStackTrace();
125                throw e;
126            }
127        }
128    
129        /**
130         * Creates an XmlaOlap4jDriver.
131         */
132        protected XmlaOlap4jDriver() {
133            String factoryClassName;
134            try {
135                Class.forName("java.sql.Wrapper");
136                factoryClassName = "org.olap4j.driver.xmla.FactoryJdbc4Impl";
137            } catch (ClassNotFoundException e) {
138                // java.sql.Wrapper is not present. This means we are running JDBC
139                // 3.0 or earlier (probably JDK 1.5). Load the JDBC 3.0 factory
140                factoryClassName = "org.olap4j.driver.xmla.FactoryJdbc3Impl";
141            }
142            try {
143                final Class clazz = Class.forName(factoryClassName);
144                factory = (Factory) clazz.newInstance();
145            } catch (ClassNotFoundException e) {
146                throw new RuntimeException(e);
147            } catch (IllegalAccessException e) {
148                throw new RuntimeException(e);
149            } catch (InstantiationException e) {
150                throw new RuntimeException(e);
151            }
152        }
153    
154        /**
155         * Registers an instance of XmlaOlap4jDriver.
156         *
157         * <p>Called implicitly on class load, and implements the traditional
158         * 'Class.forName' way of registering JDBC drivers.
159         *
160         * @throws SQLException on error
161         */
162        private static void register() throws SQLException {
163            DriverManager.registerDriver(new XmlaOlap4jDriver());
164        }
165    
166        public Connection connect(String url, Properties info) throws SQLException {
167            // Checks if this driver handles this connection, exit otherwise.
168            if (!XmlaOlap4jConnection.acceptsURL(url)) {
169                return null;
170            }
171    
172            // Parses the connection string
173            Map<String, String> map =
174                XmlaOlap4jConnection.parseConnectString(url, info);
175    
176            // Creates a connection proxy
177            XmlaOlap4jProxy proxy = createProxy(map);
178    
179            // returns a connection object to the java API
180            return factory.newConnection(this, proxy, url, info);
181        }
182    
183        public boolean acceptsURL(String url) throws SQLException {
184            return XmlaOlap4jConnection.acceptsURL(url);
185        }
186    
187        public DriverPropertyInfo[] getPropertyInfo(
188            String url, Properties info) throws SQLException
189        {
190            List<DriverPropertyInfo> list = new ArrayList<DriverPropertyInfo>();
191    
192            // Add the contents of info
193            for (Map.Entry<Object, Object> entry : info.entrySet()) {
194                list.add(
195                    new DriverPropertyInfo(
196                        (String) entry.getKey(),
197                        (String) entry.getValue()));
198            }
199            // Next add standard properties
200    
201            return list.toArray(new DriverPropertyInfo[list.size()]);
202        }
203    
204        /**
205         * Returns the driver name. Not in the JDBC API.
206         * @return Driver name
207         */
208        String getName() {
209            return XmlaOlap4jDriverVersion.NAME;
210        }
211    
212        /**
213         * Returns the driver version. Not in the JDBC API.
214         * @return Driver version
215         */
216        public String getVersion() {
217            return XmlaOlap4jDriverVersion.VERSION;
218        }
219    
220        public int getMajorVersion() {
221            return XmlaOlap4jDriverVersion.MAJOR_VERSION;
222        }
223    
224        public int getMinorVersion() {
225            return XmlaOlap4jDriverVersion.MINOR_VERSION;
226        }
227    
228        public boolean jdbcCompliant() {
229            return false;
230        }
231    
232        /**
233         * Creates a Proxy with which to talk to send XML web-service calls.
234         * The usual implementation of Proxy uses HTTP; there is another
235         * implementation, for testing, which talks to mondrian's XMLA service
236         * in-process.
237         *
238         * @param map Connection properties
239         * @return A Proxy with which to submit XML requests
240         */
241        protected XmlaOlap4jProxy createProxy(Map<String, String> map) {
242            String cookie = map.get(Property.TestProxyCookie.name());
243            if (cookie != null) {
244                XmlaOlap4jProxy proxy = PROXY_MAP.get(cookie);
245                if (proxy != null) {
246                    return proxy;
247                }
248            }
249            return new XmlaOlap4jHttpProxy(this);
250        }
251    
252        /**
253         * Returns a future object representing an asynchronous submission of an
254         * XMLA request to a URL.
255         *
256         * @param proxy Proxy via which to send the request
257         * @param url URL of XMLA server
258         * @param request Request
259         * @return Future object from which the byte array containing the result
260         * of the XMLA call can be obtained
261         */
262        public static Future<byte[]> getFuture(
263            final XmlaOlap4jProxy proxy,
264            final URL url,
265            final String request)
266        {
267            return executor.submit(
268                new Callable<byte[]>() {
269                    public byte[] call() throws Exception {
270                        return proxy.get(url, request);
271                    }
272                }
273            );
274        }
275    
276        /**
277         * For testing. Map from a cookie value (which is uniquely generated for
278         * each test) to a proxy object. Uses a weak hash map so that, if the code
279         * that created the proxy 'forgets' the cookie value, then the proxy can
280         * be garbage-collected.
281         */
282        public static final Map<String, XmlaOlap4jProxy> PROXY_MAP =
283            Collections.synchronizedMap(new WeakHashMap<String, XmlaOlap4jProxy>());
284    
285        /**
286         * Generates and returns a unique string.
287         *
288         * @return unique string
289         */
290        public static synchronized String nextCookie() {
291            return "cookie" + nextCookie++;
292        }
293    
294        /**
295         * Properties supported by this driver.
296         */
297        public enum Property {
298            TestProxyCookie(
299                "String that uniquely identifies a proxy object via which to send "
300                    + "XMLA requests for testing purposes."),
301            Server("URL of HTTP server"),
302            Catalog("Catalog name"),
303            Provider("Name of the datasource provider"),
304            DataSource("Name of the datasource"),
305            Cache("Class name of the SOAP cache implementation");
306    
307            /**
308             * Creates a property.
309             *
310             * @param description Description of property
311             */
312            Property(String description) {
313                Olap4jUtil.discard(description);
314            }
315        }
316    
317        /**
318         * This is a mock subclass to prevent retro-compatibility issues.
319         * If you're using this class, please change your code to
320         * use XmlaOlap4jProxy instead.
321         * @author Luc Boudreau
322         *
323         */
324        @Deprecated
325        public static interface Proxy extends XmlaOlap4jProxy {
326        }
327    }
328    
329    // End XmlaOlap4jDriver.java