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