001    /*
002    // $Id: //open/mondrian/src/main/mondrian/xmla/impl/DefaultXmlaRequest.java#11 $
003    // This software is subject to the terms of the Common Public License
004    // Agreement, available at the following URL:
005    // http://www.opensource.org/licenses/cpl.html.
006    // Copyright (C) 2005-2008 Julian Hyde
007    // All Rights Reserved.
008    // You must accept the terms of that agreement to use this software.
009    */
010    package mondrian.xmla.impl;
011    
012    import java.util.*;
013    
014    import mondrian.olap.MondrianProperties;
015    import mondrian.olap.Util;
016    import mondrian.olap.Role;
017    import mondrian.xmla.XmlaConstants;
018    import mondrian.xmla.XmlaException;
019    import mondrian.xmla.XmlaRequest;
020    import mondrian.xmla.XmlaUtil;
021    
022    import org.w3c.dom.Element;
023    import org.w3c.dom.Node;
024    import org.w3c.dom.NodeList;
025    import org.apache.log4j.Logger;
026    
027    /**
028     * Default implementation of {@link mondrian.xmla.XmlaRequest} by DOM API.
029     *
030     * @author Gang Chen
031     */
032    public class DefaultXmlaRequest implements XmlaRequest,
033                                               XmlaConstants {
034    
035        private static final Logger LOGGER = Logger.getLogger(DefaultXmlaRequest.class);
036    
037        private static final String MSG_INVALID_XMLA = "Invalid XML/A message";
038        private static final String MSG_INVALID_DRILLTHROUGH = "Invalid DRILLTHROUGH statement";
039        private static final String MSG_INVALID_MAXROWS = "MAXROWS is not positive integer";
040        private static final String MSG_INVALID_FIRSTROWSET = "FIRSTROWSET isn't positive integer";
041    
042        /* common content */
043        private int method;
044        private Map<String, String> properties;
045        private final String roleName;
046        private final Role role;
047    
048        /* EXECUTE content */
049        private String statement;
050        private boolean drillthrough;
051        private int maxRows;
052        private int firstRowset;
053    
054        /* DISCOVER contnet */
055        private String requestType;
056        private Map<String, Object> restrictions;
057    
058    
059        public DefaultXmlaRequest(final Element xmlaRoot) {
060            this(xmlaRoot, null, null);
061        }
062    
063        public DefaultXmlaRequest(final Element xmlaRoot, final String roleName)
064                throws XmlaException {
065            this(xmlaRoot, roleName, null);
066        }
067    
068        public DefaultXmlaRequest(final Element xmlaRoot, final Role role)
069                throws XmlaException {
070            this(xmlaRoot, null, role);
071        }
072        protected DefaultXmlaRequest(final Element xmlaRoot,
073                                     final String roleName,
074                                     final Role role)
075                throws XmlaException {
076            init(xmlaRoot);
077            this.roleName = roleName;
078            this.role = role;
079        }
080    
081        /* Interface implmentation */
082    
083        public int getMethod() {
084            return method;
085        }
086    
087        public Map<String, String> getProperties() {
088            return properties;
089        }
090    
091        public Map<String, Object> getRestrictions() {
092            if (method != METHOD_DISCOVER) {
093                throw new IllegalStateException("Only METHOD_DISCOVER has restrictions");
094            }
095            return restrictions;
096        }
097    
098        public String getStatement() {
099            if (method != METHOD_EXECUTE) {
100                throw new IllegalStateException("Only METHOD_EXECUTE has statement");
101            }
102            return statement;
103        }
104    
105        public String getRoleName() {
106            return roleName;
107        }
108    
109        public Role getRole() {
110            return role;
111        }
112    
113    /*
114        public void setRole(String roleName) {
115            this.role = role;
116        }
117    */
118    
119        public String getRequestType() {
120            if (method != METHOD_DISCOVER)
121                throw new IllegalStateException("Only METHOD_DISCOVER has requestType");
122            return requestType;
123        }
124    
125        public boolean isDrillThrough() {
126            if (method != METHOD_EXECUTE)
127                throw new IllegalStateException("Only METHOD_EXECUTE determines drillthrough");
128            return drillthrough;
129        }
130    
131        public int drillThroughMaxRows() {
132            if (method != METHOD_EXECUTE)
133                throw new IllegalStateException("Only METHOD_EXECUTE determines drillthrough");
134            return maxRows;
135        }
136    
137        public int drillThroughFirstRowset() {
138            if (method != METHOD_EXECUTE)
139                throw new IllegalStateException("Only METHOD_EXECUTE determines drillthrough");
140            return firstRowset;
141        }
142    
143    
144        protected final void init(Element xmlaRoot) throws XmlaException {
145            if (NS_XMLA.equals(xmlaRoot.getNamespaceURI())) {
146                String lname = xmlaRoot.getLocalName();
147                if ("Discover".equals(lname)) {
148                    method = METHOD_DISCOVER;
149                    initDiscover(xmlaRoot);
150                } else if ("Execute".equals(lname)) {
151                    method = METHOD_EXECUTE;
152                    initExecute(xmlaRoot);
153                } else {
154                    // Note that is code will never be reached because
155                    // the error will be caught in
156                    // DefaultXmlaServlet.handleSoapBody first
157                    StringBuilder buf = new StringBuilder(100);
158                    buf.append(MSG_INVALID_XMLA);
159                    buf.append(": Bad method name \"");
160                    buf.append(lname);
161                    buf.append("\"");
162                    throw new XmlaException(
163                        CLIENT_FAULT_FC,
164                        HSB_BAD_METHOD_CODE,
165                        HSB_BAD_METHOD_FAULT_FS,
166                        Util.newError(buf.toString()));
167                }
168            } else {
169                // Note that is code will never be reached because
170                // the error will be caught in
171                // DefaultXmlaServlet.handleSoapBody first
172                StringBuilder buf = new StringBuilder(100);
173                buf.append(MSG_INVALID_XMLA);
174                buf.append(": Bad namespace url \"");
175                buf.append(xmlaRoot.getNamespaceURI());
176                buf.append("\"");
177                throw new XmlaException(
178                    CLIENT_FAULT_FC,
179                    HSB_BAD_METHOD_NS_CODE,
180                    HSB_BAD_METHOD_NS_FAULT_FS,
181                    Util.newError(buf.toString()));
182            }
183        }
184    
185        private void initDiscover(Element discoverRoot) throws XmlaException {
186            Element[] childElems = XmlaUtil.filterChildElements(discoverRoot,
187                                                                NS_XMLA,
188                                                                "RequestType");
189            if (childElems.length != 1) {
190                StringBuilder buf = new StringBuilder(100);
191                buf.append(MSG_INVALID_XMLA);
192                buf.append(": Wrong number of RequestType elements: ");
193                buf.append(childElems.length);
194                throw new XmlaException(
195                    CLIENT_FAULT_FC,
196                    HSB_BAD_REQUEST_TYPE_CODE,
197                    HSB_BAD_REQUEST_TYPE_FAULT_FS,
198                    Util.newError(buf.toString()));
199            }
200            requestType = XmlaUtil.textInElement(childElems[0]); // <RequestType>
201    
202            childElems = XmlaUtil.filterChildElements(discoverRoot,
203                                                      NS_XMLA,
204                                                      "Restrictions");
205            if (childElems.length != 1) {
206                StringBuilder buf = new StringBuilder(100);
207                buf.append(MSG_INVALID_XMLA);
208                buf.append(": Wrong number of Restrictions elements: ");
209                buf.append(childElems.length);
210                throw new XmlaException(
211                    CLIENT_FAULT_FC,
212                    HSB_BAD_RESTRICTIONS_CODE,
213                    HSB_BAD_RESTRICTIONS_FAULT_FS,
214                    Util.newError(buf.toString()));
215            }
216            initRestrictions(childElems[0]); // <Restriciotns><RestrictionList>
217    
218            childElems = XmlaUtil.filterChildElements(discoverRoot,
219                                                      NS_XMLA,
220                                                      "Properties");
221            if (childElems.length != 1) {
222                StringBuilder buf = new StringBuilder(100);
223                buf.append(MSG_INVALID_XMLA);
224                buf.append(": Wrong number of Properties elements: ");
225                buf.append(childElems.length);
226                throw new XmlaException(
227                    CLIENT_FAULT_FC,
228                    HSB_BAD_PROPERTIES_CODE,
229                    HSB_BAD_PROPERTIES_FAULT_FS,
230                    Util.newError(buf.toString()));
231            }
232            initProperties(childElems[0]); // <Properties><PropertyList>
233        }
234    
235        private void initExecute(Element executeRoot) throws XmlaException {
236            Element[] childElems = XmlaUtil.filterChildElements(executeRoot,
237                                                                NS_XMLA,
238                                                                "Command");
239            if (childElems.length != 1) {
240                StringBuilder buf = new StringBuilder(100);
241                buf.append(MSG_INVALID_XMLA);
242                buf.append(": Wrong number of Command elements: ");
243                buf.append(childElems.length);
244                throw new XmlaException(
245                    CLIENT_FAULT_FC,
246                    HSB_BAD_COMMAND_CODE,
247                    HSB_BAD_COMMAND_FAULT_FS,
248                    Util.newError(buf.toString()));
249            }
250            initCommand(childElems[0]); // <Command><Statement>
251    
252            childElems = XmlaUtil.filterChildElements(executeRoot,
253                                                      NS_XMLA,
254                                                      "Properties");
255            if (childElems.length != 1) {
256                StringBuilder buf = new StringBuilder(100);
257                buf.append(MSG_INVALID_XMLA);
258                buf.append(": Wrong number of Properties elements: ");
259                buf.append(childElems.length);
260                throw new XmlaException(
261                    CLIENT_FAULT_FC,
262                    HSB_BAD_PROPERTIES_CODE,
263                    HSB_BAD_PROPERTIES_FAULT_FS,
264                    Util.newError(buf.toString()));
265            }
266            initProperties(childElems[0]); // <Properties><PropertyList>
267        }
268    
269        private void initRestrictions(Element restrictionsRoot) throws XmlaException {
270            Map<String, List<String>> restrictions = new HashMap<String, List<String>>();
271            Element[] childElems =
272                XmlaUtil.filterChildElements(
273                    restrictionsRoot,
274                    NS_XMLA,
275                    "RestrictionList");
276            if (childElems.length == 1) {
277                NodeList nlst = childElems[0].getChildNodes();
278                for (int i = 0, nlen = nlst.getLength(); i < nlen; i++) {
279                    Node n = nlst.item(i);
280                    if (n instanceof Element) {
281                        Element e = (Element) n;
282                        if (NS_XMLA.equals(e.getNamespaceURI())) {
283                            String key = e.getLocalName();
284                            String value = XmlaUtil.textInElement(e);
285    
286                            List<String> values;
287                            if (restrictions.containsKey(key)) {
288                                values = restrictions.get(key);
289                            } else {
290                                values = new ArrayList<String>();
291                                restrictions.put(key, values);
292                            }
293    
294                            if (LOGGER.isDebugEnabled()) {
295                                StringBuilder buf = new StringBuilder(100);
296                                buf.append("DefaultXmlaRequest.initRestrictions: ");
297                                buf.append(" key=\"");
298                                buf.append(key);
299                                buf.append("\", value=\"");
300                                buf.append(value);
301                                buf.append("\"");
302                                LOGGER.debug(buf.toString());
303                            }
304    
305                            values.add(value);
306                        }
307                    }
308                }
309            } else if (childElems.length > 1) {
310                StringBuilder buf = new StringBuilder(100);
311                buf.append(MSG_INVALID_XMLA);
312                buf.append(": Wrong number of RestrictionList elements: ");
313                buf.append(childElems.length);
314                throw new XmlaException(
315                    CLIENT_FAULT_FC,
316                    HSB_BAD_RESTRICTION_LIST_CODE,
317                    HSB_BAD_RESTRICTION_LIST_FAULT_FS,
318                    Util.newError(buf.toString()));
319            } else {
320            }
321            this.restrictions = (Map) Collections.unmodifiableMap(restrictions);
322        }
323    
324        private void initProperties(Element propertiesRoot) throws XmlaException {
325            Map<String, String> properties = new HashMap<String, String>();
326            Element[] childElems = XmlaUtil.filterChildElements(propertiesRoot,
327                                                                NS_XMLA,
328                                                                "PropertyList");
329            if (childElems.length == 1) {
330                NodeList nlst = childElems[0].getChildNodes();
331                for (int i = 0, nlen = nlst.getLength(); i < nlen; i++) {
332                    Node n = nlst.item(i);
333                    if (n instanceof Element) {
334                        Element e = (Element) n;
335                        if (NS_XMLA.equals(e.getNamespaceURI())) {
336    
337                            String key = e.getLocalName();
338                            String value = XmlaUtil.textInElement(e);
339    
340                            if (LOGGER.isDebugEnabled()) {
341                                StringBuilder buf = new StringBuilder(100);
342                                buf.append("DefaultXmlaRequest.initProperties: ");
343                                buf.append(" key=\"");
344                                buf.append(key);
345                                buf.append("\", value=\"");
346                                buf.append(value);
347                                buf.append("\"");
348                                LOGGER.debug(buf.toString());
349                            }
350    
351                            properties.put(key, value);
352                        }
353                    }
354                }
355            } else if (childElems.length > 1) {
356                StringBuilder buf = new StringBuilder(100);
357                buf.append(MSG_INVALID_XMLA);
358                buf.append(": Wrong number of PropertyList elements: ");
359                buf.append(childElems.length);
360                throw new XmlaException(
361                    CLIENT_FAULT_FC,
362                    HSB_BAD_PROPERTIES_LIST_CODE,
363                    HSB_BAD_PROPERTIES_LIST_FAULT_FS,
364                    Util.newError(buf.toString()));
365            } else {
366            }
367            this.properties = Collections.unmodifiableMap(properties);
368        }
369    
370    
371        private void initCommand(Element commandRoot) throws XmlaException {
372            Element[] childElems = XmlaUtil.filterChildElements(commandRoot,
373                                                                NS_XMLA,
374                                                                "Statement");
375            if (childElems.length != 1) {
376                StringBuilder buf = new StringBuilder(100);
377                buf.append(MSG_INVALID_XMLA);
378                buf.append(": Wrong number of Statement elements: ");
379                buf.append(childElems.length);
380                throw new XmlaException(
381                    CLIENT_FAULT_FC,
382                    HSB_BAD_STATEMENT_CODE,
383                    HSB_BAD_STATEMENT_FAULT_FS,
384                    Util.newError(buf.toString()));
385            }
386            statement = XmlaUtil.textInElement(childElems[0]).replaceAll("\\r", "");
387    
388            String upperStatement = statement.toUpperCase();
389            int dtOffset = upperStatement.indexOf("DRILLTHROUGH");
390            int mrOffset = upperStatement.indexOf("MAXROWS");
391            int frOffset = upperStatement.indexOf("FIRSTROWSET");
392            int slOffset = upperStatement.indexOf("SELECT");
393    
394            if (dtOffset == -1) {
395                drillthrough = false;
396            } else {
397                /*
398                 * <drillthrough> := DRILLTHROUGH
399                 *     [<Max_Rows>] [<First_Rowset>] <MDX select> [<Return_Columns>]
400                 * <Max_Rows> := MAXROWS <positive number>
401                 * <First_Rowset> := FIRSTROWSET <positive number>
402                 * <Return_Columns> := RETURN <member or attribute>
403                 *     [, <member or attribute>]
404                 */
405                if (dtOffset < slOffset) {
406                    maxRows = firstRowset = -1;
407                    try {
408                        if (mrOffset > dtOffset && mrOffset < slOffset) {
409                            maxRows = parseIntValue(statement.substring(mrOffset, slOffset));
410                            if (maxRows <= 0) {
411                                StringBuilder buf = new StringBuilder(100);
412                                buf.append(MSG_INVALID_MAXROWS);
413                                buf.append(": ");
414                                buf.append(maxRows);
415                                throw new XmlaException(
416                                    CLIENT_FAULT_FC,
417                                    HSB_DRILLDOWN_BAD_MAXROWS_CODE,
418                                    HSB_DRILLDOWN_BAD_MAXROWS_FAULT_FS,
419                                    Util.newError(buf.toString()));
420                            }
421                        }
422                        if (frOffset > dtOffset && frOffset > mrOffset && frOffset < slOffset) {
423                            firstRowset = parseIntValue(statement.substring(frOffset, slOffset));
424                            if (firstRowset <= 0) {
425                                StringBuilder buf = new StringBuilder(100);
426                                buf.append(MSG_INVALID_FIRSTROWSET);
427                                buf.append(": ");
428                                buf.append(firstRowset);
429                                throw new XmlaException(
430                                    CLIENT_FAULT_FC,
431                                    HSB_DRILLDOWN_BAD_FIRST_ROWSET_CODE,
432                                    HSB_DRILLDOWN_BAD_FIRST_ROWSET_FAULT_FS,
433                                    Util.newError(buf.toString()));
434                            }
435                        }
436                    } catch (XmlaException xex) {
437                        throw xex;
438                    } catch (Exception e) {
439                        throw new XmlaException(
440                            CLIENT_FAULT_FC,
441                            HSB_DRILLDOWN_ERROR_CODE,
442                            HSB_DRILLDOWN_ERROR_FAULT_FS,
443                            Util.newError(e, MSG_INVALID_DRILLTHROUGH));
444                    }
445    
446                    int configMaxRows = MondrianProperties.instance().MaxRows.get();
447                    if (configMaxRows > 0 && maxRows > configMaxRows) {
448                        maxRows = configMaxRows;
449                    }
450    
451                    StringBuilder dtStmtBuf = new StringBuilder();
452                    dtStmtBuf.append(statement.substring(0, dtOffset)); // formulas
453                    dtStmtBuf.append(statement.substring(slOffset)); // select to end
454                    statement = dtStmtBuf.toString();
455    
456                    drillthrough = true;
457                } else {
458                    throw new XmlaException(
459                        CLIENT_FAULT_FC,
460                        HSB_DRILLDOWN_ERROR_CODE,
461                        HSB_DRILLDOWN_ERROR_FAULT_FS,
462                        Util.newError(MSG_INVALID_DRILLTHROUGH));
463                }
464            }
465        }
466    
467        private int parseIntValue(String option) {
468            String[] opts = option.split("[ \t\n]");
469            return Integer.parseInt(opts[1]);
470        }
471    
472    }
473    
474    // End DefaultXmlaRequest.java