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