├── .gitignore ├── HISTORY.md ├── NOTICE ├── README.md ├── epl-v10.html ├── pom.xml ├── src └── main │ └── java │ ├── mondrian │ └── xmla │ │ ├── Enumeration.java │ │ ├── PropertyDefinition.java │ │ ├── Rowset.java │ │ ├── RowsetDefinition.java │ │ ├── SaxWriter.java │ │ ├── XmlaConstants.java │ │ ├── XmlaException.java │ │ ├── XmlaHandler.java │ │ ├── XmlaRequest.java │ │ ├── XmlaRequestCallback.java │ │ ├── XmlaResponse.java │ │ ├── XmlaServlet.java │ │ ├── XmlaUtil.java │ │ ├── impl │ │ ├── AuthenticatingXmlaRequestCallback.java │ │ ├── DefaultSaxWriter.java │ │ ├── DefaultXmlaRequest.java │ │ ├── DefaultXmlaResponse.java │ │ ├── DefaultXmlaServlet.java │ │ ├── JsonSaxWriter.java │ │ └── Olap4jXmlaServlet.java │ │ └── package.html │ └── org │ └── olap4j │ └── xmla │ └── server │ └── impl │ ├── ArrayStack.java │ ├── Composite.java │ ├── CompositeList.java │ ├── StringEscaper.java │ └── Util.java └── xmlaserver.iml /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | target 3 | .idea/ 4 | 5 | -------------------------------------------------------------------------------- /HISTORY.md: -------------------------------------------------------------------------------- 1 | 2 | # olap4j-xmlaserver 3 | 4 | ############################################################################# 5 | ## Version 1.2.0 6 | 7 | This is the first release of olap4j-xmlaserver. We have numbered it 1.2.0 so 8 | it aligns with the core olap4j releases. Future releases might not maintain 9 | this alignment. 10 | 11 | This release is also the first to be compatible with OSGI. For a list 12 | of the exported packages, please refer to the maven build file. 13 | 14 | ############################################################################# 15 | 16 | ## Commit history 17 | 18 | [4c9a99f](../../commit/4c9a99f50b5cbf1eb5959d0fe230cc25e93a7bfa) 19 | Fri, 10 Jan 2014 11:40:13 -0500 - __(Kurtis Walker)__ 20 | Mondrian needs some additional packages exported in order to fully resolve in OSGI. This changes makes the exported xmlaserver packages explicit. 21 | 22 | [33fa613](../../commit/33fa6135672db30de665d568521c4faf962f3760) 23 | Thu, 9 Jan 2014 10:49:05 -0500 - __(Luc Boudreau)__ 24 | Update pom.xml 25 | 26 | [2939112](../../commit/29391127e752ce457f766f9009f23bca1e411bf0) 27 | Thu, 5 Dec 2013 09:51:32 -0500 - __(Nicholas Baker)__ 28 | Changes to POM to make the artifact an OSGI bundle. Upped version to 0.0.2-SNAPSHOT due to the change. 29 | 30 | [8f0f55f](../../commit/8f0f55fa2dbb68df2ea8795a40d34ba7d830ec75) 31 | Tue, 26 Nov 2013 10:39:26 -0500 - __(Kurtis Walker)__ 32 | ANALYZER-2188 - adding new methods to XmlaExtra 33 | 34 | [dad5a17](../../commit/dad5a17cb38d2fa713e2be89c1748f4b6bf37813) 35 | Fri, 26 Jul 2013 10:12:07 -0700 - __(Julian Hyde)__ 36 | Remove Intellij files from git. 37 | 38 | [d7dfa1f](../../commit/d7dfa1fce7c1791e628d7dafe22329205aaf2ef5) 39 | Wed, 19 Jun 2013 09:18:32 -0400 - __(mkambol)__ 40 | [MONDRIAN-1581] Fixing datatypes which were incorrectly marshalled in XMLA responses: Boolean, Short, Byte, Float (were formerly treated as xsd:string, xsd:int, xsd:int, and xsd:double, respectively) merged from 534768 41 | 42 | [a0e99cd](../../commit/a0e99cd8df51d7650213af3de0c2c46ca1f7edde) 43 | Thu, 21 Feb 2013 10:34:15 -0500 - __(Luc Boudreau)__ 44 | Merge of commit 24cf3d0e2a57b88bb9550e4bccd8c8bb34c6d9db form Mondrian's master. 45 | 46 | [34e6697](../../commit/34e66971c15a947ec99e8a56c6273d0c2de1648c) 47 | Thu, 21 Feb 2013 10:20:56 -0500 - __(Luc Boudreau)__ 48 | Merge of change a5c552f74ed8068a7b40fa6f2b3e9a995d94e100 from Mondrian's master. 49 | 50 | [2f444e2](../../commit/2f444e26e905804fd13b5d53636d168b6df925ed) 51 | Thu, 21 Feb 2013 09:39:15 -0500 - __(Luc Boudreau)__ 52 | Merge of changes 8fa75e276f638b1156e0fa1c447fc49006112f88 ba93ad762db859d04d6aa2411e4587280ca06249 160dadad7af7dee209fa85ba8cbd9106fa60c799 from Mondrian's master. 53 | 54 | [d87b6ad](../../commit/d87b6ad54c3ba39448ec3bc2a31a249d75d9acf5) 55 | Wed, 2 Jan 2013 11:59:47 -0500 - __(Luc Boudreau)__ 56 | Adds getSchemaId() to XmlaExtra. 57 | 58 | [1cd132a](../../commit/1cd132ad3a5f225412ba8ed8cb9c4409c59653a4) 59 | Mon, 17 Dec 2012 14:31:27 -0500 - __(Luc Boudreau)__ 60 | Idem. 61 | 62 | [04296ff](../../commit/04296ff62f7fee2531b33d342fe2a1c8cd9b118d) 63 | Mon, 17 Dec 2012 13:30:45 -0500 - __(buildguy)__ 64 | Update pom.xml 65 | 66 | [c620eab](../../commit/c620eab7bb69916df36944963c0a94169b497eb4) 67 | Mon, 17 Dec 2012 12:56:50 -0500 - __(Luc Boudreau)__ 68 | Idem. 69 | 70 | [a6f4059](../../commit/a6f405959235692e064210b1333a7e4abe1ec038) 71 | Mon, 17 Dec 2012 12:54:58 -0500 - __(Luc Boudreau)__ 72 | Idem. 73 | 74 | [773507d](../../commit/773507d0affdba351fc50e37c91479e7f3fe9633) 75 | Mon, 17 Dec 2012 12:43:44 -0500 - __(Luc Boudreau)__ 76 | An attempt to fix CI. 77 | 78 | [fae048f](../../commit/fae048f973b8aa51d4c9abee9c707042fedf33df) 79 | Mon, 17 Dec 2012 11:29:18 -0500 - __(Luc Boudreau)__ 80 | Small change to the pom to get deployment working on CI. 81 | 82 | [ffd4216](../../commit/ffd4216b41e1e0009df0ade94d2125bf984919f4) 83 | Mon, 17 Dec 2012 10:54:36 -0500 - __(Luc Boudreau)__ 84 | Upgrades to latest olap4j. 85 | 86 | [38d065a](../../commit/38d065af43a45335758d772072066cafea9159d7) 87 | Fri, 7 Sep 2012 15:48:26 -0700 - __(Julian Hyde)__ 88 | DefaultSaxWriter now writes to any Appendable, rather than a PrintWriter. If the Appendable is a StringBuilder, this should be more efficient. 89 | 90 | [ed28a49](../../commit/ed28a4973c69fe0991c63b9e4a55b0faf87d3cb0) 91 | Fri, 7 Sep 2012 13:25:31 -0700 - __(Julian Hyde)__ 92 | Fix name of project in pom; get XmlaExtra from ConnectionFactory, rather than by using "unwrap" on the OlapConnection. 93 | 94 | [de2fed9](../../commit/de2fed93ee14d5ae4a83a34b12bda513ff6d2e63) 95 | Fri, 31 Aug 2012 21:07:17 -0700 - __(Julian Hyde)__ 96 | Slim down dependencies. 97 | 98 | [3602acf](../../commit/3602acf4133dabf48a053fdb7b5186eaee7a97de) 99 | Fri, 31 Aug 2012 20:46:51 -0700 - __(Julian Hyde)__ 100 | Remove mondrian dependencies. (Extend XmlaExtra interface, remove stub classes.) 101 | 102 | [7e53b09](../../commit/7e53b09f6820d86ad38d552b71316fd05df0da61) 103 | Fri, 31 Aug 2012 17:03:32 -0700 - __(Julian Hyde)__ 104 | Initial version, copying mondrian.xmla package and stub versions of mondrian classes it depends upon. 105 | 106 | [3600730](../../commit/3600730867ac523c8cc0cbcc346b711ff5d1c42a) 107 | Fri, 31 Aug 2012 15:20:27 -0700 - __(Julian Hyde)__ 108 | Initial commit 109 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | olap4j-xmlaserver 2 | 3 | =============================================================================== 4 | This product includes software from the Mondrian project. 5 | 6 | Copyright (C) 2001-2005 Julian Hyde 7 | Copyright (C) 2005-2012 Pentaho and others 8 | 9 | =============================================================================== 10 | The StringEscaper class comes from the eigenbase-xom project. 11 | 12 | Copyright (C) 2005 Dynamo BI Corporation 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | olap4j-xmlaserver 2 | ================= 3 | 4 | XML for Analysis (XMLA) server based upon an olap4j connection -------------------------------------------------------------------------------- /epl-v10.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Eclipse Public License - Version 1.0 8 | 25 | 26 | 27 | 28 | 29 | 30 |

Eclipse Public License - v 1.0

31 | 32 |

THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE 33 | PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR 34 | DISTRIBUTION OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS 35 | AGREEMENT.

36 | 37 |

1. DEFINITIONS

38 | 39 |

"Contribution" means:

40 | 41 |

a) in the case of the initial Contributor, the initial 42 | code and documentation distributed under this Agreement, and

43 |

b) in the case of each subsequent Contributor:

44 |

i) changes to the Program, and

45 |

ii) additions to the Program;

46 |

where such changes and/or additions to the Program 47 | originate from and are distributed by that particular Contributor. A 48 | Contribution 'originates' from a Contributor if it was added to the 49 | Program by such Contributor itself or anyone acting on such 50 | Contributor's behalf. Contributions do not include additions to the 51 | Program which: (i) are separate modules of software distributed in 52 | conjunction with the Program under their own license agreement, and (ii) 53 | are not derivative works of the Program.

54 | 55 |

"Contributor" means any person or entity that distributes 56 | the Program.

57 | 58 |

"Licensed Patents" mean patent claims licensable by a 59 | Contributor which are necessarily infringed by the use or sale of its 60 | Contribution alone or when combined with the Program.

61 | 62 |

"Program" means the Contributions distributed in accordance 63 | with this Agreement.

64 | 65 |

"Recipient" means anyone who receives the Program under 66 | this Agreement, including all Contributors.

67 | 68 |

2. GRANT OF RIGHTS

69 | 70 |

a) Subject to the terms of this Agreement, each 71 | Contributor hereby grants Recipient a non-exclusive, worldwide, 72 | royalty-free copyright license to reproduce, prepare derivative works 73 | of, publicly display, publicly perform, distribute and sublicense the 74 | Contribution of such Contributor, if any, and such derivative works, in 75 | source code and object code form.

76 | 77 |

b) Subject to the terms of this Agreement, each 78 | Contributor hereby grants Recipient a non-exclusive, worldwide, 79 | royalty-free patent license under Licensed Patents to make, use, sell, 80 | offer to sell, import and otherwise transfer the Contribution of such 81 | Contributor, if any, in source code and object code form. This patent 82 | license shall apply to the combination of the Contribution and the 83 | Program if, at the time the Contribution is added by the Contributor, 84 | such addition of the Contribution causes such combination to be covered 85 | by the Licensed Patents. The patent license shall not apply to any other 86 | combinations which include the Contribution. No hardware per se is 87 | licensed hereunder.

88 | 89 |

c) Recipient understands that although each Contributor 90 | grants the licenses to its Contributions set forth herein, no assurances 91 | are provided by any Contributor that the Program does not infringe the 92 | patent or other intellectual property rights of any other entity. Each 93 | Contributor disclaims any liability to Recipient for claims brought by 94 | any other entity based on infringement of intellectual property rights 95 | or otherwise. As a condition to exercising the rights and licenses 96 | granted hereunder, each Recipient hereby assumes sole responsibility to 97 | secure any other intellectual property rights needed, if any. For 98 | example, if a third party patent license is required to allow Recipient 99 | to distribute the Program, it is Recipient's responsibility to acquire 100 | that license before distributing the Program.

101 | 102 |

d) Each Contributor represents that to its knowledge it 103 | has sufficient copyright rights in its Contribution, if any, to grant 104 | the copyright license set forth in this Agreement.

105 | 106 |

3. REQUIREMENTS

107 | 108 |

A Contributor may choose to distribute the Program in object code 109 | form under its own license agreement, provided that:

110 | 111 |

a) it complies with the terms and conditions of this 112 | Agreement; and

113 | 114 |

b) its license agreement:

115 | 116 |

i) effectively disclaims on behalf of all Contributors 117 | all warranties and conditions, express and implied, including warranties 118 | or conditions of title and non-infringement, and implied warranties or 119 | conditions of merchantability and fitness for a particular purpose;

120 | 121 |

ii) effectively excludes on behalf of all Contributors 122 | all liability for damages, including direct, indirect, special, 123 | incidental and consequential damages, such as lost profits;

124 | 125 |

iii) states that any provisions which differ from this 126 | Agreement are offered by that Contributor alone and not by any other 127 | party; and

128 | 129 |

iv) states that source code for the Program is available 130 | from such Contributor, and informs licensees how to obtain it in a 131 | reasonable manner on or through a medium customarily used for software 132 | exchange.

133 | 134 |

When the Program is made available in source code form:

135 | 136 |

a) it must be made available under this Agreement; and

137 | 138 |

b) a copy of this Agreement must be included with each 139 | copy of the Program.

140 | 141 |

Contributors may not remove or alter any copyright notices contained 142 | within the Program.

143 | 144 |

Each Contributor must identify itself as the originator of its 145 | Contribution, if any, in a manner that reasonably allows subsequent 146 | Recipients to identify the originator of the Contribution.

147 | 148 |

4. COMMERCIAL DISTRIBUTION

149 | 150 |

Commercial distributors of software may accept certain 151 | responsibilities with respect to end users, business partners and the 152 | like. While this license is intended to facilitate the commercial use of 153 | the Program, the Contributor who includes the Program in a commercial 154 | product offering should do so in a manner which does not create 155 | potential liability for other Contributors. Therefore, if a Contributor 156 | includes the Program in a commercial product offering, such Contributor 157 | ("Commercial Contributor") hereby agrees to defend and 158 | indemnify every other Contributor ("Indemnified Contributor") 159 | against any losses, damages and costs (collectively "Losses") 160 | arising from claims, lawsuits and other legal actions brought by a third 161 | party against the Indemnified Contributor to the extent caused by the 162 | acts or omissions of such Commercial Contributor in connection with its 163 | distribution of the Program in a commercial product offering. The 164 | obligations in this section do not apply to any claims or Losses 165 | relating to any actual or alleged intellectual property infringement. In 166 | order to qualify, an Indemnified Contributor must: a) promptly notify 167 | the Commercial Contributor in writing of such claim, and b) allow the 168 | Commercial Contributor to control, and cooperate with the Commercial 169 | Contributor in, the defense and any related settlement negotiations. The 170 | Indemnified Contributor may participate in any such claim at its own 171 | expense.

172 | 173 |

For example, a Contributor might include the Program in a commercial 174 | product offering, Product X. That Contributor is then a Commercial 175 | Contributor. If that Commercial Contributor then makes performance 176 | claims, or offers warranties related to Product X, those performance 177 | claims and warranties are such Commercial Contributor's responsibility 178 | alone. Under this section, the Commercial Contributor would have to 179 | defend claims against the other Contributors related to those 180 | performance claims and warranties, and if a court requires any other 181 | Contributor to pay any damages as a result, the Commercial Contributor 182 | must pay those damages.

183 | 184 |

5. NO WARRANTY

185 | 186 |

EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS 187 | PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS 188 | OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, 189 | ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY 190 | OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely 191 | responsible for determining the appropriateness of using and 192 | distributing the Program and assumes all risks associated with its 193 | exercise of rights under this Agreement , including but not limited to 194 | the risks and costs of program errors, compliance with applicable laws, 195 | damage to or loss of data, programs or equipment, and unavailability or 196 | interruption of operations.

197 | 198 |

6. DISCLAIMER OF LIABILITY

199 | 200 |

EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT 201 | NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, 202 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING 203 | WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF 204 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 205 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR 206 | DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED 207 | HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.

208 | 209 |

7. GENERAL

210 | 211 |

If any provision of this Agreement is invalid or unenforceable under 212 | applicable law, it shall not affect the validity or enforceability of 213 | the remainder of the terms of this Agreement, and without further action 214 | by the parties hereto, such provision shall be reformed to the minimum 215 | extent necessary to make such provision valid and enforceable.

216 | 217 |

If Recipient institutes patent litigation against any entity 218 | (including a cross-claim or counterclaim in a lawsuit) alleging that the 219 | Program itself (excluding combinations of the Program with other 220 | software or hardware) infringes such Recipient's patent(s), then such 221 | Recipient's rights granted under Section 2(b) shall terminate as of the 222 | date such litigation is filed.

223 | 224 |

All Recipient's rights under this Agreement shall terminate if it 225 | fails to comply with any of the material terms or conditions of this 226 | Agreement and does not cure such failure in a reasonable period of time 227 | after becoming aware of such noncompliance. If all Recipient's rights 228 | under this Agreement terminate, Recipient agrees to cease use and 229 | distribution of the Program as soon as reasonably practicable. However, 230 | Recipient's obligations under this Agreement and any licenses granted by 231 | Recipient relating to the Program shall continue and survive.

232 | 233 |

Everyone is permitted to copy and distribute copies of this 234 | Agreement, but in order to avoid inconsistency the Agreement is 235 | copyrighted and may only be modified in the following manner. The 236 | Agreement Steward reserves the right to publish new versions (including 237 | revisions) of this Agreement from time to time. No one other than the 238 | Agreement Steward has the right to modify this Agreement. The Eclipse 239 | Foundation is the initial Agreement Steward. The Eclipse Foundation may 240 | assign the responsibility to serve as the Agreement Steward to a 241 | suitable separate entity. Each new version of the Agreement will be 242 | given a distinguishing version number. The Program (including 243 | Contributions) may always be distributed subject to the version of the 244 | Agreement under which it was received. In addition, after a new version 245 | of the Agreement is published, Contributor may elect to distribute the 246 | Program (including its Contributions) under the new version. Except as 247 | expressly stated in Sections 2(a) and 2(b) above, Recipient receives no 248 | rights or licenses to the intellectual property of any Contributor under 249 | this Agreement, whether expressly, by implication, estoppel or 250 | otherwise. All rights in the Program not expressly granted under this 251 | Agreement are reserved.

252 | 253 |

This Agreement is governed by the laws of the State of New York and 254 | the intellectual property laws of the United States of America. No party 255 | to this Agreement will bring a legal action under this Agreement more 256 | than one year after the cause of action arose. Each party waives its 257 | rights to a jury trial in any resulting litigation.

258 | 259 | 260 | 261 | 262 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | 6 | org.olap4j 7 | olap4j-xmlaserver 8 | TRUNK-SNAPSHOT 9 | bundle 10 | 11 | 12 | olap4j-xmlaserver 13 | XML for Analysis (XMLA) server based upon olap4j connections 14 | http://github.com/olap4j/olap4j-xmlaserver 15 | 2012 16 | 17 | olap4j 18 | http://www.olap4j.org 19 | 20 | 21 | 22 | Apache 2 23 | http://www.apache.org/licenses/LICENSE-2.0.html 24 | 25 | 26 | 27 | 28 | 29 | julianhyde 30 | Julian Hyde 31 | julianhyde@gmail.com 32 | https://github.com/julianhyde 33 | 34 | architect 35 | developer 36 | 37 | -8 38 | 39 | 40 | 41 | 42 | 43 | UTF-8 44 | 45 | 46 | 47 | 48 | 49 | 50 | scm:git:git://github.com/olap4j/olap4j-xmlaserver.git 51 | scm:git:git@github.com:olap4j/olap4j-xmlaserver.git 52 | http://github.com/olap4j/olap4j-xmlaserver/tree/master 53 | 54 | 55 | 56 | 57 | 58 | log4j 59 | log4j 60 | 1.2.14 61 | 62 | 63 | commons-dbcp 64 | commons-dbcp 65 | 1.2.1 66 | 67 | 68 | org.olap4j 69 | olap4j 70 | 1.2.0 71 | 72 | 73 | javax.servlet 74 | servlet-api 75 | 2.4 76 | provided 77 | 78 | 79 | 80 | 81 | 82 | 83 | org.apache.maven.plugins 84 | maven-javadoc-plugin 85 | 2.8.1 86 | 87 | 88 | http://docs.oracle.com/javase/7/docs/api/ 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | false 98 | pentaho 99 | Pentaho Open Repository 100 | http://ivy-nexus.pentaho.org/content/groups/omni 101 | default 102 | 103 | 104 | 105 | 106 | 107 | pentaho 108 | http://ivy-nexus.pentaho.org/content/groups/omni 109 | 110 | true 111 | 112 | 113 | 114 | 115 | 116 | package 117 | 118 | 119 | maven-compiler-plugin 120 | 2.3.2 121 | 122 | 1.5 123 | 1.5 124 | 125 | 126 | 127 | org.apache.maven.plugins 128 | maven-surefire-plugin 129 | 130 | 131 | 132 | 133 | 134 | org.apache.felix 135 | maven-bundle-plugin 136 | true 137 | 138 | 139 | mondrian.xmla.impl,mondrian.xmla,org.olap4j.xmla.server.impl 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | -------------------------------------------------------------------------------- /src/main/java/mondrian/xmla/Enumeration.java: -------------------------------------------------------------------------------- 1 | /* 2 | // This software is subject to the terms of the Eclipse Public License v1.0 3 | // Agreement, available at the following URL: 4 | // http://www.eclipse.org/legal/epl-v10.html. 5 | // You must accept the terms of that agreement to use this software. 6 | // 7 | // Copyright (C) 2003-2005 Julian Hyde 8 | // Copyright (C) 2005-2010 Pentaho 9 | // All Rights Reserved. 10 | // 11 | // jhyde, May 2, 2003 12 | */ 13 | package mondrian.xmla; 14 | 15 | import org.olap4j.impl.UnmodifiableArrayMap; 16 | import org.olap4j.metadata.XmlaConstant; 17 | import org.olap4j.metadata.XmlaConstants; 18 | 19 | import java.util.List; 20 | import java.util.Map; 21 | 22 | /** 23 | * Contains inner classes which define enumerations used in XML for Analysis. 24 | * 25 | * @author jhyde 26 | * @since May 2, 2003 27 | */ 28 | public class Enumeration { 29 | public final String name; 30 | public final String description; 31 | public final RowsetDefinition.Type type; 32 | private final XmlaConstant.Dictionary dictionary; 33 | 34 | public static final Enumeration TREE_OP = 35 | new Enumeration( 36 | "TREE_OP", 37 | "Bitmap which controls which relatives of a member are " 38 | + "returned", 39 | RowsetDefinition.Type.Integer, 40 | org.olap4j.metadata.Member.TreeOp.getDictionary()); 41 | 42 | public static final Enumeration VISUAL_MODE = 43 | new Enumeration( 44 | "VisualMode", 45 | "This property determines the default behavior for visual " 46 | + "totals.", 47 | RowsetDefinition.Type.Integer, 48 | org.olap4j.metadata.XmlaConstants.VisualMode.getDictionary()); 49 | 50 | public static final Enumeration METHODS = 51 | new Enumeration( 52 | "Methods", 53 | "Set of methods for which a property is applicable", 54 | RowsetDefinition.Type.Enumeration, 55 | XmlaConstants.Method.getDictionary()); 56 | 57 | public static final Enumeration ACCESS = 58 | new Enumeration( 59 | "Access", 60 | "The read/write behavior of a property", 61 | RowsetDefinition.Type.Enumeration, 62 | XmlaConstants.Access.getDictionary()); 63 | 64 | public static final Enumeration AUTHENTICATION_MODE = 65 | new Enumeration( 66 | "AuthenticationMode", 67 | "Specification of what type of security mode the data source " 68 | + "uses.", 69 | RowsetDefinition.Type.EnumString, 70 | XmlaConstants.AuthenticationMode.getDictionary()); 71 | 72 | public static final Enumeration PROVIDER_TYPE = 73 | new Enumeration( 74 | "ProviderType", 75 | "The types of data supported by the provider.", 76 | RowsetDefinition.Type.Array, 77 | XmlaConstants.ProviderType.getDictionary()); 78 | 79 | public Enumeration( 80 | String name, 81 | String description, 82 | RowsetDefinition.Type type, 83 | XmlaConstant.Dictionary dictionary) 84 | { 85 | this.name = name; 86 | this.description = description; 87 | this.type = type; 88 | this.dictionary = dictionary; 89 | } 90 | 91 | public String getName() { 92 | return name; 93 | } 94 | 95 | public List getValues() { 96 | return dictionary.getValues(); 97 | } 98 | 99 | public enum ResponseMimeType { 100 | SOAP("text/xml"), 101 | JSON("application/json"); 102 | 103 | public static final Map MAP = 104 | UnmodifiableArrayMap.of( 105 | "application/soap+xml", SOAP, 106 | "application/xml", SOAP, 107 | "text/xml", SOAP, 108 | "application/json", JSON, 109 | "*/*", SOAP); 110 | 111 | private final String mimeType; 112 | 113 | ResponseMimeType(String mimeType) { 114 | this.mimeType = mimeType; 115 | } 116 | 117 | public String getMimeType() { 118 | return mimeType; 119 | } 120 | } 121 | 122 | } 123 | 124 | // End Enumeration.java 125 | -------------------------------------------------------------------------------- /src/main/java/mondrian/xmla/PropertyDefinition.java: -------------------------------------------------------------------------------- 1 | /* 2 | // This software is subject to the terms of the Eclipse Public License v1.0 3 | // Agreement, available at the following URL: 4 | // http://www.eclipse.org/legal/epl-v10.html. 5 | // You must accept the terms of that agreement to use this software. 6 | // 7 | // Copyright (C) 2003-2005 Julian Hyde 8 | // Copyright (C) 2005-2012 Pentaho 9 | // All Rights Reserved. 10 | */ 11 | package mondrian.xmla; 12 | 13 | import org.olap4j.impl.Olap4jUtil; 14 | import org.olap4j.metadata.XmlaConstants; 15 | 16 | import java.util.Set; 17 | 18 | /** 19 | * Defines an XML for Analysis Property. 20 | * 21 | * @author jhyde 22 | * @since May 2, 2003 23 | */ 24 | public enum PropertyDefinition { 25 | AxisFormat( 26 | RowsetDefinition.Type.Enumeration, 27 | Olap4jUtil.enumSetAllOf(XmlaConstants.AxisFormat.class), 28 | XmlaConstants.Access.Write, 29 | "", 30 | XmlaConstants.Method.EXECUTE, 31 | "Determines the format used within an MDDataSet result set to describe the axes of the multidimensional dataset. This property can have the values listed in the following table: TupleFormat (default), ClusterFormat, CustomFormat."), 32 | 33 | BeginRange( 34 | RowsetDefinition.Type.Integer, 35 | null, 36 | XmlaConstants.Access.Write, 37 | "-1", 38 | XmlaConstants.Method.EXECUTE, 39 | "Contains a zero-based integer value corresponding to a CellOrdinal attribute value. (The CellOrdinal attribute is part of the Cell element in the CellData section of MDDataSet.)\n" 40 | + "Used together with the EndRange property, the client application can use this property to restrict an OLAP dataset returned by a command to a specific range of cells. If -1 is specified, all cells up to the cell specified in the EndRange property are returned.\n" 41 | + "The default value for this property is -1."), 42 | 43 | Catalog( 44 | RowsetDefinition.Type.String, 45 | null, 46 | XmlaConstants.Access.ReadWrite, 47 | "", 48 | XmlaConstants.Method.DISCOVER_AND_EXECUTE, 49 | "When establishing a session with an Analysis Services instance to send an XMLA command, this property is equivalent to the OLE DB property, DBPROP_INIT_CATALOG.\n" 50 | + "When you set this property during a session to change the current database for the session, this property is equivalent to the OLE DB property, DBPROP_CURRENTCATALOG.\n" 51 | + "The default value for this property is an empty string."), 52 | 53 | Content( 54 | RowsetDefinition.Type.EnumString, 55 | Olap4jUtil.enumSetAllOf(XmlaConstants.Content.class), 56 | XmlaConstants.Access.Write, 57 | XmlaConstants.Content.DEFAULT.name(), 58 | XmlaConstants.Method.DISCOVER_AND_EXECUTE, 59 | "An enumerator that specifies what type of data is returned in the result set.\n" 60 | + "None: Allows the structure of the command to be verified, but not executed. Analogous to using Prepare to check syntax, and so on.\n" 61 | + "Schema: Contains the XML schema (which indicates column information, and so on) that relates to the requested query.\n" 62 | + "Data: Contains only the data that was requested.\n" 63 | + "SchemaData: Returns both the schema information as well as the data."), 64 | 65 | Cube( 66 | RowsetDefinition.Type.String, 67 | null, 68 | XmlaConstants.Access.ReadWrite, 69 | "", 70 | XmlaConstants.Method.EXECUTE, 71 | "The cube context for the Command parameter. If the command contains a cube name (such as an MDX FROM clause) the setting of this property is ignored."), 72 | 73 | DataSourceInfo( 74 | RowsetDefinition.Type.String, 75 | null, 76 | XmlaConstants.Access.ReadWrite, 77 | "", 78 | XmlaConstants.Method.DISCOVER_AND_EXECUTE, 79 | "A string containing provider specific information, required to access the data source."), 80 | 81 | // Mondrian-specific extension to XMLA. 82 | Deep( 83 | RowsetDefinition.Type.Boolean, 84 | null, 85 | XmlaConstants.Access.ReadWrite, 86 | "", 87 | XmlaConstants.Method.DISCOVER, 88 | "In an MDSCHEMA_CUBES request, whether to include sub-elements " 89 | + "(dimensions, hierarchies, levels, measures, named sets) of each " 90 | + "cube."), 91 | 92 | // Mondrian-specific extension to XMLA. 93 | EmitInvisibleMembers( 94 | RowsetDefinition.Type.Boolean, 95 | null, 96 | XmlaConstants.Access.ReadWrite, 97 | "", 98 | XmlaConstants.Method.DISCOVER, 99 | "Whether to include members whose VISIBLE property is false, or " 100 | + "measures whose MEASURE_IS_VISIBLE property is false."), 101 | 102 | EndRange( 103 | RowsetDefinition.Type.Integer, 104 | null, 105 | XmlaConstants.Access.Write, 106 | "-1", 107 | XmlaConstants.Method.EXECUTE, 108 | "An integer value corresponding to a CellOrdinal used to restrict an MDDataSet returned by a command to a specific range of cells. Used in conjunction with the BeginRange property. If unspecified, all cells are returned in the rowset. The value -1 means unspecified."), 109 | 110 | Format( 111 | RowsetDefinition.Type.EnumString, 112 | Olap4jUtil.enumSetAllOf(XmlaConstants.Format.class), 113 | XmlaConstants.Access.Write, 114 | "Native", 115 | XmlaConstants.Method.DISCOVER_AND_EXECUTE, 116 | "Enumerator that determines the format of the returned result set. Values include:\n" 117 | + "Tabular: a flat or hierarchical rowset. Similar to the XML RAW format in SQL. The Format property should be set to Tabular for OLE DB for Data Mining commands.\n" 118 | + "Multidimensional: Indicates that the result set will use the MDDataSet format (Execute method only).\n" 119 | + "Native: The client does not request a specific format, so the provider may return the format appropriate to the query. (The actual result type is identified by namespace of the result.)"), 120 | 121 | LocaleIdentifier( 122 | RowsetDefinition.Type.UnsignedInteger, 123 | null, 124 | XmlaConstants.Access.ReadWrite, 125 | "None", 126 | XmlaConstants.Method.DISCOVER_AND_EXECUTE, 127 | "Use this to read or set the numeric locale identifier for this request. The default is provider-specific.\n" 128 | + "For the complete hexadecimal list of language identifiers, search on \"Language Identifiers\" in the MSDN Library at http://www.msdn.microsoft.com.\n" 129 | + "As an extension to the XMLA standard, Mondrian also allows locale codes as specified by ISO-639 and ISO-3166 and as used by Java; for example 'en-US'.\n"), 130 | 131 | MDXSupport( 132 | RowsetDefinition.Type.EnumString, 133 | Olap4jUtil.enumSetAllOf(XmlaConstants.MdxSupport.class), 134 | XmlaConstants.Access.Read, 135 | "Core", 136 | XmlaConstants.Method.DISCOVER, 137 | "Enumeration that describes the degree of MDX support. At initial release Core is the only value in the enumeration. In future releases, other values will be defined for this enumeration."), 138 | 139 | Password( 140 | RowsetDefinition.Type.String, 141 | null, 142 | org.olap4j.metadata.XmlaConstants.Access.Read, 143 | "", 144 | XmlaConstants.Method.DISCOVER_AND_EXECUTE, 145 | "This property is deprecated in XMLA 1.1. To support legacy applications, the provider accepts but ignores the Password property setting when it is used with the Discover and Execute method"), 146 | 147 | ProviderName( 148 | RowsetDefinition.Type.String, 149 | null, 150 | XmlaConstants.Access.Read, 151 | "olap4j-xmlaserver", 152 | XmlaConstants.Method.DISCOVER, 153 | "The XML for Analysis Provider name."), 154 | 155 | ProviderVersion( 156 | RowsetDefinition.Type.String, 157 | null, 158 | XmlaConstants.Access.Read, 159 | "0.1", 160 | XmlaConstants.Method.DISCOVER, 161 | "The version of the Mondrian XMLA Provider"), 162 | 163 | // Mondrian-specific extension to XMLA. 164 | /** 165 | * @see Enumeration.ResponseMimeType 166 | */ 167 | ResponseMimeType( 168 | RowsetDefinition.Type.String, 169 | null, 170 | XmlaConstants.Access.ReadWrite, 171 | "None", 172 | XmlaConstants.Method.DISCOVER_AND_EXECUTE, 173 | "Accepted mime type for RPC response; accepted are 'text/xml' " 174 | + "(default), 'application/xml' (equivalent to 'text/xml'), or " 175 | + "'application/json'. If not specified, value in the 'Accept' header " 176 | + "of the HTTP request is used."), 177 | 178 | StateSupport( 179 | RowsetDefinition.Type.EnumString, 180 | Olap4jUtil.enumSetAllOf(XmlaConstants.StateSupport.class), 181 | XmlaConstants.Access.Read, 182 | "None", 183 | XmlaConstants.Method.DISCOVER, 184 | "Property that specifies the degree of support in the provider for state. For information about state in XML for Analysis, see \"Support for Statefulness in XML for Analysis.\" Minimum enumeration values are as follows:\n" 185 | + "None - No support for sessions or stateful operations.\n" 186 | + "Sessions - Provider supports sessions."), 187 | 188 | Timeout( 189 | RowsetDefinition.Type.UnsignedInteger, 190 | null, 191 | XmlaConstants.Access.ReadWrite, 192 | "Undefined", 193 | XmlaConstants.Method.DISCOVER_AND_EXECUTE, 194 | "A numeric time-out specifying in seconds the amount of time to wait for a request to be successful."), 195 | 196 | UserName( 197 | RowsetDefinition.Type.String, 198 | null, 199 | XmlaConstants.Access.Read, 200 | "", 201 | XmlaConstants.Method.DISCOVER_AND_EXECUTE, 202 | "Returns the UserName the server associates with the command.\n" 203 | + "This property is deprecated as writeable in XMLA 1.1. To support legacy applications, servers accept but ignore the password setting when it is used with the Execute method."), 204 | 205 | VisualMode( 206 | RowsetDefinition.Type.Enumeration, 207 | Olap4jUtil.enumSetAllOf(XmlaConstants.VisualMode.class), 208 | XmlaConstants.Access.Write, 209 | Integer.toString(XmlaConstants.VisualMode.VISUAL.ordinal()), 210 | XmlaConstants.Method.DISCOVER_AND_EXECUTE, 211 | "This property is equivalent to the OLE DB property, MDPROP_VISUALMODE.\n" 212 | + "The default value for this property is zero (0), equivalent to DBPROPVAL_VISUAL_MODE_DEFAULT."), 213 | 214 | // mondrian-specific property for advanced drill-through 215 | TableFields( 216 | RowsetDefinition.Type.String, 217 | null, 218 | XmlaConstants.Access.Read, 219 | "", 220 | XmlaConstants.Method.DISCOVER_AND_EXECUTE, 221 | "List of fields to return for drill-through.\n" 222 | + "The default value of this property is the empty string," 223 | + "in which case, all fields are returned."), 224 | 225 | // mondrian-specific property for advanced drill-through 226 | AdvancedFlag( 227 | RowsetDefinition.Type.Boolean, 228 | null, 229 | XmlaConstants.Access.Read, 230 | "false", 231 | XmlaConstants.Method.DISCOVER_AND_EXECUTE, 232 | ""); 233 | 234 | final RowsetDefinition.Type type; 235 | final Set enumSet; 236 | final XmlaConstants.Access access; 237 | final XmlaConstants.Method usage; 238 | final String value; 239 | final String description; 240 | 241 | PropertyDefinition( 242 | RowsetDefinition.Type type, 243 | Set enumSet, 244 | XmlaConstants.Access access, 245 | String value, 246 | XmlaConstants.Method usage, 247 | String description) 248 | { 249 | // Line endings must be UNIX style (LF) not Windows style (LF+CR). 250 | // Thus the client will receive the same XML, regardless 251 | // of the server O/S. 252 | assert description.indexOf('\r') == -1; 253 | assert value.indexOf('\r') == -1; 254 | assert (enumSet != null) == type.isEnum(); 255 | this.type = type; 256 | this.enumSet = enumSet; 257 | this.access = access; 258 | this.usage = usage; 259 | this.value = value; 260 | this.description = description; 261 | } 262 | 263 | /** 264 | * Returns the description of this PropertyDefinition. 265 | * 266 | * @return description 267 | */ 268 | public String getDescription() { 269 | return description; 270 | } 271 | } 272 | 273 | // End PropertyDefinition.java 274 | -------------------------------------------------------------------------------- /src/main/java/mondrian/xmla/SaxWriter.java: -------------------------------------------------------------------------------- 1 | /* 2 | // This software is subject to the terms of the Eclipse Public License v1.0 3 | // Agreement, available at the following URL: 4 | // http://www.eclipse.org/legal/epl-v10.html. 5 | // You must accept the terms of that agreement to use this software. 6 | // 7 | // Copyright (C) 2003-2005 Julian Hyde 8 | // Copyright (C) 2005-2010 Pentaho 9 | // All Rights Reserved. 10 | */ 11 | package mondrian.xmla; 12 | 13 | /** 14 | * SaxWriter is similar to a SAX {@link org.xml.sax.ContentHandler} 15 | * which, perversely, converts its events into an output document. 16 | * 17 | * @author jhyde 18 | * @author Gang Chen 19 | * @since 27 April, 2003 20 | */ 21 | public interface SaxWriter { 22 | 23 | public void startDocument(); 24 | 25 | public void endDocument(); 26 | 27 | public void startElement(String name); 28 | 29 | public void startElement(String name, Object... attrs); 30 | 31 | public void endElement(); 32 | 33 | public void element(String name, Object... attrs); 34 | 35 | public void characters(String data); 36 | 37 | /** 38 | * Informs the writer that a sequence of elements of the same name is 39 | * starting. 40 | * 41 | *

For XML, is equivalent to {@code startElement(name)}. 42 | * 43 | *

For JSON, initiates the array construct: 44 | * 45 | *

"name" : [
46 | *   { ... },
47 | *   { ... }
48 | * ]
49 | * 50 | * @param name Element name 51 | * @param subName Child element name 52 | */ 53 | public void startSequence(String name, String subName); 54 | 55 | /** 56 | * Informs the writer that a sequence of elements of the same name has 57 | * ended. 58 | */ 59 | public void endSequence(); 60 | 61 | /** 62 | * Generates a text-only element, {@code <name>data</name>}. 63 | * 64 | *

For XML, this is equivalent to 65 | * 66 | *

startElement(name);
67 | * characters(data);
68 | * endElement();
69 | * 70 | * but for JSON, generates {@code "name": "data"}. 71 | * 72 | * @param name Name of element 73 | * @param data Text content of element 74 | */ 75 | public void textElement(String name, Object data); 76 | 77 | public void completeBeforeElement(String tagName); 78 | 79 | /** 80 | * Sends a piece of text verbatim through the writer. It must be a piece 81 | * of well-formed XML. 82 | */ 83 | public void verbatim(String text); 84 | 85 | /** 86 | * Flushes any unwritten output. 87 | */ 88 | public void flush(); 89 | } 90 | 91 | // End SaxWriter.java 92 | -------------------------------------------------------------------------------- /src/main/java/mondrian/xmla/XmlaConstants.java: -------------------------------------------------------------------------------- 1 | /* 2 | // This software is subject to the terms of the Eclipse Public License v1.0 3 | // Agreement, available at the following URL: 4 | // http://www.eclipse.org/legal/epl-v10.html. 5 | // You must accept the terms of that agreement to use this software. 6 | // 7 | // Copyright (C) 2005-2011 Pentaho 8 | // All Rights Reserved. 9 | */ 10 | package mondrian.xmla; 11 | 12 | /** 13 | * Constants for XML/A. 14 | * 15 | * @author Gang Chen 16 | */ 17 | public interface XmlaConstants { 18 | 19 | /* SOAP 1.1 */ 20 | public static final String NS_SOAP_ENV_1_1 = 21 | "http://schemas.xmlsoap.org/soap/envelope/"; 22 | public static final String NS_SOAP_ENC_1_1 = 23 | "http://schemas.xmlsoap.org/soap/encoding/"; 24 | 25 | /* SOAP 1.2 - currently not supported */ 26 | public static final String NS_SOAP_ENV_1_2 = 27 | "http://www.w3.org/2003/05/soap-envelope"; 28 | public static final String NS_SOAP_ENC_1_2 = 29 | "http://www.w3.org/2003/05/soap-encoding"; 30 | 31 | /* Namespaces for XML */ 32 | public static final String NS_XSD = "http://www.w3.org/2001/XMLSchema"; 33 | public static final String NS_XSI = "http://www.w3.org/2001/XMLSchema-instance"; 34 | 35 | /* Namespaces for XML/A */ 36 | public static final String NS_XMLA = 37 | "urn:schemas-microsoft-com:xml-analysis"; 38 | public static final String NS_XMLA_MDDATASET = 39 | "urn:schemas-microsoft-com:xml-analysis:mddataset"; 40 | public static final String NS_XMLA_EMPTY = 41 | "urn:schemas-microsoft-com:xml-analysis:empty"; 42 | public static final String NS_XMLA_ROWSET = 43 | "urn:schemas-microsoft-com:xml-analysis:rowset"; 44 | public static final String NS_SQL = "urn:schemas-microsoft-com:xml-sql"; 45 | public static final String NS_XMLA_EX = 46 | "urn:schemas-microsoft-com:xml-analysis:exception"; 47 | 48 | public static final String NS_SOAP_SECEXT = 49 | "http://schemas.xmlsoap.org/ws/2002/04/secext"; 50 | 51 | public static final String SOAP_PREFIX = "SOAP-ENV"; 52 | 53 | /** 54 | * Soap Header mustUnderstand attribute name. 55 | */ 56 | public static final String SOAP_MUST_UNDERSTAND_ATTR = "mustUnderstand"; 57 | 58 | /** 59 | * Soap XMLA Header elements and attribute names. 60 | */ 61 | public static final String XMLA_BEGIN_SESSION = "BeginSession"; 62 | public static final String XMLA_SESSION = "Session"; 63 | public static final String XMLA_END_SESSION = "EndSession"; 64 | public static final String XMLA_SESSION_ID = "SessionId"; 65 | public static final String XMLA_SECURITY = "Security"; 66 | 67 | // Names of context keys known by both callbacks and Mondrian code. 68 | 69 | // context key for role name storage 70 | public static final String CONTEXT_ROLE_NAME = "role_name"; 71 | // context key for language (SOAP or JSON) 72 | public static final String CONTEXT_MIME_TYPE = "language"; 73 | // context key for session id storage 74 | public static final String CONTEXT_XMLA_SESSION_ID = "session_id"; 75 | 76 | // Username and password tokens 77 | public static final String CONTEXT_XMLA_USERNAME = "username"; 78 | public static final String CONTEXT_XMLA_PASSWORD = "password"; 79 | 80 | // context key for session state storage 81 | public static final String CONTEXT_XMLA_SESSION_STATE = "SessionState"; 82 | public static final String CONTEXT_XMLA_SESSION_STATE_BEGIN = 83 | "SessionStateBegin"; 84 | public static final String CONTEXT_XMLA_SESSION_STATE_WITHIN = 85 | "SessionStateWithin"; 86 | public static final String CONTEXT_XMLA_SESSION_STATE_END = 87 | "SessionStateEnd"; 88 | 89 | /************************************************************************* 90 | * 91 | * The following are XMLA exception fault codes used as faultcode entries 92 | * in the SOAP Fault element. 93 | * 94 | * If Mondrian Exceptions actually used the "id" attributes found in the 95 | * MondrianResource.xml file, then those would be used as the SOAP Fault 96 | * detail error code values, but, alas they do not show up as part of 97 | * the generated Exception Java code so, here we simply duplicate 98 | * the fault code entry. 99 | * 100 | * Currently, SOAP 1.2 errors are not supported. 101 | * 102 | *************************************************************************/ 103 | 104 | /** 105 | * This is the namespace used to qualify the faultcode identifier. 106 | */ 107 | public static final String MONDRIAN_NAMESPACE = "http://mondrian.sourceforge.net"; 108 | public static final String FAULT_NS_PREFIX = "XA"; 109 | 110 | public static final String FAULT_ACTOR = "Mondrian"; 111 | 112 | // soap 1.1 default faultcodes 113 | public static final String VERSION_MISSMATCH_FAULT_FC = "VersionMismatch"; 114 | public static final String MUST_UNDERSTAND_FAULT_FC = "MustUnderstand"; 115 | public static final String CLIENT_FAULT_FC = "Client"; 116 | public static final String SERVER_FAULT_FC = "Server"; 117 | 118 | //XA:Mondrian.XML.88BA1202 119 | public static final String FAULT_FC_PREFIX = "Mondrian"; 120 | public static final String FAULT_FS_PREFIX = "The Mondrian XML: "; 121 | 122 | ///////////////////////////////////////////////////////////////////////// 123 | // Unmarshall Soap Message : USM 124 | ///////////////////////////////////////////////////////////////////////// 125 | public static final String USM_REQUEST_STATE_CODE = "00USMA01"; 126 | public static final String USM_REQUEST_STATE_FAULT_FS = 127 | "Request input method invoked at illegal time"; 128 | 129 | public static final String USM_REQUEST_INPUT_CODE = "00USMA02"; 130 | public static final String USM_REQUEST_INPUT_FAULT_FS = 131 | "Request input Exception occurred"; 132 | 133 | public static final String USM_DOM_FACTORY_CODE = "00USMB01"; 134 | public static final String USM_DOM_FACTORY_FAULT_FS = 135 | "DocumentBuilder cannot be created which satisfies the configuration " 136 | + "requested"; 137 | 138 | public static final String USM_DOM_PARSE_IO_CODE = "00USMC01"; 139 | public static final String USM_DOM_PARSE_IO_FAULT_FS = 140 | "DOM parse IO errors occur"; 141 | 142 | public static final String USM_DOM_PARSE_CODE = "00USMC02"; 143 | public static final String USM_DOM_PARSE_FAULT_FS = 144 | "DOM parse errors occur"; 145 | 146 | // unknown error while unmarshalling soap message 147 | public static final String USM_UNKNOWN_CODE = "00USMU01"; 148 | public static final String USM_UNKNOWN_FAULT_FS = 149 | "Unknown error unmarshalling soap message"; 150 | 151 | ///////////////////////////////////////////////////////////////////////// 152 | // Callback http header : CHH 153 | ///////////////////////////////////////////////////////////////////////// 154 | public static final String CHH_CODE = "00CHHA01"; 155 | public static final String CHH_FAULT_FS = 156 | "Error in Callback processHttpHeader"; 157 | 158 | public static final String CHH_AUTHORIZATION_CODE = "00CHHA02"; 159 | public static final String CHH_AUTHORIZATION_FAULT_FS = 160 | "Error in Callback processHttpHeader Authorization"; 161 | 162 | ///////////////////////////////////////////////////////////////////////// 163 | // Callback Pre-Action : CPREA 164 | ///////////////////////////////////////////////////////////////////////// 165 | public static final String CPREA_CODE = "00CPREA01"; 166 | public static final String CPREA_FAULT_FS = 167 | "Error in Callback PreAction"; 168 | 169 | /* 170 | public static final String CPREA_AUTHORIZATION_CODE = "00CPREA02"; 171 | public static final String CPREA_AUTHORIZATION_FAULT_FS = 172 | "Error Callback PreAction Authorization"; 173 | */ 174 | 175 | ///////////////////////////////////////////////////////////////////////// 176 | // Handle Soap Header : HSH 177 | ///////////////////////////////////////////////////////////////////////// 178 | public static final String HSH_MUST_UNDERSTAND_CODE = "00HSHA01"; 179 | public static final String HSH_MUST_UNDERSTAND_FAULT_FS = 180 | "SOAP Header must understand element not recognized"; 181 | 182 | // This is used to signal XMLA clients supporting Soap header session ids 183 | // that the client's metadata may no longer be valid. 184 | public static final String HSH_BAD_SESSION_ID_CODE = "00HSHB01"; 185 | public static final String HSH_BAD_SESSION_ID_FAULT_FS = 186 | "Bad Session Id, re-start session"; 187 | 188 | // unknown error while handle soap header 189 | public static final String HSH_UNKNOWN_CODE = "00HSHU01"; 190 | public static final String HSH_UNKNOWN_FAULT_FS = 191 | "Unknown error handle soap header"; 192 | 193 | ///////////////////////////////////////////////////////////////////////// 194 | // Handle Soap Body : HSB 195 | ///////////////////////////////////////////////////////////////////////// 196 | public static final String HSB_BAD_SOAP_BODY_CODE = "00HSBA01"; 197 | public static final String HSB_BAD_SOAP_BODY_FAULT_FS = 198 | "SOAP Body not correctly formed"; 199 | 200 | public static final String HSB_PROCESS_CODE = "00HSBB01"; 201 | public static final String HSB_PROCESS_FAULT_FS = 202 | "XMLA SOAP Body processing error"; 203 | 204 | public static final String HSB_BAD_METHOD_CODE = "00HSBB02"; 205 | public static final String HSB_BAD_METHOD_FAULT_FS = 206 | "XMLA SOAP bad method"; 207 | 208 | public static final String HSB_BAD_METHOD_NS_CODE = "00HSBB03"; 209 | public static final String HSB_BAD_METHOD_NS_FAULT_FS = 210 | "XMLA SOAP bad method namespace"; 211 | 212 | public static final String HSB_BAD_REQUEST_TYPE_CODE = "00HSBB04"; 213 | public static final String HSB_BAD_REQUEST_TYPE_FAULT_FS = 214 | "XMLA SOAP bad Discover RequestType element"; 215 | 216 | public static final String HSB_BAD_RESTRICTIONS_CODE = "00HSBB05"; 217 | public static final String HSB_BAD_RESTRICTIONS_FAULT_FS = 218 | "XMLA SOAP bad Discover Restrictions element"; 219 | 220 | public static final String HSB_BAD_PROPERTIES_CODE = "00HSBB06"; 221 | public static final String HSB_BAD_PROPERTIES_FAULT_FS = 222 | "XMLA SOAP bad Discover or Execute Properties element"; 223 | 224 | public static final String HSB_BAD_COMMAND_CODE = "00HSBB07"; 225 | public static final String HSB_BAD_COMMAND_FAULT_FS = 226 | "XMLA SOAP bad Execute Command element"; 227 | 228 | public static final String HSB_BAD_RESTRICTION_LIST_CODE = "00HSBB08"; 229 | public static final String HSB_BAD_RESTRICTION_LIST_FAULT_FS = 230 | "XMLA SOAP too many Discover RestrictionList element"; 231 | 232 | public static final String HSB_BAD_PROPERTIES_LIST_CODE = "00HSBB09"; 233 | public static final String HSB_BAD_PROPERTIES_LIST_FAULT_FS = 234 | "XMLA SOAP bad Discover or Execute PropertyList element"; 235 | 236 | public static final String HSB_BAD_STATEMENT_CODE = "00HSBB10"; 237 | public static final String HSB_BAD_STATEMENT_FAULT_FS = 238 | "XMLA SOAP bad Execute Statement element"; 239 | 240 | public static final String HSB_BAD_NON_NULLABLE_COLUMN_CODE = "00HSBB16"; 241 | public static final String HSB_BAD_NON_NULLABLE_COLUMN_FAULT_FS = 242 | "XMLA SOAP non-nullable column"; 243 | 244 | 245 | public static final String HSB_CONNECTION_DATA_SOURCE_CODE = "00HSBC01"; 246 | public static final String HSB_CONNECTION_DATA_SOURCE_FAULT_FS = 247 | "XMLA connection datasource not found"; 248 | 249 | public static final String HSB_ACCESS_DENIED_CODE = "00HSBC02"; 250 | public static final String HSB_ACCESS_DENIED_FAULT_FS = 251 | "XMLA connection with role must be authenticated"; 252 | 253 | public static final String HSB_PARSE_QUERY_CODE = "00HSBD01"; 254 | public static final String HSB_PARSE_QUERY_FAULT_FS = 255 | "XMLA MDX parse failed"; 256 | 257 | public static final String HSB_EXECUTE_QUERY_CODE = "00HSBD02"; 258 | public static final String HSB_EXECUTE_QUERY_FAULT_FS = 259 | "XMLA MDX execute failed"; 260 | 261 | public static final String HSB_DISCOVER_FORMAT_CODE = "00HSBE01"; 262 | public static final String HSB_DISCOVER_FORMAT_FAULT_FS = 263 | "XMLA Discover format error"; 264 | 265 | public static final String HSB_DRILL_THROUGH_FORMAT_CODE = "00HSBE02"; 266 | public static final String HSB_DRILL_THROUGH_FORMAT_FAULT_FS = 267 | "XMLA Drill Through format error"; 268 | 269 | public static final String HSB_DISCOVER_UNPARSE_CODE = "00HSBE02"; 270 | public static final String HSB_DISCOVER_UNPARSE_FAULT_FS = 271 | "XMLA Discover unparse results error"; 272 | 273 | public static final String HSB_EXECUTE_UNPARSE_CODE = "00HSBE03"; 274 | public static final String HSB_EXECUTE_UNPARSE_FAULT_FS = 275 | "XMLA Execute unparse results error"; 276 | 277 | public static final String HSB_DRILL_THROUGH_NOT_ALLOWED_CODE = "00HSBF01"; 278 | public static final String HSB_DRILL_THROUGH_NOT_ALLOWED_FAULT_FS = 279 | "XMLA Drill Through not allowed"; 280 | 281 | public static final String HSB_DRILL_THROUGH_SQL_CODE = "00HSBF02"; 282 | public static final String HSB_DRILL_THROUGH_SQL_FAULT_FS = 283 | "XMLA Drill Through SQL error"; 284 | 285 | // unknown error while handle soap body 286 | public static final String HSB_UNKNOWN_CODE = "00HSBU01"; 287 | public static final String HSB_UNKNOWN_FAULT_FS = 288 | "Unknown error handle soap body"; 289 | 290 | ///////////////////////////////////////////////////////////////////////// 291 | // Callback Post-Action : CPOSTA 292 | ///////////////////////////////////////////////////////////////////////// 293 | public static final String CPOSTA_CODE = "00CPOSTA01"; 294 | public static final String CPOSTA_FAULT_FS = 295 | "Error in Callback PostAction"; 296 | 297 | ///////////////////////////////////////////////////////////////////////// 298 | // Marshall Soap Message : MSM 299 | ///////////////////////////////////////////////////////////////////////// 300 | 301 | // unknown error while marshalling soap message 302 | public static final String MSM_UNKNOWN_CODE = "00MSMU01"; 303 | public static final String MSM_UNKNOWN_FAULT_FS = 304 | "Unknown error marshalling soap message"; 305 | 306 | ///////////////////////////////////////////////////////////////////////// 307 | // Unknown error : UE 308 | ///////////////////////////////////////////////////////////////////////// 309 | public static final String UNKNOWN_ERROR_CODE = "00UE001"; 310 | // While this is actually "unknown", for users "internal" 311 | // is a better term 312 | public static final String UNKNOWN_ERROR_FAULT_FS = "Internal Error"; 313 | 314 | } 315 | 316 | // End XmlaConstants.java 317 | -------------------------------------------------------------------------------- /src/main/java/mondrian/xmla/XmlaException.java: -------------------------------------------------------------------------------- 1 | /* 2 | // This software is subject to the terms of the Eclipse Public License v1.0 3 | // Agreement, available at the following URL: 4 | // http://www.eclipse.org/legal/epl-v10.html. 5 | // You must accept the terms of that agreement to use this software. 6 | // 7 | // Copyright (C) 2006-2012 Pentaho and others 8 | // All Rights Reserved. 9 | */ 10 | package mondrian.xmla; 11 | 12 | /** 13 | * An exception thrown while processing an XMLA request. The faultcode 14 | * corresponds to the SOAP Fault faultcode and the faultstring 15 | * to the SOAP Fault faultstring. 16 | * 17 | * @author Richard M. Emberson 18 | */ 19 | public class XmlaException extends RuntimeException { 20 | 21 | public static String formatFaultCode(XmlaException xex) { 22 | return formatFaultCode(xex.getFaultCode(), xex.getCode()); 23 | } 24 | public static String formatFaultCode(String faultCode, String code) { 25 | return formatFaultCode( 26 | XmlaConstants.SOAP_PREFIX, 27 | faultCode, code); 28 | } 29 | 30 | public static String formatFaultCode( 31 | String nsPrefix, 32 | String faultCode, String code) 33 | { 34 | return nsPrefix 35 | + ':' 36 | + faultCode 37 | + '.' 38 | + code; 39 | } 40 | public static String formatDetail(String msg) { 41 | return XmlaConstants.FAULT_FS_PREFIX + msg; 42 | } 43 | 44 | public static Throwable getRootCause(Throwable throwable) { 45 | Throwable t = throwable; 46 | while (t.getCause() != null) { 47 | t = t.getCause(); 48 | } 49 | return t; 50 | } 51 | 52 | private final String faultCode; 53 | private final String code; 54 | private final String faultString; 55 | 56 | public XmlaException( 57 | String faultCode, 58 | String code, 59 | String faultString, 60 | Throwable cause) 61 | { 62 | super(faultString, cause); 63 | this.faultCode = faultCode; 64 | this.code = code; 65 | this.faultString = faultString; 66 | } 67 | 68 | public String getFaultCode() { 69 | return faultCode; 70 | } 71 | public String getCode() { 72 | return code; 73 | } 74 | public String getFaultString() { 75 | return faultString; 76 | } 77 | public String getDetail() { 78 | Throwable t = getCause(); 79 | t = getRootCause(t); 80 | String detail = t.getMessage(); 81 | return (detail != null) 82 | ? detail 83 | : t.getClass().getName(); 84 | } 85 | } 86 | 87 | // End XmlaException.java 88 | 89 | -------------------------------------------------------------------------------- /src/main/java/mondrian/xmla/XmlaRequest.java: -------------------------------------------------------------------------------- 1 | /* 2 | // This software is subject to the terms of the Eclipse Public License v1.0 3 | // Agreement, available at the following URL: 4 | // http://www.eclipse.org/legal/epl-v10.html. 5 | // You must accept the terms of that agreement to use this software. 6 | // 7 | // Copyright (C) 2005-2011 Pentaho 8 | // All Rights Reserved. 9 | */ 10 | package mondrian.xmla; 11 | 12 | import org.olap4j.metadata.XmlaConstants; 13 | 14 | import java.util.Map; 15 | 16 | /** 17 | * XML/A request interface. 18 | * 19 | * @author Gang Chen 20 | */ 21 | public interface XmlaRequest { 22 | 23 | /** 24 | * Indicate DISCOVER or EXECUTE method. 25 | */ 26 | XmlaConstants.Method getMethod(); 27 | 28 | /** 29 | * Properties of XML/A request. 30 | */ 31 | Map getProperties(); 32 | 33 | /** 34 | * Restrictions of DISCOVER method. 35 | * 36 | *

If the value is a list of strings, the restriction passes if the 37 | * column has one of the values. 38 | */ 39 | Map getRestrictions(); 40 | 41 | /** 42 | * Statement of EXECUTE method. 43 | */ 44 | String getStatement(); 45 | 46 | /** 47 | * Role name binds with this XML/A request. Maybe null. 48 | */ 49 | String getRoleName(); 50 | 51 | /** 52 | * Request type of DISCOVER method. 53 | */ 54 | String getRequestType(); 55 | 56 | /** 57 | * Indicate whether statement is a drill through statement of 58 | * EXECUTE method. 59 | */ 60 | boolean isDrillThrough(); 61 | 62 | /** 63 | * The username to use to open the underlying olap4j connection. 64 | * Can be null. 65 | */ 66 | String getUsername(); 67 | 68 | /** 69 | * The password to use to open the underlying olap4j connection. 70 | * Can be null. 71 | */ 72 | String getPassword(); 73 | 74 | /** 75 | * Returns the id of the session this request belongs to. 76 | * 77 | *

Not necessarily the same as the HTTP session: the SOAP request 78 | * contains its own session information.

79 | * 80 | *

The session id is used to retrieve existing olap connections. And 81 | * username / password only need to be passed on the first request in a 82 | * session.

83 | * 84 | * @return Id of the session 85 | */ 86 | String getSessionId(); 87 | } 88 | 89 | // End XmlaRequest.java 90 | -------------------------------------------------------------------------------- /src/main/java/mondrian/xmla/XmlaRequestCallback.java: -------------------------------------------------------------------------------- 1 | /* 2 | // This software is subject to the terms of the Eclipse Public License v1.0 3 | // Agreement, available at the following URL: 4 | // http://www.eclipse.org/legal/epl-v10.html. 5 | // You must accept the terms of that agreement to use this software. 6 | // 7 | // Copyright (C) 2005-2007 Pentaho 8 | // All Rights Reserved. 9 | */ 10 | package mondrian.xmla; 11 | 12 | import org.w3c.dom.Element; 13 | 14 | import java.util.Map; 15 | import javax.servlet.ServletConfig; 16 | import javax.servlet.ServletException; 17 | import javax.servlet.http.HttpServletRequest; 18 | import javax.servlet.http.HttpServletResponse; 19 | 20 | 21 | /** 22 | * Extract data from HTTP request, SOAP header for following XML/A request.

23 | * 24 | * Fill context binding with whatever data you want, then use them in 25 | * {@link XmlaServlet#handleSoapHeader} and {@link XmlaServlet#handleSoapBody}. 26 | * 27 | * @author Gang Chen 28 | */ 29 | public interface XmlaRequestCallback { 30 | String AUTHORIZATION = "Authorization"; 31 | String EXPECT = "Expect"; 32 | String EXPECT_100_CONTINUE = "100-continue"; 33 | 34 | public class Helper { 35 | public static XmlaException authorizationException(Exception ex) { 36 | return new XmlaException( 37 | XmlaConstants.CLIENT_FAULT_FC, 38 | XmlaConstants.CHH_AUTHORIZATION_CODE, 39 | XmlaConstants.CHH_AUTHORIZATION_FAULT_FS, 40 | ex); 41 | } 42 | 43 | 44 | // HTTP/1.1 100 Continue 45 | // Server: Microsoft-IIS/5.0 46 | // Date: Tue, 21 Feb 2006 21:07:57 GMT 47 | // X-Powered-By: ASP.NET 48 | public static void generatedExpectResponse( 49 | HttpServletRequest request, 50 | HttpServletResponse response, 51 | Map context) throws Exception 52 | { 53 | response.reset(); 54 | response.setStatus(HttpServletResponse.SC_CONTINUE); 55 | } 56 | } 57 | 58 | void init(ServletConfig servletConfig) throws ServletException; 59 | 60 | /** 61 | * Process the request header items. Specifically if present the 62 | * Authorization and Expect headers. If the Authorization header is 63 | * present, then the callback can validate the user/password. If 64 | * authentication fails, the callback should throw an XmlaException 65 | * with the correct XmlaConstants values. The XmlaRequestCallback.Helper 66 | * class contains the authorizationException method that can be used 67 | * by a callback to generate the XmlaException with the correct values. 68 | * If the Expect header is set with "100-continue", then it is 69 | * upto the callback to create the appropriate response and return false. 70 | * In this case, the XmlaServlet stops processing and returns the 71 | * response to the client application. To facilitate the generation of 72 | * the response, the XmlaRequestCallback.Helper has the method 73 | * generatedExpectResponse that can be called by the callback. 74 | *

75 | * Note that it is upto the XMLA client to determine whether or not 76 | * there is an Expect header entry (ADOMD.NET seems to like to do this). 77 | * 78 | * @return true if XmlaServlet handling is to continue and false if 79 | * there was an Expect header "100-continue". 80 | */ 81 | boolean processHttpHeader( 82 | HttpServletRequest request, 83 | HttpServletResponse response, 84 | Map context) throws Exception; 85 | 86 | /** 87 | * This is called after the headers have been process but before the 88 | * body (DISCOVER/EXECUTE) has been processed. 89 | * 90 | */ 91 | void preAction( 92 | HttpServletRequest request, 93 | Element[] requestSoapParts, 94 | Map context) throws Exception; 95 | 96 | /** 97 | * The Callback is requested to generate a sequence id string. This 98 | * sequence id was requested by the XMLA client and will be used 99 | * for all subsequent communications in the Soap Header block. 100 | * 101 | * Implementation can return null if they do not want 102 | * to generate a custom session ID, in which case, the default algorithm 103 | * to generate session IDs will be used. 104 | * @param context The context of this query. 105 | * @return An arbitrary session id to use, or null. 106 | */ 107 | String generateSessionId(Map context); 108 | 109 | /** 110 | * This is called after all Mondrian processing (DISCOVER/EXECUTE) has 111 | * occurred. 112 | * 113 | */ 114 | void postAction( 115 | HttpServletRequest request, 116 | HttpServletResponse response, 117 | byte[][] responseSoapParts, 118 | Map context) throws Exception; 119 | } 120 | 121 | // End XmlaRequestCallback.java 122 | -------------------------------------------------------------------------------- /src/main/java/mondrian/xmla/XmlaResponse.java: -------------------------------------------------------------------------------- 1 | /* 2 | // This software is subject to the terms of the Eclipse Public License v1.0 3 | // Agreement, available at the following URL: 4 | // http://www.eclipse.org/legal/epl-v10.html. 5 | // You must accept the terms of that agreement to use this software. 6 | // 7 | // Copyright (C) 2005-2006 Pentaho and others 8 | // All Rights Reserved. 9 | */ 10 | package mondrian.xmla; 11 | 12 | /** 13 | * XML/A response interface. 14 | * 15 | * @author Gang Chen 16 | */ 17 | public interface XmlaResponse { 18 | 19 | /** 20 | * Report XML/A error (not SOAP fault). 21 | */ 22 | public void error(Throwable t); 23 | 24 | /** 25 | * Get helper for writing XML document. 26 | */ 27 | public SaxWriter getWriter(); 28 | } 29 | 30 | // End XmlaResponse.java 31 | -------------------------------------------------------------------------------- /src/main/java/mondrian/xmla/XmlaServlet.java: -------------------------------------------------------------------------------- 1 | /* 2 | // This software is subject to the terms of the Eclipse Public License v1.0 3 | // Agreement, available at the following URL: 4 | // http://www.eclipse.org/legal/epl-v10.html. 5 | // You must accept the terms of that agreement to use this software. 6 | // 7 | // Copyright (C) 2003-2005 Julian Hyde 8 | // Copyright (C) 2005-2011 Pentaho 9 | // All Rights Reserved. 10 | */ 11 | package mondrian.xmla; 12 | 13 | import org.apache.log4j.Logger; 14 | 15 | import org.w3c.dom.Element; 16 | 17 | import java.io.IOException; 18 | import java.io.UnsupportedEncodingException; 19 | import java.util.*; 20 | import javax.servlet.ServletConfig; 21 | import javax.servlet.ServletException; 22 | import javax.servlet.http.*; 23 | 24 | /** 25 | * Base XML/A servlet. 26 | * 27 | * @author Gang Chen 28 | * @since December, 2005 29 | */ 30 | public abstract class XmlaServlet 31 | extends HttpServlet 32 | implements XmlaConstants 33 | { 34 | protected static final Logger LOGGER = Logger.getLogger(XmlaServlet.class); 35 | 36 | public static final String PARAM_DATASOURCES_CONFIG = "DataSourcesConfig"; 37 | public static final String PARAM_OPTIONAL_DATASOURCE_CONFIG = 38 | "OptionalDataSourceConfig"; 39 | public static final String PARAM_CHAR_ENCODING = "CharacterEncoding"; 40 | public static final String PARAM_CALLBACKS = "Callbacks"; 41 | 42 | protected XmlaHandler xmlaHandler = null; 43 | protected String charEncoding = null; 44 | private final List callbackList = 45 | new ArrayList(); 46 | 47 | private XmlaHandler.ConnectionFactory connectionFactory; 48 | 49 | public enum Phase { 50 | VALIDATE_HTTP_HEAD, 51 | INITIAL_PARSE, 52 | CALLBACK_PRE_ACTION, 53 | PROCESS_HEADER, 54 | PROCESS_BODY, 55 | CALLBACK_POST_ACTION, 56 | SEND_RESPONSE, 57 | SEND_ERROR 58 | } 59 | 60 | /** 61 | * Returns true if paramName's value is not null and 'true'. 62 | */ 63 | public static boolean getBooleanInitParameter( 64 | ServletConfig servletConfig, 65 | String paramName) 66 | { 67 | String paramValue = servletConfig.getInitParameter(paramName); 68 | return paramValue != null && Boolean.valueOf(paramValue); 69 | } 70 | 71 | public static boolean getParameter( 72 | HttpServletRequest req, 73 | String paramName) 74 | { 75 | String paramValue = req.getParameter(paramName); 76 | return paramValue != null && Boolean.valueOf(paramValue); 77 | } 78 | 79 | public XmlaServlet() { 80 | } 81 | 82 | 83 | /** 84 | * Initializes servlet and XML/A handler. 85 | * 86 | */ 87 | public void init(ServletConfig servletConfig) 88 | throws ServletException 89 | { 90 | super.init(servletConfig); 91 | 92 | // init: charEncoding 93 | initCharEncodingHandler(servletConfig); 94 | 95 | // init: callbacks 96 | initCallbacks(servletConfig); 97 | 98 | this.connectionFactory = createConnectionFactory(servletConfig); 99 | } 100 | 101 | protected abstract XmlaHandler.ConnectionFactory createConnectionFactory( 102 | ServletConfig servletConfig) 103 | throws ServletException; 104 | 105 | /** 106 | * Gets (creating if needed) the XmlaHandler. 107 | * 108 | * @return XMLA handler 109 | */ 110 | protected XmlaHandler getXmlaHandler() { 111 | if (this.xmlaHandler == null) { 112 | this.xmlaHandler = 113 | new XmlaHandler( 114 | connectionFactory, 115 | "cxmla"); 116 | } 117 | return this.xmlaHandler; 118 | } 119 | 120 | /** 121 | * Registers a callback. 122 | */ 123 | protected final void addCallback(XmlaRequestCallback callback) { 124 | callbackList.add(callback); 125 | } 126 | 127 | /** 128 | * Returns the list of callbacks. The list is immutable. 129 | * 130 | * @return list of callbacks 131 | */ 132 | protected final List getCallbacks() { 133 | return Collections.unmodifiableList(callbackList); 134 | } 135 | 136 | /** 137 | * Main entry for HTTP post method 138 | * 139 | */ 140 | protected void doPost( 141 | HttpServletRequest request, 142 | HttpServletResponse response) 143 | throws ServletException, IOException 144 | { 145 | // Request Soap Header and Body 146 | // header [0] and body [1] 147 | Element[] requestSoapParts = new Element[2]; 148 | 149 | // Response Soap Header and Body 150 | // An array allows response parts to be passed into callback 151 | // and possible modifications returned. 152 | // response header in [0] and response body in [1] 153 | byte[][] responseSoapParts = new byte[2][]; 154 | 155 | Phase phase = Phase.VALIDATE_HTTP_HEAD; 156 | Enumeration.ResponseMimeType mimeType = 157 | Enumeration.ResponseMimeType.SOAP; 158 | 159 | try { 160 | if (charEncoding != null) { 161 | try { 162 | request.setCharacterEncoding(charEncoding); 163 | response.setCharacterEncoding(charEncoding); 164 | } catch (UnsupportedEncodingException uee) { 165 | charEncoding = null; 166 | LOGGER.warn( 167 | "Unsupported character encoding '" + charEncoding 168 | + "': Use default character encoding from HTTP client " 169 | + "for now"); 170 | } 171 | } 172 | 173 | response.setContentType(mimeType.getMimeType()); 174 | 175 | Map context = new HashMap(); 176 | 177 | try { 178 | if (LOGGER.isDebugEnabled()) { 179 | LOGGER.debug("Invoking validate http header callbacks"); 180 | } 181 | for (XmlaRequestCallback callback : getCallbacks()) { 182 | if (!callback.processHttpHeader( 183 | request, 184 | response, 185 | context)) 186 | { 187 | return; 188 | } 189 | } 190 | } catch (XmlaException xex) { 191 | LOGGER.error( 192 | "Errors when invoking callbacks validateHttpHeader", xex); 193 | handleFault(response, responseSoapParts, phase, xex); 194 | phase = Phase.SEND_ERROR; 195 | marshallSoapMessage(response, responseSoapParts, mimeType); 196 | return; 197 | } catch (Exception ex) { 198 | LOGGER.error( 199 | "Errors when invoking callbacks validateHttpHeader", ex); 200 | handleFault( 201 | response, responseSoapParts, 202 | phase, 203 | new XmlaException( 204 | SERVER_FAULT_FC, 205 | CHH_CODE, 206 | CHH_FAULT_FS, 207 | ex)); 208 | phase = Phase.SEND_ERROR; 209 | marshallSoapMessage(response, responseSoapParts, mimeType); 210 | return; 211 | } 212 | 213 | 214 | phase = Phase.INITIAL_PARSE; 215 | 216 | try { 217 | if (LOGGER.isDebugEnabled()) { 218 | LOGGER.debug("Unmarshalling SOAP message"); 219 | } 220 | 221 | // check request's content type 222 | String contentType = request.getContentType(); 223 | if (contentType == null 224 | || !contentType.contains("text/xml")) 225 | { 226 | throw new IllegalArgumentException( 227 | "Only accepts content type 'text/xml', not '" 228 | + contentType + "'"); 229 | } 230 | 231 | // are they asking for a JSON response? 232 | String accept = request.getHeader("Accept"); 233 | if (accept != null) { 234 | mimeType = XmlaUtil.chooseResponseMimeType(accept); 235 | if (mimeType == null) { 236 | throw new IllegalArgumentException( 237 | "Accept header '" + accept + "' is not a supported" 238 | + " response content type. Allowed values:" 239 | + " text/xml, application/xml, application/json."); 240 | } 241 | if (mimeType != Enumeration.ResponseMimeType.SOAP) { 242 | response.setContentType(mimeType.getMimeType()); 243 | } 244 | } 245 | context.put(CONTEXT_MIME_TYPE, mimeType); 246 | 247 | unmarshallSoapMessage(request, requestSoapParts); 248 | } catch (XmlaException xex) { 249 | LOGGER.error("Unable to unmarshall SOAP message", xex); 250 | handleFault(response, responseSoapParts, phase, xex); 251 | phase = Phase.SEND_ERROR; 252 | marshallSoapMessage(response, responseSoapParts, mimeType); 253 | return; 254 | } 255 | 256 | phase = Phase.PROCESS_HEADER; 257 | 258 | try { 259 | if (LOGGER.isDebugEnabled()) { 260 | LOGGER.debug("Handling XML/A message header"); 261 | } 262 | 263 | // process application specified SOAP header here 264 | handleSoapHeader( 265 | response, 266 | requestSoapParts, 267 | responseSoapParts, 268 | context); 269 | } catch (XmlaException xex) { 270 | LOGGER.error("Errors when handling XML/A message", xex); 271 | handleFault(response, responseSoapParts, phase, xex); 272 | phase = Phase.SEND_ERROR; 273 | marshallSoapMessage(response, responseSoapParts, mimeType); 274 | return; 275 | } 276 | 277 | phase = Phase.CALLBACK_PRE_ACTION; 278 | 279 | 280 | try { 281 | if (LOGGER.isDebugEnabled()) { 282 | LOGGER.debug("Invoking callbacks preAction"); 283 | } 284 | 285 | for (XmlaRequestCallback callback : getCallbacks()) { 286 | callback.preAction(request, requestSoapParts, context); 287 | } 288 | } catch (XmlaException xex) { 289 | LOGGER.error("Errors when invoking callbacks preaction", xex); 290 | handleFault(response, responseSoapParts, phase, xex); 291 | phase = Phase.SEND_ERROR; 292 | marshallSoapMessage(response, responseSoapParts, mimeType); 293 | return; 294 | } catch (Exception ex) { 295 | LOGGER.error("Errors when invoking callbacks preaction", ex); 296 | handleFault( 297 | response, responseSoapParts, 298 | phase, 299 | new XmlaException( 300 | SERVER_FAULT_FC, 301 | CPREA_CODE, 302 | CPREA_FAULT_FS, 303 | ex)); 304 | phase = Phase.SEND_ERROR; 305 | marshallSoapMessage(response, responseSoapParts, mimeType); 306 | return; 307 | } 308 | 309 | phase = Phase.PROCESS_BODY; 310 | 311 | try { 312 | if (LOGGER.isDebugEnabled()) { 313 | LOGGER.debug("Handling XML/A message body"); 314 | } 315 | 316 | // process XML/A request 317 | handleSoapBody( 318 | response, 319 | requestSoapParts, 320 | responseSoapParts, 321 | context); 322 | } catch (XmlaException xex) { 323 | LOGGER.error("Errors when handling XML/A message", xex); 324 | handleFault(response, responseSoapParts, phase, xex); 325 | phase = Phase.SEND_ERROR; 326 | marshallSoapMessage(response, responseSoapParts, mimeType); 327 | return; 328 | } 329 | 330 | mimeType = 331 | (Enumeration.ResponseMimeType) context.get(CONTEXT_MIME_TYPE); 332 | 333 | phase = Phase.CALLBACK_POST_ACTION; 334 | 335 | try { 336 | if (LOGGER.isDebugEnabled()) { 337 | LOGGER.debug("Invoking callbacks postAction"); 338 | } 339 | 340 | for (XmlaRequestCallback callback : getCallbacks()) { 341 | callback.postAction( 342 | request, response, 343 | responseSoapParts, context); 344 | } 345 | } catch (XmlaException xex) { 346 | LOGGER.error("Errors when invoking callbacks postaction", xex); 347 | handleFault(response, responseSoapParts, phase, xex); 348 | phase = Phase.SEND_ERROR; 349 | marshallSoapMessage(response, responseSoapParts, mimeType); 350 | return; 351 | } catch (Exception ex) { 352 | LOGGER.error("Errors when invoking callbacks postaction", ex); 353 | handleFault( 354 | response, 355 | responseSoapParts, 356 | phase, 357 | new XmlaException( 358 | SERVER_FAULT_FC, 359 | CPOSTA_CODE, 360 | CPOSTA_FAULT_FS, 361 | ex)); 362 | phase = Phase.SEND_ERROR; 363 | marshallSoapMessage(response, responseSoapParts, mimeType); 364 | return; 365 | } 366 | 367 | phase = Phase.SEND_RESPONSE; 368 | 369 | try { 370 | response.setStatus(HttpServletResponse.SC_OK); 371 | marshallSoapMessage(response, responseSoapParts, mimeType); 372 | } catch (XmlaException xex) { 373 | LOGGER.error("Errors when handling XML/A message", xex); 374 | handleFault(response, responseSoapParts, phase, xex); 375 | phase = Phase.SEND_ERROR; 376 | marshallSoapMessage(response, responseSoapParts, mimeType); 377 | } 378 | } catch (Throwable t) { 379 | LOGGER.error("Unknown Error when handling XML/A message", t); 380 | handleFault(response, responseSoapParts, phase, t); 381 | marshallSoapMessage(response, responseSoapParts, mimeType); 382 | } 383 | } 384 | 385 | /** 386 | * Implement to provide application specified SOAP unmarshalling algorithm. 387 | */ 388 | protected abstract void unmarshallSoapMessage( 389 | HttpServletRequest request, 390 | Element[] requestSoapParts) 391 | throws XmlaException; 392 | 393 | /** 394 | * Implement to handle application specified SOAP header. 395 | */ 396 | protected abstract void handleSoapHeader( 397 | HttpServletResponse response, 398 | Element[] requestSoapParts, 399 | byte[][] responseSoapParts, 400 | Map context) 401 | throws XmlaException; 402 | 403 | /** 404 | * Implement to handle XML/A request. 405 | */ 406 | protected abstract void handleSoapBody( 407 | HttpServletResponse response, 408 | Element[] requestSoapParts, 409 | byte[][] responseSoapParts, 410 | Map context) 411 | throws XmlaException; 412 | 413 | /** 414 | * Implement to provide application specified SOAP marshalling algorithm. 415 | */ 416 | protected abstract void marshallSoapMessage( 417 | HttpServletResponse response, 418 | byte[][] responseSoapParts, 419 | Enumeration.ResponseMimeType responseMimeType) 420 | throws XmlaException; 421 | 422 | /** 423 | * Implement to application specified handler of SOAP fualt. 424 | */ 425 | protected abstract void handleFault( 426 | HttpServletResponse response, 427 | byte[][] responseSoapParts, 428 | Phase phase, 429 | Throwable t); 430 | 431 | /** 432 | * Initialize character encoding 433 | */ 434 | protected void initCharEncodingHandler(ServletConfig servletConfig) { 435 | String paramValue = servletConfig.getInitParameter(PARAM_CHAR_ENCODING); 436 | if (paramValue != null) { 437 | this.charEncoding = paramValue; 438 | } else { 439 | this.charEncoding = null; 440 | LOGGER.warn("Use default character encoding from HTTP client"); 441 | } 442 | } 443 | 444 | /** 445 | * Registers callbacks configured in web.xml. 446 | */ 447 | protected void initCallbacks(ServletConfig servletConfig) { 448 | String callbacksValue = servletConfig.getInitParameter(PARAM_CALLBACKS); 449 | 450 | if (callbacksValue != null) { 451 | String[] classNames = callbacksValue.split(";"); 452 | 453 | int count = 0; 454 | nextCallback: 455 | for (String className1 : classNames) { 456 | String className = className1.trim(); 457 | 458 | try { 459 | Class cls = Class.forName(className); 460 | if (XmlaRequestCallback.class.isAssignableFrom(cls)) { 461 | XmlaRequestCallback callback = 462 | (XmlaRequestCallback) cls.newInstance(); 463 | 464 | try { 465 | callback.init(servletConfig); 466 | } catch (Exception e) { 467 | LOGGER.warn( 468 | "Failed to initialize callback '" 469 | + className + "'", 470 | e); 471 | continue nextCallback; 472 | } 473 | 474 | addCallback(callback); 475 | count++; 476 | 477 | if (LOGGER.isDebugEnabled()) { 478 | LOGGER.debug( 479 | "Register callback '" + className + "'"); 480 | } 481 | } else { 482 | LOGGER.warn( 483 | "'" + className + "' is not an implementation of '" 484 | + XmlaRequestCallback.class + "'"); 485 | } 486 | } catch (ClassNotFoundException cnfe) { 487 | LOGGER.warn( 488 | "Callback class '" + className + "' not found", 489 | cnfe); 490 | } catch (InstantiationException ie) { 491 | LOGGER.warn( 492 | "Can't instantiate class '" + className + "'", 493 | ie); 494 | } catch (IllegalAccessException iae) { 495 | LOGGER.warn( 496 | "Can't instantiate class '" + className + "'", 497 | iae); 498 | } 499 | } 500 | LOGGER.debug( 501 | "Registered " + count + " callback" + (count > 1 ? "s" : "")); 502 | } 503 | } 504 | } 505 | 506 | // End XmlaServlet.java 507 | -------------------------------------------------------------------------------- /src/main/java/mondrian/xmla/XmlaUtil.java: -------------------------------------------------------------------------------- 1 | /* 2 | // This software is subject to the terms of the Eclipse Public License v1.0 3 | // Agreement, available at the following URL: 4 | // http://www.eclipse.org/legal/epl-v10.html. 5 | // You must accept the terms of that agreement to use this software. 6 | // 7 | // Copyright (C) 2003-2005 Julian Hyde 8 | // Copyright (C) 2005-2012 Pentaho 9 | // All Rights Reserved. 10 | */ 11 | package mondrian.xmla; 12 | 13 | import mondrian.xmla.XmlaHandler.XmlaExtra; 14 | import mondrian.xmla.impl.DefaultXmlaResponse; 15 | 16 | import org.olap4j.xmla.server.impl.Util; 17 | 18 | import org.olap4j.OlapConnection; 19 | import org.olap4j.OlapException; 20 | 21 | import org.w3c.dom.*; 22 | import org.xml.sax.InputSource; 23 | 24 | import java.io.*; 25 | import java.nio.charset.Charset; 26 | import java.sql.SQLException; 27 | import java.util.*; 28 | import java.util.concurrent.ConcurrentHashMap; 29 | import java.util.regex.Pattern; 30 | import javax.xml.parsers.DocumentBuilder; 31 | import javax.xml.parsers.DocumentBuilderFactory; 32 | import javax.xml.transform.Transformer; 33 | import javax.xml.transform.TransformerFactory; 34 | import javax.xml.transform.dom.DOMSource; 35 | import javax.xml.transform.stream.StreamResult; 36 | 37 | import static org.olap4j.metadata.XmlaConstants.Format; 38 | import static org.olap4j.metadata.XmlaConstants.Method; 39 | 40 | /** 41 | * Utility methods for XML/A implementation. 42 | * 43 | * @author Gang Chen 44 | */ 45 | public class XmlaUtil implements XmlaConstants { 46 | 47 | /** 48 | * Invalid characters for XML element name. 49 | * 50 | *

XML element name: 51 | * 52 | * Char ::= #x9 | #xA | #xD | [#x20-#xD7FF] 53 | * | [#xE000-#xFFFD] | [#x10000-#x10FFFF] 54 | * S ::= (#x20 | #x9 | #xD | #xA)+ 55 | * NameChar ::= Letter | Digit | '.' | '-' | '_' | ':' | CombiningChar 56 | * | Extender 57 | * Name ::= (Letter | '_' | ':') (NameChar)* 58 | * Names ::= Name (#x20 Name)* 59 | * Nmtoken ::= (NameChar)+ 60 | * Nmtokens ::= Nmtoken (#x20 Nmtoken)* 61 | * 62 | */ 63 | private static final String[] CHAR_TABLE = new String[256]; 64 | private static final Pattern LOWERCASE_PATTERN = 65 | Pattern.compile(".*[a-z].*"); 66 | 67 | static { 68 | initCharTable(" \t\r\n(){}[]+/*%!,?"); 69 | } 70 | 71 | private static void initCharTable(String charStr) { 72 | char[] chars = charStr.toCharArray(); 73 | for (char c : chars) { 74 | CHAR_TABLE[c] = encodeChar(c); 75 | } 76 | } 77 | 78 | private static String encodeChar(char c) { 79 | StringBuilder buf = new StringBuilder(); 80 | buf.append("_x"); 81 | String str = Integer.toHexString(c); 82 | for (int i = 4 - str.length(); i > 0; i--) { 83 | buf.append("0"); 84 | } 85 | return buf.append(str).append("_").toString(); 86 | } 87 | 88 | /** 89 | * Encodes an XML element name. 90 | * 91 | *

This function is mainly for encode element names in result of Drill 92 | * Through execute, because its element names come from database, we cannot 93 | * make sure they are valid XML contents. 94 | * 95 | *

Quoth the XML/A specification, version 96 | * 1.1: 97 | *

98 | * XML does not allow certain characters as element and attribute names. 99 | * XML for Analysis supports encoding as defined by SQL Server 2000 to 100 | * address this XML constraint. For column names that contain invalid XML 101 | * name characters (according to the XML 1.0 specification), the nonvalid 102 | * Unicode characters are encoded using the corresponding hexadecimal 103 | * values. These are escaped as _xHHHH_ where HHHH stands for 104 | * the four-digit hexadecimal UCS-2 code for the character in 105 | * most-significant bit first order. For example, the name "Order Details" 106 | * is encoded as Order_x0020_Details, where the space character is 107 | * replaced by the corresponding hexadecimal code. 108 | *
109 | * 110 | * @param name Name of XML element 111 | * @return encoded name 112 | */ 113 | private static String encodeElementName(String name) { 114 | StringBuilder buf = new StringBuilder(); 115 | char[] nameChars = name.toCharArray(); 116 | for (char ch : nameChars) { 117 | String encodedStr = 118 | (ch >= CHAR_TABLE.length ? null : CHAR_TABLE[ch]); 119 | if (encodedStr == null) { 120 | buf.append(ch); 121 | } else { 122 | buf.append(encodedStr); 123 | } 124 | } 125 | return buf.toString(); 126 | } 127 | 128 | 129 | public static void element2Text(Element elem, final StringWriter writer) 130 | throws XmlaException 131 | { 132 | try { 133 | TransformerFactory factory = TransformerFactory.newInstance(); 134 | Transformer transformer = factory.newTransformer(); 135 | transformer.transform( 136 | new DOMSource(elem), 137 | new StreamResult(writer)); 138 | } catch (Exception e) { 139 | throw new XmlaException( 140 | CLIENT_FAULT_FC, 141 | USM_DOM_PARSE_CODE, 142 | USM_DOM_PARSE_FAULT_FS, 143 | e); 144 | } 145 | } 146 | 147 | public static Element text2Element(String text) 148 | throws XmlaException 149 | { 150 | return _2Element(new InputSource(new StringReader(text))); 151 | } 152 | 153 | public static Element stream2Element(InputStream stream) 154 | throws XmlaException 155 | { 156 | return _2Element(new InputSource(stream)); 157 | } 158 | 159 | private static Element _2Element(InputSource source) 160 | throws XmlaException 161 | { 162 | try { 163 | DocumentBuilderFactory factory = 164 | DocumentBuilderFactory.newInstance(); 165 | factory.setIgnoringElementContentWhitespace(true); 166 | factory.setIgnoringComments(true); 167 | factory.setNamespaceAware(true); 168 | DocumentBuilder builder = factory.newDocumentBuilder(); 169 | Document doc = builder.parse(source); 170 | return doc.getDocumentElement(); 171 | } catch (Exception e) { 172 | throw new XmlaException( 173 | CLIENT_FAULT_FC, 174 | USM_DOM_PARSE_CODE, 175 | USM_DOM_PARSE_FAULT_FS, 176 | e); 177 | } 178 | } 179 | 180 | public static Element[] filterChildElements( 181 | Element parent, 182 | String ns, 183 | String lname) 184 | { 185 | /* 186 | way too noisy 187 | if (LOGGER.isDebugEnabled()) { 188 | StringBuilder buf = new StringBuilder(100); 189 | buf.append("XmlaUtil.filterChildElements: "); 190 | buf.append(" ns=\""); 191 | buf.append(ns); 192 | buf.append("\", lname=\""); 193 | buf.append(lname); 194 | buf.append("\""); 195 | LOGGER.debug(buf.toString()); 196 | } 197 | */ 198 | 199 | List elems = new ArrayList(); 200 | NodeList nlst = parent.getChildNodes(); 201 | for (int i = 0, nlen = nlst.getLength(); i < nlen; i++) { 202 | Node n = nlst.item(i); 203 | if (n instanceof Element) { 204 | Element e = (Element) n; 205 | if ((ns == null || ns.equals(e.getNamespaceURI())) 206 | && (lname == null || lname.equals(e.getLocalName()))) 207 | { 208 | elems.add(e); 209 | } 210 | } 211 | } 212 | return elems.toArray(new Element[elems.size()]); 213 | } 214 | 215 | public static String textInElement(Element elem) { 216 | StringBuilder buf = new StringBuilder(100); 217 | elem.normalize(); 218 | NodeList nlst = elem.getChildNodes(); 219 | for (int i = 0, nlen = nlst.getLength(); i < nlen ; i++) { 220 | Node n = nlst.item(i); 221 | if (n instanceof Text) { 222 | final String data = ((Text) n).getData(); 223 | buf.append(data); 224 | } 225 | } 226 | return buf.toString(); 227 | } 228 | 229 | /** 230 | * Finds root MondrianException in exception chain if exists, 231 | * otherwise the input throwable. 232 | * 233 | * @param throwable Exception 234 | * @return Root exception 235 | */ 236 | public static Throwable rootThrowable(Throwable throwable) { 237 | Throwable rootThrowable = throwable.getCause(); 238 | if (rootThrowable != null 239 | && rootThrowable instanceof XmlaException) 240 | { 241 | return rootThrowable(rootThrowable); 242 | } 243 | return throwable; 244 | } 245 | 246 | /** 247 | * Corrects for the differences between numeric strings arising because 248 | * JDBC drivers use different representations for numbers 249 | * ({@link Double} vs. {@link java.math.BigDecimal}) and 250 | * these have different toString() behavior. 251 | * 252 | *

If it contains a decimal point, then 253 | * strip off trailing '0's. After stripping off 254 | * the '0's, if there is nothing right of the 255 | * decimal point, then strip off decimal point. 256 | * 257 | * @param numericStr Numeric string 258 | * @return Normalized string 259 | */ 260 | public static String normalizeNumericString(String numericStr) { 261 | int index = numericStr.indexOf('.'); 262 | if (index > 0) { 263 | // If it uses exponential notation, 1.0E4, then it could 264 | // have a trailing '0' that should not be stripped of, 265 | // e.g., 1.0E10. This would be rather bad. 266 | if (numericStr.indexOf('e') != -1) { 267 | return numericStr; 268 | } else if (numericStr.indexOf('E') != -1) { 269 | return numericStr; 270 | } 271 | 272 | boolean found = false; 273 | int p = numericStr.length(); 274 | char c = numericStr.charAt(p - 1); 275 | while (c == '0') { 276 | found = true; 277 | p--; 278 | c = numericStr.charAt(p - 1); 279 | } 280 | if (c == '.') { 281 | p--; 282 | } 283 | if (found) { 284 | return numericStr.substring(0, p); 285 | } 286 | } 287 | return numericStr; 288 | } 289 | 290 | /** 291 | * Returns a set of column headings and rows for a given metadata request. 292 | * 293 | *

Leverages mondrian's implementation of the XML/A specification, and 294 | * is exposed here for use by mondrian's olap4j driver. 295 | * 296 | * @param connection Connection 297 | * @param methodName Metadata method name per XMLA (e.g. "MDSCHEMA_CUBES") 298 | * @param restrictionMap Restrictions 299 | * @return Set of rows and column headings 300 | */ 301 | public static MetadataRowset getMetadataRowset( 302 | final OlapConnection connection, 303 | String methodName, 304 | final Map restrictionMap) 305 | throws OlapException 306 | { 307 | RowsetDefinition rowsetDefinition = 308 | RowsetDefinition.valueOf(methodName); 309 | 310 | final XmlaHandler.ConnectionFactory connectionFactory = 311 | new XmlaHandler.ConnectionFactory() { 312 | public OlapConnection getConnection( 313 | String catalog, String schema, String roleName, 314 | Properties props) 315 | throws SQLException 316 | { 317 | return connection; 318 | } 319 | 320 | public Map 321 | getPreConfiguredDiscoverDatasourcesResponse() 322 | { 323 | // This method should not be used by the olap4j xmla 324 | // servlet. For the mondrian xmla servlet we don't provide 325 | // the "pre configured discover datasources" feature. 326 | return null; 327 | } 328 | 329 | public XmlaHandler.Request startRequest( 330 | XmlaRequest request, 331 | OlapConnection connection) 332 | { 333 | return null; 334 | } 335 | 336 | public void endRequest(XmlaHandler.Request request) { 337 | // nothing 338 | } 339 | 340 | public XmlaHandler.XmlaExtra getExtra() { 341 | try { 342 | return connection.unwrap(XmlaHandler.XmlaExtra.class); 343 | } catch (SQLException e) { 344 | throw new RuntimeException(e); 345 | } 346 | } 347 | }; 348 | final XmlaRequest request = new XmlaRequest() { 349 | public Method getMethod() { 350 | return Method.DISCOVER; 351 | } 352 | 353 | public Map getProperties() { 354 | return Collections.emptyMap(); 355 | } 356 | 357 | public Map getRestrictions() { 358 | return restrictionMap; 359 | } 360 | 361 | public String getStatement() { 362 | return null; 363 | } 364 | 365 | public String getRoleName() { 366 | return null; 367 | } 368 | 369 | public String getRequestType() { 370 | throw new UnsupportedOperationException(); 371 | } 372 | 373 | public boolean isDrillThrough() { 374 | throw new UnsupportedOperationException(); 375 | } 376 | 377 | public Format getFormat() { 378 | throw new UnsupportedOperationException(); 379 | } 380 | 381 | public String getUsername() { 382 | return null; 383 | } 384 | 385 | public String getPassword() { 386 | return null; 387 | } 388 | 389 | public String getSessionId() { 390 | return null; 391 | } 392 | }; 393 | final Rowset rowset = 394 | rowsetDefinition.getRowset( 395 | request, 396 | new XmlaHandler( 397 | connectionFactory, 398 | "xmla") 399 | { 400 | @Override 401 | public OlapConnection getConnection( 402 | XmlaRequest request, 403 | Map propMap) 404 | { 405 | return connection; 406 | } 407 | } 408 | ); 409 | List rowList = new ArrayList(); 410 | rowset.populate( 411 | new DefaultXmlaResponse( 412 | new ByteArrayOutputStream(), 413 | Charset.defaultCharset().name(), 414 | Enumeration.ResponseMimeType.SOAP), 415 | connection, 416 | rowList); 417 | MetadataRowset result = new MetadataRowset(); 418 | final List colDefs = 419 | new ArrayList(); 420 | for (RowsetDefinition.Column columnDefinition 421 | : rowsetDefinition.columnDefinitions) 422 | { 423 | if (columnDefinition.type == RowsetDefinition.Type.Rowset) { 424 | // olap4j does not support the extended columns, e.g. 425 | // Cube.Dimensions 426 | continue; 427 | } 428 | colDefs.add(columnDefinition); 429 | } 430 | for (Rowset.Row row : rowList) { 431 | Object[] values = new Object[colDefs.size()]; 432 | int k = -1; 433 | for (RowsetDefinition.Column colDef : colDefs) { 434 | Object o = row.get(colDef.name); 435 | if (o instanceof List) { 436 | o = toString((List) o); 437 | } else if (o instanceof String[]) { 438 | o = toString(Arrays.asList((String []) o)); 439 | } 440 | values[++k] = o; 441 | } 442 | result.rowList.add(Arrays.asList(values)); 443 | } 444 | for (RowsetDefinition.Column colDef : colDefs) { 445 | String columnName = colDef.name; 446 | if (LOWERCASE_PATTERN.matcher(columnName).matches()) { 447 | columnName = Util.camelToUpper(columnName); 448 | } 449 | // VALUE is a SQL reserved word 450 | if (columnName.equals("VALUE")) { 451 | columnName = "PROPERTY_VALUE"; 452 | } 453 | result.headerList.add(columnName); 454 | } 455 | return result; 456 | } 457 | 458 | private static String toString(List list) { 459 | StringBuilder buf = new StringBuilder(); 460 | int k = -1; 461 | for (T t : list) { 462 | if (++k > 0) { 463 | buf.append(", "); 464 | } 465 | buf.append(t); 466 | } 467 | return buf.toString(); 468 | } 469 | 470 | /** 471 | * Chooses the appropriate response mime type given an HTTP "Accept" header. 472 | * 473 | *

The header can contain a list of mime types and optional qualities, 474 | * for example "text/html,application/xhtml+xml,application/xml;q=0.9" 475 | * 476 | * @param accept Accept header 477 | * @return Mime type, or null if none is acceptable 478 | */ 479 | public static Enumeration.ResponseMimeType chooseResponseMimeType( 480 | String accept) 481 | { 482 | for (String s : accept.split(",")) { 483 | s = s.trim(); 484 | final int semicolon = s.indexOf(";"); 485 | if (semicolon >= 0) { 486 | s = s.substring(0, semicolon); 487 | } 488 | Enumeration.ResponseMimeType mimeType = 489 | Enumeration.ResponseMimeType.MAP.get(s); 490 | if (mimeType != null) { 491 | return mimeType; 492 | } 493 | } 494 | return null; 495 | } 496 | 497 | /** 498 | * Returns whether an XMLA request should return invisible members. 499 | * 500 | *

According to the XMLA spec, it should not. But we allow the client to 501 | * specify different behavior. In particular, the olap4j driver for XMLA 502 | * may need to access invisible members. 503 | * 504 | *

Returns true if the EmitInvisibleMembers property is specified and 505 | * equal to "true". 506 | * 507 | * @param request XMLA request 508 | * @return Whether to return invisible members 509 | */ 510 | public static boolean shouldEmitInvisibleMembers(XmlaRequest request) { 511 | final String value = 512 | request.getProperties().get( 513 | PropertyDefinition.EmitInvisibleMembers.name()); 514 | return Boolean.parseBoolean(value); 515 | } 516 | 517 | /** 518 | * Result of a metadata query. 519 | */ 520 | public static class MetadataRowset { 521 | public final List headerList = new ArrayList(); 522 | public final List> rowList = new ArrayList>(); 523 | } 524 | 525 | /** 526 | * Wrapper which indicates that a restriction is to be treated as a 527 | * SQL-style wildcard match. 528 | */ 529 | public static class Wildcard { 530 | public final String pattern; 531 | 532 | public Wildcard(String pattern) { 533 | this.pattern = pattern; 534 | } 535 | } 536 | 537 | public static class ElementNameEncoder { 538 | private final Map map = 539 | new ConcurrentHashMap(); 540 | public static final ElementNameEncoder INSTANCE = 541 | new ElementNameEncoder(); 542 | 543 | public String encode(String name) { 544 | String encoded = map.get(name); 545 | if (encoded == null) { 546 | encoded = encodeElementName(name); 547 | map.put(name, encoded); 548 | } 549 | return encoded; 550 | } 551 | } 552 | } 553 | 554 | // End XmlaUtil.java 555 | -------------------------------------------------------------------------------- /src/main/java/mondrian/xmla/impl/AuthenticatingXmlaRequestCallback.java: -------------------------------------------------------------------------------- 1 | /* 2 | // This software is subject to the terms of the Eclipse Public License v1.0 3 | // Agreement, available at the following URL: 4 | // http://www.eclipse.org/legal/epl-v10.html. 5 | // You must accept the terms of that agreement to use this software. 6 | // 7 | // Copyright (C) 2011-2011 Pentaho and others 8 | // All Rights Reserved. 9 | */ 10 | package mondrian.xmla.impl; 11 | 12 | import mondrian.xmla.*; 13 | 14 | import org.w3c.dom.Element; 15 | 16 | import java.util.Map; 17 | import javax.servlet.ServletConfig; 18 | import javax.servlet.ServletException; 19 | import javax.servlet.http.HttpServletRequest; 20 | import javax.servlet.http.HttpServletResponse; 21 | 22 | /** 23 | * This is an abstract implementation of {@link XmlaRequestCallback} 24 | * specialized in authenticating the requests coming in. Subclasses are 25 | * only required to implement {@link #authenticate(String, String, String)}. 26 | * 27 | *

Once implemented, you only need to register your class using the XMLA 28 | * servlet config, within your web.xml descriptor. 29 | * 30 | * @author LBoudreau 31 | */ 32 | public abstract class AuthenticatingXmlaRequestCallback 33 | implements XmlaRequestCallback 34 | { 35 | public String generateSessionId(Map context) { 36 | // We don't want to override the session ID generation algorithm. 37 | return null; 38 | } 39 | 40 | public void init(ServletConfig servletConfig) throws ServletException { 41 | // Nothing to initialize here. Subclasses can override 42 | // this if they wish. 43 | } 44 | 45 | public void postAction( 46 | HttpServletRequest request, 47 | HttpServletResponse response, 48 | byte[][] responseSoapParts, 49 | Map context) 50 | throws Exception 51 | { 52 | return; 53 | } 54 | 55 | public void preAction( 56 | HttpServletRequest request, 57 | Element[] requestSoapParts, 58 | Map context) 59 | throws Exception 60 | { 61 | // This is where the magic happens. At this stage, we have 62 | // the username/password known. We will delegate the authentication 63 | // process down to the subclass. 64 | final String roleNames = 65 | authenticate( 66 | (String) context.get(XmlaConstants.CONTEXT_XMLA_USERNAME), 67 | (String) context.get(XmlaConstants.CONTEXT_XMLA_PASSWORD), 68 | (String) context.get(XmlaConstants.CONTEXT_XMLA_SESSION_ID)); 69 | context.put( 70 | XmlaConstants.CONTEXT_ROLE_NAME, 71 | roleNames); 72 | } 73 | 74 | /** 75 | * This function is expected to do two things. 76 | *

    77 | *
  • Validate the credentials.
  • 78 | *
  • Return a comma separated list of role names associated to 79 | * these credentials.
  • 80 | *
81 | *

Should there be any problems with the credentials, subclasses 82 | * can invoke {@link #throwAuthenticationException(String)} to throw 83 | * an authentication exception back to the client. 84 | * @param username Username used for authentication, as specified 85 | * in the SOAP security header. Might be null. 86 | * @param password Password used for authentication, as specified 87 | * in the SOAP security header. Might be null. 88 | * @param sessionID A unique identifier for this client session. 89 | * Session IDs should remain the same between different queries from 90 | * a same client, although some clients do not implement the XMLA 91 | * Session header properly, resulting in a new session ID for each 92 | * request. 93 | * @return A comma separated list of roles associated to this user, 94 | * or null for root access. 95 | */ 96 | public abstract String authenticate( 97 | String username, 98 | String password, 99 | String sessionID); 100 | 101 | /** 102 | * Helper method to create and throw an authentication exception. 103 | * @param reason A textual explanation of why the credentials are 104 | * rejected. 105 | */ 106 | protected void throwAuthenticationException(String reason) { 107 | throw new XmlaException( 108 | XmlaConstants.CLIENT_FAULT_FC, 109 | XmlaConstants.CHH_AUTHORIZATION_CODE, 110 | XmlaConstants.CHH_AUTHORIZATION_FAULT_FS, 111 | new Exception( 112 | "There was a problem with the credentials: " 113 | + reason)); 114 | } 115 | 116 | public boolean processHttpHeader( 117 | HttpServletRequest request, 118 | HttpServletResponse response, 119 | Map context) 120 | throws Exception 121 | { 122 | // We do not perform any special header treatment. 123 | return true; 124 | } 125 | } 126 | // End AuthenticatingXmlaRequestCallback.java 127 | -------------------------------------------------------------------------------- /src/main/java/mondrian/xmla/impl/DefaultSaxWriter.java: -------------------------------------------------------------------------------- 1 | /* 2 | // This software is subject to the terms of the Eclipse Public License v1.0 3 | // Agreement, available at the following URL: 4 | // http://www.eclipse.org/legal/epl-v10.html. 5 | // You must accept the terms of that agreement to use this software. 6 | // 7 | // Copyright (C) 2005-2012 Pentaho 8 | // All Rights Reserved. 9 | */ 10 | package mondrian.xmla.impl; 11 | 12 | import mondrian.xmla.SaxWriter; 13 | 14 | import org.olap4j.xmla.server.impl.*; 15 | 16 | import org.xml.sax.Attributes; 17 | 18 | import java.io.*; 19 | import java.util.regex.Pattern; 20 | 21 | /** 22 | * Default implementation of {@link SaxWriter}. 23 | * 24 | * @author jhyde 25 | * @author Gang Chen 26 | * @since 27 April, 2003 27 | */ 28 | public class DefaultSaxWriter implements SaxWriter { 29 | /** Inside the tag of an element. */ 30 | private static final int STATE_IN_TAG = 0; 31 | /** After the tag at the end of an element. */ 32 | private static final int STATE_END_ELEMENT = 1; 33 | /** After the tag at the start of an element. */ 34 | private static final int STATE_AFTER_TAG = 2; 35 | /** After a burst of character data. */ 36 | private static final int STATE_CHARACTERS = 3; 37 | 38 | private final Appendable buf; 39 | private int indent; 40 | private final String indentStr = " "; 41 | private final ArrayStack stack = new ArrayStack(); 42 | private int state = STATE_END_ELEMENT; 43 | 44 | private final static Pattern nlPattern = Pattern.compile("\\r\\n|\\r|\\n"); 45 | 46 | /** 47 | * Creates a DefaultSaxWriter writing to an {@link java.io.OutputStream}. 48 | */ 49 | public DefaultSaxWriter(OutputStream stream) { 50 | this(new BufferedWriter(new OutputStreamWriter(stream))); 51 | } 52 | 53 | public DefaultSaxWriter(OutputStream stream, String xmlEncoding) 54 | throws UnsupportedEncodingException 55 | { 56 | this(new BufferedWriter(new OutputStreamWriter(stream, xmlEncoding))); 57 | } 58 | 59 | /** 60 | * Creates a DefaultSaxWriter without indentation. 61 | * 62 | * @param buf String builder to write to 63 | */ 64 | public DefaultSaxWriter(Appendable buf) { 65 | this(buf, 0); 66 | } 67 | 68 | /** 69 | * Creates a DefaultSaxWriter. 70 | * 71 | * @param buf String builder to write to 72 | * @param initialIndent Initial indent (0 to write on single line) 73 | */ 74 | public DefaultSaxWriter(Appendable buf, int initialIndent) { 75 | this.buf = buf; 76 | this.indent = initialIndent; 77 | } 78 | 79 | private void _startElement( 80 | String namespaceURI, 81 | String localName, 82 | String qName, 83 | Attributes atts) throws IOException 84 | { 85 | _checkTag(); 86 | if (indent > 0) { 87 | buf.append(Util.nl); 88 | } 89 | for (int i = 0; i < indent; i++) { 90 | buf.append(indentStr); 91 | } 92 | indent++; 93 | buf.append('<'); 94 | buf.append(qName); 95 | final int length = atts.getLength(); 96 | for (int i = 0; i < length; i++) { 97 | String val = atts.getValue(i); 98 | if (val != null) { 99 | buf.append(' '); 100 | buf.append(atts.getQName(i)); 101 | buf.append("=\""); 102 | StringEscaper.XML_NUMERIC_ESCAPER.appendEscapedString(val, buf); 103 | buf.append("\""); 104 | } 105 | } 106 | state = STATE_IN_TAG; 107 | assert qName != null; 108 | stack.add(qName); 109 | } 110 | 111 | private void _checkTag() throws IOException { 112 | if (state == STATE_IN_TAG) { 113 | state = STATE_AFTER_TAG; 114 | buf.append('>'); 115 | } 116 | } 117 | 118 | private void _endElement() throws IOException 119 | { 120 | String qName = stack.pop(); 121 | indent--; 122 | if (state == STATE_IN_TAG) { 123 | buf.append("/>"); 124 | } else { 125 | if (state != STATE_CHARACTERS) { 126 | buf.append(Util.nl); 127 | for (int i = 0; i < indent; i++) { 128 | buf.append(indentStr); 129 | } 130 | } 131 | buf.append("'); 134 | } 135 | state = STATE_END_ELEMENT; 136 | } 137 | 138 | private void _characters(String s) throws IOException 139 | { 140 | _checkTag(); 141 | StringEscaper.XML_NUMERIC_ESCAPER.appendEscapedString(s, buf); 142 | state = STATE_CHARACTERS; 143 | } 144 | 145 | // 146 | // Simplifying methods 147 | 148 | public void characters(String s) { 149 | try { 150 | _characters(s); 151 | } catch (IOException e) { 152 | throw new RuntimeException("Error while appending XML", e); 153 | } 154 | } 155 | 156 | public void startSequence(String name, String subName) { 157 | if (name != null) { 158 | startElement(name); 159 | } else { 160 | stack.push(null); 161 | } 162 | } 163 | 164 | public void endSequence() { 165 | if (stack.peek() == null) { 166 | stack.pop(); 167 | } else { 168 | endElement(); 169 | } 170 | } 171 | 172 | public final void textElement(String name, Object data) { 173 | try { 174 | _startElement(null, null, name, EmptyAttributes); 175 | 176 | // Replace line endings with spaces. IBM's DOM implementation keeps 177 | // line endings, whereas Sun's does not. For consistency, always 178 | // strip them. 179 | // 180 | // REVIEW: It would be better to enclose in CDATA, but some clients 181 | // might not be expecting this. 182 | characters( 183 | nlPattern.matcher(data.toString()).replaceAll(" ")); 184 | _endElement(); 185 | } catch (IOException e) { 186 | throw new RuntimeException("Error while appending XML", e); 187 | } 188 | } 189 | 190 | public void element(String tagName, Object... attributes) { 191 | startElement(tagName, attributes); 192 | endElement(); 193 | } 194 | 195 | public void startElement(String tagName) { 196 | try { 197 | _startElement(null, null, tagName, EmptyAttributes); 198 | } catch (IOException e) { 199 | throw new RuntimeException("Error while appending XML", e); 200 | } 201 | } 202 | 203 | public void startElement(String tagName, Object... attributes) { 204 | try { 205 | _startElement( 206 | null, null, tagName, new StringAttributes(attributes)); 207 | } catch (IOException e) { 208 | throw new RuntimeException("Error while appending XML", e); 209 | } 210 | } 211 | 212 | public void endElement() { 213 | try { 214 | _endElement(); 215 | } catch (IOException e) { 216 | throw new RuntimeException("Error while appending XML", e); 217 | } 218 | } 219 | 220 | public void startDocument() { 221 | if (stack.size() != 0) { 222 | throw new IllegalStateException("Document already started"); 223 | } 224 | } 225 | 226 | public void endDocument() { 227 | if (stack.size() != 0) { 228 | throw new IllegalStateException( 229 | "Document may have unbalanced elements"); 230 | } 231 | flush(); 232 | } 233 | 234 | public void completeBeforeElement(String tagName) { 235 | if (stack.indexOf(tagName) == -1) { 236 | return; 237 | } 238 | 239 | String currentTagName = stack.peek(); 240 | while (!tagName.equals(currentTagName)) { 241 | try { 242 | _endElement(); 243 | } catch (IOException e) { 244 | throw new RuntimeException("Error while appending XML", e); 245 | } 246 | currentTagName = stack.peek(); 247 | } 248 | } 249 | 250 | public void verbatim(String text) { 251 | try { 252 | _checkTag(); 253 | buf.append(text); 254 | } catch (IOException e) { 255 | throw new RuntimeException("Error while appending XML", e); 256 | } 257 | } 258 | 259 | public void flush() { 260 | if (buf instanceof Writer) { 261 | try { 262 | ((Writer) buf).flush(); 263 | } catch (IOException e) { 264 | throw new RuntimeException("Error while flushing XML", e); 265 | } 266 | } 267 | } 268 | 269 | private static final Attributes EmptyAttributes = new Attributes() { 270 | public int getLength() { 271 | return 0; 272 | } 273 | 274 | public String getURI(int index) { 275 | return null; 276 | } 277 | 278 | public String getLocalName(int index) { 279 | return null; 280 | } 281 | 282 | public String getQName(int index) { 283 | return null; 284 | } 285 | 286 | public String getType(int index) { 287 | return null; 288 | } 289 | 290 | public String getValue(int index) { 291 | return null; 292 | } 293 | 294 | public int getIndex(String uri, String localName) { 295 | return 0; 296 | } 297 | 298 | public int getIndex(String qName) { 299 | return 0; 300 | } 301 | 302 | public String getType(String uri, String localName) { 303 | return null; 304 | } 305 | 306 | public String getType(String qName) { 307 | return null; 308 | } 309 | 310 | public String getValue(String uri, String localName) { 311 | return null; 312 | } 313 | 314 | public String getValue(String qName) { 315 | return null; 316 | } 317 | }; 318 | 319 | /** 320 | * List of SAX attributes based upon a string array. 321 | */ 322 | public static class StringAttributes implements Attributes { 323 | private final Object[] strings; 324 | 325 | public StringAttributes(Object[] strings) { 326 | this.strings = strings; 327 | } 328 | 329 | public int getLength() { 330 | return strings.length / 2; 331 | } 332 | 333 | public String getURI(int index) { 334 | return null; 335 | } 336 | 337 | public String getLocalName(int index) { 338 | return null; 339 | } 340 | 341 | public String getQName(int index) { 342 | return (String) strings[index * 2]; 343 | } 344 | 345 | public String getType(int index) { 346 | return null; 347 | } 348 | 349 | public String getValue(int index) { 350 | return stringValue(strings[index * 2 + 1]); 351 | } 352 | 353 | public int getIndex(String uri, String localName) { 354 | return -1; 355 | } 356 | 357 | public int getIndex(String qName) { 358 | final int count = strings.length / 2; 359 | for (int i = 0; i < count; i++) { 360 | String string = (String) strings[i * 2]; 361 | if (string.equals(qName)) { 362 | return i; 363 | } 364 | } 365 | return -1; 366 | } 367 | 368 | public String getType(String uri, String localName) { 369 | return null; 370 | } 371 | 372 | public String getType(String qName) { 373 | return null; 374 | } 375 | 376 | public String getValue(String uri, String localName) { 377 | return null; 378 | } 379 | 380 | public String getValue(String qName) { 381 | final int index = getIndex(qName); 382 | if (index < 0) { 383 | return null; 384 | } else { 385 | return stringValue(strings[index * 2 + 1]); 386 | } 387 | } 388 | 389 | private static String stringValue(Object s) { 390 | return s == null ? null : s.toString(); 391 | } 392 | } 393 | } 394 | 395 | // End DefaultSaxWriter.java 396 | -------------------------------------------------------------------------------- /src/main/java/mondrian/xmla/impl/DefaultXmlaRequest.java: -------------------------------------------------------------------------------- 1 | /* 2 | // This software is subject to the terms of the Eclipse Public License v1.0 3 | // Agreement, available at the following URL: 4 | // http://www.eclipse.org/legal/epl-v10.html. 5 | // You must accept the terms of that agreement to use this software. 6 | // 7 | // Copyright (C) 2005-2012 Pentaho 8 | // All Rights Reserved. 9 | */ 10 | package mondrian.xmla.impl; 11 | 12 | import mondrian.xmla.*; 13 | 14 | import org.olap4j.xmla.server.impl.Util; 15 | 16 | import org.apache.log4j.Logger; 17 | 18 | import org.w3c.dom.*; 19 | 20 | import java.util.*; 21 | 22 | import static org.olap4j.metadata.XmlaConstants.Method; 23 | 24 | /** 25 | * Default implementation of {@link mondrian.xmla.XmlaRequest} by DOM API. 26 | * 27 | * @author Gang Chen 28 | */ 29 | public class DefaultXmlaRequest 30 | implements XmlaRequest, XmlaConstants 31 | { 32 | private static final Logger LOGGER = 33 | Logger.getLogger(DefaultXmlaRequest.class); 34 | 35 | private static final String MSG_INVALID_XMLA = "Invalid XML/A message"; 36 | 37 | /* common content */ 38 | private Method method; 39 | private Map properties; 40 | private final String roleName; 41 | 42 | /* EXECUTE content */ 43 | private String statement; 44 | private boolean drillthrough; 45 | 46 | /* DISCOVER contnet */ 47 | private String requestType; 48 | private Map restrictions; 49 | 50 | private final String username; 51 | private final String password; 52 | private final String sessionId; 53 | 54 | public DefaultXmlaRequest( 55 | final Element xmlaRoot, 56 | final String roleName, 57 | final String username, 58 | final String password, 59 | final String sessionId) 60 | throws XmlaException 61 | { 62 | init(xmlaRoot); 63 | this.roleName = roleName; 64 | this.username = username; 65 | this.password = password; 66 | this.sessionId = sessionId; 67 | } 68 | 69 | public String getSessionId() { 70 | return sessionId; 71 | } 72 | 73 | public String getUsername() { 74 | return username; 75 | } 76 | 77 | public String getPassword() { 78 | return password; 79 | } 80 | 81 | public Method getMethod() { 82 | return method; 83 | } 84 | 85 | public Map getProperties() { 86 | return properties; 87 | } 88 | 89 | public Map getRestrictions() { 90 | if (method != Method.DISCOVER) { 91 | throw new IllegalStateException( 92 | "Only METHOD_DISCOVER has restrictions"); 93 | } 94 | return restrictions; 95 | } 96 | 97 | public String getStatement() { 98 | if (method != Method.EXECUTE) { 99 | throw new IllegalStateException( 100 | "Only METHOD_EXECUTE has statement"); 101 | } 102 | return statement; 103 | } 104 | 105 | public String getRoleName() { 106 | return roleName; 107 | } 108 | 109 | public String getRequestType() { 110 | if (method != Method.DISCOVER) { 111 | throw new IllegalStateException( 112 | "Only METHOD_DISCOVER has requestType"); 113 | } 114 | return requestType; 115 | } 116 | 117 | public boolean isDrillThrough() { 118 | if (method != Method.EXECUTE) { 119 | throw new IllegalStateException( 120 | "Only METHOD_EXECUTE determines drillthrough"); 121 | } 122 | return drillthrough; 123 | } 124 | 125 | 126 | protected final void init(Element xmlaRoot) throws XmlaException { 127 | if (NS_XMLA.equals(xmlaRoot.getNamespaceURI())) { 128 | String lname = xmlaRoot.getLocalName(); 129 | if ("Discover".equals(lname)) { 130 | method = Method.DISCOVER; 131 | initDiscover(xmlaRoot); 132 | } else if ("Execute".equals(lname)) { 133 | method = Method.EXECUTE; 134 | initExecute(xmlaRoot); 135 | } else { 136 | // Note that is code will never be reached because 137 | // the error will be caught in 138 | // DefaultXmlaServlet.handleSoapBody first 139 | StringBuilder buf = new StringBuilder(100); 140 | buf.append(MSG_INVALID_XMLA); 141 | buf.append(": Bad method name \""); 142 | buf.append(lname); 143 | buf.append("\""); 144 | throw new XmlaException( 145 | CLIENT_FAULT_FC, 146 | HSB_BAD_METHOD_CODE, 147 | HSB_BAD_METHOD_FAULT_FS, 148 | Util.newError(buf.toString())); 149 | } 150 | } else { 151 | // Note that is code will never be reached because 152 | // the error will be caught in 153 | // DefaultXmlaServlet.handleSoapBody first 154 | StringBuilder buf = new StringBuilder(100); 155 | buf.append(MSG_INVALID_XMLA); 156 | buf.append(": Bad namespace url \""); 157 | buf.append(xmlaRoot.getNamespaceURI()); 158 | buf.append("\""); 159 | throw new XmlaException( 160 | CLIENT_FAULT_FC, 161 | HSB_BAD_METHOD_NS_CODE, 162 | HSB_BAD_METHOD_NS_FAULT_FS, 163 | Util.newError(buf.toString())); 164 | } 165 | } 166 | 167 | private void initDiscover(Element discoverRoot) throws XmlaException { 168 | Element[] childElems = 169 | XmlaUtil.filterChildElements( 170 | discoverRoot, 171 | NS_XMLA, 172 | "RequestType"); 173 | if (childElems.length != 1) { 174 | StringBuilder buf = new StringBuilder(100); 175 | buf.append(MSG_INVALID_XMLA); 176 | buf.append(": Wrong number of RequestType elements: "); 177 | buf.append(childElems.length); 178 | throw new XmlaException( 179 | CLIENT_FAULT_FC, 180 | HSB_BAD_REQUEST_TYPE_CODE, 181 | HSB_BAD_REQUEST_TYPE_FAULT_FS, 182 | Util.newError(buf.toString())); 183 | } 184 | requestType = XmlaUtil.textInElement(childElems[0]); // 185 | 186 | childElems = 187 | XmlaUtil.filterChildElements( 188 | discoverRoot, 189 | NS_XMLA, 190 | "Properties"); 191 | if (childElems.length != 1) { 192 | StringBuilder buf = new StringBuilder(100); 193 | buf.append(MSG_INVALID_XMLA); 194 | buf.append(": Wrong number of Properties elements: "); 195 | buf.append(childElems.length); 196 | throw new XmlaException( 197 | CLIENT_FAULT_FC, 198 | HSB_BAD_PROPERTIES_CODE, 199 | HSB_BAD_PROPERTIES_FAULT_FS, 200 | Util.newError(buf.toString())); 201 | } 202 | initProperties(childElems[0]); // 203 | 204 | childElems = 205 | XmlaUtil.filterChildElements( 206 | discoverRoot, 207 | NS_XMLA, 208 | "Restrictions"); 209 | if (childElems.length != 1) { 210 | StringBuilder buf = new StringBuilder(100); 211 | buf.append(MSG_INVALID_XMLA); 212 | buf.append(": Wrong number of Restrictions elements: "); 213 | buf.append(childElems.length); 214 | throw new XmlaException( 215 | CLIENT_FAULT_FC, 216 | HSB_BAD_RESTRICTIONS_CODE, 217 | HSB_BAD_RESTRICTIONS_FAULT_FS, 218 | Util.newError(buf.toString())); 219 | } 220 | initRestrictions(childElems[0]); // 221 | } 222 | 223 | private void initExecute(Element executeRoot) throws XmlaException { 224 | Element[] childElems = 225 | XmlaUtil.filterChildElements( 226 | executeRoot, 227 | NS_XMLA, 228 | "Command"); 229 | if (childElems.length != 1) { 230 | StringBuilder buf = new StringBuilder(100); 231 | buf.append(MSG_INVALID_XMLA); 232 | buf.append(": Wrong number of Command elements: "); 233 | buf.append(childElems.length); 234 | throw new XmlaException( 235 | CLIENT_FAULT_FC, 236 | HSB_BAD_COMMAND_CODE, 237 | HSB_BAD_COMMAND_FAULT_FS, 238 | Util.newError(buf.toString())); 239 | } 240 | initCommand(childElems[0]); // 241 | 242 | childElems = 243 | XmlaUtil.filterChildElements( 244 | executeRoot, 245 | NS_XMLA, 246 | "Properties"); 247 | if (childElems.length != 1) { 248 | StringBuilder buf = new StringBuilder(100); 249 | buf.append(MSG_INVALID_XMLA); 250 | buf.append(": Wrong number of Properties elements: "); 251 | buf.append(childElems.length); 252 | throw new XmlaException( 253 | CLIENT_FAULT_FC, 254 | HSB_BAD_PROPERTIES_CODE, 255 | HSB_BAD_PROPERTIES_FAULT_FS, 256 | Util.newError(buf.toString())); 257 | } 258 | initProperties(childElems[0]); // 259 | } 260 | 261 | private void initRestrictions(Element restrictionsRoot) 262 | throws XmlaException 263 | { 264 | Map> restrictions = 265 | new HashMap>(); 266 | Element[] childElems = 267 | XmlaUtil.filterChildElements( 268 | restrictionsRoot, 269 | NS_XMLA, 270 | "RestrictionList"); 271 | if (childElems.length == 1) { 272 | NodeList nlst = childElems[0].getChildNodes(); 273 | for (int i = 0, nlen = nlst.getLength(); i < nlen; i++) { 274 | Node n = nlst.item(i); 275 | if (n instanceof Element) { 276 | Element e = (Element) n; 277 | if (NS_XMLA.equals(e.getNamespaceURI())) { 278 | String key = e.getLocalName(); 279 | String value = XmlaUtil.textInElement(e); 280 | 281 | List values; 282 | if (restrictions.containsKey(key)) { 283 | values = restrictions.get(key); 284 | } else { 285 | values = new ArrayList(); 286 | restrictions.put(key, values); 287 | } 288 | 289 | if (LOGGER.isDebugEnabled()) { 290 | LOGGER.debug( 291 | "DefaultXmlaRequest.initRestrictions: " 292 | + " key=\"" 293 | + key 294 | + "\", value=\"" 295 | + value 296 | + "\""); 297 | } 298 | 299 | values.add(value); 300 | } 301 | } 302 | } 303 | } else if (childElems.length > 1) { 304 | StringBuilder buf = new StringBuilder(100); 305 | buf.append(MSG_INVALID_XMLA); 306 | buf.append(": Wrong number of RestrictionList elements: "); 307 | buf.append(childElems.length); 308 | throw new XmlaException( 309 | CLIENT_FAULT_FC, 310 | HSB_BAD_RESTRICTION_LIST_CODE, 311 | HSB_BAD_RESTRICTION_LIST_FAULT_FS, 312 | Util.newError(buf.toString())); 313 | } 314 | 315 | // If there is a Catalog property, 316 | // we have to consider it a constraint as well. 317 | String key = 318 | org.olap4j.metadata.XmlaConstants 319 | .Literal.CATALOG_NAME.name(); 320 | 321 | if (this.properties.containsKey(key) 322 | && !restrictions.containsKey(key)) 323 | { 324 | List values; 325 | values = new ArrayList(); 326 | restrictions.put(this.properties.get(key), values); 327 | 328 | if (LOGGER.isDebugEnabled()) { 329 | LOGGER.debug( 330 | "DefaultXmlaRequest.initRestrictions: " 331 | + " key=\"" 332 | + key 333 | + "\", value=\"" 334 | + this.properties.get(key) 335 | + "\""); 336 | } 337 | } 338 | 339 | this.restrictions = (Map) Collections.unmodifiableMap(restrictions); 340 | } 341 | 342 | private void initProperties(Element propertiesRoot) throws XmlaException { 343 | Map properties = new HashMap(); 344 | Element[] childElems = 345 | XmlaUtil.filterChildElements( 346 | propertiesRoot, 347 | NS_XMLA, 348 | "PropertyList"); 349 | if (childElems.length == 1) { 350 | NodeList nlst = childElems[0].getChildNodes(); 351 | for (int i = 0, nlen = nlst.getLength(); i < nlen; i++) { 352 | Node n = nlst.item(i); 353 | if (n instanceof Element) { 354 | Element e = (Element) n; 355 | if (NS_XMLA.equals(e.getNamespaceURI())) { 356 | String key = e.getLocalName(); 357 | String value = XmlaUtil.textInElement(e); 358 | 359 | if (LOGGER.isDebugEnabled()) { 360 | LOGGER.debug( 361 | "DefaultXmlaRequest.initProperties: " 362 | + " key=\"" 363 | + key 364 | + "\", value=\"" 365 | + value 366 | + "\""); 367 | } 368 | 369 | properties.put(key, value); 370 | } 371 | } 372 | } 373 | } else if (childElems.length > 1) { 374 | StringBuilder buf = new StringBuilder(100); 375 | buf.append(MSG_INVALID_XMLA); 376 | buf.append(": Wrong number of PropertyList elements: "); 377 | buf.append(childElems.length); 378 | throw new XmlaException( 379 | CLIENT_FAULT_FC, 380 | HSB_BAD_PROPERTIES_LIST_CODE, 381 | HSB_BAD_PROPERTIES_LIST_FAULT_FS, 382 | Util.newError(buf.toString())); 383 | } else { 384 | } 385 | this.properties = Collections.unmodifiableMap(properties); 386 | } 387 | 388 | 389 | private void initCommand(Element commandRoot) throws XmlaException { 390 | Element[] childElems = 391 | XmlaUtil.filterChildElements( 392 | commandRoot, 393 | NS_XMLA, 394 | "Statement"); 395 | if (childElems.length != 1) { 396 | StringBuilder buf = new StringBuilder(100); 397 | buf.append(MSG_INVALID_XMLA); 398 | buf.append(": Wrong number of Statement elements: "); 399 | buf.append(childElems.length); 400 | throw new XmlaException( 401 | CLIENT_FAULT_FC, 402 | HSB_BAD_STATEMENT_CODE, 403 | HSB_BAD_STATEMENT_FAULT_FS, 404 | Util.newError(buf.toString())); 405 | } 406 | statement = XmlaUtil.textInElement(childElems[0]).replaceAll("\\r", ""); 407 | drillthrough = statement.toUpperCase().indexOf("DRILLTHROUGH") != -1; 408 | } 409 | } 410 | 411 | // End DefaultXmlaRequest.java 412 | -------------------------------------------------------------------------------- /src/main/java/mondrian/xmla/impl/DefaultXmlaResponse.java: -------------------------------------------------------------------------------- 1 | /* 2 | // This software is subject to the terms of the Eclipse Public License v1.0 3 | // Agreement, available at the following URL: 4 | // http://www.eclipse.org/legal/epl-v10.html. 5 | // You must accept the terms of that agreement to use this software. 6 | // 7 | // Copyright (C) 2005-2012 Pentaho 8 | // All Rights Reserved. 9 | */ 10 | package mondrian.xmla.impl; 11 | 12 | import mondrian.xmla.*; 13 | 14 | import org.olap4j.xmla.server.impl.Util; 15 | 16 | import java.io.OutputStream; 17 | import java.io.UnsupportedEncodingException; 18 | 19 | /** 20 | * Default implementation of {@link mondrian.xmla.XmlaResponse}. 21 | * 22 | * @author Gang Chen 23 | */ 24 | public class DefaultXmlaResponse implements XmlaResponse { 25 | 26 | // TODO: add a msg to MondrianResource for this. 27 | private static final String MSG_ENCODING_ERROR = "Encoding unsupported: "; 28 | 29 | private final SaxWriter writer; 30 | 31 | public DefaultXmlaResponse( 32 | OutputStream outputStream, 33 | String encoding, 34 | Enumeration.ResponseMimeType responseMimeType) 35 | { 36 | try { 37 | switch (responseMimeType) { 38 | case JSON: 39 | writer = new JsonSaxWriter(outputStream); 40 | break; 41 | case SOAP: 42 | default: 43 | writer = new DefaultSaxWriter(outputStream, encoding); 44 | break; 45 | } 46 | } catch (UnsupportedEncodingException uee) { 47 | throw Util.newError(uee, MSG_ENCODING_ERROR + encoding); 48 | } 49 | } 50 | 51 | public SaxWriter getWriter() { 52 | return writer; 53 | } 54 | 55 | public void error(Throwable t) { 56 | writer.completeBeforeElement("root"); 57 | @SuppressWarnings({"ThrowableResultOfMethodCallIgnored"}) 58 | Throwable throwable = XmlaUtil.rootThrowable(t); 59 | writer.startElement("Messages"); 60 | writer.startElement( 61 | "Error", 62 | "ErrorCode", throwable.getClass().getName(), 63 | "Description", throwable.getMessage(), 64 | "Source", "Mondrian", 65 | "Help", ""); 66 | writer.endElement(); // 67 | writer.endElement(); // 68 | } 69 | } 70 | 71 | // End DefaultXmlaResponse.java 72 | -------------------------------------------------------------------------------- /src/main/java/mondrian/xmla/impl/JsonSaxWriter.java: -------------------------------------------------------------------------------- 1 | /* 2 | // This software is subject to the terms of the Eclipse Public License v1.0 3 | // Agreement, available at the following URL: 4 | // http://www.eclipse.org/legal/epl-v10.html. 5 | // You must accept the terms of that agreement to use this software. 6 | // 7 | // Copyright (C) 2010-2012 Pentaho 8 | // All Rights Reserved. 9 | */ 10 | package mondrian.xmla.impl; 11 | 12 | import mondrian.xmla.SaxWriter; 13 | 14 | import org.olap4j.xmla.server.impl.ArrayStack; 15 | import org.olap4j.xmla.server.impl.Util; 16 | 17 | import java.io.IOException; 18 | import java.io.OutputStream; 19 | import java.util.Arrays; 20 | 21 | /** 22 | * Implementation of SaxWriter which, perversely, generates a 23 | * JSON (JavaScript Object Notation) document. 24 | * 25 | * @author jhyde 26 | */ 27 | class JsonSaxWriter implements SaxWriter { 28 | private final StringBuilder buf = new StringBuilder(); 29 | private int indent; 30 | private String[] indentStrings = INITIAL_INDENT_STRINGS; 31 | private String indentString = indentStrings[0]; 32 | private final ArrayStack stack = new ArrayStack(); 33 | private OutputStream outputStream; 34 | 35 | private static final String[] INITIAL_INDENT_STRINGS = { 36 | "", 37 | " ", 38 | " ", 39 | " ", 40 | " ", 41 | " ", 42 | " ", 43 | " ", 44 | " ", 45 | " ", 46 | }; 47 | 48 | /** 49 | * Creates a JsonSaxWriter. 50 | * 51 | * @param outputStream Output stream 52 | */ 53 | public JsonSaxWriter(OutputStream outputStream) { 54 | this.outputStream = outputStream; 55 | } 56 | 57 | public void startDocument() { 58 | stack.push(new Frame(null)); 59 | } 60 | 61 | public void endDocument() { 62 | stack.pop(); 63 | flush(); 64 | } 65 | 66 | public void startSequence(String name, String subName) { 67 | comma(); 68 | buf.append(indentString); 69 | if (name == null) { 70 | name = subName; 71 | } 72 | if (stack.peek().name != null) { 73 | assert name.equals(stack.peek().name) 74 | : "In sequence [" + stack.peek() + "], element name [" 75 | + name + "]"; 76 | buf.append("["); 77 | } else { 78 | Util.quoteForMdx(buf, name); 79 | buf.append(": ["); 80 | } 81 | 82 | assert subName != null; 83 | stack.push(new Frame(subName)); 84 | indent(); 85 | } 86 | 87 | public void endSequence() { 88 | assert stack.peek() != null : "not in sequence"; 89 | stack.pop(); 90 | outdent(); 91 | 92 | buf.append("\n"); 93 | buf.append(indentString); 94 | buf.append("]"); 95 | } 96 | 97 | public void startElement(String name) { 98 | comma(); 99 | buf.append(indentString); 100 | if (stack.peek().name != null) { 101 | assert name.equals(stack.peek().name) 102 | : "In sequence [" + stack.peek() + "], element name [" 103 | + name + "]"; 104 | buf.append("{"); 105 | } else { 106 | Util.quoteForMdx(buf, name); 107 | buf.append(": {"); 108 | } 109 | 110 | stack.push(new Frame(null)); 111 | indent(); 112 | } 113 | 114 | public void startElement(String name, Object... attrs) { 115 | startElement(name); 116 | for (int i = 0; i < attrs.length;) { 117 | if (i > 0) { 118 | buf.append(",\n"); 119 | } else { 120 | buf.append("\n"); 121 | } 122 | String attr = (String) attrs[i++]; 123 | buf.append(indentString); 124 | Util.quoteForMdx(buf, attr); 125 | buf.append(": "); 126 | Object value = attrs[i++]; 127 | value(value); 128 | } 129 | stack.peek().ordinal = attrs.length / 2; 130 | } 131 | 132 | public void endElement() { 133 | Frame prev = stack.pop(); 134 | assert prev.name == null 135 | : "Ended an element, but in sequence " + prev.name; 136 | buf.append("\n"); 137 | outdent(); 138 | buf.append(indentString); 139 | buf.append("}"); 140 | } 141 | 142 | public void element(String name, Object... attrs) { 143 | startElement(name, attrs); 144 | endElement(); 145 | } 146 | 147 | public void characters(String data) { 148 | throw new UnsupportedOperationException(); 149 | } 150 | 151 | public void textElement(String name, Object data) { 152 | comma(); 153 | buf.append(indentString); 154 | Util.quoteForMdx(buf, name); 155 | buf.append(": "); 156 | value(data); 157 | } 158 | 159 | public void completeBeforeElement(String tagName) { 160 | throw new UnsupportedOperationException(); 161 | } 162 | 163 | public void verbatim(String text) { 164 | throw new UnsupportedOperationException(); 165 | } 166 | 167 | public void flush() { 168 | try { 169 | outputStream.write(buf.toString().substring(1).getBytes()); 170 | } catch (IOException e) { 171 | throw Util.newError(e, "While encoding JSON response"); 172 | } 173 | } 174 | 175 | // helper methods 176 | 177 | private void indent() { 178 | ++indent; 179 | if (indent >= indentStrings.length) { 180 | final int newLength = indentStrings.length * 2 + 1; 181 | final int INDENT = 2; 182 | assert indentStrings[1].length() == INDENT; 183 | char[] chars = new char[newLength * INDENT]; 184 | Arrays.fill(chars, ' '); 185 | String s = new String(chars); 186 | indentStrings = new String[newLength]; 187 | for (int i = 0; i < newLength; ++i) { 188 | indentStrings[i] = s.substring(0, i * INDENT); 189 | } 190 | } 191 | indentString = indentStrings[indent]; 192 | } 193 | 194 | private void outdent() { 195 | indentString = indentStrings[--indent]; 196 | } 197 | 198 | /** 199 | * Writes a value with appropriate quoting for a JavaScript constant 200 | * of that type. 201 | * 202 | *

Examples: {@code "a \"quoted\" string"} (string), 203 | * {@code 12} (int), {@code 12.345} (float), {@code null} (null value). 204 | * 205 | * @param value Value 206 | */ 207 | private void value(Object value) { 208 | if (value instanceof String) { 209 | String s = (String) value; 210 | Util.quoteForMdx(buf, s); 211 | } else { 212 | buf.append(value); 213 | } 214 | } 215 | 216 | private void comma() { 217 | if (stack.peek().ordinal++ > 0) { 218 | buf.append(",\n"); 219 | } else { 220 | buf.append("\n"); 221 | } 222 | } 223 | 224 | private static class Frame { 225 | final String name; 226 | int ordinal; 227 | 228 | Frame(String name) { 229 | this.name = name; 230 | } 231 | } 232 | } 233 | 234 | // End JsonSaxWriter.java 235 | -------------------------------------------------------------------------------- /src/main/java/mondrian/xmla/impl/Olap4jXmlaServlet.java: -------------------------------------------------------------------------------- 1 | /* 2 | // This software is subject to the terms of the Eclipse Public License v1.0 3 | // Agreement, available at the following URL: 4 | // http://www.eclipse.org/legal/epl-v10.html. 5 | // You must accept the terms of that agreement to use this software. 6 | // 7 | // Copyright (C) 2011-2012 Pentaho 8 | // All Rights Reserved. 9 | */ 10 | package mondrian.xmla.impl; 11 | 12 | import mondrian.xmla.XmlaHandler; 13 | import mondrian.xmla.XmlaRequest; 14 | 15 | import org.olap4j.xmla.server.impl.Util; 16 | 17 | import org.apache.commons.dbcp.BasicDataSource; 18 | import org.apache.commons.dbcp.DelegatingConnection; 19 | import org.apache.log4j.Logger; 20 | 21 | import org.olap4j.OlapConnection; 22 | import org.olap4j.OlapWrapper; 23 | 24 | import java.lang.reflect.*; 25 | import java.sql.Connection; 26 | import java.sql.SQLException; 27 | import java.util.*; 28 | import javax.servlet.ServletConfig; 29 | import javax.servlet.ServletException; 30 | 31 | /** 32 | * XMLA servlet that gets its connections from an olap4j data source. 33 | * 34 | * @author Julian Hyde 35 | * @author Michele Rossi 36 | */ 37 | public class Olap4jXmlaServlet extends DefaultXmlaServlet { 38 | private static final Logger LOGGER = 39 | Logger.getLogger(Olap4jXmlaServlet.class); 40 | 41 | private static final String OLAP_DRIVER_CLASS_NAME_PARAM = 42 | "OlapDriverClassName"; 43 | 44 | private static final String OLAP_DRIVER_CONNECTION_STRING_PARAM = 45 | "OlapDriverConnectionString"; 46 | 47 | private static final String OLAP_DRIVER_CONNECTION_PROPERTIES_PREFIX = 48 | "OlapDriverConnectionProperty."; 49 | 50 | private static final String 51 | OLAP_DRIVER_PRECONFIGURED_DISCOVER_DATASOURCES_RESPONSE = 52 | "OlapDriverUsePreConfiguredDiscoverDatasourcesResponse"; 53 | 54 | private static final String OLAP_DRIVER_IDLE_CONNECTIONS_TIMEOUT_MINUTES = 55 | "OlapDriverIdleConnectionsTimeoutMinutes"; 56 | 57 | private static final String 58 | OLAP_DRIVER_PRECONFIGURED_DISCOVER_DATASOURCES_PREFIX = 59 | "OlapDriverDiscoverDatasources."; 60 | 61 | /** 62 | * Name of property used by JDBC to hold user name. 63 | */ 64 | private static final String JDBC_USER = "user"; 65 | 66 | /** 67 | * Name of property used by JDBC to hold password. 68 | */ 69 | private static final String JDBC_PASSWORD = "password"; 70 | 71 | /** idle connections are cleaned out after 5 minutes by default */ 72 | private static final int DEFAULT_IDLE_CONNECTIONS_TIMEOUT_MS = 73 | 5 * 60 * 1000; 74 | 75 | private static final String OLAP_DRIVER_MAX_NUM_CONNECTIONS_PER_USER = 76 | "OlapDriverMaxNumConnectionsPerUser"; 77 | 78 | /** 79 | * Unwraps a given interface from a given connection. 80 | * 81 | * @param connection Connection object 82 | * @param clazz Interface to unwrap 83 | * @param Type of interface 84 | * @return Unwrapped object; never null 85 | * @throws java.sql.SQLException if cannot convert 86 | */ 87 | private static T unwrap(Connection connection, Class clazz) 88 | throws SQLException 89 | { 90 | // Invoke Wrapper.unwrap(). Works for JDK 1.6 and later, but we use 91 | // reflection so that it compiles on JDK 1.5. 92 | try { 93 | final Class wrapperClass = Class.forName("java.sql.Wrapper"); 94 | if (wrapperClass.isInstance(connection)) { 95 | Method unwrapMethod = wrapperClass.getMethod("unwrap"); 96 | return clazz.cast(unwrapMethod.invoke(connection, clazz)); 97 | } 98 | } catch (ClassNotFoundException e) { 99 | // ignore 100 | } catch (NoSuchMethodException e) { 101 | // ignore 102 | } catch (InvocationTargetException e) { 103 | // ignore 104 | } catch (IllegalAccessException e) { 105 | // ignore 106 | } 107 | if (connection instanceof OlapWrapper) { 108 | OlapWrapper olapWrapper = (OlapWrapper) connection; 109 | return olapWrapper.unwrap(clazz); 110 | } 111 | throw new SQLException("not an instance"); 112 | } 113 | 114 | @Override 115 | protected XmlaHandler.ConnectionFactory createConnectionFactory( 116 | ServletConfig servletConfig) 117 | throws ServletException 118 | { 119 | final String olap4jDriverClassName = 120 | servletConfig.getInitParameter(OLAP_DRIVER_CLASS_NAME_PARAM); 121 | final String olap4jDriverConnectionString = 122 | servletConfig.getInitParameter(OLAP_DRIVER_CONNECTION_STRING_PARAM); 123 | final String olap4jUsePreConfiguredDiscoverDatasourcesRes = 124 | servletConfig.getInitParameter( 125 | OLAP_DRIVER_PRECONFIGURED_DISCOVER_DATASOURCES_RESPONSE); 126 | boolean hardcodedDiscoverDatasources = 127 | olap4jUsePreConfiguredDiscoverDatasourcesRes != null 128 | && Boolean.parseBoolean( 129 | olap4jUsePreConfiguredDiscoverDatasourcesRes); 130 | 131 | final String idleConnTimeoutStr = 132 | servletConfig.getInitParameter( 133 | OLAP_DRIVER_IDLE_CONNECTIONS_TIMEOUT_MINUTES); 134 | final int idleConnectionsCleanupTimeoutMs = 135 | idleConnTimeoutStr != null 136 | ? Integer.parseInt(idleConnTimeoutStr) * 60 * 1000 137 | : DEFAULT_IDLE_CONNECTIONS_TIMEOUT_MS; 138 | 139 | final String maxNumConnPerUserStr = 140 | servletConfig.getInitParameter( 141 | OLAP_DRIVER_MAX_NUM_CONNECTIONS_PER_USER); 142 | int maxNumConnectionsPerUser = 143 | maxNumConnPerUserStr != null 144 | ? Integer.parseInt(maxNumConnPerUserStr) 145 | : 1; 146 | try { 147 | Map connectionProperties = 148 | getOlap4jConnectionProperties( 149 | servletConfig, 150 | OLAP_DRIVER_CONNECTION_PROPERTIES_PREFIX); 151 | final Map ddhcRes; 152 | if (hardcodedDiscoverDatasources) { 153 | ddhcRes = 154 | getDiscoverDatasourcesPreConfiguredResponse(servletConfig); 155 | } else { 156 | ddhcRes = null; 157 | } 158 | 159 | return new Olap4jPoolingConnectionFactory( 160 | olap4jDriverClassName, 161 | olap4jDriverConnectionString, 162 | connectionProperties, 163 | idleConnectionsCleanupTimeoutMs, 164 | maxNumConnectionsPerUser, 165 | ddhcRes); 166 | } catch (Exception ex) { 167 | String msg = 168 | "Exception [" + ex + "] while trying to create " 169 | + "olap4j connection to [" 170 | + olap4jDriverConnectionString + "] using driver " 171 | + "[" + olap4jDriverClassName + "]"; 172 | LOGGER.error(msg, ex); 173 | throw new ServletException(msg, ex); 174 | } 175 | } 176 | 177 | private static Map 178 | getDiscoverDatasourcesPreConfiguredResponse( 179 | ServletConfig servletConfig) 180 | { 181 | final Map map = new LinkedHashMap(); 182 | foo(map, "DataSourceName", servletConfig, "dataSourceName"); 183 | foo( 184 | map, "DataSourceDescription", 185 | servletConfig, "dataSourceDescription"); 186 | foo(map, "URL", servletConfig, "url"); 187 | foo(map, "DataSourceInfo", servletConfig, "dataSourceInfo"); 188 | foo(map, "ProviderName", servletConfig, "providerName"); 189 | foo(map, "ProviderType", servletConfig, "providerType"); 190 | foo(map, "AuthenticationMode", servletConfig, "authenticationMode"); 191 | return map; 192 | } 193 | 194 | private static void foo( 195 | Map map, 196 | String targetProp, 197 | ServletConfig servletConfig, 198 | String sourceProp) 199 | { 200 | final String value = 201 | servletConfig.getInitParameter( 202 | OLAP_DRIVER_PRECONFIGURED_DISCOVER_DATASOURCES_PREFIX 203 | + sourceProp); 204 | map.put(targetProp, value); 205 | } 206 | 207 | private static class Olap4jPoolingConnectionFactory 208 | implements XmlaHandler.ConnectionFactory 209 | { 210 | private final String olap4jDriverConnectionString; 211 | private final Properties connProperties; 212 | private final Map discoverDatasourcesResponse; 213 | private final String olap4jDriverClassName; 214 | private final Map datasourcesPool = 215 | new HashMap(); 216 | private final int idleConnectionsCleanupTimeoutMs; 217 | private final int maxPerUserConnectionCount; 218 | private final XmlaHandler.XmlaExtra extra = 219 | new XmlaHandler.XmlaExtraImpl(); 220 | 221 | /** 222 | * Creates an Olap4jPoolingConnectionFactory. 223 | * 224 | * @param olap4jDriverClassName Driver class name 225 | * @param olap4jDriverConnectionString Connect string 226 | * @param connectionProperties Connection properties 227 | * @param maxPerUserConnectionCount max number of connections to create 228 | * for every different username 229 | * @param idleConnectionsCleanupTimeoutMs pooled connections inactive 230 | * for longer than this period of time can be cleaned up 231 | * @param discoverDatasourcesResponse Pre-configured response to 232 | * DISCOVER_DATASOURCES request, or null 233 | * @throws ClassNotFoundException if driver class is not found 234 | */ 235 | public Olap4jPoolingConnectionFactory( 236 | final String olap4jDriverClassName, 237 | final String olap4jDriverConnectionString, 238 | final Map connectionProperties, 239 | final int idleConnectionsCleanupTimeoutMs, 240 | final int maxPerUserConnectionCount, 241 | final Map discoverDatasourcesResponse) 242 | throws ClassNotFoundException 243 | { 244 | Class.forName(olap4jDriverClassName); 245 | this.maxPerUserConnectionCount = maxPerUserConnectionCount; 246 | this.idleConnectionsCleanupTimeoutMs = 247 | idleConnectionsCleanupTimeoutMs; 248 | this.olap4jDriverClassName = olap4jDriverClassName; 249 | this.olap4jDriverConnectionString = olap4jDriverConnectionString; 250 | this.connProperties = new Properties(); 251 | this.connProperties.putAll(connectionProperties); 252 | this.discoverDatasourcesResponse = discoverDatasourcesResponse; 253 | } 254 | 255 | public OlapConnection getConnection( 256 | String catalog, 257 | String schema, 258 | String roleName, 259 | Properties props) 260 | throws SQLException 261 | { 262 | final String user = props.getProperty(JDBC_USER); 263 | final String pwd = props.getProperty(JDBC_PASSWORD); 264 | 265 | // note: this works also for un-authenticated connections; they will 266 | // simply all be created by the same BasicDataSource object 267 | final String dataSourceKey = user + "_" + pwd; 268 | 269 | BasicDataSource bds; 270 | synchronized (datasourcesPool) { 271 | bds = datasourcesPool.get(dataSourceKey); 272 | if (bds == null) { 273 | bds = new BasicDataSource(); 274 | for (Map.Entry entry : connProperties.entrySet()) { 275 | bds.addConnectionProperty( 276 | (String) entry.getKey(), 277 | (String) entry.getValue()); 278 | } 279 | bds.setDefaultReadOnly(true); 280 | bds.setDriverClassName(olap4jDriverClassName); 281 | bds.setPassword(pwd); 282 | bds.setUsername(user); 283 | bds.setUrl(olap4jDriverConnectionString); 284 | bds.setPoolPreparedStatements(false); 285 | bds.setMaxIdle(maxPerUserConnectionCount); 286 | bds.setMaxActive(maxPerUserConnectionCount); 287 | bds.setMinEvictableIdleTimeMillis( 288 | idleConnectionsCleanupTimeoutMs); 289 | bds.setAccessToUnderlyingConnectionAllowed(true); 290 | bds.setInitialSize(1); 291 | bds.setTimeBetweenEvictionRunsMillis(60000); 292 | if (catalog != null) { 293 | bds.setDefaultCatalog(catalog); 294 | } 295 | datasourcesPool.put(dataSourceKey, bds); 296 | } 297 | } 298 | 299 | Connection connection = bds.getConnection(); 300 | DelegatingConnection dc = (DelegatingConnection) connection; 301 | Connection underlyingOlapConnection = dc.getInnermostDelegate(); 302 | OlapConnection olapConnection = 303 | unwrap(underlyingOlapConnection, OlapConnection.class); 304 | 305 | if (LOGGER.isDebugEnabled()) { 306 | LOGGER.debug( 307 | "Obtained connection object [" + olapConnection 308 | + "] (ext pool wrapper " + connection + ") for key " 309 | + dataSourceKey); 310 | } 311 | if (catalog != null) { 312 | olapConnection.setCatalog(catalog); 313 | } 314 | if (schema != null) { 315 | olapConnection.setSchema(schema); 316 | } 317 | if (roleName != null) { 318 | olapConnection.setRoleName(roleName); 319 | } 320 | 321 | return createDelegatingOlapConnection(connection, olapConnection); 322 | } 323 | 324 | public Map getPreConfiguredDiscoverDatasourcesResponse() 325 | { 326 | return discoverDatasourcesResponse; 327 | } 328 | 329 | public XmlaHandler.Request startRequest( 330 | XmlaRequest request, 331 | OlapConnection connection) 332 | { 333 | // This XMLA server implementation does not track requests. 334 | return null; 335 | } 336 | 337 | public void endRequest(XmlaHandler.Request request) { 338 | // This XMLA server implementation does not track requests. 339 | } 340 | 341 | public XmlaHandler.XmlaExtra getExtra() { 342 | return extra; 343 | } 344 | } 345 | 346 | /** 347 | * Obtains connection properties from the 348 | * ServletConfig init parameters and from System properties. 349 | * 350 | *

The properties found in the System properties override the ones in 351 | * the ServletConfig. 352 | * 353 | *

copies the values of init parameters / properties which 354 | * start with the given prefix to a target Map object stripping out the 355 | * configured prefix from the property name. 356 | * 357 | *

The following example uses prefix "olapConn.": 358 | * 359 | *

360 |      *  <init-param>
361 |      *      <param-name>olapConn.User</param-name>
362 |      *      <param-value>mrossi</param-value>
363 |      *  </init-param>
364 |      *  <init-param>
365 |      *      <param-name>olapConn.Password</param-name>
366 |      *      <param-value>manhattan</param-value>
367 |      *  </init-param>
368 |      *
369 |      * 
370 | * 371 | *

This will result in a connection properties object with entries 372 | * {("User", "mrossi"), ("Password", "manhattan")}. 373 | * 374 | * @param prefix Prefix to property name 375 | * @param servletConfig Servlet config 376 | * @return Map containing property names and values 377 | */ 378 | private static Map getOlap4jConnectionProperties( 379 | final ServletConfig servletConfig, 380 | final String prefix) 381 | { 382 | Map options = new LinkedHashMap(); 383 | 384 | // Get properties from servlet config. 385 | @SuppressWarnings({"unchecked"}) 386 | java.util.Enumeration en = 387 | servletConfig.getInitParameterNames(); 388 | while (en.hasMoreElements()) { 389 | String paramName = en.nextElement(); 390 | if (paramName.startsWith(prefix)) { 391 | String paramValue = servletConfig.getInitParameter(paramName); 392 | String prefixRemovedParamName = 393 | paramName.substring(prefix.length()); 394 | options.put(prefixRemovedParamName, paramValue); 395 | } 396 | } 397 | 398 | // Get system properties. 399 | final Map systemProps = 400 | Util.toMap(System.getProperties()); 401 | for (Map.Entry entry : systemProps.entrySet()) { 402 | String sk = entry.getKey(); 403 | if (sk.startsWith(prefix)) { 404 | String value = entry.getValue(); 405 | String prefixRemovedKey = sk.substring(prefix.length()); 406 | options.put(prefixRemovedKey, value); 407 | } 408 | } 409 | 410 | return options; 411 | } 412 | 413 | /** 414 | * Returns something that implements {@link OlapConnection} but still 415 | * behaves as the wrapper returned by the connection pool. 416 | * 417 | *

In other words we want the "close" method to play nice and do all the 418 | * pooling actions while we want all the olap methods to execute directly on 419 | * the un-wrapped OlapConnection object. 420 | */ 421 | private static OlapConnection createDelegatingOlapConnection( 422 | final Connection connection, 423 | final OlapConnection olapConnection) 424 | { 425 | return (OlapConnection) Proxy.newProxyInstance( 426 | olapConnection.getClass().getClassLoader(), 427 | new Class[] {OlapConnection.class}, 428 | new InvocationHandler() { 429 | public Object invoke( 430 | Object proxy, 431 | Method method, 432 | Object[] args) 433 | throws Throwable 434 | { 435 | if ("unwrap".equals(method.getName()) 436 | || OlapConnection.class 437 | .isAssignableFrom(method.getDeclaringClass())) 438 | { 439 | return method.invoke(olapConnection, args); 440 | } else { 441 | return method.invoke(connection, args); 442 | } 443 | } 444 | } 445 | ); 446 | } 447 | } 448 | 449 | // End Olap4jXmlaServlet.java 450 | -------------------------------------------------------------------------------- /src/main/java/mondrian/xmla/package.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Implements the XML for Analysis API. 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/main/java/org/olap4j/xmla/server/impl/ArrayStack.java: -------------------------------------------------------------------------------- 1 | /* 2 | // This software is subject to the terms of the Eclipse Public License v1.0 3 | // Agreement, available at the following URL: 4 | // http://www.eclipse.org/legal/epl-v10.html. 5 | // You must accept the terms of that agreement to use this software. 6 | // 7 | // Copyright (C) 2009-2012 Pentaho 8 | // All Rights Reserved. 9 | */ 10 | package org.olap4j.xmla.server.impl; 11 | 12 | import java.util.ArrayList; 13 | import java.util.EmptyStackException; 14 | 15 | /** 16 | * Stack implementation based on {@link java.util.ArrayList}. 17 | * 18 | *

More efficient than {@link java.util.Stack}, which extends 19 | * {@link java.util.Vector} and is 20 | * therefore synchronized whether you like it or not. 21 | * 22 | * @param Element type 23 | * 24 | * @author jhyde 25 | */ 26 | public class ArrayStack extends ArrayList { 27 | /** 28 | * Default constructor. 29 | */ 30 | public ArrayStack() { 31 | super(); 32 | } 33 | 34 | /** 35 | * Copy Constructor 36 | * @param toCopy Instance of {@link ArrayStack} to copy. 37 | */ 38 | public ArrayStack(ArrayStack toCopy) { 39 | super(); 40 | this.addAll(toCopy); 41 | } 42 | 43 | /** 44 | * Analogous to {@link java.util.Stack#push}. 45 | */ 46 | public E push(E item) { 47 | add(item); 48 | return item; 49 | } 50 | 51 | /** 52 | * Analogous to {@link java.util.Stack#pop}. 53 | */ 54 | public E pop() { 55 | int len = size(); 56 | E obj = peek(); 57 | remove(len - 1); 58 | return obj; 59 | } 60 | 61 | /** 62 | * Analogous to {@link java.util.Stack#peek}. 63 | */ 64 | public E peek() { 65 | int len = size(); 66 | if (len <= 0) { 67 | throw new EmptyStackException(); 68 | } 69 | return get(len - 1); 70 | } 71 | } 72 | 73 | // End ArrayStack.java 74 | -------------------------------------------------------------------------------- /src/main/java/org/olap4j/xmla/server/impl/Composite.java: -------------------------------------------------------------------------------- 1 | /* 2 | // This software is subject to the terms of the Eclipse Public License v1.0 3 | // Agreement, available at the following URL: 4 | // http://www.eclipse.org/legal/epl-v10.html. 5 | // You must accept the terms of that agreement to use this software. 6 | // 7 | // Copyright (C) 2011-2012 Pentaho 8 | // All Rights Reserved. 9 | */ 10 | package org.olap4j.xmla.server.impl; 11 | 12 | import java.util.*; 13 | 14 | /** 15 | * Composite collections. 16 | * 17 | * @author jhyde 18 | */ 19 | public abstract class Composite { 20 | 21 | /** 22 | * Creates a composite list, inferring the element type from the arguments. 23 | * 24 | * @param lists One or more lists 25 | * @param element type 26 | * @return composite list 27 | */ 28 | public static List of( 29 | List... lists) 30 | { 31 | return CompositeList.of(lists); 32 | } 33 | 34 | /** 35 | * Creates a composite iterable, inferring the element type from the 36 | * arguments. 37 | * 38 | * @param iterables One or more iterables 39 | * @param element type 40 | * @return composite iterable 41 | */ 42 | public static Iterable of( 43 | Iterable... iterables) 44 | { 45 | return new CompositeIterable(iterables); 46 | } 47 | 48 | /** 49 | * Creates a composite list, inferring the element type from the arguments. 50 | * 51 | * @param iterators One or more iterators 52 | * @param element type 53 | * @return composite list 54 | */ 55 | public static Iterator of( 56 | Iterator... iterators) 57 | { 58 | //noinspection unchecked 59 | return new CompositeIterator((Iterator[]) iterators); 60 | } 61 | 62 | private static class CompositeIterable implements Iterable { 63 | private final Iterable[] iterables; 64 | 65 | private CompositeIterable(Iterable[] iterables) { 66 | this.iterables = iterables; 67 | } 68 | 69 | public Iterator iterator() { 70 | //noinspection unchecked 71 | return new CompositeIterator(iterables); 72 | } 73 | } 74 | 75 | private static class CompositeIterator implements Iterator { 76 | private final Iterator> iteratorIterator; 77 | private boolean hasNext; 78 | private T next; 79 | private Iterator iterator; 80 | 81 | public CompositeIterator(Iterator[] iterables) { 82 | this.iteratorIterator = Arrays.asList(iterables).iterator(); 83 | this.iterator = EmptyIterator.instance(); 84 | this.hasNext = true; 85 | advance(); 86 | } 87 | 88 | public CompositeIterator(Iterable[] iterables) { 89 | this.iteratorIterator = new IterableIterator(iterables); 90 | this.iterator = EmptyIterator.instance(); 91 | this.hasNext = true; 92 | advance(); 93 | } 94 | 95 | private void advance() { 96 | for (;;) { 97 | if (iterator.hasNext()) { 98 | next = iterator.next(); 99 | return; 100 | } 101 | if (!iteratorIterator.hasNext()) { 102 | hasNext = false; 103 | break; 104 | } 105 | iterator = iteratorIterator.next(); 106 | } 107 | } 108 | 109 | public boolean hasNext() { 110 | return hasNext; 111 | } 112 | 113 | public T next() { 114 | final T next1 = next; 115 | advance(); 116 | return next1; 117 | } 118 | 119 | public void remove() { 120 | throw new UnsupportedOperationException(); 121 | } 122 | } 123 | 124 | private static class IterableIterator 125 | implements Iterator> 126 | { 127 | private int i; 128 | private final Iterable[] iterables; 129 | 130 | public IterableIterator(Iterable[] iterables) { 131 | this.iterables = iterables; 132 | i = 0; 133 | } 134 | 135 | public boolean hasNext() { 136 | return i < iterables.length; 137 | } 138 | 139 | public Iterator next() { 140 | return iterables[i++].iterator(); 141 | } 142 | 143 | public void remove() { 144 | throw new UnsupportedOperationException(); 145 | } 146 | } 147 | 148 | private static class EmptyIterator implements Iterator { 149 | private static final Iterator INSTANCE = new EmptyIterator(); 150 | 151 | private static Iterator instance() { 152 | //noinspection unchecked 153 | return INSTANCE; 154 | } 155 | 156 | public boolean hasNext() { 157 | return false; 158 | } 159 | 160 | public Object next() { 161 | throw new NoSuchElementException(); 162 | } 163 | 164 | public void remove() { 165 | throw new IllegalStateException(); 166 | } 167 | } 168 | } 169 | 170 | // End Composite.java 171 | -------------------------------------------------------------------------------- /src/main/java/org/olap4j/xmla/server/impl/CompositeList.java: -------------------------------------------------------------------------------- 1 | /* 2 | // This software is subject to the terms of the Eclipse Public License v1.0 3 | // Agreement, available at the following URL: 4 | // http://www.eclipse.org/legal/epl-v10.html. 5 | // You must accept the terms of that agreement to use this software. 6 | // 7 | // Copyright (C) 2009-2012 Pentaho 8 | // All Rights Reserved. 9 | */ 10 | package org.olap4j.xmla.server.impl; 11 | 12 | import java.util.AbstractList; 13 | import java.util.List; 14 | 15 | /** 16 | * List composed of several lists. 17 | * 18 | * @param element type 19 | * 20 | * @author jhyde 21 | */ 22 | public class CompositeList extends AbstractList { 23 | private final List[] lists; 24 | 25 | /** 26 | * Creates a composite list. 27 | * 28 | * @param lists Component lists 29 | */ 30 | public CompositeList( 31 | List... lists) 32 | { 33 | this.lists = lists; 34 | } 35 | 36 | /** 37 | * Creates a composite list, inferring the element type from the arguments. 38 | * 39 | * @param lists One or more lists 40 | * @param element type 41 | * @return composite list 42 | */ 43 | public static CompositeList of( 44 | List... lists) 45 | { 46 | return new CompositeList(lists); 47 | } 48 | 49 | public T get(int index) { 50 | int n = 0; 51 | for (List list : lists) { 52 | int next = n + list.size(); 53 | if (index < next) { 54 | return list.get(index - n); 55 | } 56 | n = next; 57 | } 58 | throw new IndexOutOfBoundsException( 59 | "index" + index + " out of bounds in list of size " + n); 60 | } 61 | 62 | public int size() { 63 | int n = 0; 64 | for (List array : lists) { 65 | n += array.size(); 66 | } 67 | return n; 68 | } 69 | } 70 | 71 | // End CompositeList.java 72 | -------------------------------------------------------------------------------- /src/main/java/org/olap4j/xmla/server/impl/StringEscaper.java: -------------------------------------------------------------------------------- 1 | /* 2 | // Licensed to Julian Hyde under one or more contributor license 3 | // agreements. See the NOTICE file distributed with this work for 4 | // additional information regarding copyright ownership. 5 | // 6 | // Julian Hyde licenses this file to you under the Apache License, 7 | // Version 2.0 (the "License"); you may not use this file except in 8 | // compliance with the License. You may obtain a copy of the License at: 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | */ 18 | package org.olap4j.xmla.server.impl; 19 | 20 | import java.io.IOException; 21 | import java.util.*; 22 | 23 | /** 24 | * Utility for replacing special characters 25 | * with escape sequences in strings. 26 | * 27 | *

To create a StringEscaper, create a builder. 28 | * Initially it is the identity transform (it leaves every character unchanged). 29 | * Call {@link Builder#defineEscape} as many 30 | * times as necessary to set up mappings, and then call {@link Builder#build} 31 | * to create a StringEscaper.

32 | * 33 | *

StringEscaper is immutable, but you can call {@link #toBuilder()} to 34 | * get a builder back.

35 | * 36 | *

Several escapers are pre-defined:

37 | * 38 | *
39 | *
{@link #HTML_ESCAPER}
40 | *
HTML (using &amp;, &<, etc.)
41 | *
{@link #XML_ESCAPER}
42 | *
XML (same as HTML escaper)
43 | *
{@link #XML_NUMERIC_ESCAPER}
44 | *
Uses numeric codes, e.g. &#38; for &.
45 | *
{@link #URL_ARG_ESCAPER}
46 | *
Converts '?' and '&' in URL arguments into URL format
47 | *
{@link #URL_ESCAPER}
48 | *
Converts to URL format
49 | *
50 | */ 51 | public class StringEscaper 52 | { 53 | private final String [] translationTable; 54 | 55 | public static final StringEscaper HTML_ESCAPER = 56 | new Builder() 57 | .defineEscape('&', "&") 58 | .defineEscape('"', """) 59 | .defineEscape('\'', "'") // not "'" 60 | .defineEscape('<', "<") 61 | .defineEscape('>', ">") 62 | .build(); 63 | 64 | public static final StringEscaper XML_ESCAPER = HTML_ESCAPER; 65 | 66 | public static final StringEscaper XML_NUMERIC_ESCAPER = 67 | new Builder() 68 | .defineEscape('&',"&") 69 | .defineEscape('"',""") 70 | .defineEscape('\'',"'") 71 | .defineEscape('<',"<") 72 | .defineEscape('>',">") 73 | .defineEscape('\t'," ") 74 | .defineEscape('\n'," ") 75 | .defineEscape('\r'," ") 76 | .build(); 77 | 78 | public static final StringEscaper URL_ARG_ESCAPER = 79 | new Builder() 80 | .defineEscape('?', "%3f") 81 | .defineEscape('&', "%26") 82 | .build(); 83 | 84 | public static final StringEscaper URL_ESCAPER = 85 | URL_ARG_ESCAPER.toBuilder() 86 | .defineEscape('%', "%%") 87 | .defineEscape('"', "%22") 88 | .defineEscape('\r', "+") 89 | .defineEscape('\n', "+") 90 | .defineEscape(' ', "+") 91 | .defineEscape('#', "%23") 92 | .build(); 93 | 94 | /** 95 | * Creates a StringEscaper. Only called from Builder. 96 | */ 97 | private StringEscaper(String[] translationTable) { 98 | this.translationTable = translationTable; 99 | } 100 | 101 | /** 102 | * Apply an immutable transformation to the given string. 103 | */ 104 | public String escapeString(String s) 105 | { 106 | StringBuilder sb = null; 107 | int n = s.length(); 108 | for (int i = 0; i < n; i++) { 109 | char c = s.charAt(i); 110 | String escape; 111 | // codes >= 128 (e.g. Euro sign) are always escaped 112 | if (c > 127) { 113 | escape = "&#" + Integer.toString(c) + ";"; 114 | } else if (c >= translationTable.length) { 115 | escape = null; 116 | } else { 117 | escape = translationTable[c]; 118 | } 119 | if (escape == null) { 120 | if (sb != null) { 121 | sb.append(c); 122 | } 123 | } else { 124 | if (sb == null) { 125 | sb = new StringBuilder(n * 2); 126 | sb.append(s.substring(0, i)); 127 | } 128 | sb.append(escape); 129 | } 130 | } 131 | 132 | if (sb == null) { 133 | return s; 134 | } else { 135 | return sb.toString(); 136 | } 137 | } 138 | 139 | /** 140 | * Applies an immutable transformation to the given string, writing the 141 | * results to a string buffer. 142 | */ 143 | public void appendEscapedString(String s, StringBuffer sb) 144 | { 145 | int n = s.length(); 146 | for (int i = 0; i < n; i++) { 147 | char c = s.charAt(i); 148 | String escape; 149 | if (c >= translationTable.length) { 150 | escape = null; 151 | } else { 152 | escape = translationTable[c]; 153 | } 154 | if (escape == null) { 155 | sb.append(c); 156 | } else { 157 | sb.append(escape); 158 | } 159 | } 160 | } 161 | 162 | /** 163 | * Applies an immutable transformation to the given string, writing the 164 | * results to an {@link Appendable} (such as a {@link StringBuilder}). 165 | */ 166 | public void appendEscapedString(String s, Appendable sb) throws IOException 167 | { 168 | int n = s.length(); 169 | for (int i = 0; i < n; i++) { 170 | char c = s.charAt(i); 171 | String escape; 172 | if (c >= translationTable.length) { 173 | escape = null; 174 | } else { 175 | escape = translationTable[c]; 176 | } 177 | if (escape == null) { 178 | sb.append(c); 179 | } else { 180 | sb.append(escape); 181 | } 182 | } 183 | } 184 | 185 | /** 186 | * Creates a builder from an existing escaper. 187 | */ 188 | public Builder toBuilder() 189 | { 190 | return new Builder( 191 | new ArrayList(Arrays.asList(translationTable))); 192 | } 193 | 194 | /** 195 | * Builder for {@link StringEscaper} instances. 196 | */ 197 | public static class Builder { 198 | private final List translationVector; 199 | 200 | public Builder() { 201 | this(new ArrayList()); 202 | } 203 | 204 | private Builder(List translationVector) { 205 | this.translationVector = translationVector; 206 | } 207 | 208 | /** 209 | * Creates an escaper with the current state of the translation 210 | * table. 211 | * 212 | * @return A string escaper 213 | */ 214 | public StringEscaper build() { 215 | return new StringEscaper( 216 | translationVector.toArray( 217 | new String[translationVector.size()])); 218 | } 219 | 220 | /** 221 | * Map character "from" to escape sequence "to" 222 | */ 223 | public Builder defineEscape(char from, String to) { 224 | int i = (int) from; 225 | if (i >= translationVector.size()) { 226 | // Extend list by adding the requisite number of nulls. 227 | translationVector.addAll( 228 | Collections.nCopies( 229 | i + 1 - translationVector.size(), null)); 230 | } 231 | translationVector.set(i, to); 232 | return this; 233 | } 234 | } 235 | } 236 | 237 | // End StringEscaper.java 238 | -------------------------------------------------------------------------------- /src/main/java/org/olap4j/xmla/server/impl/Util.java: -------------------------------------------------------------------------------- 1 | /* 2 | // This software is subject to the terms of the Eclipse Public License v1.0 3 | // Agreement, available at the following URL: 4 | // http://www.eclipse.org/legal/epl-v10.html. 5 | // You must accept the terms of that agreement to use this software. 6 | // 7 | // Copyright (C) 2001-2005 Julian Hyde 8 | // Copyright (C) 2005-2012 Pentaho and others 9 | // All Rights Reserved. 10 | */ 11 | package org.olap4j.xmla.server.impl; 12 | 13 | import org.apache.commons.collections.Predicate; 14 | 15 | import java.math.BigDecimal; 16 | import java.math.MathContext; 17 | import java.util.*; 18 | import java.util.regex.Pattern; 19 | 20 | /** 21 | * Cut-down version of mondrian.olap.Util. 22 | */ 23 | public class Util { 24 | 25 | public static final String nl = System.getProperty("line.separator"); 26 | 27 | /** 28 | * When the compiler is complaining that you are not using a variable, just 29 | * call one of these routines with it. 30 | **/ 31 | public static void discard(boolean b) { 32 | } 33 | 34 | public static void discard(byte b) { 35 | } 36 | 37 | public static void discard(char c) { 38 | } 39 | 40 | public static void discard(double d) { 41 | } 42 | 43 | public static void discard(float d) { 44 | } 45 | 46 | public static void discard(int i) { 47 | } 48 | 49 | public static void discard(long l) { 50 | } 51 | 52 | public static void discard(Object o) { 53 | } 54 | 55 | public static void discard(short s) { 56 | } 57 | 58 | /** 59 | * Appends a double-quoted string to a string builder. 60 | */ 61 | public static StringBuilder quoteForMdx(StringBuilder buf, String val) { 62 | buf.append("\""); 63 | String s0 = replace(val, "\"", "\"\""); 64 | buf.append(s0); 65 | buf.append("\""); 66 | return buf; 67 | } 68 | 69 | /** 70 | * Return string quoted in [...]. For example, "San Francisco" becomes 71 | * "[San Francisco]"; "a [bracketed] string" becomes 72 | * "[a [bracketed]] string]". 73 | */ 74 | public static String quoteMdxIdentifier(String id) { 75 | StringBuilder buf = new StringBuilder(id.length() + 20); 76 | quoteMdxIdentifier(id, buf); 77 | return buf.toString(); 78 | } 79 | 80 | public static void quoteMdxIdentifier(String id, StringBuilder buf) { 81 | buf.append('['); 82 | int start = buf.length(); 83 | buf.append(id); 84 | replace(buf, start, "]", "]]"); 85 | buf.append(']'); 86 | } 87 | 88 | /** 89 | * Returns a string with every occurrence of a seek string replaced with 90 | * another. 91 | */ 92 | public static String replace(String s, String find, String replace) { 93 | // let's be optimistic 94 | int found = s.indexOf(find); 95 | if (found == -1) { 96 | return s; 97 | } 98 | StringBuilder sb = new StringBuilder(s.length() + 20); 99 | int start = 0; 100 | char[] chars = s.toCharArray(); 101 | final int step = find.length(); 102 | if (step == 0) { 103 | // Special case where find is "". 104 | sb.append(s); 105 | replace(sb, 0, find, replace); 106 | } else { 107 | for (;;) { 108 | sb.append(chars, start, found - start); 109 | if (found == s.length()) { 110 | break; 111 | } 112 | sb.append(replace); 113 | start = found + step; 114 | found = s.indexOf(find, start); 115 | if (found == -1) { 116 | found = s.length(); 117 | } 118 | } 119 | } 120 | return sb.toString(); 121 | } 122 | 123 | /** 124 | * Replaces all occurrences of a string in a buffer with another. 125 | * 126 | * @param buf String buffer to act on 127 | * @param start Ordinal within find to start searching 128 | * @param find String to find 129 | * @param replace String to replace it with 130 | * @return The string buffer 131 | */ 132 | public static StringBuilder replace( 133 | StringBuilder buf, 134 | int start, 135 | String find, 136 | String replace) 137 | { 138 | // Search and replace from the end towards the start, to avoid O(n ^ 2) 139 | // copying if the string occurs very commonly. 140 | int findLength = find.length(); 141 | if (findLength == 0) { 142 | // Special case where the seek string is empty. 143 | for (int j = buf.length(); j >= 0; --j) { 144 | buf.insert(j, replace); 145 | } 146 | return buf; 147 | } 148 | int k = buf.length(); 149 | while (k > 0) { 150 | int i = buf.lastIndexOf(find, k); 151 | if (i < start) { 152 | break; 153 | } 154 | buf.replace(i, i + find.length(), replace); 155 | // Step back far enough to ensure that the beginning of the section 156 | // we just replaced does not cause a match. 157 | k = i - findLength; 158 | } 159 | return buf; 160 | } 161 | 162 | /** 163 | * Converts a list of SQL-style patterns into a Java regular expression. 164 | * 165 | *

For example, {"Foo_", "Bar%BAZ"} becomes "Foo.|Bar.*BAZ". 166 | * 167 | * @param wildcards List of SQL-style wildcard expressions 168 | * @return Regular expression 169 | */ 170 | public static String wildcardToRegexp(List wildcards) { 171 | StringBuilder buf = new StringBuilder(); 172 | for (String value : wildcards) { 173 | if (buf.length() > 0) { 174 | buf.append('|'); 175 | } 176 | int i = 0; 177 | while (true) { 178 | int percent = value.indexOf('%', i); 179 | int underscore = value.indexOf('_', i); 180 | if (percent == -1 && underscore == -1) { 181 | if (i < value.length()) { 182 | buf.append(quotePattern(value.substring(i))); 183 | } 184 | break; 185 | } 186 | if (underscore >= 0 && (underscore < percent || percent < 0)) { 187 | if (i < underscore) { 188 | buf.append( 189 | quotePattern(value.substring(i, underscore))); 190 | } 191 | buf.append('.'); 192 | i = underscore + 1; 193 | } else if (percent >= 0 194 | && (percent < underscore || underscore < 0)) 195 | { 196 | if (i < percent) { 197 | buf.append( 198 | quotePattern(value.substring(i, percent))); 199 | } 200 | buf.append(".*"); 201 | i = percent + 1; 202 | } else { 203 | throw new IllegalArgumentException(); 204 | } 205 | } 206 | } 207 | return buf.toString(); 208 | } 209 | 210 | /** 211 | * Converts a camel-case name to an upper-case name with underscores. 212 | * 213 | *

For example, camelToUpper("FooBar") returns "FOO_BAR". 214 | * 215 | * @param s Camel-case string 216 | * @return Upper-case string 217 | */ 218 | public static String camelToUpper(String s) { 219 | StringBuilder buf = new StringBuilder(s.length() + 10); 220 | int prevUpper = -1; 221 | for (int i = 0; i < s.length(); ++i) { 222 | char c = s.charAt(i); 223 | if (Character.isUpperCase(c)) { 224 | if (i > prevUpper + 1) { 225 | buf.append('_'); 226 | } 227 | prevUpper = i; 228 | } else { 229 | c = Character.toUpperCase(c); 230 | } 231 | buf.append(c); 232 | } 233 | return buf.toString(); 234 | } 235 | 236 | /** 237 | * Parses a locale string. 238 | * 239 | *

The inverse operation of {@link java.util.Locale#toString()}. 240 | * 241 | * @param localeString Locale string, e.g. "en" or "en_US" 242 | * @return Java locale object 243 | */ 244 | public static Locale parseLocale(String localeString) { 245 | String[] strings = localeString.split("_"); 246 | switch (strings.length) { 247 | case 1: 248 | return new Locale(strings[0]); 249 | case 2: 250 | return new Locale(strings[0], strings[1]); 251 | case 3: 252 | return new Locale(strings[0], strings[1], strings[2]); 253 | default: 254 | throw newInternal( 255 | "bad locale string '" + localeString + "'"); 256 | } 257 | } 258 | 259 | /** 260 | * Applies a collection of filters to an iterable. 261 | * 262 | * @param iterable Iterable 263 | * @param conds Zero or more conditions 264 | * @param element type 265 | * @return Iterable that returns only members of underlying iterable for 266 | * for which all conditions evaluate to true 267 | */ 268 | public static Iterable filter( 269 | final Iterable iterable, 270 | final Predicate1... conds) 271 | { 272 | final Predicate1[] conds2 = optimizeConditions(conds); 273 | if (conds2.length == 0) { 274 | return iterable; 275 | } 276 | return new Iterable() { 277 | public Iterator iterator() { 278 | return new Iterator() { 279 | final Iterator iterator = iterable.iterator(); 280 | T next; 281 | boolean hasNext = moveToNext(); 282 | 283 | private boolean moveToNext() { 284 | outer: 285 | while (iterator.hasNext()) { 286 | next = iterator.next(); 287 | for (Predicate1 cond : conds2) { 288 | if (!cond.test(next)) { 289 | continue outer; 290 | } 291 | } 292 | return true; 293 | } 294 | return false; 295 | } 296 | 297 | public boolean hasNext() { 298 | return hasNext; 299 | } 300 | 301 | public T next() { 302 | T t = next; 303 | hasNext = moveToNext(); 304 | return t; 305 | } 306 | 307 | public void remove() { 308 | throw new UnsupportedOperationException(); 309 | } 310 | }; 311 | } 312 | }; 313 | } 314 | 315 | private static Predicate1[] optimizeConditions( 316 | Predicate1[] conds) 317 | { 318 | final List> predicateList = 319 | new ArrayList>(Arrays.asList(conds)); 320 | for (Iterator> funcIter = predicateList.iterator(); 321 | funcIter.hasNext();) 322 | { 323 | Predicate1 predicate = funcIter.next(); 324 | if (predicate == truePredicate1()) { 325 | funcIter.remove(); 326 | } 327 | } 328 | if (predicateList.size() < conds.length) { 329 | //noinspection unchecked 330 | return predicateList.toArray(new Predicate1[predicateList.size()]); 331 | } else { 332 | return conds; 333 | } 334 | } 335 | 336 | /** 337 | * Sorts a collection of {@link Comparable} objects and returns a list. 338 | * 339 | * @param collection Collection 340 | * @param Element type 341 | * @return Sorted list 342 | */ 343 | public static List sort( 344 | Collection collection) 345 | { 346 | Object[] a = collection.toArray(new Object[collection.size()]); 347 | Arrays.sort(a); 348 | return cast(Arrays.asList(a)); 349 | } 350 | 351 | /** 352 | * Sorts a collection of objects using a {@link java.util.Comparator} and 353 | * returns a list. 354 | * 355 | * @param collection Collection 356 | * @param comparator Comparator 357 | * @param Element type 358 | * @return Sorted list 359 | */ 360 | public static List sort( 361 | Collection collection, 362 | Comparator comparator) 363 | { 364 | Object[] a = collection.toArray(new Object[collection.size()]); 365 | //noinspection unchecked 366 | Arrays.sort(a, (Comparator) comparator); 367 | return cast(Arrays.asList(a)); 368 | } 369 | 370 | /** 371 | * Creates an internal error with a given message. 372 | */ 373 | public static RuntimeException newInternal(String message) { 374 | return new RuntimeException("Internal error: " + message); 375 | } 376 | 377 | /** 378 | * Creates an internal error with a given message and cause. 379 | */ 380 | public static RuntimeException newInternal(Throwable e, String message) { 381 | return new RuntimeException("Internal error: " + message, e); 382 | } 383 | 384 | /** 385 | * Creates a non-internal error. Currently implemented in terms of 386 | * internal errors, but later we will create resourced messages. 387 | */ 388 | public static RuntimeException newError(String message) { 389 | return newInternal(message); 390 | } 391 | 392 | /** 393 | * Creates a non-internal error. Currently implemented in terms of 394 | * internal errors, but later we will create resourced messages. 395 | */ 396 | public static RuntimeException newError(Throwable e, String message) { 397 | return newInternal(e, message); 398 | } 399 | 400 | /** 401 | * Converts a {@link Properties} object to a string-to-string {@link Map}. 402 | * 403 | * @param properties Properties 404 | * @return String-to-string map 405 | */ 406 | public static Map toMap(final Properties properties) { 407 | return new AbstractMap() { 408 | @SuppressWarnings({"unchecked"}) 409 | public Set> entrySet() { 410 | return (Set) properties.entrySet(); 411 | } 412 | }; 413 | } 414 | 415 | public static String printMemory() { 416 | return printMemory(null); 417 | } 418 | 419 | public static String printMemory(String msg) { 420 | final Runtime rt = Runtime.getRuntime(); 421 | final long freeMemory = rt.freeMemory(); 422 | final long totalMemory = rt.totalMemory(); 423 | final StringBuilder buf = new StringBuilder(64); 424 | 425 | buf.append("FREE_MEMORY:"); 426 | if (msg != null) { 427 | buf.append(msg); 428 | buf.append(':'); 429 | } 430 | buf.append(' '); 431 | buf.append(freeMemory / 1024); 432 | buf.append("kb "); 433 | 434 | long hundredths = (freeMemory * 10000) / totalMemory; 435 | 436 | buf.append(hundredths / 100); 437 | hundredths %= 100; 438 | if (hundredths >= 10) { 439 | buf.append('.'); 440 | } else { 441 | buf.append(".0"); 442 | } 443 | buf.append(hundredths); 444 | buf.append('%'); 445 | 446 | return buf.toString(); 447 | } 448 | 449 | /** 450 | * Casts a List to a List with a different element type. 451 | * 452 | * @param list List 453 | * @return List of desired type 454 | */ 455 | @SuppressWarnings({"unchecked"}) 456 | public static List cast(List list) { 457 | return (List) list; 458 | } 459 | 460 | /** 461 | * Looks up an enumeration by name, returning null if null or not valid. 462 | * 463 | * @param clazz Enumerated type 464 | * @param name Name of constant 465 | */ 466 | public static > E lookup(Class clazz, String name) { 467 | return lookup(clazz, name, null); 468 | } 469 | 470 | /** 471 | * Looks up an enumeration by name, returning a given default value if null 472 | * or not valid. 473 | * 474 | * @param clazz Enumerated type 475 | * @param name Name of constant 476 | * @param defaultValue Default value if constant is not found 477 | * @return Value, or null if name is null or value does not exist 478 | */ 479 | public static > E lookup( 480 | Class clazz, 481 | String name, 482 | E defaultValue) 483 | { 484 | if (name == null) { 485 | return defaultValue; 486 | } 487 | try { 488 | return Enum.valueOf(clazz, name); 489 | } catch (IllegalArgumentException e) { 490 | return defaultValue; 491 | } 492 | } 493 | 494 | /** 495 | * Make a BigDecimal from a double. On JDK 1.5 or later, the BigDecimal 496 | * precision reflects the precision of the double while with JDK 1.4 497 | * this is not the case. 498 | * 499 | * @param d the input double 500 | * @return the BigDecimal 501 | */ 502 | public static BigDecimal makeBigDecimalFromDouble(double d) { 503 | return new BigDecimal(d, MathContext.DECIMAL64); 504 | } 505 | 506 | /** 507 | * Returns a literal pattern String for the specified String. 508 | * 509 | * @param s The string to be literalized 510 | * @return A literal string replacement 511 | */ 512 | public static String quotePattern(String s) { 513 | return Pattern.quote(s); 514 | } 515 | 516 | /** 517 | * Function that takes one argument ({@code PT}) and returns {@code RT}. 518 | * 519 | * @param Return type 520 | * @param Parameter type 521 | */ 522 | public static interface Function1 { 523 | RT apply(PT param); 524 | } 525 | 526 | /** 527 | * Predicate that takes one argument ({@code PT}). 528 | * Can be used as a {@code Function1<PT>} or as an Apache-collections 529 | * Predicate. 530 | * 531 | * @param Parameter type 532 | */ 533 | public static abstract class Predicate1 534 | implements Predicate, Function1 535 | { 536 | public Boolean apply(PT param) { 537 | return test(param); 538 | } 539 | 540 | public boolean evaluate(Object o) { 541 | //noinspection unchecked 542 | return test((PT) o); 543 | } 544 | 545 | public abstract boolean test(PT pt); 546 | } 547 | 548 | public static Function1 identityFunction() { 549 | //noinspection unchecked 550 | return (Function1) IDENTITY_FUNCTION; 551 | } 552 | 553 | private static final Function1 IDENTITY_FUNCTION = 554 | new Function1() { 555 | public Object apply(Object param) { 556 | return param; 557 | } 558 | }; 559 | 560 | /** 561 | * Returns a predicate that takes 1 argument and always returns true. 562 | * 563 | * @param Parameter type 564 | * @return Predicate that always returns true 565 | */ 566 | public static Predicate1 truePredicate1() { 567 | //noinspection unchecked 568 | return (Predicate1) TRUE_PREDICATE1; 569 | } 570 | 571 | private static final Predicate1 TRUE_PREDICATE1 = 572 | new Predicate1() { 573 | public boolean test(Object o) { 574 | return true; 575 | } 576 | }; 577 | } 578 | 579 | // End Util.java 580 | -------------------------------------------------------------------------------- /xmlaserver.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | --------------------------------------------------------------------------------