001 /* 002 // This software is subject to the terms of the Eclipse Public License v1.0 003 // Agreement, available at the following URL: 004 // http://www.eclipse.org/legal/epl-v10.html. 005 // Copyright (C) 2007-2009 Julian Hyde 006 // All Rights Reserved. 007 // You must accept the terms of that agreement to use this software. 008 */ 009 package org.olap4j.driver.xmla; 010 011 import org.olap4j.driver.xmla.proxy.*; 012 import org.olap4j.impl.Olap4jUtil; 013 014 import java.net.*; 015 import java.sql.*; 016 import java.util.*; 017 import java.util.concurrent.*; 018 019 /** 020 * Olap4j driver for generic XML for Analysis (XMLA) providers. 021 * 022 * <p>Since olap4j is a superset of JDBC, you register this driver as you would 023 * any JDBC driver: 024 * 025 * <blockquote> 026 * <code>Class.forName("org.olap4j.driver.xmla.XmlaOlap4jDriver");</code> 027 * </blockquote> 028 * 029 * Then create a connection using a URL with the prefix "jdbc:xmla:". 030 * For example, 031 * 032 * <blockquote> 033 * <code>import java.sql.Connection;<br/> 034 * import java.sql.DriverManager;<br/> 035 * import org.olap4j.OlapConnection;<br/> 036 * <br/> 037 * Connection connection =<br/> 038 * DriverManager.getConnection(<br/> 039 * "jdbc:xmla:");<br/> 040 * OlapConnection olapConnection =<br/> 041 * connection.unwrap(OlapConnection.class);</code> 042 * </blockquote> 043 * 044 * <p>Note how we use the {@link java.sql.Connection#unwrap(Class)} method to down-cast 045 * the JDBC connection object to the extension {@link org.olap4j.OlapConnection} 046 * object. This method is only available in JDBC 4.0 (JDK 1.6 onwards). 047 * 048 * <h3>Connection properties</h3> 049 * 050 * <p>Unless otherwise stated, properties are optional. If a property occurs 051 * multiple times in the connect string, the first occurrence is used. 052 * 053 * <table border="1"> 054 * <tr><th>Property</th> <th>Description</th> </tr> 055 * 056 * <tr><td>Server</td> <td>URL of HTTP server. Required.</td> </tr> 057 * 058 * <tr><td>Catalog</td> <td>Catalog name to use. 059 * By default, the first one returned by the 060 * XMLA server will be used.</td> </tr> 061 * 062 * <tr><td>Provider</td> <td>Name of the XMLA provider.</td> </tr> 063 * 064 * <tr><td>DataSource</td> <td>Name of the XMLA datasource. When using a 065 * Mondrian backed XMLA server, be sure to 066 * include the full datasource name between 067 * quotes.</td> </tr> 068 * 069 * <tr><td>Cache</td> <td><p>Class name of the SOAP cache to use. 070 * Must implement interface 071 * {@link org.olap4j.driver.xmla.proxy.XmlaOlap4jCachedProxy}. 072 * A built-in memory cache is available with 073 * {@link org.olap4j.driver.xmla.cache.XmlaOlap4jNamedMemoryCache}. 074 * 075 * <p>By default, no SOAP query cache will be 076 * used.</td> </tr> 077 * 078 * <tr> <td>Cache.*</td> <td>Properties to transfer to the selected cache 079 * implementation. See 080 * {@link org.olap4j.driver.xmla.cache.XmlaOlap4jCache} 081 * or your selected implementation for properties 082 * details.</td> </tr> 083 * 084 * <tr><td>TestProxyCookie</td><td>String that uniquely identifies a proxy 085 * object in {@link #PROXY_MAP} via which to 086 * send XMLA requests for testing 087 * purposes.</td> </tr> 088 * 089 * </table> 090 * 091 * @author jhyde, Luc Boudreau 092 * @version $Id: XmlaOlap4jDriver.java 279 2009-09-30 19:07:29Z lucboudreau $ 093 * @since May 22, 2007 094 */ 095 public class XmlaOlap4jDriver implements Driver { 096 097 private final Factory factory; 098 099 /** 100 * Executor shared by all connections making asynchronous XMLA calls. 101 */ 102 private static final ExecutorService executor; 103 104 static { 105 executor = Executors.newCachedThreadPool( 106 new ThreadFactory() { 107 public Thread newThread(Runnable r) { 108 Thread t = Executors.defaultThreadFactory().newThread(r); 109 t.setDaemon(true); 110 return t; 111 } 112 } 113 ); 114 } 115 116 private static int nextCookie; 117 118 static { 119 try { 120 register(); 121 } catch (SQLException e) { 122 e.printStackTrace(); 123 } catch (RuntimeException e) { 124 e.printStackTrace(); 125 throw e; 126 } 127 } 128 129 /** 130 * Creates an XmlaOlap4jDriver. 131 */ 132 protected XmlaOlap4jDriver() { 133 String factoryClassName; 134 try { 135 Class.forName("java.sql.Wrapper"); 136 factoryClassName = "org.olap4j.driver.xmla.FactoryJdbc4Impl"; 137 } catch (ClassNotFoundException e) { 138 // java.sql.Wrapper is not present. This means we are running JDBC 139 // 3.0 or earlier (probably JDK 1.5). Load the JDBC 3.0 factory 140 factoryClassName = "org.olap4j.driver.xmla.FactoryJdbc3Impl"; 141 } 142 try { 143 final Class clazz = Class.forName(factoryClassName); 144 factory = (Factory) clazz.newInstance(); 145 } catch (ClassNotFoundException e) { 146 throw new RuntimeException(e); 147 } catch (IllegalAccessException e) { 148 throw new RuntimeException(e); 149 } catch (InstantiationException e) { 150 throw new RuntimeException(e); 151 } 152 } 153 154 /** 155 * Registers an instance of XmlaOlap4jDriver. 156 * 157 * <p>Called implicitly on class load, and implements the traditional 158 * 'Class.forName' way of registering JDBC drivers. 159 * 160 * @throws SQLException on error 161 */ 162 private static void register() throws SQLException { 163 DriverManager.registerDriver(new XmlaOlap4jDriver()); 164 } 165 166 public Connection connect(String url, Properties info) throws SQLException { 167 // Checks if this driver handles this connection, exit otherwise. 168 if (!XmlaOlap4jConnection.acceptsURL(url)) { 169 return null; 170 } 171 172 // Parses the connection string 173 Map<String, String> map = 174 XmlaOlap4jConnection.parseConnectString(url, info); 175 176 // Creates a connection proxy 177 XmlaOlap4jProxy proxy = createProxy(map); 178 179 // returns a connection object to the java API 180 return factory.newConnection(this, proxy, url, info); 181 } 182 183 public boolean acceptsURL(String url) throws SQLException { 184 return XmlaOlap4jConnection.acceptsURL(url); 185 } 186 187 public DriverPropertyInfo[] getPropertyInfo( 188 String url, Properties info) throws SQLException 189 { 190 List<DriverPropertyInfo> list = new ArrayList<DriverPropertyInfo>(); 191 192 // Add the contents of info 193 for (Map.Entry<Object, Object> entry : info.entrySet()) { 194 list.add( 195 new DriverPropertyInfo( 196 (String) entry.getKey(), 197 (String) entry.getValue())); 198 } 199 // Next add standard properties 200 201 return list.toArray(new DriverPropertyInfo[list.size()]); 202 } 203 204 /** 205 * Returns the driver name. Not in the JDBC API. 206 * @return Driver name 207 */ 208 String getName() { 209 return XmlaOlap4jDriverVersion.NAME; 210 } 211 212 /** 213 * Returns the driver version. Not in the JDBC API. 214 * @return Driver version 215 */ 216 public String getVersion() { 217 return XmlaOlap4jDriverVersion.VERSION; 218 } 219 220 public int getMajorVersion() { 221 return XmlaOlap4jDriverVersion.MAJOR_VERSION; 222 } 223 224 public int getMinorVersion() { 225 return XmlaOlap4jDriverVersion.MINOR_VERSION; 226 } 227 228 public boolean jdbcCompliant() { 229 return false; 230 } 231 232 /** 233 * Creates a Proxy with which to talk to send XML web-service calls. 234 * The usual implementation of Proxy uses HTTP; there is another 235 * implementation, for testing, which talks to mondrian's XMLA service 236 * in-process. 237 * 238 * @param map Connection properties 239 * @return A Proxy with which to submit XML requests 240 */ 241 protected XmlaOlap4jProxy createProxy(Map<String, String> map) { 242 String cookie = map.get(Property.TestProxyCookie.name()); 243 if (cookie != null) { 244 XmlaOlap4jProxy proxy = PROXY_MAP.get(cookie); 245 if (proxy != null) { 246 return proxy; 247 } 248 } 249 return new XmlaOlap4jHttpProxy(this); 250 } 251 252 /** 253 * Returns a future object representing an asynchronous submission of an 254 * XMLA request to a URL. 255 * 256 * @param proxy Proxy via which to send the request 257 * @param url URL of XMLA server 258 * @param request Request 259 * @return Future object from which the byte array containing the result 260 * of the XMLA call can be obtained 261 */ 262 public static Future<byte[]> getFuture( 263 final XmlaOlap4jProxy proxy, 264 final URL url, 265 final String request) 266 { 267 return executor.submit( 268 new Callable<byte[]>() { 269 public byte[] call() throws Exception { 270 return proxy.get(url, request); 271 } 272 } 273 ); 274 } 275 276 /** 277 * For testing. Map from a cookie value (which is uniquely generated for 278 * each test) to a proxy object. Uses a weak hash map so that, if the code 279 * that created the proxy 'forgets' the cookie value, then the proxy can 280 * be garbage-collected. 281 */ 282 public static final Map<String, XmlaOlap4jProxy> PROXY_MAP = 283 Collections.synchronizedMap(new WeakHashMap<String, XmlaOlap4jProxy>()); 284 285 /** 286 * Generates and returns a unique string. 287 * 288 * @return unique string 289 */ 290 public static synchronized String nextCookie() { 291 return "cookie" + nextCookie++; 292 } 293 294 /** 295 * Properties supported by this driver. 296 */ 297 public enum Property { 298 TestProxyCookie( 299 "String that uniquely identifies a proxy object via which to send " 300 + "XMLA requests for testing purposes."), 301 Server("URL of HTTP server"), 302 Catalog("Catalog name"), 303 Provider("Name of the datasource provider"), 304 DataSource("Name of the datasource"), 305 Cache("Class name of the SOAP cache implementation"); 306 307 /** 308 * Creates a property. 309 * 310 * @param description Description of property 311 */ 312 Property(String description) { 313 Olap4jUtil.discard(description); 314 } 315 } 316 317 /** 318 * This is a mock subclass to prevent retro-compatibility issues. 319 * If you're using this class, please change your code to 320 * use XmlaOlap4jProxy instead. 321 * @author Luc Boudreau 322 * 323 */ 324 @Deprecated 325 public static interface Proxy extends XmlaOlap4jProxy { 326 } 327 } 328 329 // End XmlaOlap4jDriver.java