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// You must accept the terms of that agreement to use this software.
006//
007// Copyright (C) 2007-2012 Pentaho and others
008// All Rights Reserved.
009*/
010package mondrian.olap4j;
011
012import mondrian.mdx.*;
013import mondrian.olap.*;
014import mondrian.olap.Member;
015import mondrian.olap.fun.MondrianEvaluationException;
016import mondrian.rolap.*;
017import mondrian.util.Bug;
018import mondrian.xmla.XmlaHandler;
019
020import org.olap4j.Axis;
021import org.olap4j.Cell;
022import org.olap4j.*;
023import org.olap4j.impl.*;
024import org.olap4j.mdx.*;
025import org.olap4j.mdx.parser.*;
026import org.olap4j.mdx.parser.impl.DefaultMdxParserImpl;
027import org.olap4j.metadata.*;
028import org.olap4j.metadata.Database.AuthenticationMode;
029import org.olap4j.metadata.Database.ProviderType;
030import org.olap4j.metadata.Schema;
031import org.olap4j.type.*;
032import org.olap4j.type.DimensionType;
033
034import java.io.PrintWriter;
035import java.io.StringWriter;
036import java.math.BigDecimal;
037import java.math.BigInteger;
038import java.sql.*;
039import java.util.*;
040
041/**
042 * Implementation of {@link org.olap4j.OlapConnection}
043 * for the Mondrian OLAP engine.
044 *
045 * <p>This class has sub-classes which implement JDBC 3.0 and JDBC 4.0 APIs;
046 * it is instantiated using
047 * {@link Factory#newConnection(MondrianOlap4jDriver, String, java.util.Properties)}.</p>
048 *
049 * <p>This class is public, to allow access to the
050 * {@link #setRoleNames(java.util.List)} method before it is added to olap4j
051 * version 2.0. <b>This may change without notice</b>. Code should not rely on
052 * this class being public.</p>
053 *
054 * @author jhyde
055 * @since May 23, 2007
056 */
057public abstract class MondrianOlap4jConnection implements OlapConnection {
058    static {
059        Bug.olap4jUpgrade(
060            "Make this class package-protected when we upgrade to olap4j 2.0. "
061            + "The setRoleNames method will then be available through the "
062            + "olap4j API");
063    }
064
065    /**
066     * Handler for errors.
067     */
068    final Helper helper = new Helper();
069
070    /**
071     * Underlying mondrian connection. Set on creation, cleared on close.
072     * Developers, please keep this member private. Access it via
073     * {@link #getMondrianConnection()} or {@link #getMondrianConnection2()},
074     * and these will throw if the connection has been closed.
075     */
076    private RolapConnection mondrianConnection;
077
078    /**
079     * Map from mondrian schema objects to olap4j schemas.
080     *
081     * <p>REVIEW: This assumes that a RolapSchema occurs at most once in a
082     * catalog. It is possible for a schema to be mapped more than once, with
083     * different names; the same RolapSchema object will be used.
084     */
085    final Map<mondrian.olap.Schema, MondrianOlap4jSchema> schemaMap =
086        new HashMap<mondrian.olap.Schema, MondrianOlap4jSchema>();
087
088    private final MondrianOlap4jDatabaseMetaData olap4jDatabaseMetaData;
089
090    private static final String CONNECT_STRING_PREFIX = "jdbc:mondrian:";
091
092    private static final String ENGINE_CONNECT_STRING_PREFIX =
093        "jdbc:mondrian:engine:";
094
095    final Factory factory;
096    final MondrianOlap4jDriver driver;
097    private String roleName;
098
099    /** List of role names. Empty if role is the 'all' role. Value must always
100     * be an unmodifiable list, because {@link #getRoleNames()} returns the
101     * value directly. */
102    private List<String> roleNames = Collections.emptyList();
103    private boolean autoCommit;
104    private boolean readOnly;
105    boolean preferList;
106
107    final MondrianServer mondrianServer;
108    private final MondrianOlap4jSchema olap4jSchema;
109    private final NamedList<MondrianOlap4jDatabase> olap4jDatabases;
110
111    /**
112     * Creates an Olap4j connection to Mondrian.
113     *
114     * <p>This method is intentionally package-protected. The public API
115     * uses the traditional JDBC {@link java.sql.DriverManager}.
116     * See {@link mondrian.olap4j.MondrianOlap4jDriver} for more details.
117     *
118     * @param factory Factory
119     * @param driver Driver
120     * @param url Connect-string URL
121     * @param info Additional properties
122     * @throws SQLException if there is an error
123     */
124    MondrianOlap4jConnection(
125        Factory factory,
126        MondrianOlap4jDriver driver,
127        String url,
128        Properties info)
129        throws SQLException
130    {
131        // Required for the logic below to work.
132        assert ENGINE_CONNECT_STRING_PREFIX.startsWith(CONNECT_STRING_PREFIX);
133
134        this.factory = factory;
135        this.driver = driver;
136        String x;
137        if (url.startsWith(ENGINE_CONNECT_STRING_PREFIX)) {
138            x = url.substring(ENGINE_CONNECT_STRING_PREFIX.length());
139        } else if (url.startsWith(CONNECT_STRING_PREFIX)) {
140            x = url.substring(CONNECT_STRING_PREFIX.length());
141        } else {
142            // This is not a URL we can handle.
143            // DriverManager should not have invoked us.
144            throw new AssertionError(
145                "does not start with '" + CONNECT_STRING_PREFIX + "'");
146        }
147        Util.PropertyList list = Util.parseConnectString(x);
148        final Map<String, String> map = Util.toMap(info);
149        for (Map.Entry<String, String> entry : map.entrySet()) {
150            list.put(entry.getKey(), entry.getValue());
151        }
152
153        this.mondrianConnection =
154            (RolapConnection) mondrian.olap.DriverManager
155                .getConnection(list, null);
156
157        this.olap4jDatabaseMetaData =
158            factory.newDatabaseMetaData(this, mondrianConnection);
159
160
161        this.mondrianServer =
162            MondrianServer.forConnection(mondrianConnection);
163        final CatalogFinder catalogFinder =
164            (CatalogFinder) mondrianServer;
165
166        NamedList<MondrianOlap4jCatalog> olap4jCatalogs = new
167            NamedListImpl<MondrianOlap4jCatalog>();
168        this.olap4jDatabases =
169            new NamedListImpl<MondrianOlap4jDatabase>();
170
171        List<Map<String, Object>> dbpropsMaps =
172            mondrianServer.getDatabases(mondrianConnection);
173        if (dbpropsMaps.size() != 1) {
174            throw new AssertionError();
175        }
176        Map<String, Object> dbpropsMap = dbpropsMaps.get(0);
177        StringTokenizer st =
178            new StringTokenizer(
179                String.valueOf(dbpropsMap.get("ProviderType")),
180                ",");
181        List<ProviderType> pTypes =
182            new ArrayList<ProviderType>();
183        while (st.hasMoreTokens()) {
184            pTypes.add(ProviderType.valueOf(st.nextToken()));
185        }
186        st = new StringTokenizer(
187            String.valueOf(dbpropsMap.get("AuthenticationMode")), ",");
188        List<AuthenticationMode> aModes =
189            new ArrayList<AuthenticationMode>();
190        while (st.hasMoreTokens()) {
191            aModes.add(AuthenticationMode.valueOf(st.nextToken()));
192        }
193        final MondrianOlap4jDatabase database =
194            new MondrianOlap4jDatabase(
195                this,
196                olap4jCatalogs,
197                String.valueOf(dbpropsMap.get("DataSourceName")),
198                String.valueOf(dbpropsMap.get("DataSourceDescription")),
199                String.valueOf(dbpropsMap.get("ProviderName")),
200                String.valueOf(dbpropsMap.get("URL")),
201                String.valueOf(dbpropsMap.get("DataSourceInfo")),
202                pTypes,
203                aModes);
204        this.olap4jDatabases.add(database);
205
206        for (String catalogName
207            : catalogFinder.getCatalogNames(mondrianConnection))
208        {
209            final Map<String, RolapSchema> schemaMap =
210                catalogFinder.getRolapSchemas(
211                    mondrianConnection,
212                    catalogName);
213            olap4jCatalogs.add(
214                new MondrianOlap4jCatalog(
215                    olap4jDatabaseMetaData,
216                    catalogName,
217                    database,
218                    schemaMap));
219        }
220
221        this.olap4jSchema = toOlap4j(mondrianConnection.getSchema());
222    }
223
224    static boolean acceptsURL(String url) {
225        return url.startsWith(CONNECT_STRING_PREFIX);
226    }
227
228    public OlapStatement createStatement() {
229        final MondrianOlap4jStatement statement =
230            factory.newStatement(this);
231        mondrianServer.addStatement(statement);
232        return statement;
233    }
234
235    public ScenarioImpl createScenario() throws OlapException {
236        return getMondrianConnection().createScenario();
237    }
238
239    public void setScenario(Scenario scenario) throws OlapException {
240        getMondrianConnection().setScenario(scenario);
241    }
242
243    public Scenario getScenario() throws OlapException {
244        return getMondrianConnection().getScenario();
245    }
246
247    public PreparedStatement prepareStatement(String sql) throws SQLException {
248        throw new UnsupportedOperationException();
249    }
250
251    public CallableStatement prepareCall(String sql) throws SQLException {
252        throw new UnsupportedOperationException();
253    }
254
255    public String nativeSQL(String sql) throws SQLException {
256        throw new UnsupportedOperationException();
257    }
258
259    public void setAutoCommit(boolean autoCommit) throws SQLException {
260        this.autoCommit = autoCommit;
261    }
262
263    public boolean getAutoCommit() throws SQLException {
264        return autoCommit;
265    }
266
267    public void commit() throws SQLException {
268        throw new UnsupportedOperationException();
269    }
270
271    public void rollback() throws SQLException {
272        throw new UnsupportedOperationException();
273    }
274
275    public void close() throws SQLException {
276        if (mondrianConnection != null) {
277            RolapConnection c = mondrianConnection;
278            mondrianConnection = null;
279            c.close();
280        }
281    }
282
283    public boolean isClosed() throws SQLException {
284        return mondrianConnection == null;
285    }
286
287    public OlapDatabaseMetaData getMetaData() {
288        return olap4jDatabaseMetaData;
289    }
290
291    public void setReadOnly(boolean readOnly) throws SQLException {
292        this.readOnly = readOnly;
293    }
294
295    public boolean isReadOnly() throws SQLException {
296        return readOnly;
297    }
298
299    public void setSchema(String schemaName) throws OlapException {
300        // no op.
301    }
302
303    public String getSchema() throws OlapException {
304        return olap4jSchema.getName();
305    }
306
307    public Schema getOlapSchema() throws OlapException {
308        return olap4jSchema;
309    }
310
311    public NamedList<Schema> getOlapSchemas() throws OlapException {
312        return getOlapCatalog().getSchemas();
313    }
314
315    public void setCatalog(String catalogName) throws OlapException {
316        // no op
317    }
318
319    public String getCatalog() throws OlapException {
320        return olap4jSchema.olap4jCatalog.getName();
321    }
322
323    public Catalog getOlapCatalog() throws OlapException {
324        return olap4jSchema.olap4jCatalog;
325    }
326
327    public NamedList<Catalog> getOlapCatalogs() throws OlapException {
328        return getOlapDatabase().getCatalogs();
329    }
330
331    public void setDatabase(String databaseName) throws OlapException {
332        // no op.
333    }
334
335    public String getDatabase() throws OlapException {
336        return getOlapDatabase().getName();
337    }
338
339    public Database getOlapDatabase() throws OlapException {
340        // It is assumed that Mondrian supports only a single
341        // database.
342        return this.olap4jDatabases.get(0);
343    }
344
345    public NamedList<Database> getOlapDatabases() throws OlapException {
346        return Olap4jUtil.cast(this.olap4jDatabases);
347    }
348
349    public void setTransactionIsolation(int level) throws SQLException {
350        throw new UnsupportedOperationException();
351    }
352
353    public int getTransactionIsolation() throws SQLException {
354        return TRANSACTION_NONE;
355    }
356
357    public SQLWarning getWarnings() throws SQLException {
358        throw new UnsupportedOperationException();
359    }
360
361    public void clearWarnings() throws SQLException {
362    }
363
364    public Statement createStatement(
365        int resultSetType, int resultSetConcurrency) throws SQLException
366    {
367        throw new UnsupportedOperationException();
368    }
369
370    public PreparedStatement prepareStatement(
371        String sql,
372        int resultSetType,
373        int resultSetConcurrency) throws SQLException
374    {
375        throw new UnsupportedOperationException();
376    }
377
378    public CallableStatement prepareCall(
379        String sql,
380        int resultSetType,
381        int resultSetConcurrency) throws SQLException
382    {
383        throw new UnsupportedOperationException();
384    }
385
386    public Map<String, Class<?>> getTypeMap() throws SQLException {
387        throw new UnsupportedOperationException();
388    }
389
390    public void setTypeMap(Map<String, Class<?>> map) throws SQLException {
391        throw new UnsupportedOperationException();
392    }
393
394    public void setHoldability(int holdability) throws SQLException {
395        throw new UnsupportedOperationException();
396    }
397
398    public int getHoldability() throws SQLException {
399        throw new UnsupportedOperationException();
400    }
401
402    public Savepoint setSavepoint() throws SQLException {
403        throw new UnsupportedOperationException();
404    }
405
406    public Savepoint setSavepoint(String name) throws SQLException {
407        throw new UnsupportedOperationException();
408    }
409
410    public void rollback(Savepoint savepoint) throws SQLException {
411        throw new UnsupportedOperationException();
412    }
413
414    public void releaseSavepoint(Savepoint savepoint) throws SQLException {
415        throw new UnsupportedOperationException();
416    }
417
418    public Statement createStatement(
419        int resultSetType,
420        int resultSetConcurrency,
421        int resultSetHoldability) throws SQLException
422    {
423        throw new UnsupportedOperationException();
424    }
425
426    public PreparedStatement prepareStatement(
427        String sql,
428        int resultSetType,
429        int resultSetConcurrency,
430        int resultSetHoldability) throws SQLException
431    {
432        throw new UnsupportedOperationException();
433    }
434
435    public CallableStatement prepareCall(
436        String sql,
437        int resultSetType,
438        int resultSetConcurrency,
439        int resultSetHoldability) throws SQLException
440    {
441        throw new UnsupportedOperationException();
442    }
443
444    public PreparedStatement prepareStatement(
445        String sql, int autoGeneratedKeys) throws SQLException
446    {
447        throw new UnsupportedOperationException();
448    }
449
450    public PreparedStatement prepareStatement(
451        String sql, int columnIndexes[]) throws SQLException
452    {
453        throw new UnsupportedOperationException();
454    }
455
456    public PreparedStatement prepareStatement(
457        String sql, String columnNames[]) throws SQLException
458    {
459        throw new UnsupportedOperationException();
460    }
461
462    // implement Wrapper
463
464    public <T> T unwrap(Class<T> iface) throws SQLException {
465        if (iface.isInstance(this)) {
466            return iface.cast(this);
467        } else if (iface.isInstance(mondrianConnection)) {
468            return iface.cast(mondrianConnection);
469        }
470        if (iface == XmlaHandler.XmlaExtra.class) {
471            return iface.cast(MondrianOlap4jExtra.INSTANCE);
472        }
473        throw helper.createException("does not implement '" + iface + "'");
474    }
475
476    public boolean isWrapperFor(Class<?> iface) throws SQLException {
477        return iface.isInstance(this)
478            || iface.isInstance(mondrianConnection);
479    }
480
481    // implement OlapConnection
482
483    public PreparedOlapStatement prepareOlapStatement(
484        String mdx)
485        throws OlapException
486    {
487        final MondrianOlap4jPreparedStatement preparedStatement =
488            factory.newPreparedStatement(mdx, this);
489        mondrianServer.addStatement(preparedStatement);
490        return preparedStatement;
491    }
492
493    public MdxParserFactory getParserFactory() {
494        return new MdxParserFactory() {
495            public MdxParser createMdxParser(OlapConnection connection) {
496                return new DefaultMdxParserImpl();
497            }
498
499            public MdxValidator createMdxValidator(OlapConnection connection) {
500                return new MondrianOlap4jMdxValidator(connection);
501            }
502        };
503    }
504
505    MondrianOlap4jCube toOlap4j(mondrian.olap.Cube cube) {
506        MondrianOlap4jSchema schema = toOlap4j(cube.getSchema());
507        return new MondrianOlap4jCube(cube, schema);
508    }
509
510    MondrianOlap4jDimension toOlap4j(mondrian.olap.Dimension dimension) {
511        if (dimension == null) {
512            return null;
513        }
514        return new MondrianOlap4jDimension(
515            toOlap4j(dimension.getSchema()),
516            dimension);
517    }
518
519    synchronized MondrianOlap4jSchema toOlap4j(
520        mondrian.olap.Schema schema)
521    {
522        MondrianOlap4jSchema olap4jSchema = schemaMap.get(schema);
523        if (olap4jSchema == null) {
524            throw new RuntimeException("schema not registered: " + schema);
525        }
526        return olap4jSchema;
527    }
528
529    Type toOlap4j(mondrian.olap.type.Type type) {
530        if (type instanceof mondrian.olap.type.BooleanType) {
531            return new BooleanType();
532        } else if (type instanceof mondrian.olap.type.CubeType) {
533            final mondrian.olap.Cube mondrianCube =
534                ((mondrian.olap.type.CubeType) type).getCube();
535            return new CubeType(toOlap4j(mondrianCube));
536        } else if (type instanceof mondrian.olap.type.DecimalType) {
537            mondrian.olap.type.DecimalType decimalType =
538                (mondrian.olap.type.DecimalType) type;
539            return new DecimalType(
540                decimalType.getPrecision(),
541                decimalType.getScale());
542        } else if (type instanceof mondrian.olap.type.DimensionType) {
543            mondrian.olap.type.DimensionType dimensionType =
544                (mondrian.olap.type.DimensionType) type;
545            return new DimensionType(
546                toOlap4j(dimensionType.getDimension()));
547        } else if (type instanceof mondrian.olap.type.HierarchyType) {
548            return new BooleanType();
549        } else if (type instanceof mondrian.olap.type.LevelType) {
550            return new BooleanType();
551        } else if (type instanceof mondrian.olap.type.MemberType) {
552            final mondrian.olap.type.MemberType memberType =
553                (mondrian.olap.type.MemberType) type;
554            return new MemberType(
555                toOlap4j(memberType.getDimension()),
556                toOlap4j(memberType.getHierarchy()),
557                toOlap4j(memberType.getLevel()),
558                toOlap4j(memberType.getMember()));
559        } else if (type instanceof mondrian.olap.type.NullType) {
560            return new NullType();
561        } else if (type instanceof mondrian.olap.type.NumericType) {
562            return new NumericType();
563        } else if (type instanceof mondrian.olap.type.SetType) {
564            final mondrian.olap.type.SetType setType =
565                (mondrian.olap.type.SetType) type;
566            return new SetType(toOlap4j(setType.getElementType()));
567        } else if (type instanceof mondrian.olap.type.StringType) {
568            return new StringType();
569        } else if (type instanceof mondrian.olap.type.TupleType) {
570            mondrian.olap.type.TupleType tupleType =
571                (mondrian.olap.type.TupleType) type;
572            final Type[] types = toOlap4j(tupleType.elementTypes);
573            return new TupleType(types);
574        } else if (type instanceof mondrian.olap.type.SymbolType) {
575            return new SymbolType();
576        } else {
577            throw new UnsupportedOperationException();
578        }
579    }
580
581    MondrianOlap4jMember toOlap4j(mondrian.olap.Member member) {
582        if (member == null) {
583            return null;
584        }
585        if (member instanceof RolapMeasure) {
586            RolapMeasure measure = (RolapMeasure) member;
587            return new MondrianOlap4jMeasure(
588                toOlap4j(member.getDimension().getSchema()),
589                measure);
590        }
591        return new MondrianOlap4jMember(
592            toOlap4j(member.getDimension().getSchema()),
593            member);
594    }
595
596    MondrianOlap4jLevel toOlap4j(mondrian.olap.Level level) {
597        if (level == null) {
598            return null;
599        }
600        return new MondrianOlap4jLevel(
601            toOlap4j(level.getDimension().getSchema()),
602            level);
603    }
604
605    MondrianOlap4jHierarchy toOlap4j(mondrian.olap.Hierarchy hierarchy) {
606        if (hierarchy == null) {
607            return null;
608        }
609        return new MondrianOlap4jHierarchy(
610            toOlap4j(hierarchy.getDimension().getSchema()),
611            hierarchy);
612    }
613
614    Type[] toOlap4j(mondrian.olap.type.Type[] mondrianTypes) {
615        final Type[] types = new Type[mondrianTypes.length];
616        for (int i = 0; i < types.length; i++) {
617            types[i] = toOlap4j(mondrianTypes[i]);
618        }
619        return types;
620    }
621
622    NamedList<MondrianOlap4jMember> toOlap4j(
623        final List<Member> memberList)
624    {
625        return new AbstractNamedList<MondrianOlap4jMember>() {
626            public String getName(Object olap4jMember) {
627                return ((MondrianOlap4jMember)olap4jMember).getName();
628            }
629
630            public MondrianOlap4jMember get(int index) {
631                return toOlap4j(memberList.get(index));
632            }
633
634            public int size() {
635                return memberList.size();
636            }
637        };
638    }
639
640    MondrianOlap4jNamedSet toOlap4j(
641        mondrian.olap.Cube cube,
642        mondrian.olap.NamedSet namedSet)
643    {
644        if (namedSet == null) {
645            return null;
646        }
647        return new MondrianOlap4jNamedSet(
648            toOlap4j(cube),
649            namedSet);
650    }
651
652    ParseTreeNode toOlap4j(Exp exp) {
653        return new MondrianToOlap4jNodeConverter(this).toOlap4j(exp);
654    }
655
656    SelectNode toOlap4j(Query query) {
657        return new MondrianToOlap4jNodeConverter(this).toOlap4j(query);
658    }
659
660    public void setLocale(Locale locale) {
661        mondrianConnection.setLocale(locale);
662    }
663
664    public Locale getLocale() {
665        return mondrianConnection.getLocale();
666    }
667
668    public void setRoleName(String roleName) throws OlapException {
669        if (roleName == null) {
670            final RolapConnection connection1 = getMondrianConnection();
671            final Role role = Util.createRootRole(connection1.getSchema());
672            assert role != null;
673            this.roleName = roleName;
674            this.roleNames = Collections.emptyList();
675            connection1.setRole(role);
676        } else {
677            setRoleNames(Collections.singletonList(roleName));
678        }
679    }
680
681    /**
682     * <p>Set the active role(s) in this connection based on a list of role
683     * names.</p>
684     *
685     * <p>The list may be not be empty. Each role name must be not-null and the
686     * name of a valid role for the current user.</p>
687     *
688     * <p>This method is not part of the olap4j-1.x API. It may be included
689     * in olap4j-2.0. If you want to call this method on a
690     * {@link OlapConnection}, use {@link #unwrap} to get the underlying
691     * Mondrian connection.</p>
692     *
693     * @param roleNames List of role names
694     *
695     * @see #getRoleNames()
696     */
697    public void setRoleNames(List<String> roleNames) throws OlapException {
698        final RolapConnection connection1 = getMondrianConnection();
699        final List<Role> roleList = new ArrayList<Role>();
700        for (String roleName : roleNames) {
701            if (roleName == null) {
702                throw new NullPointerException("null role name");
703            }
704            final Role role = connection1.getSchema().lookupRole(roleName);
705            if (role == null) {
706                throw helper.createException("Unknown role '" + roleName + "'");
707            }
708            roleList.add(role);
709        }
710
711        // Remember the name of the role, because mondrian roles don't know
712        // their own name.
713        Role role;
714        switch (roleList.size()) {
715        case 0:
716            throw helper.createException("Empty list of role names");
717        case 1:
718            role = roleList.get(0);
719            this.roleName = roleNames.get(0);
720            this.roleNames = Collections.singletonList(roleName);
721            break;
722        default:
723            role = RoleImpl.union(roleList);
724            this.roleNames =
725                Collections.unmodifiableList(new ArrayList<String>(roleNames));
726            this.roleName = this.roleNames.toString();
727            break;
728        }
729        connection1.setRole(role);
730    }
731
732    public String getRoleName() {
733        return roleName;
734    }
735
736    /**
737     * Returns a list of the current role names.
738     *
739     * <p>This method is not part of the olap4j-1.x API. It may be included
740     * in olap4j-2.0. If you want to call this method on a
741     * {@link OlapConnection}, use {@link #unwrap} to get the underlying
742     * Mondrian connection.</p>
743     *
744     * @return List of the current role names
745     */
746    public List<String> getRoleNames() {
747        return roleNames;
748    }
749
750    public List<String> getAvailableRoleNames() throws OlapException {
751        return UnmodifiableArrayList.of(
752            getMondrianConnection().getSchema().roleNames());
753    }
754
755    public void setPreferList(boolean preferList) {
756        this.preferList = preferList;
757    }
758
759    /**
760     * Cop-out version of {@link #getMondrianConnection()} that doesn't throw
761     * a checked exception. For those situations where the olap4j API doesn't
762     * declare 'throws OlapException', but we need an open connection anyway.
763     * Use {@link #getMondrianConnection()} where possible.
764     *
765     * @return Mondrian connection
766     * @throws RuntimeException if connection is closed
767     */
768    RolapConnection getMondrianConnection2() throws RuntimeException {
769        try {
770            return getMondrianConnection();
771        } catch (OlapException e) {
772            // Demote from checked to unchecked exception.
773            throw new RuntimeException(e);
774        }
775    }
776
777    RolapConnection getMondrianConnection() throws OlapException {
778        final RolapConnection connection1 = mondrianConnection;
779        if (connection1 == null) {
780            throw helper.createException("Connection is closed.");
781        }
782        return connection1;
783    }
784
785    // inner classes
786
787    /**
788     * Package-private helper class which encapsulates policies which are
789     * common throughout the driver. These policies include exception handling
790     * and factory methods.
791     */
792    static class Helper {
793        OlapException createException(String msg) {
794            return new OlapException(msg);
795        }
796
797        /**
798         * Creates an exception in the context of a particular Cell.
799         *
800         * @param context Cell context for exception
801         * @param msg Message
802         * @return New exception
803         */
804        OlapException createException(Cell context, String msg) {
805            OlapException exception = new OlapException(msg);
806            exception.setContext(context);
807            return exception;
808        }
809
810        /**
811         * Creates an exception in the context of a particular Cell and with
812         * a given cause.
813         *
814         * @param context Cell context for exception
815         * @param msg Message
816         * @param cause Causing exception
817         * @return New exception
818         */
819        OlapException createException(
820            Cell context, String msg, Throwable cause)
821        {
822            OlapException exception = createException(msg, cause);
823            exception.setContext(context);
824            return exception;
825        }
826
827        /**
828         * Creates an exception with a given cause.
829         *
830         * @param msg Message
831         * @param cause Causing exception
832         * @return New exception
833         */
834        OlapException createException(
835            String msg, Throwable cause)
836        {
837            String sqlState = deduceSqlState(cause);
838            assert !mondrian.util.Bug.olap4jUpgrade(
839                "use OlapException(String, String, Throwable) ctor");
840            final OlapException e = new OlapException(msg, sqlState);
841            e.initCause(cause);
842            return e;
843        }
844
845        private String deduceSqlState(Throwable cause) {
846            if (cause == null) {
847                return null;
848            }
849            if (cause instanceof ResourceLimitExceededException) {
850                return "ResourceLimitExceeded";
851            }
852            if (cause instanceof QueryTimeoutException) {
853                return "QueryTimeout";
854            }
855            if (cause instanceof MondrianEvaluationException) {
856                return "EvaluationException";
857            }
858            if (cause instanceof QueryCanceledException) {
859                return "QueryCanceledException";
860            }
861            return null;
862        }
863
864        /**
865         * Converts a SQLException to an OlapException. Casts the exception
866         * if it is already an OlapException, wraps otherwise.
867         *
868         * <p>This method is typically used as an adapter for SQLException
869         * instances coming from a base class, where derived interface declares
870         * that it throws the more specific OlapException.
871         *
872         * @param e Exception
873         * @return Exception as an OlapException
874         */
875        public OlapException toOlapException(SQLException e) {
876            if (e instanceof OlapException) {
877                return (OlapException) e;
878            } else {
879                return new OlapException(null, e);
880            }
881        }
882    }
883
884    private static class MondrianOlap4jMdxValidator implements MdxValidator {
885        private final MondrianOlap4jConnection connection;
886
887        public MondrianOlap4jMdxValidator(OlapConnection connection) {
888            this.connection = (MondrianOlap4jConnection) connection;
889        }
890
891        public SelectNode validateSelect(SelectNode selectNode)
892            throws OlapException
893        {
894            try {
895                // A lot of mondrian's validation happens during parsing.
896                // Therefore to do effective validation, we need to go back to
897                // the MDX string. Someday we will reshape mondrian's
898                // parse/validation process to fit the olap4j model better.
899                StringWriter sw = new StringWriter();
900                selectNode.unparse(new ParseTreeWriter(new PrintWriter(sw)));
901                String mdx = sw.toString();
902                Query query =
903                    connection.mondrianConnection
904                        .parseQuery(mdx);
905                query.resolve();
906                return connection.toOlap4j(query);
907            } catch (MondrianException e) {
908                throw connection.helper.createException("Validation error", e);
909            }
910        }
911    }
912
913    private static class MondrianToOlap4jNodeConverter {
914        private final MondrianOlap4jConnection olap4jConnection;
915
916        MondrianToOlap4jNodeConverter(
917            MondrianOlap4jConnection olap4jConnection)
918        {
919            this.olap4jConnection = olap4jConnection;
920        }
921
922        public SelectNode toOlap4j(Query query) {
923            List<IdentifierNode> list = Collections.emptyList();
924            return new SelectNode(
925                null,
926                toOlap4j(query.getFormulas()),
927                toOlap4j(query.getAxes()),
928                new CubeNode(
929                    null,
930                    olap4jConnection.toOlap4j(query.getCube())),
931                query.getSlicerAxis() == null
932                    ? null
933                    : toOlap4j(query.getSlicerAxis()),
934                list);
935        }
936
937        private AxisNode toOlap4j(QueryAxis axis) {
938            return new AxisNode(
939                null,
940                axis.isNonEmpty(),
941                Axis.Factory.forOrdinal(
942                    axis.getAxisOrdinal().logicalOrdinal()),
943                toOlap4j(axis.getDimensionProperties()),
944                toOlap4j(axis.getSet()));
945        }
946
947        private List<IdentifierNode> toOlap4j(Id[] dimensionProperties) {
948            final List<IdentifierNode> list = new ArrayList<IdentifierNode>();
949            for (Id property : dimensionProperties) {
950                list.add(toOlap4j(property));
951            }
952            return list;
953        }
954
955        private ParseTreeNode toOlap4j(Exp exp) {
956            if (exp instanceof Id) {
957                Id id = (Id) exp;
958                return toOlap4j(id);
959            }
960            if (exp instanceof ResolvedFunCall) {
961                ResolvedFunCall call = (ResolvedFunCall) exp;
962                return toOlap4j(call);
963            }
964            if (exp instanceof DimensionExpr) {
965                DimensionExpr dimensionExpr = (DimensionExpr) exp;
966                return new DimensionNode(
967                    null,
968                    olap4jConnection.toOlap4j(dimensionExpr.getDimension()));
969            }
970            if (exp instanceof HierarchyExpr) {
971                HierarchyExpr hierarchyExpr = (HierarchyExpr) exp;
972                return new HierarchyNode(
973                    null,
974                    olap4jConnection.toOlap4j(hierarchyExpr.getHierarchy()));
975            }
976            if (exp instanceof LevelExpr) {
977                LevelExpr levelExpr = (LevelExpr) exp;
978                return new LevelNode(
979                    null,
980                    olap4jConnection.toOlap4j(levelExpr.getLevel()));
981            }
982            if (exp instanceof MemberExpr) {
983                MemberExpr memberExpr = (MemberExpr) exp;
984                return new MemberNode(
985                    null,
986                    olap4jConnection.toOlap4j(memberExpr.getMember()));
987            }
988            if (exp instanceof Literal) {
989                Literal literal = (Literal) exp;
990                final Object value = literal.getValue();
991                if (literal.getCategory() == Category.Symbol) {
992                    return LiteralNode.createSymbol(
993                        null, (String) literal.getValue());
994                } else if (value instanceof Number) {
995                    Number number = (Number) value;
996                    BigDecimal bd = bigDecimalFor(number);
997                    return LiteralNode.createNumeric(null, bd, false);
998                } else if (value instanceof String) {
999                    return LiteralNode.createString(null, (String) value);
1000                } else if (value == null) {
1001                    return LiteralNode.createNull(null);
1002                } else {
1003                    throw new RuntimeException("unknown literal " + literal);
1004                }
1005            }
1006            throw Util.needToImplement(exp.getClass());
1007        }
1008
1009        /**
1010         * Converts a number to big decimal, non-lossy if possible.
1011         *
1012         * @param number Number
1013         * @return BigDecimal
1014         */
1015        private static BigDecimal bigDecimalFor(Number number) {
1016            if (number instanceof BigDecimal) {
1017                return (BigDecimal) number;
1018            } else if (number instanceof BigInteger) {
1019                return new BigDecimal((BigInteger) number);
1020            } else if (number instanceof Integer) {
1021                return new BigDecimal((Integer) number);
1022            } else if (number instanceof Double) {
1023                return new BigDecimal((Double) number);
1024            } else if (number instanceof Float) {
1025                return new BigDecimal((Float) number);
1026            } else if (number instanceof Long) {
1027                return new BigDecimal((Long) number);
1028            } else if (number instanceof Short) {
1029                return new BigDecimal((Short) number);
1030            } else if (number instanceof Byte) {
1031                return new BigDecimal((Byte) number);
1032            } else {
1033                return new BigDecimal(number.doubleValue());
1034            }
1035        }
1036
1037        private ParseTreeNode toOlap4j(ResolvedFunCall call) {
1038            final CallNode callNode = new CallNode(
1039                null,
1040                call.getFunName(),
1041                toOlap4j(call.getSyntax()),
1042                toOlap4j(Arrays.asList(call.getArgs())));
1043            if (call.getType() != null) {
1044                callNode.setType(olap4jConnection.toOlap4j(call.getType()));
1045            }
1046            return callNode;
1047        }
1048
1049        private List<ParseTreeNode> toOlap4j(List<Exp> exprList) {
1050            final List<ParseTreeNode> result = new ArrayList<ParseTreeNode>();
1051            for (Exp expr : exprList) {
1052                result.add(toOlap4j(expr));
1053            }
1054            return result;
1055        }
1056
1057        private org.olap4j.mdx.Syntax toOlap4j(mondrian.olap.Syntax syntax) {
1058            return org.olap4j.mdx.Syntax.valueOf(syntax.name());
1059        }
1060
1061        private List<AxisNode> toOlap4j(QueryAxis[] axes) {
1062            final ArrayList<AxisNode> axisList = new ArrayList<AxisNode>();
1063            for (QueryAxis axis : axes) {
1064                axisList.add(toOlap4j(axis));
1065            }
1066            return axisList;
1067        }
1068
1069        private List<ParseTreeNode> toOlap4j(Formula[] formulas) {
1070            final List<ParseTreeNode> list = new ArrayList<ParseTreeNode>();
1071            for (Formula formula : formulas) {
1072                if (formula.isMember()) {
1073                    List<PropertyValueNode> memberPropertyList =
1074                        new ArrayList<PropertyValueNode>();
1075                    for (Object child : formula.getChildren()) {
1076                        if (child instanceof MemberProperty) {
1077                            MemberProperty memberProperty =
1078                                (MemberProperty) child;
1079                            memberPropertyList.add(
1080                                new PropertyValueNode(
1081                                    null,
1082                                    memberProperty.getName(),
1083                                    toOlap4j(memberProperty.getExp())));
1084                        }
1085                    }
1086                    list.add(
1087                        new WithMemberNode(
1088                            null,
1089                            toOlap4j(formula.getIdentifier()),
1090                            toOlap4j(formula.getExpression()),
1091                            memberPropertyList));
1092                }
1093            }
1094            return list;
1095        }
1096
1097        private static IdentifierNode toOlap4j(Id id) {
1098            List<IdentifierSegment> list = Util.toOlap4j(id.getSegments());
1099            return new IdentifierNode(
1100                list.toArray(
1101                    new IdentifierSegment[list.size()]));
1102        }
1103    }
1104}
1105
1106// End MondrianOlap4jConnection.java