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