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