/* // // Licensed to Benedikt Kämpgen under one or more contributor license // agreements. See the NOTICE file distributed with this work for // additional information regarding copyright ownership. // // Benedikt Kämpgen licenses this file to you under the Apache License, // Version 2.0 (the "License"); you may not use this file except in // compliance with the License. You may obtain a copy of the License at: // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. */ package org.olap4j.driver.olap4ld.linkeddata; import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; import java.net.URLEncoder; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import org.olap4j.OlapException; import org.olap4j.Position; import org.olap4j.driver.olap4ld.Olap4ldUtil; import org.olap4j.driver.olap4ld.helper.Olap4ldLinkedDataUtil; import org.olap4j.mdx.CallNode; import org.olap4j.mdx.MemberNode; import org.olap4j.metadata.Cube; import org.olap4j.metadata.Level; import org.olap4j.metadata.Measure; import org.olap4j.metadata.Member; import org.semanticweb.yars.nx.Literal; import org.semanticweb.yars.nx.Node; import org.semanticweb.yars.nx.Variable; import org.semanticweb.yars.nx.parser.NxParser; /** * Implements methods of XmlaOlap4jDatabaseMetadata, returning the specified * columns as nodes from a sparql endpoint. * * @author b-kaempgen * */ public class OpenVirtuosoEngine_old implements LinkedDataCubesEngine { /* * Open Virtuoso is default triple store */ private int SPARQLSERVERTYPE = 2; private static final int QCRUMB = 1; private static final int OPENVIRTUOSO = 2; private static final int OPENVIRTUOSORULESET = 3; /** * The type of SPARQL endpoint should be found out automatically and with * the server string */ private static String SPARQLSERVERURL; private static final String DATASOURCEDESCRIPTION = "OLAP data from the statistical Linked Data cloud."; private static final String PROVIDERNAME = "The community."; private static String URL; private static final String DATASOURCEINFO = "Data following the Linked Data principles."; private static final String TABLE_CAT = "LdCatalog"; private static final String TABLE_SCHEM = "LdSchema"; /* * Array of datasets of the database/catalog/schema that shall be integrated */ public String DATASOURCENAME; public String DATASOURCEVERSION; private ArrayList<String> datastructuredefinitions; private ArrayList<String> datasets; private HashMap<Integer, List<Node[]>> sparqlResultMap = new HashMap<Integer, List<Node[]>>(); public OpenVirtuosoEngine_old(URL serverUrlObject, ArrayList<String> datastructuredefinitions, ArrayList<String> datasets, String databasename) { URL = serverUrlObject.toString(); this.datastructuredefinitions = datastructuredefinitions; this.datasets = datasets; // TODO: I could add another version with a specific rule set for open // virtuoso if (databasename.equals("QCRUMB")) { SPARQLSERVERTYPE = QCRUMB; this.DATASOURCENAME = databasename; this.DATASOURCEVERSION = "1.0"; } if (databasename.equals("OPENVIRTUOSO")) { SPARQLSERVERTYPE = OPENVIRTUOSO; DATASOURCENAME = databasename; DATASOURCEVERSION = "1.0"; } if (databasename.equals("OPENVIRTUOSORULESET")) { SPARQLSERVERTYPE = OPENVIRTUOSORULESET; DATASOURCENAME = databasename; DATASOURCEVERSION = "1.0"; } // Set URL SPARQLSERVERURL = serverUrlObject.toString(); } /** * Returns from String in order to retrieve information about these URIs * * * Properly access the triple store: For dsd and ds we query separate * graphs. * * @param uris * @return fromResult */ private String askForFrom(boolean isDsdQuery) { switch (SPARQLSERVERTYPE) { case QCRUMB: /* * * Qcrumb has special requirements for querying. The given named * graphs in qcrumbs case would already be the locations to be * queried. * * TODO: Think that through. Can we just query data using qcrumb? We * would simply... */ /* * Query for all the members of the dimension. * * The problem is now, how to only query for members of the specific * hierarchy? */ // First, for each dataset, I need the location // String dsFrom = askForFrom(datastructuredefinition // .toArray(new String[datastructuredefinition.size()])); // // // Get all dsd // String query = standardPrefix + "select ?ds ?dsd " + dsFrom // + "where { ?ds qb:structure ?dsd } "; // List<Node[]> dsdsdUris = sparql(query, true); // // // Now, for each dsd, I need the location // ArrayList<String> dsdArray = new ArrayList<String>(); // boolean first = true; // for (Node[] nodes1 : dsdsdUris) { // if (first) { // first = false; // } else { // // Create array of dsds // dsdArray.add(nodes1[1].toString()); // } // } // String dsdFrom = askForFrom(dsdArray.toArray(new String[dsdsdUris // .size() - 1])); // // /* // * TODO: How to consider equal dimensions? // */ // // // Get all dimensions, with // query = standardPrefix // + "select ?dimension ?dimensionCaption ?dimensionRange" // + dsFrom // + " " // + dsdFrom // + // "where { ?ds qb:structure ?dsd. ?dsd qb:component ?compSpec. ?compSpec qb:dimension ?dimension. OPTIONAL{?dimension rdfs:label ?dimensionCaption}. OPTIONAL{?dimension rdfs:range ?dimensionRange} } "; // List<Node[]> dimensionUris = sparql(query, true); // // // Now, for each dimension, I need the location // ArrayList<String> dimensionArray = new ArrayList<String>(); // boolean first1 = true; // for (Node[] nodes1 : dimensionUris) { // if (first1) { // first1 = false; // } else { // // Create array of dimensions // dimensionArray.add(nodes1[0].toString()); // } // } // String dimensionFrom = askForFrom(dimensionArray // .toArray(new String[dsdsdUris.size() - 1])); // // // String fromResult = ""; // for (String uri : uris) { // String fromLocation; // try { // fromLocation = askForLocation(uri); // fromResult += " from <" + fromLocation + "> "; // } catch (MalformedURLException e) { // // TODO Auto-generated catch block // e.printStackTrace(); // } // } // return fromResult; return null; case OPENVIRTUOSO: // This is our hard-coded graph that we use for OLAP4LD // We do not use several named graphs, yet. // return " from <http://olap4ld/> "; // return " from <http://constructed.com> "; // return // " from <http://vmdeb18.deri.ie:8080/saiku-ui-2.2.RC/#> from <http://vmdeb18.deri.ie:8080/saiku-ui-2.2.RC/#ffiec>"; /* * Depending on whether we want to query for dsd or data, we ask * different named graphs */ String fromResult = " "; if (isDsdQuery) { // create a from from dsd parameter for (String dsdGraph : datastructuredefinitions) { fromResult += "from <" + dsdGraph + "> "; } } else { // We query for all ds parameters for (String dsGraph : datasets) { fromResult += "from <" + dsGraph + "> "; // // Experiment, we query with Sponger // String spongerQuery = // "define get:refresh \"3600\" SELECT ?g FROM NAMED <" // + dsGraph // + // "> OPTION (get:soft \"soft\", get:method \"GET\") WHERE { graph ?g { ?s ?p ?o } } LIMIT 1"; // // List<Node[]> cubeUris = sparql(spongerQuery, false); } } return fromResult; case OPENVIRTUOSORULESET: // This is our hard-coded graph that we use for OLAP4LD // We do not use several named graphs, yet. // return " from <http://olap4ld/> "; // return " from <http://constructed.com> "; // return // " from <http://vmdeb18.deri.ie:8080/saiku-ui-2.2.RC/#> from <http://vmdeb18.deri.ie:8080/saiku-ui-2.2.RC/#ffiec>"; /* * Depending on whether we want to query for dsd or data, we ask * different named graphs */ fromResult = " "; if (isDsdQuery) { // create a from from dsd parameter for (String dsdGraph : datastructuredefinitions) { fromResult += "from <" + dsdGraph + "> "; } } else { // We query for all ds parameters for (String dsGraph : datasets) { fromResult += "from <" + dsGraph + "> "; } } return fromResult; default: return " "; } } /** * Helper Method for asking for location * * @param uri * @return * @throws MalformedURLException * @throws IOException */ @SuppressWarnings("unused") private String askForLocation(String uri) throws MalformedURLException { URL url; url = new URL(uri); HttpURLConnection.setFollowRedirects(false); HttpURLConnection connection; try { connection = (HttpURLConnection) url.openConnection(); connection.setRequestProperty("Accept", "application/rdf+xml"); String header = connection.getHeaderField("location"); // TODO: Could be that we need to check whether bogus comes out // (e.g., Not found). if (header != null) { if (header.startsWith("http:")) { uri = header; } else { // Header only contains the local uri // Cut all off until first / from the back int index = uri.lastIndexOf("/"); uri = uri.substring(0, index + 1) + header; } } } catch (IOException e) { throw new MalformedURLException(e.getMessage()); } if (uri.endsWith(".ttl")) { throw new MalformedURLException( "Qcrumb cannot handle non-rdf files, yet"); } return uri; } /** * I think, caching some sparql results would be very useful. * * I create a map between hash value of sparql query and the Nodes. * * If the value is available, I return it. * * However, when to empty the cache? I empty the cache if I populate a new * cube. * * @param query * @param caching * @return */ private List<Node[]> sparql(String query, Boolean caching) { // XXX: Caching on caching = true; Integer hash = null; if (caching) { hash = query.hashCode(); } if (caching) { if (this.sparqlResultMap.containsKey(hash)) { return this.sparqlResultMap.get(hash); } } Olap4ldUtil._log.config("SPARQL query: " + query); List<Node[]> result; switch (SPARQLSERVERTYPE) { case QCRUMB: result = sparqlQcrumb(query); break; case OPENVIRTUOSO: result = sparqlOpenVirtuoso(query); break; case OPENVIRTUOSORULESET: result = sparqlOpenVirtuosoRuleset(query); break; default: result = null; break; } if (caching) { if (this.sparqlResultMap != null && !this.sparqlResultMap.containsKey(hash)) { this.sparqlResultMap.put(hash, result); } else { // TODO: Problem: If this method is run simultaneously, it could // be that this happens. // throw new UnsupportedOperationException( // "Hash key should not be contained."); } } return result; } /** * If new cube is created, I empty the cache of the Linked Data Engine */ public void emptySparqlResultCache() { this.sparqlResultMap.clear(); } private List<Node[]> sparqlQcrumb(String query) { List<Node[]> myBindings = new ArrayList<Node[]>(); try { // Better? ISO-8859-1 String querysuffix = "?query=" + URLEncoder.encode(query, "UTF-8"); /* * Curly brackets should not be encoded, says qcrumb * * querysuffix = querysuffix.replaceAll("%7B", "{"); querysuffix = * querysuffix.replaceAll("%7D", "}"); * * Stimmt nicht: Das Problem ist NX-Darstellung und leere bindings. * * Momentan wird dazu einfach <null> verwendet. */ String rulesuffix = "&rules="; String acceptsuffix = "&accept=text%2Fnx"; String fullurl = SPARQLSERVERURL + querysuffix + rulesuffix + acceptsuffix; // String fullurl = // "http://qcrumb.com/sparql?query=PREFIX+sdmx-measure%3A+<http%3A%2F%2Fpurl.org%2Flinked-data%2Fsdmx%2F2009%2Fmeasure%23>%0D%0APREFIX+dcterms%3A+<http%3A%2F%2Fpurl.org%2Fdc%2Fterms%2F>%0D%0APREFIX+eus%3A+<http%3A%2F%2Fontologycentral.com%2F2009%2F01%2Feurostat%2Fns%23>%0D%0APREFIX+rdf%3A+<http%3A%2F%2Fwww.w3.org%2F1999%2F02%2F22-rdf-syntax-ns%23>%0D%0APREFIX+qb%3A+<http%3A%2F%2Fpurl.org%2Flinked-data%2Fcube%23>%0D%0APREFIX+rdfs%3A+<http%3A%2F%2Fwww.w3.org%2F2000%2F01%2Frdf-schema%23>%0D%0A%0D%0ASELECT+%3Ftime+%3Fvalue+%3Fgeo%0D%0AFROM+<http%3A%2F%2Festatwrap.ontologycentral.com%2Fdata%2Ftsieb020>%0D%0AFROM+<http%3A%2F%2Festatwrap.ontologycentral.com%2Fdic%2Fgeo>%0D%0AWHERE+{%0D%0A++%3Fs+qb%3Adataset+<http%3A%2F%2Festatwrap.ontologycentral.com%2Fid%2Ftsieb020%23ds>+.%0D%0A++%3Fs+dcterms%3Adate+%3Ftime+.%0D%0A++%3Fs+eus%3Ageo+%3Fg+.%0D%0A++%3Fg+rdfs%3Alabel+%3Fgeo+.%0D%0A++%3Fs+sdmx-measure%3AobsValue+%3Fvalue+.%0D%0A++FILTER+(lang(%3Fgeo)+%3D+\"\")%0D%0A}+ORDER+BY+%3Fgeo%0D%0A&rules=&accept=application%2Fsparql-results%2Bjson" HttpURLConnection con = (HttpURLConnection) new URL(fullurl) .openConnection(); // TODO: How to properly set header? Does not work therefore // manually // added con.setRequestProperty("Accept", "application/sparql-results+json"); con.setRequestMethod("POST"); if (con.getResponseCode() != 200) { throw new RuntimeException("lookup on " + fullurl + " resulted HTTP in status code " + con.getResponseCode()); } // String test = convertStreamToString(con.getInputStream()); NxParser nxp = new NxParser(con.getInputStream()); Node[] nxx; while (nxp.hasNext()) { nxx = nxp.next(); myBindings.add(nxx); } } catch (UnsupportedEncodingException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (MalformedURLException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } return myBindings; } private List<Node[]> sparqlOpenVirtuoso(String query) { List<Node[]> myBindings = new ArrayList<Node[]>(); try { // Better? ISO-8859-1 String querysuffix = "?query=" + URLEncoder.encode(query, "UTF-8"); /* * Curly brackets should not be encoded, says qcrumb * * querysuffix = querysuffix.replaceAll("%7B", "{"); querysuffix = * querysuffix.replaceAll("%7D", "}"); * * Stimmt nicht: Das Problem ist NX-Darstellung und leere bindings. * * Momentan wird dazu einfach <null> verwendet. */ // String acceptsuffix = "&accept=text%2Fn3"; String fullurl = SPARQLSERVERURL + querysuffix; // String fullurl = // "http://qcrumb.com/sparql?query=PREFIX+sdmx-measure%3A+<http%3A%2F%2Fpurl.org%2Flinked-data%2Fsdmx%2F2009%2Fmeasure%23>%0D%0APREFIX+dcterms%3A+<http%3A%2F%2Fpurl.org%2Fdc%2Fterms%2F>%0D%0APREFIX+eus%3A+<http%3A%2F%2Fontologycentral.com%2F2009%2F01%2Feurostat%2Fns%23>%0D%0APREFIX+rdf%3A+<http%3A%2F%2Fwww.w3.org%2F1999%2F02%2F22-rdf-syntax-ns%23>%0D%0APREFIX+qb%3A+<http%3A%2F%2Fpurl.org%2Flinked-data%2Fcube%23>%0D%0APREFIX+rdfs%3A+<http%3A%2F%2Fwww.w3.org%2F2000%2F01%2Frdf-schema%23>%0D%0A%0D%0ASELECT+%3Ftime+%3Fvalue+%3Fgeo%0D%0AFROM+<http%3A%2F%2Festatwrap.ontologycentral.com%2Fdata%2Ftsieb020>%0D%0AFROM+<http%3A%2F%2Festatwrap.ontologycentral.com%2Fdic%2Fgeo>%0D%0AWHERE+{%0D%0A++%3Fs+qb%3Adataset+<http%3A%2F%2Festatwrap.ontologycentral.com%2Fid%2Ftsieb020%23ds>+.%0D%0A++%3Fs+dcterms%3Adate+%3Ftime+.%0D%0A++%3Fs+eus%3Ageo+%3Fg+.%0D%0A++%3Fg+rdfs%3Alabel+%3Fgeo+.%0D%0A++%3Fs+sdmx-measure%3AobsValue+%3Fvalue+.%0D%0A++FILTER+(lang(%3Fgeo)+%3D+\"\")%0D%0A}+ORDER+BY+%3Fgeo%0D%0A&rules=&accept=application%2Fsparql-results%2Bjson" HttpURLConnection con = (HttpURLConnection) new URL(fullurl) .openConnection(); // TODO: How to properly set header? Does not work therefore // manually // added // con.setRequestProperty("Accept", // "application/sparql-results+json"); // con.setRequestProperty("Accept", "text/n3"); con.setRequestProperty("Accept", "application/sparql-results+xml"); con.setRequestMethod("POST"); if (con.getResponseCode() != 200) { throw new RuntimeException("lookup on " + fullurl + " resulted HTTP in status code " + con.getResponseCode()); } // InputStream inputStream = con.getInputStream(); // String test = XmlaOlap4jUtil.convertStreamToString(inputStream); // _log.config("XML output: " + test); // Transform sparql xml to nx InputStream nx = Olap4ldLinkedDataUtil.transformSparqlXmlToNx(con .getInputStream()); String test2 = Olap4ldLinkedDataUtil.convertStreamToString(nx); Olap4ldUtil._log.config("NX output: " + test2); nx.reset(); NxParser nxp = new NxParser(nx); Node[] nxx; while (nxp.hasNext()) { try { nxx = nxp.next(); myBindings.add(nxx); } catch (Exception e) { Olap4ldUtil._log .warning("NxParser: Could not parse properly: " + e.getMessage()); } ; } } catch (UnsupportedEncodingException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (MalformedURLException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } return myBindings; } private List<Node[]> sparqlOpenVirtuosoRuleset(String query) { List<Node[]> myBindings = new ArrayList<Node[]>(); try { // obsValue sameAs // String inferencesuffix = URLEncoder // .encode("define input:inference \"http://localhost:8890/schema/property_rules2\" \n", // "UTF-8"); // owl same as String inferencesuffix = URLEncoder.encode( "define input:same-as \"yes\" \n", "UTF-8"); // Better? ISO-8859-1 String querysuffix = "?query=" + inferencesuffix + URLEncoder.encode(query, "UTF-8"); /* * Curly brackets should not be encoded, says qcrumb * * querysuffix = querysuffix.replaceAll("%7B", "{"); querysuffix = * querysuffix.replaceAll("%7D", "}"); * * Stimmt nicht: Das Problem ist NX-Darstellung und leere bindings. * * Momentan wird dazu einfach <null> verwendet. */ // String acceptsuffix = "&accept=text%2Fn3"; String fullurl = SPARQLSERVERURL + querysuffix; // String fullurl = // "http://qcrumb.com/sparql?query=PREFIX+sdmx-measure%3A+<http%3A%2F%2Fpurl.org%2Flinked-data%2Fsdmx%2F2009%2Fmeasure%23>%0D%0APREFIX+dcterms%3A+<http%3A%2F%2Fpurl.org%2Fdc%2Fterms%2F>%0D%0APREFIX+eus%3A+<http%3A%2F%2Fontologycentral.com%2F2009%2F01%2Feurostat%2Fns%23>%0D%0APREFIX+rdf%3A+<http%3A%2F%2Fwww.w3.org%2F1999%2F02%2F22-rdf-syntax-ns%23>%0D%0APREFIX+qb%3A+<http%3A%2F%2Fpurl.org%2Flinked-data%2Fcube%23>%0D%0APREFIX+rdfs%3A+<http%3A%2F%2Fwww.w3.org%2F2000%2F01%2Frdf-schema%23>%0D%0A%0D%0ASELECT+%3Ftime+%3Fvalue+%3Fgeo%0D%0AFROM+<http%3A%2F%2Festatwrap.ontologycentral.com%2Fdata%2Ftsieb020>%0D%0AFROM+<http%3A%2F%2Festatwrap.ontologycentral.com%2Fdic%2Fgeo>%0D%0AWHERE+{%0D%0A++%3Fs+qb%3Adataset+<http%3A%2F%2Festatwrap.ontologycentral.com%2Fid%2Ftsieb020%23ds>+.%0D%0A++%3Fs+dcterms%3Adate+%3Ftime+.%0D%0A++%3Fs+eus%3Ageo+%3Fg+.%0D%0A++%3Fg+rdfs%3Alabel+%3Fgeo+.%0D%0A++%3Fs+sdmx-measure%3AobsValue+%3Fvalue+.%0D%0A++FILTER+(lang(%3Fgeo)+%3D+\"\")%0D%0A}+ORDER+BY+%3Fgeo%0D%0A&rules=&accept=application%2Fsparql-results%2Bjson" HttpURLConnection con = (HttpURLConnection) new URL(fullurl) .openConnection(); // TODO: How to properly set header? Does not work therefore // manually // added // con.setRequestProperty("Accept", // "application/sparql-results+json"); // con.setRequestProperty("Accept", "text/n3"); con.setRequestProperty("Accept", "application/sparql-results+xml"); con.setRequestMethod("POST"); if (con.getResponseCode() != 200) { throw new RuntimeException("lookup on " + fullurl + " resulted HTTP in status code " + con.getResponseCode()); } // InputStream inputStream = con.getInputStream(); // String test = XmlaOlap4jUtil.convertStreamToString(inputStream); // _log.info("XML output: " + test); // Transform sparql xml to nx InputStream nx = Olap4ldLinkedDataUtil.transformSparqlXmlToNx(con .getInputStream()); String test2 = Olap4ldLinkedDataUtil.convertStreamToString(nx); Olap4ldUtil._log.config("NX output: " + test2); nx.reset(); NxParser nxp = new NxParser(nx); Node[] nxx; while (nxp.hasNext()) { try { nxx = nxp.next(); myBindings.add(nxx); } catch (Exception e) { Olap4ldUtil._log .warning("NxParser: Could not parse properly: " + e.getMessage()); } ; } } catch (UnsupportedEncodingException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (MalformedURLException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } return myBindings; } /** * * @return */ public List<Node[]> getDatabases(Restrictions restrictions) { /* * DISCOVER_DATASOURCES(new MetadataColumn("DataSourceName"), new * MetadataColumn("DataSourceDescription"), new MetadataColumn("URL"), * new MetadataColumn("DataSourceInfo"), new MetadataColumn( * "ProviderName"), new MetadataColumn("ProviderType"), new * MetadataColumn("AuthenticationMode")), */ List<Node[]> results = new ArrayList<Node[]>(); Node[] bindingNames = new Node[] { new Variable("?DATA_SOURCE_NAME"), new Variable("?DATA_SOURCE_DESCRIPTION"), new Variable("?PROVIDER_NAME"), new Variable("?URL"), new Variable("?DATA_SOURCE_INFO") }; results.add(bindingNames); Node[] triple = new Node[] { new Literal(DATASOURCENAME), new Literal(DATASOURCEDESCRIPTION), new Literal(PROVIDERNAME), new Literal(URL), new Literal(DATASOURCEINFO) }; results.add(triple); return results; } public List<Node[]> getCatalogs(Restrictions restrictions) { /* * DBSCHEMA_CATALOGS( new MetadataColumn("CATALOG_NAME"), new * MetadataColumn( "DESCRIPTION"), new MetadataColumn("ROLES"), new * MetadataColumn("DATE_MODIFIED")) */ List<Node[]> results = new ArrayList<Node[]>(); Node[] bindingNames = new Node[] { new Variable("?TABLE_CAT") }; results.add(bindingNames); Node[] triple = new Node[] { new Literal(TABLE_CAT) }; results.add(triple); return results; } /** * * @return */ public List<Node[]> getSchemas(Restrictions restrictions) { List<Node[]> results = new ArrayList<Node[]>(); /* * DBSCHEMA_SCHEMATA(new MetadataColumn( "CATALOG_NAME"), new * MetadataColumn("SCHEMA_NAME"), new MetadataColumn("SCHEMA_OWNER")) */ Node[] bindingNames = new Node[] { new Variable("?TABLE_SCHEM"), new Variable("?TABLE_CAT") }; results.add(bindingNames); Node[] triple = new Node[] { new Literal(TABLE_SCHEM), new Literal(TABLE_CAT), // No owner new Literal("") }; results.add(triple); return results; } /** * * Get Cubes from the triple store. * * Here, the restrictions are strict restrictions without patterns. * * ==Task: Show proper captions== Problem: Where to take captions from? * rdfs:label Problem: There might be several rdfs:label -> only English * When creating such dsds, we could give the dsd an english label Also, we * need an english label for dimension, hierarchy, level, members Cell * Values will be numeric and not require language * * @return Node[]{} */ public List<Node[]> getCubes(Restrictions restrictions) { String additionalFilters = createFilterForRestrictions(restrictions); // If new cube is created, I empty the cache of the Linked Data Engine // this.emptySparqlResultCache(); /* * To improve performance, we filter for certain cubes. We either find * the cube name in the context or in the restrictions. * * Question: How do we cache? For all metadata queries, we cache the * SPARQL queries. If new cube is created, I empty the cache of the * Linked Data Engine. */ // String cubeURI = null; // if (context != null && context.olap4jCube != null) { // // The name should not have any brackets. // cubeURI = LdOlap4jUtil // .decodeUriForMdx(context.olap4jCube.getName()); // } // for (int i = 0; i < restrictions.length; i++) { // if (restrictions[i].equals("CUBE_NAME")) { // cubeURI = LdOlap4jUtil.decodeUriForMdx(restrictions[i + 1] // .toString()); // break; // } // } // Add filter for certain cube // additionalFilters += // "FILTER (?CUBE_NAME = <http://public.b-kaempgen.de:8080/fios#secyhofdsd>)"; // TODO: For now, we only use the non-language-tag CAPTION and // DESCRIPTION String query = Olap4ldLinkedDataUtil.getStandardPrefixes() + "select distinct \"" + TABLE_CAT + "\" as ?CATALOG_NAME \"" + TABLE_SCHEM + "\" as ?SCHEMA_NAME ?CUBE_NAME \"CUBE\" as ?CUBE_TYPE min(?DESCRIPTION) as ?DESCRIPTION min(?CUBE_CAPTION) as ?CUBE_CAPTION " + askForFrom(true) + "where { ?ds qb:structure ?CUBE_NAME. ?CUBE_NAME a qb:DataStructureDefinition. OPTIONAL {?CUBE_NAME rdfs:label ?CUBE_CAPTION FILTER ( lang(?CUBE_CAPTION) = \"en\" )} OPTIONAL {?CUBE_NAME rdfs:comment ?DESCRIPTION FILTER ( lang(?DESCRIPTION) = \"en\" )} " + additionalFilters + "}" + "group by ?CUBE_NAME order by ?CUBE_NAME"; List<Node[]> result = sparql(query, true); /* * Check on restrictions that the interface makes: * * Restrictions are strong restrictions, no fuzzy, since those wild * cards have been eliminated before. */ // List<Node[]> result = applyRestrictions(cubeUris, restrictions); return result; } /** * Every measure also needs to be listed as member. When I create the dsd, I * add obsValue as a dimension, but also as a measure. However, members of * the measure dimension would typically all be named differently from the * measure (e.g., obsValue5), therefore, we do not find a match. The problem * is, that getMembers() has to return the measures. So, either, in the dsd, * we need to add a dimension with the measure as a member, or, the query * for the members should return for measures the measure property as * member. * * * Here, all the measure properties are returned. * * @param context * @param metadataRequest * @param restrictions * @return */ public List<Node[]> getMeasures(Restrictions restrictions) { String additionalFilters = createFilterForRestrictions(restrictions); // ///////////QUERY////////////////////////// /* * TODO: How to consider equal measures? */ // Boolean values need to be returned as "true" or "false". // Get all measures String query = Olap4ldLinkedDataUtil.getStandardPrefixes() + "select \"" + TABLE_CAT + "\" as ?CATALOG_NAME \"" + TABLE_SCHEM + "\" as ?SCHEMA_NAME ?CUBE_NAME ?MEASURE_UNIQUE_NAME ?MEASURE_UNIQUE_NAME as ?MEASURE_NAME min(?MEASURE_CAPTION) as ?MEASURE_CAPTION \"5\" as ?DATA_TYPE \"true\" as ?MEASURE_IS_VISIBLE min(?MEASURE_AGGREGATOR) as ?MEASURE_AGGREGATOR min(?EXPRESSION) as ?EXPRESSION " + askForFrom(true) // Hint: For now, MEASURE_CAPTION is simply the // MEASURE_DIMENSION. // Important: Only measures are queried // XXX: EXPRESSION also needs to be attached to // ?COMPONENT_SPECIFICATION + " where { ?CUBE_NAME qb:component ?COMPONENT_SPECIFICATION " + ". ?COMPONENT_SPECIFICATION qb:measure ?MEASURE_UNIQUE_NAME. OPTIONAL {?MEASURE_UNIQUE_NAME rdfs:label ?MEASURE_CAPTION FILTER ( lang(?MEASURE_CAPTION) = \"en\" )} OPTIONAL {?COMPONENT_SPECIFICATION qb:aggregator ?MEASURE_AGGREGATOR } OPTIONAL {?COMPONENT_SPECIFICATION qb:expression ?EXPRESSION } " + additionalFilters + "} " + "group by ?CUBE_NAME ?MEASURE_UNIQUE_NAME order by ?CUBE_NAME ?MEASURE_UNIQUE_NAME "; List<Node[]> result = sparql(query, true); // List<Node[]> result = applyRestrictions(measureUris, restrictions); return result; } /** * Get possible dimensions (component properties) for each cube from the * triple store. * * Approach: I create the output from Linked Data, and then I filter it * using the restrictions. * * I have to also return the Measures dimension for each cube. * * @return Node[]{?dsd ?dimension ?compPropType ?name} * @throws MalformedURLException */ public List<Node[]> getDimensions(Restrictions restrictions) { String additionalFilters = createFilterForRestrictions(restrictions); // Get all dimensions String query = ""; query = Olap4ldLinkedDataUtil.getStandardPrefixes() + "select \"" + TABLE_CAT + "\" as ?CATALOG_NAME \"" + TABLE_SCHEM + "\" as ?SCHEMA_NAME ?CUBE_NAME ?DIMENSION_UNIQUE_NAME as ?DIMENSION_NAME ?DIMENSION_UNIQUE_NAME min(?DIMENSION_CAPTION) as ?DIMENSION_CAPTION min(?DESCRIPTION) as ?DESCRIPTION \"0\" as ?DIMENSION_TYPE \"0\" as ?DIMENSION_ORDINAL " + askForFrom(true) + " where { ?CUBE_NAME qb:component ?compSpec " + ". ?compSpec qb:dimension ?DIMENSION_UNIQUE_NAME. OPTIONAL {?DIMENSION_UNIQUE_NAME rdfs:label ?DIMENSION_CAPTION FILTER ( lang(?DIMENSION_CAPTION) = \"en\" )} OPTIONAL {?DIMENSION_UNIQUE_NAME rdfs:comment ?DESCRIPTION FILTER ( lang(?DESCRIPTION) = \"en\" )} " + additionalFilters + "} " + "group by ?CUBE_NAME ?DIMENSION_UNIQUE_NAME order by ?CUBE_NAME ?DIMENSION_NAME "; List<Node[]> result = sparql(query, true); // List<Node[]> result = applyRestrictions(dimensionUris, restrictions); /* * Get all measures: We query for all cubes and simply add measure * information. * * We need "distinct" in case we have several measures per cube. */ if (isMeasureQueriedFor(restrictions.dimensionUniqueName, restrictions.hierarchyUniqueName, restrictions.levelUniqueName)) { // Here, we again need specific filters, since only cube makes // sense String specificFilters = ""; if (restrictions.cubeNamePattern != null) { specificFilters = " FILTER (?CUBE_NAME = <" + restrictions.cubeNamePattern + ">) "; } // In this case, we do ask for a measure dimension. query = Olap4ldLinkedDataUtil.getStandardPrefixes() + "select distinct \"" + TABLE_CAT + "\" as ?CATALOG_NAME \"" + TABLE_SCHEM + "\" as ?SCHEMA_NAME ?CUBE_NAME \"Measures\" as ?DIMENSION_NAME \"Measures\" as ?DIMENSION_UNIQUE_NAME \"Measures\" as ?DIMENSION_CAPTION \"Measures\" as ?DESCRIPTION \"2\" as ?DIMENSION_TYPE \"0\" as ?DIMENSION_ORDINAL " + askForFrom(true) + " where { ?CUBE_NAME qb:component ?compSpec " + ". ?compSpec qb:measure ?MEASURE_PROPERTY. " + specificFilters + "} " + "order by ?CUBE_NAME ?MEASURE_PROPERTY"; List<Node[]> result2 = sparql(query, true); // List<Node[]> result2 = applyRestrictions(memberUris2, // restrictions); // Add all of result2 to result boolean first = true; for (Node[] nodes : result2) { if (first) { first = false; continue; } result.add(nodes); } } return result; } /** * We query for a measure if dim, hier, levl = measure OR if nothing is * stated. * * @param dimensionUniqueName * @param hierarchyUniqueName * @param levelUniqueName * @return */ private boolean isMeasureQueriedFor(Node dimensionUniqueName, Node hierarchyUniqueName, Node levelUniqueName) { // If one is set, it should not be Measures, not. // Watch out: square brackets are needed. boolean notExplicitlyStated = dimensionUniqueName == null && hierarchyUniqueName == null && levelUniqueName == null; boolean explicitlyStated = (dimensionUniqueName != null && dimensionUniqueName .equals("Measures")) || (hierarchyUniqueName != null && hierarchyUniqueName .equals("Measures")) || (levelUniqueName != null && levelUniqueName .equals("Measures")); return notExplicitlyStated || explicitlyStated; } /** * * Return hierarchies * * @param context * @param metadataRequest * @param restrictions * @return */ public List<Node[]> getHierarchies(Restrictions restrictions) { String additionalFilters = createFilterForRestrictions(restrictions); // Get all hierarchies with codeLists String query = Olap4ldLinkedDataUtil.getStandardPrefixes() + "select \"" + TABLE_CAT + "\" as ?CATALOG_NAME \"" + TABLE_SCHEM + "\" as ?SCHEMA_NAME ?CUBE_NAME ?DIMENSION_UNIQUE_NAME ?HIERARCHY_UNIQUE_NAME ?HIERARCHY_UNIQUE_NAME as ?HIERARCHY_NAME min(?HIERARCHY_CAPTION) as ?HIERARCHY_CAPTION min(?DESCRIPTION) as ?DESCRIPTION " + askForFrom(true) + " where { ?CUBE_NAME qb:component ?compSpec " + ". ?compSpec qb:dimension ?DIMENSION_UNIQUE_NAME. ?DIMENSION_UNIQUE_NAME qb:codeList ?HIERARCHY_UNIQUE_NAME. OPTIONAL {?HIERARCHY_UNIQUE_NAME rdfs:label ?HIERARCHY_CAPTION FILTER ( lang(?HIERARCHY_CAPTION) = \"en\" ) } OPTIONAL {?HIERARCHY_UNIQUE_NAME rdfs:comment ?DESCRIPTION FILTER ( lang(?DESCRIPTION) = \"en\" ) } " + additionalFilters + "} " + "group by ?CUBE_NAME ?DIMENSION_UNIQUE_NAME ?HIERARCHY_UNIQUE_NAME order by ?CUBE_NAME ?DIMENSION_UNIQUE_NAME ?HIERARCHY_UNIQUE_NAME "; List<Node[]> result = sparql(query, true); // TODO: No sorting done, yet // List<Node[]> result = applyRestrictions(hierarchyResults, // restrictions); // Get measure dimensions, but only if neither dim, hier, lev is set and // not "Measures". if (isMeasureQueriedFor(restrictions.dimensionUniqueName, restrictions.hierarchyUniqueName, restrictions.levelUniqueName)) { // Here, we again need specific filters, since only cube makes // sense String specificFilters = ""; if (restrictions.cubeNamePattern != null) { specificFilters = " FILTER (?CUBE_NAME = <" + restrictions.cubeNamePattern + ">) "; } // In this case, we do ask for a measure hierarchy. query = Olap4ldLinkedDataUtil.getStandardPrefixes() + "select distinct \"" + TABLE_CAT + "\" as ?CATALOG_NAME \"" + TABLE_SCHEM + "\" as ?SCHEMA_NAME ?CUBE_NAME \"Measures\" as ?DIMENSION_UNIQUE_NAME \"Measures\" as ?HIERARCHY_UNIQUE_NAME \"Measures\" as ?HIERARCHY_NAME \"Measures\" as ?HIERARCHY_CAPTION \"Measures\" as ?DESCRIPTION " + askForFrom(true) + " where { ?CUBE_NAME qb:component ?compSpec " + ". ?compSpec qb:measure ?MEASURE_PROPERTY. " + specificFilters + "} order by ?CUBE_NAME ?MEASURE_PROPERTY ?HIERARCHY_NAME "; List<Node[]> result2 = sparql(query, true); // List<Node[]> result2 = applyRestrictions(memberUris2, // restrictions); // Add all of result2 to result boolean first = true; for (Node[] nodes : result2) { if (first) { first = false; continue; } result.add(nodes); } } // Get dimension hierarchies without codeList, but only if hierarchy is // not set and different from dimension unique name if (restrictions.hierarchyUniqueName != null && !restrictions.hierarchyUniqueName .equals(restrictions.dimensionUniqueName)) { // we do not need to do this } else { query = Olap4ldLinkedDataUtil.getStandardPrefixes() + "select \"" + TABLE_CAT + "\" as ?CATALOG_NAME \"" + TABLE_SCHEM + "\" as ?SCHEMA_NAME ?CUBE_NAME ?DIMENSION_UNIQUE_NAME ?DIMENSION_UNIQUE_NAME as ?HIERARCHY_UNIQUE_NAME ?DIMENSION_UNIQUE_NAME as ?HIERARCHY_NAME ?DIMENSION_UNIQUE_NAME as ?HIERARCHY_CAPTION ?DIMENSION_UNIQUE_NAME as ?DESCRIPTION " + askForFrom(true) + " where { ?CUBE_NAME qb:component ?compSpec " + ". ?compSpec qb:dimension ?DIMENSION_UNIQUE_NAME. FILTER NOT EXISTS { ?DIMENSION_UNIQUE_NAME qb:codeList ?HIERARCHY_UNIQUE_NAME. } " + additionalFilters + "} order by ?CUBE_NAME ?DIMENSION_UNIQUE_NAME "; List<Node[]> result3 = sparql(query, true); // List<Node[]> result3 = applyRestrictions(memberUris3, // restrictions); // Add all of result2 to result boolean first = true; for (Node[] nodes : result3) { if (first) { first = false; continue; } result.add(nodes); } } return result; } /** * * @param context * @param metadataRequest * @param restrictions * @return */ public List<Node[]> getLevels(Restrictions restrictions) { String additionalFilters = createFilterForRestrictions(restrictions); // Get all levels of code lists without levels // TODO: LEVEL_CARDINALITY is not solved, yet. String query = Olap4ldLinkedDataUtil.getStandardPrefixes() + "select \"" + TABLE_CAT + "\" as ?CATALOG_NAME \"" + TABLE_SCHEM + "\" as ?SCHEMA_NAME ?CUBE_NAME ?DIMENSION_UNIQUE_NAME ?HIERARCHY_UNIQUE_NAME ?HIERARCHY_UNIQUE_NAME as ?LEVEL_UNIQUE_NAME ?HIERARCHY_UNIQUE_NAME as ?LEVEL_CAPTION ?HIERARCHY_UNIQUE_NAME as ?LEVEL_NAME \"N/A\" as ?DESCRIPTION \"1\" as ?LEVEL_NUMBER \"0\" as ?LEVEL_CARDINALITY \"0x0000\" as ?LEVEL_TYPE " + askForFrom(true) + " where { ?CUBE_NAME qb:component ?compSpec " + ". ?compSpec qb:dimension ?DIMENSION_UNIQUE_NAME. ?DIMENSION_UNIQUE_NAME qb:codeList ?HIERARCHY_UNIQUE_NAME. FILTER NOT EXISTS { ?LEVEL_UNIQUE_NAME skos:inScheme ?HIERARCHY_UNIQUE_NAME. }" + additionalFilters + " }" + " order by ?CUBE_NAME ?DIMENSION_UNIQUE_NAME ?HIERARCHY_UNIQUE_NAME ?LEVEL_NUMBER "; List<Node[]> result = sparql(query, true); // List<Node[]> result = applyRestrictions(levelResults, restrictions); // TODO: No sorting done, yet // TODO: Add properly modeled level query = Olap4ldLinkedDataUtil.getStandardPrefixes() + "select \"" + TABLE_CAT + "\" as ?CATALOG_NAME \"" + TABLE_SCHEM + "\" as ?SCHEMA_NAME ?CUBE_NAME ?DIMENSION_UNIQUE_NAME ?HIERARCHY_UNIQUE_NAME ?LEVEL_UNIQUE_NAME min(?LEVEL_CAPTION) as ?LEVEL_CAPTION ?LEVEL_UNIQUE_NAME as ?LEVEL_NAME min(?DESCRIPTION) as ?DESCRIPTION ?LEVEL_NUMBER \"0\" as ?LEVEL_CARDINALITY \"0x0000\" as ?LEVEL_TYPE " + askForFrom(true) + " where { ?CUBE_NAME qb:component ?compSpec " + ". ?compSpec qb:dimension ?DIMENSION_UNIQUE_NAME. ?DIMENSION_UNIQUE_NAME qb:codeList ?HIERARCHY_UNIQUE_NAME. ?LEVEL_UNIQUE_NAME skos:inScheme ?HIERARCHY_UNIQUE_NAME. ?LEVEL_UNIQUE_NAME skosclass:depth ?LEVEL_NUMBER. OPTIONAL {?LEVEL_UNIQUE_NAME rdfs:label ?LEVEL_CAPTION FILTER ( lang(?LEVEL_CAPTION) = \"en\" ) } OPTIONAL {?LEVEL_UNIQUE_NAME rdfs:comment ?DESCRIPTION FILTER ( lang(?DESCRIPTION) = \"en\" ) }" + additionalFilters + "}" + "group by ?CUBE_NAME ?DIMENSION_UNIQUE_NAME ?HIERARCHY_UNIQUE_NAME ?LEVEL_UNIQUE_NAME ?LEVEL_NUMBER order by ?CUBE_NAME ?DIMENSION_UNIQUE_NAME ?HIERARCHY_UNIQUE_NAME ?LEVEL_NUMBER "; List<Node[]> result0 = sparql(query, true); // List<Node[]> result0 = applyRestrictions(memberUris0, restrictions); // Add all of result2 to result boolean first = true; for (Node[] nodes : result0) { if (first) { first = false; continue; } result.add(nodes); } // Distinct for several measures per cube. // Add measures levels // Second, ask for the measures (which are also members), but only if // measure if (isMeasureQueriedFor(restrictions.dimensionUniqueName, restrictions.hierarchyUniqueName, restrictions.levelUniqueName)) { // Here, we again need specific filters, since only cube makes // sense String specificFilters = ""; if (restrictions.cubeNamePattern != null) { specificFilters = " FILTER (?CUBE_NAME = <" + restrictions.cubeNamePattern + ">) "; } // In this case, we do ask for a measure dimension. query = Olap4ldLinkedDataUtil.getStandardPrefixes() + "select distinct \"" + TABLE_CAT + "\" as ?CATALOG_NAME \"" + TABLE_SCHEM + "\" as ?SCHEMA_NAME ?CUBE_NAME \"Measures\" as ?DIMENSION_UNIQUE_NAME \"Measures\" as ?HIERARCHY_UNIQUE_NAME \"Measures\" as ?LEVEL_UNIQUE_NAME \"Measures\" as ?LEVEL_CAPTION \"Measures\" as ?LEVEL_NAME \"Measures\" as ?DESCRIPTION \"1\" as ?LEVEL_NUMBER \"0\" as ?LEVEL_CARDINALITY \"0x0000\" as ?LEVEL_TYPE " + askForFrom(true) + " where { ?CUBE_NAME qb:component ?compSpec " + ". ?compSpec qb:measure ?MEASURE_PROPERTY. " + specificFilters + "} " + "order by ?MEASURE_PROPERTY"; List<Node[]> result2 = sparql(query, true); // List<Node[]> result2 = applyRestrictions(memberUris2, // restrictions); // Add all of result2 to result first = true; for (Node[] nodes : result2) { if (first) { first = false; continue; } result.add(nodes); } } // Add levels for dimensions without codelist, but only if hierarchy and // dimension names are equal if (restrictions.hierarchyUniqueName != null && !restrictions.hierarchyUniqueName .equals(restrictions.dimensionUniqueName)) { // we do not need to do this } else { // We need specific filter String specificFilters = ""; if (restrictions.cubeNamePattern != null) { specificFilters += " FILTER (?CUBE_NAME = <" + restrictions.cubeNamePattern + ">) "; } if (restrictions.dimensionUniqueName != null) { specificFilters += " FILTER (?DIMENSION_UNIQUE_NAME = <" + restrictions.dimensionUniqueName + ">) "; } query = Olap4ldLinkedDataUtil.getStandardPrefixes() + "select \"" + TABLE_CAT + "\" as ?CATALOG_NAME \"" + TABLE_SCHEM + "\" as ?SCHEMA_NAME ?CUBE_NAME ?DIMENSION_UNIQUE_NAME ?DIMENSION_UNIQUE_NAME as ?HIERARCHY_UNIQUE_NAME ?DIMENSION_UNIQUE_NAME as ?LEVEL_UNIQUE_NAME ?DIMENSION_UNIQUE_NAME as ?LEVEL_CAPTION ?DIMENSION_UNIQUE_NAME as ?LEVEL_NAME \"N/A\" as ?DESCRIPTION \"1\" as ?LEVEL_NUMBER \"0\" as ?LEVEL_CARDINALITY \"0x0000\" as ?LEVEL_TYPE " + askForFrom(true) + " where { ?CUBE_NAME qb:component ?compSpec " + ". ?compSpec qb:dimension ?DIMENSION_UNIQUE_NAME. FILTER NOT EXISTS { ?DIMENSION_UNIQUE_NAME qb:codeList ?HIERARCHY_UNIQUE_NAME. } " + specificFilters + "} " + "order by ?CUBE_NAME ?DIMENSION_UNIQUE_NAME ?LEVEL_NUMBER "; // Second, ask for the measures (which are also members) List<Node[]> result3 = sparql(query, true); // List<Node[]> result3 = applyRestrictions(memberUris3, // restrictions); // Add all of result3 to result first = true; for (Node[] nodes : result3) { if (first) { first = false; continue; } result.add(nodes); } } return result; } /** * Important issues to remember: Every measure also needs to be listed as * member. When I create the dsd, I add obsValue as a dimension, but also as * a measure. However, members of the measure dimension would typically all * be named differently from the measure (e.g., obsValue5), therefore, we do * not find a match. The problem is, that getMembers() has to return the * measures. So, either, in the dsd, we need to add a dimension with the * measure as a member, or, the query for the members should return for * measures the measure property as member. * * The dimension/hierarchy/level of a measure should always be "Measures". * * Typically, a measure should not have a codeList, since we can have many * many members. If a measure does not have a codelist, the bounding would * still work, since The componentProperty is existing, but no hierarchy... * * For caption of members, we should eventually use * http://www.w3.org/2004/02/skos/core#notation skos:notation, since members * are in rdf represented as skos:Concept and this is the proper way to give * them a representation. * * Assumptions of this method: * * The restrictions are set up only as follows 1) cube, dim, hier, level 2) * cube, dim, hier, level, member, null 3) cube, dim, hier, level, member, * treeOp * * The members are only modelled as follows 1) Measure Member (member of the * measure dimension) 2) Level Member (member of a regular dimension) 3) Top * Concept Member (member via skos:topConcept) 4) Degenerated Member (member * without code list) * * @return Node[]{?memberURI ?name} * @throws MalformedURLException */ public List<Node[]> getMembers(Restrictions restrictions) { List<Node[]> result = new ArrayList<Node[]>(); List<Node[]> intermediaryresult = null; // Create header Node[] header = new Node[] { new Variable("?CATALOG_NAME"), new Variable("?SCHEMA_NAME"), new Variable("?CUBE_NAME"), new Variable("?DIMENSION_UNIQUE_NAME"), new Variable("?HIERARCHY_UNIQUE_NAME"), new Variable("?LEVEL_UNIQUE_NAME"), new Variable("?LEVEL_NUMBER"), new Variable("?MEMBER_NAME"), new Variable("?MEMBER_UNIQUE_NAME"), new Variable("?MEMBER_CAPTION"), new Variable("?MEMBER_TYPE"), new Variable("?PARENT_UNIQUE_NAME"), new Variable("?PARENT_LEVEL") }; result.add(header); // Measure Member if (isMeasureQueriedFor(restrictions.dimensionUniqueName, restrictions.hierarchyUniqueName, restrictions.levelUniqueName)) { intermediaryresult = getMeasureMembers(restrictions); addToResult(intermediaryresult, result); } // Normal Member // Watch out: No square brackets if ((restrictions.dimensionUniqueName == null || !restrictions.dimensionUniqueName .equals("Measures")) || (restrictions.hierarchyUniqueName == null || !restrictions.hierarchyUniqueName .equals("Measures")) || (restrictions.levelUniqueName == null || !restrictions.levelUniqueName .equals("Measures"))) { intermediaryresult = getLevelMembers(restrictions); addToResult(intermediaryresult, result); } // If we still do not have members, then we might have top members // only if (result.size() == 1) { intermediaryresult = getHasTopConceptMembers(restrictions); addToResult(intermediaryresult, result); } // If we still do not have members, then we might have degenerated // members if (result.size() == 1) { // Members without codeList intermediaryresult = getDegeneratedMembers(restrictions); addToResult(intermediaryresult, result); } return result; } private List<Node[]> getMeasureMembers(Restrictions restrictions) { String additionalFilters = ""; /* * I would assume that if TREE_OP is set, we have a unique member given * and either want its children, its siblings, its parent, self, * ascendants, or descendants. */ if (restrictions.tree != null && (restrictions.tree & 8) != 8) { // Assumption 1: Treeop only uses Member if (restrictions.memberUniqueName == null) { throw new UnsupportedOperationException( "If a treeMask is given, we should also have a unique member name!"); } if ((restrictions.tree & 1) == 1) { // CHILDREN Olap4ldUtil._log.config("TreeOp:CHILDREN"); } if ((restrictions.tree & 2) == 2) { // SIBLINGS Olap4ldUtil._log.config("TreeOp:SIBLINGS"); if (restrictions.cubeNamePattern != null) { additionalFilters += " FILTER (?CUBE_NAME = <" + restrictions.cubeNamePattern + ">) "; } } if ((restrictions.tree & 4) == 4) { // PARENT Olap4ldUtil._log.config("TreeOp:PARENT"); } if ((restrictions.tree & 16) == 16) { // DESCENDANTS Olap4ldUtil._log.config("TreeOp:DESCENDANTS"); } if ((restrictions.tree & 32) == 32) { // ANCESTORS Olap4ldUtil._log.config("TreeOp:ANCESTORS"); } } else { // TreeOp = Self or null Olap4ldUtil._log.config("TreeOp:SELF"); if (restrictions.cubeNamePattern != null) { additionalFilters += " FILTER (?CUBE_NAME = <" + restrictions.cubeNamePattern + ">) "; } if (restrictions.memberUniqueName != null) { additionalFilters += " FILTER (?MEASURE_UNIQUE_NAME = <" + restrictions.memberUniqueName + ">) "; } } // Second, ask for the measures (which are also members) String query = Olap4ldLinkedDataUtil.getStandardPrefixes() + "Select \"" + TABLE_CAT + "\" as ?CATALOG_NAME \"" + TABLE_SCHEM + "\" as ?SCHEMA_NAME ?CUBE_NAME \"Measures\" as ?DIMENSION_UNIQUE_NAME \"Measures\" as ?HIERARCHY_UNIQUE_NAME \"Measures\" as ?LEVEL_UNIQUE_NAME \"0\" as ?LEVEL_NUMBER ?MEASURE_UNIQUE_NAME as ?MEMBER_UNIQUE_NAME ?MEASURE_UNIQUE_NAME as ?MEMBER_NAME min(?MEMBER_CAPTION) as ?MEMBER_CAPTION \"3\" as ?MEMBER_TYPE \"null\" as ?PARENT_UNIQUE_NAME \"0\" as ?PARENT_LEVEL " + askForFrom(true) + " where { ?CUBE_NAME qb:component ?COMPONENT_SPECIFICATION. " + "?COMPONENT_SPECIFICATION qb:measure ?MEASURE_UNIQUE_NAME. OPTIONAL {?MEASURE_UNIQUE_NAME rdfs:label ?MEMBER_CAPTION FILTER ( lang(?MEMBER_CAPTION) = \"en\" OR lang(?MEMBER_CAPTION) = \"\" ) } " + additionalFilters + "} " + "group by ?CUBE_NAME ?MEASURE_UNIQUE_NAME order by ?MEASURE_UNIQUE_NAME"; List<Node[]> memberUris2 = sparql(query, true); return memberUris2; } /** * Finds specific typical members. * * @param restrictions * * @return */ private List<Node[]> getLevelMembers(Restrictions restrictions) { String query = ""; String additionalFilters = createFilterForRestrictions(restrictions); List<Node[]> intermediaryresult; /* * I would assume that if TREE_OP is set, we have a unique member given * and either want its children, its siblings, its parent, self, * ascendants, or descendants. */ if (restrictions.tree != null && (restrictions.tree & 8) != 8) { // Assumption 1: Treeop only uses Member if (restrictions.memberUniqueName == null) { throw new UnsupportedOperationException( "If a treeMask is given, we should also have a unique member name!"); } if ((restrictions.tree & 1) == 1) { // CHILDREN Olap4ldUtil._log.config("TreeOp:CHILDREN"); // Here, we need a specific filter additionalFilters = " FILTER (?PARENT_UNIQUE_NAME = <" + restrictions.memberUniqueName + ">) "; query = Olap4ldLinkedDataUtil.getStandardPrefixes() + "Select \"" + TABLE_CAT + "\" as ?CATALOG_NAME \"" + TABLE_SCHEM + "\" as ?SCHEMA_NAME ?CUBE_NAME ?DIMENSION_UNIQUE_NAME ?HIERARCHY_UNIQUE_NAME ?LEVEL_UNIQUE_NAME ?LEVEL_NUMBER ?MEMBER_UNIQUE_NAME as ?MEMBER_NAME ?MEMBER_UNIQUE_NAME min(?MEMBER_CAPTION) as ?MEMBER_CAPTION \"1\" as ?MEMBER_TYPE ?PARENT_UNIQUE_NAME min(?PARENT_LEVEL) as ?PARENT_LEVEL " + askForFrom(true) + " where { ?CUBE_NAME qb:component ?compSpec. " + "?compSpec qb:dimension ?DIMENSION_UNIQUE_NAME. ?DIMENSION_UNIQUE_NAME qb:codeList ?HIERARCHY_UNIQUE_NAME. ?LEVEL_UNIQUE_NAME skos:inScheme ?HIERARCHY_UNIQUE_NAME. ?MEMBER_UNIQUE_NAME skos:member ?LEVEL_UNIQUE_NAME. ?LEVEL_UNIQUE_NAME skosclass:depth ?LEVEL_NUMBER. " + "?MEMBER_UNIQUE_NAME skos:narrower ?PARENT_UNIQUE_NAME. ?PARENT_UNIQUE_NAME skos:member ?PARENT_LEVEL_UNIQUE_NAME. ?PARENT_LEVEL_UNIQUE_NAME skosclass:depth ?PARENT_LEVEL. OPTIONAL {?MEMBER_UNIQUE_NAME skos:notation ?MEMBER_CAPTION FILTER ( lang(?MEMBER_CAPTION) = \"en\" OR lang(?MEMBER_CAPTION) = \"\" ) } " + additionalFilters + "} " + " group by ?CUBE_NAME ?DIMENSION_UNIQUE_NAME ?HIERARCHY_UNIQUE_NAME ?LEVEL_UNIQUE_NAME ?LEVEL_NUMBER ?PARENT_UNIQUE_NAME ?MEMBER_UNIQUE_NAME order by ?CUBE_NAME ?DIMENSION_UNIQUE_NAME ?HIERARCHY_UNIQUE_NAME ?LEVEL_UNIQUE_NAME ?LEVEL_NUMBER ?MEMBER_UNIQUE_NAME"; intermediaryresult = sparql(query, true); return intermediaryresult; } if ((restrictions.tree & 2) == 2) { // SIBLINGS Olap4ldUtil._log.config("TreeOp:SIBLINGS"); } if ((restrictions.tree & 4) == 4) { // PARENT Olap4ldUtil._log.config("TreeOp:PARENT"); } if ((restrictions.tree & 16) == 16) { // DESCENDANTS Olap4ldUtil._log.config("TreeOp:DESCENDANTS"); } if ((restrictions.tree & 32) == 32) { // ANCESTORS Olap4ldUtil._log.config("TreeOp:ANCESTORS"); } throw new UnsupportedOperationException( "TreeOp and getLevelMember failed."); } else { // TreeOp = Self or null Olap4ldUtil._log.config("TreeOp:SELF"); // XXX: Can be aligned with other sparql query?? query = Olap4ldLinkedDataUtil.getStandardPrefixes() + "Select \"" + TABLE_CAT + "\" as ?CATALOG_NAME \"" + TABLE_SCHEM + "\" as ?SCHEMA_NAME ?CUBE_NAME ?DIMENSION_UNIQUE_NAME ?HIERARCHY_UNIQUE_NAME ?LEVEL_UNIQUE_NAME ?LEVEL_NUMBER ?MEMBER_UNIQUE_NAME as ?MEMBER_NAME ?MEMBER_UNIQUE_NAME min(?MEMBER_CAPTION) as ?MEMBER_CAPTION \"1\" as ?MEMBER_TYPE ?PARENT_UNIQUE_NAME min(?PARENT_LEVEL) as ?PARENT_LEVEL " + askForFrom(true) + " where { ?CUBE_NAME qb:component ?compSpec. " + "?compSpec qb:dimension ?DIMENSION_UNIQUE_NAME. ?DIMENSION_UNIQUE_NAME qb:codeList ?HIERARCHY_UNIQUE_NAME. ?LEVEL_UNIQUE_NAME skos:inScheme ?HIERARCHY_UNIQUE_NAME. ?MEMBER_UNIQUE_NAME skos:member ?LEVEL_UNIQUE_NAME. ?LEVEL_UNIQUE_NAME skosclass:depth ?LEVEL_NUMBER. " + "OPTIONAL { ?MEMBER_UNIQUE_NAME skos:narrower ?PARENT_UNIQUE_NAME. ?PARENT_UNIQUE_NAME skosclass:depth ?PARENT_LEVEL. }" + " OPTIONAL {?MEMBER_UNIQUE_NAME skos:notation ?MEMBER_CAPTION FILTER ( lang(?MEMBER_CAPTION) = \"en\" OR lang(?MEMBER_CAPTION) = \"\" ) } " + additionalFilters + "} " + "group by ?CUBE_NAME ?DIMENSION_UNIQUE_NAME ?HIERARCHY_UNIQUE_NAME ?LEVEL_UNIQUE_NAME ?LEVEL_NUMBER ?PARENT_UNIQUE_NAME ?MEMBER_UNIQUE_NAME order by ?CUBE_NAME ?DIMENSION_UNIQUE_NAME ?HIERARCHY_UNIQUE_NAME ?LEVEL_UNIQUE_NAME ?LEVEL_NUMBER ?MEMBER_UNIQUE_NAME"; List<Node[]> memberUris2 = sparql(query, true); return memberUris2; } } /** * Returns all hasTopConcept members of the cube. * * @param dimensionUniqueName * @param cubeNamePattern * * @param cubeNamePattern * @return */ private List<Node[]> getHasTopConceptMembers(Restrictions restrictions) { String query = ""; /* * I would assume that if TREE_OP is set, we have a unique member given * and either want its children, its siblings, its parent, self, * ascendants, or descendants. */ if (restrictions.tree != null && (restrictions.tree & 8) != 8) { // Assumption 1: Treeop only uses Member if (restrictions.memberUniqueName == null) { throw new UnsupportedOperationException( "If a treeMask is given, we should also have a unique member name!"); } if ((restrictions.tree & 1) == 1) { // CHILDREN Olap4ldUtil._log.config("TreeOp:CHILDREN"); } if ((restrictions.tree & 2) == 2) { // SIBLINGS Olap4ldUtil._log.config("TreeOp:SIBLINGS"); } if ((restrictions.tree & 4) == 4) { // PARENT Olap4ldUtil._log.config("TreeOp:PARENT"); } if ((restrictions.tree & 16) == 16) { // DESCENDANTS Olap4ldUtil._log.config("TreeOp:DESCENDANTS"); } if ((restrictions.tree & 32) == 32) { // ANCESTORS Olap4ldUtil._log.config("TreeOp:ANCESTORS"); } throw new UnsupportedOperationException( "TreeOp and getLevelMember failed."); } else { // TreeOp = Self or null Olap4ldUtil._log.config("TreeOp:SELF"); // Here, we again need specific filters, since only cube makes // sense String specificFilters = ""; if (restrictions.cubeNamePattern != null) { specificFilters += " FILTER (?CUBE_NAME = <" + restrictions.cubeNamePattern + ">) "; } if (restrictions.dimensionUniqueName != null) { specificFilters += " FILTER (?DIMENSION_UNIQUE_NAME = <" + restrictions.dimensionUniqueName + ">) "; } // First, ask for all members // Get all members of hierarchies without levels, that simply // define // skos:hasTopConcept members with skos:notation. query = Olap4ldLinkedDataUtil.getStandardPrefixes() + "Select \"" + TABLE_CAT + "\" as ?CATALOG_NAME \"" + TABLE_SCHEM + "\" as ?SCHEMA_NAME ?CUBE_NAME ?DIMENSION_UNIQUE_NAME ?HIERARCHY_UNIQUE_NAME ?HIERARCHY_UNIQUE_NAME as ?LEVEL_UNIQUE_NAME \"0\" as ?LEVEL_NUMBER ?MEMBER_UNIQUE_NAME as ?MEMBER_NAME ?MEMBER_UNIQUE_NAME min(?MEMBER_CAPTION) as ?MEMBER_CAPTION \"1\" as ?MEMBER_TYPE \"null\" as ?PARENT_UNIQUE_NAME \"null\" as ?PARENT_LEVEL " + askForFrom(true) + " where { ?CUBE_NAME qb:component ?compSpec. " + "?compSpec qb:dimension ?DIMENSION_UNIQUE_NAME. ?DIMENSION_UNIQUE_NAME qb:codeList ?HIERARCHY_UNIQUE_NAME. ?HIERARCHY_UNIQUE_NAME skos:hasTopConcept ?MEMBER_UNIQUE_NAME. " + " OPTIONAL {?MEMBER_UNIQUE_NAME skos:notation ?MEMBER_CAPTION FILTER ( lang(?MEMBER_CAPTION) = \"en\" OR lang(?MEMBER_CAPTION) = \"\" ) } " + specificFilters + " } " + " group by ?CUBE_NAME ?DIMENSION_UNIQUE_NAME ?HIERARCHY_UNIQUE_NAME ?LEVEL_UNIQUE_NAME ?LEVEL_NUMBER ?MEMBER_UNIQUE_NAME order by ?CUBE_NAME ?DIMENSION_UNIQUE_NAME ?HIERARCHY_UNIQUE_NAME ?LEVEL_UNIQUE_NAME ?LEVEL_NUMBER ?MEMBER_UNIQUE_NAME"; List<Node[]> memberUris = sparql(query, true); return memberUris; } } /** * For degenerated dimensions, we have to assume that either dim, hier, or * level are given. * * @return */ private List<Node[]> getDegeneratedMembers(Restrictions restrictions) { if (restrictions.tree != null && (restrictions.tree & 8) != 8) { // Assumption 1: Treeop only uses Member if (restrictions.memberUniqueName == null) { throw new UnsupportedOperationException( "If a treeMask is given, we should also have a unique member name!"); } if ((restrictions.tree & 1) == 1) { // CHILDREN Olap4ldUtil._log.config("TreeOp:CHILDREN"); } if ((restrictions.tree & 2) == 2) { // SIBLINGS Olap4ldUtil._log.config("TreeOp:SIBLINGS"); } if ((restrictions.tree & 4) == 4) { // PARENT Olap4ldUtil._log.config("TreeOp:PARENT"); } if ((restrictions.tree & 16) == 16) { // DESCENDANTS Olap4ldUtil._log.config("TreeOp:DESCENDANTS"); } if ((restrictions.tree & 32) == 32) { // ANCESTORS Olap4ldUtil._log.config("TreeOp:ANCESTORS"); } throw new UnsupportedOperationException( "TreeOp and getLevelMember failed."); } else { // TreeOp = Self or null Olap4ldUtil._log.config("TreeOp:SELF"); // First we ask for the dimensionWithoutHierarchies String dimensionWithoutHierarchies = null; if (restrictions.dimensionUniqueName != null) { dimensionWithoutHierarchies = restrictions.dimensionUniqueName .toString(); } else if (restrictions.hierarchyUniqueName != null) { dimensionWithoutHierarchies = restrictions.hierarchyUniqueName .toString(); } else if (restrictions.levelUniqueName != null) { dimensionWithoutHierarchies = restrictions.levelUniqueName .toString(); } // Here, we need specific filters, since only cube and dimension // unique name makes sense String alteredadditionalFilters = ""; if (restrictions.cubeNamePattern != null) { alteredadditionalFilters += " FILTER (?CUBE_NAME = <" + restrictions.cubeNamePattern + ">) "; } if (restrictions.memberUniqueName != null) { // Check whether uri or Literal Node resource = restrictions.memberUniqueName; if (isResourceAndNotLiteral(resource)) { alteredadditionalFilters += " FILTER (?MEMBER_UNIQUE_NAME = <" + resource + ">) "; } else { alteredadditionalFilters += " FILTER (str(?MEMBER_UNIQUE_NAME) = \"" + resource + "\") "; } } // It is possible that dimensionWithoutHierarchies is null List<Node[]> memberUris1 = null; String query = null; // XXX: Necessary distinction? I think not... if (dimensionWithoutHierarchies == null) { query = Olap4ldLinkedDataUtil.getStandardPrefixes() + "Select distinct \"" + TABLE_CAT + "\" as ?CATALOG_NAME \"" + TABLE_SCHEM + "\" as ?SCHEMA_NAME ?CUBE_NAME " + " ?DIMENSION_UNIQUE_NAME " + " ?DIMENSION_UNIQUE_NAME as ?HIERARCHY_UNIQUE_NAME " + "?DIMENSION_UNIQUE_NAME as ?LEVEL_UNIQUE_NAME \"0\" as ?LEVEL_NUMBER ?MEMBER_UNIQUE_NAME as ?MEMBER_NAME ?MEMBER_UNIQUE_NAME min(?MEMBER_CAPTION) as ?MEMBER_CAPTION \"1\" as ?MEMBER_TYPE \"null\" as ?PARENT_UNIQUE_NAME \"null\" as ?PARENT_LEVEL " // Since we query for instances + askForFrom(true) + askForFrom(false) + " where { ?CUBE_NAME qb:component ?compSpec. " + "?compSpec qb:dimension ?DIMENSION_UNIQUE_NAME. " + "?obs qb:dataSet ?ds. ?ds qb:structure ?CUBE_NAME. " + "?obs ?DIMENSION_UNIQUE_NAME ?MEMBER_UNIQUE_NAME. " + " OPTIONAL {?MEMBER_UNIQUE_NAME skos:notation ?MEMBER_CAPTION FILTER ( lang(?MEMBER_CAPTION) = \"en\" OR lang(?MEMBER_CAPTION) = \"\" ) } " + alteredadditionalFilters + "} " + " group by ?CUBE_NAME ?DIMENSION_UNIQUE_NAME ?LEVEL_NUMBER ?MEMBER_UNIQUE_NAME order by ?CUBE_NAME ?LEVEL_NUMBER ?MEMBER_UNIQUE_NAME"; memberUris1 = sparql(query, true); } else { query = Olap4ldLinkedDataUtil.getStandardPrefixes() + "Select distinct \"" + TABLE_CAT + "\" as ?CATALOG_NAME \"" + TABLE_SCHEM + "\" as ?SCHEMA_NAME ?CUBE_NAME \"" + dimensionWithoutHierarchies + "\" as ?DIMENSION_UNIQUE_NAME \"" + dimensionWithoutHierarchies + "\" as ?HIERARCHY_UNIQUE_NAME \"" + dimensionWithoutHierarchies + "\" as ?LEVEL_UNIQUE_NAME \"0\" as ?LEVEL_NUMBER ?MEMBER_UNIQUE_NAME as ?MEMBER_NAME ?MEMBER_UNIQUE_NAME min(?MEMBER_CAPTION) as ?MEMBER_CAPTION \"1\" as ?MEMBER_TYPE \"null\" as ?PARENT_UNIQUE_NAME \"null\" as ?PARENT_LEVEL " // Since we query for instances + askForFrom(true) + askForFrom(false) + " where { ?CUBE_NAME qb:component ?compSpec. " + "?compSpec qb:dimension <" + dimensionWithoutHierarchies + ">. " + "?obs qb:dataSet ?ds. ?ds qb:structure ?CUBE_NAME. ?obs <" + dimensionWithoutHierarchies + "> ?MEMBER_UNIQUE_NAME. " + " OPTIONAL {?MEMBER_UNIQUE_NAME skos:notation ?MEMBER_CAPTION FILTER ( lang(?MEMBER_CAPTION) = \"en\" OR lang(?MEMBER_CAPTION) = \"\" ) } " + alteredadditionalFilters + "} " + " group by ?CUBE_NAME ?LEVEL_NUMBER ?MEMBER_UNIQUE_NAME order by ?CUBE_NAME ?LEVEL_NUMBER ?MEMBER_UNIQUE_NAME"; memberUris1 = sparql(query, true); } return memberUris1; } } private boolean isResourceAndNotLiteral(Node resource) { return resource.toString().startsWith("http:"); } private String createFilterForRestrictions(Restrictions restrictions) { // We need to create a filter for the specific restriction String cubeNamePatternFilter = (restrictions.cubeNamePattern != null) ? " FILTER (?CUBE_NAME = <" + restrictions.cubeNamePattern + ">) " : ""; String dimensionUniqueNameFilter = (restrictions.dimensionUniqueName != null) ? " FILTER (?DIMENSION_UNIQUE_NAME = <" + restrictions.dimensionUniqueName + ">) " : ""; String hierarchyUniqueNameFilter = (restrictions.hierarchyUniqueName != null) ? " FILTER (?HIERARCHY_UNIQUE_NAME = <" + restrictions.hierarchyUniqueName + ">) " : ""; String levelUniqueNameFilter = (restrictions.levelUniqueName != null) ? " FILTER (?LEVEL_UNIQUE_NAME = <" + restrictions.levelUniqueName + ">) " : ""; String memberUniqueNameFilter = (restrictions.memberUniqueName != null) ? " FILTER (str(?MEMBER_UNIQUE_NAME) = str(<" + restrictions.memberUniqueName + ">)) " : ""; return cubeNamePatternFilter + dimensionUniqueNameFilter + hierarchyUniqueNameFilter + levelUniqueNameFilter + memberUniqueNameFilter; } /** * Adds intermediary results to result. * * @param intermediaryresult * @param result */ private void addToResult(List<Node[]> intermediaryresult, List<Node[]> result) { boolean first = true; for (Node[] nodes : intermediaryresult) { if (first) { first = false; continue; } result.add(nodes); } } public List<Node[]> getSets(Restrictions restrictions) { // TODO Auto-generated method stub return null; } /** * Input: We get the queried cube. We get a list of levels of inquired * dimensions. Also a list of measures. And for each diced dimension a set * of members. * * @return The SPARQL output of the relational format of the * multidimensional result. */ public List<Node[]> executeOlapQuery(Cube cube, List<Level> slicesrollups, List<Position> dices, List<Measure> projections) { // cube is olap4ld Cube mycube = (Cube) cube; Node dsd = Olap4ldLinkedDataUtil .convertMDXtoURI(mycube.getUniqueName()); // Now, we have all neccessary data String selectClause = ""; String whereClause = ""; String groupByClause = "group by "; String orderByClause = "order by "; // Typically, a cube is a dataset (actually, it represents a cube, but, // anyway) whereClause += " ?obs qb:dataSet ?ds." + " ?ds qb:structure <" + dsd + ">."; // HashMaps for level height of dimension HashMap<Integer, Integer> levelHeightMap = new HashMap<Integer, Integer>(); for (Level level : slicesrollups) { // At the moment, we do not support hierarchy/roll-up, but simply // query for all dimension members // String hierarchyURI = LdOlap4jUtil.decodeUriForMdx(hierarchy // .getUniqueName()); Node dimensionProperty = Olap4ldLinkedDataUtil .convertMDXtoURI(level.getDimension().getUniqueName()); // XXX Why exactly do I include the level? Node levelURI = Olap4ldLinkedDataUtil.convertMDXtoURI(level .getUniqueName()); // Now, depending on level depth we need to behave differently // Slicer level is the number of property-value pairs from the most // granular value // For that, the maximum depth and the level depth are used. // TODO: THis is quite a simple approach, does not consider possible // access restrictions. Integer levelHeight = (level.getHierarchy().getLevels().size() - 1 - level .getDepth()); // Note as inserted. levelHeightMap.put(dimensionProperty.hashCode(), levelHeight); whereClause += addLevelPropertyPath(0, levelHeight, dimensionProperty, levelURI); Node dimensionPropertyVariable = makeUriToParameter(dimensionProperty); selectClause += " ?" + dimensionPropertyVariable + levelHeight + " "; groupByClause += " ?" + dimensionPropertyVariable + levelHeight + " "; orderByClause += " ?" + dimensionPropertyVariable + levelHeight + " "; } // For each filter tuple if (dices != null && !dices.isEmpty()) { // We assume that each position has the same metadata (i.e., Levels) // so that we only need to add graph patterns for the first position for (Member member : dices.get(0).getMembers()) { // We should be testing whether diceslevelHeight higher than // slicesRollupsLevelHeight // Dices Level Height Integer diceslevelHeight = (member.getHierarchy().getLevels() .size() - 1 - member.getLevel().getDepth()); Node dimensionProperty = Olap4ldLinkedDataUtil .convertMDXtoURI(member.getLevel().getDimension() .getUniqueName()); Integer slicesRollupsLevelHeight = (levelHeightMap .containsKey(dimensionProperty.hashCode())) ? levelHeightMap .get(dimensionProperty.hashCode()) : 0; if (member.getMemberType() != Member.Type.MEASURE && diceslevelHeight > slicesRollupsLevelHeight) { Node levelURI = Olap4ldLinkedDataUtil .convertMDXtoURI(member.getLevel().getUniqueName()); whereClause += addLevelPropertyPath( slicesRollupsLevelHeight, diceslevelHeight, dimensionProperty, levelURI); } } whereClause += "FILTER ("; List<String> orList = new ArrayList<String>(); for (Position position : dices) { // Each position is an OR List<String> andList = new ArrayList<String>(); for (Member member : position.getMembers()) { // We need to know the variable to filter // First, we need to convert it to URI representation. Node dimensionPropertyVariable = makeUriToParameter(Olap4ldLinkedDataUtil .convertMDXtoURI(member.getLevel().getDimension() .getUniqueName())); // We need to know the member to filter Node memberResource = Olap4ldLinkedDataUtil .convertMDXtoURI(member.getUniqueName()); // Need to know the level of the member Integer diceslevelHeight = (member.getHierarchy() .getLevels().size() - 1 - member.getLevel() .getDepth()); if (isResourceAndNotLiteral(memberResource)) { andList.add(" ?" + dimensionPropertyVariable + diceslevelHeight + " = " + "<" + memberResource + "> "); } else { // For some reason, we need to convert the variable // using str. andList.add(" str(?" + dimensionPropertyVariable + diceslevelHeight + ") = " + "\"" + memberResource + "\" "); } } orList.add(Olap4ldLinkedDataUtil.implodeArray( andList.toArray(new String[0]), " AND ")); } whereClause += Olap4ldLinkedDataUtil.implodeArray( orList.toArray(new String[0]), " OR "); whereClause += ") "; } // This huge thing probably tries to combine filter expressions to // efficient SPARQL expressions // // First, what dimension? // // If Measures, then do not filter (since the // // selectionpredicates also contain measures, since that was // // easier to do beforehand) // try { // if (position.get(0).getDimension().getDimensionType() // .equals(Dimension.Type.MEASURE)) { // continue; // } // } catch (OlapException e) { // // TODO Auto-generated catch block // e.printStackTrace(); // } // // // We assume that all contained members are of the same // // dimension and the same level // String slicerDimensionProperty = LdOlap4jUtil // .convertMDXtoURI(position.get(0).getDimension() // .getUniqueName()); // String slicerDimensionPropertyVariable = makeParameter(position // .get(0).getDimension().getUniqueName()); // // /* // * slicerLevelNumber would give me the difference between the // * maximal depth of all levels and the level depth (presumably // * the same for all tuples). This tells us the number of // * property-value pairs from the most granular element. // */ // Integer slicerLevelNumber = (position.get(0).getLevel() // .getHierarchy().getLevels().size() - 1 - position // .get(0).getLevel().getDepth()); // // /* // * Here, instead of going through all the members and filter for // * them one by one, we need to "compress" the filter expression // * somehow. // */ // // Check whether the leveltype is integer and define smallest // // and biggest // boolean isInteger = true; // int smallestTuple = 0; // int biggestTuple = 0; // int smallestLevelMember = 0; // int biggestLevelMember = 0; // try { // // Initialize // smallestTuple = Integer.parseInt(position.get(0) // .getUniqueName()); // biggestTuple = smallestTuple; // // Search for the smallest and the tallest member of // // slicerTuple // for (Member member : position) { // // int x = Integer.parseInt(member.getUniqueName()); // if (x < smallestTuple) { // smallestTuple = x; // } // if (x > biggestTuple) { // biggestTuple = x; // } // } // // Initialize // smallestLevelMember = smallestTuple; // biggestLevelMember = smallestTuple; // // Search for the smallest and the tallest member of all // // level // // members // try { // for (Member member : position.get(0).getLevel() // .getMembers()) { // int x = Integer.parseInt(member.getUniqueName()); // if (x < smallestLevelMember) { // smallestLevelMember = x; // } // if (x > biggestLevelMember) { // biggestLevelMember = x; // } // } // } catch (OlapException e) { // // TODO Auto-generated catch block // e.printStackTrace(); // } // // } catch (NumberFormatException nFE) { // isInteger = false; // } // // boolean isRestricted = false; // try { // for (Member levelMember : position.get(0).getLevel() // .getMembers()) { // if (isInteger) { // // Then we do not need to do this // break; // } // boolean isContained = false; // for (Member slicerMember : position) { // // if any member is not contained in the // // slicerTuple, it is restricted // if (levelMember.getUniqueName().equals( // slicerMember.getUniqueName())) { // isContained = true; // break; // } // } // if (!isContained) { // isRestricted = true; // break; // } // } // } catch (OlapException e) { // // TODO Auto-generated catch block // e.printStackTrace(); // } // // // Now, we create the string that shall be put between filter. // String filterstring = ""; // if (isInteger) { // // if (smallestTuple > smallestLevelMember) { // filterstring = "?" + slicerDimensionPropertyVariable // + " >= " + smallestTuple + " AND "; // } // if (biggestTuple < biggestLevelMember) { // filterstring += "?" + slicerDimensionPropertyVariable // + " <= " + biggestTuple; // } // // } else if (isRestricted) { // String[] slicerTupleArray = new String[position.size()]; // // for (int j = 0; j < slicerTupleArray.length; j++) { // // Member member = position.get(j); // String memberString = LdOlap4jUtil // .convertMDXtoURI(member.getUniqueName()); // // // Check whether member is uri or literal value // if (memberString.startsWith("http://")) { // memberString = "?" // + slicerDimensionPropertyVariable + " = <" // + memberString + "> "; // } else { // memberString = "?" // + slicerDimensionPropertyVariable + " = \"" // + memberString + "\" "; // } // slicerTupleArray[j] = memberString; // } // // filterstring = LdOlap4jUtil.implodeArray(slicerTupleArray, // " || "); // } else { // // Nothing to do, there is nothing to filter for. // break; // } // // // Only if this kind of level has not been selected, yet, we // // need to do it. // if (levelHeightMap.containsKey(slicerDimensionProperty // .hashCode())) { // query += " FILTER(" + filterstring + ")."; // // } else { // // // Since we are on a specific level, we need to add // // intermediary // // property-values // if (slicerLevelNumber == 0) { // query += "?obs <" + slicerDimensionProperty + "> ?" // + slicerDimensionPropertyVariable + " FILTER(" // + filterstring + ")."; // } else { // // Level path will start with ?obs and end with // // slicerConcept // // // First, what dimension? // // If Measures, then do not filter (since the // // selectionpredicates also contain measures, since that was // // easier to do beforehand) // try { // if (position.get(0).getDimension().getDimensionType() // .equals(Dimension.Type.MEASURE)) { // continue; // } // } catch (OlapException e) { // // TODO Auto-generated catch block // e.printStackTrace(); // } // // // We assume that all contained members are of the same // // dimension and the same level // String slicerDimensionProperty = LdOlap4jUtil // .convertMDXtoURI(position.get(0).getDimension() // .getUniqueName()); // String slicerDimensionPropertyVariable = makeParameter(position // .get(0).getDimension().getUniqueName()); // // /* // * slicerLevelNumber would give me the difference between the // * maximal depth of all levels and the level depth (presumably // * the same for all tuples). This tells us the number of // * property-value pairs from the most granular element. // */ // Integer slicerLevelNumber = (position.get(0).getLevel() // .getHierarchy().getLevels().size() - 1 - position // .get(0).getLevel().getDepth()); // // /* // * Here, instead of going through all the members and filter for // * them one by one, we need to "compress" the filter expression // * somehow. // */ // // Check whether the leveltype is integer and define smallest // // and biggest // boolean isInteger = true; // int smallestTuple = 0; // int biggestTuple = 0; // int smallestLevelMember = 0; // int biggestLevelMember = 0; // try { // // Initialize // smallestTuple = Integer.parseInt(position.get(0) // .getUniqueName()); // biggestTuple = smallestTuple; // // Search for the smallest and the tallest member of // // slicerTuple // for (Member member : position) { // // int x = Integer.parseInt(member.getUniqueName()); // if (x < smallestTuple) { // smallestTuple = x; // } // if (x > biggestTuple) { // biggestTuple = x; // } // } // // Initialize // smallestLevelMember = smallestTuple; // biggestLevelMember = smallestTuple; // // Search for the smallest and the tallest member of all // // level // // members // try { // for (Member member : position.get(0).getLevel() // .getMembers()) { // int x = Integer.parseInt(member.getUniqueName()); // if (x < smallestLevelMember) { // smallestLevelMember = x; // } // if (x > biggestLevelMember) { // biggestLevelMember = x; // } // } // } catch (OlapException e) { // // TODO Auto-generated catch block // e.printStackTrace(); // } // // } catch (NumberFormatException nFE) { // isInteger = false; // } // // boolean isRestricted = false; // try { // for (Member levelMember : position.get(0).getLevel() // .getMembers()) { // if (isInteger) { // // Then we do not need to do this // break; // } // boolean isContained = false; // for (Member slicerMember : position) { // // if any member is not contained in the // // slicerTuple, it is restricted // if (levelMember.getUniqueName().equals( // slicerMember.getUniqueName())) { // isContained = true; // break; // } // } // if (!isContained) { // isRestricted = true; // break; // } // } // } catch (OlapException e) { // // TODO Auto-generated catch block // e.printStackTrace(); // } // // // Now, we create the string that shall be put between filter. // String filterstring = ""; // if (isInteger) { // // if (smallestTuple > smallestLevelMember) { // filterstring = "?" + slicerDimensionPropertyVariable // + " >= " + smallestTuple + " AND "; // } // if (biggestTuple < biggestLevelMember) { // filterstring += "?" + slicerDimensionPropertyVariable // + " <= " + biggestTuple; // } // // } else if (isRestricted) { // String[] slicerTupleArray = new String[position.size()]; // // for (int j = 0; j < slicerTupleArray.length; j++) { // // Member member = position.get(j); // String memberString = LdOlap4jUtil // .convertMDXtoURI(member.getUniqueName()); // // // Check whether member is uri or literal value // if (memberString.startsWith("http://")) { // memberString = "?" // + slicerDimensionPropertyVariable + " = <" // + memberString + "> "; // } else { // memberString = "?" // + slicerDimensionPropertyVariable + " = \"" // + memberString + "\" "; // } // slicerTupleArray[j] = memberString; // } // // filterstring = LdOlap4jUtil.implodeArray(slicerTupleArray, // " || "); // } else { // // Nothing to do, there is nothing to filter for. // break; // } // // // Only if this kind of level has not been selected, yet, we // // need to do it. // if (levelHeightMap.containsKey(slicerDimensionProperty // .hashCode())) { // query += " FILTER(" + filterstring + ")."; // // } else { // // // Since we are on a specific level, we need to add // // intermediary // // property-values // if (slicerLevelNumber == 0) { // query += "?obs <" + slicerDimensionProperty + "> ?" // + slicerDimensionPropertyVariable + " FILTER(" // + filterstring + ")."; // } else { // // Level path will start with ?obs and end with // // slicerConcept // String levelPath = ""; // // for (int i = 0; i < slicerLevelNumber; i++) { // levelPath += " ?" + slicerDimensionPropertyVariable // + i + ". ?" // + slicerDimensionPropertyVariable + i // + " skos:narrower "; // } // // query += "?obs <" + slicerDimensionProperty + "> " // + levelPath + "?" // + slicerDimensionPropertyVariable + " FILTER(" // + filterstring + ")."; // } // } // } // } String levelPath = ""; // // for (int i = 0; i < slicerLevelNumber; i++) { // levelPath += " ?" + slicerDimensionPropertyVariable // + i + ". ?" // + slicerDimensionPropertyVariable + i // + " skos:narrower "; // } // // query += "?obs <" + slicerDimensionProperty + "> " // + levelPath + "?" // + slicerDimensionPropertyVariable + " FILTER(" // + filterstring + ")."; // } // } // } // } // Now, for each measure, we create a measure value column. // Any measure should be contained only once, unless calculated measures HashMap<Integer, Boolean> measureMap = new HashMap<Integer, Boolean>(); for (Measure measure : projections) { // For formulas, this is different if (measure.getAggregator().equals(Measure.Aggregator.CALCULATED)) { /* * For now, hard coded. Here, I also partly evaluate a query * string, thus, I could do the same with filters. */ Measure measure1 = (Measure) ((MemberNode) ((CallNode) measure .getExpression()).getArgList().get(0)).getMember(); Measure measure2 = (Measure) ((MemberNode) ((CallNode) measure .getExpression()).getArgList().get(1)).getMember(); String operatorName = ((CallNode) measure.getExpression()) .getOperatorName(); // Measure // Measure property has aggregator attached to it " avg". Node measureProperty1 = Olap4ldLinkedDataUtil .convertMDXtoURI(measure1.getUniqueName().replace( " " + measure.getAggregator().name(), "")); // We do not encode the aggregation function in the measure, any // more. Node measurePropertyVariable1 = makeUriToParameter(Olap4ldLinkedDataUtil .convertMDXtoURI(measure1.getUniqueName())); Node measureProperty2 = Olap4ldLinkedDataUtil .convertMDXtoURI(measure2.getUniqueName().replace( " " + measure.getAggregator().name(), "")); Node measurePropertyVariable2 = makeUriToParameter(Olap4ldLinkedDataUtil .convertMDXtoURI(measure2.getUniqueName())); // We take the aggregator from the measure selectClause += " " + measure1.getAggregator().name() + "(?" + measurePropertyVariable1 + " " + operatorName + " " + "?" + measurePropertyVariable2 + ")"; // I have to select them only, if we haven't inserted any // before. if (!measureMap.containsKey(measureProperty1.hashCode())) { whereClause += "?obs <" + measureProperty1 + "> ?" + measurePropertyVariable1 + ". "; measureMap.put(measureProperty1.hashCode(), true); } if (!measureMap.containsKey(measureProperty2.hashCode())) { whereClause += "?obs <" + measureProperty2 + "> ?" + measurePropertyVariable2 + ". "; measureMap.put(measureProperty2.hashCode(), true); } } else { // Measure Node measureProperty = Olap4ldLinkedDataUtil .convertMDXtoURI(measure.getUniqueName().replace( " " + measure.getAggregator().name(), "")); Node measurePropertyVariable = makeUriToParameter(Olap4ldLinkedDataUtil .convertMDXtoURI(measure.getUniqueName())); // We take the aggregator from the measure // Since we use OPTIONAL, there might be empty columns, which is // why we need // to convert them to decimal. selectClause += " " + measure.getAggregator().name() + "(xsd:decimal(?" + measurePropertyVariable + "))"; // Optional clause is used for the case that several measures // are selected. if (!measureMap.containsKey(measureProperty.hashCode())) { whereClause += "OPTIONAL { ?obs <" + measureProperty + "> ?" + measurePropertyVariable + ". }"; measureMap.put(measureProperty.hashCode(), true); } } } // Now that we consider DSD in queries, we need to consider DSD // locations String query = Olap4ldLinkedDataUtil.getStandardPrefixes() + "select " + selectClause + askForFrom(false) + askForFrom(true) + "where { " + whereClause + "}" + groupByClause + orderByClause; return sparql(query, true); } /** * This method returns graph patterns for a property path for levels. * * @param levelStart * startingPoint of level * @param levelHeight * endPoint of level * @param dimensionProperty * @param levelURI * @return */ private String addLevelPropertyPath(Integer levelStart, Integer levelHeight, Node dimensionProperty, Node levelURI) { String whereClause = ""; Node dimensionPropertyVariable = makeUriToParameter(dimensionProperty); if (levelHeight == 0) { /* * For now, we simply query for all and later match it to the pivot * table. A more performant way would be to filter only for those * dimension members, that are mentioned by any position. Should be * easy possible to do that if we simply ask each position for the * i'th position member. */ whereClause += "?obs <" + dimensionProperty + "> ?" + dimensionPropertyVariable + levelHeight + ". "; // If level height is 0, it could be that no level is existing which // is why we leave that // pattern out. } else { // Level path will start with ?obs and end with // slicerConcept String levelPath = ""; for (int i = levelStart; i < levelHeight; i++) { levelPath += " ?" + dimensionPropertyVariable + i + ". ?" + dimensionPropertyVariable + i + " skos:narrower "; } whereClause += "?obs <" + dimensionProperty + "> " + levelPath + "?" + dimensionPropertyVariable + levelHeight + ". "; // And this concept needs to be contained in the level. whereClause += "?" + dimensionPropertyVariable + levelHeight + " skos:member <" + levelURI + ">. "; // Removed part of hasTopConcept, since we know the members // already. } return whereClause; } /** * SPARQL does not allow all characters as parameter names, e.g., * ?sdmx-measure:obsValue. Therefore, we transform the URI representation * into a parameter. * * @param uriRepresentation * @return */ private Node makeUriToParameter(Node uriRepresentation) { // We simply remove all special characters return new Variable(uriRepresentation.toString().replaceAll( "[^a-zA-Z0-9]+", "")); } @Override public List<Node[]> executeOlapQuery(LogicalOlapQueryPlan queryplan) { // XXX: Will not work, since OV uses different syntax than Sesame LogicalToPhysical logicaltophysical = new LogicalToPhysical(null); PhysicalOlapIterator newRoot; newRoot = logicaltophysical.compile(queryplan._root); Olap4ldUtil._log.info("Physical query plan: " + newRoot); PhysicalOlapQueryPlan ap = new PhysicalOlapQueryPlan(newRoot); PhysicalOlapIterator resultIterator = ap.getIterator(); List<Node[]> result = new ArrayList<Node[]>(); while (resultIterator.hasNext()) { Object nextObject = resultIterator.next(); // Will be Node[] Node[] node = (Node[]) nextObject; result.add(node); } return result; } @Override public void rollback() { ; } public static void main(String[] args) { try { List<Node[]> myBindings = new ArrayList<Node[]>(); String query = "prefix surgipedia: <http://surgipedia.sfb125.de/wiki/Special:URIResolver/> SELECT * WHERE { ?opphase a surgipedia:Category-3AOperation_Phase . ?opphase rdfs:label ?label.} LIMIT 100 "; String querysuffix = "?query=" + URLEncoder.encode(query, "UTF-8"); String serverurl = "http://aifb-ls3-vm2.aifb.kit.edu:8890/sparql"; String fullurl = serverurl + querysuffix; HttpURLConnection con = (HttpURLConnection) new URL(fullurl) .openConnection(); con.setRequestProperty("Accept", "text/plain"); // con.setRequestProperty("Accept", "text/n3"); // con.setRequestProperty("Accept", // "application/sparql-results+xml"); con.setRequestMethod("POST"); if (con.getResponseCode() != 200) { throw new RuntimeException("lookup on " + fullurl + " resulted HTTP in status code " + con.getResponseCode()); } InputStream xml = con.getInputStream(); // // Transform sparql xml to nx // javax.xml.transform.TransformerFactory tf = // javax.xml.transform.TransformerFactory // .newInstance("net.sf.saxon.TransformerFactoryImpl", Thread // .currentThread().getContextClassLoader()); // // Transformer t; // // ByteArrayOutputStream baos = new ByteArrayOutputStream(); // // t = tf.newTransformer(new // StreamSource(Olap4ldLinkedDataUtil.class // .getResourceAsStream("/xml2nx.xsl"))); // // StreamSource ssource = new StreamSource(xml); // StreamResult sresult = new StreamResult(baos); // // t.transform(ssource, sresult); // // // We need to make InputStream out of OutputStream // ByteArrayInputStream nx = new ByteArrayInputStream( // baos.toByteArray()); // // // String test2 = // Olap4ldLinkedDataUtil.convertStreamToString(nx); // // nx.reset(); // // NxParser nxp = new NxParser(nx); NxParser nxp = new NxParser(xml); Node[] nxx; while (nxp.hasNext()) { System.out.println(); try { nxx = nxp.next(); myBindings.add(nxx); for (int i = 0; i < nxx.length; i++) { Node node = nxx[i]; System.out.print(node.toString() + "; "); } } catch (Exception e) { System.out.println("Could not parse properly."); } ; } } catch (UnsupportedEncodingException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (MalformedURLException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } @Override public PhysicalOlapQueryPlan getExecplan() throws OlapException { // TODO Auto-generated method stub return null; } @Override public List<Node[]> executeSparqlSelectQuery(String observationquery, boolean b) { // TODO Auto-generated method stub return null; } @Override public void executeSparqlConstructQuery(String constructquery) { // TODO Auto-generated method stub } }