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