001    /*
002    // $Id: //open/mondrian/src/main/mondrian/tui/CmdRunner.java#48 $
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) 2005-2008 Julian Hyde and others
007    // All Rights Reserved.
008    // You must accept the terms of that agreement to use this software.
009    */
010    
011    package mondrian.tui;
012    
013    import mondrian.olap.*;
014    import mondrian.olap.type.TypeUtil;
015    import mondrian.olap.fun.FunInfo;
016    import mondrian.rolap.RolapConnectionProperties;
017    import mondrian.rolap.RolapCube;
018    import org.apache.log4j.Level;
019    import org.apache.log4j.Logger;
020    import org.apache.log4j.LogManager;
021    import org.eigenbase.util.property.Property;
022    
023    import java.io.*;
024    import java.text.NumberFormat;
025    import java.text.ParseException;
026    import java.util.ArrayList;
027    import java.util.Enumeration;
028    import java.util.Iterator;
029    import java.util.List;
030    import java.util.HashMap;
031    import java.util.Map;
032    import java.util.regex.Matcher;
033    import java.util.regex.Pattern;
034    import java.lang.reflect.Field;
035    import java.lang.reflect.Modifier;
036    
037    /**
038     * Command line utility which reads and executes MDX commands.
039     *
040     * <p>TODO: describe how to use this class.</p>
041     *
042     * @author Richard Emberson
043     * @version $Id: //open/mondrian/src/main/mondrian/tui/CmdRunner.java#48 $
044     */
045    public class CmdRunner {
046    
047        private static final String nl = Util.nl;
048    
049        private static boolean RELOAD_CONNECTION = true;
050        private static String CATALOG_NAME = "FoodMart";
051    
052        private static final Map<Object, String> paraNameValues =
053            new HashMap<Object, String>();
054    
055        private static String[][] commentDelim;
056        private static char[] commentStartChars;
057        private static boolean allowNestedComments;
058    
059        private final Options options;
060        private long queryTime;
061        private long totalQueryTime;
062        private String filename;
063        private String mdxCmd;
064        private String mdxResult;
065        private String error;
066        private String stack;
067        private String connectString;
068        private Connection connection;
069        private final PrintWriter out;
070    
071        static {
072            setDefaultCommentState();
073        }
074    
075        /**
076         * Creates a <code>CmdRunner</code>.
077         *
078         * @param options Option set, or null to use default options
079         * @param out Output writer, or null to use {@link System#out}.
080         */
081        public CmdRunner(Options options, PrintWriter out) {
082            if (options == null) {
083                options = new Options();
084            }
085            this.options = options;
086            this.filename = null;
087            this.mdxResult = null;
088            this.error = null;
089            this.queryTime = -1;
090            if (out == null) {
091                out = new PrintWriter(System.out);
092            }
093            this.out = out;
094        }
095    
096        public void setTimeQueries(boolean timeQueries) {
097            this.options.timeQueries = timeQueries;
098        }
099    
100        public boolean getTimeQueries() {
101            return options.timeQueries;
102        }
103    
104        public long getQueryTime() {
105            return queryTime;
106        }
107    
108        public long getTotalQueryTime() {
109            return totalQueryTime;
110        }
111    
112        public void noCubeCaching() {
113            Cube[] cubes = getCubes();
114            for (Cube cube : cubes) {
115                RolapCube rcube = (RolapCube) cube;
116                rcube.setCacheAggregations(false);
117            }
118        }
119    
120        void setError(String s) {
121            this.error = s;
122        }
123    
124        void setError(Throwable t) {
125            this.error = formatError(t);
126            StringWriter sw = new StringWriter();
127            PrintWriter pw = new PrintWriter(sw);
128            t.printStackTrace(pw);
129            pw.flush();
130            this.stack = sw.toString();
131        }
132    
133        void clearError() {
134            this.error = null;
135            this.stack = null;
136        }
137    
138        private String formatError(Throwable mex) {
139                String message = mex.getMessage();
140                if (message == null) {
141                    message = mex.toString();
142                }
143                if (mex.getCause() != null && mex.getCause() != mex) {
144                        message = message + nl + formatError(mex.getCause());
145                }
146                return message;
147        }
148    
149        public static void listPropertyNames(StringBuilder buf) {
150            PropertyInfo propertyInfo =
151                    new PropertyInfo(MondrianProperties.instance());
152            for (int i = 0; i < propertyInfo.size(); i++) {
153                buf.append(propertyInfo.getProperty(i).getPath());
154                buf.append(nl);
155            }
156        }
157    
158        public static void listPropertiesAll(StringBuilder buf) {
159            PropertyInfo propertyInfo =
160                    new PropertyInfo(MondrianProperties.instance());
161            for (int i = 0; i < propertyInfo.size(); i++) {
162                String propertyName = propertyInfo.getPropertyName(i);
163                String propertyValue = propertyInfo.getProperty(i).getString();
164                buf.append(propertyName);
165                buf.append('=');
166                buf.append(propertyValue);
167                buf.append(nl);
168            }
169        }
170    
171        /**
172         * Returns the value of a property, or null if it is not set.
173         */
174        private static String getPropertyValue(String propertyName) {
175            final Property property = PropertyInfo.lookupProperty(
176                    MondrianProperties.instance(),
177                    propertyName);
178            return property.isSet() ?
179                    property.getString() :
180                    null;
181        }
182    
183        public static void listProperty(String propertyName, StringBuilder buf) {
184            buf.append(getPropertyValue(propertyName));
185        }
186    
187        public static boolean isProperty(String propertyName) {
188            final Property property = PropertyInfo.lookupProperty(
189                    MondrianProperties.instance(),
190                    propertyName);
191            return property != null;
192        }
193    
194        public static boolean setProperty(String name, String value) {
195            final Property property = PropertyInfo.lookupProperty(
196                    MondrianProperties.instance(),
197                    name);
198            String oldValue = property.getString();
199            if (! Util.equals(oldValue, value)) {
200                property.setString(value);
201                return true;
202            } else {
203                return false;
204            }
205        }
206    
207        public void loadParameters(Query query) {
208            Parameter[] params = query.getParameters();
209            for (Parameter param : params) {
210                loadParameter(query, param);
211            }
212        }
213    
214        /**
215         * Looks up the definition of a property with a given name.
216         */
217        private static class PropertyInfo {
218            private final List<Property> propertyList = new ArrayList<Property>();
219            private final List<String> propertyNameList = new ArrayList<String>();
220    
221            PropertyInfo(MondrianProperties properties) {
222                final Class<? extends Object> clazz = properties.getClass();
223                final Field[] fields = clazz.getFields();
224                for (Field field : fields) {
225                    if (!Modifier.isPublic(field.getModifiers()) ||
226                        Modifier.isStatic(field.getModifiers()) ||
227                        !Property.class.isAssignableFrom(
228                            field.getType())) {
229                        continue;
230                    }
231                    final Property property;
232                    try {
233                        property = (Property) field.get(properties);
234                    } catch (IllegalAccessException e) {
235                        continue;
236                    }
237                    propertyList.add(property);
238                    propertyNameList.add(field.getName());
239                }
240            }
241    
242            public int size() {
243                return propertyList.size();
244            }
245    
246            public Property getProperty(int i) {
247                return propertyList.get(i);
248            }
249    
250            public String getPropertyName(int i) {
251                return propertyNameList.get(i);
252            }
253    
254            /**
255             * Looks up the definition of a property with a given name.
256             */
257            public static Property lookupProperty(
258                    MondrianProperties properties,
259                    String propertyName)
260            {
261                final Class<? extends Object> clazz = properties.getClass();
262                final Field field;
263                try {
264                    field = clazz.getField(propertyName);
265                } catch (NoSuchFieldException e) {
266                    return null;
267                }
268                if (!Modifier.isPublic(field.getModifiers()) ||
269                        Modifier.isStatic(field.getModifiers()) ||
270                        !Property.class.isAssignableFrom(field.getType())) {
271                    return null;
272                }
273                try {
274                    return (Property) field.get(properties);
275                } catch (IllegalAccessException e) {
276                    return null;
277                }
278            }
279        }
280    
281        private static class Expr {
282            enum Type {
283                STRING,
284                NUMERIC,
285                MEMBER
286            }
287    
288            final Object value;
289            final Type type;
290            Expr(Object value, Type type) {
291                this.value = value;
292                this.type = type;
293            }
294        }
295    
296        public void loadParameter(Query query, Parameter param) {
297            int category = TypeUtil.typeToCategory(param.getType());
298            String name = param.getName();
299            String value = CmdRunner.paraNameValues.get(name);
300            debug("loadParameter: name=" + name + ", value=" + value);
301            if (value == null) {
302                return;
303            }
304            Expr expr = parseParameter(value);
305            if  (expr == null) {
306                return;
307            }
308            Expr.Type type = expr.type;
309            // found the parameter with the given name in the query
310            switch (category) {
311            case Category.Numeric:
312                if (type != Expr.Type.NUMERIC) {
313                    String msg = "For parameter named \""
314                            + name
315                            + "\" of Catetory.Numeric, "
316                            + "the value was type \""
317                            + type
318                            + "\"";
319                    throw new IllegalArgumentException(msg);
320                }
321                break;
322            case Category.String:
323                if (type != Expr.Type.STRING) {
324                    String msg = "For parameter named \""
325                            + name
326                            + "\" of Catetory.String, "
327                            + "the value was type \""
328                            + type
329                            + "\"";
330                    throw new IllegalArgumentException(msg);
331                }
332                break;
333    
334            case Category.Member:
335                if (type != Expr.Type.MEMBER) {
336                    String msg = "For parameter named \""
337                            + name
338                            + "\" of Catetory.Member, "
339                            + "the value was type \""
340                            + type
341                            + "\"";
342                    throw new IllegalArgumentException(msg);
343                }
344                break;
345    
346            default:
347                throw Util.newInternal("unexpected category " + category);
348            }
349            query.setParameter(param.getName(), String.valueOf(expr.value));
350        }
351    
352        static NumberFormat nf = NumberFormat.getInstance();
353    
354        // this is taken from JPivot
355        public Expr parseParameter(String value) {
356            // is it a String (enclose in double or single quotes ?
357            String trimmed = value.trim();
358            int len = trimmed.length();
359            if (trimmed.charAt(0) == '"' && trimmed.charAt(len - 1) == '"') {
360                debug("parseParameter. STRING_TYPE: " + trimmed);
361                return new Expr(
362                    trimmed.substring(1, trimmed.length() - 1),
363                    Expr.Type.STRING);
364            }
365            if (trimmed.charAt(0) == '\'' && trimmed.charAt(len - 1) == '\'') {
366                debug("parseParameter. STRING_TYPE: " + trimmed);
367                return new Expr(
368                    trimmed.substring(1, trimmed.length() - 1),
369                    Expr.Type.STRING);
370            }
371    
372            // is it a Number ?
373            Number number = null;
374            try {
375                number = nf.parse(trimmed);
376            } catch (ParseException pex) {
377                // nothing to do, should be member
378            }
379            if (number != null) {
380                debug("parseParameter. NUMERIC_TYPE: " + number);
381                return new Expr(number, Expr.Type.NUMERIC);
382            }
383    
384            debug("parseParameter. MEMBER_TYPE: " + trimmed);
385            Query query = this.connection.parseQuery(this.mdxCmd);
386            // dont have to execute
387            //this.connection.execute(query);
388    
389            // assume member, dimension, hierarchy, level
390            OlapElement element = Util.lookup(query, Util.parseIdentifier(trimmed));
391    
392            debug("parseParameter. exp="
393                +((element == null) ? "null" : element.getClass().getName()));
394    
395            if (element instanceof Member) {
396                Member member = (Member) element;
397                return new Expr(member, Expr.Type.MEMBER);
398            } else if (element instanceof mondrian.olap.Level) {
399                mondrian.olap.Level level = (mondrian.olap.Level) element;
400                return new Expr(level, Expr.Type.MEMBER);
401            } else if (element instanceof Hierarchy) {
402                Hierarchy hier = (Hierarchy) element;
403                return new Expr(hier, Expr.Type.MEMBER);
404            } else if (element instanceof Dimension) {
405                Dimension dim = (Dimension) element;
406                return new Expr(dim, Expr.Type.MEMBER);
407            }
408            return null;
409        }
410    
411        public static void listParameterNameValues(StringBuilder buf) {
412            for (Map.Entry<Object, String> e : CmdRunner.paraNameValues.entrySet()) {
413                buf.append(e.getKey());
414                buf.append('=');
415                buf.append(e.getValue());
416                buf.append(nl);
417            }
418        }
419    
420        public static void listParam(String name, StringBuilder buf) {
421            String v = CmdRunner.paraNameValues.get(name);
422            buf.append(v);
423        }
424    
425        public static boolean isParam(String name) {
426            String v = CmdRunner.paraNameValues.get(name);
427            return (v != null);
428        }
429    
430        public static void setParameter(String name, String value) {
431            if (name == null) {
432                CmdRunner.paraNameValues.clear();
433            } else {
434                if (value == null) {
435                    CmdRunner.paraNameValues.remove(name);
436                } else {
437                    CmdRunner.paraNameValues.put(name, value);
438                }
439            }
440        }
441    
442        /////////////////////////////////////////////////////////////////////////
443        //
444        // cubes
445        //
446        public Cube[] getCubes() {
447            Connection conn = getConnection();
448            return conn.getSchemaReader().getCubes();
449        }
450    
451        public Cube getCube(String name) {
452            Cube[] cubes = getCubes();
453            for (Cube cube : cubes) {
454                if (cube.getName().equals(name)) {
455                    return cube;
456                }
457            }
458            return null;
459        }
460    
461        public void listCubeName(StringBuilder buf) {
462            Cube[] cubes = getCubes();
463            for (Cube cube : cubes) {
464                buf.append(cube.getName());
465                buf.append(nl);
466            }
467        }
468    
469        public void listCubeAttribues(String name, StringBuilder buf) {
470            Cube cube = getCube(name);
471            if (cube == null) {
472                buf.append("No cube found with name \"");
473                buf.append(name);
474                buf.append("\"");
475            } else {
476                RolapCube rcube = (RolapCube) cube;
477                buf.append("facttable=");
478                buf.append(rcube.getStar().getFactTable().getAlias());
479                buf.append(nl);
480                buf.append("caching=");
481                buf.append(rcube.isCacheAggregations());
482                buf.append(nl);
483            }
484        }
485    
486        public void executeCubeCommand(
487                String cubename,
488                String command,
489                StringBuilder buf) {
490            Cube cube = getCube(cubename);
491            if (cube == null) {
492                buf.append("No cube found with name \"");
493                buf.append(cubename);
494                buf.append("\"");
495            } else {
496                if (command.equals("clearCache")) {
497                    RolapCube rcube = (RolapCube) cube;
498                    rcube.clearCachedAggregations();
499                } else {
500                    buf.append("For cube \"");
501                    buf.append(cubename);
502                    buf.append("\" there is no command \"");
503                    buf.append(command);
504                    buf.append("\"");
505                }
506            }
507        }
508    
509        public void setCubeAttribute(
510                String cubename,
511                String name,
512                String value,
513                StringBuilder buf) {
514            Cube cube = getCube(cubename);
515            if (cube == null) {
516                buf.append("No cube found with name \"");
517                buf.append(cubename);
518                buf.append("\"");
519            } else {
520                if (name.equals("caching")) {
521                    RolapCube rcube = (RolapCube) cube;
522                    boolean isCache = Boolean.valueOf(value);
523                    rcube.setCacheAggregations(isCache);
524                } else {
525                    buf.append("For cube \"");
526                    buf.append(cubename);
527                    buf.append("\" there is no attribute \"");
528                    buf.append(name);
529                    buf.append("\"");
530                }
531            }
532        }
533        //
534        /////////////////////////////////////////////////////////////////////////
535    
536        /**
537         * Executes a query and returns the result as a string.
538         *
539         * @param queryString MDX query text
540         * @return result String
541         */
542        public String execute(String queryString) {
543            Result result = runQuery(queryString, true);
544            if (this.options.highCardResults) {
545                return highCardToString(result);
546            } else {
547                return toString(result);
548            }
549        }
550    
551        /**
552         * Executes a query and returns the result.
553         *
554         * @param queryString MDX query text
555         * @return a {@link Result} object
556         */
557        public Result runQuery(String queryString, boolean loadParams) {
558            debug("CmdRunner.runQuery: TOP");
559            Result result = null;
560            long start = System.currentTimeMillis();
561            try {
562                this.connection = getConnection();
563                debug("CmdRunner.runQuery: AFTER getConnection");
564                Query query = this.connection.parseQuery(queryString);
565                debug("CmdRunner.runQuery: AFTER parseQuery");
566                if (loadParams) {
567                    loadParameters(query);
568                }
569                start = System.currentTimeMillis();
570                result = this.connection.execute(query);
571            } finally {
572                queryTime = (System.currentTimeMillis() - start);
573                totalQueryTime += queryTime;
574                debug("CmdRunner.runQuery: BOTTOM");
575            }
576            return result;
577        }
578    
579    
580        /**
581         * Converts a {@link Result} object to a string
582         *
583         * @return String version of mondrian Result object.
584         */
585        public String toString(Result result) {
586            StringWriter sw = new StringWriter();
587            PrintWriter pw = new PrintWriter(sw);
588            result.print(pw);
589            pw.flush();
590            return sw.toString();
591        }
592        /**
593         * Converts a {@link Result} object to a string printing to standard
594         * output directly, without buffering.
595         *
596         * @return null String since output is dump directly to stdout.
597         */
598        public String highCardToString(Result result) {
599            result.print(new PrintWriter(System.out, true));
600            return null;
601        }
602    
603    
604        public void makeConnectString() {
605            String connectString = CmdRunner.getConnectStringProperty();
606            debug("CmdRunner.makeConnectString: connectString=" + connectString);
607    
608            Util.PropertyList connectProperties;
609            if (connectString == null || connectString.equals("")) {
610                // create new and add provider
611                connectProperties = new Util.PropertyList();
612                connectProperties.put(RolapConnectionProperties.Provider.name(),"mondrian");
613            } else {
614                // load with existing connect string
615                connectProperties = Util.parseConnectString(connectString);
616            }
617    
618            // override jdbc url
619            String jdbcURL = CmdRunner.getJdbcURLProperty();
620    
621            debug("CmdRunner.makeConnectString: jdbcURL=" + jdbcURL);
622    
623            if (jdbcURL != null) {
624                // add jdbc url to connect string
625                connectProperties.put(RolapConnectionProperties.Jdbc.name(), jdbcURL);
626            }
627    
628            // override jdbc drivers
629            String jdbcDrivers = CmdRunner.getJdbcDriversProperty();
630    
631            debug("CmdRunner.makeConnectString: jdbcDrivers=" + jdbcDrivers);
632            if (jdbcDrivers != null) {
633                // add jdbc drivers to connect string
634                connectProperties.put(RolapConnectionProperties.JdbcDrivers.name(), jdbcDrivers);
635            }
636    
637            // override catalog url
638            String catalogURL = CmdRunner.getCatalogURLProperty();
639    
640            debug("CmdRunner.makeConnectString: catalogURL=" + catalogURL);
641    
642            if (catalogURL != null) {
643                // add catalog url to connect string
644                connectProperties.put(RolapConnectionProperties.Catalog.name(), catalogURL);
645            }
646    
647            // override JDBC user
648            String jdbcUser = CmdRunner.getJdbcUserProperty();
649    
650            debug("CmdRunner.makeConnectString: jdbcUser=" + jdbcUser);
651    
652            if (jdbcUser != null) {
653                // add user to connect string
654                connectProperties.put(RolapConnectionProperties.JdbcUser.name(), jdbcUser);
655            }
656    
657            // override JDBC password
658            String jdbcPassword = CmdRunner.getJdbcPasswordProperty();
659    
660            debug("CmdRunner.makeConnectString: jdbcPassword=" + jdbcPassword);
661    
662            if (jdbcPassword != null) {
663                // add password to connect string
664                connectProperties.put(RolapConnectionProperties.JdbcPassword.name(), jdbcPassword);
665            }
666    
667            if (options.roleName != null) {
668                connectProperties.put(RolapConnectionProperties.Role.name(), options.roleName);
669            }
670    
671            debug("CmdRunner.makeConnectString: connectProperties=" + connectProperties);
672    
673            this.connectString = connectProperties.toString();
674        }
675    
676        /**
677         * Gets a connection to Mondrian.
678         *
679         * @return Mondrian {@link Connection}
680         */
681        public Connection getConnection() {
682            return getConnection(CmdRunner.RELOAD_CONNECTION);
683        }
684    
685        /**
686         * Gets a Mondrian connection, creating a new one if fresh is true.
687         *
688         * @return mondrian Connection.
689         */
690        public synchronized Connection getConnection(boolean fresh) {
691            // FIXME: fresh is currently ignored.
692            if (this.connectString == null) {
693                makeConnectString();
694            }
695            if (this.connection == null) {
696                this.connection =
697                    DriverManager.getConnection(this.connectString, null);
698            }
699            return this.connection;
700        }
701        public String getConnectString() {
702            return getConnectString(CmdRunner.RELOAD_CONNECTION);
703        }
704        public synchronized String getConnectString(boolean fresh) {
705            if (this.connectString == null) {
706                makeConnectString();
707            }
708            return this.connectString;
709        }
710    
711        /////////////////////////////////////////////////////////////////////////
712        /////////////////////////////////////////////////////////////////////////
713        //
714        // static methods
715        //
716        /////////////////////////////////////////////////////////////////////////
717        /////////////////////////////////////////////////////////////////////////
718    
719        protected void debug(String msg) {
720            if (options.debug) {
721                out.println(msg);
722            }
723        }
724    
725        /////////////////////////////////////////////////////////////////////////
726        // properties
727        /////////////////////////////////////////////////////////////////////////
728        protected static String getConnectStringProperty() {
729            return MondrianProperties.instance().TestConnectString.get();
730        }
731        protected static String getJdbcURLProperty() {
732            return MondrianProperties.instance().TestJdbcURL.get();
733        }
734    
735        protected static String getJdbcUserProperty() {
736            return MondrianProperties.instance().TestJdbcUser.get();
737        }
738    
739        protected static String getJdbcPasswordProperty() {
740            return MondrianProperties.instance().TestJdbcPassword.get();
741        }
742        protected static String getCatalogURLProperty() {
743            return MondrianProperties.instance().CatalogURL.get();
744        }
745        protected static String getJdbcDriversProperty() {
746            return MondrianProperties.instance().JdbcDrivers.get();
747        }
748    
749        /////////////////////////////////////////////////////////////////////////
750        // command loop
751        /////////////////////////////////////////////////////////////////////////
752    
753        protected void commandLoop(boolean interactive) throws IOException {
754            commandLoop(
755                    new BufferedReader(
756                            new InputStreamReader(System.in)),
757                    interactive);
758        }
759    
760        protected void commandLoop(File file) throws IOException {
761            // If we open a stream, then we close it.
762            FileReader in = new FileReader(file);
763            try {
764                commandLoop(new BufferedReader(in), false);
765            } finally {
766                try {
767                    in.close();
768                } catch (Exception ex) {
769                    // ignore
770                }
771            }
772        }
773    
774        protected void commandLoop(String mdxCmd, boolean interactive)
775            throws IOException {
776    
777            StringReader is = new StringReader(mdxCmd);
778            commandLoop(is, interactive);
779        }
780    
781        private static final String COMMAND_PROMPT_START = "> ";
782        private static final String COMMAND_PROMPT_MID = "? ";
783    
784        /**
785         * The Command Loop where lines are read from the InputStream and
786         * interpreted. If interactive then prompts are printed.
787         *
788         * @param in Input reader (preferably buffered)
789         * @param interactive Whether the session is interactive
790         */
791        protected void commandLoop(Reader in, boolean interactive) {
792    
793            StringBuilder buf = new StringBuilder(2048);
794            boolean inMdxCmd = false;
795            String resultString = null;
796    
797            for (;;) {
798                if (resultString != null) {
799                    printResults(resultString);
800                    printQueryTime();
801                    resultString = null;
802                    buf.setLength(0);
803                } else if (interactive && (error != null)) {
804                    printResults(error);
805                    printQueryTime();
806                }
807                if (interactive) {
808                    if (inMdxCmd) {
809                        out.print(COMMAND_PROMPT_MID);
810                    } else {
811                        out.print(COMMAND_PROMPT_START);
812                    }
813                    out.flush();
814                }
815                if (!inMdxCmd) {
816                    buf.setLength(0);
817                }
818                String line;
819                try {
820                    line = readLine(in, inMdxCmd);
821                } catch (IOException e) {
822                    throw new RuntimeException(
823                            "Exception while reading command line", e);
824                }
825                if (line != null) {
826                    line = line.trim();
827                }
828                debug("line=" + line);
829    
830                if (! inMdxCmd) {
831                    // If not in the middle of reading an mdx query and
832                    // we reach end of file on the stream, then we are over.
833                    if (line == null) {
834                        return;
835                    }
836                }
837    
838                // If not reading an mdx query, then check if the line is a
839                // user command.
840                if (! inMdxCmd) {
841                    String cmd = line;
842                    if (cmd.startsWith("help")) {
843                        resultString = executeHelp(cmd);
844                    } else if (cmd.startsWith("set")) {
845                        resultString = executeSet(cmd);
846                    } else if (cmd.startsWith("log")) {
847                        resultString = executeLog(cmd);
848                    } else if (cmd.startsWith("file")) {
849                        resultString = executeFile(cmd);
850                    } else if (cmd.startsWith("list")) {
851                        resultString = executeList(cmd);
852                    } else if (cmd.startsWith("func")) {
853                        resultString = executeFunc(cmd);
854                    } else if (cmd.startsWith("param")) {
855                        resultString = executeParam(cmd);
856                    } else if (cmd.startsWith("cube")) {
857                        resultString = executeCube(cmd);
858                    } else if (cmd.startsWith("error")) {
859                        resultString = executeError(cmd);
860                    } else if (cmd.startsWith("echo")) {
861                        resultString = executeEcho(cmd);
862                    } else if (cmd.startsWith("expr")) {
863                        resultString = executeExpr(cmd);
864                    } else if (cmd.equals("=")) {
865                        resultString = reExecuteMdxCmd();
866                    } else if (cmd.startsWith("exit")) {
867                        break;
868                    }
869                    if (resultString != null) {
870                        inMdxCmd = false;
871                        continue;
872                    }
873                }
874    
875                // Are we ready to execute an mdx query.
876                if ((line == null) ||
877                        ((line.length() == 1) &&
878                        ((line.charAt(0) == EXECUTE_CHAR) ||
879                            (line.charAt(0) == CANCEL_CHAR)))) {
880    
881                    // If EXECUTE_CHAR, then execute, otherwise its the
882                    // CANCEL_CHAR and simply empty buffer.
883                    if ((line == null) || (line.charAt(0) == EXECUTE_CHAR)) {
884                        String mdxCmd = buf.toString().trim();
885                        debug("mdxCmd=\"" + mdxCmd + "\"");
886                        resultString = executeMdxCmd(mdxCmd);
887                    }
888    
889                    inMdxCmd = false;
890    
891                } else if (line.length() > 0) {
892    
893                    // OK, just add the line to the mdx query we are building.
894                    inMdxCmd = true;
895    
896                    if (line.endsWith(SEMI_COLON_STRING)) {
897                        // Remove the ';' character.
898                        buf.append(line.substring(0, line.length() - 1));
899                        String mdxCmd = buf.toString().trim();
900                        debug("mdxCmd=\"" + mdxCmd + "\"");
901                        resultString = executeMdxCmd(mdxCmd);
902                        inMdxCmd = false;
903                    } else {
904                        buf.append(line);
905                        // add carriage return so that query keeps formatting
906                        buf.append(nl);
907                    }
908                }
909            }
910        }
911    
912        protected void printResults(String resultString) {
913            if (resultString != null) {
914                resultString = resultString.trim();
915                if (resultString.length() > 0) {
916                    out.println(resultString);
917                    out.flush();
918                }
919            }
920        }
921        protected void printQueryTime() {
922            if (options.timeQueries && (queryTime != -1)) {
923                out.println("time[" + queryTime +  "ms]");
924                out.flush();
925                queryTime = -1;
926            }
927        }
928    
929        /**
930         * Gather up a line ending in '\n' or EOF.
931         * Returns null if at EOF.
932         * Strip out comments. If a comment character appears within a
933         * string then its not a comment. Strings are defined with "\"" or
934         * "'" characters. Also, a string can span more than one line (a
935         * nice little complication). So, if we read a string, then we consume
936         * the whole string as part of the "line" returned,
937         * including EOL characters.
938         * If an escape character is seen '\\', then it and the next character
939         * is added to the line regardless of what the next character is.
940         */
941        protected static String readLine(Reader reader, boolean inMdxCmd)
942            throws IOException {
943    
944            StringBuilder buf = new StringBuilder(128);
945            StringBuilder line = new StringBuilder(128);
946            int offset;
947            int i = getLine(reader, line);
948            boolean inName = false;
949    
950            for (offset = 0; offset < line.length(); offset++) {
951                char c = line.charAt(offset);
952    
953                if (c == ESCAPE_CHAR) {
954                    buf.append(ESCAPE_CHAR);
955                    buf.append(line.charAt(++offset));
956                } else if (!inName &&
957                        ((c == STRING_CHAR_1) || (c == STRING_CHAR_2))) {
958                    i = readString(reader, line, offset, buf, i);
959                    offset = 0;
960                } else {
961                    int commentType=-1;
962    
963                    if (c == BRACKET_START) {
964                        inName = true;
965                    } else if (c == BRACKET_END) {
966                        inName = false;
967                    } else if (! inName) {
968                        // check if we have the start of a comment block
969                        // check if we have the start of a comment block
970                        for (int x = 0; x < commentDelim.length; x++) {
971                            if (c != commentStartChars[x]) {
972                                continue;
973                            }
974                            String startComment = commentDelim[x][0];
975                            boolean foundCommentStart = true;
976                            for (int j = 1;
977                                j + offset < line.length() && j < startComment.length();
978                                    j++) {
979                                if (line.charAt(j + offset) != startComment.charAt(j)) {
980                                    foundCommentStart = false;
981                                }
982                            }
983    
984                            if (foundCommentStart) {
985                                if (x == 0) {
986                                    // A '#' must be the first character on a line
987                                    if (offset == 0) {
988                                        commentType = x;
989                                        break;
990                                    }
991                                } else {
992                                    commentType = x;
993                                    break;
994                                }
995                            }
996                        }
997                    }
998    
999                    // -1 means no comment
1000                    if (commentType == -1) {
1001                        buf.append(c);
1002                    } else {
1003                        // check for comment to end of line comment
1004                        if (commentDelim[commentType][1] == null) {
1005                            break;
1006                        } else {
1007                            // handle delimited comment block
1008                            i = readBlock(reader, line, offset,
1009                                    commentDelim[commentType][0],
1010                                    commentDelim[commentType][1],
1011                                    false, false, buf, i);
1012                            offset = 0;
1013                        }
1014                    }
1015                }
1016            }
1017    
1018            if (i == -1 && buf.length() == 0) {
1019                return null;
1020            } else {
1021                return buf.toString();
1022            }
1023        }
1024    
1025       /**
1026         * Read the next line of input.  Return the terminating character,
1027         * -1 for end of file, or \n or \r.  Add \n and \r to the end of the
1028         * buffer to be included in strings and comment blocks.
1029         */
1030        protected static int getLine(Reader reader, StringBuilder line)
1031            throws IOException {
1032    
1033            line.setLength(0);
1034            for (;;) {
1035                int i = reader.read();
1036    
1037                if (i == -1) {
1038                    return i;
1039                }
1040    
1041                line.append((char)i);
1042    
1043                if (i == '\n' || i == '\r') {
1044                    return i;
1045                }
1046            }
1047        }
1048    
1049        /**
1050         * Start of a string, read all of it even if it spans
1051         * more than one line adding each line's <cr> to the
1052         * buffer.
1053         */
1054        protected static int readString(
1055                Reader reader,
1056                StringBuilder line,
1057                int offset,
1058                StringBuilder buf,
1059                int i)
1060                throws IOException {
1061    
1062            String delim = line.substring(offset, offset + 1);
1063            return readBlock(reader, line, offset, delim, delim, true, true, buf, i);
1064        }
1065    
1066        /**
1067         * Start of a delimted block, read all of it even if it spans
1068         * more than one line adding each line's <cr> to the
1069         * buffer.
1070         *
1071         * A delimited block is a delimited comment (/\* ... *\/), or a string.
1072         */
1073        protected static int readBlock(
1074                Reader reader,
1075                StringBuilder line,
1076                int offset,
1077                final String startDelim,
1078                final String endDelim,
1079                final boolean allowEscape,
1080                final boolean addToBuf,
1081                StringBuilder buf,
1082                int i)
1083                throws IOException {
1084    
1085            int depth = 1;
1086            if (addToBuf) {
1087                buf.append(startDelim);
1088            }
1089            offset += startDelim.length();
1090    
1091            for (;;) {
1092                // see if we are at the end of the block
1093                if (line.substring(offset).startsWith(endDelim)) {
1094                    if (addToBuf) {
1095                        buf.append(endDelim);
1096                    }
1097                    offset += endDelim.length();
1098                    if (--depth == 0) {
1099                         break;
1100                    }
1101                // check for nested block
1102                } else if (allowNestedComments &&
1103                         line.substring(offset).startsWith(startDelim)) {
1104                    if (addToBuf) {
1105                        buf.append(startDelim);
1106                    }
1107                    offset += startDelim.length();
1108                    depth++;
1109                } else if (offset < line.length()) {
1110                    // not at the end of line, so eat the next char
1111                    char c = line.charAt(offset++);
1112                    if (allowEscape && c == ESCAPE_CHAR) {
1113                        if (addToBuf) {
1114                            buf.append(ESCAPE_CHAR);
1115                        }
1116    
1117                        if (offset < line.length()) {
1118                            if (addToBuf) {
1119                                buf.append(line.charAt(offset));
1120                            }
1121                            offset++;
1122                        }
1123                    } else if (addToBuf) {
1124                        buf.append(c);
1125                    }
1126                } else {
1127                    // finished a line; read in the next one and continue
1128                    if (i == -1) {
1129                        break;
1130                    }
1131                    i = getLine(reader, line);
1132    
1133                    // line will always contain EOL marker at least, unless at EOF
1134                    offset = 0;
1135                   if (line.length() == 0) {
1136                        break;
1137                    }
1138                }
1139            }
1140    
1141            // remove to the end of the string, so caller starts at offset 0
1142            if (offset > 0) {
1143                line.delete(0, offset - 1);
1144            }
1145    
1146            return i;
1147        }
1148    
1149        /////////////////////////////////////////////////////////////////////////
1150        // xmla file
1151        /////////////////////////////////////////////////////////////////////////
1152    
1153        /**
1154         * This is called to process a file containing XMLA as the contents
1155         * of SOAP xml.
1156         *
1157         */
1158        protected void processSoapXmla(File file, int validateXmlaResponse)
1159                throws Exception {
1160    
1161    
1162            String catalogURL = CmdRunner.getCatalogURLProperty();
1163            Map<String, String> catalogNameUrls = new HashMap<String, String>();
1164            catalogNameUrls.put(CATALOG_NAME, catalogURL);
1165    
1166            long start = System.currentTimeMillis();
1167    
1168            byte[] bytes = null;
1169            try {
1170                bytes = XmlaSupport.processSoapXmla(file,
1171                            getConnectString(),
1172                            catalogNameUrls,
1173                            null);
1174    
1175            } finally {
1176                queryTime = (System.currentTimeMillis() - start);
1177                totalQueryTime += queryTime;
1178            }
1179    
1180            String response = new String(bytes);
1181            out.println(response);
1182    
1183            switch (validateXmlaResponse) {
1184            case VALIDATE_NONE:
1185                break;
1186            case VALIDATE_TRANSFORM:
1187                XmlaSupport.validateSchemaSoapXmla(bytes);
1188                out.println("XML Data is Valid");
1189                break;
1190            case VALIDATE_XPATH:
1191                XmlaSupport.validateSoapXmlaUsingXpath(bytes);
1192                out.println("XML Data is Valid");
1193                break;
1194            }
1195        }
1196    
1197        /**
1198         * This is called to process a file containing XMLA xml.
1199         *
1200         */
1201        protected void processXmla(File file, int validateXmlaResponce)
1202                throws Exception {
1203    
1204    
1205            String catalogURL = CmdRunner.getCatalogURLProperty();
1206            Map<String, String> catalogNameUrls = new HashMap<String, String>();
1207            catalogNameUrls.put(CATALOG_NAME, catalogURL);
1208    
1209            long start = System.currentTimeMillis();
1210    
1211            byte[] bytes = null;
1212            try {
1213                bytes = XmlaSupport.processXmla(
1214                    file,
1215                    getConnectString(),
1216                    catalogNameUrls);
1217    
1218            } finally {
1219                queryTime = (System.currentTimeMillis() - start);
1220                totalQueryTime += queryTime;
1221            }
1222    
1223            String response = new String(bytes);
1224            out.println(response);
1225    
1226            switch (validateXmlaResponce) {
1227            case VALIDATE_NONE:
1228                break;
1229            case VALIDATE_TRANSFORM:
1230                XmlaSupport.validateSchemaXmla(bytes);
1231                out.println("XML Data is Valid");
1232                break;
1233            case VALIDATE_XPATH:
1234                XmlaSupport.validateXmlaUsingXpath(bytes);
1235                out.println("XML Data is Valid");
1236                break;
1237            }
1238        }
1239    
1240        /////////////////////////////////////////////////////////////////////////
1241        // user commands and help messages
1242        /////////////////////////////////////////////////////////////////////////
1243        private static final String INDENT = "  ";
1244    
1245        private static final int UNKNOWN_CMD        = 0x0000;
1246        private static final int HELP_CMD           = 0x0001;
1247        private static final int SET_CMD            = 0x0002;
1248        private static final int LOG_CMD            = 0x0004;
1249        private static final int FILE_CMD           = 0x0008;
1250        private static final int LIST_CMD           = 0x0010;
1251        private static final int MDX_CMD            = 0x0020;
1252        private static final int FUNC_CMD           = 0x0040;
1253        private static final int PARAM_CMD          = 0x0080;
1254        private static final int CUBE_CMD           = 0x0100;
1255        private static final int ERROR_CMD          = 0x0200;
1256        private static final int ECHO_CMD           = 0x0400;
1257        private static final int EXPR_CMD           = 0x0800;
1258        private static final int EXIT_CMD           = 0x1000;
1259    
1260        private static final int ALL_CMD  = HELP_CMD  |
1261                                            SET_CMD   |
1262                                            LOG_CMD   |
1263                                            FILE_CMD  |
1264                                            LIST_CMD  |
1265                                            MDX_CMD   |
1266                                            FUNC_CMD  |
1267                                            PARAM_CMD |
1268                                            CUBE_CMD  |
1269                                            ERROR_CMD |
1270                                            ECHO_CMD  |
1271                                            EXPR_CMD  |
1272                                            EXIT_CMD;
1273    
1274        private static final char ESCAPE_CHAR         = '\\';
1275        private static final char EXECUTE_CHAR        = '=';
1276        private static final char CANCEL_CHAR         = '~';
1277        private static final char STRING_CHAR_1       = '"';
1278        private static final char STRING_CHAR_2       = '\'';
1279        private static final char BRACKET_START       = '[';
1280        private static final char BRACKET_END         = ']';
1281    
1282        private static final String SEMI_COLON_STRING = ";";
1283    
1284        //////////////////////////////////////////////////////////////////////////
1285        // help
1286        //////////////////////////////////////////////////////////////////////////
1287        protected static String executeHelp(String mdxCmd) {
1288            StringBuilder buf = new StringBuilder(200);
1289    
1290            String[] tokens = mdxCmd.split("\\s+");
1291    
1292            int cmd = UNKNOWN_CMD;
1293    
1294            if (tokens.length == 1) {
1295                buf.append("Commands:");
1296                cmd = ALL_CMD;
1297    
1298            } else if (tokens.length == 2) {
1299                String cmdName = tokens[1];
1300    
1301                if (cmdName.equals("help")) {
1302                    cmd = HELP_CMD;
1303                } else if (cmdName.equals("set")) {
1304                    cmd = SET_CMD;
1305                } else if (cmdName.equals("log")) {
1306                    cmd = LOG_CMD;
1307                } else if (cmdName.equals("file")) {
1308                    cmd = FILE_CMD;
1309                } else if (cmdName.equals("list")) {
1310                    cmd = LIST_CMD;
1311                } else if (cmdName.equals("func")) {
1312                    cmd = FUNC_CMD;
1313                } else if (cmdName.equals("param")) {
1314                    cmd = PARAM_CMD;
1315                } else if (cmdName.equals("cube")) {
1316                    cmd = CUBE_CMD;
1317                } else if (cmdName.equals("error")) {
1318                    cmd = ERROR_CMD;
1319                } else if (cmdName.equals("echo")) {
1320                    cmd = ECHO_CMD;
1321                } else if (cmdName.equals("exit")) {
1322                    cmd = EXIT_CMD;
1323                } else {
1324                    cmd = UNKNOWN_CMD;
1325                }
1326            }
1327    
1328            if (cmd == UNKNOWN_CMD) {
1329                buf.append("Unknown help command: ");
1330                buf.append(mdxCmd);
1331                buf.append(nl);
1332                buf.append("Type \"help\" for list of commands");
1333            }
1334    
1335            if ((cmd & HELP_CMD) != 0) {
1336                // help
1337                buf.append(nl);
1338                appendIndent(buf, 1);
1339                buf.append("help");
1340                buf.append(nl);
1341                appendIndent(buf, 2);
1342                buf.append("Prints this text");
1343            }
1344    
1345            if ((cmd & SET_CMD) != 0) {
1346                // set
1347                buf.append(nl);
1348                appendSet(buf);
1349            }
1350    
1351            if ((cmd & LOG_CMD) != 0) {
1352                // set
1353                buf.append(nl);
1354                appendLog(buf);
1355            }
1356    
1357            if ((cmd & FILE_CMD) != 0) {
1358                // file
1359                buf.append(nl);
1360                appendFile(buf);
1361    
1362            }
1363            if ((cmd & LIST_CMD) != 0) {
1364                // list
1365                buf.append(nl);
1366                appendList(buf);
1367            }
1368    
1369            if ((cmd & MDX_CMD) != 0) {
1370                buf.append(nl);
1371                appendIndent(buf, 1);
1372                buf.append("<mdx query> <cr> ( '");
1373                buf.append(EXECUTE_CHAR);
1374                buf.append("' | '");
1375                buf.append(CANCEL_CHAR);
1376                buf.append("' ) <cr>");
1377                buf.append(nl);
1378                appendIndent(buf, 2);
1379                buf.append("Execute or cancel mdx query.");
1380                buf.append(nl);
1381                appendIndent(buf, 2);
1382                buf.append("An mdx query may span one or more lines.");
1383                buf.append(nl);
1384                appendIndent(buf, 2);
1385                buf.append("After the last line of the query has been entered,");
1386                buf.append(nl);
1387                appendIndent(buf, 3);
1388                buf.append("on the next line a single execute character, '");
1389                buf.append(EXECUTE_CHAR);
1390                buf.append("', may be entered");
1391                buf.append(nl);
1392                appendIndent(buf, 3);
1393                buf.append("followed by a carriage return.");
1394                buf.append(nl);
1395                appendIndent(buf, 3);
1396                buf.append("The lone '");
1397                buf.append(EXECUTE_CHAR);
1398                buf.append("' informs the interpreter that the query has");
1399                buf.append(nl);
1400                appendIndent(buf, 3);
1401                buf.append("has been entered and is ready to execute.");
1402                buf.append(nl);
1403                appendIndent(buf, 2);
1404                buf.append("At anytime during the entry of a query the cancel");
1405                buf.append(nl);
1406                appendIndent(buf, 3);
1407                buf.append("character, '");
1408                buf.append(CANCEL_CHAR);
1409                buf.append("', may be entered alone on a line.");
1410                buf.append(nl);
1411                appendIndent(buf, 3);
1412                buf.append("This removes all of the query text from the");
1413                buf.append(nl);
1414                appendIndent(buf, 3);
1415                buf.append("the command interpreter.");
1416                buf.append(nl);
1417                appendIndent(buf, 2);
1418                buf.append("Queries can also be ended by using a semicolon ';'");
1419                buf.append(nl);
1420                appendIndent(buf, 3);
1421                buf.append("at the end of a line.");
1422            }
1423            if ((cmd & FUNC_CMD) != 0) {
1424                buf.append(nl);
1425                appendFunc(buf);
1426            }
1427    
1428            if ((cmd & PARAM_CMD) != 0) {
1429                buf.append(nl);
1430                appendParam(buf);
1431            }
1432    
1433            if ((cmd & CUBE_CMD) != 0) {
1434                buf.append(nl);
1435                appendCube(buf);
1436            }
1437    
1438            if ((cmd & ERROR_CMD) != 0) {
1439                buf.append(nl);
1440                appendError(buf);
1441            }
1442    
1443            if ((cmd & ECHO_CMD) != 0) {
1444                buf.append(nl);
1445                appendEcho(buf);
1446            }
1447    
1448            if ((cmd & EXPR_CMD) != 0) {
1449                buf.append(nl);
1450                appendExpr(buf);
1451            }
1452    
1453            if (cmd == ALL_CMD) {
1454                // reexecute
1455                buf.append(nl);
1456                appendIndent(buf, 1);
1457                buf.append("= <cr>");
1458                buf.append(nl);
1459                appendIndent(buf, 2);
1460                buf.append("Re-Execute mdx query.");
1461            }
1462    
1463            if ((cmd & EXIT_CMD) != 0) {
1464                // exit
1465                buf.append(nl);
1466                appendExit(buf);
1467            }
1468    
1469    
1470            return buf.toString();
1471        }
1472    
1473        protected static void appendIndent(StringBuilder buf, int i) {
1474            while (i-- > 0) {
1475                buf.append(CmdRunner.INDENT);
1476            }
1477        }
1478    
1479        //////////////////////////////////////////////////////////////////////////
1480        // set
1481        //////////////////////////////////////////////////////////////////////////
1482        protected static void appendSet(StringBuilder buf) {
1483            appendIndent(buf, 1);
1484            buf.append("set [ property[=value ] ] <cr>");
1485            buf.append(nl);
1486            appendIndent(buf, 2);
1487            buf.append("With no args, prints all mondrian properties and values.");
1488            buf.append(nl);
1489            appendIndent(buf, 2);
1490            buf.append("With \"property\" prints property's value.");
1491            buf.append(nl);
1492            appendIndent(buf, 2);
1493            buf.append("With \"property=value\" set property to that value.");
1494        }
1495    
1496        protected String executeSet(String mdxCmd) {
1497            StringBuilder buf = new StringBuilder(400);
1498    
1499            String[] tokens = mdxCmd.split("\\s+");
1500    
1501            if (tokens.length == 1) {
1502                // list all properties
1503                listPropertiesAll(buf);
1504    
1505            } else if (tokens.length == 2) {
1506                String arg = tokens[1];
1507                int index = arg.indexOf('=');
1508                if (index == -1) {
1509                    listProperty(arg, buf);
1510                } else {
1511                    String[] nv = arg.split("=");
1512                    String name = nv[0];
1513                    String value = nv[1];
1514                    if (isProperty(name)) {
1515                        try {
1516                            if (setProperty(name, value)) {
1517                                this.connectString = null;
1518                            }
1519                        } catch (Exception ex) {
1520                            setError(ex);
1521                        }
1522                    } else {
1523                        buf.append("Bad property name:");
1524                        buf.append(name);
1525                        buf.append(nl);
1526                    }
1527                }
1528    
1529            } else {
1530                buf.append("Bad command usage: \"");
1531                buf.append(mdxCmd);
1532                buf.append('"');
1533                buf.append(nl);
1534                appendSet(buf);
1535            }
1536    
1537            return buf.toString();
1538        }
1539    
1540        //////////////////////////////////////////////////////////////////////////
1541        // log
1542        //////////////////////////////////////////////////////////////////////////
1543        protected static void appendLog(StringBuilder buf) {
1544            appendIndent(buf, 1);
1545            buf.append("log [ classname[=level ] ] <cr>");
1546            buf.append(nl);
1547            appendIndent(buf, 2);
1548            buf.append("With no args, prints the current log level of all classes.");
1549            buf.append(nl);
1550            appendIndent(buf, 2);
1551            buf.append("With \"classname\" prints the current log level of the class.");
1552            buf.append(nl);
1553            appendIndent(buf, 2);
1554            buf.append("With \"classname=level\" set log level to new value.");
1555        }
1556    
1557        protected String executeLog(String mdxCmd) {
1558            StringBuilder buf = new StringBuilder(200);
1559    
1560            String[] tokens = mdxCmd.split("\\s+");
1561    
1562            if (tokens.length == 1) {
1563                Enumeration e = LogManager.getCurrentLoggers();
1564                while (e.hasMoreElements()) {
1565                    Logger logger = (Logger) e.nextElement();
1566                    buf.append(logger.getName());
1567                    buf.append(':');
1568                    buf.append(logger.getLevel());
1569                    buf.append(nl);
1570                }
1571    
1572            } else if (tokens.length == 2) {
1573                String arg = tokens[1];
1574                int index = arg.indexOf('=');
1575                if (index == -1) {
1576                    Logger logger = LogManager.exists(arg);
1577                    if (logger == null) {
1578                        buf.append("Bad log name: ");
1579                        buf.append(arg);
1580                        buf.append(nl);
1581                    } else {
1582                        buf.append(logger.getName());
1583                        buf.append(':');
1584                        buf.append(logger.getLevel());
1585                        buf.append(nl);
1586                    }
1587                } else {
1588                    String[] nv = arg.split("=");
1589                    String classname = nv[0];
1590                    String levelStr = nv[1];
1591    
1592                    Logger logger = LogManager.getLogger(classname);
1593    
1594                    if (logger == null) {
1595                        buf.append("Bad log name: ");
1596                        buf.append(classname);
1597                        buf.append(nl);
1598                    } else {
1599                        Level level = Level.toLevel(levelStr, null);
1600                        if (level == null) {
1601                            buf.append("Bad log level: ");
1602                            buf.append(levelStr);
1603                            buf.append(nl);
1604                        } else {
1605                            logger.setLevel(level);
1606                        }
1607                    }
1608                }
1609    
1610            } else {
1611                buf.append("Bad command usage: \"");
1612                buf.append(mdxCmd);
1613                buf.append('"');
1614                buf.append(nl);
1615                appendSet(buf);
1616            }
1617    
1618            return buf.toString();
1619        }
1620    
1621        //////////////////////////////////////////////////////////////////////////
1622        // file
1623        //////////////////////////////////////////////////////////////////////////
1624        protected static void appendFile(StringBuilder buf) {
1625            appendIndent(buf, 1);
1626            buf.append("file [ filename | '=' ] <cr>");
1627            buf.append(nl);
1628            appendIndent(buf, 2);
1629            buf.append("With no args, prints the last filename executed.");
1630            buf.append(nl);
1631            appendIndent(buf, 2);
1632            buf.append("With \"filename\", read and execute filename .");
1633            buf.append(nl);
1634            appendIndent(buf, 2);
1635            buf.append("With \"=\" character, re-read and re-execute previous filename .");
1636        }
1637    
1638        protected String executeFile(String mdxCmd) {
1639            StringBuilder buf = new StringBuilder(512);
1640            String[] tokens = mdxCmd.split("\\s+");
1641    
1642            if (tokens.length == 1) {
1643                if (this.filename != null) {
1644                    buf.append(this.filename);
1645                }
1646    
1647            } else if (tokens.length == 2) {
1648                String token = tokens[1];
1649                String nameOfFile = null;
1650                if ((token.length() == 1) && (token.charAt(0) == EXECUTE_CHAR)) {
1651                    // file '='
1652                    if (this.filename == null) {
1653                        buf.append("Bad command usage: \"");
1654                        buf.append(mdxCmd);
1655                        buf.append("\", no file to re-execute");
1656                        buf.append(nl);
1657                        appendFile(buf);
1658                    } else {
1659                        nameOfFile = this.filename;
1660                    }
1661                } else {
1662                    // file filename
1663                    nameOfFile = token;
1664                }
1665    
1666                if (nameOfFile != null) {
1667                    this.filename = nameOfFile;
1668    
1669                    try {
1670                        commandLoop(new File(this.filename));
1671                    } catch (IOException ex) {
1672                        setError(ex);
1673                        buf.append("Error: ").append(ex);
1674                    }
1675                }
1676    
1677            } else {
1678                buf.append("Bad command usage: \"");
1679                buf.append(mdxCmd);
1680                buf.append('"');
1681                buf.append(nl);
1682                appendFile(buf);
1683            }
1684            return buf.toString();
1685        }
1686    
1687        //////////////////////////////////////////////////////////////////////////
1688        // list
1689        //////////////////////////////////////////////////////////////////////////
1690        protected static void appendList(StringBuilder buf) {
1691            appendIndent(buf, 1);
1692            buf.append("list [ cmd | result ] <cr>");
1693            buf.append(nl);
1694            appendIndent(buf, 2);
1695            buf.append("With no arguments, list previous cmd and result");
1696            buf.append(nl);
1697            appendIndent(buf, 2);
1698            buf.append("With \"cmd\" argument, list the last mdx query cmd.");
1699            buf.append(nl);
1700            appendIndent(buf, 2);
1701            buf.append("With \"result\" argument, list the last mdx query result.");
1702        }
1703    
1704        protected String executeList(String mdxCmd) {
1705            StringBuilder buf = new StringBuilder(200);
1706    
1707            String[] tokens = mdxCmd.split("\\s+");
1708    
1709            if (tokens.length == 1) {
1710                if (this.mdxCmd != null) {
1711                    buf.append(this.mdxCmd);
1712                    if (mdxResult != null) {
1713                        buf.append(nl);
1714                        buf.append(mdxResult);
1715                    }
1716                } else if (mdxResult != null) {
1717                    buf.append(mdxResult);
1718                }
1719    
1720            } else if (tokens.length == 2) {
1721                String arg = tokens[1];
1722                if (arg.equals("cmd")) {
1723                    if (this.mdxCmd != null) {
1724                        buf.append(this.mdxCmd);
1725                    }
1726                } else if (arg.equals("result")) {
1727                    if (mdxResult != null) {
1728                        buf.append(mdxResult);
1729                    }
1730                } else {
1731                    buf.append("Bad sub command usage:");
1732                    buf.append(mdxCmd);
1733                    buf.append(nl);
1734                    appendList(buf);
1735                }
1736            } else {
1737                buf.append("Bad command usage: \"");
1738                buf.append(mdxCmd);
1739                buf.append('"');
1740                buf.append(nl);
1741                appendList(buf);
1742            }
1743    
1744            return buf.toString();
1745        }
1746    
1747        //////////////////////////////////////////////////////////////////////////
1748        // func
1749        //////////////////////////////////////////////////////////////////////////
1750        protected static void appendFunc(StringBuilder buf) {
1751            appendIndent(buf, 1);
1752            buf.append("func [ name ] <cr>");
1753            buf.append(nl);
1754            appendIndent(buf, 2);
1755            buf.append("With no arguments, list all defined function names");
1756            buf.append(nl);
1757            appendIndent(buf, 2);
1758            buf.append("With \"name\" argument, display the functions:");
1759            buf.append(nl);
1760            appendIndent(buf, 3);
1761            buf.append("name, description, and syntax");
1762        }
1763        protected String executeFunc(String mdxCmd) {
1764            StringBuilder buf = new StringBuilder(200);
1765    
1766            String[] tokens = mdxCmd.split("\\s+");
1767    
1768            final FunTable funTable = getConnection().getSchema().getFunTable();
1769            if (tokens.length == 1) {
1770                // prints names only once
1771                List<FunInfo> funInfoList = funTable.getFunInfoList();
1772                Iterator<FunInfo> it = funInfoList.iterator();
1773                String prevName = null;
1774                while (it.hasNext()) {
1775                    FunInfo fi = it.next();
1776                    String name = fi.getName();
1777                    if (prevName == null || ! prevName.equals(name)) {
1778                        buf.append(name);
1779                        buf.append(nl);
1780                        prevName = name;
1781                    }
1782                }
1783    
1784            } else if (tokens.length == 2) {
1785                String funcname = tokens[1];
1786                List<FunInfo> funInfoList = funTable.getFunInfoList();
1787                List<FunInfo> matches = new ArrayList<FunInfo>();
1788    
1789                for (FunInfo fi : funInfoList) {
1790                    if (fi.getName().equalsIgnoreCase(funcname)) {
1791                        matches.add(fi);
1792                    }
1793                }
1794    
1795                if (matches.size() == 0) {
1796                    buf.append("Bad function name \"");
1797                    buf.append(funcname);
1798                    buf.append("\", usage:");
1799                    buf.append(nl);
1800                    appendList(buf);
1801                } else {
1802                    Iterator<FunInfo> it = matches.iterator();
1803                    boolean doname = true;
1804                    while (it.hasNext()) {
1805                        FunInfo fi = it.next();
1806                        if (doname) {
1807                            buf.append(fi.getName());
1808                            buf.append(nl);
1809                            doname = false;
1810                        }
1811    
1812                        appendIndent(buf, 1);
1813                        buf.append(fi.getDescription());
1814                        buf.append(nl);
1815    
1816                        String[] sigs = fi.getSignatures();
1817                        if (sigs == null) {
1818                            appendIndent(buf, 2);
1819                            buf.append("Signature: ");
1820                            buf.append("NONE");
1821                            buf.append(nl);
1822                        } else {
1823                            for (String sig : sigs) {
1824                                appendIndent(buf, 2);
1825                                buf.append(sig);
1826                                buf.append(nl);
1827                            }
1828                        }
1829    /*
1830                        appendIndent(buf, 1);
1831                        buf.append("Return Type: ");
1832                        int returnType = fi.getReturnTypes();
1833                        if (returnType >= 0) {
1834                            buf.append(cat.getName(returnType));
1835                        } else {
1836                            buf.append("NONE");
1837                        }
1838                        buf.append(nl);
1839                        int[][] paramsArray = fi.getParameterTypes();
1840                        if (paramsArray == null) {
1841                            appendIndent(buf, 1);
1842                            buf.append("Paramter Types: ");
1843                            buf.append("NONE");
1844                            buf.append(nl);
1845    
1846                        } else {
1847    
1848                            for (int j = 0; j < paramsArray.length; j++) {
1849                                int[] params = paramsArray[j];
1850                                appendIndent(buf, 1);
1851                                buf.append("Paramter Types: ");
1852                                for (int k = 0; k < params.length; k++) {
1853                                    int param = params[k];
1854                                    buf.append(cat.getName(param));
1855                                    buf.append(' ');
1856                                }
1857                                buf.append(nl);
1858                            }
1859                        }
1860    */
1861                    }
1862                }
1863            } else {
1864                buf.append("Bad command usage: \"");
1865                buf.append(mdxCmd);
1866                buf.append('"');
1867                buf.append(nl);
1868                appendList(buf);
1869            }
1870    
1871            return buf.toString();
1872        }
1873        //////////////////////////////////////////////////////////////////////////
1874        // param
1875        //////////////////////////////////////////////////////////////////////////
1876        protected static void appendParam(StringBuilder buf) {
1877            appendIndent(buf, 1);
1878            buf.append("param [ name[=value ] ] <cr>");
1879            buf.append(nl);
1880            appendIndent(buf, 2);
1881            buf.append("With no argumnts, all param name/value pairs are printed.");
1882            buf.append(nl);
1883            appendIndent(buf, 2);
1884            buf.append("With \"name\" argument, the value of the param is printed.");
1885            buf.append(nl);
1886            appendIndent(buf, 2);
1887            buf.append("With \"name=value\" sets the parameter with name to value.");
1888            buf.append(nl);
1889            appendIndent(buf, 3);
1890            buf.append(" If name is null, then unsets all parameters");
1891            buf.append(nl);
1892            appendIndent(buf, 3);
1893            buf.append(" If value is null, then unsets the parameter associated with value");
1894        }
1895        protected String executeParam(String mdxCmd) {
1896            StringBuilder buf = new StringBuilder(200);
1897    
1898            String[] tokens = mdxCmd.split("\\s+");
1899    
1900            if (tokens.length == 1) {
1901                // list all properties
1902                listParameterNameValues(buf);
1903    
1904            } else if (tokens.length == 2) {
1905                String arg = tokens[1];
1906                int index = arg.indexOf('=');
1907                if (index == -1) {
1908                    if (isParam(arg)) {
1909                        listParam(arg, buf);
1910                    } else {
1911                        buf.append("Bad parameter name:");
1912                        buf.append(arg);
1913                        buf.append(nl);
1914                    }
1915                } else {
1916                    String[] nv = arg.split("=");
1917                    String name = (nv.length == 0) ? null : nv[0];
1918                    String value = (nv.length == 2) ? nv[1] : null;
1919                    setParameter(name, value);
1920                }
1921    
1922            } else {
1923                buf.append("Bad command usage: \"");
1924                buf.append(mdxCmd);
1925                buf.append('"');
1926                buf.append(nl);
1927                appendSet(buf);
1928            }
1929    
1930            return buf.toString();
1931        }
1932        //////////////////////////////////////////////////////////////////////////
1933        // cube
1934        //////////////////////////////////////////////////////////////////////////
1935        protected static void appendCube(StringBuilder buf) {
1936            appendIndent(buf, 1);
1937            buf.append("cube [ cubename [ name [=value | command] ] ] <cr>");
1938            buf.append(nl);
1939            appendIndent(buf, 2);
1940            buf.append("With no argumnts, all cubes are listed by name.");
1941            buf.append(nl);
1942            appendIndent(buf, 2);
1943            buf.append("With \"cubename\" argument, cube attribute name/values for:");
1944            buf.append(nl);
1945            appendIndent(buf, 3);
1946            buf.append("fact table (readonly)");
1947            buf.append(nl);
1948            appendIndent(buf, 3);
1949            buf.append("aggregate caching (readwrite)");
1950            buf.append(nl);
1951            appendIndent(buf, 2);
1952            buf.append("are printed");
1953            buf.append(nl);
1954            appendIndent(buf, 2);
1955            buf.append("With \"cubename name=value\" sets the readwrite attribute with name to value.");
1956            buf.append(nl);
1957            appendIndent(buf, 2);
1958            buf.append("With \"cubename command\" executes the commands:");
1959            buf.append(nl);
1960            appendIndent(buf, 3);
1961            buf.append("clearCache");
1962        }
1963    
1964        protected String executeCube(String mdxCmd) {
1965            StringBuilder buf = new StringBuilder(200);
1966    
1967            String[] tokens = mdxCmd.split("\\s+");
1968    
1969            if (tokens.length == 1) {
1970                // list all properties
1971                listCubeName(buf);
1972            } else if (tokens.length == 2) {
1973                String cubename = tokens[1];
1974                listCubeAttribues(cubename, buf);
1975    
1976            } else if (tokens.length == 3) {
1977                String cubename = tokens[1];
1978                String arg = tokens[2];
1979                int index = arg.indexOf('=');
1980                if (index == -1) {
1981                    // its a commnd
1982                    executeCubeCommand(cubename, arg, buf);
1983                } else {
1984                    String[] nv = arg.split("=");
1985                    String name = (nv.length == 0) ? null : nv[0];
1986                    String value = (nv.length == 2) ? nv[1] : null;
1987                    setCubeAttribute(cubename, name, value, buf);
1988                }
1989    
1990            } else {
1991                buf.append("Bad command usage: \"");
1992                buf.append(mdxCmd);
1993                buf.append('"');
1994                buf.append(nl);
1995                appendSet(buf);
1996            }
1997    
1998            return buf.toString();
1999        }
2000        //////////////////////////////////////////////////////////////////////////
2001        // error
2002        //////////////////////////////////////////////////////////////////////////
2003        protected static void appendError(StringBuilder buf) {
2004            appendIndent(buf, 1);
2005            buf.append("error [ msg | stack ] <cr>");
2006            buf.append(nl);
2007            appendIndent(buf, 2);
2008            buf.append("With no argumnts, both message and stack are printed.");
2009            buf.append(nl);
2010            appendIndent(buf, 2);
2011            buf.append("With \"msg\" argument, the Error message is printed.");
2012            buf.append(nl);
2013            appendIndent(buf, 2);
2014            buf.append("With \"stack\" argument, the Error stack trace is printed.");
2015        }
2016    
2017        protected String executeError(String mdxCmd) {
2018            StringBuilder buf = new StringBuilder(200);
2019    
2020            String[] tokens = mdxCmd.split("\\s+");
2021    
2022            if (tokens.length == 1) {
2023                if (error != null) {
2024                    buf.append(error);
2025                    if (stack != null) {
2026                        buf.append(nl);
2027                        buf.append(stack);
2028                    }
2029                } else if (stack != null) {
2030                    buf.append(stack);
2031                }
2032    
2033            } else if (tokens.length == 2) {
2034                String arg = tokens[1];
2035                if (arg.equals("msg")) {
2036                    if (error != null) {
2037                        buf.append(error);
2038                    }
2039                } else if (arg.equals("stack")) {
2040                    if (stack != null) {
2041                        buf.append(stack);
2042                    }
2043                } else {
2044                    buf.append("Bad sub command usage:");
2045                    buf.append(mdxCmd);
2046                    buf.append(nl);
2047                    appendList(buf);
2048                }
2049            } else {
2050                buf.append("Bad command usage: \"");
2051                buf.append(mdxCmd);
2052                buf.append('"');
2053                buf.append(nl);
2054                appendList(buf);
2055            }
2056    
2057            return buf.toString();
2058        }
2059        //////////////////////////////////////////////////////////////////////////
2060        // echo
2061        //////////////////////////////////////////////////////////////////////////
2062        protected static void appendEcho(StringBuilder buf) {
2063            appendIndent(buf, 1);
2064            buf.append("echo text <cr>");
2065            buf.append(nl);
2066            appendIndent(buf, 2);
2067            buf.append("echo text to standard out.");
2068        }
2069        protected String executeEcho(String mdxCmd) {
2070    
2071            try {
2072                String resultString = (mdxCmd.length() == 4)
2073                    ? "" : mdxCmd.substring(4);
2074                return resultString;
2075    
2076            } catch (Exception ex) {
2077                setError(ex);
2078                //return error;
2079                return null;
2080            }
2081        }
2082        //////////////////////////////////////////////////////////////////////////
2083        // expr
2084        //////////////////////////////////////////////////////////////////////////
2085        protected static void appendExpr(StringBuilder buf) {
2086            appendIndent(buf, 1);
2087            buf.append("expr cubename expression<cr>");
2088            buf.append(nl);
2089            appendIndent(buf, 2);
2090            buf.append("evaluate an expression against a cube.");
2091            buf.append(nl);
2092            appendIndent(buf, 2);
2093            buf.append("where: ");
2094            buf.append(nl);
2095            appendIndent(buf, 3);
2096            buf.append("cubename is single word or string using [], '' or \"\"");
2097            buf.append(nl);
2098            appendIndent(buf, 3);
2099            buf.append("expression is string using \"\"");
2100        }
2101        protected String executeExpr(String mdxCmd) {
2102            StringBuilder buf = new StringBuilder(256);
2103    
2104            mdxCmd = (mdxCmd.length() == 5)
2105                    ? "" : mdxCmd.substring(5);
2106    
2107            String regex = "(\"[^\"]+\"|'[^\']+'|\\[[^\\]]+\\]|[^\\s]+)\\s+.*";
2108            Pattern p = Pattern.compile(regex);
2109            Matcher m = p.matcher(mdxCmd);
2110            boolean b = m.matches();
2111    
2112            if (! b) {
2113                buf.append("Could not parse into \"cubename expression\" command:");
2114                buf.append(nl);
2115                buf.append(mdxCmd);
2116                String msg = buf.toString();
2117                setError(msg);
2118                return msg;
2119            } else {
2120    
2121                String cubeName = m.group(1);
2122                String expression = mdxCmd.substring(cubeName.length() + 1);
2123    
2124                if (cubeName.charAt(0) == '"') {
2125                    cubeName = cubeName.substring(1, cubeName.length() - 1);
2126                } else if (cubeName.charAt(0) == '\'') {
2127                    cubeName = cubeName.substring(1, cubeName.length() - 1);
2128                } else if (cubeName.charAt(0) == '[') {
2129                    cubeName = cubeName.substring(1, cubeName.length() - 1);
2130                }
2131    
2132                int len = expression.length();
2133                if (expression.charAt(0) == '"') {
2134                    if (expression.charAt(len - 1) != '"') {
2135                        buf.append("Missing end '\"' in expression:");
2136                        buf.append(nl);
2137                        buf.append(expression);
2138                        String msg = buf.toString();
2139                        setError(msg);
2140                        return msg;
2141                    }
2142                    expression = expression.substring(1, len - 1);
2143    
2144                } else if (expression.charAt(0) == '\'') {
2145                    if (expression.charAt(len - 1) != '\'') {
2146                        buf.append("Missing end \"'\" in expression:");
2147                        buf.append(nl);
2148                        buf.append(expression);
2149                        String msg = buf.toString();
2150                        setError(msg);
2151                        return msg;
2152                    }
2153                    expression = expression.substring(1, len - 1);
2154                }
2155    
2156                Cube cube = getCube(cubeName);
2157                if (cube == null) {
2158                    buf.append("No cube found with name \"");
2159                    buf.append(cubeName);
2160                    buf.append("\"");
2161                    String msg = buf.toString();
2162                    setError(msg);
2163                    return msg;
2164    
2165                } else {
2166                    try {
2167                        if (cubeName.indexOf(' ') >= 0) {
2168                            if (cubeName.charAt(0) != '[') {
2169                                cubeName = Util.quoteMdxIdentifier(cubeName);
2170                            }
2171                        }
2172                        final char c = '\'';
2173                        if (expression.indexOf('\'') != -1) {
2174                            // make sure all "'" are escaped
2175                            int start = 0;
2176                            int index = expression.indexOf('\'', start);
2177                            if (index == 0) {
2178                                // error: starts with "'"
2179                                buf.append("Double \"''\" starting expression:");
2180                                buf.append(nl);
2181                                buf.append(expression);
2182                                String msg = buf.toString();
2183                                setError(msg);
2184                                return msg;
2185                            }
2186                            while (index != -1) {
2187                                if (expression.charAt(index - 1) != '\\') {
2188                                    // error
2189                                    buf.append("Non-escaped \"'\" in expression:");
2190                                    buf.append(nl);
2191                                    buf.append(expression);
2192                                    String msg = buf.toString();
2193                                    setError(msg);
2194                                    return msg;
2195                                }
2196                                start = index + 1;
2197                                index = expression.indexOf('\'', start);
2198                            }
2199                        }
2200    
2201                        // taken from FoodMartTest code
2202                        StringBuilder queryStringBuf = new StringBuilder(64);
2203                        queryStringBuf.append("with member [Measures].[Foo] as ");
2204                        queryStringBuf.append(c);
2205                        queryStringBuf.append(expression);
2206                        queryStringBuf.append(c);
2207                        queryStringBuf.append(" select {[Measures].[Foo]} on columns from ");
2208                        queryStringBuf.append(cubeName);
2209    
2210                        String queryString = queryStringBuf.toString();
2211    
2212                        Result result = runQuery(queryString, true);
2213                        String resultString =
2214                            result.getCell(new int[]{0}).getFormattedValue();
2215                        mdxResult = resultString;
2216                        clearError();
2217    
2218                        buf.append(resultString);
2219    
2220                    } catch (Exception ex) {
2221                        setError(ex);
2222                        buf.append("Error: ").append(ex);
2223                    }
2224                }
2225            }
2226            return buf.toString();
2227        }
2228        //////////////////////////////////////////////////////////////////////////
2229        // exit
2230        //////////////////////////////////////////////////////////////////////////
2231        protected static void appendExit(StringBuilder buf) {
2232            appendIndent(buf, 1);
2233            buf.append("exit <cr>");
2234            buf.append(nl);
2235            appendIndent(buf, 2);
2236            buf.append("Exit mdx command interpreter.");
2237        }
2238    
2239    
2240        protected String reExecuteMdxCmd() {
2241            if (this.mdxCmd == null) {
2242                return "No command to execute";
2243            } else {
2244                return executeMdxCmd(this.mdxCmd);
2245            }
2246        }
2247    
2248        protected String executeMdxCmd(String mdxCmd) {
2249    
2250            this.mdxCmd = mdxCmd;
2251            try {
2252    
2253                String resultString = execute(mdxCmd);
2254                mdxResult = resultString;
2255                clearError();
2256                return resultString;
2257    
2258            } catch (Exception ex) {
2259                setError(ex);
2260                //return error;
2261                return null;
2262            }
2263        }
2264    
2265        /////////////////////////////////////////////////////////////////////////
2266        // helpers
2267        /////////////////////////////////////////////////////////////////////////
2268        protected static void loadPropertiesFromFile(String propFile)
2269                    throws IOException {
2270    
2271            MondrianProperties.instance().load(new FileInputStream(propFile));
2272        }
2273    
2274        /////////////////////////////////////////////////////////////////////////
2275        // main
2276        /////////////////////////////////////////////////////////////////////////
2277    
2278        /**
2279         * Prints a usage message.
2280         *
2281         * @param msg Prefix to the message
2282         * @param out Output stream
2283         */
2284        protected static void usage(String msg, PrintStream out) {
2285            StringBuilder buf = new StringBuilder(256);
2286            if (msg != null) {
2287                buf.append(msg);
2288                buf.append(nl);
2289            }
2290            buf.append("Usage: mondrian.tui.CmdRunner args");
2291            buf.append(nl);
2292            buf.append("  args:");
2293            buf.append(nl);
2294            buf.append("  -h               : print this usage text");
2295            buf.append(nl);
2296            buf.append("  -H               : ready to print out high cardinality");
2297            buf.append(nl);
2298            buf.append("                     dimensions");
2299            buf.append(nl);
2300            buf.append("  -d               : enable local debugging");
2301            buf.append(nl);
2302            buf.append("  -t               : time each mdx query");
2303            buf.append(nl);
2304            buf.append("  -nocache         : turn off in-memory aggregate caching");
2305            buf.append(nl);
2306            buf.append("                     for all cubes regardless of setting");
2307            buf.append(nl);
2308            buf.append("                     in schema");
2309            buf.append(nl);
2310            buf.append("  -rc              : do NOT reload connections each query");
2311            buf.append(nl);
2312            buf.append("                     (default is to reload connections)");
2313            buf.append(nl);
2314            buf.append("  -p propertyfile  : load mondrian properties");
2315            buf.append(nl);
2316            buf.append("  -r role_name     : set the connections role name");
2317            buf.append(nl);
2318            buf.append("  -f mdx_filename+ : execute mdx in one or more files");
2319            buf.append(nl);
2320            buf.append("  -x xmla_filename+: execute XMLA in one or more files");
2321            buf.append("                     the XMLA request has no SOAP wrapper");
2322            buf.append(nl);
2323            buf.append("  -xs soap_xmla_filename+ ");
2324            buf.append("                   : execute Soap XMLA in one or more files");
2325            buf.append("                     the XMLA request has a SOAP wrapper");
2326            buf.append(nl);
2327            buf.append("  -vt              : validate xmla response using transforms");
2328            buf.append("                     only used with -x or -xs flags");
2329            buf.append(nl);
2330            buf.append("  -vx              : validate xmla response using xpaths");
2331            buf.append("                     only used with -x or -xs flags");
2332            buf.append(nl);
2333            buf.append("  mdx_cmd          : execute mdx_cmd");
2334            buf.append(nl);
2335    
2336            out.println(buf.toString());
2337        }
2338    
2339        /**
2340         * Set the default comment delimiters for CmdRunner.  These defaults are
2341         * # to end of line
2342         * plus all the comment delimiters in Scanner.
2343         */
2344        private static void setDefaultCommentState() {
2345            allowNestedComments = mondrian.olap.Scanner.getNestedCommentsState();
2346            String[][] scannerCommentsDelimiters = mondrian.olap.Scanner.getCommentDelimiters();
2347            commentDelim = new String[scannerCommentsDelimiters.length + 1][2];
2348            commentStartChars = new char[scannerCommentsDelimiters.length + 1];
2349    
2350    
2351            // CmdRunner has extra delimiter; # to end of line
2352            commentDelim[0][0] = "#";
2353            commentDelim[0][1] = null;
2354            commentStartChars[0] = commentDelim[0][0].charAt(0);
2355    
2356    
2357            // copy all the rest of the delimiters
2358            for (int x = 0; x < scannerCommentsDelimiters.length; x++) {
2359                commentDelim[x + 1][0] = scannerCommentsDelimiters[x][0];
2360                commentDelim[x + 1][1] = scannerCommentsDelimiters[x][1];
2361                commentStartChars[x + 1] = commentDelim[x + 1][0].charAt(0);
2362            }
2363        }
2364    
2365        private static final int DO_MDX             = 1;
2366        private static final int DO_XMLA            = 2;
2367        private static final int DO_SOAP_XMLA       = 3;
2368    
2369        private static final int VALIDATE_NONE      = 1;
2370        private static final int VALIDATE_TRANSFORM = 2;
2371        private static final int VALIDATE_XPATH     = 3;
2372    
2373        protected static class Options {
2374            private boolean debug = false;
2375            private boolean timeQueries;
2376            private boolean noCache = false;
2377            private String roleName;
2378            private int validateXmlaResponse = VALIDATE_NONE;
2379            private final List<String> filenames = new ArrayList<String>();
2380            private int doingWhat = DO_MDX;
2381            private String singleMdxCmd;
2382            private boolean highCardResults;
2383        }
2384    
2385        public static void main(String[] args) throws Exception {
2386    
2387            Options options;
2388            try {
2389                options = parseOptions(args);
2390            } catch (BadOption badOption) {
2391                usage(badOption.getMessage(), System.out);
2392                Throwable t = badOption.getCause();
2393                if (t != null) {
2394                    System.out.println(t);
2395                    t.printStackTrace();
2396                }
2397                return;
2398            }
2399    
2400            CmdRunner cmdRunner =
2401                    new CmdRunner(options, new PrintWriter(System.out));
2402            if (options.noCache) {
2403                cmdRunner.noCubeCaching();
2404            }
2405    
2406            if (!options.filenames.isEmpty()) {
2407                for (String filename : options.filenames) {
2408                    cmdRunner.filename = filename;
2409                    switch (options.doingWhat) {
2410                    case DO_MDX:
2411                        // its a file containing mdx
2412                        cmdRunner.commandLoop(new File(filename));
2413                        break;
2414                    case DO_XMLA:
2415                        // its a file containing XMLA
2416                        cmdRunner.processXmla(
2417                            new File(filename),
2418                            options.validateXmlaResponse);
2419                        break;
2420                    default:
2421                        // its a file containing SOAP XMLA
2422                        cmdRunner.processSoapXmla(
2423                            new File(filename),
2424                            options.validateXmlaResponse);
2425                        break;
2426                    }
2427                    if (cmdRunner.error != null) {
2428                        System.err.println(filename);
2429                        System.err.println(cmdRunner.error);
2430                        if (cmdRunner.stack != null) {
2431                            System.err.println(cmdRunner.stack);
2432                        }
2433                        cmdRunner.printQueryTime();
2434                        cmdRunner.clearError();
2435                    }
2436                }
2437            } else if (options.singleMdxCmd != null) {
2438                cmdRunner.commandLoop(options.singleMdxCmd, false);
2439                if (cmdRunner.error != null) {
2440                    System.err.println(cmdRunner.error);
2441                    if (cmdRunner.stack != null) {
2442                        System.err.println(cmdRunner.stack);
2443                    }
2444                }
2445            } else {
2446                cmdRunner.commandLoop(true);
2447            }
2448            cmdRunner.printTotalQueryTime();
2449        }
2450    
2451        private void printTotalQueryTime() {
2452            if (options.timeQueries) {
2453                // only print if different
2454                if (totalQueryTime != queryTime) {
2455                    out.println("total[" + totalQueryTime + "ms]");
2456                }
2457            }
2458            out.flush();
2459        }
2460    
2461        private static Options parseOptions(String[] args)
2462                throws BadOption, IOException {
2463            final Options options = new Options();
2464            for (int i = 0; i < args.length; i++) {
2465                String arg = args[i];
2466    
2467                if (arg.equals("-h")) {
2468                    throw new BadOption(null);
2469                } else if (arg.equals("-H")) {
2470                    options.highCardResults = true;
2471    
2472                } else if (arg.equals("-d")) {
2473                    options.debug = true;
2474    
2475                } else if (arg.equals("-t")) {
2476                    options.timeQueries = true;
2477    
2478                } else if (arg.equals("-nocache")) {
2479                    options.noCache = true;
2480    
2481                } else if (arg.equals("-rc")) {
2482                    CmdRunner.RELOAD_CONNECTION = false;
2483    
2484                } else if (arg.equals("-vt")) {
2485                    options.validateXmlaResponse = VALIDATE_TRANSFORM;
2486    
2487                } else if (arg.equals("-vx")) {
2488                    options.validateXmlaResponse = VALIDATE_XPATH;
2489    
2490                } else if (arg.equals("-f")) {
2491                    i++;
2492                    if (i == args.length) {
2493                        throw new BadOption("no mdx filename given");
2494                    }
2495                    options.filenames.add(args[i]);
2496    
2497                } else if (arg.equals("-x")) {
2498                    i++;
2499                    if (i == args.length) {
2500                        throw new BadOption("no XMLA filename given");
2501                    }
2502                    options.doingWhat = DO_XMLA;
2503                    options.filenames.add(args[i]);
2504    
2505                } else if (arg.equals("-xs")) {
2506                    i++;
2507                    if (i == args.length) {
2508                        throw new BadOption("no XMLA filename given");
2509                    }
2510                    options.doingWhat = DO_SOAP_XMLA;
2511                    options.filenames.add(args[i]);
2512    
2513                } else if (arg.equals("-p")) {
2514                    i++;
2515                    if (i == args.length) {
2516                        throw new BadOption("no mondrian properties file given");
2517                    }
2518                    String propFile = args[i];
2519                    loadPropertiesFromFile(propFile);
2520    
2521                } else if (arg.equals("-r")) {
2522                    i++;
2523                    if (i == args.length) {
2524                        throw new BadOption("no role name given");
2525                    }
2526                    options.roleName = args[i];
2527                } else if (!options.filenames.isEmpty()) {
2528                    options.filenames.add(arg);
2529                } else {
2530                    options.singleMdxCmd = arg;
2531                }
2532            }
2533            return options;
2534        }
2535    
2536        private static class BadOption extends Exception {
2537            BadOption(String msg) {
2538                super(msg);
2539            }
2540            BadOption(String msg, Exception ex) {
2541                super(msg, ex);
2542            }
2543        }
2544    }
2545    
2546    // End CmdRunner.java
2547