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*/
020package org.olap4j.driver.xmla;
021
022import org.olap4j.driver.xmla.proxy.XmlaOlap4jHttpProxy;
023import org.olap4j.driver.xmla.proxy.XmlaOlap4jProxy;
024import org.olap4j.impl.Olap4jUtil;
025
026import java.sql.*;
027import java.util.*;
028import java.util.concurrent.*;
029import 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 */
132public 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