001    /*
002    // $Id: XmlaOlap4jDriver.java 490 2012-01-23 22:35:25Z lucboudreau $
003    //
004    // Licensed to Julian Hyde under one or more contributor license
005    // agreements. See the NOTICE file distributed with this work for
006    // additional information regarding copyright ownership.
007    //
008    // Julian Hyde licenses this file to you under the Apache License,
009    // Version 2.0 (the "License"); you may not use this file except in
010    // compliance with the License. You may obtain a copy of the License at:
011    //
012    // http://www.apache.org/licenses/LICENSE-2.0
013    //
014    // Unless required by applicable law or agreed to in writing, software
015    // distributed under the License is distributed on an "AS IS" BASIS,
016    // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017    // See the License for the specific language governing permissions and
018    // limitations under the License.
019    */
020    package org.olap4j.driver.xmla;
021    
022    import org.olap4j.driver.xmla.proxy.XmlaOlap4jHttpProxy;
023    import org.olap4j.driver.xmla.proxy.XmlaOlap4jProxy;
024    import org.olap4j.impl.Olap4jUtil;
025    
026    import java.sql.*;
027    import java.util.*;
028    import java.util.concurrent.*;
029    import java.util.logging.Logger;
030    
031    /**
032     * Olap4j driver for generic XML for Analysis (XMLA) providers.
033     *
034     * <p>Since olap4j is a superset of JDBC, you register this driver as you would
035     * any JDBC driver:
036     *
037     * <blockquote>
038     * <code>Class.forName("org.olap4j.driver.xmla.XmlaOlap4jDriver");</code>
039     * </blockquote>
040     *
041     * Then create a connection using a URL with the prefix "jdbc:xmla:".
042     * For example,
043     *
044     * <blockquote>
045     * <code>import java.sql.Connection;<br/>
046     * import java.sql.DriverManager;<br/>
047     * import org.olap4j.OlapConnection;<br/>
048     * <br/>
049     * Connection connection =<br/>
050     * &nbsp;&nbsp;&nbsp;DriverManager.getConnection(<br/>
051     * &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"jdbc:xmla:");<br/>
052     * OlapConnection olapConnection =<br/>
053     * &nbsp;&nbsp;&nbsp;connection.unwrap(OlapConnection.class);</code>
054     * </blockquote>
055     *
056     * <p>Note how we use the java.sql.Connection#unwrap(Class) method to down-cast
057     * the JDBC connection object to the extension {@link org.olap4j.OlapConnection}
058     * object. This method is only available in JDBC 4.0 (JDK 1.6 onwards).
059     *
060     * <h3>Connection properties</h3>
061     *
062     * <p>Unless otherwise stated, properties are optional. If a property occurs
063     * multiple times in the connect string, the first occurrence is used.
064     *
065     * <p>It is also possible to pass properties to the server end-point using
066     * JDBC connection properties as part of the XMLA driver connection properties.
067     * If the JDBC URL contains properties that are not enumerated in
068     * {@link Property}, they will be included as part of the SOAP PropertyList
069     * element.
070     *
071     *
072     * <table border="1">
073     * <tr><th>Property</th>     <th>Description</th> </tr>
074     *
075     * <tr><td>Server</td>       <td>URL of HTTP server. Required.</td></tr>
076     *
077     * <tr><td>Catalog</td>      <td>Catalog name to use.
078     *                               By default, the first one returned by the
079     *                               XMLA server will be used.</td></tr>
080     *
081     * <tr><td>Schema</td>      <td>Schema name to use.
082     *                               By default, the first one returned by the
083     *                               XMLA server will be used.</td></tr>
084     *
085     * <tr><td>Database</td>    <td>Name of the XMLA database.
086     *                               By default, the first one returned by the
087     *                               XMLA server will be used.</td></tr>
088     *
089     * <tr><td>Cache</td>      <td><p>Class name of the SOAP cache to use.
090     *                             Must implement interface
091     *              {@link org.olap4j.driver.xmla.proxy.XmlaOlap4jCachedProxy}.
092     *                             A built-in memory cache is available with
093     *              {@link org.olap4j.driver.xmla.cache.XmlaOlap4jNamedMemoryCache}.
094     *
095     *                         <p>By default, no SOAP query cache will be
096     *                             used.
097     *                             </td></tr>
098     * <tr><td>Cache.*</td>    <td>Properties to transfer to the selected cache
099     *                             implementation. See
100     *                          {@link org.olap4j.driver.xmla.cache.XmlaOlap4jCache}
101     *                             or your selected implementation for properties
102     *                             details.
103     *                             </td></tr>
104     * <tr><td>TestProxyCookie</td><td>String that uniquely identifies a proxy
105     *                             object in {@link #PROXY_MAP} via which to
106     *                             send XMLA requests for testing
107     *                             purposes.
108     *                             </td></tr>
109     * <tr><td>Role</td>       <td>Comma separated list of role names used for
110     *                             this connection (Optional). <br />
111     *                             Available role names can be retrieved via
112     *    {@link org.olap4j.driver.xmla.XmlaOlap4jConnection#getAvailableRoleNames}
113     *                             </td></tr>
114     * <tr><td>User</td>       <td>User name to use when establishing a
115     *                             connection to the server. The credentials are
116     *                             passed using the HTTP Basic authentication
117     *                             protocol, but are also sent as part of the SOAP
118     *                             Security headers.
119     *                             </td></tr>
120     * <tr><td>Password</td>   <td>Password to use when establishing a
121     *                             connection to the server. The credentials are
122     *                             passed using the HTTP Basic authentication
123     *                             protocol, but are also sent as part of the SOAP
124     *                             Security headers.
125     *                             </td></tr>
126     * </table>
127     *
128     * @author jhyde, Luc Boudreau
129     * @version $Id: XmlaOlap4jDriver.java 490 2012-01-23 22:35:25Z lucboudreau $
130     * @since May 22, 2007
131     */
132    public class XmlaOlap4jDriver implements Driver {
133    
134        private final Factory factory;
135    
136        /**
137         * Executor shared by all connections making asynchronous XMLA calls.
138         */
139        private static final ExecutorService executor;
140    
141        static {
142            executor = Executors.newCachedThreadPool(
143                new ThreadFactory() {
144                    public Thread newThread(Runnable r) {
145                        Thread t = Executors.defaultThreadFactory().newThread(r);
146                        t.setDaemon(true);
147                        return t;
148                   }
149                }
150            );
151        }
152    
153        private static int nextCookie;
154    
155        static {
156            try {
157                register();
158            } catch (SQLException e) {
159                e.printStackTrace();
160            } catch (RuntimeException e) {
161                e.printStackTrace();
162                throw e;
163            }
164        }
165    
166        /**
167         * Creates an XmlaOlap4jDriver.
168         */
169        public XmlaOlap4jDriver() {
170            factory = createFactory();
171        }
172    
173        private static Factory createFactory() {
174            final String factoryClassName = getFactoryClassName();
175            try {
176                final Class<?> clazz = Class.forName(factoryClassName);
177                return (Factory) clazz.newInstance();
178            } catch (ClassNotFoundException e) {
179                throw new RuntimeException(e);
180            } catch (IllegalAccessException e) {
181                throw new RuntimeException(e);
182            } catch (InstantiationException e) {
183                throw new RuntimeException(e);
184            }
185        }
186    
187        /*
188    
189            String factoryClassName;
190            try {
191                Class.forName("java.sql.Wrapper");
192                factoryClassName = "org.olap4j.driver.xmla.FactoryJdbc4Impl";
193            } catch (ClassNotFoundException e) {
194                // java.sql.Wrapper is not present. This means we are running JDBC
195                // 3.0 or earlier (probably JDK 1.5). Load the JDBC 3.0 factory
196                factoryClassName = "org.olap4j.driver.xmla.FactoryJdbc3Impl";
197            }
198            try {
199                final Class<?> clazz = Class.forName(factoryClassName);
200                factory = (Factory) clazz.newInstance();
201            } catch (ClassNotFoundException e) {
202                throw new RuntimeException(e);
203            } catch (IllegalAccessException e) {
204                throw new RuntimeException(e);
205            } catch (InstantiationException e) {
206                throw new RuntimeException(e);
207            }
208         */
209    
210        private static String getFactoryClassName() {
211            try {
212                // If java.sql.PseudoColumnUsage is present, we are running JDBC 4.1
213                // or later.
214                Class.forName("java.sql.PseudoColumnUsage");
215                return "org.olap4j.driver.xmla.FactoryJdbc41Impl";
216            } catch (ClassNotFoundException e) {
217                // java.sql.PseudoColumnUsage is not present. This means we are
218                // running JDBC 4.0 or earlier.
219                try {
220                    Class.forName("java.sql.Wrapper");
221                    return "org.olap4j.driver.xmla.FactoryJdbc4Impl";
222                } catch (ClassNotFoundException e2) {
223                    // java.sql.Wrapper is not present. This means we are running
224                    // JDBC 3.0 or earlier (probably JDK 1.5). Load the JDBC 3.0
225                    // factory.
226                    return "org.olap4j.driver.xmla.FactoryJdbc3Impl";
227                }
228            }
229        }
230    
231        /**
232         * Registers an instance of XmlaOlap4jDriver.
233         *
234         * <p>Called implicitly on class load, and implements the traditional
235         * 'Class.forName' way of registering JDBC drivers.
236         *
237         * @throws SQLException on error
238         */
239        private static void register() throws SQLException {
240            DriverManager.registerDriver(new XmlaOlap4jDriver());
241        }
242    
243        public Connection connect(String url, Properties info) throws SQLException {
244            // Checks if this driver handles this connection, exit otherwise.
245            if (!XmlaOlap4jConnection.acceptsURL(url)) {
246                return null;
247            }
248    
249            // Parses the connection string
250            Map<String, String> map =
251                XmlaOlap4jConnection.parseConnectString(url, info);
252    
253            // Creates a connection proxy
254            XmlaOlap4jProxy proxy = createProxy(map);
255    
256            // returns a connection object to the java API
257            return factory.newConnection(this, proxy, url, info);
258        }
259    
260        public boolean acceptsURL(String url) throws SQLException {
261            return XmlaOlap4jConnection.acceptsURL(url);
262        }
263    
264        public DriverPropertyInfo[] getPropertyInfo(
265            String url, Properties info) throws SQLException
266        {
267            List<DriverPropertyInfo> list = new ArrayList<DriverPropertyInfo>();
268    
269            // Add the contents of info
270            for (Map.Entry<Object, Object> entry : info.entrySet()) {
271                list.add(
272                    new DriverPropertyInfo(
273                        (String) entry.getKey(),
274                        (String) entry.getValue()));
275            }
276            // Next add standard properties
277    
278            return list.toArray(new DriverPropertyInfo[list.size()]);
279        }
280    
281        /**
282         * Returns the driver name. Not in the JDBC API.
283         * @return Driver name
284         */
285        String getName() {
286            return XmlaOlap4jDriverVersion.NAME;
287        }
288    
289        /**
290         * Returns the driver version. Not in the JDBC API.
291         * @return Driver version
292         */
293        public String getVersion() {
294            return XmlaOlap4jDriverVersion.VERSION;
295        }
296    
297        public int getMajorVersion() {
298            return XmlaOlap4jDriverVersion.MAJOR_VERSION;
299        }
300    
301        public int getMinorVersion() {
302            return XmlaOlap4jDriverVersion.MINOR_VERSION;
303        }
304    
305        public boolean jdbcCompliant() {
306            return false;
307        }
308    
309        // for JDBC 4.1
310        public Logger getParentLogger() {
311            return Logger.getLogger("");
312        }
313    
314        /**
315         * Creates a Proxy with which to talk to send XML web-service calls.
316         * The usual implementation of Proxy uses HTTP; there is another
317         * implementation, for testing, which talks to mondrian's XMLA service
318         * in-process.
319         *
320         * @param map Connection properties
321         * @return A Proxy with which to submit XML requests
322         */
323        protected XmlaOlap4jProxy createProxy(Map<String, String> map) {
324            String cookie = map.get(Property.TESTPROXYCOOKIE.name());
325            if (cookie != null) {
326                XmlaOlap4jProxy proxy = PROXY_MAP.get(cookie);
327                if (proxy != null) {
328                    return proxy;
329                }
330            }
331            return new XmlaOlap4jHttpProxy(this);
332        }
333    
334        /**
335         * Returns a future object representing an asynchronous submission of an
336         * XMLA request to a URL.
337         *
338         * @param proxy Proxy via which to send the request
339         * @param serverInfos Server infos.
340         * @param request Request
341         * @return Future object from which the byte array containing the result
342         * of the XMLA call can be obtained
343         */
344        public static Future<byte[]> getFuture(
345            final XmlaOlap4jProxy proxy,
346            final XmlaOlap4jServerInfos serverInfos,
347            final String request)
348        {
349            return executor.submit(
350                new Callable<byte[]>() {
351                    public byte[] call() throws Exception {
352                        return proxy.get(serverInfos, request);
353                    }
354                }
355            );
356        }
357    
358        /**
359         * For testing. Map from a cookie value (which is uniquely generated for
360         * each test) to a proxy object. Uses a weak hash map so that, if the code
361         * that created the proxy 'forgets' the cookie value, then the proxy can
362         * be garbage-collected.
363         */
364        public static final Map<String, XmlaOlap4jProxy> PROXY_MAP =
365            Collections.synchronizedMap(new WeakHashMap<String, XmlaOlap4jProxy>());
366    
367        /**
368         * Generates and returns a unique string.
369         *
370         * @return unique string
371         */
372        public static synchronized String nextCookie() {
373            return "cookie" + nextCookie++;
374        }
375    
376        /**
377         * Properties supported by this driver.
378         */
379        public enum Property {
380            TESTPROXYCOOKIE(
381                "String that uniquely identifies a proxy object via which to send "
382                + "XMLA requests for testing purposes."),
383            SERVER("URL of HTTP server"),
384            DATABASE("Name of the database"),
385            CATALOG("Catalog name"),
386            SCHEMA("Name of the schema"),
387            CACHE("Class name of the SOAP cache implementation"),
388            ROLE("Comma separated list of roles this connection impersonates"),
389            USER("Username to use when creating connections to the server."),
390            PASSWORD("Password to use when creating connections to the server.");
391    
392            /**
393             * Creates a property.
394             *
395             * @param description Description of property
396             */
397            Property(String description) {
398                Olap4jUtil.discard(description);
399            }
400        }
401    }
402    
403    // End XmlaOlap4jDriver.java