├── docker ├── no-auth │ ├── files │ │ ├── .gitignore │ │ └── catalina.properties │ └── Dockerfile └── bearer-auth │ ├── files │ ├── .gitignore │ ├── application.properties │ └── catalina.properties │ └── Dockerfile ├── .gitignore ├── src ├── main │ ├── resources │ │ ├── mondrian.properties │ │ ├── saml-assertion-request-authorizer.json │ │ ├── test.properties │ │ ├── bearer-token-request-authorizer.json │ │ ├── mondrian-connections.json │ │ ├── log4j.properties │ │ ├── test.script │ │ ├── logback.xml │ │ ├── application.properties │ │ └── test.xml │ ├── webapp │ │ └── query-ui │ │ │ ├── css │ │ │ └── QueryUI.css │ │ │ ├── js │ │ │ └── QueryUI.js │ │ │ └── QueryUI.html │ └── java │ │ └── org │ │ └── ojbc │ │ └── mondrian │ │ ├── CellSetWrapperType.java │ │ ├── rest │ │ ├── SamlTokenStrategy.java │ │ ├── DefaultSamlTokenStrategy.java │ │ ├── NameIdSamlTokenStrategy.java │ │ ├── Application.java │ │ ├── AttributeSamlTokenStrategy.java │ │ ├── QueryRequest.java │ │ ├── DefaultRequestAuthorizer.java │ │ ├── SamlAssertionRequestRoleAuthorizer.java │ │ ├── SamlUtils.java │ │ ├── QueryUIController.java │ │ ├── RequestAuthorizer.java │ │ ├── SamlAssertionRequestAuthorizer.java │ │ ├── BearerTokenRequestAuthorizer.java │ │ └── AbstractSamlAssertionRequestAuthorizer.java │ │ ├── MeasureGroupWrapper.java │ │ ├── PositionMemberWrapper.java │ │ ├── MeasureWrapper.java │ │ ├── AxisWrapper.java │ │ ├── CellWrapper.java │ │ ├── DimensionWrapper.java │ │ ├── HierarchyWrapper.java │ │ ├── PositionWrapper.java │ │ ├── CellSetWrapper.java │ │ ├── CubeWrapper.java │ │ ├── LevelWrapper.java │ │ ├── MondrianUtils.java │ │ ├── MemberWrapper.java │ │ ├── SchemaWrapper.java │ │ ├── TidyCellSetWrapper.java │ │ └── MondrianConnectionFactory.java └── test │ ├── resources │ ├── test-authorizer-util.json │ ├── test-mondrian-connections.json │ └── transient-name-id-assertion.xml │ └── java │ └── org │ └── ojbc │ └── mondrian │ ├── rest │ ├── AuthorizerUtilTest.java │ ├── TestRequestAuthorizer.java │ ├── PreCachedMetadataTest.java │ ├── DiskCachedMetadataTest.java │ ├── SamlUtilsTest.java │ ├── QueryTimeoutTest.java │ ├── AbstractMondrianRestControllerTest.java │ ├── DemoConnectionRemovalTest.java │ ├── BearerTokenRequestAuthorizerTest.java │ ├── SamlAssertionRequestAuthorizerTest.java │ ├── RequestAuthorizerIntegrationTest.java │ └── RequestAuthorizerTest.java │ ├── TestJsonSerialization.java │ ├── SchemaWrapperTest.java │ ├── util │ ├── BasicMondrianQueryTest.java │ ├── DatabaseUtils.java │ ├── TestDatabaseAvailabilityTest.java │ └── BasicOlap4jQueryTest.java │ ├── TestMondrianUtils.java │ ├── TestSchemaFactory.java │ ├── MondrianConnectionFactoryTest.java │ ├── TestTidyCellSet.java │ └── CellSetWrapperTest.java ├── NEWS.md └── pom.xml /docker/no-auth/files/.gitignore: -------------------------------------------------------------------------------- 1 | mondrian-rest-*.war 2 | 3 | -------------------------------------------------------------------------------- /docker/bearer-auth/files/.gitignore: -------------------------------------------------------------------------------- 1 | mondrian-rest-*.war 2 | 3 | -------------------------------------------------------------------------------- /docker/bearer-auth/files/application.properties: -------------------------------------------------------------------------------- 1 | requestAuthorizerBeanName=bearerTokenRequestAuthorizer 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **/.DS_Store 2 | /target/ 3 | .classpath 4 | .project 5 | .settings 6 | /WebContent/ 7 | mondrian-rest.war 8 | /extra-tomcat-classpath/ 9 | -------------------------------------------------------------------------------- /src/main/resources/mondrian.properties: -------------------------------------------------------------------------------- 1 | # no default jdbc drivers, avoids warning log messages 2 | mondrian.jdbcDrivers= 3 | 4 | mondrian.olap.case.sensitive=true 5 | -------------------------------------------------------------------------------- /src/main/resources/saml-assertion-request-authorizer.json: -------------------------------------------------------------------------------- 1 | { 2 | "STATE:AGENCY:IDP:user1" : { 3 | "test": "ROLE_ADMIN" 4 | }, 5 | "STATE:AGENCY:IDP:user2" : { 6 | "test": "ROLE_USER" 7 | } 8 | } -------------------------------------------------------------------------------- /src/test/resources/test-authorizer-util.json: -------------------------------------------------------------------------------- 1 | { 2 | "outerKey1" : { 3 | "innerKey1.1" : "1.1", 4 | "innerKey1.2" : "1.2" 5 | }, 6 | "outerKey2" : { 7 | "innerKey2.1" : "2.1", 8 | "innerKey2.2" : "2.2" 9 | } 10 | } -------------------------------------------------------------------------------- /src/test/resources/test-mondrian-connections.json: -------------------------------------------------------------------------------- 1 | { 2 | "test" : { 3 | "JdbcDriver" : "org.hsqldb.jdbc.JDBCDriver", 4 | "Jdbc" : "jdbc:hsqldb:res:test", 5 | "Catalog" : "/test.xml", 6 | "Description" : "Test version of test connection", 7 | "UnrecognizedProperty" : "foobar", 8 | "IsDemo" : true 9 | } 10 | } -------------------------------------------------------------------------------- /src/main/resources/test.properties: -------------------------------------------------------------------------------- 1 | #HSQL Database Engine 1.8.0.10 2 | #Tue Nov 05 16:53:53 PST 2013 3 | hsqldb.script_format=0 4 | runtime.gc_interval=0 5 | sql.enforce_strict_size=false 6 | hsqldb.cache_size_scale=8 7 | readonly=false 8 | hsqldb.nio_data_file=true 9 | hsqldb.cache_scale=14 10 | version=2.5.1 11 | hsqldb.default_table_type=memory 12 | hsqldb.cache_file_scale=1 13 | hsqldb.log_size=200 14 | modified=yes 15 | hsqldb.cache_version=2.5.1 16 | hsqldb.original_version=2.5.1 17 | hsqldb.compatible_version=2.5.1 -------------------------------------------------------------------------------- /src/main/resources/bearer-token-request-authorizer.json: -------------------------------------------------------------------------------- 1 | { 2 | "TOKEN1" : { 3 | "test": "org.ojbc.mondrian.rest.RequestAuthorizer-All-Access", 4 | "foodmart": "org.ojbc.mondrian.rest.RequestAuthorizer-All-Access" 5 | }, 6 | "TOKEN2" : { 7 | "test": "Restricted_User", 8 | "foodmart": "org.ojbc.mondrian.rest.RequestAuthorizer-All-Access" 9 | }, 10 | "CA_MANAGER_TOKEN" : { 11 | "test": "org.ojbc.mondrian.rest.RequestAuthorizer-All-Access", 12 | "foodmart": "California manager" 13 | }, 14 | "NO_HR_CUBE_TOKEN" : { 15 | "test": "org.ojbc.mondrian.rest.RequestAuthorizer-All-Access", 16 | "foodmart": "No HR Cube" 17 | } 18 | } -------------------------------------------------------------------------------- /src/main/resources/mondrian-connections.json: -------------------------------------------------------------------------------- 1 | { 2 | "test" : { 3 | "JdbcDriver" : "org.hsqldb.jdbc.JDBCDriver", 4 | "Jdbc" : "jdbc:hsqldb:res:test", 5 | "Catalog" : "/test.xml", 6 | "Description" : "Main version of test connection", 7 | "UnrecognizedProperty" : "foobar", 8 | "IsDemo" : true 9 | }, 10 | "foodmart" : { 11 | "JdbcDriver" : "org.hsqldb.jdbc.JDBCDriver", 12 | "Jdbc" : "jdbc:hsqldb:res:foodmart;set schema \"foodmart\"", 13 | "JdbcUser" : "FOODMART", 14 | "JdbcPassword" : "FOODMART", 15 | "Catalog" : "https://raw.githubusercontent.com/pentaho/mondrian/master/demo/FoodMart.xml", 16 | "Description" : "Pentaho/Hyde FoodMart Database", 17 | "IsDemo" : true 18 | } 19 | } -------------------------------------------------------------------------------- /src/test/java/org/ojbc/mondrian/rest/AuthorizerUtilTest.java: -------------------------------------------------------------------------------- 1 | package org.ojbc.mondrian.rest; 2 | 3 | import static org.junit.jupiter.api.Assertions.*; 4 | 5 | import java.util.Map; 6 | 7 | import org.junit.jupiter.api.Test; 8 | 9 | public class AuthorizerUtilTest { 10 | 11 | @Test 12 | public void testJsonParsing() throws Exception { 13 | Map> map = RequestAuthorizer.AuthorizerUtil.convertRoleConnectionJsonToMaps("test-authorizer-util.json"); 14 | assertEquals(2, map.size()); 15 | assertTrue(map.containsKey("outerKey1")); 16 | Map innerMap = map.get("outerKey1"); 17 | assertTrue(innerMap.containsKey("innerKey1.1")); 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/main/resources/log4j.properties: -------------------------------------------------------------------------------- 1 | # this file is needed because for some reason, Mondrian (somewhere) hardcodes a dependency on log4j that logback doesn't pick up 2 | 3 | # if you don't have a log4j.properties on the classpath, you'll get an error message from log4j about a missing appender for mondrian.olap.Properties, 4 | # and you won't get any log messages generated by Mondrian 5 | 6 | # should look into this at some point, but for now, this will enable Mondrian logging 7 | 8 | log4j.rootLogger=INFO, out 9 | 10 | log4j.appender.out=org.apache.log4j.ConsoleAppender 11 | log4j.appender.out.layout=org.apache.log4j.PatternLayout 12 | log4j.appender.out.layout.ConversionPattern=%d{YYYY-MM-dd HH:mm:ss.SSS} [M ] [%10.10t] %-5p [%36.36c]: %m%n 13 | 14 | # uncomment the following line (or add such a line to a configured spring properties file) to enable logging of the sql executed by mondrian 15 | #log4j.category.mondrian.sql=DEBUG 16 | -------------------------------------------------------------------------------- /NEWS.md: -------------------------------------------------------------------------------- 1 | ## mondrian-rest news / release notes 2 | 3 | #### Version 2.0.3 4 | 5 | 13 February 2020 6 | 7 | * Improved and more consistent logging 8 | * Allow pre-caching of metadata per connection 9 | 10 | #### Version 2.0.2 11 | 12 | 8 February 2020 13 | 14 | * Fix metadata cache to cache metadata objects by connection + role 15 | * Add a disk cache for metadata objects 16 | * Control cache parameters (for metadata and query caches) via spring properties 17 | 18 | #### Version 2.0.1 19 | 20 | 23 January 2020 21 | 22 | * Ensure `SchemaWrapper.getCubes()` returns cubes in order specified in mondrian schema xml 23 | 24 | #### Version 2.0.0 25 | 26 | 21 January 2020 27 | 28 | * Migrate to mondrian 8.x 29 | 30 | #### Version 1.5.0 31 | 32 | * Add saml token strategies and an impl that gets token from saml subject nameid 33 | * Last version of the controller that supports Mondrian 4.x schemas 34 | 35 | #### Versions prior to 1.5.0 36 | 37 | _we will document prior versions as necessary; see `git log` for details_ 38 | -------------------------------------------------------------------------------- /src/main/webapp/query-ui/css/QueryUI.css: -------------------------------------------------------------------------------- 1 | .app-container { 2 | margin: 15px; 3 | } 4 | 5 | .btn { 6 | margin-top: 10px; 7 | } 8 | 9 | #results { 10 | background-color: #e5e5e5; 11 | padding: 20px; 12 | white-space: pre; 13 | font-family: monospace; 14 | } 15 | 16 | #connectionDropdownButton { 17 | margin-bottom: 10px; 18 | } 19 | 20 | #currentConnectionLabel { 21 | padding-left: 10px; 22 | } 23 | 24 | #wait-spinner { 25 | display: none; 26 | position: absolute; 27 | left: 50%; 28 | top: 50%; 29 | z-index: 1; 30 | width: 150px; 31 | height: 150px; 32 | margin: -75px 0 0 -75px; 33 | border: 16px solid #f3f3f3; 34 | border-radius: 50%; 35 | border-top: 16px solid #3498db; 36 | width: 120px; 37 | height: 120px; 38 | -webkit-animation: spin 2s linear infinite; 39 | animation: spin 2s linear infinite; 40 | } 41 | 42 | @-webkit-keyframes spin { 43 | 0% { -webkit-transform: rotate(0deg); } 44 | 100% { -webkit-transform: rotate(360deg); } 45 | } 46 | 47 | @keyframes spin { 48 | 0% { transform: rotate(0deg); } 49 | 100% { transform: rotate(360deg); } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/resources/test.script: -------------------------------------------------------------------------------- 1 | CREATE USER SA PASSWORD "" 2 | GRANT DBA TO SA 3 | CREATE SCHEMA "test" AUTHORIZATION DBA 4 | ALTER USER SA SET INITIAL SCHEMA "test" 5 | SET WRITE_DELAY 10 6 | SET SCHEMA "test" 7 | create table F1 (F1_id INT NOT NULL, D1_id INT NOT NULL) 8 | create table D1(D1_id INT NOT NULL,D1_description VARCHAR(10) NOT NULL) 9 | create table F2( F2_id INT NOT NULL, D2_id INT NOT NULL) 10 | create table D2( D2_id INT NOT NULL, D2_description VARCHAR(10) NOT NULL, D2_rollup VARCHAR(10) NOT NULL) 11 | create table F3( F3_id INT NOT NULL, D1_id INT NOT NULL, D2_id INT NOT NULL, F3_value NUMERIC) 12 | insert into F1 values (1,1) 13 | insert into F1 values (2,1) 14 | insert into F1 values (3,2) 15 | insert into D1 values (1,'D1 One') 16 | insert into D1 values (2,'D1 Two') 17 | insert into F2 values (1,1) 18 | insert into F2 values (2,2) 19 | insert into F2 values (3,3) 20 | insert into D2 values (1,'D2 One', 'D2 A') 21 | insert into D2 values (2,'D2 Two', 'D2 A') 22 | insert into D2 values (3,'D2 Three', 'D2 B') 23 | insert into F3 values (1,1,1,10) 24 | insert into F3 values (2,1,2,20) 25 | insert into F3 values (3,2,3,35) -------------------------------------------------------------------------------- /src/main/java/org/ojbc/mondrian/CellSetWrapperType.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Unless explicitly acquired and licensed from Licensor under another license, the contents of 3 | * this file are subject to the Reciprocal Public License ("RPL") Version 1.5, or subsequent 4 | * versions as allowed by the RPL, and You may not copy or use this file in either source code 5 | * or executable form, except in compliance with the terms and conditions of the RPL 6 | * 7 | * All software distributed under the RPL is provided strictly on an "AS IS" basis, WITHOUT 8 | * WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, AND LICENSOR HEREBY DISCLAIMS ALL SUCH 9 | * WARRANTIES, INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 10 | * PARTICULAR PURPOSE, QUIET ENJOYMENT, OR NON-INFRINGEMENT. See the RPL for specific language 11 | * governing rights and limitations under the RPL. 12 | * 13 | * http://opensource.org/licenses/RPL-1.5 14 | * 15 | * Copyright 2012-2017 Open Justice Broker Consortium 16 | */ 17 | package org.ojbc.mondrian; 18 | 19 | /** 20 | * Marker interface for cell set wrappers that can be cached. 21 | * 22 | */ 23 | public interface CellSetWrapperType { 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/org/ojbc/mondrian/rest/SamlTokenStrategy.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Unless explicitly acquired and licensed from Licensor under another license, the contents of 3 | * this file are subject to the Reciprocal Public License ("RPL") Version 1.5, or subsequent 4 | * versions as allowed by the RPL, and You may not copy or use this file in either source code 5 | * or executable form, except in compliance with the terms and conditions of the RPL 6 | * 7 | * All software distributed under the RPL is provided strictly on an "AS IS" basis, WITHOUT 8 | * WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, AND LICENSOR HEREBY DISCLAIMS ALL SUCH 9 | * WARRANTIES, INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 10 | * PARTICULAR PURPOSE, QUIET ENJOYMENT, OR NON-INFRINGEMENT. See the RPL for specific language 11 | * governing rights and limitations under the RPL. 12 | * 13 | * http://opensource.org/licenses/RPL-1.5 14 | * 15 | * Copyright 2012-2020 Open Justice Broker Consortium 16 | */ 17 | package org.ojbc.mondrian.rest; 18 | 19 | import org.w3c.dom.Document; 20 | 21 | public interface SamlTokenStrategy { 22 | 23 | public String getToken(Document assertion); 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/test/java/org/ojbc/mondrian/rest/TestRequestAuthorizer.java: -------------------------------------------------------------------------------- 1 | package org.ojbc.mondrian.rest; 2 | 3 | import javax.servlet.http.HttpServletRequest; 4 | 5 | import org.springframework.stereotype.Component; 6 | 7 | @Component("requestAuthorizerTestAuthorizer") 8 | public class TestRequestAuthorizer implements RequestAuthorizer { 9 | 10 | static final String TEST_REQUEST_AUTHORIZER_USER_HEADER_NAME = "TestRequestAuthorizer-user-header"; 11 | static final String RESTRICTED_USER_HEADER_VALUE = "restricted"; 12 | static final String UNLIMITED_USER_HEADER_VALUE = "unlimited"; 13 | 14 | @Override 15 | public RequestAuthorizationStatus authorizeRequest(HttpServletRequest request, String connectionName) { 16 | 17 | RequestAuthorizationStatus ret = new RequestAuthorizationStatus(); 18 | ret.authorized = false; 19 | 20 | String user = request.getHeader(TEST_REQUEST_AUTHORIZER_USER_HEADER_NAME); 21 | 22 | if (user != null) { 23 | if (RESTRICTED_USER_HEADER_VALUE.equals(user)) { 24 | ret.authorized = true; 25 | ret.mondrianRole = "Restricted_User"; 26 | } else if (UNLIMITED_USER_HEADER_VALUE.equals(user)) { 27 | ret.authorized = true; 28 | } 29 | } 30 | 31 | return ret; 32 | 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/org/ojbc/mondrian/rest/DefaultSamlTokenStrategy.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Unless explicitly acquired and licensed from Licensor under another license, the contents of 3 | * this file are subject to the Reciprocal Public License ("RPL") Version 1.5, or subsequent 4 | * versions as allowed by the RPL, and You may not copy or use this file in either source code 5 | * or executable form, except in compliance with the terms and conditions of the RPL 6 | * 7 | * All software distributed under the RPL is provided strictly on an "AS IS" basis, WITHOUT 8 | * WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, AND LICENSOR HEREBY DISCLAIMS ALL SUCH 9 | * WARRANTIES, INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 10 | * PARTICULAR PURPOSE, QUIET ENJOYMENT, OR NON-INFRINGEMENT. See the RPL for specific language 11 | * governing rights and limitations under the RPL. 12 | * 13 | * http://opensource.org/licenses/RPL-1.5 14 | * 15 | * Copyright 2012-2020 Open Justice Broker Consortium 16 | */ 17 | package org.ojbc.mondrian.rest; 18 | 19 | import org.springframework.stereotype.Component; 20 | import org.w3c.dom.Document; 21 | 22 | @Component 23 | public class DefaultSamlTokenStrategy implements SamlTokenStrategy { 24 | 25 | @Override 26 | public String getToken(Document assertion) { 27 | return null; 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/org/ojbc/mondrian/rest/NameIdSamlTokenStrategy.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Unless explicitly acquired and licensed from Licensor under another license, the contents of 3 | * this file are subject to the Reciprocal Public License ("RPL") Version 1.5, or subsequent 4 | * versions as allowed by the RPL, and You may not copy or use this file in either source code 5 | * or executable form, except in compliance with the terms and conditions of the RPL 6 | * 7 | * All software distributed under the RPL is provided strictly on an "AS IS" basis, WITHOUT 8 | * WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, AND LICENSOR HEREBY DISCLAIMS ALL SUCH 9 | * WARRANTIES, INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 10 | * PARTICULAR PURPOSE, QUIET ENJOYMENT, OR NON-INFRINGEMENT. See the RPL for specific language 11 | * governing rights and limitations under the RPL. 12 | * 13 | * http://opensource.org/licenses/RPL-1.5 14 | * 15 | * Copyright 2012-2020 Open Justice Broker Consortium 16 | */ 17 | package org.ojbc.mondrian.rest; 18 | 19 | import org.springframework.stereotype.Component; 20 | import org.w3c.dom.Document; 21 | 22 | @Component 23 | public class NameIdSamlTokenStrategy implements SamlTokenStrategy { 24 | 25 | @Override 26 | public String getToken(Document assertion) { 27 | return SamlUtils.getNameIdValue(assertion); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /docker/no-auth/Dockerfile: -------------------------------------------------------------------------------- 1 | # 2 | # Unless explicitly acquired and licensed from Licensor under another license, the contents of 3 | # this file are subject to the Reciprocal Public License ("RPL") Version 1.5, or subsequent 4 | # versions as allowed by the RPL, and You may not copy or use this file in either source code 5 | # or executable form, except in compliance with the terms and conditions of the RPL 6 | # 7 | # All software distributed under the RPL is provided strictly on an "AS IS" basis, WITHOUT 8 | # WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, AND LICENSOR HEREBY DISCLAIMS ALL SUCH 9 | # WARRANTIES, INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 10 | # PARTICULAR PURPOSE, QUIET ENJOYMENT, OR NON-INFRINGEMENT. See the RPL for specific language 11 | # governing rights and limitations under the RPL. 12 | # 13 | # http://opensource.org/licenses/RPL-1.5 14 | # 15 | # Copyright 2012-2017 Open Justice Broker Consortium 16 | # 17 | 18 | FROM tomcat:8.5 19 | 20 | # RUN cd /tmp && \ 21 | # curl -O http://central.maven.org/maven2/org/ojbc/mondrian-rest/1.3.5/mondrian-rest-1.3.5.war && \ 22 | # mv mondrian-rest-1.3.5.war /usr/local/tomcat/webapps/mondrian-rest.war 23 | 24 | COPY files/mondrian-rest.war /usr/local/tomcat/webapps/mondrian-rest.war 25 | 26 | RUN sed -i s/8080/80/g /usr/local/tomcat/conf/server.xml 27 | 28 | RUN mkdir -p /usr/local/tomcat/shared/config 29 | COPY files/catalina.properties /usr/local/tomcat/conf/ 30 | 31 | WORKDIR /usr/local/tomcat 32 | -------------------------------------------------------------------------------- /src/main/java/org/ojbc/mondrian/rest/Application.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Unless explicitly acquired and licensed from Licensor under another license, the contents of 3 | * this file are subject to the Reciprocal Public License ("RPL") Version 1.5, or subsequent 4 | * versions as allowed by the RPL, and You may not copy or use this file in either source code 5 | * or executable form, except in compliance with the terms and conditions of the RPL 6 | * 7 | * All software distributed under the RPL is provided strictly on an "AS IS" basis, WITHOUT 8 | * WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, AND LICENSOR HEREBY DISCLAIMS ALL SUCH 9 | * WARRANTIES, INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 10 | * PARTICULAR PURPOSE, QUIET ENJOYMENT, OR NON-INFRINGEMENT. See the RPL for specific language 11 | * governing rights and limitations under the RPL. 12 | * 13 | * http://opensource.org/licenses/RPL-1.5 14 | * 15 | * Copyright 2012-2017 Open Justice Broker Consortium 16 | */ 17 | package org.ojbc.mondrian.rest; 18 | 19 | import org.springframework.boot.SpringApplication; 20 | import org.springframework.boot.autoconfigure.SpringBootApplication; 21 | import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; 22 | 23 | @SpringBootApplication 24 | public class Application extends SpringBootServletInitializer { 25 | 26 | 27 | public static void main(String ... args) { 28 | SpringApplication.run(Application.class, args); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 19 | 20 | 21 | 22 | 23 | %d{YYYY-MM-dd HH:mm:ss.SSS} [MR] [%10.10t] %-5level [%36.36logger]: %msg%n 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /docker/bearer-auth/Dockerfile: -------------------------------------------------------------------------------- 1 | # 2 | # Unless explicitly acquired and licensed from Licensor under another license, the contents of 3 | # this file are subject to the Reciprocal Public License ("RPL") Version 1.5, or subsequent 4 | # versions as allowed by the RPL, and You may not copy or use this file in either source code 5 | # or executable form, except in compliance with the terms and conditions of the RPL 6 | # 7 | # All software distributed under the RPL is provided strictly on an "AS IS" basis, WITHOUT 8 | # WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, AND LICENSOR HEREBY DISCLAIMS ALL SUCH 9 | # WARRANTIES, INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 10 | # PARTICULAR PURPOSE, QUIET ENJOYMENT, OR NON-INFRINGEMENT. See the RPL for specific language 11 | # governing rights and limitations under the RPL. 12 | # 13 | # http://opensource.org/licenses/RPL-1.5 14 | # 15 | # Copyright 2012-2017 Open Justice Broker Consortium 16 | # 17 | 18 | FROM tomcat:8.5 19 | 20 | # RUN cd /tmp && \ 21 | # curl -O http://central.maven.org/maven2/org/ojbc/mondrian-rest/1.3.5/mondrian-rest-1.3.5.war && \ 22 | # mv mondrian-rest-1.3.5.war /usr/local/tomcat/webapps/mondrian-rest.war 23 | 24 | COPY files/mondrian-rest.war /usr/local/tomcat/webapps/mondrian-rest.war 25 | 26 | RUN sed -i s/8080/80/g /usr/local/tomcat/conf/server.xml 27 | 28 | RUN mkdir -p /usr/local/tomcat/shared/config 29 | 30 | COPY files/catalina.properties /usr/local/tomcat/conf/ 31 | COPY files/application.properties /usr/local/tomcat/shared/config/ 32 | 33 | WORKDIR /usr/local/tomcat 34 | -------------------------------------------------------------------------------- /src/test/java/org/ojbc/mondrian/rest/PreCachedMetadataTest.java: -------------------------------------------------------------------------------- 1 | package org.ojbc.mondrian.rest; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | import static org.junit.jupiter.api.Assertions.assertNotNull; 5 | 6 | import java.net.URI; 7 | import java.util.List; 8 | 9 | import org.junit.jupiter.api.Test; 10 | import org.ojbc.mondrian.SchemaWrapper; 11 | import org.springframework.boot.test.context.SpringBootTest; 12 | import org.springframework.boot.web.server.LocalServerPort; 13 | import org.springframework.http.ResponseEntity; 14 | import org.springframework.test.annotation.DirtiesContext; 15 | import org.springframework.test.context.TestPropertySource; 16 | 17 | @TestPropertySource(properties = { "preCacheMetadata=true" }) 18 | @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) 19 | @DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD) 20 | public class PreCachedMetadataTest extends AbstractMondrianRestControllerTest { 21 | 22 | @LocalServerPort 23 | private String port; 24 | 25 | @Test 26 | public void testCachedMetadata() throws Exception { 27 | ResponseEntity response = restTemplate.getForEntity(new URI("http://localhost:" + port + "/getMetadata?connectionName=test"), SchemaWrapper.class); 28 | List responseHeaders = response.getHeaders().get("mondrian-rest-cached-result"); 29 | assertNotNull(responseHeaders); 30 | responseHeaders = response.getHeaders().get("mondrian-rest-cached-result"); 31 | assertEquals(1, responseHeaders.size()); 32 | assertEquals("true", responseHeaders.get(0)); 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/test/java/org/ojbc/mondrian/rest/DiskCachedMetadataTest.java: -------------------------------------------------------------------------------- 1 | package org.ojbc.mondrian.rest; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | import static org.junit.jupiter.api.Assertions.assertNotNull; 5 | 6 | import java.net.URI; 7 | import java.util.List; 8 | 9 | import org.junit.jupiter.api.Test; 10 | import org.ojbc.mondrian.SchemaWrapper; 11 | import org.springframework.boot.test.context.SpringBootTest; 12 | import org.springframework.boot.web.server.LocalServerPort; 13 | import org.springframework.http.ResponseEntity; 14 | import org.springframework.test.annotation.DirtiesContext; 15 | import org.springframework.test.context.TestPropertySource; 16 | 17 | @TestPropertySource(properties = { "preCacheMetadata=true", "metadataCacheHeapTierEntries=1" }) 18 | @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) 19 | @DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD) 20 | public class DiskCachedMetadataTest extends AbstractMondrianRestControllerTest { 21 | 22 | @LocalServerPort 23 | private String port; 24 | 25 | @Test 26 | public void testCachedMetadata() throws Exception { 27 | ResponseEntity response = restTemplate.getForEntity(new URI("http://localhost:" + port + "/getMetadata?connectionName=test"), SchemaWrapper.class); 28 | List responseHeaders = response.getHeaders().get("mondrian-rest-cached-result"); 29 | assertNotNull(responseHeaders); 30 | responseHeaders = response.getHeaders().get("mondrian-rest-cached-result"); 31 | assertEquals(1, responseHeaders.size()); 32 | assertEquals("true", responseHeaders.get(0)); 33 | } 34 | 35 | } -------------------------------------------------------------------------------- /src/main/java/org/ojbc/mondrian/rest/AttributeSamlTokenStrategy.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Unless explicitly acquired and licensed from Licensor under another license, the contents of 3 | * this file are subject to the Reciprocal Public License ("RPL") Version 1.5, or subsequent 4 | * versions as allowed by the RPL, and You may not copy or use this file in either source code 5 | * or executable form, except in compliance with the terms and conditions of the RPL 6 | * 7 | * All software distributed under the RPL is provided strictly on an "AS IS" basis, WITHOUT 8 | * WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, AND LICENSOR HEREBY DISCLAIMS ALL SUCH 9 | * WARRANTIES, INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 10 | * PARTICULAR PURPOSE, QUIET ENJOYMENT, OR NON-INFRINGEMENT. See the RPL for specific language 11 | * governing rights and limitations under the RPL. 12 | * 13 | * http://opensource.org/licenses/RPL-1.5 14 | * 15 | * Copyright 2012-2020 Open Justice Broker Consortium 16 | */ 17 | package org.ojbc.mondrian.rest; 18 | 19 | import org.springframework.beans.factory.annotation.Value; 20 | import org.springframework.stereotype.Component; 21 | import org.w3c.dom.Document; 22 | 23 | @Component 24 | public class AttributeSamlTokenStrategy implements SamlTokenStrategy { 25 | 26 | @Value("${samlAssertionTokenAttributeName:null}") 27 | protected String tokenAttributeName; 28 | 29 | public String getTokenAttributeName() { 30 | return tokenAttributeName; 31 | } 32 | 33 | public void setTokenAttributeName(String tokenAttributeName) { 34 | this.tokenAttributeName = tokenAttributeName; 35 | } 36 | 37 | @Override 38 | public String getToken(Document assertion) { 39 | return SamlUtils.getAssertionAttributeValue(assertion, tokenAttributeName); 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/org/ojbc/mondrian/MeasureGroupWrapper.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Unless explicitly acquired and licensed from Licensor under another license, the contents of 3 | * this file are subject to the Reciprocal Public License ("RPL") Version 1.5, or subsequent 4 | * versions as allowed by the RPL, and You may not copy or use this file in either source code 5 | * or executable form, except in compliance with the terms and conditions of the RPL 6 | * 7 | * All software distributed under the RPL is provided strictly on an "AS IS" basis, WITHOUT 8 | * WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, AND LICENSOR HEREBY DISCLAIMS ALL SUCH 9 | * WARRANTIES, INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 10 | * PARTICULAR PURPOSE, QUIET ENJOYMENT, OR NON-INFRINGEMENT. See the RPL for specific language 11 | * governing rights and limitations under the RPL. 12 | * 13 | * http://opensource.org/licenses/RPL-1.5 14 | * 15 | * Copyright 2012-2017 Open Justice Broker Consortium 16 | */ 17 | package org.ojbc.mondrian; 18 | 19 | import java.util.ArrayList; 20 | import java.util.Collections; 21 | import java.util.List; 22 | 23 | import lombok.EqualsAndHashCode; 24 | import lombok.Getter; 25 | import lombok.ToString; 26 | 27 | @Getter 28 | @EqualsAndHashCode 29 | @ToString 30 | public class MeasureGroupWrapper { 31 | 32 | private String name; 33 | private List measureReferences; 34 | private List dimensionReferences; 35 | 36 | public MeasureGroupWrapper() { 37 | measureReferences = new ArrayList(); 38 | dimensionReferences = new ArrayList(); 39 | } 40 | 41 | public List getMeasureReferences() { 42 | return Collections.unmodifiableList(measureReferences); 43 | } 44 | 45 | public List getDimensionReferences() { 46 | return Collections.unmodifiableList(dimensionReferences); 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/org/ojbc/mondrian/PositionMemberWrapper.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Unless explicitly acquired and licensed from Licensor under another license, the contents of 3 | * this file are subject to the Reciprocal Public License ("RPL") Version 1.5, or subsequent 4 | * versions as allowed by the RPL, and You may not copy or use this file in either source code 5 | * or executable form, except in compliance with the terms and conditions of the RPL 6 | * 7 | * All software distributed under the RPL is provided strictly on an "AS IS" basis, WITHOUT 8 | * WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, AND LICENSOR HEREBY DISCLAIMS ALL SUCH 9 | * WARRANTIES, INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 10 | * PARTICULAR PURPOSE, QUIET ENJOYMENT, OR NON-INFRINGEMENT. See the RPL for specific language 11 | * governing rights and limitations under the RPL. 12 | * 13 | * http://opensource.org/licenses/RPL-1.5 14 | * 15 | * Copyright 2012-2017 Open Justice Broker Consortium 16 | */ 17 | package org.ojbc.mondrian; 18 | 19 | import org.olap4j.metadata.Member; 20 | 21 | import lombok.EqualsAndHashCode; 22 | import lombok.Getter; 23 | import lombok.ToString; 24 | 25 | @Getter 26 | @EqualsAndHashCode 27 | @ToString 28 | public class PositionMemberWrapper { 29 | 30 | private String memberLevelName; 31 | private String memberLevelCaption; 32 | private String memberValue; 33 | private PositionMemberWrapper parentMember; 34 | 35 | PositionMemberWrapper() {} 36 | 37 | PositionMemberWrapper(Member member) { 38 | this.memberLevelName = member.getLevel().getUniqueName(); 39 | this.memberLevelCaption = member.getLevel().getCaption(); 40 | this.memberValue = member.getName(); 41 | Member parentMember = member.getParentMember(); 42 | if (parentMember != null) { 43 | this.parentMember = new PositionMemberWrapper(parentMember); 44 | } 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/main/webapp/query-ui/js/QueryUI.js: -------------------------------------------------------------------------------- 1 | var availableConnections = null; 2 | var currentConnection = null; 3 | 4 | $().ready(function() { 5 | 6 | $.ajax({ 7 | "url" : "../getConnections", 8 | "dataType" : "json", 9 | "contentType": "application/json", 10 | "type" : "GET", 11 | "complete" : function(response) { 12 | let menu = $("#connectionDropdownMenu"); 13 | availableConnections = response.responseJSON; 14 | for (var conn in response.responseJSON) { 15 | menu.append(""); 16 | } 17 | $(".connection-dropdown-item").on("click", function(e) { 18 | currentConnection = e.target.dataset.conn; 19 | $("#currentConnectionLabel").text(availableConnections[currentConnection].Description); 20 | }); 21 | } 22 | }); 23 | 24 | $("#runQueryButton").on("click", function(e) { 25 | showWaitPane(); 26 | let request = new Object(); 27 | request.connectionName = currentConnection; 28 | request.query = $("#mdxTextArea").val(); 29 | request.tidy = new Object(); 30 | request.tidy.enabled = $("#tidyCheckbox")[0].checked; 31 | $.ajax({ 32 | "url" : "../query", 33 | "dataType" : "json", 34 | "contentType": "application/json", 35 | "type" : "POST", 36 | "data" : JSON.stringify(request), 37 | "complete" : function(response) { 38 | $("#results").text(JSON.stringify(response.responseJSON, null, 2)); 39 | hideWaitPane(); 40 | } 41 | }); 42 | }); 43 | 44 | }); 45 | 46 | showWaitPane = function() { 47 | let waitPaneDiv = $("#wait-spinner"); 48 | waitPaneDiv.show(); 49 | } 50 | 51 | hideWaitPane = function() { 52 | $("#wait-spinner").hide(); 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/org/ojbc/mondrian/MeasureWrapper.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Unless explicitly acquired and licensed from Licensor under another license, the contents of 3 | * this file are subject to the Reciprocal Public License ("RPL") Version 1.5, or subsequent 4 | * versions as allowed by the RPL, and You may not copy or use this file in either source code 5 | * or executable form, except in compliance with the terms and conditions of the RPL 6 | * 7 | * All software distributed under the RPL is provided strictly on an "AS IS" basis, WITHOUT 8 | * WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, AND LICENSOR HEREBY DISCLAIMS ALL SUCH 9 | * WARRANTIES, INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 10 | * PARTICULAR PURPOSE, QUIET ENJOYMENT, OR NON-INFRINGEMENT. See the RPL for specific language 11 | * governing rights and limitations under the RPL. 12 | * 13 | * http://opensource.org/licenses/RPL-1.5 14 | * 15 | * Copyright 2018 Open Justice Broker Consortium and Cascadia Analytics LLC 16 | */ 17 | package org.ojbc.mondrian; 18 | 19 | import java.io.Serializable; 20 | 21 | import org.olap4j.metadata.Measure; 22 | 23 | import lombok.EqualsAndHashCode; 24 | import lombok.Getter; 25 | import lombok.ToString; 26 | 27 | /** 28 | * A wrapper around olap4j Measure objects, suitable for serialization via json. 29 | * 30 | */ 31 | @Getter 32 | @EqualsAndHashCode 33 | @ToString 34 | public class MeasureWrapper implements Serializable { 35 | 36 | private static final long serialVersionUID = 8381804073662223438L; 37 | 38 | private String name; 39 | private String caption; 40 | private boolean visible; 41 | private boolean calculated; 42 | 43 | MeasureWrapper() { } 44 | 45 | public MeasureWrapper(Measure measure) { 46 | this.name = measure.getName(); 47 | this.caption = measure.getCaption(); 48 | this.visible = measure.isVisible(); 49 | this.calculated = measure.isCalculated(); 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /src/test/java/org/ojbc/mondrian/TestJsonSerialization.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Unless explicitly acquired and licensed from Licensor under another license, the contents of 3 | * this file are subject to the Reciprocal Public License ("RPL") Version 1.5, or subsequent 4 | * versions as allowed by the RPL, and You may not copy or use this file in either source code 5 | * or executable form, except in compliance with the terms and conditions of the RPL 6 | * 7 | * All software distributed under the RPL is provided strictly on an "AS IS" basis, WITHOUT 8 | * WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, AND LICENSOR HEREBY DISCLAIMS ALL SUCH 9 | * WARRANTIES, INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 10 | * PARTICULAR PURPOSE, QUIET ENJOYMENT, OR NON-INFRINGEMENT. See the RPL for specific language 11 | * governing rights and limitations under the RPL. 12 | * 13 | * http://opensource.org/licenses/RPL-1.5 14 | * 15 | * Copyright 2012-2017 Open Justice Broker Consortium 16 | */ 17 | package org.ojbc.mondrian; 18 | 19 | import static org.junit.jupiter.api.Assertions.assertEquals; 20 | 21 | import org.junit.jupiter.api.BeforeEach; 22 | import org.junit.jupiter.api.Test; 23 | import org.olap4j.CellSet; 24 | 25 | import com.fasterxml.jackson.databind.ObjectMapper; 26 | 27 | import lombok.extern.slf4j.Slf4j; 28 | 29 | @Slf4j 30 | public class TestJsonSerialization { 31 | 32 | @BeforeEach 33 | public void setUp() throws Exception { 34 | log.debug("setUp"); 35 | } 36 | 37 | @Test 38 | public void test() throws Exception { 39 | 40 | CellSet cellSet = TestCellSetFactory.getInstance().getDualAxisTwoDimensionCellSet(); 41 | CellSetWrapper w = new CellSetWrapper(cellSet); 42 | 43 | ObjectMapper mapper = new ObjectMapper(); 44 | 45 | String json = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(w); 46 | 47 | CellSetWrapper copy = mapper.readValue(json, CellSetWrapper.class); 48 | assertEquals(copy, w); 49 | 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/org/ojbc/mondrian/rest/QueryRequest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Unless explicitly acquired and licensed from Licensor under another license, the contents of 3 | * this file are subject to the Reciprocal Public License ("RPL") Version 1.5, or subsequent 4 | * versions as allowed by the RPL, and You may not copy or use this file in either source code 5 | * or executable form, except in compliance with the terms and conditions of the RPL 6 | * 7 | * All software distributed under the RPL is provided strictly on an "AS IS" basis, WITHOUT 8 | * WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, AND LICENSOR HEREBY DISCLAIMS ALL SUCH 9 | * WARRANTIES, INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 10 | * PARTICULAR PURPOSE, QUIET ENJOYMENT, OR NON-INFRINGEMENT. See the RPL for specific language 11 | * governing rights and limitations under the RPL. 12 | * 13 | * http://opensource.org/licenses/RPL-1.5 14 | * 15 | * Copyright 2012-2017 Open Justice Broker Consortium 16 | */ 17 | package org.ojbc.mondrian.rest; 18 | 19 | import java.util.Collections; 20 | import java.util.Map; 21 | 22 | import lombok.Setter; 23 | import lombok.Value; 24 | import lombok.experimental.NonFinal; 25 | 26 | /** 27 | * Simple DAO bean representing an MDX query to be submitted to a named connection. 28 | * 29 | */ 30 | @Value 31 | @NonFinal 32 | public class QueryRequest { 33 | 34 | @Value 35 | public static final class TidyConfig { 36 | 37 | private boolean enabled; 38 | private boolean simplifyNames; 39 | private Map levelNameTranslationMap; 40 | 41 | public Map getLevelNameTranslationMap() { 42 | return levelNameTranslationMap == null ? null : Collections.unmodifiableMap(levelNameTranslationMap); 43 | } 44 | 45 | } 46 | 47 | private String connectionName; 48 | private String query; 49 | private TidyConfig tidy; 50 | 51 | @NonFinal @Setter String mondrianRole; 52 | 53 | public int getCacheKey() { 54 | return hashCode(); 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/test/java/org/ojbc/mondrian/SchemaWrapperTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Unless explicitly acquired and licensed from Licensor under another license, the contents of 3 | * this file are subject to the Reciprocal Public License ("RPL") Version 1.5, or subsequent 4 | * versions as allowed by the RPL, and You may not copy or use this file in either source code 5 | * or executable form, except in compliance with the terms and conditions of the RPL 6 | * 7 | * All software distributed under the RPL is provided strictly on an "AS IS" basis, WITHOUT 8 | * WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, AND LICENSOR HEREBY DISCLAIMS ALL SUCH 9 | * WARRANTIES, INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 10 | * PARTICULAR PURPOSE, QUIET ENJOYMENT, OR NON-INFRINGEMENT. See the RPL for specific language 11 | * governing rights and limitations under the RPL. 12 | * 13 | * http://opensource.org/licenses/RPL-1.5 14 | * 15 | * Copyright 2018 Open Justice Broker Consortium and Cascadia Analytics LLC 16 | */ 17 | package org.ojbc.mondrian; 18 | 19 | import static org.junit.jupiter.api.Assertions.assertEquals; 20 | 21 | import java.util.List; 22 | 23 | import org.junit.jupiter.api.Test; 24 | import org.olap4j.metadata.Cube; 25 | import org.olap4j.metadata.Schema; 26 | 27 | public class SchemaWrapperTest { 28 | 29 | @Test 30 | public void test() throws Exception { 31 | Schema schema = TestSchemaFactory.getInstance().getSchema(); 32 | SchemaWrapper schemaWrapper = new SchemaWrapper(schema, "foo", null); 33 | assertEquals(schema.getName(), schemaWrapper.getName()); 34 | assertEquals("foo", schemaWrapper.getConnectionName()); 35 | List cubes = schema.getCubes(); 36 | List cubeWrappers = schemaWrapper.getCubes(); 37 | assertEquals(cubes.size(), cubeWrappers.size()); 38 | for (int i=0;i < cubes.size();i++) { 39 | assertEquals(cubes.get(i).getName(), cubeWrappers.get(i).getName()); 40 | assertEquals(cubes.get(i).getCaption(), cubeWrappers.get(i).getCaption()); 41 | } 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/test/java/org/ojbc/mondrian/rest/SamlUtilsTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Unless explicitly acquired and licensed from Licensor under another license, the contents of 3 | * this file are subject to the Reciprocal Public License ("RPL") Version 1.5, or subsequent 4 | * versions as allowed by the RPL, and You may not copy or use this file in either source code 5 | * or executable form, except in compliance with the terms and conditions of the RPL 6 | * 7 | * All software distributed under the RPL is provided strictly on an "AS IS" basis, WITHOUT 8 | * WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, AND LICENSOR HEREBY DISCLAIMS ALL SUCH 9 | * WARRANTIES, INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 10 | * PARTICULAR PURPOSE, QUIET ENJOYMENT, OR NON-INFRINGEMENT. See the RPL for specific language 11 | * governing rights and limitations under the RPL. 12 | * 13 | * http://opensource.org/licenses/RPL-1.5 14 | * 15 | * Copyright 2012-2020 Open Justice Broker Consortium 16 | */ 17 | package org.ojbc.mondrian.rest; 18 | 19 | import static org.junit.jupiter.api.Assertions.assertEquals; 20 | 21 | import java.io.InputStream; 22 | 23 | import javax.xml.parsers.DocumentBuilderFactory; 24 | 25 | import org.junit.jupiter.api.BeforeEach; 26 | import org.junit.jupiter.api.Test; 27 | import org.springframework.core.io.ClassPathResource; 28 | import org.w3c.dom.Document; 29 | 30 | public class SamlUtilsTest { 31 | 32 | private Document assertionDocument; 33 | 34 | @BeforeEach 35 | public void beforeAll() throws Exception { 36 | InputStream assertionStream = new ClassPathResource("transient-name-id-assertion.xml").getInputStream(); 37 | DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); 38 | dbf.setNamespaceAware(true); 39 | assertionDocument = dbf.newDocumentBuilder().parse(assertionStream); 40 | } 41 | 42 | @Test 43 | public void testAttribute() { 44 | assertEquals("Adam", SamlUtils.getAssertionAttributeValue(assertionDocument, "gfipm:2.0:user:GivenName")); 45 | } 46 | 47 | @Test 48 | public void testNameId() { 49 | assertEquals("TransientNameId", SamlUtils.getNameIdValue(assertionDocument)); 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/org/ojbc/mondrian/AxisWrapper.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Unless explicitly acquired and licensed from Licensor under another license, the contents of 3 | * this file are subject to the Reciprocal Public License ("RPL") Version 1.5, or subsequent 4 | * versions as allowed by the RPL, and You may not copy or use this file in either source code 5 | * or executable form, except in compliance with the terms and conditions of the RPL 6 | * 7 | * All software distributed under the RPL is provided strictly on an "AS IS" basis, WITHOUT 8 | * WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, AND LICENSOR HEREBY DISCLAIMS ALL SUCH 9 | * WARRANTIES, INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 10 | * PARTICULAR PURPOSE, QUIET ENJOYMENT, OR NON-INFRINGEMENT. See the RPL for specific language 11 | * governing rights and limitations under the RPL. 12 | * 13 | * http://opensource.org/licenses/RPL-1.5 14 | * 15 | * Copyright 2012-2017 Open Justice Broker Consortium 16 | */ 17 | package org.ojbc.mondrian; 18 | 19 | import java.util.ArrayList; 20 | import java.util.Collections; 21 | import java.util.List; 22 | import java.util.stream.Collectors; 23 | 24 | import org.olap4j.CellSetAxis; 25 | 26 | import com.fasterxml.jackson.annotation.JsonProperty; 27 | 28 | import lombok.EqualsAndHashCode; 29 | import lombok.Getter; 30 | import lombok.ToString; 31 | 32 | /** 33 | * A wrapper around Mondrian Axis objects, suitable for serialization via json. 34 | * 35 | */ 36 | @Getter 37 | @EqualsAndHashCode 38 | @ToString 39 | public class AxisWrapper { 40 | 41 | private List positionWrappers = new ArrayList<>(); 42 | 43 | private int ordinal; 44 | private String name; 45 | 46 | AxisWrapper() {} 47 | 48 | public AxisWrapper(CellSetAxis axis) { 49 | positionWrappers = axis.getPositions().stream().map(position -> new PositionWrapper(position)).collect(Collectors.toList()); 50 | ordinal = axis.getAxisOrdinal().axisOrdinal(); 51 | name = axis.getAxisOrdinal().name(); 52 | } 53 | 54 | @JsonProperty("positions") 55 | public List getPositionWrappers() { 56 | return Collections.unmodifiableList(positionWrappers); 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/org/ojbc/mondrian/CellWrapper.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Unless explicitly acquired and licensed from Licensor under another license, the contents of 3 | * this file are subject to the Reciprocal Public License ("RPL") Version 1.5, or subsequent 4 | * versions as allowed by the RPL, and You may not copy or use this file in either source code 5 | * or executable form, except in compliance with the terms and conditions of the RPL 6 | * 7 | * All software distributed under the RPL is provided strictly on an "AS IS" basis, WITHOUT 8 | * WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, AND LICENSOR HEREBY DISCLAIMS ALL SUCH 9 | * WARRANTIES, INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 10 | * PARTICULAR PURPOSE, QUIET ENJOYMENT, OR NON-INFRINGEMENT. See the RPL for specific language 11 | * governing rights and limitations under the RPL. 12 | * 13 | * http://opensource.org/licenses/RPL-1.5 14 | * 15 | * Copyright 2012-2017 Open Justice Broker Consortium 16 | */ 17 | package org.ojbc.mondrian; 18 | 19 | import java.util.Collections; 20 | import java.util.List; 21 | 22 | import org.olap4j.Cell; 23 | import org.olap4j.OlapException; 24 | 25 | import lombok.EqualsAndHashCode; 26 | import lombok.Getter; 27 | import lombok.ToString; 28 | 29 | /** 30 | * A wrapper around Mondrian Cell objects, suitable for serialization via json. 31 | * 32 | */ 33 | @Getter 34 | @EqualsAndHashCode 35 | @ToString 36 | public class CellWrapper { 37 | 38 | private String formattedValue; 39 | private Number value; 40 | private int ordinal; 41 | public List coordinates; 42 | public OlapException error; 43 | 44 | CellWrapper() { } 45 | 46 | public CellWrapper(Cell cell) { 47 | formattedValue = cell.getFormattedValue(); 48 | Object v = cell.getValue(); 49 | this.value = null; 50 | this.error = null; 51 | if (v instanceof Number) { 52 | value = (Number) v; 53 | } else if (v instanceof OlapException) { 54 | error = (OlapException) v; 55 | } 56 | ordinal = cell.getOrdinal(); 57 | coordinates = cell.getCoordinateList(); 58 | } 59 | 60 | public List getCoordinates() { 61 | return Collections.unmodifiableList(coordinates); 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/org/ojbc/mondrian/rest/DefaultRequestAuthorizer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Unless explicitly acquired and licensed from Licensor under another license, the contents of 3 | * this file are subject to the Reciprocal Public License ("RPL") Version 1.5, or subsequent 4 | * versions as allowed by the RPL, and You may not copy or use this file in either source code 5 | * or executable form, except in compliance with the terms and conditions of the RPL 6 | * 7 | * All software distributed under the RPL is provided strictly on an "AS IS" basis, WITHOUT 8 | * WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, AND LICENSOR HEREBY DISCLAIMS ALL SUCH 9 | * WARRANTIES, INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 10 | * PARTICULAR PURPOSE, QUIET ENJOYMENT, OR NON-INFRINGEMENT. See the RPL for specific language 11 | * governing rights and limitations under the RPL. 12 | * 13 | * http://opensource.org/licenses/RPL-1.5 14 | * 15 | * Copyright 2012-2017 Open Justice Broker Consortium 16 | */ 17 | package org.ojbc.mondrian.rest; 18 | 19 | import javax.annotation.PostConstruct; 20 | import javax.servlet.http.HttpServletRequest; 21 | 22 | import org.springframework.beans.factory.annotation.Value; 23 | import org.springframework.stereotype.Component; 24 | 25 | import lombok.extern.slf4j.Slf4j; 26 | 27 | /** 28 | * RequestAuthorizer that authorizes all requests and assigns all requests a pre-defined role, or a null role if none is configured. 29 | */ 30 | @Component 31 | @Slf4j 32 | public class DefaultRequestAuthorizer implements RequestAuthorizer { 33 | 34 | @Value("${defaultRequestAuthorizerRole:#{null}}") 35 | private String defaultRequestAuthorizerRole; 36 | 37 | @PostConstruct 38 | public void init() throws Exception { 39 | log.info("DefaultRequestAuthorizer will use role " + defaultRequestAuthorizerRole + " for connections"); 40 | } 41 | 42 | @Override 43 | public RequestAuthorizationStatus authorizeRequest(HttpServletRequest request, String connectionName) throws Exception { 44 | RequestAuthorizationStatus ret = new RequestAuthorizationStatus(); 45 | ret.authorized = true; 46 | ret.token = "[None]"; 47 | ret.mondrianRole = defaultRequestAuthorizerRole; 48 | return ret; 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/org/ojbc/mondrian/rest/SamlAssertionRequestRoleAuthorizer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Unless explicitly acquired and licensed from Licensor under another license, the contents of 3 | * this file are subject to the Reciprocal Public License ("RPL") Version 1.5, or subsequent 4 | * versions as allowed by the RPL, and You may not copy or use this file in either source code 5 | * or executable form, except in compliance with the terms and conditions of the RPL 6 | * 7 | * All software distributed under the RPL is provided strictly on an "AS IS" basis, WITHOUT 8 | * WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, AND LICENSOR HEREBY DISCLAIMS ALL SUCH 9 | * WARRANTIES, INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 10 | * PARTICULAR PURPOSE, QUIET ENJOYMENT, OR NON-INFRINGEMENT. See the RPL for specific language 11 | * governing rights and limitations under the RPL. 12 | * 13 | * http://opensource.org/licenses/RPL-1.5 14 | * 15 | * Copyright 2012-2020 Open Justice Broker Consortium 16 | */ 17 | package org.ojbc.mondrian.rest; 18 | 19 | import org.springframework.beans.factory.annotation.Value; 20 | import org.springframework.stereotype.Component; 21 | import org.w3c.dom.Document; 22 | 23 | import lombok.Getter; 24 | import lombok.extern.slf4j.Slf4j; 25 | 26 | @Component 27 | @Slf4j 28 | public class SamlAssertionRequestRoleAuthorizer extends AbstractSamlAssertionRequestAuthorizer { 29 | 30 | @Value("${samlAssertionRoleAttributeName:null}") 31 | @Getter 32 | private String roleAttributeName; 33 | 34 | @Override 35 | protected RequestAuthorizationStatus authorizeAssertion(String connectionName, Document assertion) { 36 | RequestAuthorizationStatus ret = new RequestAuthorizationStatus(); 37 | ret.authorized = false; 38 | String role = SamlUtils.getAssertionAttributeValue(assertion, roleAttributeName); 39 | if (role != null) { 40 | ret.authorized = true; 41 | ret.token = getToken(assertion); 42 | ret.mondrianRole = role; 43 | if (role.equals(ALL_ACCESS_ROLE_NAME)) { 44 | ret.mondrianRole = null; 45 | } 46 | log.info("Authorized token " + getToken(assertion) + " with role " + role); 47 | } else { 48 | ret.message = "No role attribute with name " + roleAttributeName + " found in assertion with token " + getToken(assertion); 49 | } 50 | return ret; 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/org/ojbc/mondrian/DimensionWrapper.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Unless explicitly acquired and licensed from Licensor under another license, the contents of 3 | * this file are subject to the Reciprocal Public License ("RPL") Version 1.5, or subsequent 4 | * versions as allowed by the RPL, and You may not copy or use this file in either source code 5 | * or executable form, except in compliance with the terms and conditions of the RPL 6 | * 7 | * All software distributed under the RPL is provided strictly on an "AS IS" basis, WITHOUT 8 | * WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, AND LICENSOR HEREBY DISCLAIMS ALL SUCH 9 | * WARRANTIES, INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 10 | * PARTICULAR PURPOSE, QUIET ENJOYMENT, OR NON-INFRINGEMENT. See the RPL for specific language 11 | * governing rights and limitations under the RPL. 12 | * 13 | * http://opensource.org/licenses/RPL-1.5 14 | * 15 | * Copyright 2018 Open Justice Broker Consortium and Cascadia Analytics LLC 16 | */ 17 | package org.ojbc.mondrian; 18 | 19 | import java.io.Serializable; 20 | import java.util.ArrayList; 21 | import java.util.Collections; 22 | import java.util.List; 23 | 24 | import org.olap4j.OlapException; 25 | import org.olap4j.metadata.Dimension; 26 | import org.olap4j.metadata.Hierarchy; 27 | 28 | import lombok.EqualsAndHashCode; 29 | import lombok.Getter; 30 | import lombok.ToString; 31 | 32 | /** 33 | * A wrapper around olap4j Dimension objects, suitable for serialization via json. 34 | * 35 | */ 36 | @Getter 37 | @EqualsAndHashCode 38 | @ToString 39 | public class DimensionWrapper implements Serializable { 40 | 41 | private static final long serialVersionUID = 4499740199331630404L; 42 | 43 | private String name; 44 | private String caption; 45 | private String type; 46 | private List hierarchies; 47 | 48 | DimensionWrapper() { 49 | } 50 | 51 | public DimensionWrapper(Dimension d) throws OlapException { 52 | this.name = d.getName(); 53 | this.caption = d.getCaption(); 54 | this.type = d.getDimensionType().getDescription(); 55 | hierarchies = new ArrayList<>(); 56 | for(Hierarchy h : d.getHierarchies()) { 57 | hierarchies.add(new HierarchyWrapper(h)); 58 | } 59 | } 60 | 61 | public List getHierarchies() { 62 | return Collections.unmodifiableList(hierarchies); 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/org/ojbc/mondrian/HierarchyWrapper.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Unless explicitly acquired and licensed from Licensor under another license, the contents of 3 | * this file are subject to the Reciprocal Public License ("RPL") Version 1.5, or subsequent 4 | * versions as allowed by the RPL, and You may not copy or use this file in either source code 5 | * or executable form, except in compliance with the terms and conditions of the RPL 6 | * 7 | * All software distributed under the RPL is provided strictly on an "AS IS" basis, WITHOUT 8 | * WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, AND LICENSOR HEREBY DISCLAIMS ALL SUCH 9 | * WARRANTIES, INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 10 | * PARTICULAR PURPOSE, QUIET ENJOYMENT, OR NON-INFRINGEMENT. See the RPL for specific language 11 | * governing rights and limitations under the RPL. 12 | * 13 | * http://opensource.org/licenses/RPL-1.5 14 | * 15 | * Copyright 2018 Open Justice Broker Consortium and Cascadia Analytics LLC 16 | */ 17 | package org.ojbc.mondrian; 18 | 19 | import java.io.Serializable; 20 | import java.util.ArrayList; 21 | import java.util.Collections; 22 | import java.util.List; 23 | 24 | import org.olap4j.OlapException; 25 | import org.olap4j.metadata.Hierarchy; 26 | import org.olap4j.metadata.Level; 27 | 28 | import com.fasterxml.jackson.annotation.JsonProperty; 29 | 30 | import lombok.EqualsAndHashCode; 31 | import lombok.Getter; 32 | import lombok.ToString; 33 | 34 | /** 35 | * A wrapper around olap4j Hierarchy objects, suitable for serialization via json. 36 | * 37 | */ 38 | @Getter 39 | @EqualsAndHashCode 40 | @ToString 41 | public class HierarchyWrapper implements Serializable { 42 | 43 | private static final long serialVersionUID = 6513035017849268043L; 44 | 45 | private String name; 46 | private String caption; 47 | @JsonProperty("hasAll") private boolean hasAll; 48 | private List levels; 49 | 50 | HierarchyWrapper() { } 51 | 52 | public HierarchyWrapper(Hierarchy h) throws OlapException { 53 | this.name = h.getName(); 54 | this.caption = h.getCaption(); 55 | this.hasAll = h.hasAll(); 56 | levels = new ArrayList<>(); 57 | for(Level level : h.getLevels()) { 58 | levels.add(new LevelWrapper(level)); 59 | } 60 | } 61 | 62 | public List getLevels() { 63 | return Collections.unmodifiableList(levels); 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /src/test/java/org/ojbc/mondrian/rest/QueryTimeoutTest.java: -------------------------------------------------------------------------------- 1 | package org.ojbc.mondrian.rest; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | import static org.junit.jupiter.api.Assertions.assertTrue; 5 | 6 | import java.net.URI; 7 | import java.util.Map; 8 | 9 | import org.junit.jupiter.api.Disabled; 10 | import org.junit.jupiter.api.Test; 11 | import org.springframework.boot.test.context.SpringBootTest; 12 | import org.springframework.boot.web.server.LocalServerPort; 13 | import org.springframework.core.ParameterizedTypeReference; 14 | import org.springframework.http.HttpEntity; 15 | import org.springframework.http.HttpMethod; 16 | import org.springframework.http.ResponseEntity; 17 | import org.springframework.test.context.TestPropertySource; 18 | 19 | @TestPropertySource(properties = { "queryTimeout=1" }) 20 | @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) 21 | public class QueryTimeoutTest extends AbstractMondrianRestControllerTest { 22 | 23 | @LocalServerPort 24 | private String port; 25 | 26 | @Test 27 | @Disabled // because we can't guarantee the query will always timeout; this is included more to document the behavior 28 | public void testQueryTimeout() throws Exception { 29 | 30 | HttpEntity requestEntity = buildQueryRequestEntity("foodmart", "SELECT NON EMPTY CrossJoin(Hierarchize({{[Product].[Products].[Product Family].Members}, {[Product].[Products].[Product Department].Members}, {[Product].[Products].[Product Category].Members}, {[Product].[Products].[Product Subcategory].Members}}), Hierarchize({{[Store].[Stores].[Store Country].Members}, {[Store].[Stores].[Store State].Members}, {[Store].[Stores].[Store City].Members}})) ON ROWS, NON EMPTY {[Measures].[Units Ordered]} ON COLUMNS FROM [Warehouse]"); 31 | 32 | //ResponseEntity response = restTemplate.postForEntity(new URI("http://localhost:" + port + "/query"), requestEntity, String.class); 33 | //log.info(response.getBody()); 34 | 35 | ParameterizedTypeReference> responseType = new ParameterizedTypeReference>() {}; 36 | 37 | ResponseEntity> errorResponse = restTemplate.exchange(new URI("http://localhost:" + port + "/query"), HttpMethod.POST, requestEntity, responseType); 38 | assertEquals(500, errorResponse.getStatusCode().value()); 39 | 40 | Map errorMap = errorResponse.getBody(); 41 | assertTrue(errorMap.get("reason").matches(".+Query timeout of.+reached")); 42 | 43 | 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/org/ojbc/mondrian/PositionWrapper.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Unless explicitly acquired and licensed from Licensor under another license, the contents of 3 | * this file are subject to the Reciprocal Public License ("RPL") Version 1.5, or subsequent 4 | * versions as allowed by the RPL, and You may not copy or use this file in either source code 5 | * or executable form, except in compliance with the terms and conditions of the RPL 6 | * 7 | * All software distributed under the RPL is provided strictly on an "AS IS" basis, WITHOUT 8 | * WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, AND LICENSOR HEREBY DISCLAIMS ALL SUCH 9 | * WARRANTIES, INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 10 | * PARTICULAR PURPOSE, QUIET ENJOYMENT, OR NON-INFRINGEMENT. See the RPL for specific language 11 | * governing rights and limitations under the RPL. 12 | * 13 | * http://opensource.org/licenses/RPL-1.5 14 | * 15 | * Copyright 2012-2017 Open Justice Broker Consortium 16 | */ 17 | package org.ojbc.mondrian; 18 | 19 | import java.util.ArrayList; 20 | import java.util.Collections; 21 | import java.util.List; 22 | 23 | import org.olap4j.Position; 24 | 25 | import lombok.EqualsAndHashCode; 26 | import lombok.Getter; 27 | import lombok.ToString; 28 | 29 | /** 30 | * A wrapper around Mondrian Position objects, suitable for serialization via json. 31 | * 32 | */ 33 | @Getter 34 | @EqualsAndHashCode 35 | @ToString 36 | public class PositionWrapper { 37 | 38 | private List memberDimensionNames; 39 | private List memberDimensionCaptions; 40 | private List positionMembers; 41 | 42 | PositionWrapper() { } 43 | 44 | public PositionWrapper(Position position) { 45 | 46 | memberDimensionNames = new ArrayList<>(); 47 | memberDimensionCaptions = new ArrayList<>(); 48 | positionMembers = new ArrayList<>(); 49 | 50 | position.getMembers().forEach(member -> { 51 | memberDimensionNames.add(member.getDimension().getName()); 52 | memberDimensionCaptions.add(member.getDimension().getCaption()); 53 | positionMembers.add(new PositionMemberWrapper(member)); 54 | }); 55 | 56 | } 57 | 58 | public List getPositionMembers() { 59 | return Collections.unmodifiableList(positionMembers); 60 | } 61 | 62 | public List getMemberDimensionNames() { 63 | return Collections.unmodifiableList(memberDimensionNames); 64 | } 65 | 66 | public List getMemberDimensionCaptions() { 67 | return Collections.unmodifiableList(memberDimensionCaptions); 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /src/main/java/org/ojbc/mondrian/CellSetWrapper.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Unless explicitly acquired and licensed from Licensor under another license, the contents of 3 | * this file are subject to the Reciprocal Public License ("RPL") Version 1.5, or subsequent 4 | * versions as allowed by the RPL, and You may not copy or use this file in either source code 5 | * or executable form, except in compliance with the terms and conditions of the RPL 6 | * 7 | * All software distributed under the RPL is provided strictly on an "AS IS" basis, WITHOUT 8 | * WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, AND LICENSOR HEREBY DISCLAIMS ALL SUCH 9 | * WARRANTIES, INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 10 | * PARTICULAR PURPOSE, QUIET ENJOYMENT, OR NON-INFRINGEMENT. See the RPL for specific language 11 | * governing rights and limitations under the RPL. 12 | * 13 | * http://opensource.org/licenses/RPL-1.5 14 | * 15 | * Copyright 2012-2017 Open Justice Broker Consortium 16 | */ 17 | package org.ojbc.mondrian; 18 | 19 | import java.util.ArrayList; 20 | import java.util.Collections; 21 | import java.util.List; 22 | 23 | import org.olap4j.CellSet; 24 | import org.olap4j.CellSetAxis; 25 | import org.olap4j.Position; 26 | 27 | import com.fasterxml.jackson.annotation.JsonProperty; 28 | 29 | import lombok.EqualsAndHashCode; 30 | import lombok.Getter; 31 | import lombok.ToString; 32 | 33 | /** 34 | * A wrapper around Mondrian CellSet objects, suitable for serialization via json. 35 | * 36 | */ 37 | @Getter 38 | @EqualsAndHashCode 39 | @ToString 40 | public class CellSetWrapper implements CellSetWrapperType { 41 | 42 | @JsonProperty("cells") 43 | private List cellWrappers; 44 | 45 | @JsonProperty("axes") 46 | private List axisWrappers; 47 | 48 | CellSetWrapper() {} 49 | 50 | public CellSetWrapper(CellSet cellSet) { 51 | 52 | cellWrappers = new ArrayList<>(); 53 | axisWrappers = new ArrayList<>(); 54 | 55 | int totalCellCount = 1; 56 | 57 | for (CellSetAxis axis : cellSet.getAxes()) { 58 | axisWrappers.add(new AxisWrapper(axis)); 59 | List positions = axis.getPositions(); 60 | totalCellCount *= positions.size(); 61 | } 62 | 63 | for (int i=0;i < totalCellCount;i++) { 64 | cellWrappers.add(new CellWrapper(cellSet.getCell(i))); 65 | } 66 | 67 | } 68 | 69 | public List getCellWrappers() { 70 | return Collections.unmodifiableList(cellWrappers); 71 | } 72 | 73 | public List getAxisWrappers() { 74 | return Collections.unmodifiableList(axisWrappers); 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /src/main/java/org/ojbc/mondrian/CubeWrapper.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Unless explicitly acquired and licensed from Licensor under another license, the contents of 3 | * this file are subject to the Reciprocal Public License ("RPL") Version 1.5, or subsequent 4 | * versions as allowed by the RPL, and You may not copy or use this file in either source code 5 | * or executable form, except in compliance with the terms and conditions of the RPL 6 | * 7 | * All software distributed under the RPL is provided strictly on an "AS IS" basis, WITHOUT 8 | * WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, AND LICENSOR HEREBY DISCLAIMS ALL SUCH 9 | * WARRANTIES, INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 10 | * PARTICULAR PURPOSE, QUIET ENJOYMENT, OR NON-INFRINGEMENT. See the RPL for specific language 11 | * governing rights and limitations under the RPL. 12 | * 13 | * http://opensource.org/licenses/RPL-1.5 14 | * 15 | * Copyright 2018 Open Justice Broker Consortium and Cascadia Analytics LLC 16 | */ 17 | package org.ojbc.mondrian; 18 | 19 | import java.io.Serializable; 20 | import java.util.ArrayList; 21 | import java.util.Collections; 22 | import java.util.List; 23 | import java.util.stream.Collectors; 24 | 25 | import org.olap4j.OlapException; 26 | import org.olap4j.metadata.Cube; 27 | import org.olap4j.metadata.Dimension; 28 | import org.w3c.dom.Document; 29 | 30 | import lombok.EqualsAndHashCode; 31 | import lombok.Getter; 32 | import lombok.ToString; 33 | 34 | /** 35 | * A wrapper around olap4j Cube objects, suitable for serialization via json. 36 | * 37 | */ 38 | @Getter 39 | @EqualsAndHashCode 40 | @ToString 41 | public class CubeWrapper implements Serializable { 42 | 43 | private static final long serialVersionUID = 8561468085905307671L; 44 | 45 | private String name; 46 | private String caption; 47 | private List measures; 48 | private List dimensions; 49 | 50 | CubeWrapper() { } 51 | 52 | public CubeWrapper(Cube cube, Document xmlSchema) throws OlapException { 53 | 54 | this.name = cube.getName(); 55 | this.caption = cube.getCaption(); 56 | measures = cube.getMeasures().stream().map(measure -> new MeasureWrapper(measure)).collect(Collectors.toList()); 57 | 58 | dimensions = new ArrayList<>(); 59 | for (Dimension d : cube.getDimensions()) { 60 | dimensions.add(new DimensionWrapper(d)); 61 | } 62 | 63 | } 64 | 65 | public List getMeasures() { 66 | return Collections.unmodifiableList(measures); 67 | } 68 | 69 | public List getDimensions() { 70 | return Collections.unmodifiableList(dimensions); 71 | } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /src/main/java/org/ojbc/mondrian/LevelWrapper.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Unless explicitly acquired and licensed from Licensor under another license, the contents of 3 | * this file are subject to the Reciprocal Public License ("RPL") Version 1.5, or subsequent 4 | * versions as allowed by the RPL, and You may not copy or use this file in either source code 5 | * or executable form, except in compliance with the terms and conditions of the RPL 6 | * 7 | * All software distributed under the RPL is provided strictly on an "AS IS" basis, WITHOUT 8 | * WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, AND LICENSOR HEREBY DISCLAIMS ALL SUCH 9 | * WARRANTIES, INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 10 | * PARTICULAR PURPOSE, QUIET ENJOYMENT, OR NON-INFRINGEMENT. See the RPL for specific language 11 | * governing rights and limitations under the RPL. 12 | * 13 | * http://opensource.org/licenses/RPL-1.5 14 | * 15 | * Copyright 2018 Open Justice Broker Consortium and Cascadia Analytics LLC 16 | */ 17 | package org.ojbc.mondrian; 18 | 19 | import java.io.Serializable; 20 | import java.util.ArrayList; 21 | import java.util.Collections; 22 | import java.util.List; 23 | 24 | import org.olap4j.OlapException; 25 | import org.olap4j.metadata.Level; 26 | import org.olap4j.metadata.Member; 27 | 28 | import lombok.EqualsAndHashCode; 29 | import lombok.Getter; 30 | import lombok.ToString; 31 | 32 | /** 33 | * A wrapper around olap4j Level objects, suitable for serialization via json. 34 | * 35 | */ 36 | @Getter 37 | @EqualsAndHashCode 38 | @ToString 39 | public class LevelWrapper implements Serializable { 40 | 41 | private static final long serialVersionUID = -5854141970363132112L; 42 | 43 | static final int CARDINALITY_LIMIT = Integer.MAX_VALUE; 44 | 45 | private String name; 46 | private String uniqueName; 47 | private String caption; 48 | private int depth; 49 | private int cardinality; 50 | private boolean populated; 51 | private List members = new ArrayList<>(); 52 | 53 | LevelWrapper() { } 54 | 55 | public LevelWrapper(Level level) throws OlapException { 56 | this.name = level.getName(); 57 | this.uniqueName = level.getUniqueName(); 58 | this.caption = level.getCaption(); 59 | this.depth = level.getDepth(); 60 | this.cardinality = level.getCardinality(); 61 | if (cardinality <= CARDINALITY_LIMIT) { 62 | populated = true; 63 | for(Member member : level.getMembers()) { 64 | members.add(new MemberWrapper(member)); 65 | } 66 | } else { 67 | populated = false; 68 | } 69 | } 70 | 71 | public List getMembers() { 72 | return Collections.unmodifiableList(members); 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /src/main/java/org/ojbc/mondrian/MondrianUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Unless explicitly acquired and licensed from Licensor under another license, the contents of 3 | * this file are subject to the Reciprocal Public License ("RPL") Version 1.5, or subsequent 4 | * versions as allowed by the RPL, and You may not copy or use this file in either source code 5 | * or executable form, except in compliance with the terms and conditions of the RPL 6 | * 7 | * All software distributed under the RPL is provided strictly on an "AS IS" basis, WITHOUT 8 | * WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, AND LICENSOR HEREBY DISCLAIMS ALL SUCH 9 | * WARRANTIES, INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 10 | * PARTICULAR PURPOSE, QUIET ENJOYMENT, OR NON-INFRINGEMENT. See the RPL for specific language 11 | * governing rights and limitations under the RPL. 12 | * 13 | * http://opensource.org/licenses/RPL-1.5 14 | * 15 | * Copyright 2012-2017 Open Justice Broker Consortium 16 | */ 17 | package org.ojbc.mondrian; 18 | 19 | import java.util.ArrayList; 20 | import java.util.List; 21 | 22 | import org.olap4j.Position; 23 | import org.olap4j.metadata.Member; 24 | 25 | /** 26 | * Class of assorted utility methods for dealing with the Mondrian object model. 27 | * 28 | */ 29 | public class MondrianUtils { 30 | 31 | /** 32 | * Get a string representation of the members within a position, suitable for debug trace etc. 33 | * @param position the position to print 34 | * @return a string containing the members of the position 35 | */ 36 | public static final String getMembersString(Position position) { 37 | List members = position.getMembers(); 38 | StringBuffer sb = new StringBuffer(); 39 | sb.append("*"); 40 | members.forEach(m -> { 41 | sb.append(m.getDimension().getName()).append("=").append(m.getName()).append("|"); 42 | }); 43 | String sbs = sb.toString().substring(0, sb.length()-1); 44 | StringBuffer ret = new StringBuffer(sbs).append("*"); 45 | return ret.toString(); 46 | } 47 | 48 | public static final List> permuteLists(List> lists) { 49 | List> ret = new ArrayList<>(); 50 | permuteLists(ret, new ArrayList(), lists); 51 | return ret; 52 | } 53 | 54 | private static final void permuteLists(List> accum, List current, List> lists) { 55 | 56 | lists.get(current.size()).forEach(element -> { 57 | List copy = new ArrayList<>(); 58 | copy.addAll(current); 59 | copy.add(element); 60 | if (copy.size() < lists.size()) { 61 | permuteLists(accum, copy, lists); 62 | } else { 63 | accum.add(copy); 64 | } 65 | }); 66 | 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /src/test/java/org/ojbc/mondrian/rest/AbstractMondrianRestControllerTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Unless explicitly acquired and licensed from Licensor under another license, the contents of 3 | * this file are subject to the Reciprocal Public License ("RPL") Version 1.5, or subsequent 4 | * versions as allowed by the RPL, and You may not copy or use this file in either source code 5 | * or executable form, except in compliance with the terms and conditions of the RPL 6 | * 7 | * All software distributed under the RPL is provided strictly on an "AS IS" basis, WITHOUT 8 | * WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, AND LICENSOR HEREBY DISCLAIMS ALL SUCH 9 | * WARRANTIES, INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 10 | * PARTICULAR PURPOSE, QUIET ENJOYMENT, OR NON-INFRINGEMENT. See the RPL for specific language 11 | * governing rights and limitations under the RPL. 12 | * 13 | * http://opensource.org/licenses/RPL-1.5 14 | * 15 | * Copyright 2012-2017 Open Justice Broker Consortium 16 | */ 17 | package org.ojbc.mondrian.rest; 18 | 19 | import java.util.Map; 20 | 21 | import org.springframework.beans.factory.annotation.Autowired; 22 | import org.springframework.boot.test.web.client.TestRestTemplate; 23 | import org.springframework.http.HttpEntity; 24 | import org.springframework.http.HttpHeaders; 25 | import org.springframework.http.MediaType; 26 | 27 | /** 28 | * Abstract base class of rest controller tests 29 | * 30 | */ 31 | public abstract class AbstractMondrianRestControllerTest { 32 | 33 | @Autowired 34 | protected TestRestTemplate restTemplate; 35 | 36 | protected HttpEntity buildQueryRequestEntity(String connectionName, String queryString) { 37 | return buildQueryRequestEntity(connectionName, queryString, null); 38 | } 39 | 40 | protected HttpEntity buildQueryRequestEntity(String connectionName, String queryString, Map headerMap) { 41 | HttpHeaders headers = new HttpHeaders(); 42 | headers.setContentType(MediaType.APPLICATION_JSON); 43 | if (headerMap != null) { 44 | for(String key : headerMap.keySet()) { 45 | headers.set(key, headerMap.get(key)); 46 | } 47 | } 48 | return new HttpEntity("{ \"connectionName\" : \"" + connectionName + "\", \"query\" : \"" + queryString + "\"}", headers); 49 | } 50 | 51 | protected HttpEntity buildQueryRequestEntity(String connectionName, String queryString, boolean tidy) { 52 | HttpHeaders headers = new HttpHeaders(); 53 | headers.setContentType(MediaType.APPLICATION_JSON); 54 | return new HttpEntity("{ \"connectionName\" : \"" + connectionName + "\", \"query\" : \"" + queryString + "\", \"tidy\" : { \"enabled\": " + 55 | tidy + "}}", headers); 56 | } 57 | 58 | } -------------------------------------------------------------------------------- /src/test/java/org/ojbc/mondrian/rest/DemoConnectionRemovalTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Unless explicitly acquired and licensed from Licensor under another license, the contents of 3 | * this file are subject to the Reciprocal Public License ("RPL") Version 1.5, or subsequent 4 | * versions as allowed by the RPL, and You may not copy or use this file in either source code 5 | * or executable form, except in compliance with the terms and conditions of the RPL 6 | * 7 | * All software distributed under the RPL is provided strictly on an "AS IS" basis, WITHOUT 8 | * WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, AND LICENSOR HEREBY DISCLAIMS ALL SUCH 9 | * WARRANTIES, INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 10 | * PARTICULAR PURPOSE, QUIET ENJOYMENT, OR NON-INFRINGEMENT. See the RPL for specific language 11 | * governing rights and limitations under the RPL. 12 | * 13 | * http://opensource.org/licenses/RPL-1.5 14 | * 15 | * Copyright 2012-2017 Open Justice Broker Consortium 16 | */ 17 | package org.ojbc.mondrian.rest; 18 | 19 | import static org.junit.jupiter.api.Assertions.assertEquals; 20 | 21 | import java.net.URI; 22 | import java.util.Map; 23 | 24 | import org.junit.jupiter.api.Test; 25 | import org.ojbc.mondrian.MondrianConnectionFactory.MondrianConnection; 26 | import org.springframework.beans.factory.annotation.Autowired; 27 | import org.springframework.boot.test.context.SpringBootTest; 28 | import org.springframework.boot.test.web.client.TestRestTemplate; 29 | import org.springframework.boot.web.server.LocalServerPort; 30 | import org.springframework.core.ParameterizedTypeReference; 31 | import org.springframework.http.RequestEntity; 32 | import org.springframework.http.ResponseEntity; 33 | import org.springframework.test.context.TestPropertySource; 34 | 35 | @TestPropertySource(properties = { "removeDemoConnections=true" }) 36 | @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) 37 | public class DemoConnectionRemovalTest { 38 | 39 | @LocalServerPort 40 | private String port; 41 | 42 | @Autowired 43 | private TestRestTemplate restTemplate; 44 | 45 | @Test 46 | public void testGetConnections() throws Exception { 47 | 48 | ParameterizedTypeReference> responseType = new ParameterizedTypeReference>() {}; 49 | RequestEntity request = RequestEntity.get(new URI("http://localhost:" + port + "/getConnections")).build(); 50 | 51 | ResponseEntity> response = restTemplate.exchange(request, responseType); 52 | Map connections = response.getBody(); 53 | 54 | assertEquals(0, connections.size()); 55 | 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /src/test/java/org/ojbc/mondrian/util/BasicMondrianQueryTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Unless explicitly acquired and licensed from Licensor under another license, the contents of 3 | * this file are subject to the Reciprocal Public License ("RPL") Version 1.5, or subsequent 4 | * versions as allowed by the RPL, and You may not copy or use this file in either source code 5 | * or executable form, except in compliance with the terms and conditions of the RPL 6 | * 7 | * All software distributed under the RPL is provided strictly on an "AS IS" basis, WITHOUT 8 | * WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, AND LICENSOR HEREBY DISCLAIMS ALL SUCH 9 | * WARRANTIES, INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 10 | * PARTICULAR PURPOSE, QUIET ENJOYMENT, OR NON-INFRINGEMENT. See the RPL for specific language 11 | * governing rights and limitations under the RPL. 12 | * 13 | * http://opensource.org/licenses/RPL-1.5 14 | * 15 | * Copyright 2012-2017 Open Justice Broker Consortium 16 | */ 17 | package org.ojbc.mondrian.util; 18 | 19 | import static org.junit.jupiter.api.Assertions.assertEquals; 20 | import static org.junit.jupiter.api.Assertions.assertTrue; 21 | 22 | import java.util.List; 23 | 24 | import org.junit.jupiter.api.AfterEach; 25 | import org.junit.jupiter.api.BeforeEach; 26 | import org.junit.jupiter.api.Test; 27 | 28 | import lombok.extern.slf4j.Slf4j; 29 | import mondrian.olap.Axis; 30 | import mondrian.olap.Cell; 31 | import mondrian.olap.Connection; 32 | import mondrian.olap.Member; 33 | import mondrian.olap.Position; 34 | import mondrian.olap.Query; 35 | import mondrian.olap.Result; 36 | 37 | @Slf4j 38 | public class BasicMondrianQueryTest { 39 | 40 | private Connection mondrianOlapConnection; 41 | 42 | @BeforeEach 43 | public void setUp() throws Exception { 44 | log.debug("setUp"); 45 | mondrianOlapConnection = DatabaseUtils.getInstance().getMondrianConnection(); 46 | } 47 | 48 | @AfterEach 49 | public void tearDown() throws Exception { 50 | mondrianOlapConnection.close(); 51 | } 52 | 53 | @SuppressWarnings("deprecation") 54 | @Test 55 | public void test() throws Exception { 56 | String query = "select {[Measures].[F1_M1]} on columns from Test_F1"; 57 | Query q = mondrianOlapConnection.parseQuery(query); 58 | Result result = mondrianOlapConnection.execute(q); 59 | Axis[] axes = result.getAxes(); 60 | List positions = axes[0].getPositions(); 61 | assertEquals(1, positions.size()); 62 | Position p = positions.get(0); 63 | Member m = p.get(0); 64 | assertEquals("Measures", m.getDimension().getName()); 65 | assertTrue(m.isMeasure()); 66 | Cell cell = result.getCell(new int[]{0}); 67 | assertEquals(3.0, cell.getValue()); 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /src/main/webapp/query-ui/QueryUI.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Mondrian REST Query UI 8 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 21 | 22 |
23 |
24 |
25 | 33 |
34 | 35 | 36 |
37 |
38 | 39 | 40 |
41 |
42 | 43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | # mondrian-rest Spring application properties file 2 | # 3 | # this file controls the configuration of the REST controller 4 | # to customize configuration, edit the properties in this file as appropriate, then place the file on the Tomcat classpath (usually this 5 | # means enabling the Tomcat shared loader and placing the edited application.properties file in the shared loader directory; see the 6 | # included Docker images for examples of how to do this.) 7 | 8 | # defaults for the request authorizer bean and saml token strategy bean 9 | requestAuthorizerBeanName=defaultRequestAuthorizer 10 | samlTokenStrategyBeanName=defaultSamlTokenStrategy 11 | 12 | # to use alternative security mechanisms: 13 | 14 | # for bearer authorization 15 | #requestAuthorizerBeanName=bearerTokenRequestAuthorizer 16 | #bearerTokenRequestAuthorizerConfigFileName=MyCustomBearerAuthorizationConfig.json 17 | 18 | # for SAML assertion authorization, looking up authorized users and their roles from config file, getting user token from an assertion attribute 19 | #requestAuthorizerBeanName=samlAssertionRequestAuthorizer 20 | #samlAssertionRequestAuthorizerConfigFileName=MyCustomSamlAuthorizationConfig.json 21 | #samlTokenStrategyBeanName=attributeSamlTokenStrategy 22 | #samlAssertionTokenAttributeName=gfipm:2.0:user:FederationId 23 | 24 | # for SAML assertion authorization, assigning role based on value of an assertion attribute, and getting user token from an assertion attribute 25 | #requestAuthorizerBeanName=samlAssertionRequestRoleAuthorizer 26 | #samlTokenStrategyBeanName=attributeSamlTokenStrategy 27 | #samlAssertionTokenAttributeName=gfipm:2.0:user:FederationId 28 | #samlAssertionRoleAttributeName=gfipm:2.0:user:EmployerORI 29 | 30 | # set this to true to start up the controller without the demo connections (the Test and Foodmart datasets)...typically you want to do this 31 | # when you deploy the controller with your own data sources 32 | removeDemoConnections=false 33 | 34 | # set this to false to disable the simple query UI 35 | #queryUIEnabled=true 36 | 37 | # set this to true to cache metadata for available connections at startup time; this avoids the first user who accesses metadata or performs a query 38 | # from enduring a potentially long response time (especially for connections/schemas with deep and wide hierarchy levels and/or poorly performing databases), but 39 | # at the cost of a longer startup time for the controller 40 | #preCacheMetadata=false 41 | 42 | # set a timeout in seconds for queries (queries run indefinitely by default) 43 | #queryTimeout=30 44 | 45 | # generally you shouldn't change the properties below this line 46 | server.tomcat.additional-tld-skip-patterns=xercesImpl.jar,xml-apis.jar,serializer.jar,*.jar 47 | -------------------------------------------------------------------------------- /src/test/java/org/ojbc/mondrian/rest/BearerTokenRequestAuthorizerTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Unless explicitly acquired and licensed from Licensor under another license, the contents of 3 | * this file are subject to the Reciprocal Public License ("RPL") Version 1.5, or subsequent 4 | * versions as allowed by the RPL, and You may not copy or use this file in either source code 5 | * or executable form, except in compliance with the terms and conditions of the RPL 6 | * 7 | * All software distributed under the RPL is provided strictly on an "AS IS" basis, WITHOUT 8 | * WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, AND LICENSOR HEREBY DISCLAIMS ALL SUCH 9 | * WARRANTIES, INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 10 | * PARTICULAR PURPOSE, QUIET ENJOYMENT, OR NON-INFRINGEMENT. See the RPL for specific language 11 | * governing rights and limitations under the RPL. 12 | * 13 | * http://opensource.org/licenses/RPL-1.5 14 | * 15 | * Copyright 2012-2017 Open Justice Broker Consortium 16 | */ 17 | package org.ojbc.mondrian.rest; 18 | 19 | import static org.junit.jupiter.api.Assertions.assertEquals; 20 | import static org.junit.jupiter.api.Assertions.assertNotNull; 21 | import static org.junit.jupiter.api.Assertions.assertTrue; 22 | 23 | import java.util.HashMap; 24 | import java.util.Map; 25 | 26 | import javax.servlet.http.HttpServletRequest; 27 | 28 | import org.junit.jupiter.api.BeforeEach; 29 | import org.junit.jupiter.api.Test; 30 | import org.mockito.Mockito; 31 | 32 | public class BearerTokenRequestAuthorizerTest { 33 | 34 | private BearerTokenRequestAuthorizer bearerTokenRequestAuthorizer; 35 | Map> tokenRoleMappings = new HashMap<>(); 36 | 37 | @BeforeEach 38 | public void setUp() { 39 | Map innerMap = new HashMap<>(); 40 | innerMap.put("test", "ROLE1"); 41 | tokenRoleMappings.put("TOKEN1", innerMap); 42 | bearerTokenRequestAuthorizer = new BearerTokenRequestAuthorizer(); 43 | bearerTokenRequestAuthorizer.setTokenRoleMappings(tokenRoleMappings); 44 | assertNotNull(bearerTokenRequestAuthorizer); 45 | } 46 | 47 | @Test 48 | public void test() { 49 | 50 | HttpServletRequest request = Mockito.mock(HttpServletRequest.class); 51 | Mockito.when(request.getHeader("Authorization")).thenReturn("Bearer TOKEN1"); 52 | 53 | QueryRequest queryRequest = Mockito.mock(QueryRequest.class); 54 | Mockito.when(queryRequest.getConnectionName()).thenReturn("test"); 55 | 56 | RequestAuthorizer.RequestAuthorizationStatus status = bearerTokenRequestAuthorizer.authorizeRequest(request, queryRequest.getConnectionName()); 57 | assertTrue(status.authorized); 58 | String role = status.mondrianRole; 59 | assertNotNull(role); 60 | assertEquals("ROLE1", role); 61 | 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /src/test/java/org/ojbc/mondrian/TestMondrianUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Unless explicitly acquired and licensed from Licensor under another license, the contents of 3 | * this file are subject to the Reciprocal Public License ("RPL") Version 1.5, or subsequent 4 | * versions as allowed by the RPL, and You may not copy or use this file in either source code 5 | * or executable form, except in compliance with the terms and conditions of the RPL 6 | * 7 | * All software distributed under the RPL is provided strictly on an "AS IS" basis, WITHOUT 8 | * WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, AND LICENSOR HEREBY DISCLAIMS ALL SUCH 9 | * WARRANTIES, INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 10 | * PARTICULAR PURPOSE, QUIET ENJOYMENT, OR NON-INFRINGEMENT. See the RPL for specific language 11 | * governing rights and limitations under the RPL. 12 | * 13 | * http://opensource.org/licenses/RPL-1.5 14 | * 15 | * Copyright 2012-2017 Open Justice Broker Consortium 16 | */ 17 | package org.ojbc.mondrian; 18 | 19 | import static org.junit.jupiter.api.Assertions.assertEquals; 20 | 21 | import java.util.ArrayList; 22 | import java.util.Arrays; 23 | import java.util.List; 24 | 25 | import org.junit.jupiter.api.BeforeEach; 26 | import org.junit.jupiter.api.Test; 27 | 28 | import lombok.extern.slf4j.Slf4j; 29 | 30 | @Slf4j 31 | public class TestMondrianUtils { 32 | 33 | @BeforeEach 34 | public void setUp() throws Exception { 35 | log.debug("setUp"); 36 | } 37 | 38 | @Test 39 | public void testPermuteLists() { 40 | 41 | List> lists = new ArrayList<>(); 42 | lists.add(Arrays.asList(new String[] {"A", "B", "C"})); 43 | lists.add(Arrays.asList(new String[] {"W", "X", "Y", "Z"})); 44 | 45 | List> permutations = MondrianUtils.permuteLists(lists); 46 | 47 | assertEquals(12, permutations.size()); 48 | assertEquals(2, permutations.get(0).size()); 49 | 50 | lists.add(Arrays.asList(new String[] {"one", "two"})); 51 | 52 | permutations = MondrianUtils.permuteLists(lists); 53 | 54 | assertEquals(24, permutations.size()); 55 | assertEquals(3, permutations.get(0).size()); 56 | 57 | lists.add(Arrays.asList(new String[] {null})); 58 | 59 | permutations = MondrianUtils.permuteLists(lists); 60 | 61 | assertEquals(24, permutations.size()); 62 | List outputElement1 = permutations.get(0); 63 | assertEquals(4, outputElement1.size()); 64 | assertEquals(Arrays.asList(new String[] {"A", "W", "one", null}), outputElement1); 65 | assertEquals(Arrays.asList(new String[] {"A", "W", "two", null}), permutations.get(1)); 66 | assertEquals(Arrays.asList(new String[] {"A", "X", "one", null}), permutations.get(2)); 67 | assertEquals(Arrays.asList(new String[] {"B", "W", "one", null}), permutations.get(8)); 68 | 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/org/ojbc/mondrian/rest/SamlUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Unless explicitly acquired and licensed from Licensor under another license, the contents of 3 | * this file are subject to the Reciprocal Public License ("RPL") Version 1.5, or subsequent 4 | * versions as allowed by the RPL, and You may not copy or use this file in either source code 5 | * or executable form, except in compliance with the terms and conditions of the RPL 6 | * 7 | * All software distributed under the RPL is provided strictly on an "AS IS" basis, WITHOUT 8 | * WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, AND LICENSOR HEREBY DISCLAIMS ALL SUCH 9 | * WARRANTIES, INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 10 | * PARTICULAR PURPOSE, QUIET ENJOYMENT, OR NON-INFRINGEMENT. See the RPL for specific language 11 | * governing rights and limitations under the RPL. 12 | * 13 | * http://opensource.org/licenses/RPL-1.5 14 | * 15 | * Copyright 2012-2020 Open Justice Broker Consortium 16 | */ 17 | package org.ojbc.mondrian.rest; 18 | 19 | import java.util.Collections; 20 | import java.util.Iterator; 21 | 22 | import javax.xml.namespace.NamespaceContext; 23 | import javax.xml.xpath.XPath; 24 | import javax.xml.xpath.XPathConstants; 25 | import javax.xml.xpath.XPathExpressionException; 26 | import javax.xml.xpath.XPathFactory; 27 | 28 | import org.w3c.dom.Document; 29 | 30 | public class SamlUtils { 31 | 32 | protected static final NamespaceContext SAML_NAMESPACE_CONTEXT = new NamespaceContext() { 33 | @Override 34 | public Iterator getPrefixes(String uri) { 35 | return "urn:oasis:names:tc:SAML:2.0:assertion".equals(uri) ? Collections.singletonList("saml2").iterator() : null; 36 | } 37 | @Override 38 | public String getPrefix(String uri) { 39 | return "urn:oasis:names:tc:SAML:2.0:assertion".equals(uri) ? "saml2" : null; 40 | } 41 | @Override 42 | public String getNamespaceURI(String prefix) { 43 | return "saml2".equals(prefix) ? "urn:oasis:names:tc:SAML:2.0:assertion" : null; 44 | } 45 | }; 46 | 47 | public static final String getAssertionAttributeValue(Document assertion, String attributeName) { 48 | return getStringXPathValue(assertion, "/saml2:Assertion/saml2:AttributeStatement/saml2:Attribute[@Name='" + attributeName + "']/saml2:AttributeValue"); 49 | } 50 | 51 | public static String getNameIdValue(Document assertion) { 52 | return getStringXPathValue(assertion, "/saml2:Assertion/saml2:Subject/saml2:NameID"); 53 | } 54 | 55 | private static String getStringXPathValue(Document assertion, String expression) { 56 | XPath xPath = XPathFactory.newInstance().newXPath(); 57 | xPath.setNamespaceContext(SAML_NAMESPACE_CONTEXT); 58 | try { 59 | return (String) xPath.evaluate(expression, assertion, XPathConstants.STRING); 60 | } catch (XPathExpressionException e) { 61 | throw new RuntimeException(e); 62 | } 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/org/ojbc/mondrian/MemberWrapper.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Unless explicitly acquired and licensed from Licensor under another license, the contents of 3 | * this file are subject to the Reciprocal Public License ("RPL") Version 1.5, or subsequent 4 | * versions as allowed by the RPL, and You may not copy or use this file in either source code 5 | * or executable form, except in compliance with the terms and conditions of the RPL 6 | * 7 | * All software distributed under the RPL is provided strictly on an "AS IS" basis, WITHOUT 8 | * WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, AND LICENSOR HEREBY DISCLAIMS ALL SUCH 9 | * WARRANTIES, INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 10 | * PARTICULAR PURPOSE, QUIET ENJOYMENT, OR NON-INFRINGEMENT. See the RPL for specific language 11 | * governing rights and limitations under the RPL. 12 | * 13 | * http://opensource.org/licenses/RPL-1.5 14 | * 15 | * Copyright 2018 Open Justice Broker Consortium and Cascadia Analytics LLC 16 | */ 17 | package org.ojbc.mondrian; 18 | 19 | import java.io.Serializable; 20 | import java.util.ArrayList; 21 | import java.util.Collections; 22 | import java.util.HashMap; 23 | import java.util.List; 24 | import java.util.Map; 25 | 26 | import org.olap4j.OlapException; 27 | import org.olap4j.metadata.Member; 28 | 29 | import lombok.EqualsAndHashCode; 30 | import lombok.Getter; 31 | import lombok.ToString; 32 | 33 | /** 34 | * A wrapper around olap4j Member objects, suitable for serialization via json. 35 | * 36 | */ 37 | @Getter 38 | @EqualsAndHashCode 39 | @ToString 40 | public class MemberWrapper implements Serializable { 41 | 42 | private static final long serialVersionUID = -4507370793045423111L; 43 | 44 | private String name; 45 | private String caption; 46 | private boolean isAll; 47 | private int childMemberCount; 48 | private List childMembers; 49 | private boolean childMembersPopulated; 50 | 51 | MemberWrapper() { } 52 | 53 | public MemberWrapper(Member member) throws OlapException { 54 | this.name = member.getName(); 55 | this.caption = member.getCaption(); 56 | this.isAll = member.isAll(); 57 | childMembers = new ArrayList<>(); 58 | childMembersPopulated = true; 59 | // this appears necessary because Mondrian seems to include some dup members... 60 | Map handledMemberLookup = new HashMap<>(); 61 | for(Member child : member.getChildMembers()) { 62 | String childName = child.getName(); 63 | if (!handledMemberLookup.keySet().contains(childName) || handledMemberLookup.get(childName) == 0) { 64 | childMembers.add(new MemberWrapper(child)); 65 | handledMemberLookup.put(childName, child.getChildMembers().size()); 66 | } 67 | } 68 | this.childMemberCount = childMembers.size(); 69 | } 70 | 71 | public List getChildMembers() { 72 | return Collections.unmodifiableList(childMembers); 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /src/test/java/org/ojbc/mondrian/TestSchemaFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Unless explicitly acquired and licensed from Licensor under another license, the contents of 3 | * this file are subject to the Reciprocal Public License ("RPL") Version 1.5, or subsequent 4 | * versions as allowed by the RPL, and You may not copy or use this file in either source code 5 | * or executable form, except in compliance with the terms and conditions of the RPL 6 | * 7 | * All software distributed under the RPL is provided strictly on an "AS IS" basis, WITHOUT 8 | * WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, AND LICENSOR HEREBY DISCLAIMS ALL SUCH 9 | * WARRANTIES, INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 10 | * PARTICULAR PURPOSE, QUIET ENJOYMENT, OR NON-INFRINGEMENT. See the RPL for specific language 11 | * governing rights and limitations under the RPL. 12 | * 13 | * http://opensource.org/licenses/RPL-1.5 14 | * 15 | * Copyright 2018 Open Justice Broker Consortium and Cascadia Analytics LLC 16 | */ 17 | package org.ojbc.mondrian; 18 | 19 | import static org.mockito.Mockito.mock; 20 | import static org.mockito.Mockito.when; 21 | 22 | import java.util.ArrayList; 23 | import java.util.List; 24 | 25 | import org.olap4j.OlapException; 26 | import org.olap4j.impl.ArrayNamedListImpl; 27 | import org.olap4j.metadata.Cube; 28 | import org.olap4j.metadata.Dimension; 29 | import org.olap4j.metadata.Schema; 30 | 31 | public class TestSchemaFactory { 32 | 33 | private static final TestSchemaFactory INSTANCE = new TestSchemaFactory(); 34 | 35 | private TestSchemaFactory() { 36 | } 37 | 38 | public static final TestSchemaFactory getInstance() { 39 | return INSTANCE; 40 | } 41 | 42 | public Schema getSchema() throws OlapException { 43 | 44 | Schema ret = mock(Schema.class); 45 | when(ret.getName()).thenReturn("schema1"); 46 | 47 | Cube cube = mock(Cube.class); 48 | when(cube.getName()).thenReturn("cube1"); 49 | when(cube.getCaption()).thenReturn("Cube 1"); 50 | 51 | List cubes = new ArrayList<>(); 52 | cubes.add(cube); 53 | 54 | when(ret.getCubes()).thenReturn(new ArrayNamedListImpl(cubes) { 55 | private static final long serialVersionUID = 1L; 56 | @Override 57 | public String getName(Object element) { 58 | return ((Cube) element).getName(); 59 | } 60 | }); 61 | 62 | // we do a fair amount of testing of this structure in the controller test 63 | // if we find we need to do more, we can mock more objects 64 | 65 | when(cube.getMeasures()).thenReturn(new ArrayList<>()); 66 | List dimensions = new ArrayList<>(); 67 | when(cube.getDimensions()).thenReturn(new ArrayNamedListImpl(dimensions) { 68 | private static final long serialVersionUID = 1L; 69 | @Override 70 | public String getName(Object element) { 71 | return ""; 72 | } 73 | }); 74 | 75 | return ret; 76 | 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /src/main/java/org/ojbc/mondrian/rest/QueryUIController.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Unless explicitly acquired and licensed from Licensor under another license, the contents of 3 | * this file are subject to the Reciprocal Public License ("RPL") Version 1.5, or subsequent 4 | * versions as allowed by the RPL, and You may not copy or use this file in either source code 5 | * or executable form, except in compliance with the terms and conditions of the RPL 6 | * 7 | * All software distributed under the RPL is provided strictly on an "AS IS" basis, WITHOUT 8 | * WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, AND LICENSOR HEREBY DISCLAIMS ALL SUCH 9 | * WARRANTIES, INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 10 | * PARTICULAR PURPOSE, QUIET ENJOYMENT, OR NON-INFRINGEMENT. See the RPL for specific language 11 | * governing rights and limitations under the RPL. 12 | * 13 | * http://opensource.org/licenses/RPL-1.5 14 | * 15 | * Copyright 2012-2017 Open Justice Broker Consortium 16 | */ 17 | package org.ojbc.mondrian.rest; 18 | 19 | import java.io.BufferedReader; 20 | import java.io.IOException; 21 | import java.io.InputStream; 22 | import java.io.InputStreamReader; 23 | 24 | import javax.annotation.PostConstruct; 25 | import javax.servlet.ServletContext; 26 | 27 | import org.springframework.beans.factory.annotation.Autowired; 28 | import org.springframework.beans.factory.annotation.Value; 29 | import org.springframework.http.HttpStatus; 30 | import org.springframework.http.ResponseEntity; 31 | import org.springframework.stereotype.Controller; 32 | import org.springframework.web.bind.annotation.RequestMapping; 33 | import org.springframework.web.bind.annotation.RequestMethod; 34 | 35 | @Controller 36 | public class QueryUIController { 37 | 38 | @Autowired 39 | private ServletContext servletContext; 40 | 41 | @Value("${queryUIEnabled:true}") 42 | private boolean queryUIEnabled; 43 | 44 | private String queryUIBody; 45 | private HttpStatus status; 46 | 47 | @PostConstruct 48 | public void init() throws IOException { 49 | queryUIBody = queryUIEnabled ? readResource("/query-ui/QueryUI.html").toString() : null; 50 | status = queryUIEnabled ? HttpStatus.OK : HttpStatus.FORBIDDEN; 51 | } 52 | 53 | @RequestMapping(value="/query-ui/*", method=RequestMethod.GET, produces="text/html") 54 | public ResponseEntity getQueryUI() { 55 | return new ResponseEntity(queryUIBody, status); 56 | } 57 | 58 | private StringBuffer readResource(String htmlPath) { 59 | InputStream is = servletContext.getResourceAsStream(htmlPath); 60 | BufferedReader br = new BufferedReader(new InputStreamReader(is)); 61 | StringBuffer sb = new StringBuffer(1024*10); 62 | String line = null; 63 | try { 64 | while ((line = br.readLine()) != null) { 65 | sb.append(line).append("\n"); 66 | } 67 | } catch (IOException ioe) { 68 | throw new RuntimeException(ioe); 69 | } 70 | return sb; 71 | } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /src/test/java/org/ojbc/mondrian/util/DatabaseUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Unless explicitly acquired and licensed from Licensor under another license, the contents of 3 | * this file are subject to the Reciprocal Public License ("RPL") Version 1.5, or subsequent 4 | * versions as allowed by the RPL, and You may not copy or use this file in either source code 5 | * or executable form, except in compliance with the terms and conditions of the RPL 6 | * 7 | * All software distributed under the RPL is provided strictly on an "AS IS" basis, WITHOUT 8 | * WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, AND LICENSOR HEREBY DISCLAIMS ALL SUCH 9 | * WARRANTIES, INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 10 | * PARTICULAR PURPOSE, QUIET ENJOYMENT, OR NON-INFRINGEMENT. See the RPL for specific language 11 | * governing rights and limitations under the RPL. 12 | * 13 | * http://opensource.org/licenses/RPL-1.5 14 | * 15 | * Copyright 2012-2017 Open Justice Broker Consortium 16 | */ 17 | package org.ojbc.mondrian.util; 18 | 19 | import java.net.URL; 20 | import java.sql.DriverManager; 21 | import java.sql.SQLException; 22 | import java.util.Properties; 23 | 24 | import mondrian.olap.Util.PropertyList; 25 | 26 | class DatabaseUtils { 27 | 28 | private static final DatabaseUtils INSTANCE = new DatabaseUtils(); 29 | 30 | public static final DatabaseUtils getInstance() { 31 | return INSTANCE; 32 | } 33 | 34 | private java.sql.Connection olap4jConnection; 35 | private mondrian.olap.Connection mondrianConnection; 36 | 37 | private DatabaseUtils() { 38 | } 39 | 40 | public java.sql.Connection getTestDatabaseConnection(boolean init) throws SQLException { 41 | return java.sql.DriverManager.getConnection(getConnectionString()); 42 | } 43 | 44 | public java.sql.Connection getOlap4jConnection() throws SQLException, ClassNotFoundException { 45 | if (olap4jConnection == null || olap4jConnection.isClosed()) { 46 | Class.forName("mondrian.olap4j.MondrianOlap4jDriver"); 47 | String url = "jdbc:mondrian:"; 48 | Properties props = new Properties(); 49 | props.setProperty("Jdbc", getConnectionString()); 50 | props.setProperty("Catalog", getMondrianSchemaPath()); 51 | props.setProperty("JdbcDrivers", getDriverClassName()); 52 | olap4jConnection = DriverManager.getConnection(url, props); 53 | } 54 | return olap4jConnection; 55 | } 56 | 57 | public mondrian.olap.Connection getMondrianConnection() { 58 | if (mondrianConnection == null) { 59 | PropertyList list = new PropertyList(); 60 | list.put("Provider", "mondrian"); 61 | list.put("Catalog", getMondrianSchemaPath()); 62 | list.put("Jdbc", getConnectionString()); 63 | mondrianConnection = mondrian.olap.DriverManager.getConnection(list, null); 64 | } 65 | return mondrianConnection; 66 | } 67 | 68 | private String getDriverClassName() { 69 | return "org.hsqldb.jdbc.JDBCDriver"; 70 | } 71 | 72 | private String getConnectionString() { 73 | return "jdbc:hsqldb:res:test"; 74 | } 75 | 76 | private String getMondrianSchemaPath() { 77 | URL url = DatabaseUtils.class.getResource("/test.xml"); 78 | return url.getFile(); 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /src/main/java/org/ojbc/mondrian/rest/RequestAuthorizer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Unless explicitly acquired and licensed from Licensor under another license, the contents of 3 | * this file are subject to the Reciprocal Public License ("RPL") Version 1.5, or subsequent 4 | * versions as allowed by the RPL, and You may not copy or use this file in either source code 5 | * or executable form, except in compliance with the terms and conditions of the RPL 6 | * 7 | * All software distributed under the RPL is provided strictly on an "AS IS" basis, WITHOUT 8 | * WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, AND LICENSOR HEREBY DISCLAIMS ALL SUCH 9 | * WARRANTIES, INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 10 | * PARTICULAR PURPOSE, QUIET ENJOYMENT, OR NON-INFRINGEMENT. See the RPL for specific language 11 | * governing rights and limitations under the RPL. 12 | * 13 | * http://opensource.org/licenses/RPL-1.5 14 | * 15 | * Copyright 2012-2017 Open Justice Broker Consortium 16 | */ 17 | package org.ojbc.mondrian.rest; 18 | 19 | import java.util.Map; 20 | 21 | import javax.servlet.http.HttpServletRequest; 22 | 23 | import org.springframework.core.io.Resource; 24 | import org.springframework.core.io.support.PathMatchingResourcePatternResolver; 25 | import org.springframework.core.io.support.ResourcePatternResolver; 26 | 27 | import com.fasterxml.jackson.core.type.TypeReference; 28 | import com.fasterxml.jackson.databind.ObjectMapper; 29 | 30 | import lombok.extern.slf4j.Slf4j; 31 | 32 | /** 33 | * Interface for objects that can authorize query requests incoming to the API. 34 | * 35 | */ 36 | public interface RequestAuthorizer { 37 | 38 | public static final String ALL_ACCESS_ROLE_NAME = RequestAuthorizer.class.getName() + "-All-Access"; 39 | 40 | static final class RequestAuthorizationStatus { 41 | public boolean authorized; 42 | public String message; 43 | public String mondrianRole; 44 | public String token; 45 | } 46 | 47 | @Slf4j 48 | static final class AuthorizerUtil { 49 | 50 | public static final Map> convertRoleConnectionJsonToMaps(String jsonFileName) throws Exception { 51 | 52 | ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); 53 | Resource[] resources = resolver.getResources("classpath*:" + jsonFileName); 54 | 55 | ObjectMapper mapper = new ObjectMapper(); 56 | TypeReference>> typeRef = new TypeReference>>() {}; 57 | 58 | Map> ret = null; 59 | 60 | if (resources.length == 0) { 61 | log.warn("Role mapping file " + jsonFileName + " not found"); 62 | } else if (resources.length > 1) { 63 | log.warn("Multiple role mapping files found with name " + jsonFileName + ", the last one read from the classpath will be used"); 64 | } 65 | 66 | for (int i=0;i < resources.length && ret == null;i++) { 67 | Resource resource = resources[i]; 68 | if (resource.exists()) { 69 | ret = mapper.readValue(resource.getInputStream(), typeRef); 70 | } 71 | } 72 | 73 | return ret; 74 | 75 | } 76 | 77 | } 78 | 79 | public RequestAuthorizationStatus authorizeRequest(HttpServletRequest request, String connectionName) throws Exception; 80 | 81 | } 82 | -------------------------------------------------------------------------------- /src/main/java/org/ojbc/mondrian/rest/SamlAssertionRequestAuthorizer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Unless explicitly acquired and licensed from Licensor under another license, the contents of 3 | * this file are subject to the Reciprocal Public License ("RPL") Version 1.5, or subsequent 4 | * versions as allowed by the RPL, and You may not copy or use this file in either source code 5 | * or executable form, except in compliance with the terms and conditions of the RPL 6 | * 7 | * All software distributed under the RPL is provided strictly on an "AS IS" basis, WITHOUT 8 | * WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, AND LICENSOR HEREBY DISCLAIMS ALL SUCH 9 | * WARRANTIES, INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 10 | * PARTICULAR PURPOSE, QUIET ENJOYMENT, OR NON-INFRINGEMENT. See the RPL for specific language 11 | * governing rights and limitations under the RPL. 12 | * 13 | * http://opensource.org/licenses/RPL-1.5 14 | * 15 | * Copyright 2012-2017 Open Justice Broker Consortium 16 | */ 17 | package org.ojbc.mondrian.rest; 18 | 19 | import java.util.Map; 20 | 21 | import javax.annotation.PostConstruct; 22 | 23 | import org.springframework.beans.factory.annotation.Value; 24 | import org.springframework.stereotype.Component; 25 | import org.w3c.dom.Document; 26 | 27 | import lombok.Setter; 28 | 29 | /** 30 | * RequestAuthorizer that uses information in a SAML assertion to authorize each request and determine that user's role for the connection in the query request. It determines the role 31 | * by looking up the token in a map in the config file. 32 | */ 33 | @Component 34 | public class SamlAssertionRequestAuthorizer extends AbstractSamlAssertionRequestAuthorizer { 35 | 36 | private Map> tokenRoleMappings; 37 | 38 | @Value("${samlAssertionRequestAuthorizerConfigFileName:saml-assertion-request-authorizer.json}") 39 | @Setter 40 | private String samlAssertionRequestAuthorizerConfigFileName; 41 | 42 | @PostConstruct 43 | public void init() throws Exception { 44 | tokenRoleMappings = RequestAuthorizer.AuthorizerUtil.convertRoleConnectionJsonToMaps(samlAssertionRequestAuthorizerConfigFileName); 45 | } 46 | 47 | @Override 48 | protected RequestAuthorizationStatus authorizeAssertion(String connectionName, Document assertion) { 49 | Map connectionMappings = getConnectionMappingsForAssertion(assertion); 50 | RequestAuthorizationStatus ret = new RequestAuthorizationStatus(); 51 | ret.authorized = false; 52 | if (connectionMappings != null) { 53 | String role = connectionMappings.get(connectionName); 54 | if (role != null) { 55 | ret.authorized = true; 56 | ret.mondrianRole = role; 57 | ret.token = getToken(assertion); 58 | if (role.equals(ALL_ACCESS_ROLE_NAME)) { 59 | ret.mondrianRole = null; 60 | } 61 | } else { 62 | ret.message = "Authentication failed for SAML assertion with token " + getToken(assertion) + " for connection " + connectionName; 63 | } 64 | } else { 65 | ret.message = "Authentication failed. No connection-role mappings found for assertion with token " + getToken(assertion); 66 | } 67 | return ret; 68 | } 69 | 70 | private Map getConnectionMappingsForAssertion(Document assertion) { 71 | String token = getToken(assertion); 72 | if (token != null) { 73 | return tokenRoleMappings.get(token); 74 | } 75 | return null; 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /src/main/java/org/ojbc/mondrian/rest/BearerTokenRequestAuthorizer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Unless explicitly acquired and licensed from Licensor under another license, the contents of 3 | * this file are subject to the Reciprocal Public License ("RPL") Version 1.5, or subsequent 4 | * versions as allowed by the RPL, and You may not copy or use this file in either source code 5 | * or executable form, except in compliance with the terms and conditions of the RPL 6 | * 7 | * All software distributed under the RPL is provided strictly on an "AS IS" basis, WITHOUT 8 | * WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, AND LICENSOR HEREBY DISCLAIMS ALL SUCH 9 | * WARRANTIES, INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 10 | * PARTICULAR PURPOSE, QUIET ENJOYMENT, OR NON-INFRINGEMENT. See the RPL for specific language 11 | * governing rights and limitations under the RPL. 12 | * 13 | * http://opensource.org/licenses/RPL-1.5 14 | * 15 | * Copyright 2012-2017 Open Justice Broker Consortium 16 | */ 17 | package org.ojbc.mondrian.rest; 18 | 19 | import java.util.Map; 20 | 21 | import javax.annotation.PostConstruct; 22 | import javax.servlet.http.HttpServletRequest; 23 | 24 | import org.springframework.beans.factory.annotation.Value; 25 | import org.springframework.stereotype.Component; 26 | 27 | import lombok.AccessLevel; 28 | import lombok.Getter; 29 | import lombok.Setter; 30 | 31 | /** 32 | * RequestAuthorizer that uses bearer token authentication on each request and determines that user's role for the connection in the query request. 33 | */ 34 | @Component 35 | public class BearerTokenRequestAuthorizer implements RequestAuthorizer { 36 | 37 | @Getter 38 | @Setter(AccessLevel.PACKAGE) 39 | private Map> tokenRoleMappings; 40 | 41 | @Value("${bearerTokenRequestAuthorizerConfigFileName:bearer-token-request-authorizer.json}") 42 | @Setter 43 | private String bearerTokenRequestAuthorizerConfigFileName; 44 | 45 | @PostConstruct 46 | public void init() throws Exception { 47 | tokenRoleMappings = RequestAuthorizer.AuthorizerUtil.convertRoleConnectionJsonToMaps(bearerTokenRequestAuthorizerConfigFileName); 48 | } 49 | 50 | @Override 51 | public RequestAuthorizationStatus authorizeRequest(HttpServletRequest request, String connectionName) { 52 | 53 | RequestAuthorizationStatus ret = new RequestAuthorizationStatus(); 54 | ret.authorized = false; 55 | 56 | String authHeader = request.getHeader("Authorization"); 57 | if (authHeader != null && authHeader.matches("^Bearer .+")) { 58 | String token = authHeader.replaceFirst("^Bearer (.+)", "$1"); 59 | ret.token = token; 60 | Map connectionMappings = tokenRoleMappings.get(token); 61 | if (connectionMappings != null) { 62 | if (connectionName != null) { 63 | String role = connectionMappings.get(connectionName); 64 | if (role != null) { 65 | ret.authorized = true; 66 | ret.mondrianRole = role; 67 | if (role.equals(ALL_ACCESS_ROLE_NAME)) { 68 | ret.mondrianRole = null; 69 | } 70 | } else { 71 | ret.message = "Authentication failed. Token " + token + " found in config but not mapped to any connections."; 72 | } 73 | } else { 74 | ret.message = "Authentication failed. Query request did not specify a connection."; 75 | } 76 | 77 | } else { 78 | ret.message = "Authentication failed. Token " + token + " not found in config."; 79 | } 80 | } else { 81 | ret.message = "Authentication failed, no bearer authentication header present in request."; 82 | } 83 | 84 | return ret; 85 | 86 | } 87 | 88 | } 89 | -------------------------------------------------------------------------------- /src/test/java/org/ojbc/mondrian/util/TestDatabaseAvailabilityTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Unless explicitly acquired and licensed from Licensor under another license, the contents of 3 | * this file are subject to the Reciprocal Public License ("RPL") Version 1.5, or subsequent 4 | * versions as allowed by the RPL, and You may not copy or use this file in either source code 5 | * or executable form, except in compliance with the terms and conditions of the RPL 6 | * 7 | * All software distributed under the RPL is provided strictly on an "AS IS" basis, WITHOUT 8 | * WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, AND LICENSOR HEREBY DISCLAIMS ALL SUCH 9 | * WARRANTIES, INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 10 | * PARTICULAR PURPOSE, QUIET ENJOYMENT, OR NON-INFRINGEMENT. See the RPL for specific language 11 | * governing rights and limitations under the RPL. 12 | * 13 | * http://opensource.org/licenses/RPL-1.5 14 | * 15 | * Copyright 2012-2017 Open Justice Broker Consortium 16 | */ 17 | package org.ojbc.mondrian.util; 18 | 19 | import static org.junit.jupiter.api.Assertions.assertEquals; 20 | import static org.junit.jupiter.api.Assertions.assertTrue; 21 | 22 | import java.sql.Connection; 23 | import java.sql.DatabaseMetaData; 24 | import java.sql.DriverManager; 25 | import java.sql.ResultSet; 26 | import java.sql.SQLException; 27 | import java.sql.Statement; 28 | 29 | import org.junit.jupiter.api.AfterEach; 30 | import org.junit.jupiter.api.BeforeEach; 31 | import org.junit.jupiter.api.Disabled; 32 | import org.junit.jupiter.api.Test; 33 | 34 | import lombok.extern.slf4j.Slf4j; 35 | 36 | @Slf4j 37 | public class TestDatabaseAvailabilityTest { 38 | 39 | private Connection connection; 40 | 41 | @BeforeEach 42 | public void setUp() throws Exception { 43 | connection = DatabaseUtils.getInstance().getTestDatabaseConnection(true); 44 | //dumpTableList(); 45 | } 46 | 47 | @AfterEach 48 | public void tearDown() throws Exception { 49 | connection.close(); 50 | } 51 | 52 | @Test 53 | public void test() throws Exception { 54 | Statement statement = connection.createStatement(); 55 | ResultSet resultSet = statement.executeQuery("select count(*) from F1"); 56 | assertTrue(resultSet.next()); 57 | int rowCount = resultSet.getInt(1); 58 | assertEquals(3, rowCount); 59 | resultSet = statement.executeQuery("select count(*) from D1"); 60 | assertTrue(resultSet.next()); 61 | rowCount = resultSet.getInt(1); 62 | assertEquals(2, rowCount); 63 | resultSet = statement.executeQuery("select * from D1"); 64 | } 65 | 66 | @Test 67 | public void testCase() throws Exception { 68 | Statement statement = connection.createStatement(); 69 | ResultSet resultSet = statement.executeQuery("select F3_value from F3"); 70 | assertTrue(resultSet.next()); 71 | resultSet = statement.executeQuery("select F3_VALUE from F3"); 72 | assertTrue(resultSet.next()); 73 | } 74 | 75 | @Test 76 | @Disabled // because it takes awhile for the database to load up 77 | public void testFoodmartAvailable() throws Exception { 78 | 79 | // verify that we can load up the hsqldb with foodmart in it 80 | Connection conn = DriverManager.getConnection("jdbc:hsqldb:res:foodmart;set schema \"foodmart\"", "FOODMART", "FOODMART"); 81 | Statement s = conn.createStatement(); 82 | s.executeQuery("select \"employee_id\" from \"employee\""); 83 | 84 | } 85 | 86 | void dumpTableList() throws SQLException { 87 | DatabaseMetaData dbmd = connection.getMetaData(); 88 | ResultSet rs = dbmd.getTables(null, null, "%", null); 89 | while (rs.next()) { 90 | log.info(rs.getString(1) + ", " + rs.getString(2) + ", " + rs.getString(3)); 91 | } 92 | } 93 | 94 | } 95 | -------------------------------------------------------------------------------- /src/test/java/org/ojbc/mondrian/rest/SamlAssertionRequestAuthorizerTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Unless explicitly acquired and licensed from Licensor under another license, the contents of 3 | * this file are subject to the Reciprocal Public License ("RPL") Version 1.5, or subsequent 4 | * versions as allowed by the RPL, and You may not copy or use this file in either source code 5 | * or executable form, except in compliance with the terms and conditions of the RPL 6 | * 7 | * All software distributed under the RPL is provided strictly on an "AS IS" basis, WITHOUT 8 | * WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, AND LICENSOR HEREBY DISCLAIMS ALL SUCH 9 | * WARRANTIES, INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 10 | * PARTICULAR PURPOSE, QUIET ENJOYMENT, OR NON-INFRINGEMENT. See the RPL for specific language 11 | * governing rights and limitations under the RPL. 12 | * 13 | * http://opensource.org/licenses/RPL-1.5 14 | * 15 | * Copyright 2012-2020 Open Justice Broker Consortium 16 | */ 17 | package org.ojbc.mondrian.rest; 18 | 19 | import static org.junit.jupiter.api.Assertions.assertEquals; 20 | import static org.junit.jupiter.api.Assertions.assertTrue; 21 | 22 | import java.io.InputStream; 23 | 24 | import javax.xml.parsers.DocumentBuilderFactory; 25 | import javax.xml.xpath.XPath; 26 | import javax.xml.xpath.XPathConstants; 27 | import javax.xml.xpath.XPathFactory; 28 | 29 | import org.junit.jupiter.api.BeforeEach; 30 | import org.junit.jupiter.api.Test; 31 | import org.ojbc.mondrian.rest.RequestAuthorizer.RequestAuthorizationStatus; 32 | import org.springframework.beans.factory.annotation.Autowired; 33 | import org.springframework.boot.test.context.SpringBootTest; 34 | import org.springframework.core.io.ClassPathResource; 35 | import org.springframework.test.context.TestPropertySource; 36 | import org.w3c.dom.Document; 37 | import org.w3c.dom.Element; 38 | 39 | @SpringBootTest 40 | @TestPropertySource(properties = { "samlAssertionTokenAttributeName=gfipm:2.0:user:FederationId", "samlTokenStrategyBeanName=attributeSamlTokenStrategy" }) 41 | public class SamlAssertionRequestAuthorizerTest { 42 | 43 | @Autowired 44 | private AbstractSamlAssertionRequestAuthorizer samlAssertionRequestAuthorizer; 45 | 46 | private Document transientNameIdAssertion; 47 | 48 | @BeforeEach 49 | public void beforeAll() throws Exception { 50 | InputStream assertionStream = new ClassPathResource("transient-name-id-assertion.xml").getInputStream(); 51 | DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); 52 | dbf.setNamespaceAware(true); 53 | transientNameIdAssertion = dbf.newDocumentBuilder().parse(assertionStream); 54 | } 55 | 56 | @Test 57 | public void testTransientNameIdAssertionSuccess() throws Exception { 58 | RequestAuthorizationStatus status = samlAssertionRequestAuthorizer.authorizeAssertion("test", transientNameIdAssertion); 59 | assertEquals(status.authorized, true); 60 | assertEquals(status.mondrianRole, "ROLE_ADMIN"); 61 | } 62 | 63 | @Test 64 | public void testTransientNameIdAssertionFailure() throws Exception { 65 | XPath xPath = XPathFactory.newInstance().newXPath(); 66 | xPath.setNamespaceContext(SamlUtils.SAML_NAMESPACE_CONTEXT); 67 | String expression = "/saml2:Assertion/saml2:AttributeStatement/saml2:Attribute[@Name='gfipm:2.0:user:FederationId']/saml2:AttributeValue"; 68 | Element valueElement = (Element) xPath.evaluate(expression, transientNameIdAssertion, XPathConstants.NODE); 69 | valueElement.setTextContent("STATE:AGENCY:IDP:nonexistent"); 70 | RequestAuthorizationStatus status = samlAssertionRequestAuthorizer.authorizeAssertion("test", transientNameIdAssertion); 71 | assertEquals(status.authorized, false); 72 | assertTrue(status.message.matches(".+No connection-role mappings found.+")); 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /src/main/java/org/ojbc/mondrian/SchemaWrapper.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Unless explicitly acquired and licensed from Licensor under another license, the contents of 3 | * this file are subject to the Reciprocal Public License ("RPL") Version 1.5, or subsequent 4 | * versions as allowed by the RPL, and You may not copy or use this file in either source code 5 | * or executable form, except in compliance with the terms and conditions of the RPL 6 | * 7 | * All software distributed under the RPL is provided strictly on an "AS IS" basis, WITHOUT 8 | * WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, AND LICENSOR HEREBY DISCLAIMS ALL SUCH 9 | * WARRANTIES, INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 10 | * PARTICULAR PURPOSE, QUIET ENJOYMENT, OR NON-INFRINGEMENT. See the RPL for specific language 11 | * governing rights and limitations under the RPL. 12 | * 13 | * http://opensource.org/licenses/RPL-1.5 14 | * 15 | * Copyright 2018 Open Justice Broker Consortium and Cascadia Analytics LLC 16 | */ 17 | package org.ojbc.mondrian; 18 | 19 | import java.io.Serializable; 20 | import java.util.ArrayList; 21 | import java.util.Collections; 22 | import java.util.Comparator; 23 | import java.util.List; 24 | 25 | import javax.xml.xpath.XPath; 26 | import javax.xml.xpath.XPathConstants; 27 | import javax.xml.xpath.XPathException; 28 | import javax.xml.xpath.XPathExpression; 29 | import javax.xml.xpath.XPathFactory; 30 | 31 | import org.olap4j.OlapException; 32 | import org.olap4j.metadata.Cube; 33 | import org.olap4j.metadata.Schema; 34 | import org.w3c.dom.Document; 35 | import org.w3c.dom.Element; 36 | import org.w3c.dom.NodeList; 37 | 38 | import lombok.EqualsAndHashCode; 39 | import lombok.Getter; 40 | import lombok.ToString; 41 | import lombok.extern.slf4j.Slf4j; 42 | 43 | /** 44 | * A wrapper around olap4j Schema objects, suitable for serialization via json. 45 | * 46 | */ 47 | @Getter 48 | @EqualsAndHashCode 49 | @ToString 50 | @Slf4j 51 | public class SchemaWrapper implements Serializable { 52 | 53 | private static final long serialVersionUID = 6324155925760637995L; 54 | 55 | private String name; 56 | private String connectionName; 57 | private List cubes; 58 | 59 | SchemaWrapper() { } 60 | 61 | public SchemaWrapper(Schema schema, String connectionName, Document xmlSchema) throws OlapException { 62 | this.name = schema.getName(); 63 | this.connectionName = connectionName; 64 | cubes = new ArrayList<>(); 65 | for (Cube cube : schema.getCubes()) { 66 | cubes.add(new CubeWrapper(cube, xmlSchema)); 67 | } 68 | List schemaCubeOrder = new ArrayList<>(); 69 | XPath xp = XPathFactory.newInstance().newXPath(); 70 | try { 71 | XPathExpression xpe = xp.compile("//Cube"); 72 | NodeList cubeNodeList = (NodeList) xpe.evaluate(xmlSchema, XPathConstants.NODESET); 73 | for (int i=0;i < cubeNodeList.getLength();i++) { 74 | schemaCubeOrder.add(((Element) cubeNodeList.item(i)).getAttribute("name")); 75 | } 76 | } catch (XPathException e) { 77 | throw new RuntimeException(e); 78 | } 79 | if (cubes.size() == schemaCubeOrder.size()) { 80 | cubes.sort(new Comparator() { 81 | @Override 82 | public int compare(CubeWrapper o1, CubeWrapper o2) { 83 | int pos1 = schemaCubeOrder.indexOf(o1.getName()); 84 | int pos2 = schemaCubeOrder.indexOf(o2.getName()); 85 | int ret = 0; 86 | if (pos1 > pos2) { 87 | ret = 1; 88 | } else { 89 | ret = -1; 90 | } 91 | return ret; 92 | } 93 | }); 94 | } else { 95 | log.warn("Cannot sort cubes as olap4j has different number of cubes than xml schema for connection " + connectionName); 96 | } 97 | } 98 | 99 | public List getCubes() { 100 | return Collections.unmodifiableList(cubes); 101 | } 102 | 103 | } 104 | -------------------------------------------------------------------------------- /src/main/resources/test.xml: -------------------------------------------------------------------------------- 1 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 |
31 | 32 | 33 | 34 | 35 | 36 | 37 |
38 | 39 | 40 | 41 | 42 | 43 |
44 | 45 | 46 | 47 | 48 | 49 |
50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 |
60 | 61 | 62 | 63 | 64 | 65 |
66 | 67 | 68 | 69 | 70 | 71 |
72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /src/test/java/org/ojbc/mondrian/MondrianConnectionFactoryTest.java: -------------------------------------------------------------------------------- 1 | package org.ojbc.mondrian; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | import static org.junit.jupiter.api.Assertions.assertFalse; 5 | import static org.junit.jupiter.api.Assertions.assertTrue; 6 | 7 | import java.io.IOException; 8 | import java.util.Collections; 9 | import java.util.List; 10 | import java.util.Map; 11 | 12 | import org.junit.jupiter.api.BeforeEach; 13 | import org.junit.jupiter.api.Disabled; 14 | import org.junit.jupiter.api.Test; 15 | import org.ojbc.mondrian.MondrianConnectionFactory.MondrianConnection; 16 | import org.springframework.core.io.Resource; 17 | import org.springframework.core.io.support.PathMatchingResourcePatternResolver; 18 | import org.springframework.core.io.support.ResourcePatternResolver; 19 | 20 | import com.fasterxml.jackson.databind.ObjectMapper; 21 | 22 | import lombok.extern.slf4j.Slf4j; 23 | 24 | @Slf4j 25 | public class MondrianConnectionFactoryTest { 26 | 27 | private MondrianConnectionFactory factory; 28 | 29 | @BeforeEach 30 | public void setUp() throws Exception { 31 | log.debug("setUp"); 32 | factory = new MondrianConnectionFactory(); 33 | factory.init(); 34 | } 35 | 36 | @Test 37 | public void testClasspathScan() throws IOException { 38 | ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); 39 | Resource[] resources = resolver.getResources("classpath*:*mondrian-connections.json"); 40 | assertEquals(2, resources.length); 41 | assertEquals("test-mondrian-connections.json", resources[0].getFilename()); 42 | assertEquals("mondrian-connections.json", resources[1].getFilename()); 43 | } 44 | 45 | @Test 46 | public void testMondrianConnectionFactory() throws Exception { 47 | 48 | List connectionCollections = factory.getConnectionCollections(); 49 | assertEquals(2, connectionCollections.size()); 50 | 51 | MondrianConnectionFactory.MondrianConnectionCollection connectionCollection = connectionCollections.get(0); 52 | assertTrue(connectionCollection.getSourceFilePath().endsWith("/mondrian-connections.json")); 53 | assertFalse(connectionCollection.getSourceFilePath().endsWith("/test-mondrian-connections.json")); 54 | Map connections = connectionCollection.getConnections(); 55 | assertEquals(2, connections.size()); 56 | assertTrue(connections.keySet().contains("test")); 57 | 58 | connectionCollection = connectionCollections.get(1); 59 | assertFalse(connectionCollection.getSourceFilePath().endsWith("/mondrian-connections.json")); 60 | assertTrue(connectionCollection.getSourceFilePath().endsWith("/test-mondrian-connections.json")); 61 | connections = connectionCollection.getConnections(); 62 | assertEquals(1, connections.size()); 63 | assertEquals(Collections.singleton("test"), connections.keySet()); 64 | 65 | connections = factory.getConnections(); 66 | assertEquals(2, connections.size()); 67 | assertTrue(connections.keySet().contains("test")); 68 | MondrianConnection testConnection = connections.get("test"); 69 | assertEquals("Test version of test connection", testConnection.getDescription()); 70 | 71 | // expect no exception here... 72 | testConnection.getOlap4jConnection(); 73 | 74 | } 75 | 76 | @Test 77 | @Disabled 78 | public void testJsonSerialization() throws Exception { 79 | // This test isn't really a test, just a convenient way to dump out some json when needed 80 | Map connections = factory.getConnections(); 81 | String json = new ObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(connections); 82 | log.info(json); 83 | } 84 | 85 | @Test 86 | public void testFoodMartConnection() throws Exception { 87 | 88 | Map connections = factory.getConnections(); 89 | MondrianConnection foodmartConnection = connections.get("foodmart"); 90 | 91 | // expect no exception here... 92 | foodmartConnection.getOlap4jConnection(); 93 | 94 | } 95 | 96 | } 97 | -------------------------------------------------------------------------------- /src/test/resources/transient-name-id-assertion.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | https://idp.ojbc.org/idp/shibboleth 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | ... 18 | 19 | 20 | 21 | ... 22 | 23 | 24 | 25 | ... 26 | 27 | 28 | 29 | 30 | TransientNameId 31 | 32 | 34 | 35 | 36 | 37 | 38 | https://sp.ojbc.org/shibboleth 39 | 40 | 41 | 42 | 43 | 44 | urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport 45 | 46 | 47 | 48 | 49 | STATE:AGENCY:IDP:user1 50 | 51 | 52 | testadmin@localhost 53 | 54 | 55 | ROLE_ADMIN 56 | 57 | 58 | Adam 59 | 60 | 61 | Administrator 62 | 63 | 64 | Administrator 65 | 66 | 67 | OJBC 68 | 69 | 70 | -------------------------------------------------------------------------------- /src/test/java/org/ojbc/mondrian/TestTidyCellSet.java: -------------------------------------------------------------------------------- 1 | package org.ojbc.mondrian; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | import static org.junit.jupiter.api.Assertions.assertNull; 5 | 6 | import java.util.HashMap; 7 | import java.util.List; 8 | import java.util.Map; 9 | 10 | import org.junit.jupiter.api.BeforeEach; 11 | import org.junit.jupiter.api.Test; 12 | import org.olap4j.CellSet; 13 | 14 | import lombok.extern.slf4j.Slf4j; 15 | 16 | @Slf4j 17 | public class TestTidyCellSet { 18 | 19 | private TidyCellSetWrapper tidyCellSet; 20 | 21 | @BeforeEach 22 | public void setUp() { 23 | log.debug("setUp"); 24 | tidyCellSet = new TidyCellSetWrapper(); 25 | } 26 | 27 | @Test 28 | public void testSingleAxisSingleDimension() { 29 | CellSet cellSet = TestCellSetFactory.getInstance().getSingleAxisSingleDimensionCellSet(); 30 | tidyCellSet.init(cellSet); 31 | List> rows = tidyCellSet.getValues(); 32 | assertEquals(1, rows.size()); 33 | assertEquals(1.0, rows.get(0).get("M1")); 34 | } 35 | 36 | @Test 37 | public void testDualAxisSingleDimensionCellSet() { 38 | CellSet cellSet = TestCellSetFactory.getInstance().getDualAxisSingleDimensionCellSet(); 39 | tidyCellSet.init(cellSet); 40 | List> rows = tidyCellSet.getValues(); 41 | assertEquals(2, rows.size()); 42 | assertEquals(1.0, rows.get(0).get("M1")); 43 | assertEquals("D1_V1", rows.get(0).get("[D1].[D1].[D1_V1]")); 44 | assertEquals(2.0, rows.get(1).get("M1")); 45 | assertEquals("D1_V2", rows.get(1).get("[D1].[D1].[D1_V2]")); 46 | tidyCellSet.init(cellSet, true, null); 47 | rows = tidyCellSet.getValues(); 48 | assertEquals(2, rows.size()); 49 | assertEquals(1.0, rows.get(0).get("M1")); 50 | assertEquals("D1_V1", rows.get(0).get("D1_V1")); 51 | assertEquals(2.0, rows.get(1).get("M1")); 52 | assertEquals("D1_V2", rows.get(1).get("D1_V2")); 53 | } 54 | 55 | @Test 56 | public void testDualAxisTwoDimensions() { 57 | CellSet cellSet = TestCellSetFactory.getInstance().getDualAxisTwoDimensionCellSet(); 58 | tidyCellSet.init(cellSet); 59 | List> rows = tidyCellSet.getValues(); 60 | assertEquals(6, rows.size()); 61 | assertEquals(1.0, rows.get(0).get("M1")); 62 | assertEquals("D1_V1", rows.get(0).get("[D1].[D1].[D1_V1]")); 63 | assertEquals("D2_V1", rows.get(0).get("[D2].[D2].[D2_V1]")); 64 | assertEquals(2.0, rows.get(1).get("M1")); 65 | assertEquals("D1_V1", rows.get(1).get("[D1].[D1].[D1_V1]")); 66 | assertEquals("D2_V2", rows.get(1).get("[D2].[D2].[D2_V2]")); 67 | assertEquals(10.0, rows.get(3).get("M1")); 68 | assertEquals("D1_V2", rows.get(3).get("[D1].[D1].[D1_V2]")); 69 | assertEquals("D2_V1", rows.get(3).get("[D2].[D2].[D2_V1]")); 70 | assertNull(rows.get(5).get("M1")); 71 | assertEquals("D1_V2", rows.get(5).get("[D1].[D1].[D1_V2]")); 72 | assertEquals("D2_V3", rows.get(5).get("[D2].[D2].[D2_V3]")); 73 | } 74 | 75 | @Test 76 | public void testDualAxisTwoDimensionsTwoMeasures() { 77 | CellSet cellSet = TestCellSetFactory.getInstance().getDualAxisTwoDimensionTwoMeasuresCellSet(); 78 | tidyCellSet.init(cellSet); 79 | List> rows = tidyCellSet.getValues(); 80 | assertEquals(6, rows.size()); 81 | assertEquals(1.0, rows.get(0).get("M1")); 82 | assertEquals(4.0, rows.get(0).get("M2")); 83 | assertEquals(2.0, rows.get(1).get("M1")); 84 | assertEquals(5.0, rows.get(1).get("M2")); 85 | assertEquals("D1_V1", rows.get(0).get("[D1].[D1].[D1_V1]")); 86 | assertEquals("D2_V1", rows.get(0).get("[D2].[D2].[D2_V1]")); 87 | assertEquals("D1_V1", rows.get(1).get("[D1].[D1].[D1_V1]")); 88 | assertEquals("D2_V2", rows.get(1).get("[D2].[D2].[D2_V2]")); 89 | tidyCellSet.init(cellSet, true, null); 90 | rows = tidyCellSet.getValues(); 91 | assertEquals(6, rows.size()); 92 | assertEquals(1.0, rows.get(0).get("M1")); 93 | assertEquals(4.0, rows.get(0).get("M2")); 94 | assertEquals(2.0, rows.get(1).get("M1")); 95 | assertEquals(5.0, rows.get(1).get("M2")); 96 | assertEquals("D1_V1", rows.get(0).get("D1_V1")); 97 | assertEquals("D2_V1", rows.get(0).get("D2_V1")); 98 | assertEquals("D1_V1", rows.get(1).get("D1_V1")); 99 | assertEquals("D2_V2", rows.get(1).get("D2_V2")); 100 | } 101 | 102 | @Test 103 | public void testLevelNameTranslation() { 104 | CellSet cellSet = TestCellSetFactory.getInstance().getDualAxisTwoDimensionTwoMeasuresCellSet(); 105 | Map levelNameTranslationMap = new HashMap<>(); 106 | levelNameTranslationMap.put("[D1].[D1].[D1_V1]", "foobar"); 107 | tidyCellSet.init(cellSet, true, levelNameTranslationMap); 108 | List> rows = tidyCellSet.getValues(); 109 | assertEquals(1.0, rows.get(0).get("M1")); 110 | assertEquals(4.0, rows.get(0).get("M2")); 111 | assertEquals(2.0, rows.get(1).get("M1")); 112 | assertEquals(5.0, rows.get(1).get("M2")); 113 | assertEquals("D1_V1", rows.get(0).get("foobar")); 114 | assertEquals("D2_V1", rows.get(0).get("D2_V1")); 115 | } 116 | 117 | } 118 | -------------------------------------------------------------------------------- /src/main/java/org/ojbc/mondrian/rest/AbstractSamlAssertionRequestAuthorizer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Unless explicitly acquired and licensed from Licensor under another license, the contents of 3 | * this file are subject to the Reciprocal Public License ("RPL") Version 1.5, or subsequent 4 | * versions as allowed by the RPL, and You may not copy or use this file in either source code 5 | * or executable form, except in compliance with the terms and conditions of the RPL 6 | * 7 | * All software distributed under the RPL is provided strictly on an "AS IS" basis, WITHOUT 8 | * WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, AND LICENSOR HEREBY DISCLAIMS ALL SUCH 9 | * WARRANTIES, INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 10 | * PARTICULAR PURPOSE, QUIET ENJOYMENT, OR NON-INFRINGEMENT. See the RPL for specific language 11 | * governing rights and limitations under the RPL. 12 | * 13 | * http://opensource.org/licenses/RPL-1.5 14 | * 15 | * Copyright 2012-2020 Open Justice Broker Consortium 16 | */ 17 | package org.ojbc.mondrian.rest; 18 | 19 | import java.io.InputStream; 20 | import java.net.URL; 21 | import java.net.URLConnection; 22 | import java.security.GeneralSecurityException; 23 | import java.security.cert.X509Certificate; 24 | 25 | import javax.annotation.Resource; 26 | import javax.net.ssl.HostnameVerifier; 27 | import javax.net.ssl.HttpsURLConnection; 28 | import javax.net.ssl.SSLContext; 29 | import javax.net.ssl.SSLSession; 30 | import javax.net.ssl.TrustManager; 31 | import javax.net.ssl.X509TrustManager; 32 | import javax.servlet.http.HttpServletRequest; 33 | import javax.xml.parsers.DocumentBuilderFactory; 34 | 35 | import org.w3c.dom.Document; 36 | 37 | /** 38 | * Abstract template method class for RequestAuthorizers that use SAML. 39 | * 40 | */ 41 | public abstract class AbstractSamlAssertionRequestAuthorizer implements RequestAuthorizer { 42 | 43 | private static final String SHIB_ASSERTION_KEY = "Shib-Assertion-01"; 44 | 45 | @Resource(name="${samlTokenStrategyBeanName:#{null}}") 46 | private SamlTokenStrategy samlTokenStrategy; 47 | 48 | @Override 49 | public final RequestAuthorizationStatus authorizeRequest(HttpServletRequest request, String connectionName) throws Exception { 50 | 51 | RequestAuthorizationStatus ret = new RequestAuthorizationStatus(); 52 | ret.authorized = false; 53 | 54 | fixCertificatePathError(); 55 | 56 | // Hard coded to pick up a single assertion...could loop through assertion headers if there will be more than one 57 | String assertionRetrievalURL = request.getHeader(SHIB_ASSERTION_KEY); 58 | 59 | if (assertionRetrievalURL != null) { 60 | URL url = new URL(assertionRetrievalURL); 61 | URLConnection con = url.openConnection(); 62 | InputStream is = con.getInputStream(); 63 | Document assertion = parseAssertion(is); 64 | if (connectionName == null) { 65 | ret.message = "Authentication failed. Query request did not specify a connection."; 66 | } else { 67 | ret = authorizeAssertion(connectionName, assertion); 68 | } 69 | } else { 70 | ret.message = "Authentication failed. No assertion found in request."; 71 | } 72 | 73 | return ret; 74 | 75 | } 76 | 77 | protected final String getToken(Document assertion) { 78 | return samlTokenStrategy.getToken(assertion); 79 | } 80 | 81 | /** 82 | * Implementers implement this method to determine authorization and mondrian role, given the supplied connection and SAML assertion 83 | * @param connectionName the name of the Mondrian connection 84 | * @param assertion the SAML assertion 85 | * @return status of authorization 86 | */ 87 | protected abstract RequestAuthorizationStatus authorizeAssertion(String connectionName, Document assertion); 88 | 89 | private void fixCertificatePathError() throws GeneralSecurityException { 90 | /* 91 | * fix for Exception in thread "main" javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: 92 | * sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target 93 | */ 94 | TrustManager[] trustAllCerts = new TrustManager[]{ 95 | new X509TrustManager() { 96 | public X509Certificate[] getAcceptedIssuers() { 97 | return null; 98 | } 99 | 100 | public void checkClientTrusted(X509Certificate[] certs, String authType) { 101 | } 102 | 103 | public void checkServerTrusted(X509Certificate[] certs, String authType) { 104 | } 105 | } 106 | }; 107 | SSLContext sc = SSLContext.getInstance("SSL"); 108 | sc.init(null, trustAllCerts, new java.security.SecureRandom()); 109 | HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory()); 110 | 111 | // Create all-trusting host name verifier 112 | HostnameVerifier allHostsValid = new HostnameVerifier() { 113 | @Override 114 | public boolean verify(String arg0, SSLSession arg1) { 115 | return true; 116 | } 117 | }; 118 | // Install the all-trusting host verifier 119 | HttpsURLConnection.setDefaultHostnameVerifier(allHostsValid); 120 | } 121 | 122 | private Document parseAssertion(InputStream is) throws Exception { 123 | DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); 124 | documentBuilderFactory.setNamespaceAware(true); 125 | return documentBuilderFactory.newDocumentBuilder().parse(is); 126 | } 127 | 128 | } -------------------------------------------------------------------------------- /src/test/java/org/ojbc/mondrian/rest/RequestAuthorizerIntegrationTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Unless explicitly acquired and licensed from Licensor under another license, the contents of 3 | * this file are subject to the Reciprocal Public License ("RPL") Version 1.5, or subsequent 4 | * versions as allowed by the RPL, and You may not copy or use this file in either source code 5 | * or executable form, except in compliance with the terms and conditions of the RPL 6 | * 7 | * All software distributed under the RPL is provided strictly on an "AS IS" basis, WITHOUT 8 | * WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, AND LICENSOR HEREBY DISCLAIMS ALL SUCH 9 | * WARRANTIES, INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 10 | * PARTICULAR PURPOSE, QUIET ENJOYMENT, OR NON-INFRINGEMENT. See the RPL for specific language 11 | * governing rights and limitations under the RPL. 12 | * 13 | * http://opensource.org/licenses/RPL-1.5 14 | * 15 | * Copyright 2012-2017 Open Justice Broker Consortium 16 | */ 17 | package org.ojbc.mondrian.rest; 18 | 19 | import static org.junit.jupiter.api.Assertions.assertEquals; 20 | import static org.junit.jupiter.api.Assertions.assertTrue; 21 | 22 | import java.net.URI; 23 | import java.util.HashMap; 24 | import java.util.HashSet; 25 | import java.util.Map; 26 | import java.util.Set; 27 | 28 | import org.junit.jupiter.api.Test; 29 | import org.ojbc.mondrian.CellSetWrapper; 30 | import org.ojbc.mondrian.CubeWrapper; 31 | import org.ojbc.mondrian.SchemaWrapper; 32 | import org.springframework.boot.test.context.SpringBootTest; 33 | import org.springframework.boot.web.server.LocalServerPort; 34 | import org.springframework.core.ParameterizedTypeReference; 35 | import org.springframework.http.HttpEntity; 36 | import org.springframework.http.HttpHeaders; 37 | import org.springframework.http.HttpMethod; 38 | import org.springframework.http.ResponseEntity; 39 | import org.springframework.test.context.TestPropertySource; 40 | 41 | @TestPropertySource(properties = { "requestAuthorizerBeanName=bearerTokenRequestAuthorizer" }) 42 | @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) 43 | public class RequestAuthorizerIntegrationTest extends AbstractMondrianRestControllerTest { 44 | 45 | @LocalServerPort 46 | private String port; 47 | 48 | @Test 49 | public void testAccess() throws Exception { 50 | 51 | Map headerMap = new HashMap<>(); 52 | headerMap.put("Authorization", "Bearer TOKEN1"); 53 | 54 | HttpEntity requestEntity = buildQueryRequestEntity("test", "select {[Measures].[F2_M1]} on columns from Test_F2", headerMap); 55 | 56 | ResponseEntity response = restTemplate.postForEntity(new URI("http://localhost:" + port + "/query"), requestEntity, CellSetWrapper.class); 57 | assertEquals(200, response.getStatusCode().value()); 58 | 59 | headerMap.put("Authorization", "Bearer TOKEN2"); 60 | requestEntity = buildQueryRequestEntity("test", "select {[Measures].[F2_M1]} on columns from Test_F2", headerMap); 61 | 62 | response = restTemplate.postForEntity(new URI("http://localhost:" + port + "/query"), requestEntity, CellSetWrapper.class); 63 | assertEquals(200, response.getStatusCode().value()); 64 | 65 | } 66 | 67 | @Test 68 | public void testMetadata() throws Exception { 69 | 70 | HttpHeaders headers = new HttpHeaders(); 71 | headers.set("Authorization", "Bearer TOKEN2"); 72 | 73 | HttpEntity entity = new HttpEntity<>(headers); 74 | 75 | ResponseEntity response = restTemplate.exchange("http://localhost:" + port + "/getMetadata?connectionName=test", HttpMethod.GET, entity, SchemaWrapper.class); 76 | 77 | assertEquals(200, response.getStatusCode().value()); 78 | 79 | Set cubeNames = new HashSet<>(); 80 | for (CubeWrapper cw : response.getBody().getCubes()) { 81 | cubeNames.add(cw.getName()); 82 | } 83 | 84 | assertEquals(4, cubeNames.size()); 85 | cubeNames.remove("Test_F1"); 86 | cubeNames.remove("Test_F1_Secure"); 87 | cubeNames.remove("Test_F2"); 88 | cubeNames.remove("Test_F3"); 89 | assertEquals(0, cubeNames.size()); 90 | 91 | } 92 | 93 | @Test 94 | public void testDeniedAccess() throws Exception { 95 | 96 | Map headerMap = new HashMap<>(); 97 | headerMap.put("Authorization", "Bearer TOKEN2"); 98 | 99 | HttpEntity requestEntity = buildQueryRequestEntity("test", "select {[Measures].[F2_M1]} on columns from Test_F2_Secure", headerMap); 100 | 101 | ParameterizedTypeReference> responseType = new ParameterizedTypeReference>() {}; 102 | 103 | requestEntity = buildQueryRequestEntity("test", "select {[Measures].[F2_M1]} on columns from Test_F2_Secure", headerMap); 104 | ResponseEntity> errorResponse = restTemplate.exchange(new URI("http://localhost:" + port + "/query"), HttpMethod.POST, requestEntity, responseType); 105 | assertEquals(500, errorResponse.getStatusCode().value()); 106 | 107 | Map errorMap = errorResponse.getBody(); 108 | 109 | String rootCauseReason = errorMap.get("rootCauseReason"); 110 | assertTrue(rootCauseReason.matches(".+cube 'Test_F2_Secure' not found")); 111 | 112 | } 113 | 114 | @Test 115 | public void testForbidden() throws Exception { 116 | 117 | Map headerMap = new HashMap<>(); 118 | headerMap.put("Authorization", "Bearer TOKENDOESNTEXIST"); 119 | 120 | HttpEntity requestEntity = buildQueryRequestEntity("test", "select {[Measures].[F2_M1]} on columns from Test_Secure", headerMap); 121 | 122 | ParameterizedTypeReference> responseType = new ParameterizedTypeReference>() {}; 123 | 124 | requestEntity = buildQueryRequestEntity("test", "select {[Measures].[F2_M1]} on columns from Test_Secure", headerMap); 125 | ResponseEntity> errorResponse = restTemplate.exchange(new URI("http://localhost:" + port + "/query"), HttpMethod.POST, requestEntity, responseType); 126 | assertEquals(403, errorResponse.getStatusCode().value()); 127 | 128 | } 129 | 130 | } 131 | -------------------------------------------------------------------------------- /src/test/java/org/ojbc/mondrian/rest/RequestAuthorizerTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Unless explicitly acquired and licensed from Licensor under another license, the contents of 3 | * this file are subject to the Reciprocal Public License ("RPL") Version 1.5, or subsequent 4 | * versions as allowed by the RPL, and You may not copy or use this file in either source code 5 | * or executable form, except in compliance with the terms and conditions of the RPL 6 | * 7 | * All software distributed under the RPL is provided strictly on an "AS IS" basis, WITHOUT 8 | * WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, AND LICENSOR HEREBY DISCLAIMS ALL SUCH 9 | * WARRANTIES, INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 10 | * PARTICULAR PURPOSE, QUIET ENJOYMENT, OR NON-INFRINGEMENT. See the RPL for specific language 11 | * governing rights and limitations under the RPL. 12 | * 13 | * http://opensource.org/licenses/RPL-1.5 14 | * 15 | * Copyright 2012-2017 Open Justice Broker Consortium 16 | */ 17 | package org.ojbc.mondrian.rest; 18 | 19 | import static org.junit.jupiter.api.Assertions.assertEquals; 20 | import static org.junit.jupiter.api.Assertions.assertTrue; 21 | 22 | import java.net.URI; 23 | import java.util.HashMap; 24 | import java.util.Map; 25 | 26 | import org.junit.jupiter.api.Test; 27 | import org.ojbc.mondrian.CellSetWrapper; 28 | import org.springframework.boot.test.context.SpringBootTest; 29 | import org.springframework.boot.web.server.LocalServerPort; 30 | import org.springframework.core.ParameterizedTypeReference; 31 | import org.springframework.http.HttpEntity; 32 | import org.springframework.http.HttpMethod; 33 | import org.springframework.http.ResponseEntity; 34 | import org.springframework.test.context.TestPropertySource; 35 | 36 | @TestPropertySource(properties = { "requestAuthorizerBeanName=requestAuthorizerTestAuthorizer" }) 37 | @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) 38 | public class RequestAuthorizerTest extends AbstractMondrianRestControllerTest { 39 | 40 | @LocalServerPort 41 | private String port; 42 | 43 | @Test 44 | public void testUnauthenticatedAccess() throws Exception { 45 | HttpEntity requestEntity = buildQueryRequestEntity("test", "select {[Measures].[F1_M1]} on columns from Test"); 46 | ResponseEntity response = restTemplate.postForEntity(new URI("http://localhost:" + port + "/query"), requestEntity, CellSetWrapper.class); 47 | // note: no user header... 48 | assertEquals(403, response.getStatusCode().value()); 49 | } 50 | 51 | @Test 52 | public void testUnlimitedAccess() throws Exception { 53 | 54 | Map headerMap = new HashMap<>(); 55 | headerMap.put(TestRequestAuthorizer.TEST_REQUEST_AUTHORIZER_USER_HEADER_NAME, TestRequestAuthorizer.UNLIMITED_USER_HEADER_VALUE); 56 | 57 | HttpEntity requestEntity = buildQueryRequestEntity("test", "select {[Measures].[F2_M1]} on columns from Test_F2", headerMap); 58 | 59 | ResponseEntity response = restTemplate.postForEntity(new URI("http://localhost:" + port + "/query"), requestEntity, CellSetWrapper.class); 60 | assertEquals(200, response.getStatusCode().value()); 61 | CellSetWrapper csw = response.getBody(); 62 | assertEquals(1, csw.getCellWrappers().size()); 63 | 64 | requestEntity = buildQueryRequestEntity("test", "select {[Measures].[F2_M1]} on columns from Test_F2_Secure", headerMap); 65 | response = restTemplate.postForEntity(new URI("http://localhost:" + port + "/query"), requestEntity, CellSetWrapper.class); 66 | assertEquals(200, response.getStatusCode().value()); 67 | csw = response.getBody(); 68 | assertEquals(1, csw.getCellWrappers().size()); 69 | 70 | requestEntity = buildQueryRequestEntity("test", "select {[Measures].[F1_M1]} on columns from Test_F1", headerMap); 71 | response = restTemplate.postForEntity(new URI("http://localhost:" + port + "/query"), requestEntity, CellSetWrapper.class); 72 | assertEquals(200, response.getStatusCode().value()); 73 | csw = response.getBody(); 74 | assertEquals(1, csw.getCellWrappers().size()); 75 | 76 | requestEntity = buildQueryRequestEntity("test", "select {[Measures].[F1_M1]} on columns from Test_F1_Secure", headerMap); 77 | response = restTemplate.postForEntity(new URI("http://localhost:" + port + "/query"), requestEntity, CellSetWrapper.class); 78 | assertEquals(200, response.getStatusCode().value()); 79 | csw = response.getBody(); 80 | assertEquals(1, csw.getCellWrappers().size()); 81 | 82 | } 83 | 84 | @Test 85 | public void testRestrictedAccess() throws Exception { 86 | 87 | Map headerMap = new HashMap<>(); 88 | headerMap.put(TestRequestAuthorizer.TEST_REQUEST_AUTHORIZER_USER_HEADER_NAME, TestRequestAuthorizer.RESTRICTED_USER_HEADER_VALUE); 89 | 90 | HttpEntity requestEntity = buildQueryRequestEntity("test", "select {[Measures].[F1_M1]} on columns from Test_F1", headerMap); 91 | 92 | ResponseEntity response = restTemplate.postForEntity(new URI("http://localhost:" + port + "/query"), requestEntity, CellSetWrapper.class); 93 | assertEquals(200, response.getStatusCode().value()); 94 | CellSetWrapper csw = response.getBody(); 95 | assertEquals(1, csw.getCellWrappers().size()); 96 | 97 | requestEntity = buildQueryRequestEntity("test", "select {[Measures].[F1_M1]} on columns from Test_F1_Secure", headerMap); 98 | response = restTemplate.postForEntity(new URI("http://localhost:" + port + "/query"), requestEntity, CellSetWrapper.class); 99 | assertEquals(200, response.getStatusCode().value()); 100 | csw = response.getBody(); 101 | assertEquals(1, csw.getCellWrappers().size()); 102 | 103 | requestEntity = buildQueryRequestEntity("test", "select {[Measures].[F2_M1]} on columns from Test_F2", headerMap); 104 | response = restTemplate.postForEntity(new URI("http://localhost:" + port + "/query"), requestEntity, CellSetWrapper.class); 105 | assertEquals(200, response.getStatusCode().value()); 106 | csw = response.getBody(); 107 | assertEquals(1, csw.getCellWrappers().size()); 108 | 109 | ParameterizedTypeReference> responseType = new ParameterizedTypeReference>() {}; 110 | 111 | requestEntity = buildQueryRequestEntity("test", "select {[Measures].[F2_M1]} on columns from Test_F1_Secure", headerMap); 112 | ResponseEntity> errorResponse = restTemplate.exchange(new URI("http://localhost:" + port + "/query"), HttpMethod.POST, requestEntity, responseType); 113 | assertEquals(500, errorResponse.getStatusCode().value()); 114 | 115 | Map errorMap = errorResponse.getBody(); 116 | 117 | String rootCauseReason = errorMap.get("rootCauseReason"); 118 | assertTrue(rootCauseReason.matches(".+F2_M1.+not found in cube.+Test_F1_Secure.+")); 119 | 120 | } 121 | 122 | } 123 | -------------------------------------------------------------------------------- /src/main/java/org/ojbc/mondrian/TidyCellSetWrapper.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Unless explicitly acquired and licensed from Licensor under another license, the contents of 3 | * this file are subject to the Reciprocal Public License ("RPL") Version 1.5, or subsequent 4 | * versions as allowed by the RPL, and You may not copy or use this file in either source code 5 | * or executable form, except in compliance with the terms and conditions of the RPL 6 | * 7 | * All software distributed under the RPL is provided strictly on an "AS IS" basis, WITHOUT 8 | * WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, AND LICENSOR HEREBY DISCLAIMS ALL SUCH 9 | * WARRANTIES, INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 10 | * PARTICULAR PURPOSE, QUIET ENJOYMENT, OR NON-INFRINGEMENT. See the RPL for specific language 11 | * governing rights and limitations under the RPL. 12 | * 13 | * http://opensource.org/licenses/RPL-1.5 14 | * 15 | * Copyright 2012-2017 Open Justice Broker Consortium 16 | */ 17 | package org.ojbc.mondrian; 18 | 19 | import java.util.ArrayList; 20 | import java.util.Collections; 21 | import java.util.Comparator; 22 | import java.util.HashMap; 23 | import java.util.List; 24 | import java.util.Map; 25 | import java.util.stream.Collectors; 26 | 27 | import org.olap4j.Cell; 28 | import org.olap4j.CellSet; 29 | import org.olap4j.CellSetAxis; 30 | import org.olap4j.Position; 31 | import org.olap4j.metadata.Level; 32 | import org.olap4j.metadata.Member; 33 | import org.olap4j.metadata.Member.Type; 34 | 35 | import lombok.extern.slf4j.Slf4j; 36 | 37 | /** 38 | * Wrapper object for tidied cell sets. 39 | * 40 | */ 41 | @Slf4j 42 | public class TidyCellSetWrapper implements CellSetWrapperType { 43 | 44 | private static final String MEASURES_LEVEL_UNIQUE_NAME = "[Measures].[MeasuresLevel]"; 45 | private static final String HASH_KEY = ".H"; 46 | private static final String ORDER_KEY = ".O"; 47 | 48 | private List> values = new ArrayList<>(); 49 | 50 | /** 51 | * Initialize this wrapper 52 | * @param cellSet the olap4j cell set to wrap 53 | * @param simplifyNames whether to simplify names or not (i.e., remove higher dimension qualifiers) 54 | * @param dimensionNameTranslationMap a map to use in creating custom names from the original names in the cell set 55 | */ 56 | public void init(CellSet cellSet, boolean simplifyNames, Map dimensionNameTranslationMap) { 57 | 58 | log.debug("Start of init"); 59 | 60 | List> positionIntersectionList = buildPositionIntersectionList(cellSet); 61 | values = reducePositionIntersectionList(positionIntersectionList); 62 | 63 | if (dimensionNameTranslationMap == null) { 64 | dimensionNameTranslationMap = new HashMap<>(); 65 | } 66 | 67 | if (simplifyNames) { 68 | List> newValues = new ArrayList<>(); 69 | for (Map rowMap : values) { 70 | Map translatedMap = new HashMap<>(); 71 | for (String key : rowMap.keySet()) { 72 | String value = dimensionNameTranslationMap.get(key); 73 | if (value == null) { 74 | value = getLevelNameForUniqueName(cellSet, key); 75 | } 76 | translatedMap.put(value, rowMap.get(key)); 77 | } 78 | newValues.add(translatedMap); 79 | } 80 | values = newValues; 81 | } 82 | 83 | } 84 | 85 | private String getLevelNameForUniqueName(CellSet cellSet, String levelUniqueName) { 86 | for (CellSetAxis axis : cellSet.getAxes()) { 87 | for (Position position : axis.getPositions()) { 88 | for (Member member : position.getMembers()) { 89 | Member m = member; 90 | while (m != null) { 91 | if (m.getMemberType() != Type.ALL) { 92 | Level level = m.getLevel(); 93 | if (level.getUniqueName().equals(levelUniqueName)) { 94 | return level.getName(); 95 | } 96 | } 97 | m = m.getParentMember(); 98 | } 99 | } 100 | } 101 | } 102 | return levelUniqueName; 103 | } 104 | 105 | public void init(CellSet cellSet) { 106 | init(cellSet, false, null); 107 | } 108 | 109 | private List> reducePositionIntersectionList(List> positionIntersectionList) { 110 | 111 | Map> reducedMap = new HashMap<>(); 112 | 113 | positionIntersectionList.forEach(map -> { 114 | Object hashKey = map.get(HASH_KEY); 115 | if (reducedMap.containsKey(hashKey)) { 116 | String measureName = (String) map.get(MEASURES_LEVEL_UNIQUE_NAME); 117 | reducedMap.get(hashKey).put(measureName, map.get(measureName)); 118 | } else { 119 | map.remove(MEASURES_LEVEL_UNIQUE_NAME); 120 | reducedMap.put(hashKey, map); 121 | } 122 | }); 123 | 124 | List> ret = reducedMap.values().stream().collect(Collectors.toList()); 125 | ret.sort(new Comparator>() { 126 | @SuppressWarnings("unchecked") 127 | @Override 128 | public int compare(Map o1, Map o2) { 129 | return ((Comparable) o1.get(ORDER_KEY)).compareTo((Integer) o2.get(ORDER_KEY)); 130 | } 131 | }); 132 | 133 | ret.forEach(row -> { 134 | row.remove(HASH_KEY); 135 | row.remove(ORDER_KEY); 136 | }); 137 | 138 | return ret; 139 | 140 | } 141 | 142 | private List> buildPositionIntersectionList(CellSet cellSet) { 143 | 144 | List> ret = new ArrayList<>(); 145 | 146 | List> positionLists = cellSet.getAxes().stream().map(axis -> axis.getPositions()).collect(Collectors.toList()); 147 | positionLists = MondrianUtils.permuteLists(positionLists); 148 | 149 | int valueIndex = 0; 150 | for (List positionList : positionLists) { 151 | 152 | Cell cell = cellSet.getCell(positionList.toArray(new Position[0])); 153 | Map map = new HashMap<>(); 154 | 155 | for (Position position : positionList) { 156 | List members = position.getMembers(); 157 | for (Member member : members) { 158 | map.putAll(getHierarchyMap(member)); 159 | } 160 | } 161 | 162 | String measureValue = (String) map.get(MEASURES_LEVEL_UNIQUE_NAME); 163 | map.remove(MEASURES_LEVEL_UNIQUE_NAME); 164 | map.put(HASH_KEY, map.hashCode()); 165 | map.put(MEASURES_LEVEL_UNIQUE_NAME, measureValue); 166 | 167 | map.put(measureValue, cell.getValue()); 168 | map.put(ORDER_KEY, valueIndex++); 169 | ret.add(map); 170 | 171 | } 172 | 173 | return ret; 174 | 175 | } 176 | 177 | private Map getHierarchyMap(Member member) { 178 | Map ret = new HashMap<>(); 179 | Member m = member; 180 | while (m != null) { 181 | if (m.getMemberType() != Type.ALL) { 182 | String uniqueName = m.getLevel().getUniqueName(); 183 | String value = m.getName(); 184 | ret.put(uniqueName, value); 185 | } 186 | m = m.getParentMember(); 187 | } 188 | return ret; 189 | } 190 | 191 | public List> getValues() { 192 | return Collections.unmodifiableList(values); 193 | } 194 | 195 | } 196 | -------------------------------------------------------------------------------- /docker/no-auth/files/catalina.properties: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one or more 2 | # contributor license agreements. See the NOTICE file distributed with 3 | # this work for additional information regarding copyright ownership. 4 | # The ASF licenses this file to You under the Apache License, Version 2.0 5 | # (the "License"); you may not use this file except in compliance with 6 | # the License. You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | # 17 | # List of comma-separated packages that start with or equal this string 18 | # will cause a security exception to be thrown when 19 | # passed to checkPackageAccess unless the 20 | # corresponding RuntimePermission ("accessClassInPackage."+package) has 21 | # been granted. 22 | package.access=sun.,org.apache.catalina.,org.apache.coyote.,org.apache.jasper.,org.apache.tomcat. 23 | # 24 | # List of comma-separated packages that start with or equal this string 25 | # will cause a security exception to be thrown when 26 | # passed to checkPackageDefinition unless the 27 | # corresponding RuntimePermission ("defineClassInPackage."+package) has 28 | # been granted. 29 | # 30 | # by default, no packages are restricted for definition, and none of 31 | # the class loaders supplied with the JDK call checkPackageDefinition. 32 | # 33 | package.definition=sun.,java.,org.apache.catalina.,org.apache.coyote.,\ 34 | org.apache.jasper.,org.apache.naming.,org.apache.tomcat. 35 | 36 | # 37 | # 38 | # List of comma-separated paths defining the contents of the "common" 39 | # classloader. Prefixes should be used to define what is the repository type. 40 | # Path may be relative to the CATALINA_HOME or CATALINA_BASE path or absolute. 41 | # If left as blank,the JVM system loader will be used as Catalina's "common" 42 | # loader. 43 | # Examples: 44 | # "foo": Add this folder as a class repository 45 | # "foo/*.jar": Add all the JARs of the specified folder as class 46 | # repositories 47 | # "foo/bar.jar": Add bar.jar as a class repository 48 | # 49 | # Note: Values are enclosed in double quotes ("...") in case either the 50 | # ${catalina.base} path or the ${catalina.home} path contains a comma. 51 | # Because double quotes are used for quoting, the double quote character 52 | # may not appear in a path. 53 | common.loader="${catalina.base}/lib","${catalina.base}/lib/*.jar","${catalina.home}/lib","${catalina.home}/lib/*.jar" 54 | 55 | # 56 | # List of comma-separated paths defining the contents of the "server" 57 | # classloader. Prefixes should be used to define what is the repository type. 58 | # Path may be relative to the CATALINA_HOME or CATALINA_BASE path or absolute. 59 | # If left as blank, the "common" loader will be used as Catalina's "server" 60 | # loader. 61 | # Examples: 62 | # "foo": Add this folder as a class repository 63 | # "foo/*.jar": Add all the JARs of the specified folder as class 64 | # repositories 65 | # "foo/bar.jar": Add bar.jar as a class repository 66 | # 67 | # Note: Values may be enclosed in double quotes ("...") in case either the 68 | # ${catalina.base} path or the ${catalina.home} path contains a comma. 69 | # Because double quotes are used for quoting, the double quote character 70 | # may not appear in a path. 71 | server.loader= 72 | 73 | # 74 | # List of comma-separated paths defining the contents of the "shared" 75 | # classloader. Prefixes should be used to define what is the repository type. 76 | # Path may be relative to the CATALINA_BASE path or absolute. If left as blank, 77 | # the "common" loader will be used as Catalina's "shared" loader. 78 | # Examples: 79 | # "foo": Add this folder as a class repository 80 | # "foo/*.jar": Add all the JARs of the specified folder as class 81 | # repositories 82 | # "foo/bar.jar": Add bar.jar as a class repository 83 | # Please note that for single jars, e.g. bar.jar, you need the URL form 84 | # starting with file:. 85 | # 86 | # Note: Values may be enclosed in double quotes ("...") in case either the 87 | # ${catalina.base} path or the ${catalina.home} path contains a comma. 88 | # Because double quotes are used for quoting, the double quote character 89 | # may not appear in a path. 90 | shared.loader=/usr/local/tomcat/shared,/usr/local/tomcat/shared/*.jar 91 | 92 | # Default list of JAR files that should not be scanned using the JarScanner 93 | # functionality. This is typically used to scan JARs for configuration 94 | # information. JARs that do not contain such information may be excluded from 95 | # the scan to speed up the scanning process. This is the default list. JARs on 96 | # this list are excluded from all scans. The list must be a comma separated list 97 | # of JAR file names. 98 | # The list of JARs to skip may be over-ridden at a Context level for individual 99 | # scan types by configuring a JarScanner with a nested JarScanFilter. 100 | # The JARs listed below include: 101 | # - Tomcat Bootstrap JARs 102 | # - Tomcat API JARs 103 | # - Catalina JARs 104 | # - Jasper JARs 105 | # - Tomcat JARs 106 | # - Common non-Tomcat JARs 107 | # - Test JARs (JUnit, Cobertura and dependencies) 108 | tomcat.util.scan.StandardJarScanFilter.jarsToSkip=\ 109 | bootstrap.jar,commons-daemon.jar,tomcat-juli.jar,\ 110 | annotations-api.jar,el-api.jar,jsp-api.jar,servlet-api.jar,websocket-api.jar,\ 111 | catalina.jar,catalina-ant.jar,catalina-ha.jar,catalina-storeconfig.jar,\ 112 | catalina-tribes.jar,\ 113 | jasper.jar,jasper-el.jar,ecj-*.jar,\ 114 | tomcat-api.jar,tomcat-util.jar,tomcat-util-scan.jar,tomcat-coyote.jar,\ 115 | tomcat-dbcp.jar,tomcat-jni.jar,tomcat-websocket.jar,\ 116 | tomcat-i18n-en.jar,tomcat-i18n-es.jar,tomcat-i18n-fr.jar,tomcat-i18n-ja.jar,\ 117 | tomcat-juli-adapters.jar,catalina-jmx-remote.jar,catalina-ws.jar,\ 118 | tomcat-jdbc.jar,\ 119 | tools.jar,\ 120 | commons-beanutils*.jar,commons-codec*.jar,commons-collections*.jar,\ 121 | commons-dbcp*.jar,commons-digester*.jar,commons-fileupload*.jar,\ 122 | commons-httpclient*.jar,commons-io*.jar,commons-lang*.jar,commons-logging*.jar,\ 123 | commons-math*.jar,commons-pool*.jar,\ 124 | jstl.jar,taglibs-standard-spec-*.jar,\ 125 | geronimo-spec-jaxrpc*.jar,wsdl4j*.jar,\ 126 | ant.jar,ant-junit*.jar,aspectj*.jar,jmx.jar,h2*.jar,hibernate*.jar,httpclient*.jar,\ 127 | jmx-tools.jar,jta*.jar,log4j*.jar,mail*.jar,slf4j*.jar,\ 128 | xercesImpl.jar,xmlParserAPIs.jar,xml-apis.jar,\ 129 | junit.jar,junit-*.jar,ant-launcher.jar,\ 130 | cobertura-*.jar,asm-*.jar,dom4j-*.jar,icu4j-*.jar,jaxen-*.jar,jdom-*.jar,\ 131 | jetty-*.jar,oro-*.jar,servlet-api-*.jar,tagsoup-*.jar,xmlParserAPIs-*.jar,\ 132 | xom-*.jar 133 | 134 | # Default list of JAR files that should be scanned that overrides the default 135 | # jarsToSkip list above. This is typically used to include a specific JAR that 136 | # has been excluded by a broad file name pattern in the jarsToSkip list. 137 | # The list of JARs to scan may be over-ridden at a Context level for individual 138 | # scan types by configuring a JarScanner with a nested JarScanFilter. 139 | tomcat.util.scan.StandardJarScanFilter.jarsToScan=\ 140 | log4j-web*.jar,log4j-taglib*.jar,log4javascript*.jar,slf4j-taglib*.jar 141 | 142 | # String cache configuration. 143 | tomcat.util.buf.StringCache.byte.enabled=true 144 | #tomcat.util.buf.StringCache.char.enabled=true 145 | #tomcat.util.buf.StringCache.trainThreshold=500000 146 | #tomcat.util.buf.StringCache.cacheSize=5000 147 | 148 | # Allow for changes to HTTP request validation 149 | # WARNING: Using this option will expose the server to CVE-2016-6816 150 | #tomcat.util.http.parser.HttpParser.requestTargetAllow=| 151 | -------------------------------------------------------------------------------- /docker/bearer-auth/files/catalina.properties: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one or more 2 | # contributor license agreements. See the NOTICE file distributed with 3 | # this work for additional information regarding copyright ownership. 4 | # The ASF licenses this file to You under the Apache License, Version 2.0 5 | # (the "License"); you may not use this file except in compliance with 6 | # the License. You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | # 17 | # List of comma-separated packages that start with or equal this string 18 | # will cause a security exception to be thrown when 19 | # passed to checkPackageAccess unless the 20 | # corresponding RuntimePermission ("accessClassInPackage."+package) has 21 | # been granted. 22 | package.access=sun.,org.apache.catalina.,org.apache.coyote.,org.apache.jasper.,org.apache.tomcat. 23 | # 24 | # List of comma-separated packages that start with or equal this string 25 | # will cause a security exception to be thrown when 26 | # passed to checkPackageDefinition unless the 27 | # corresponding RuntimePermission ("defineClassInPackage."+package) has 28 | # been granted. 29 | # 30 | # by default, no packages are restricted for definition, and none of 31 | # the class loaders supplied with the JDK call checkPackageDefinition. 32 | # 33 | package.definition=sun.,java.,org.apache.catalina.,org.apache.coyote.,\ 34 | org.apache.jasper.,org.apache.naming.,org.apache.tomcat. 35 | 36 | # 37 | # 38 | # List of comma-separated paths defining the contents of the "common" 39 | # classloader. Prefixes should be used to define what is the repository type. 40 | # Path may be relative to the CATALINA_HOME or CATALINA_BASE path or absolute. 41 | # If left as blank,the JVM system loader will be used as Catalina's "common" 42 | # loader. 43 | # Examples: 44 | # "foo": Add this folder as a class repository 45 | # "foo/*.jar": Add all the JARs of the specified folder as class 46 | # repositories 47 | # "foo/bar.jar": Add bar.jar as a class repository 48 | # 49 | # Note: Values are enclosed in double quotes ("...") in case either the 50 | # ${catalina.base} path or the ${catalina.home} path contains a comma. 51 | # Because double quotes are used for quoting, the double quote character 52 | # may not appear in a path. 53 | common.loader="${catalina.base}/lib","${catalina.base}/lib/*.jar","${catalina.home}/lib","${catalina.home}/lib/*.jar" 54 | 55 | # 56 | # List of comma-separated paths defining the contents of the "server" 57 | # classloader. Prefixes should be used to define what is the repository type. 58 | # Path may be relative to the CATALINA_HOME or CATALINA_BASE path or absolute. 59 | # If left as blank, the "common" loader will be used as Catalina's "server" 60 | # loader. 61 | # Examples: 62 | # "foo": Add this folder as a class repository 63 | # "foo/*.jar": Add all the JARs of the specified folder as class 64 | # repositories 65 | # "foo/bar.jar": Add bar.jar as a class repository 66 | # 67 | # Note: Values may be enclosed in double quotes ("...") in case either the 68 | # ${catalina.base} path or the ${catalina.home} path contains a comma. 69 | # Because double quotes are used for quoting, the double quote character 70 | # may not appear in a path. 71 | server.loader= 72 | 73 | # 74 | # List of comma-separated paths defining the contents of the "shared" 75 | # classloader. Prefixes should be used to define what is the repository type. 76 | # Path may be relative to the CATALINA_BASE path or absolute. If left as blank, 77 | # the "common" loader will be used as Catalina's "shared" loader. 78 | # Examples: 79 | # "foo": Add this folder as a class repository 80 | # "foo/*.jar": Add all the JARs of the specified folder as class 81 | # repositories 82 | # "foo/bar.jar": Add bar.jar as a class repository 83 | # Please note that for single jars, e.g. bar.jar, you need the URL form 84 | # starting with file:. 85 | # 86 | # Note: Values may be enclosed in double quotes ("...") in case either the 87 | # ${catalina.base} path or the ${catalina.home} path contains a comma. 88 | # Because double quotes are used for quoting, the double quote character 89 | # may not appear in a path. 90 | shared.loader=/usr/local/tomcat/shared,/usr/local/tomcat/shared/*.jar 91 | 92 | # Default list of JAR files that should not be scanned using the JarScanner 93 | # functionality. This is typically used to scan JARs for configuration 94 | # information. JARs that do not contain such information may be excluded from 95 | # the scan to speed up the scanning process. This is the default list. JARs on 96 | # this list are excluded from all scans. The list must be a comma separated list 97 | # of JAR file names. 98 | # The list of JARs to skip may be over-ridden at a Context level for individual 99 | # scan types by configuring a JarScanner with a nested JarScanFilter. 100 | # The JARs listed below include: 101 | # - Tomcat Bootstrap JARs 102 | # - Tomcat API JARs 103 | # - Catalina JARs 104 | # - Jasper JARs 105 | # - Tomcat JARs 106 | # - Common non-Tomcat JARs 107 | # - Test JARs (JUnit, Cobertura and dependencies) 108 | tomcat.util.scan.StandardJarScanFilter.jarsToSkip=\ 109 | bootstrap.jar,commons-daemon.jar,tomcat-juli.jar,\ 110 | annotations-api.jar,el-api.jar,jsp-api.jar,servlet-api.jar,websocket-api.jar,\ 111 | catalina.jar,catalina-ant.jar,catalina-ha.jar,catalina-storeconfig.jar,\ 112 | catalina-tribes.jar,\ 113 | jasper.jar,jasper-el.jar,ecj-*.jar,\ 114 | tomcat-api.jar,tomcat-util.jar,tomcat-util-scan.jar,tomcat-coyote.jar,\ 115 | tomcat-dbcp.jar,tomcat-jni.jar,tomcat-websocket.jar,\ 116 | tomcat-i18n-en.jar,tomcat-i18n-es.jar,tomcat-i18n-fr.jar,tomcat-i18n-ja.jar,\ 117 | tomcat-juli-adapters.jar,catalina-jmx-remote.jar,catalina-ws.jar,\ 118 | tomcat-jdbc.jar,\ 119 | tools.jar,\ 120 | commons-beanutils*.jar,commons-codec*.jar,commons-collections*.jar,\ 121 | commons-dbcp*.jar,commons-digester*.jar,commons-fileupload*.jar,\ 122 | commons-httpclient*.jar,commons-io*.jar,commons-lang*.jar,commons-logging*.jar,\ 123 | commons-math*.jar,commons-pool*.jar,\ 124 | jstl.jar,taglibs-standard-spec-*.jar,\ 125 | geronimo-spec-jaxrpc*.jar,wsdl4j*.jar,\ 126 | ant.jar,ant-junit*.jar,aspectj*.jar,jmx.jar,h2*.jar,hibernate*.jar,httpclient*.jar,\ 127 | jmx-tools.jar,jta*.jar,log4j*.jar,mail*.jar,slf4j*.jar,\ 128 | xercesImpl.jar,xmlParserAPIs.jar,xml-apis.jar,\ 129 | junit.jar,junit-*.jar,ant-launcher.jar,\ 130 | cobertura-*.jar,asm-*.jar,dom4j-*.jar,icu4j-*.jar,jaxen-*.jar,jdom-*.jar,\ 131 | jetty-*.jar,oro-*.jar,servlet-api-*.jar,tagsoup-*.jar,xmlParserAPIs-*.jar,\ 132 | xom-*.jar 133 | 134 | # Default list of JAR files that should be scanned that overrides the default 135 | # jarsToSkip list above. This is typically used to include a specific JAR that 136 | # has been excluded by a broad file name pattern in the jarsToSkip list. 137 | # The list of JARs to scan may be over-ridden at a Context level for individual 138 | # scan types by configuring a JarScanner with a nested JarScanFilter. 139 | tomcat.util.scan.StandardJarScanFilter.jarsToScan=\ 140 | log4j-web*.jar,log4j-taglib*.jar,log4javascript*.jar,slf4j-taglib*.jar 141 | 142 | # String cache configuration. 143 | tomcat.util.buf.StringCache.byte.enabled=true 144 | #tomcat.util.buf.StringCache.char.enabled=true 145 | #tomcat.util.buf.StringCache.trainThreshold=500000 146 | #tomcat.util.buf.StringCache.cacheSize=5000 147 | 148 | # Allow for changes to HTTP request validation 149 | # WARNING: Using this option will expose the server to CVE-2016-6816 150 | #tomcat.util.http.parser.HttpParser.requestTargetAllow=| 151 | -------------------------------------------------------------------------------- /src/test/java/org/ojbc/mondrian/util/BasicOlap4jQueryTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Unless explicitly acquired and licensed from Licensor under another license, the contents of 3 | * this file are subject to the Reciprocal Public License ("RPL") Version 1.5, or subsequent 4 | * versions as allowed by the RPL, and You may not copy or use this file in either source code 5 | * or executable form, except in compliance with the terms and conditions of the RPL 6 | * 7 | * All software distributed under the RPL is provided strictly on an "AS IS" basis, WITHOUT 8 | * WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, AND LICENSOR HEREBY DISCLAIMS ALL SUCH 9 | * WARRANTIES, INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 10 | * PARTICULAR PURPOSE, QUIET ENJOYMENT, OR NON-INFRINGEMENT. See the RPL for specific language 11 | * governing rights and limitations under the RPL. 12 | * 13 | * http://opensource.org/licenses/RPL-1.5 14 | * 15 | * Copyright 2012-2017 Open Justice Broker Consortium 16 | */ 17 | package org.ojbc.mondrian.util; 18 | 19 | import static org.junit.jupiter.api.Assertions.assertEquals; 20 | import static org.junit.jupiter.api.Assertions.assertFalse; 21 | import static org.junit.jupiter.api.Assertions.assertTrue; 22 | 23 | import java.sql.Connection; 24 | import java.util.Arrays; 25 | import java.util.List; 26 | 27 | import org.junit.jupiter.api.AfterEach; 28 | import org.junit.jupiter.api.BeforeEach; 29 | import org.junit.jupiter.api.Disabled; 30 | import org.junit.jupiter.api.Test; 31 | import org.olap4j.Axis; 32 | import org.olap4j.Cell; 33 | import org.olap4j.CellSet; 34 | import org.olap4j.CellSetAxis; 35 | import org.olap4j.OlapConnection; 36 | import org.olap4j.OlapStatement; 37 | import org.olap4j.Position; 38 | import org.olap4j.metadata.Level; 39 | import org.olap4j.metadata.Member; 40 | 41 | import lombok.extern.slf4j.Slf4j; 42 | 43 | @Slf4j 44 | public class BasicOlap4jQueryTest { 45 | 46 | private Connection jdbcConnection; 47 | 48 | @BeforeEach 49 | public void setUp() throws Exception { 50 | jdbcConnection = DatabaseUtils.getInstance().getOlap4jConnection(); 51 | } 52 | 53 | @AfterEach 54 | public void tearDown() throws Exception { 55 | jdbcConnection.close(); 56 | } 57 | 58 | @Test 59 | @Disabled 60 | public void testExpt() throws Exception { 61 | // convenience test, ignored when doing builds, as a place to play around with MDX queries, do experiments, etc. 62 | OlapConnection olapConnection = jdbcConnection.unwrap(OlapConnection.class); 63 | OlapStatement statement = olapConnection.createStatement(); 64 | CellSet cellSet = statement.executeOlapQuery("select {[Measures].[F1_M1]} on columns, " + 65 | " {[D1].[D1_DESCRIPTION].members as foo} on rows" + 66 | " from Test_F1"); 67 | List axes = cellSet.getAxes(); 68 | CellSetAxis axis = axes.get(1); 69 | for (Position p : axis.getPositions()) { 70 | List members = p.getMembers(); 71 | for (Member m : members) { 72 | log.info(m.getName()); 73 | log.info(m.getHierarchy().getUniqueName()); 74 | Level level = m.getLevel(); 75 | log.info(level.getName()); 76 | log.info(level.getUniqueName()); 77 | log.info(level.getCaption()); 78 | } 79 | } 80 | } 81 | 82 | @Test 83 | public void test1Axis() throws Exception { 84 | OlapConnection olapConnection = jdbcConnection.unwrap(OlapConnection.class); 85 | OlapStatement statement = olapConnection.createStatement(); 86 | CellSet cellSet = statement.executeOlapQuery("select {[Measures].[F1_M1]} on columns from Test_F1"); 87 | List axes = cellSet.getAxes(); 88 | assertEquals(1, axes.size()); 89 | CellSetAxis axis = axes.get(0); 90 | List positions = axis.getPositions(); 91 | assertEquals(1, positions.size()); 92 | Position p = positions.get(0); 93 | List members = p.getMembers(); 94 | assertEquals(1, members.size()); 95 | Member m = members.get(0); 96 | assertEquals("Measures", m.getDimension().getName()); 97 | Cell cell = cellSet.getCell(0); 98 | assertEquals(3.0, cell.getValue()); 99 | } 100 | 101 | @Test 102 | public void test2Axes2Dimensions() throws Exception { 103 | 104 | OlapConnection olapConnection = jdbcConnection.unwrap(OlapConnection.class); 105 | OlapStatement statement = olapConnection.createStatement(); 106 | 107 | CellSet cellSet = statement.executeOlapQuery("select CrossJoin({[D2].[D2_DESCRIPTION].members}, {[Measures].[F3_M1]}) on columns, " + 108 | " {[D1].[D1_DESCRIPTION].members} on rows" + 109 | " from Test_F3"); 110 | 111 | List axes = cellSet.getAxes(); 112 | assertEquals(2, axes.size()); 113 | 114 | List columnPositions = axes.get(Axis.COLUMNS.axisOrdinal()).getPositions(); 115 | List rowPositions = axes.get(Axis.ROWS.axisOrdinal()).getPositions(); 116 | 117 | int nCols = columnPositions.size(); 118 | int nRows = rowPositions.size(); 119 | 120 | assertEquals(2, nRows); 121 | assertEquals(3, nCols); 122 | 123 | // no need to repeat all the assertions in the other tests 124 | 125 | // primarily what we demonstrate here is that the cell ordinals are set in a row-wise fashion. that is: 126 | 127 | // cell R1, C1 = ordinal 0 128 | // cell R1, C2 = ordinal 1 129 | // cell R2, C1 = ordinal 2 130 | // etc. 131 | 132 | // this is a bit counter-intuitive, since the columns axis is ordinal 0, and also the CellSet.getCell(Integer[] coordinates) method takes its 133 | // coordinates in axis-ordinal order (so, (col, row)) 134 | 135 | boolean[] expectedEmpties = new boolean[] {false, false, true, true, true, false}; 136 | Double[] expectedValues = new Double[] {1.0, 1.0, null, null, null, 1.0}; 137 | 138 | int expectedOrdinal = 0; 139 | for (int row=0;row < nRows;row++) { 140 | for (int col=0;col < nCols;col++) { 141 | Cell cell = cellSet.getCell(Arrays.asList(new Integer[]{col, row})); 142 | assertEquals(expectedOrdinal, cell.getOrdinal()); 143 | assertEquals(expectedEmpties[expectedOrdinal], cell.isEmpty()); 144 | assertEquals(expectedEmpties[expectedOrdinal], cell.isNull()); 145 | assertTrue((expectedValues[expectedOrdinal] == null && cell.getValue() == null) || 146 | expectedValues[expectedOrdinal].equals(cell.getValue())); 147 | expectedOrdinal++; 148 | } 149 | } 150 | 151 | 152 | } 153 | 154 | @Test 155 | public void test2Axes() throws Exception { 156 | 157 | OlapConnection olapConnection = jdbcConnection.unwrap(OlapConnection.class); 158 | OlapStatement statement = olapConnection.createStatement(); 159 | 160 | CellSet cellSet = statement.executeOlapQuery("select {[Measures].[F1_M1]} on columns, " + 161 | " {[D1].[D1_DESCRIPTION].members} on rows" + 162 | " from Test_F1"); 163 | 164 | List axes = cellSet.getAxes(); 165 | assertEquals(2, axes.size()); 166 | 167 | CellSetAxis columnAxis = axes.get(0); 168 | CellSetAxis rowAxis = axes.get(1); 169 | 170 | assertEquals(1, columnAxis.getPositionCount()); 171 | Position columnAxisPosition = columnAxis.getPositions().get(0); 172 | List members = columnAxisPosition.getMembers(); 173 | assertEquals(1, members.size()); 174 | assertEquals("Measures", members.get(0).getDimension().getName()); 175 | assertEquals("F1_M1", members.get(0).getName()); 176 | 177 | int cellCount = columnAxis.getPositionCount() * rowAxis.getPositionCount(); 178 | assertEquals(2, cellCount); 179 | 180 | assertEquals(2, rowAxis.getPositionCount()); 181 | Position rowAxisPosition1 = rowAxis.getPositions().get(0); 182 | members = rowAxisPosition1.getMembers(); 183 | 184 | assertEquals(1, members.size()); 185 | assertEquals("D1", members.get(0).getDimension().getName()); 186 | assertEquals("D1 One", members.get(0).getName()); 187 | 188 | Position rowAxisPosition2 = rowAxis.getPositions().get(1); 189 | members = rowAxisPosition2.getMembers(); 190 | assertEquals(1, members.size()); 191 | assertEquals("D1", members.get(0).getDimension().getName()); 192 | assertEquals("D1 Two", members.get(0).getName()); 193 | 194 | // Different ways of navigating the cube of cells... 195 | 196 | // 1. by ordinal 197 | Cell cell11 = cellSet.getCell(0); 198 | Cell cell21 = cellSet.getCell(1); 199 | assertFalse(cell11.getValue().equals(cell21.getValue())); 200 | 201 | // 2. by column axis + row axis coordinates (zero-based) 202 | assertEquals(cell11.getValue(), cellSet.getCell(Arrays.asList(new Integer[] {0,0})).getValue()); 203 | assertEquals(cell21.getValue(), cellSet.getCell(Arrays.asList(new Integer[] {0,1})).getValue()); 204 | 205 | // 3. by position object intersection 206 | assertEquals(cell11.getValue(), cellSet.getCell(columnAxisPosition, rowAxisPosition1).getValue()); 207 | assertEquals(cell11.getValue(), cellSet.getCell(rowAxisPosition1, columnAxisPosition).getValue()); // order of positions unimportant 208 | assertEquals(cell21.getValue(), cellSet.getCell(columnAxisPosition, rowAxisPosition2).getValue()); 209 | 210 | } 211 | 212 | } 213 | -------------------------------------------------------------------------------- /src/test/java/org/ojbc/mondrian/CellSetWrapperTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Unless explicitly acquired and licensed from Licensor under another license, the contents of 3 | * this file are subject to the Reciprocal Public License ("RPL") Version 1.5, or subsequent 4 | * versions as allowed by the RPL, and You may not copy or use this file in either source code 5 | * or executable form, except in compliance with the terms and conditions of the RPL 6 | * 7 | * All software distributed under the RPL is provided strictly on an "AS IS" basis, WITHOUT 8 | * WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, AND LICENSOR HEREBY DISCLAIMS ALL SUCH 9 | * WARRANTIES, INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 10 | * PARTICULAR PURPOSE, QUIET ENJOYMENT, OR NON-INFRINGEMENT. See the RPL for specific language 11 | * governing rights and limitations under the RPL. 12 | * 13 | * http://opensource.org/licenses/RPL-1.5 14 | * 15 | * Copyright 2012-2017 Open Justice Broker Consortium 16 | */ 17 | package org.ojbc.mondrian; 18 | 19 | 20 | import static org.junit.jupiter.api.Assertions.assertEquals; 21 | import static org.junit.jupiter.api.Assertions.assertTrue; 22 | 23 | import java.util.Arrays; 24 | import java.util.Collections; 25 | import java.util.List; 26 | 27 | import org.junit.jupiter.api.Test; 28 | import org.olap4j.Cell; 29 | import org.olap4j.CellSet; 30 | 31 | public class CellSetWrapperTest { 32 | 33 | @Test 34 | public void testSingleAxisSingleDimension() { 35 | 36 | CellSet cellSet = TestCellSetFactory.getInstance().getSingleAxisSingleDimensionCellSet(); 37 | CellSetWrapper w = new CellSetWrapper(cellSet); 38 | 39 | List axes = w.getAxisWrappers(); 40 | assertEquals(1, axes.size()); 41 | 42 | AxisWrapper axisWrapper = axes.get(0); 43 | assertEquals("COLUMNS", axisWrapper.getName()); 44 | assertEquals(0, axisWrapper.getOrdinal()); 45 | 46 | List positions = axes.get(0).getPositionWrappers(); 47 | assertEquals(1, positions.size()); 48 | 49 | PositionWrapper positionWrapper = positions.get(0); 50 | assertEquals(Collections.singletonList("Measures"), positionWrapper.getMemberDimensionNames()); 51 | assertEquals(Collections.singletonList("Measures"), positionWrapper.getMemberDimensionCaptions()); 52 | assertEquals(positionWrapper.getPositionMembers().size(), 1); 53 | assertEquals("M1", positionWrapper.getPositionMembers().get(0).getMemberValue()); 54 | 55 | List cells = w.getCellWrappers(); 56 | assertEquals(1, cells.size()); 57 | 58 | CellWrapper cellWrapper = cells.get(0); 59 | assertEquals(1.0, cellWrapper.getValue()); 60 | assertEquals("1.0", cellWrapper.getFormattedValue()); 61 | assertEquals(0, cellWrapper.getOrdinal()); 62 | assertEquals(Collections.singletonList(0), cellWrapper.getCoordinates()); 63 | 64 | } 65 | 66 | @Test 67 | public void testDualAxisSingleDimension() { 68 | 69 | CellSet cellSet = TestCellSetFactory.getInstance().getDualAxisSingleDimensionCellSet(); 70 | CellSetWrapper w = new CellSetWrapper(cellSet); 71 | 72 | List axes = w.getAxisWrappers(); 73 | assertEquals(2, axes.size()); 74 | 75 | AxisWrapper axisWrapper = axes.get(0); 76 | assertEquals("COLUMNS", axisWrapper.getName()); 77 | assertEquals(0, axisWrapper.getOrdinal()); 78 | 79 | List columnPositions = axisWrapper.getPositionWrappers(); 80 | assertEquals(1, columnPositions.size()); 81 | 82 | PositionWrapper positionWrapper = columnPositions.get(0); 83 | assertEquals(Collections.singletonList("Measures"), positionWrapper.getMemberDimensionNames()); 84 | assertEquals(Collections.singletonList("Measures"), positionWrapper.getMemberDimensionCaptions()); 85 | assertEquals(positionWrapper.getPositionMembers().size(), 1); 86 | assertEquals("M1", positionWrapper.getPositionMembers().get(0).getMemberValue()); 87 | 88 | axisWrapper = axes.get(1); 89 | assertEquals("ROWS", axisWrapper.getName()); 90 | assertEquals(1, axisWrapper.getOrdinal()); 91 | 92 | List rowPositions = axisWrapper.getPositionWrappers(); 93 | assertEquals(2, rowPositions.size()); 94 | 95 | positionWrapper = rowPositions.get(0); 96 | assertEquals(Collections.singletonList("D1"), positionWrapper.getMemberDimensionNames()); 97 | assertEquals(Collections.singletonList("D1"), positionWrapper.getMemberDimensionCaptions()); 98 | assertEquals(positionWrapper.getPositionMembers().size(), 1); 99 | assertEquals("D1_V1", positionWrapper.getPositionMembers().get(0).getMemberValue()); 100 | 101 | 102 | positionWrapper = rowPositions.get(1); 103 | assertEquals(Collections.singletonList("D1"), positionWrapper.getMemberDimensionNames()); 104 | assertEquals(Collections.singletonList("D1"), positionWrapper.getMemberDimensionCaptions()); 105 | assertEquals(positionWrapper.getPositionMembers().size(), 1); 106 | assertEquals("D1_V2", positionWrapper.getPositionMembers().get(0).getMemberValue()); 107 | 108 | List cells = w.getCellWrappers(); 109 | assertEquals(2, cells.size()); 110 | 111 | CellWrapper cellWrapper = cells.get(0); 112 | assertEquals(1.0, cellWrapper.getValue()); 113 | assertEquals("1.0", cellWrapper.getFormattedValue()); 114 | assertEquals(0, cellWrapper.getOrdinal()); 115 | List coordinates = cellWrapper.getCoordinates(); 116 | assertEquals(Arrays.asList(new Integer[]{0, 0}), coordinates); 117 | assertEquals(Collections.singletonList("Measures"), columnPositions.get(coordinates.get(0)).getMemberDimensionNames()); 118 | assertEquals("M1", columnPositions.get(coordinates.get(0)).getPositionMembers().get(0).getMemberValue()); 119 | assertEquals(Collections.singletonList("D1"), rowPositions.get(coordinates.get(1)).getMemberDimensionNames()); 120 | assertEquals("D1_V1", rowPositions.get(coordinates.get(0)).getPositionMembers().get(0).getMemberValue()); 121 | 122 | cellWrapper = cells.get(1); 123 | assertEquals(2.0, cellWrapper.getValue()); 124 | assertEquals("2.0", cellWrapper.getFormattedValue()); 125 | assertEquals(1, cellWrapper.getOrdinal()); 126 | coordinates = cellWrapper.getCoordinates(); 127 | assertEquals(Arrays.asList(new Integer[]{0, 1}), coordinates); 128 | assertEquals(Collections.singletonList("Measures"), columnPositions.get(coordinates.get(0)).getMemberDimensionNames()); 129 | assertEquals("M1", columnPositions.get(coordinates.get(0)).getPositionMembers().get(0).getMemberValue()); 130 | assertEquals(Collections.singletonList("D1"), rowPositions.get(coordinates.get(1)).getMemberDimensionNames()); 131 | assertEquals("D1_V2", rowPositions.get(coordinates.get(1)).getPositionMembers().get(0).getMemberValue()); 132 | 133 | } 134 | 135 | @Test 136 | public void testDualAxisTwoDimensions() { 137 | 138 | CellSet cellSet = TestCellSetFactory.getInstance().getDualAxisTwoDimensionCellSet(); 139 | CellSetWrapper w = new CellSetWrapper(cellSet); 140 | 141 | List axes = w.getAxisWrappers(); 142 | assertEquals(2, axes.size()); 143 | 144 | AxisWrapper columnsAxis = axes.get(0); 145 | 146 | assertEquals("COLUMNS", columnsAxis.getName()); 147 | assertEquals(0, columnsAxis.getOrdinal()); 148 | 149 | List columnPositions = columnsAxis.getPositionWrappers(); 150 | assertEquals(2, columnPositions.size()); 151 | 152 | int valIndex = 1; 153 | for (PositionWrapper positionWrapper : columnPositions) { 154 | assertEquals(Arrays.asList(new String[] {"Measures", "D1"}), positionWrapper.getMemberDimensionNames()); 155 | assertEquals(Arrays.asList(new String[] {"Measures", "D1"}), positionWrapper.getMemberDimensionCaptions()); 156 | assertEquals(positionWrapper.getPositionMembers().size(), 2); 157 | assertEquals(positionWrapper.getPositionMembers().get(0).getMemberValue(), "M1"); 158 | assertEquals(positionWrapper.getPositionMembers().get(1).getMemberValue(), "D1_V" + valIndex++); 159 | } 160 | 161 | AxisWrapper rowsAxis = axes.get(1); 162 | 163 | assertEquals("ROWS", rowsAxis.getName()); 164 | assertEquals(1, rowsAxis.getOrdinal()); 165 | 166 | columnPositions = rowsAxis.getPositionWrappers(); 167 | assertEquals(3, columnPositions.size()); 168 | 169 | valIndex = 1; 170 | for (PositionWrapper positionWrapper : columnPositions) { 171 | assertEquals(Collections.singletonList("D2"), positionWrapper.getMemberDimensionNames()); 172 | assertEquals(Collections.singletonList("D2"), positionWrapper.getMemberDimensionCaptions()); 173 | assertEquals(positionWrapper.getPositionMembers().size(), 1); 174 | assertEquals(positionWrapper.getPositionMembers().get(0).getMemberValue(), "D2_V" + valIndex++); 175 | } 176 | 177 | Double[] expectedValues = new Double[] {1.0, 10.0, 2.0, 11.0, 3.0, null}; 178 | 179 | List cellWrappers = w.getCellWrappers(); 180 | 181 | for (int i=0;i < expectedValues.length;i++) { 182 | CellWrapper cellWrapper = cellWrappers.get(i); 183 | Cell cell = cellSet.getCell(i); 184 | assertEquals(i, cellWrapper.getOrdinal()); 185 | Number cellWrapperValue = cellWrapper.getValue(); 186 | Object cellValue = cell.getValue(); 187 | assertTrue(expectedValues[i] == null && cellWrapperValue == null || 188 | expectedValues[i].equals(cellWrapperValue)); 189 | assertTrue(cellValue == null && cellWrapperValue == null || 190 | cellValue.equals(cellWrapperValue)); 191 | } 192 | 193 | } 194 | 195 | 196 | } 197 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 19 | 21 | 22 | 4.0.0 23 | org.ojbc 24 | mondrian-rest 25 | 2.0.6 26 | Mondrian REST API Implementation 27 | war 28 | 29 | 30 | org.springframework.boot 31 | spring-boot-starter-parent 32 | 2.4.3 33 | 34 | 35 | 36 | 8.3.0.21-1159 37 | 1.0.0 38 | 1.10.19 39 | 1.7.30 40 | 41 | 42 | 43 | 44 | 45 | org.springframework.boot 46 | spring-boot-starter-web 47 | 48 | 49 | 50 | org.springframework.boot 51 | spring-boot-starter-tomcat 52 | provided 53 | 54 | 55 | 56 | pentaho 57 | mondrian 58 | ${mondrian-version} 59 | 60 | 61 | javax.servlet 62 | servlet-api 63 | 64 | 65 | javax.servlet 66 | jsp-api 67 | 68 | 69 | xml-apis 70 | xml-apis 71 | 72 | 73 | xercesImpl 74 | xerces 75 | 76 | 77 | xalan 78 | xalan 79 | 80 | 81 | dom4j 82 | dom4j 83 | 84 | 85 | 86 | 87 | 88 | 89 | xalan 90 | xalan 91 | 2.7.2 92 | 93 | 94 | 95 | org.ehcache 96 | ehcache 97 | 98 | 99 | 100 | org.ojbc 101 | foodmart-data-hsqldb 102 | ${foodmart-hsqldb-version} 103 | 104 | 105 | 106 | org.hsqldb 107 | hsqldb 108 | 109 | 110 | 111 | org.slf4j 112 | jcl-over-slf4j 113 | 114 | 115 | 116 | org.slf4j 117 | log4j-over-slf4j 118 | 119 | 120 | 121 | org.slf4j 122 | slf4j-api 123 | 124 | 125 | 126 | org.projectlombok 127 | lombok 128 | provided 129 | 130 | 131 | 132 | 133 | 134 | org.mockito 135 | mockito-all 136 | ${mockito-version} 137 | test 138 | 139 | 140 | org.springframework.boot 141 | spring-boot-starter-test 142 | test 143 | 144 | 145 | org.junit.jupiter 146 | junit-jupiter-api 147 | test 148 | 149 | 150 | org.junit.jupiter 151 | junit-jupiter-engine 152 | test 153 | 154 | 155 | org.junit.platform 156 | junit-platform-launcher 157 | test 158 | 159 | 160 | 161 | 162 | 163 | 164 | ossrh 165 | https://oss.sonatype.org/content/repositories/snapshots 166 | 167 | 168 | ossrh 169 | https://oss.sonatype.org/service/local/staging/deploy/maven2/ 170 | 171 | 172 | 173 | 174 | 175 | 176 | true 177 | always 178 | warn 179 | 180 | pentaho 181 | Pentaho 182 | https://nexus.pentaho.org/repository/proxy-pentaho-public-release-group/ 183 | 184 | 185 | 186 | true 187 | always 188 | warn 189 | 190 | pentaho3p 191 | Pentaho Third Party Releases 192 | https://nexus.pentaho.org/repository/proxy-public-3rd-party-release/ 193 | 194 | 195 | 196 | 197 | install 198 | 199 | 200 | org.apache.maven.plugins 201 | maven-compiler-plugin 202 | 203 | 1.8 204 | 1.8 205 | 206 | 207 | 208 | org.springframework.boot 209 | spring-boot-maven-plugin 210 | 211 | 212 | 213 | repackage 214 | 215 | 216 | 217 | 218 | mondrian-rest-executable 219 | 220 | 221 | 222 | org.apache.maven.plugins 223 | maven-war-plugin 224 | 225 | WEB-INF/lib/geronimo-servlet_2.5_spec-1.1.2.jar 226 | WEB-INF/lib/xercesImpl-2.9.1.jar 227 | WEB-INF/lib/xml-apis-1.4.01.jar 228 | WEB-INF/lib/xalan-2.6.0.jar 229 | true 230 | false 231 | mondrian-rest 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | release 240 | 241 | 242 | 243 | org.sonatype.plugins 244 | nexus-staging-maven-plugin 245 | 1.6.3 246 | true 247 | 248 | ossrh 249 | https://oss.sonatype.org/ 250 | true 251 | 252 | 253 | 254 | org.apache.maven.plugins 255 | maven-gpg-plugin 256 | 1.5 257 | 258 | 259 | sign-artifacts 260 | verify 261 | 262 | sign 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | -------------------------------------------------------------------------------- /src/main/java/org/ojbc/mondrian/MondrianConnectionFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Unless explicitly acquired and licensed from Licensor under another license, the contents of 3 | * this file are subject to the Reciprocal Public License ("RPL") Version 1.5, or subsequent 4 | * versions as allowed by the RPL, and You may not copy or use this file in either source code 5 | * or executable form, except in compliance with the terms and conditions of the RPL 6 | * 7 | * All software distributed under the RPL is provided strictly on an "AS IS" basis, WITHOUT 8 | * WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, AND LICENSOR HEREBY DISCLAIMS ALL SUCH 9 | * WARRANTIES, INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 10 | * PARTICULAR PURPOSE, QUIET ENJOYMENT, OR NON-INFRINGEMENT. See the RPL for specific language 11 | * governing rights and limitations under the RPL. 12 | * 13 | * http://opensource.org/licenses/RPL-1.5 14 | * 15 | * Copyright 2012-2017 Open Justice Broker Consortium 16 | */ 17 | package org.ojbc.mondrian; 18 | 19 | import java.io.IOException; 20 | import java.io.InputStream; 21 | import java.io.StringReader; 22 | import java.net.MalformedURLException; 23 | import java.net.URL; 24 | import java.sql.DriverManager; 25 | import java.sql.SQLException; 26 | import java.util.ArrayList; 27 | import java.util.Collections; 28 | import java.util.HashMap; 29 | import java.util.HashSet; 30 | import java.util.List; 31 | import java.util.Map; 32 | import java.util.Properties; 33 | import java.util.Set; 34 | 35 | import javax.xml.parsers.DocumentBuilderFactory; 36 | import javax.xml.parsers.ParserConfigurationException; 37 | 38 | import org.apache.commons.io.IOUtils; 39 | import org.springframework.core.io.Resource; 40 | import org.springframework.core.io.support.PathMatchingResourcePatternResolver; 41 | import org.springframework.core.io.support.ResourcePatternResolver; 42 | import org.w3c.dom.Document; 43 | import org.xml.sax.InputSource; 44 | import org.xml.sax.SAXException; 45 | 46 | import com.fasterxml.jackson.annotation.JsonIgnore; 47 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 48 | import com.fasterxml.jackson.annotation.JsonInclude; 49 | import com.fasterxml.jackson.annotation.JsonInclude.Include; 50 | import com.fasterxml.jackson.annotation.JsonProperty; 51 | import com.fasterxml.jackson.core.type.TypeReference; 52 | import com.fasterxml.jackson.databind.ObjectMapper; 53 | 54 | import lombok.extern.slf4j.Slf4j; 55 | 56 | /** 57 | * A factory object that creates olap4j connection objects from json specifications that it finds on the classpath. Connections are instantiated lazily...that is, no 58 | * actual connection is made to the underlying relational database until getOlap4jConnection() is called. 59 | * 60 | */ 61 | @Slf4j 62 | public final class MondrianConnectionFactory { 63 | 64 | static final class MondrianConnectionCollection { 65 | private String sourceFilePath; 66 | private Map connections; 67 | public Map getConnections() { 68 | return Collections.unmodifiableMap(connections); 69 | } 70 | public String getSourceFilePath() { 71 | return sourceFilePath; 72 | } 73 | } 74 | 75 | @JsonIgnoreProperties(ignoreUnknown = true) 76 | @JsonInclude(Include.NON_NULL) 77 | public static final class MondrianConnection { 78 | 79 | @JsonProperty("JdbcDriver") 80 | private String jdbcDriver; 81 | @JsonProperty("Jdbc") 82 | private String jdbcConnectionString; 83 | private String jdbcUser; 84 | private String jdbcPassword; 85 | @JsonProperty("Description") 86 | private String description; 87 | private String catalog; 88 | private String resolvedMondrianSchemaURL; 89 | private URL catalogUrl; 90 | private String catalogContent; 91 | private String sourceResourcePath; 92 | private boolean isDemo = false; 93 | private boolean jdbcDriverClass; 94 | 95 | public boolean isJdbcDriverClass() { 96 | return jdbcDriverClass; 97 | } 98 | @JsonProperty(value="JdbcDriverClass") 99 | public void setJdbcDriverClass(boolean jdbcDriverClass) { 100 | this.jdbcDriverClass = jdbcDriverClass; 101 | } 102 | public boolean getIsDemo() { 103 | return isDemo; 104 | } 105 | @JsonProperty(value="IsDemo") 106 | void setIsDemo(boolean value) { 107 | isDemo=value; 108 | } 109 | @JsonProperty(value="ConnectionDefinitionSource") 110 | public String getSourceResourcePath() { 111 | return sourceResourcePath; 112 | } 113 | public String getJdbcDriver() { 114 | return jdbcDriver; 115 | } 116 | public String getJdbcConnectionString() { 117 | return jdbcConnectionString; 118 | } 119 | @JsonIgnore 120 | public String getResolvedMondrianSchemaURL() { 121 | return resolvedMondrianSchemaURL; 122 | } 123 | @JsonProperty(value="MondrianSchemaUrl") 124 | public String getMondrianSchemaUrl() { 125 | return getResolvedMondrianSchemaURL(); 126 | } 127 | @JsonProperty(value="MondrianSchemaUrl") 128 | @JsonIgnore 129 | void setMondrianSchemaUrl(String value) { 130 | resolvedMondrianSchemaURL=value; 131 | } 132 | public String getDescription() { 133 | return description; 134 | } 135 | @JsonIgnore 136 | public String getJdbcUser() { 137 | return jdbcUser; 138 | } 139 | @JsonProperty(value="JdbcUser") 140 | void setJdbcUser(String value) { 141 | jdbcUser=value; 142 | } 143 | @JsonIgnore 144 | public String getJdbcPassword() { 145 | return jdbcPassword; 146 | } 147 | @JsonProperty(value="JdbcPassword") 148 | void setJdbcPassword(String value) { 149 | jdbcPassword=value; 150 | } 151 | @JsonProperty(value="MondrianSchemaContent") 152 | public String getMondrianSchemaContent() { 153 | return catalogContent; 154 | } 155 | @JsonIgnore 156 | public String getCatalog() { 157 | return catalog; 158 | } 159 | @JsonIgnore 160 | public Document getMondrianSchemaContentDocument() throws SAXException, IOException, ParserConfigurationException { 161 | return DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(new InputSource(new StringReader(catalogContent))); 162 | } 163 | 164 | @JsonProperty(value="Catalog") 165 | void setCatalog(String path) { 166 | if (path != null) { 167 | catalogUrl = MondrianConnection.class.getResource(path); 168 | if (catalogUrl == null) { 169 | try { 170 | catalogUrl = new URL(path); 171 | } catch (MalformedURLException e) { 172 | log.warn("Invalid URL specified for Mondrian schema: " + path); 173 | } 174 | } 175 | if (catalogUrl != null) { 176 | resolvedMondrianSchemaURL = catalogUrl.toExternalForm(); 177 | try { 178 | InputStream stream = catalogUrl.openStream(); 179 | catalogContent = IOUtils.toString(stream); 180 | } catch (IOException e) { 181 | log.warn("Exception occurred when attempting to read Mondrian schema from URL " + catalogUrl.toExternalForm()); 182 | } 183 | } 184 | } 185 | catalog = path; 186 | } 187 | 188 | /** 189 | * Get the olap4j connection for the parameters represented by this MondrianConnection 190 | * @return the connection object 191 | * @throws SQLException if something goes wrong with the underlying connection to the relational database 192 | */ 193 | @JsonIgnore 194 | public java.sql.Connection getOlap4jConnection() throws SQLException { 195 | 196 | try { 197 | Class.forName("mondrian.olap4j.MondrianOlap4jDriver"); 198 | } catch (ClassNotFoundException e) { 199 | log.error("Mondrian olap4j driver class not found. Mondrian appears to be missing or improperly installed."); 200 | throw new RuntimeException(e); 201 | } 202 | 203 | Properties props = new Properties(); 204 | setPropertyValue(props, "Jdbc", jdbcConnectionString); 205 | setPropertyValue(props, "JdbcDrivers", jdbcDriver); 206 | setPropertyValue(props, "CatalogContent", catalogContent); 207 | setPropertyValue(props, "JdbcUser", jdbcUser); 208 | setPropertyValue(props, "JdbcPassword", jdbcPassword); 209 | 210 | return DriverManager.getConnection("jdbc:mondrian:", props); 211 | 212 | } 213 | 214 | boolean validate() { 215 | boolean ret = true; 216 | if (jdbcConnectionString == null) { 217 | log.warn("JDBC Connection String (specified by json property \"Jdbc\") is null"); 218 | ret = false; 219 | } 220 | if (jdbcDriver == null) { 221 | log.warn("JDBC Driver (specified by json property \"JdbcDriver\") is null"); 222 | ret = false; 223 | } 224 | if (catalog == null) { 225 | log.warn("Mondrian Schema/Catalog (specified by json property \"Catalog\") is null"); 226 | ret = false; 227 | } 228 | if (catalogContent == null) { 229 | log.warn("Mondrian Schema content (read from catalog URL) is null"); 230 | ret = false; 231 | } 232 | return ret; 233 | } 234 | 235 | private static final Properties setPropertyValue(Properties props, String name, String value) { 236 | if (value != null) { 237 | props.setProperty(name, value); 238 | } 239 | return props; 240 | } 241 | 242 | } 243 | 244 | private List connectionCollections = new ArrayList<>(); 245 | private Map connections = new HashMap<>(); 246 | 247 | /** 248 | * Initialize the factory by scanning the classpath for resources matching the pattern *mondrian-connections.json. 249 | * @throws IOException if something goes wrong scanning the classpath or reading resources 250 | */ 251 | public void init() throws IOException { 252 | init(false); 253 | } 254 | 255 | /** 256 | * Initialize the factory by scanning the classpath for resources matching the pattern *mondrian-connections.json. 257 | * @param removeDemoConnections whether to strip out connections where IsDemo=true 258 | * @throws IOException if something goes wrong scanning the classpath or reading resources 259 | */ 260 | public void init(boolean removeDemoConnections) throws IOException { 261 | 262 | ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); 263 | Resource[] resources = resolver.getResources("classpath*:*mondrian-connections.json"); 264 | 265 | ObjectMapper mapper = new ObjectMapper(); 266 | TypeReference> typeRef = new TypeReference>() {}; 267 | 268 | for (Resource resource : resources) { 269 | String resourceSourcePath = null; 270 | String url = resource.getURL().toExternalForm(); 271 | if (url.contains("/WEB-INF/classes!/")) { 272 | resourceSourcePath = url.replace("/WEB-INF/classes!/", "/WEB-INF/classes/"); 273 | log.info("Working around Spring Boot / Tomcat bug that occurs in standalone mode, to adjust file path found via PathMatchingResourcePatternResolver"); 274 | } else { 275 | resourceSourcePath = resource.getFile().getCanonicalPath(); 276 | } 277 | log.info("Processing connection definition json found at " + resourceSourcePath); 278 | Map connections = mapper.readValue(resource.getInputStream(), typeRef); 279 | Set invalidConnections = new HashSet<>(); 280 | String finalResourceSourcePath = resourceSourcePath; 281 | connections.forEach((nm, c) -> { 282 | if (!c.validate()) { 283 | log.warn("Ignoring connection " + nm + " due to invalid/missing properties (see prior messages for details)"); 284 | invalidConnections.add(nm); 285 | } else { 286 | log.info("Adding valid connection " + nm + ": connection string=" + c.getJdbcConnectionString() + ", Mondrian schema path=" + c.getResolvedMondrianSchemaURL()); 287 | c.sourceResourcePath = finalResourceSourcePath; 288 | } 289 | }); 290 | invalidConnections.forEach(nm -> connections.remove(nm)); 291 | MondrianConnectionCollection collection = new MondrianConnectionCollection(); 292 | collection.connections = connections; 293 | collection.sourceFilePath = finalResourceSourcePath; 294 | connectionCollections.add(collection); 295 | } 296 | 297 | Collections.reverse(connectionCollections); 298 | 299 | connectionCollections.forEach(mcc -> { 300 | mcc.getConnections().forEach((name, mc) -> { 301 | if (connections.containsKey(name)) { 302 | MondrianConnection conn = connections.get(name); 303 | log.warn("Overriding connection " + name + " defined at " + conn.getSourceResourcePath() + 304 | " with connection defined \"higher\" on the classpath, at " + mc.getSourceResourcePath()); 305 | } 306 | if (mc != null && (!removeDemoConnections || !mc.getIsDemo())) { 307 | connections.put(name, mc); 308 | } else if (mc != null) { 309 | log.info("Removing demo connection " + name + " with description '" + mc.getDescription() + "' defined at " + mc.getSourceResourcePath()); 310 | } else { 311 | // this should never happen, but just in case... 312 | log.warn("Connection defined with name " + name + " but no available definition on classpath"); 313 | } 314 | }); 315 | }); 316 | 317 | } 318 | 319 | List getConnectionCollections() { 320 | return Collections.unmodifiableList(connectionCollections); 321 | } 322 | 323 | /** 324 | * Get the collection of available Mondrian Connection objects in this factory. The key in the map is the name of the connection, as specified in the .json. The value 325 | * in the map is the connection object. 326 | * @return the map of connections 327 | */ 328 | public Map getConnections() { 329 | return Collections.unmodifiableMap(connections); 330 | } 331 | 332 | } 333 | --------------------------------------------------------------------------------