001    /*
002    // $Id: //open/mondrian-release/3.0/src/main/mondrian/rolap/RolapUtil.java#3 $
003    // This software is subject to the terms of the Common Public License
004    // Agreement, available at the following URL:
005    // http://www.opensource.org/licenses/cpl.html.
006    // Copyright (C) 2001-2002 Kana Software, Inc.
007    // Copyright (C) 2001-2007 Julian Hyde and others
008    // All Rights Reserved.
009    // You must accept the terms of that agreement to use this software.
010    //
011    // jhyde, 22 December, 2001
012    */
013    
014    package mondrian.rolap;
015    import mondrian.olap.*;
016    import mondrian.olap.fun.FunUtil;
017    import mondrian.resource.MondrianResource;
018    
019    import org.apache.log4j.Logger;
020    import org.eigenbase.util.property.StringProperty;
021    import java.io.*;
022    import java.lang.reflect.Array;
023    import java.sql.SQLException;
024    import java.util.*;
025    
026    import mondrian.calc.ExpCompiler;
027    import mondrian.rolap.sql.SqlQuery;
028    
029    import javax.sql.DataSource;
030    
031    /**
032     * Utility methods for classes in the <code>mondrian.rolap</code> package.
033     *
034     * @author jhyde
035     * @since 22 December, 2001
036     * @version $Id: //open/mondrian-release/3.0/src/main/mondrian/rolap/RolapUtil.java#3 $
037     */
038    public class RolapUtil {
039        public static final Logger MDX_LOGGER = Logger.getLogger("mondrian.mdx");
040        public static final Logger SQL_LOGGER = Logger.getLogger("mondrian.sql");
041        static final Logger LOGGER = Logger.getLogger(RolapUtil.class);
042        static final RolapMember[] emptyMemberArray = new RolapMember[0];
043        private static Semaphore querySemaphore;
044    
045        /**
046         * Special cell value indicates that the value is not in cache yet.
047         */
048        public static final Object valueNotReadyException = new Double(0);
049    
050        /**
051         * Hook to run when a query is executed.
052         */
053        static final ThreadLocal<ExecuteQueryHook> threadHooks =
054            new ThreadLocal<ExecuteQueryHook>();
055    
056        /**
057         * Special value represents a null key.
058         */
059        public static final Comparable sqlNullValue = new Comparable() {
060            public boolean equals(Object o) {
061                return o == this;
062            }
063            public int hashCode() {
064                return super.hashCode();
065            }
066            public String toString() {
067                return "#null";
068            }
069    
070            public int compareTo(Object o) {
071                return o == this ? 0 : -1;
072            }
073        };
074    
075        /**
076         * Runtime NullMemberRepresentation property change not taken into
077         * consideration
078         */
079        public static final String mdxNullLiteral =
080                MondrianProperties.instance().NullMemberRepresentation.get();
081        public static final String sqlNullLiteral = "null";
082    
083        /**
084         * Names of classes of drivers we've loaded (or have tried to load).
085         *
086         * <p>NOTE: Synchronization policy: Lock the {@link RolapConnection} class
087         * before modifying or using this member.
088         */
089        private static final Set<String> loadedDrivers = new HashSet<String>();
090    
091        static RolapMember[] toArray(List<RolapMember> v) {
092            return v.isEmpty()
093                ? emptyMemberArray
094                : v.toArray(new RolapMember[v.size()]);
095        }
096    
097        static RolapMember lookupMember(
098            MemberReader reader,
099            List<Id.Segment> uniqueNameParts,
100            boolean failIfNotFound)
101        {
102            RolapMember member =
103                lookupMemberInternal(
104                    uniqueNameParts, null, reader, failIfNotFound);
105            if (member != null) {
106                return member;
107            }
108    
109            // If this hierarchy has an 'all' member, we can omit it.
110            // For example, '[Gender].[(All Gender)].[F]' can be abbreviated
111            // '[Gender].[F]'.
112            final List<RolapMember> rootMembers = reader.getRootMembers();
113            if (rootMembers.size() == 1) {
114                final RolapMember rootMember = rootMembers.get(0);
115                if (rootMember.isAll()) {
116                    member =
117                        lookupMemberInternal(
118                            uniqueNameParts, rootMember, reader, failIfNotFound);
119                }
120            }
121            return member;
122        }
123    
124        private static RolapMember lookupMemberInternal(
125            List<Id.Segment> segments,
126            RolapMember member,
127            MemberReader reader,
128            boolean failIfNotFound)
129        {
130            for (Id.Segment segment : segments) {
131                List<RolapMember> children;
132                if (member == null) {
133                    children = reader.getRootMembers();
134                } else {
135                    children = new ArrayList<RolapMember>();
136                    reader.getMemberChildren(member, children);
137                    member = null;
138                }
139                for (RolapMember child : children) {
140                    if (child.getName().equals(segment.name)) {
141                        member = child;
142                        break;
143                    }
144                }
145                if (member == null) {
146                    break;
147                }
148            }
149            if (member == null && failIfNotFound) {
150                throw MondrianResource.instance().MdxCantFindMember.ex(
151                    Util.implode(segments));
152            }
153            return member;
154        }
155    
156        /**
157         * Adds an object to the end of an array.  The resulting array is of the
158         * same type (e.g. <code>String[]</code>) as the input array.
159         */
160        static <T> T[] addElement(T[] a, T o) {
161            Class clazz = a.getClass().getComponentType();
162            T[] a2 = (T[]) Array.newInstance(clazz, a.length + 1);
163            System.arraycopy(a, 0, a2, 0, a.length);
164            a2[a.length] = o;
165            return a2;
166        }
167    
168        /**
169         * Adds an array to the end of an array.  The resulting array is of the
170         * same type (e.g. <code>String[]</code>) as the input array.
171         */
172        static <T> T[] addElements(T[] a, T[] b) {
173            Class clazz = a.getClass().getComponentType();
174            T[] c = (T[]) Array.newInstance(clazz, a.length + b.length);
175            System.arraycopy(a, 0, c, 0, a.length);
176            System.arraycopy(b, 0, c, a.length, b.length);
177            return c;
178        }
179    
180        /**
181         * Executes a query, printing to the trace log if tracing is enabled.
182         *
183         * <p>If the query fails, it wraps the {@link SQLException} in a runtime
184         * exception with <code>message</code> as description, and closes the result
185         * set.
186         *
187         * <p>If it succeeds, the caller must call the {@link SqlStatement#close}
188         * method of the returned {@link SqlStatement}.
189         *
190         * @param dataSource DataSource
191         * @param sql SQL string
192         * @param component Description of a the component executing the query,
193         *   generally a method name, e.g. "SqlTupleReader.readTuples"
194         * @param message Description of the purpose of this statement, to be
195         *   printed if there is an error
196         * @return ResultSet
197         */
198        public static SqlStatement executeQuery(
199            DataSource dataSource,
200            String sql,
201            String component,
202            String message)
203        {
204            return executeQuery(
205                dataSource, sql, -1, component, message, -1, -1);
206        }
207    
208        /**
209         * Executes a query.
210         *
211         * <p>If the query fails, it wraps the {@link SQLException} in a runtime
212         * exception with <code>message</code> as description, and closes the result
213         * set.
214         *
215         * <p>If it succeeds, the caller must call the {@link SqlStatement#close}
216         * method of the returned {@link SqlStatement}.
217         *
218         * @param dataSource DataSource
219         * @param sql SQL string
220         * @param maxRows Row limit, or -1 if no limit
221         * @param component Description of a the component executing the query,
222         *   generally a method name, e.g. "SqlTupleReader.readTuples"
223         * @param message Description of the purpose of this statement, to be
224         *   printed if there is an error
225         * @param resultSetType Result set type, or -1 to use default
226         * @param resultSetConcurrency Result set concurrency, or -1 to use default
227         * @return ResultSet
228         */
229        public static SqlStatement executeQuery(
230            DataSource dataSource,
231            String sql,
232            int maxRows,
233            String component,
234            String message,
235            int resultSetType,
236            int resultSetConcurrency)
237        {
238            SqlStatement stmt =
239                new SqlStatement(
240                    dataSource, sql, maxRows, component, message,
241                    resultSetType, resultSetConcurrency);
242            try {
243                stmt.execute();
244                return stmt;
245            } catch (SQLException e) {
246                throw stmt.handle(e);
247            }
248        }
249    
250        /**
251         * Raises an alert that native SQL evaluation could not be used
252         * in a case where it might have been beneficial, but some
253         * limitation in Mondrian's implementation prevented it.
254         * (Do not call this in cases where native evaluation would
255         * have been wasted effort.)
256         *
257         * @param functionName name of function for which native evaluation
258         * was skipped
259         *
260         * @param reason reason why native evaluation was skipped
261         */
262        public static void alertNonNative(
263            String functionName, String reason)
264            throws NativeEvaluationUnsupportedException {
265    
266            // No i18n for log message, but yes for excn
267            String alertMsg =
268                "Unable to use native SQL evaluation for '" + functionName
269                + "'; reason:  " + reason;
270    
271            StringProperty alertProperty =
272                MondrianProperties.instance().AlertNativeEvaluationUnsupported;
273            String alertValue = alertProperty.get();
274    
275            if (alertValue.equalsIgnoreCase(
276                    org.apache.log4j.Level.WARN.toString())) {
277                LOGGER.warn(alertMsg);
278            } else if (alertValue.equalsIgnoreCase(
279                           org.apache.log4j.Level.ERROR.toString())) {
280                LOGGER.error(alertMsg);
281                throw MondrianResource.instance().NativeEvaluationUnsupported.ex(
282                    functionName);
283            }
284        }
285    
286        /**
287         * Loads a set of JDBC drivers.
288         *
289         * @param jdbcDrivers A string consisting of the comma-separated names
290         *  of JDBC driver classes. For example
291         *  <code>"sun.jdbc.odbc.JdbcOdbcDriver,com.mysql.jdbc.Driver"</code>.
292         */
293        public static synchronized void loadDrivers(String jdbcDrivers) {
294            StringTokenizer tok = new StringTokenizer(jdbcDrivers, ",");
295            while (tok.hasMoreTokens()) {
296                String jdbcDriver = tok.nextToken();
297                if (loadedDrivers.add(jdbcDriver)) {
298                    try {
299                        Class.forName(jdbcDriver);
300                        LOGGER.info("Mondrian: JDBC driver "
301                            + jdbcDriver + " loaded successfully");
302                    } catch (ClassNotFoundException e) {
303                        LOGGER.warn("Mondrian: Warning: JDBC driver "
304                            + jdbcDriver + " not found");
305                    }
306                }
307            }
308        }
309    
310        /**
311         * Creates a compiler which will generate programs which will test
312         * whether the dependencies declared via
313         * {@link mondrian.calc.Calc#dependsOn(mondrian.olap.Dimension)} are
314         * accurate.
315         */
316        public static ExpCompiler createDependencyTestingCompiler(
317                ExpCompiler compiler) {
318            return new RolapDependencyTestingEvaluator.DteCompiler(compiler);
319        }
320    
321        /**
322         * Locates a member specified by its member name, from an array of
323         * members.  If an exact match isn't found, but a matchType of BEFORE
324         * or AFTER is specified, then the closest matching member is returned.
325         *
326         * @param members array of members to search from
327         * @param parent parent member corresponding to the member being searched
328         * for
329         * @param level level of the member
330         * @param searchName member name
331         * @param matchType match type
332         * @param caseInsensitive if true, use case insensitive search (if
333         * applicable) when when doing exact searches
334         *
335         * @return matching member (if it exists) or the closest matching one
336         * in the case of a BEFORE or AFTER search
337         */
338        public static Member findBestMemberMatch(
339            List<? extends Member> members,
340            RolapMember parent,
341            RolapLevel level,
342            Id.Segment searchName,
343            MatchType matchType,
344            boolean caseInsensitive)
345        {
346            // create a member corresponding to the member we're trying
347            // to locate so we can use it to hierarchically compare against
348            // the members array
349            Member searchMember = level.getHierarchy().createMember(parent, level, searchName.name, null);
350            Member bestMatch = null;
351            for (Member member : members) {
352                int rc;
353                if (searchName.quoting==Id.Quoting.KEY
354                        && member instanceof RolapMember) {
355                    if (((RolapMember) member).getKey().toString()
356                            .equals(searchName.name)) {
357                        return member;
358                    }
359                }
360                if (matchType == MatchType.EXACT) {
361                    if (caseInsensitive) {
362                        rc = Util.compareName(member.getName(), searchName.name);
363                    } else {
364                        rc = member.getName().compareTo(searchName.name);
365                    }
366                } else {
367                    rc =
368                        FunUtil.compareSiblingMembers(
369                            member,
370                            searchMember);
371                }
372                if (rc == 0) {
373                    return member;
374                }
375                if (matchType == MatchType.BEFORE) {
376                    if (rc < 0 &&
377                        (bestMatch == null ||
378                            FunUtil.compareSiblingMembers(member, bestMatch) > 0)) {
379                        bestMatch = member;
380                    }
381                } else if (matchType == MatchType.AFTER) {
382                    if (rc > 0 &&
383                        (bestMatch == null ||
384                            FunUtil.compareSiblingMembers(member, bestMatch) < 0)) {
385                        bestMatch = member;
386                    }
387                }
388            }
389            if (matchType == MatchType.EXACT) {
390                return null;
391            }
392            return bestMatch;
393        }
394    
395        public static MondrianDef.Relation convertInlineTableToRelation(
396            MondrianDef.InlineTable inlineTable,
397            final SqlQuery.Dialect dialect)
398        {
399            MondrianDef.View view = new MondrianDef.View();
400            view.alias = inlineTable.alias;
401    
402            final int columnCount = inlineTable.columnDefs.array.length;
403            List<String> columnNames = new ArrayList<String>();
404            List<String> columnTypes = new ArrayList<String>();
405            for (int i = 0; i < columnCount; i++) {
406                columnNames.add(inlineTable.columnDefs.array[i].name);
407                columnTypes.add(inlineTable.columnDefs.array[i].type);
408            }
409            List<String[]> valueList = new ArrayList<String[]>();
410            for (MondrianDef.Row row : inlineTable.rows.array) {
411                String[] values = new String[columnCount];
412                for (MondrianDef.Value value : row.values) {
413                    final int columnOrdinal = columnNames.indexOf(value.column);
414                    if (columnOrdinal < 0) {
415                        throw Util.newError(
416                            "Unknown column '" + value.column + "'");
417                    }
418                    values[columnOrdinal] = value.cdata;
419                }
420                valueList.add(values);
421            }
422            view.addCode(
423                "generic",
424                dialect.generateInline(
425                    columnNames,
426                    columnTypes,
427                    valueList));
428            return view;
429        }
430    
431        /**
432         * Writes to a string and also to an underlying writer.
433         */
434        public static class TeeWriter extends FilterWriter {
435            StringWriter buf = new StringWriter();
436            public TeeWriter(Writer out) {
437                super(out);
438            }
439    
440            /**
441             * Returns everything which has been written so far.
442             */
443            public String toString() {
444                return buf.toString();
445            }
446    
447            /**
448             * Returns the underlying writer.
449             */
450            public Writer getWriter() {
451                return out;
452            }
453    
454            public void write(int c) throws IOException {
455                super.write(c);
456                buf.write(c);
457            }
458    
459            public void write(char cbuf[]) throws IOException {
460                super.write(cbuf);
461                buf.write(cbuf);
462            }
463    
464            public void write(char cbuf[], int off, int len) throws IOException {
465                super.write(cbuf, off, len);
466                buf.write(cbuf, off, len);
467            }
468    
469            public void write(String str) throws IOException {
470                super.write(str);
471                buf.write(str);
472            }
473    
474            public void write(String str, int off, int len) throws IOException {
475                super.write(str, off, len);
476                buf.write(str, off, len);
477            }
478        }
479    
480        /**
481         * Writer which throws away all input.
482         */
483        private static class NullWriter extends Writer {
484            public void write(char cbuf[], int off, int len) throws IOException {
485            }
486    
487            public void flush() throws IOException {
488            }
489    
490            public void close() throws IOException {
491            }
492        }
493    
494        /**
495         * Gets the semaphore which controls how many people can run queries
496         * simultaneously.
497         */
498        static synchronized Semaphore getQuerySemaphore() {
499            if (querySemaphore == null) {
500                int queryCount = MondrianProperties.instance().QueryLimit.get();
501                querySemaphore = new Semaphore(queryCount);
502            }
503            return querySemaphore;
504        }
505    
506        /**
507         * Creates a dummy evaluator.
508         */
509        public static Evaluator createEvaluator(Query query) {
510            final RolapResult result = new RolapResult(query, false);
511            return result.getRootEvaluator();
512        }
513    
514        /**
515         * A <code>Semaphore</code> is a primitive for process synchronization.
516         *
517         * <p>Given a semaphore initialized with <code>count</code>, no more than
518         * <code>count</code> threads can acquire the semaphore using the
519         * {@link #enter} method. Waiting threads block until enough threads have
520         * called {@link #leave}.
521         */
522        static class Semaphore {
523            private int count;
524            Semaphore(int count) {
525                if (count < 0) {
526                    count = Integer.MAX_VALUE;
527                }
528                this.count = count;
529            }
530            synchronized void enter() {
531                if (count == Integer.MAX_VALUE) {
532                    return;
533                }
534                if (count == 0) {
535                    try {
536                        wait();
537                    } catch (InterruptedException e) {
538                        throw Util.newInternal(e, "while waiting for semaphore");
539                    }
540                }
541                Util.assertTrue(count > 0);
542                count--;
543            }
544            synchronized void leave() {
545                if (count == Integer.MAX_VALUE) {
546                    return;
547                }
548                count++;
549                notify();
550            }
551        }
552    
553        static interface ExecuteQueryHook {
554            void onExecuteQuery(String sql);
555        }
556    
557    }
558    
559    // End RolapUtil.java