SuperX-Kernmodul
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

457 lines
18 KiB

package de.superx.saiku.cube;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.net.CookieHandler;
import java.net.CookieManager;
import java.net.HttpCookie;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.log4j.Logger;
import org.junit.Before;
import org.junit.Ignore;
/**
* Test your cube. You can execute a MDX-Query over the REST Service.
* Execute MDX with executeMDX (it returns the resultMap). You can validate your result by using validateResultEquals function or directly work with the resultMap.
* The result is a map with the values position in the table as key. (position: "column:row"; i.e: if you want the value in 2nd column and first row the key would be: "1:0")
* To know which position your value has, take a look at the result table Saiku returns for your query.
*
* Also you can validate that a cube with a given name exists with validateCubeExists.
*
* Default login is linde/linde!.
* If you want to use another login or url you could override connectInitAndAuth() and change the values before calling super.connectInitAndAuth()
*
* TODO: better result validation. Atm only functions for equals are given, if you need to do some other validation you need to work with the resultMap directly.
*
* @author rogat
*/
@Ignore
public abstract class AbstractCubeTest {
private static final Logger LOG = Logger.getLogger(AbstractCubeTest.class);
protected String url = System.getProperty("url", "http://127.0.0.1:8080/");
protected String user = "linde";
protected String password = "linde!";
private final CookieManager cookieManager = new CookieManager();
private String qisName = null;
private String xsrfToken;
protected Map<String, Double> resultMap;
protected List<String> cubeList;
/**
* Initializes the the cookieManager, fetches the QisName authenticates with HISINONE and superX.
* @throws IOException
*/
@Before
public void connectInitAndAuth() throws IOException {
CookieHandler.setDefault(cookieManager);
//open connection to url and extract the name of the server
fetchAndSetQisName();
//sign in HISINONE
HttpURLConnection con = auth(user, password);
assertTrue("HISINONE-Login war nicht erfolgereich.", !readContent(con).contains("infobox error_infobox messages-infobox"));
//auth with superX
authSuperx();
//fetch cubes
fetchCubes();
}
/**
* Tries to find the cube name in the list fetched from server.
* @param cubeName
*/
protected void validateCubeExists(String cubeName) {
assertTrue("A cube with the name " + cubeName + " does not exists or is not visible for user " + user, cubeList.contains(cubeName));
}
/**
* Tries to find the value at the given position. If the value doesn't match -> test failed.
* @param position
* @param value
*/
protected void validateResultEquals(String position, Double value) {
Double resultValue = resultMap.get(position);
assertTrue("Value " + value + " not found at position " + position, resultValue != null && resultValue.equals(value));
}
/**
* Tries to find the value at the given position. If the value doesn't match -> test failed.
* @param position
* @param value
*/
protected void validateResultEquals(String position, double value) {
validateResultEquals(position, Double.valueOf(value));
}
/**
* Tries to find the value at the given position. If the value doesn't match -> test failed.
* @param position
* @param value
*/
protected void validateResultEquals(String position, int value) {
validateResultEquals(position, Double.valueOf(value));
}
/**
* Tries to find the value at the given position. If the value doesn't match -> test failed.
* @param column
* @param row
* @param value
*/
protected void validateResultEquals(int column, int row, Double value) {
validateResultEquals(column + ":" + row, value);
}
/**
* Tries to find the value at the given position. If the value doesn't match -> test failed.
* @param column
* @param row
* @param value
*/
protected void validateResultEquals(int column, int row, double value) {
validateResultEquals(column + ":" + row, Double.valueOf(value));
}
/**
* Tries to find the value at the given position. If the value doesn't match -> test failed.
* @param column
* @param row
* @param value
*/
protected void validateResultEquals(int column, int row, int value) {
validateResultEquals(column + ":" + row, Double.valueOf(value));
}
/**
* Extracts the resultData from the json result.
* The returned map contains the values at a position. Key is the position as a string like "0:4" (= Column 0, Row 4)
* @param result
* @return
*/
private Map<String, Double> convertResult(String result) {
resultMap = new HashMap<String, Double>();
String tmp_result = result;
//go through the result and extract every resultValue and its position
int datacell_index = tmp_result.indexOf(STR_DATACELL);
while ( datacell_index != -1) {
int begin_index = tmp_result.lastIndexOf('{', datacell_index);
int end_index = tmp_result.indexOf('}', datacell_index);
String dataCell = tmp_result.substring(begin_index, end_index); //here we got a datacell (contains value, position and more)
String value = extractValueFromDatacell(dataCell, STR_VALUE); //extract the value
String position = extractValueFromDatacell(dataCell, STR_POSITION); //extract the position
Double d_value = Double.valueOf(0);
try {
d_value = Double.valueOf(Double.parseDouble(value));
} catch (NumberFormatException e) {
// this is likely to be an empty value -> treat it like 0
}
resultMap.put(position, d_value); //store value at position in resultMap
tmp_result = tmp_result.substring(end_index); //cut away the already processed datacell
datacell_index = tmp_result.indexOf(STR_DATACELL);
}
return resultMap;
}
/**
* Extracts the value from a jsonString.
* (I.e: extractValueFromDatacell("\"value\":\"1\"", "\"value\":\"") -> returns "1")
* @param text
* @param identifier
* @return
*/
private static String extractValueFromDatacell(String text, String identifier) {
String str = text;
int index = str.indexOf(identifier);
str = str.substring(index + identifier.length());
String value = str.substring(0, str.indexOf("\""));
return value;
}
/**
* Prepares a HttpURLConnection connection for the Saiku rest call to execute a query
* @return prepared HttpURLConnection
* @throws IOException
*/
private HttpURLConnection prepareOlapConnection() throws IOException {
HttpURLConnection con = openConnection("superx/rest/saiku/api/query/execute");
con.setRequestProperty("X-Requested-With", "XMLHttpRequest");
con.setRequestProperty("X-XSRF-TOKEN", xsrfToken);
con.setRequestMethod("POST");
con.setRequestProperty("Content-Type", "application/json");
con.setRequestProperty("Accept", "application/json, text/javascript, */*; q=0.01");
con.setDoOutput(true);
con.setDoInput(true);
return con;
}
/**
* Builds and executes a OlapQuery with the given MDX
* Converts and stores the result in this.resultMap
* Also validated whether a cube with the given name exists
* @param cubeName name of the cube (e.g. res_project_cube)
* @param mdx
* @return resultMap
* @throws IOException
*/
protected Map<String, Double> executeMDX(String cubeName, String mdx) throws IOException {
validateCubeExists(cubeName);
HttpURLConnection con = prepareOlapConnection();
BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(con.getOutputStream()));
bufferedWriter.write(String.format(QUERY_TEMPLATE, cubeName, mdx.replaceAll("\n", " ")));
bufferedWriter.flush();
bufferedWriter.close();
return convertResult(readContent(con));
}
/**
* Authenticate with superX after the authentication with HISINONE is made
* @throws IOException
*/
private void authSuperx() throws IOException {
//start sso
String token = extractToken();
LOG.debug("Token: " + token);
assertNotNull("No SSO token found.", token);
//finish sso with token
HttpURLConnection con = openConnection("superx/servlet/SuperXmlAnmeldung?token=" + token);
con.setRequestMethod("POST");
send(con);
// find the xsrf cookie and store it (we need to add it to the header for saiku requests)
for (HttpCookie cookie : cookieManager.getCookieStore().getCookies()) {
if (cookie.getName().equals("XSRF-TOKEN")) {
xsrfToken = cookie.getValue();
break;
}
}
//validate sso
con = openConnection("superx/saiku/");
assertTrue("Could not log into Saiku...SSO failed?", send(con) == 200);
//start session
con = openConnection("superx/rest/saiku/session?username=" + encodeURL(user) + "&password=notsoimportant");
con.setRequestProperty("X-Requested-With", "XMLHttpRequest");
con.setRequestProperty("X-XSRF-TOKEN", xsrfToken);
con.setRequestMethod("POST");
send(con);
}
/**
* Fetch all possible cubes from server and stores them in cubeList
* @throws IOException
*/
private void fetchCubes() throws IOException {
HttpURLConnection con = openConnection("superx/rest/saiku/" + encodeURL(user) + "/discover/refresh");
con.setRequestProperty("X-Requested-With", "XMLHttpRequest");
con.setRequestProperty("X-XSRF-TOKEN", xsrfToken);
String content = readContent(con);
//go through the content and extract the cubeNames
cubeList = new ArrayList<String>();
int cubeCell_index = content.indexOf(STR_CAPTION);
while ( cubeCell_index != -1) {
int begin_index = content.lastIndexOf('{', cubeCell_index);
int end_index = content.indexOf('}', cubeCell_index);
String cubeCell = content.substring(begin_index, end_index); //here we got a cubeCell (contains name and more)
String name = extractValueFromDatacell(cubeCell, STR_NAME); //extract cubeName
// String caption = extractValueFromDatacell(cubeCell, STR_CAPTION);
cubeList.add(name);
content = content.substring(end_index); //cut the already processed cubeCell away
cubeCell_index = content.indexOf(STR_CAPTION);
}
}
/**
* Established the connection and reads the httpResponeCode
* @param con
* @return ResponseCode
* @throws IOException
*/
private static int send(HttpURLConnection con) throws IOException {
int status = con.getResponseCode();
LOG.debug("ResponseConde: " + status);
assertTrue("HTTP-ResponseCode: " + status + ". For URL: " + con.getURL(), status == 200);
return status;
}
/**
* reads content of a HttpURLConnection
* @param con
* @return
* @throws IOException
*/
private static String readContent(HttpURLConnection con) throws IOException {
int status = con.getResponseCode();
LOG.debug("ResponseConde: " + status);
assertTrue("HTTP-ResponseCode: " + status + ". For URL: " + con.getURL(), status == 200);
BufferedReader in = null;
if (status > 299) {
in = new BufferedReader(new InputStreamReader(con.getErrorStream()));
} else {
in = new BufferedReader(new InputStreamReader(con.getInputStream()));
}
String inputLine;
StringBuffer content = new StringBuffer();
while ((inputLine = in.readLine()) != null) {
content.append(inputLine);
}
in.close();
LOG.debug("Content: " + content);
return content.toString();
}
/**
* Calls http://......./rds?state=redirect&sso=superx and extracts the token to authenticated with superX
* @param con
* @return token
* @throws IOException
*/
private String extractToken() throws IOException {
//make the call
HttpURLConnection con = openConnection(qisName + "rds?state=redirect&sso=superx");
String token = null;
int status = con.getResponseCode();
if (status == 200) { // was successful?
BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream()));
String inputLine;
//search the content for the token
while ((inputLine = in.readLine()) != null) {
int index = inputLine.indexOf("value=\"");
if (index != -1) {
//extract the token from line
token = inputLine.substring(index + 7, inputLine.indexOf("\">"));
break;
}
}
}
return token;
}
/**
* Authenticate with HISINONE
* @param user
* @param pass
* @return HttpURLConnection
* @throws IOException
*/
private HttpURLConnection auth(String user, String pass) throws IOException {
HttpURLConnection con = openConnection(qisName + "rds?state=user&type=1&username=" + encodeURL(user) + "&password=" + encodeURL(pass));
return con;
}
/**
* Extracts the qisName from the content send back from URL and stores it in var: qisName
* (qisName has usually values like: "qisserver" or "hisinone")
* @throws IOException
*/
private void fetchAndSetQisName() throws IOException {
HttpURLConnection con = openConnection("");
String content = readContent(con);
int startIndex = content.indexOf("0;URL=/");
int endIndex = content.indexOf("rds?state");
if (startIndex != -1 && endIndex != -1) {
qisName = content.substring(startIndex + 7, endIndex);
}
assertNotNull("Could extract QIS_NAME", qisName);
}
/**
* Prepares a connection on URL + urlSuffix
* The connection will not be established
* @param urlSuffix
* @return
* @throws IOException
*/
private HttpURLConnection openConnection(String urlSuffix) throws IOException {
String tmp_url = this.url + urlSuffix;
LOG.debug("\n\nopening: " + tmp_url.toString());
HttpURLConnection con = (HttpURLConnection) new URL(tmp_url).openConnection();
con.setConnectTimeout(5000);
return con;
}
/**
* UTF8 encoding
* @param str
* @return
*/
public static String encodeURL(String str) {
if (str == null) {
return "";
}
try {
return URLEncoder.encode(str, "UTF8");
} catch (final UnsupportedEncodingException e) {
return str;
}
}
private final String STR_NAME = "\"name\":\"";
private final String STR_CAPTION = "\"caption\":\"";
private final String STR_VALUE = "\"value\":\"";
private final String STR_POSITION = "\"position\":\"";
private final String STR_DATACELL = "\"type\":\"DATA_CELL\"";
private final String QUERY_TEMPLATE = "{\n" + " \"queryModel\": {\n" + " \n" + " },\n" + " \"cube\": {\n"
+ " \"uniqueName\": \"[BI].[HISinOne].[HISinOne].[%s]\",\n" + " \"name\": \"res_employment_cube\",\n"
+ " \"connection\": \"BI\",\n" + " \"catalog\": \"HISinOne\",\n" + " \"schema\": \"HISinOne\",\n"
+ " \"caption\": null,\n" + " \"visible\": false\n" + " },\n" + " \"mdx\": \"%s\",\n"
+ " \"name\": \"7E6533B9-FB6E-8FAA-B8D9-C3737EFEA0E7\",\n" + " \"parameters\": {\n" + " \n" + " },\n"
+ " \"plugins\": {\n" + " \n" + " },\n" + " \"properties\": {\n"
+ " \"saiku.olap.query.automatic_execution\": true,\n" + " \"saiku.olap.query.nonempty\": true,\n"
+ " \"saiku.olap.query.nonempty.rows\": true,\n" + " \"saiku.olap.query.nonempty.columns\": true,\n"
+ " \"saiku.ui.render.mode\": \"table\",\n" + " \"saiku.olap.query.filter\": true,\n"
+ " \"saiku.olap.result.formatter\": \"flat\",\n" + " \"org.saiku.query.explain\": true,\n"
+ " \"saiku.olap.query.drillthrough\": true,\n" + " \"org.saiku.connection.scenario\": false\n" + " },\n"
+ " \"metadata\": {\n" + " \n" + " },\n" + " \"queryType\": \"OLAP\",\n" + " \"type\": \"MDX\"\n" + "}";
private final String EMPTY_RESULT = "{\"cellset\":null,\"rowTotalsLists\":null,\"colTotalsLists\":null,\"runtime\":null,\"error\":\"NullPointerException: \",\"height\":null,\"width\":null,\"query\":null,\"topOffset\":0,\"leftOffset\":0}";
}