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 * DriverManager.getConnection(<br/>
051 * "jdbc:xmla:");<br/>
052 * OlapConnection olapConnection =<br/>
053 * 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