KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > mondrian > rolap > RolapConnection


1 /*
2 // $Id: //open/mondrian/src/main/mondrian/rolap/RolapConnection.java#61 $
3 // This software is subject to the terms of the Common Public License
4 // Agreement, available at the following URL:
5 // http://www.opensource.org/licenses/cpl.html.
6 // Copyright (C) 2001-2002 Kana Software, Inc.
7 // Copyright (C) 2001-2007 Julian Hyde and others
8 // All Rights Reserved.
9 // You must accept the terms of that agreement to use this software.
10 //
11 // jhyde, 2 October, 2002
12 */

13 package mondrian.rolap;
14
15 import mondrian.olap.*;
16 import mondrian.util.MemoryMonitor;
17 import mondrian.util.MemoryMonitorFactory;
18 import mondrian.rolap.agg.AggregationManager;
19 import org.apache.commons.dbcp.ConnectionFactory;
20 import org.apache.commons.dbcp.DataSourceConnectionFactory;
21 import org.apache.commons.dbcp.DriverManagerConnectionFactory;
22
23 import org.apache.log4j.Logger;
24 import javax.naming.InitialContext JavaDoc;
25 import javax.naming.NamingException JavaDoc;
26 import javax.sql.DataSource JavaDoc;
27 import java.io.PrintWriter JavaDoc;
28 import java.io.StringWriter JavaDoc;
29 import java.sql.Connection JavaDoc;
30 import java.sql.SQLException JavaDoc;
31 import java.util.*;
32
33 /**
34  * A <code>RolapConnection</code> is a connection to a Mondrian OLAP Server.
35  *
36  * <p>Typically, you create a connection via
37  * {@link DriverManager#getConnection(String, mondrian.spi.CatalogLocator, boolean)}.
38  * {@link RolapConnectionProperties} describes allowable keywords.</p>
39  *
40  * @see RolapSchema
41  * @see DriverManager
42  * @author jhyde
43  * @since 2 October, 2002
44  * @version $Id: //open/mondrian/src/main/mondrian/rolap/RolapConnection.java#61 $
45  */

46 public class RolapConnection extends ConnectionBase {
47     private static final Logger LOGGER = Logger.getLogger(RolapConnection.class);
48
49     private final Util.PropertyList connectInfo;
50
51     /**
52      * Factory for JDBC connections to talk to the RDBMS. This factory will
53      * usually use a connection pool.
54      */

55     private final DataSource JavaDoc dataSource;
56     private final String JavaDoc catalogUrl;
57     private final RolapSchema schema;
58     private SchemaReader schemaReader;
59     protected Role role;
60     private Locale locale = Locale.US;
61
62     /**
63      * Creates a connection.
64      *
65      * @param connectInfo Connection properties; keywords are described in
66      * {@link RolapConnectionProperties}.
67      */

68     public RolapConnection(Util.PropertyList connectInfo) {
69         this(connectInfo, null, null);
70     }
71
72     /**
73      * Creates a connection.
74      *
75      * @param connectInfo Connection properties; keywords are described in
76      * {@link RolapConnectionProperties}.
77      */

78     public RolapConnection(Util.PropertyList connectInfo, DataSource JavaDoc datasource) {
79         this(connectInfo, null, datasource);
80     }
81
82     /**
83      * Creates a RolapConnection.
84      *
85      * <p>Only {@link mondrian.rolap.RolapSchema.Pool#get} calls this with schema != null (to
86      * create a schema's internal connection). Other uses retrieve a schema
87      * from the cache based upon the <code>Catalog</code> property.
88      *
89      * @param connectInfo Connection properties; keywords are described in
90      * {@link RolapConnectionProperties}.
91      * @param schema Schema for the connection. Must be null unless this is to
92      * be an internal connection.
93      * @pre connectInfo != null
94      */

95     RolapConnection(Util.PropertyList connectInfo, RolapSchema schema) {
96         this(connectInfo, schema, null);
97     }
98
99     /**
100      * Creates a RolapConnection.
101      *
102      * <p>Only {@link mondrian.rolap.RolapSchema.Pool#get} calls this with
103      * schema != null (to create a schema's internal connection).
104      * Other uses retrieve a schema from the cache based upon
105      * the <code>Catalog</code> property.
106      *
107      * @param connectInfo Connection properties; keywords are described in
108      * {@link RolapConnectionProperties}.
109      * @param schema Schema for the connection. Must be null unless this is to
110      * be an internal connection.
111      * @param dataSource If not null an external DataSource to be used
112      * by Mondrian
113      * @pre connectInfo != null
114      */

115     RolapConnection(
116         Util.PropertyList connectInfo,
117         RolapSchema schema,
118         DataSource JavaDoc dataSource)
119     {
120         super();
121
122         String JavaDoc provider = connectInfo.get(
123             RolapConnectionProperties.Provider.name(), "mondrian");
124         Util.assertTrue(provider.equalsIgnoreCase("mondrian"));
125         this.connectInfo = connectInfo;
126         this.catalogUrl =
127             connectInfo.get(RolapConnectionProperties.Catalog.name());
128         this.dataSource = (dataSource != null)
129             ? dataSource
130             : createDataSource(connectInfo);
131         Role role = null;
132         if (schema == null) {
133             // If RolapSchema.Pool.get were to call this with schema == null,
134
// we would loop.
135
if (dataSource == null) {
136                 // If there is no external data source is passed in,
137
// we expect the following properties to be set,
138
// as they are used to generate the schema cache key.
139
final String JavaDoc jdbcConnectString =
140                     connectInfo.get(RolapConnectionProperties.Jdbc.name());
141                 final String JavaDoc jdbcUser =
142                     connectInfo.get(RolapConnectionProperties.JdbcUser.name());
143                 final String JavaDoc strDataSource =
144                     connectInfo.get(RolapConnectionProperties.DataSource.name());
145                 final String JavaDoc connectionKey = jdbcConnectString +
146                 getJDBCProperties(connectInfo).toString();
147
148                 schema = RolapSchema.Pool.instance().get(
149                     catalogUrl,
150                     connectionKey,
151                     jdbcUser,
152                     strDataSource,
153                     connectInfo);
154             } else {
155                 schema = RolapSchema.Pool.instance().get(
156                     catalogUrl,
157                     dataSource,
158                     connectInfo);
159             }
160             String JavaDoc roleName =
161                 connectInfo.get(RolapConnectionProperties.Role.name());
162             if (roleName != null) {
163                 role = schema.lookupRole(roleName);
164                 if (role == null) {
165                     throw Util.newError("Role '" + roleName + "' not found");
166                 }
167             }
168         }
169         if (role == null) {
170             role = schema.getDefaultRole();
171         }
172
173         // Set the locale.
174
String JavaDoc localeString =
175             connectInfo.get(RolapConnectionProperties.Locale.name());
176         if (localeString != null) {
177             String JavaDoc[] strings = localeString.split("_");
178             switch (strings.length) {
179             case 1:
180                 this.locale = new Locale(strings[0]);
181                 break;
182             case 2:
183                 this.locale = new Locale(strings[0], strings[1]);
184                 break;
185             case 3:
186                 this.locale = new Locale(strings[0], strings[1], strings[2]);
187                 break;
188             default:
189                 throw Util.newInternal("bad locale string '" + localeString + "'");
190             }
191         }
192
193         this.schema = schema;
194         setRole(role);
195     }
196
197     protected Logger getLogger() {
198         return LOGGER;
199     }
200
201
202     // This is package-level in order for the RolapConnectionTest class to have
203
// access.
204
static DataSource JavaDoc createDataSource(Util.PropertyList connectInfo) {
205         final String JavaDoc jdbcConnectString =
206                 connectInfo.get(RolapConnectionProperties.Jdbc.name());
207         final String JavaDoc poolNeededString =
208                 connectInfo.get(RolapConnectionProperties.PoolNeeded.name());
209
210         Properties jdbcProperties = getJDBCProperties(connectInfo);
211         String JavaDoc propertyString = jdbcProperties.toString();
212         if (jdbcConnectString != null) {
213             // Get connection through own pooling datasource
214
String JavaDoc jdbcDrivers =
215                 connectInfo.get(RolapConnectionProperties.JdbcDrivers.name());
216             if (jdbcDrivers != null) {
217                 RolapUtil.loadDrivers(jdbcDrivers);
218             }
219             final String JavaDoc jdbcDriversProp =
220                     MondrianProperties.instance().JdbcDrivers.get();
221             RolapUtil.loadDrivers(jdbcDriversProp);
222
223             final boolean poolNeeded = (poolNeededString == null)
224                 // JDBC connections are dumb beasts, so we assume they're not
225
// pooled.
226
? true
227                 : poolNeededString.equalsIgnoreCase("true");
228
229             final String JavaDoc jdbcUser =
230                 connectInfo.get(RolapConnectionProperties.JdbcUser.name());
231             final String JavaDoc jdbcPassword =
232                 connectInfo.get(RolapConnectionProperties.JdbcPassword.name());
233
234             if (jdbcUser != null) {
235                 jdbcProperties.put("user", jdbcUser);
236             }
237             if (jdbcPassword != null) {
238                 jdbcProperties.put("password", jdbcPassword);
239             }
240
241             if (!poolNeeded) {
242                 // Connection is already pooled; don't pool it again.
243
return new DriverManagerDataSource(jdbcConnectString,
244                         jdbcProperties);
245             }
246
247             if (jdbcConnectString.toLowerCase().indexOf("mysql") > -1) {
248                 // mysql driver needs this autoReconnect parameter
249
jdbcProperties.setProperty("autoReconnect", "true");
250             }
251             // use the DriverManagerConnectionFactory to create connections
252
ConnectionFactory connectionFactory =
253                     new DriverManagerConnectionFactory(jdbcConnectString ,
254                             jdbcProperties);
255             try {
256                 return RolapConnectionPool.instance().getPoolingDataSource(
257                         jdbcConnectString + propertyString, connectionFactory);
258             } catch (Throwable JavaDoc e) {
259                 throw Util.newInternal(e,
260                         "Error while creating connection pool (with URI " +
261                         jdbcConnectString + ")");
262             }
263
264         } else {
265
266             final String JavaDoc dataSourceName =
267                 connectInfo.get(RolapConnectionProperties.DataSource.name());
268             if (dataSourceName == null) {
269                 throw Util.newInternal(
270                     "Connect string '" + connectInfo.toString() +
271                     "' must contain either '" + RolapConnectionProperties.Jdbc +
272                     "' or '" + RolapConnectionProperties.DataSource + "'");
273             }
274
275             final boolean poolNeeded = (poolNeededString == null)
276                 // Data sources are fairly smart, so we assume they look after
277
// their own pooling.
278
? false
279                 : poolNeededString.equalsIgnoreCase("true");
280
281             // Get connection from datasource.
282
final DataSource JavaDoc dataSource;
283             try {
284                 dataSource =
285                     (DataSource JavaDoc) new InitialContext JavaDoc().lookup(dataSourceName);
286             } catch (NamingException JavaDoc e) {
287                 throw Util.newInternal(e,
288                     "Error while looking up data source (" +
289                         dataSourceName + ")");
290             }
291             if (!poolNeeded) {
292                 return dataSource;
293             }
294             ConnectionFactory connectionFactory =
295                     new DataSourceConnectionFactory(dataSource);
296             try {
297                 return RolapConnectionPool.instance().getPoolingDataSource(
298                         dataSourceName, connectionFactory);
299             } catch (Exception JavaDoc e) {
300                 throw Util.newInternal(e,
301                         "Error while creating connection pool (with URI " +
302                         dataSourceName + ")");
303             }
304         }
305     }
306
307     /**
308      * Creates a {@link Properties} object containing all of the JDBC
309      * connection properties present in the
310      * {@link Util.PropertyList connectInfo}.
311      *
312      * @param connectInfo
313      * @return The JDBC connection properties.
314      */

315     private static Properties getJDBCProperties(Util.PropertyList connectInfo) {
316         Properties jdbcProperties = new Properties();
317         Iterator<String JavaDoc[]> iterator = connectInfo.iterator();
318         while (iterator.hasNext()) {
319             String JavaDoc[] entry = iterator.next();
320             if (entry[0].startsWith(RolapConnectionProperties.JdbcPropertyPrefix)) {
321                 jdbcProperties.put(entry[0].substring(RolapConnectionProperties.JdbcPropertyPrefix.length()), entry[1]);
322             }
323         }
324         return jdbcProperties;
325     }
326
327     public Util.PropertyList getConnectInfo() {
328         return connectInfo;
329     }
330
331     public void close() {
332     }
333
334     public Schema getSchema() {
335         return schema;
336     }
337
338     public String JavaDoc getConnectString() {
339         return connectInfo.toString();
340     }
341
342     public String JavaDoc getCatalogName() {
343         return catalogUrl;
344     }
345
346     public Locale getLocale() {
347         return locale;
348     }
349
350     public void setLocale(Locale locale) {
351         this.locale = locale;
352     }
353
354     public SchemaReader getSchemaReader() {
355         return schemaReader;
356     }
357
358     public Object JavaDoc getProperty(String JavaDoc name) {
359         // Mask out the values of certain properties.
360
if (name.equals(RolapConnectionProperties.JdbcPassword.name()) ||
361             name.equals(RolapConnectionProperties.CatalogContent.name())) {
362             return "";
363         }
364         return connectInfo.get(name);
365     }
366
367     public CacheControl getCacheControl(PrintWriter JavaDoc pw) {
368         return AggregationManager.instance().getCacheControl(pw);
369     }
370
371     /**
372      * Executes a Query.
373      *
374      * @throws ResourceLimitExceededException if some resource limit specified in the
375      * property file was exceeded
376      * @throws QueryCanceledException if query was canceled during execution
377      * @throws QueryTimeoutException if query exceeded timeout specified in
378      * the property file
379      */

380     public Result execute(Query query) {
381         class Listener implements MemoryMonitor.Listener {
382             private final Query query;
383             Listener(final Query query) {
384                 this.query = query;
385             }
386             public void memoryUsageNotification(long used, long max) {
387                 StringBuilder JavaDoc buf = new StringBuilder JavaDoc(200);
388                 buf.append("OutOfMemory used=");
389                 buf.append(used);
390                 buf.append(", max=");
391                 buf.append(max);
392                 buf.append(" for connection: ");
393                 buf.append(getConnectString());
394                 // Call ConnectionBase method which has access to
395
// Query methods.
396
RolapConnection.memoryUsageNotification(query, buf.toString());
397             }
398         }
399         Listener listener = new Listener(query);
400         MemoryMonitor mm = MemoryMonitorFactory.getMemoryMonitor();
401         try {
402             mm.addListener(listener);
403             // Check to see if we must punt
404
query.checkCancelOrTimeout();
405
406             if (LOGGER.isDebugEnabled()) {
407                 LOGGER.debug(Util.unparse(query));
408             }
409             query.setQueryStartTime();
410             Result result = new RolapResult(query, true);
411             for (int i = 0; i < query.axes.length; i++) {
412                 QueryAxis axis = query.axes[i];
413                 if (axis.isNonEmpty()) {
414                     result = new NonEmptyResult(result, query, i);
415                 }
416             }
417             if (LOGGER.isDebugEnabled()) {
418                 StringWriter JavaDoc sw = new StringWriter JavaDoc();
419                 PrintWriter JavaDoc pw = new PrintWriter JavaDoc(sw);
420                 result.print(pw);
421                 pw.flush();
422                 LOGGER.debug(sw.toString());
423             }
424             query.setQueryEndExecution();
425             return result;
426
427         } catch (ResultLimitExceededException e) {
428             // query has been punted
429
throw e;
430         } catch (Exception JavaDoc e) {
431             String JavaDoc queryString;
432             query.setQueryEndExecution();
433             try {
434                 queryString = Util.unparse(query);
435             } catch (Exception JavaDoc e1) {
436                 queryString = "?";
437             }
438             throw Util.newError(e, "Error while executing query [" +
439                     queryString + "]");
440         } finally {
441             mm.removeListener(listener);
442         }
443     }
444
445     public void setRole(Role role) {
446         assert role != null;
447         assert !role.isMutable();
448
449         this.role = role;
450         this.schemaReader = new RolapSchemaReader(role, schema) {
451             public Cube getCube() {
452                 throw new UnsupportedOperationException JavaDoc();
453             }
454         };
455     }
456
457     public Role getRole() {
458         Util.assertPostcondition(role != null, "role != null");
459         Util.assertPostcondition(!role.isMutable(), "!role.isMutable()");
460
461         return role;
462     }
463
464     /**
465      * Implementation of {@link DataSource} which calls the good ol'
466      * {@link java.sql.DriverManager}.
467      */

468     private static class DriverManagerDataSource implements DataSource JavaDoc {
469         private final String JavaDoc jdbcConnectString;
470         private PrintWriter JavaDoc logWriter;
471         private int loginTimeout;
472         private Properties jdbcProperties;
473
474         public DriverManagerDataSource(String JavaDoc jdbcConnectString,
475                                        Properties properties) {
476             this.jdbcConnectString = jdbcConnectString;
477             this.jdbcProperties = properties;
478         }
479
480         public Connection JavaDoc getConnection() throws SQLException JavaDoc {
481             return new org.apache.commons.dbcp.DelegatingConnection(
482                 java.sql.DriverManager.getConnection(
483                     jdbcConnectString, jdbcProperties));
484         }
485
486         public Connection JavaDoc getConnection(String JavaDoc username, String JavaDoc password)
487                 throws SQLException JavaDoc {
488             if (jdbcProperties == null) {
489                 return java.sql.DriverManager.getConnection(jdbcConnectString,
490                         username, password);
491             } else {
492                 Properties temp = (Properties)jdbcProperties.clone();
493                 temp.put("user", username);
494                 temp.put("password", password);
495                 return java.sql.DriverManager.getConnection(jdbcConnectString, temp);
496             }
497         }
498
499         public PrintWriter JavaDoc getLogWriter() throws SQLException JavaDoc {
500             return logWriter;
501         }
502
503         public void setLogWriter(PrintWriter JavaDoc out) throws SQLException JavaDoc {
504             logWriter = out;
505         }
506
507         public void setLoginTimeout(int seconds) throws SQLException JavaDoc {
508             loginTimeout = seconds;
509         }
510
511         public int getLoginTimeout() throws SQLException JavaDoc {
512             return loginTimeout;
513         }
514
515         public <T> T unwrap(Class JavaDoc<T> iface) throws SQLException JavaDoc {
516             throw new SQLException JavaDoc("not a wrapper");
517         }
518
519         public boolean isWrapperFor(Class JavaDoc<?> iface) throws SQLException JavaDoc {
520             return false;
521         }
522     }
523
524     public DataSource JavaDoc getDataSource() {
525         return dataSource;
526     }
527
528     /**
529      * A <code>NonEmptyResult</code> filters a result by removing empty rows
530      * on a particular axis.
531      */

532     class NonEmptyResult extends ResultBase {
533
534         final Result underlying;
535         private final int axis;
536         private final Map<Integer JavaDoc, Integer JavaDoc> map;
537         /** workspace. Synchronized access only. */
538         private final int[] pos;
539
540         NonEmptyResult(Result result, Query query, int axis) {
541             super(query, result.getAxes().clone());
542
543             this.underlying = result;
544             this.axis = axis;
545             this.map = new HashMap<Integer JavaDoc, Integer JavaDoc>();
546             int axisCount = underlying.getAxes().length;
547             this.pos = new int[axisCount];
548             this.slicerAxis = underlying.getSlicerAxis();
549             List<Position> positions = underlying.getAxes()[axis].getPositions();
550
551             List<Position> positionsList = new ArrayList<Position>();
552             int i = 0;
553             for (Position position: positions) {
554                 if (! isEmpty(i, axis)) {
555                     map.put(positionsList.size(), i);
556                     positionsList.add(position);
557                 }
558                 i++;
559             }
560             this.axes[axis] = new RolapAxis.PositionList(positionsList);
561         }
562
563         protected Logger getLogger() {
564             return LOGGER;
565         }
566
567         /**
568          * Returns true if all cells at a given offset on a given axis are
569          * empty. For example, in a 2x2x2 dataset, <code>isEmpty(1,0)</code>
570          * returns true if cells <code>{(1,0,0), (1,0,1), (1,1,0),
571          * (1,1,1)}</code> are all empty. As you can see, we hold the 0th
572          * coordinate fixed at 1, and vary all other coordinates over all
573          * possible values.
574          */

575         private boolean isEmpty(int offset, int fixedAxis) {
576             int axisCount = getAxes().length;
577             pos[fixedAxis] = offset;
578             return isEmptyRecurse(fixedAxis, axisCount - 1);
579         }
580
581         private boolean isEmptyRecurse(int fixedAxis, int axis) {
582             if (axis < 0) {
583                 RolapCell cell = (RolapCell) underlying.getCell(pos);
584                 return cell.isNull();
585             } else if (axis == fixedAxis) {
586                 return isEmptyRecurse(fixedAxis, axis - 1);
587             } else {
588                 List<Position> positions = getAxes()[axis].getPositions();
589                 int i = 0;
590                 for (Position position: positions) {
591                     pos[axis] = i;
592                     if (!isEmptyRecurse(fixedAxis, axis - 1)) {
593                         return false;
594                     }
595                     i++;
596                 }
597                 return true;
598             }
599         }
600
601         // synchronized because we use 'pos'
602
public synchronized Cell getCell(int[] externalPos) {
603             System.arraycopy(externalPos, 0, this.pos, 0, externalPos.length);
604             int offset = externalPos[axis];
605             int mappedOffset = mapOffsetToUnderlying(offset);
606             this.pos[axis] = mappedOffset;
607             return underlying.getCell(this.pos);
608         }
609
610         private int mapOffsetToUnderlying(int offset) {
611             return map.get(offset);
612         }
613
614         public void close() {
615             underlying.close();
616         }
617     }
618 }
619
620 // End RolapConnection.java
621
Popular Tags